commit 8bfc183b662a2b9e132253cbabd6b9db1c25158d Author: LeonMMcoset Date: Sun Oct 19 13:31:11 2025 +0000 first commit diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..b24fe4e --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +# same build output folder as fe v3 +build +dist-ssr +dev-dist +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.prettierignore b/.prettierignore new file mode 100755 index 0000000..0519ecb --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100755 index 0000000..94d737c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "printWidth": 120 +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100755 index 0000000..0d6babe --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default { + // other rules... + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +} +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/eslint.config.js b/eslint.config.js new file mode 100755 index 0000000..346cf21 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,18 @@ +import pluginReact from "eslint-plugin-react"; +import { defineConfig } from "eslint/config"; +import globals from "globals"; +import tseslint from "typescript-eslint"; + +export default defineConfig([ + { + files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], + languageOptions: { globals: globals.browser }, + settings: { + react: { + version: "detect", + }, + }, + }, + tseslint.configs.recommended, + pluginReact.configs.flat.recommended, +]); diff --git a/frontend-master.zip b/frontend-master.zip new file mode 100755 index 0000000..09a6da5 Binary files /dev/null and b/frontend-master.zip differ diff --git a/index.html b/index.html new file mode 100755 index 0000000..2cab0fb --- /dev/null +++ b/index.html @@ -0,0 +1,118 @@ + + + + + + + + + + {siteName} + + + + + +
+ +
+ + + + +
+
+ +
+ + + {siteScript} + diff --git a/package-lock.json b/package-lock.json new file mode 100755 index 0000000..49db0df --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3300 @@ +{ + "name": "cloudreve-pro-frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cloudreve-pro-frontend", + "version": "0.0.0", + "dependencies": { + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.15.1", + "@mui/material": "^5.15.1", + "@reduxjs/toolkit": "^2.0.1", + "axios": "^1.6.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-redux": "^9.0.4", + "react-router-dom": "^6.21.0", + "redux": "^5.0.0" + }, + "devDependencies": { + "@types/node": "^20.10.5", + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^8.55.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "prettier": "3.1.1", + "typescript": "^5.2.2", + "vite": "^5.0.8" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.6.tgz", + "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", + "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", + "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", + "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==", + "license": "MIT" + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.10.tgz", + "integrity": "sha512-YSRRs2zOpwypck+6GL3wGXx2gNP7DXzetmo5pHXLrY/VIMsS59yKfjPizQ4lLt5vEI80M41gjm2BxrGZ5U+VMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.2.tgz", + "integrity": "sha512-Ii3MrfY/GAIN3OhXNzpCKaLxHQfJF9qvwq/kEJYdqDxeIHa01K8sldugal6TmeeXl+WMvhv9cnVzUTaFFJF09A==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", + "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==", + "license": "MIT" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@mui/base": { + "version": "5.0.0-beta.28", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.28.tgz", + "integrity": "sha512-KIoSc5sUFceeCaZTq5MQBapFzhHqMo4kj+4azWaCAjorduhcRQtN+BCgVHmo+gvEjix74bUfxwTqGifnu2fNTg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.5", + "@floating-ui/react-dom": "^2.0.4", + "@mui/types": "^7.2.11", + "@mui/utils": "^5.15.1", + "@popperjs/core": "^2.11.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.1.tgz", + "integrity": "sha512-y/nUEsWHyBzaKYp9zLtqJKrLod/zMNEWpMj488FuQY9QTmqBiyUhI2uh7PVaLqLewXRtdmG6JV0b6T5exyuYRw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.1.tgz", + "integrity": "sha512-VPJdBSyap6uOxCb5BLbWbkvd6aeJCp1pQZm8DcZBITCH0NOSv8Mz9c8Zvo8xr4Od7+xyWHUAgvRSL4047pL2WQ==", + "dependencies": { + "@babel/runtime": "^7.23.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.1.tgz", + "integrity": "sha512-WA5DVyvacxDakVyAhNqu/rRT28ppuuUFFw1bLpmRzrCJ4uw/zLTATcd4WB3YbB+7MdZNEGG/SJNWTDLEIyn3xQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.5", + "@mui/base": "5.0.0-beta.28", + "@mui/core-downloads-tracker": "^5.15.1", + "@mui/system": "^5.15.1", + "@mui/types": "^7.2.11", + "@mui/utils": "^5.15.1", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.0.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1", + "react-is": "^18.2.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "license": "MIT" + }, + "node_modules/@mui/private-theming": { + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.1.tgz", + "integrity": "sha512-wTbzuy5KjSvCPE9UVJktWHJ0b/tD5biavY9wvF+OpYDLPpdXK52vc1hTDxSbdkHIFMkJExzrwO9GvpVAHZBnFQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.5", + "@mui/utils": "^5.15.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.1.tgz", + "integrity": "sha512-7WDZTJLqGexWDjqE9oAgjU8ak6hEtUw2yQU7SIYID5kLVO2Nj/Wi/KicbLsXnTsJNvSqePIlUIWTBSXwWJCPZw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.5", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.1.tgz", + "integrity": "sha512-LAnP0ls69rqW9eBgI29phIx/lppv+WDGI7b3EJN7VZIqw0RezA0GD7NRpV12BgEYJABEii6z5Q9B5tg7dsX0Iw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.5", + "@mui/private-theming": "^5.15.1", + "@mui/styled-engine": "^5.15.1", + "@mui/types": "^7.2.11", + "@mui/utils": "^5.15.1", + "clsx": "^2.0.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.11", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.11.tgz", + "integrity": "sha512-KWe/QTEsFFlFSH+qRYf3zoFEj3z67s+qAuSnMMg+gFwbxG7P96Hm6g300inQL1Wy///gSRb8juX7Wafvp93m3w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.1.tgz", + "integrity": "sha512-V1/d0E3Bju5YdB59HJf2G0tnHrFEvWLN+f8hAXp9+JSNy/LC2zKyqUfPPahflR6qsI681P8G9r4mEZte/SrrYA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.5", + "@types/prop-types": "^15.7.11", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.0.1.tgz", + "integrity": "sha512-fxIjrR9934cmS8YXIGd9e7s1XRsEU++aFc9DVNMFMRTM5Vtsg2DCRMj21eslGtDt43IUf9bJL3h5bwUlZleibA==", + "license": "MIT", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.0", + "redux-thunk": "^3.1.0", + "reselect": "^5.0.1" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@remix-run/router": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.0.tgz", + "integrity": "sha512-WOHih+ClN7N8oHk9N4JUiMxQJmRVaOxcg8w7F/oHUXzJt920ekASLI/7cYX8XkntDWRhLZtsk6LbGrkgOAvi5A==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.1.tgz", + "integrity": "sha512-LtYcLNM+bhsaKAIGwVkh5IOWhaZhjTfNOkGzGqdHvhiCUVuJDalvDxEdSnhFzAn+g23wgsycmZk1vbnaibZwwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@swc/core": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.101.tgz", + "integrity": "sha512-w5aQ9qYsd/IYmXADAnkXPGDMTqkQalIi+kfFf/MHRKTpaOL7DHjMXwPp/n8hJ0qNjRvchzmPtOqtPBiER50d8A==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.1", + "@swc/types": "^0.1.5" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.101", + "@swc/core-darwin-x64": "1.3.101", + "@swc/core-linux-arm-gnueabihf": "1.3.101", + "@swc/core-linux-arm64-gnu": "1.3.101", + "@swc/core-linux-arm64-musl": "1.3.101", + "@swc/core-linux-x64-gnu": "1.3.101", + "@swc/core-linux-x64-musl": "1.3.101", + "@swc/core-win32-arm64-msvc": "1.3.101", + "@swc/core-win32-ia32-msvc": "1.3.101", + "@swc/core-win32-x64-msvc": "1.3.101" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.101", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.101.tgz", + "integrity": "sha512-mNFK+uHNPRXSnfTOG34zJOeMl2waM4hF4a2NY7dkMXrPqw9CoJn4MwTXJcyMiSz1/BnNjjTCHF3Yhj0jPxmkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", + "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", + "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.2.45", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.45.tgz", + "integrity": "sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.18", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", + "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "license": "MIT" + }, + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.15.0.tgz", + "integrity": "sha512-j5qoikQqPccq9QoBAupOP+CBu8BaJ8BLjaXSioDISeTZkVO3ig7oSIKh3H+rEpee7xCXtWwSB4KIL5l6hWZzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/type-utils": "6.15.0", + "@typescript-eslint/utils": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.15.0.tgz", + "integrity": "sha512-MkgKNnsjC6QwcMdlNAel24jjkEO/0hQaMDLqP4S9zq5HBAUJNQB6y+3DwLjX7b3l2b37eNAxMPLwb3/kh8VKdA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/typescript-estree": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.15.0.tgz", + "integrity": "sha512-+BdvxYBltqrmgCNu4Li+fGDIkW9n//NrruzG9X1vBzaNK+ExVXPoGB71kneaVw/Jp+4rH/vaMAGC6JfMbHstVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.15.0.tgz", + "integrity": "sha512-CnmHKTfX6450Bo49hPg2OkIm/D/TVYV7jO1MCfPYGwf6x3GO0VU8YMO5AYMn+u3X05lRRxA4fWCz87GFQV6yVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "6.15.0", + "@typescript-eslint/utils": "6.15.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.15.0.tgz", + "integrity": "sha512-yXjbt//E4T/ee8Ia1b5mGlbNj9fB9lJP4jqLbZualwpP2BCQ5is6BcWwxpIsY4XKAhmdv3hrW92GdtJbatC6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.15.0.tgz", + "integrity": "sha512-7mVZJN7Hd15OmGuWrp2T9UvqR2Ecg+1j/Bp1jXUEY2GZKV6FXlOIoqVDmLpBiEiq3katvj/2n2mR0SDwtloCew==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.15.0.tgz", + "integrity": "sha512-eF82p0Wrrlt8fQSRL0bGXzK5nWPRV2dYQZdajcfzOD9+cQz9O7ugifrJxclB+xVOvWvagXfqS4Es7vpLP4augw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/typescript-estree": "6.15.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.15.0.tgz", + "integrity": "sha512-1zvtdC1a9h5Tb5jU9x3ADNXO9yjP8rXlaoChu0DQX40vf5ACVpYIVIZhIMZ6d5sDXH7vq4dsZBT1fEGj8D2n2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.15.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.5.0.tgz", + "integrity": "sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@swc/core": "^1.3.96" + }, + "peerDependencies": { + "vite": "^4 || ^5" + } + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.10.tgz", + "integrity": "sha512-S1Y27QGt/snkNYrRcswgRFqZjaTG5a5xM3EQo97uNBnH505pdzSNe/HLBq1v0RO7iK/ngdbhJB6mDAp0OK+iUA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.10", + "@esbuild/android-arm": "0.19.10", + "@esbuild/android-arm64": "0.19.10", + "@esbuild/android-x64": "0.19.10", + "@esbuild/darwin-arm64": "0.19.10", + "@esbuild/darwin-x64": "0.19.10", + "@esbuild/freebsd-arm64": "0.19.10", + "@esbuild/freebsd-x64": "0.19.10", + "@esbuild/linux-arm": "0.19.10", + "@esbuild/linux-arm64": "0.19.10", + "@esbuild/linux-ia32": "0.19.10", + "@esbuild/linux-loong64": "0.19.10", + "@esbuild/linux-mips64el": "0.19.10", + "@esbuild/linux-ppc64": "0.19.10", + "@esbuild/linux-riscv64": "0.19.10", + "@esbuild/linux-s390x": "0.19.10", + "@esbuild/linux-x64": "0.19.10", + "@esbuild/netbsd-x64": "0.19.10", + "@esbuild/openbsd-x64": "0.19.10", + "@esbuild/sunos-x64": "0.19.10", + "@esbuild/win32-arm64": "0.19.10", + "@esbuild/win32-ia32": "0.19.10", + "@esbuild/win32-x64": "0.19.10" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.5.tgz", + "integrity": "sha512-D53FYKJa+fDmZMtriODxvhwrO+IOqrxoEo21gMA0sjHdU6dPVH4OhyFip9ypl8HOF5RV5KdTo+rBQLvnY2cO8w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", + "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz", + "integrity": "sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", + "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", + "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-redux": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.0.4.tgz", + "integrity": "sha512-9J1xh8sWO0vYq2sCxK2My/QO7MzUMRi3rpiILP/+tDr8krBHixC6JMM17fMK88+Oh3e4Ae6/sHIhNBgkUivwFA==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "react-native": ">=0.69", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.0.tgz", + "integrity": "sha512-hGZ0HXbwz3zw52pLZV3j3+ec+m/PQ9cTpBvqjFQmy2XVUWGn5MD+31oXHb6dVTxYzmAeaiUBYjkoNz66n3RGCg==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.14.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.0.tgz", + "integrity": "sha512-1dUdVj3cwc1npzJaf23gulB562ESNvxf7E4x8upNJycqyUm5BRRZ6dd3LrlzhtLaMrwOCO8R0zoiYxdaJx4LlQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.14.0", + "react-router": "6.21.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/redux": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.0.tgz", + "integrity": "sha512-blLIYmYetpZMET6Q6uCY7Jtl/Im5OBldy+vNPauA8vvsdqyt66oep4EUpAMWNHauTC6xa9JuRPhRB72rY82QGA==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/reselect": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.0.1.tgz", + "integrity": "sha512-D72j2ubjgHpvuCiORWkOUxndHJrxDaSolheiz5CO+roz8ka97/4msh2E8F5qay4GawR5vzBt5MkbDHT+Rdy/Wg==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.1.tgz", + "integrity": "sha512-pgPO9DWzLoW/vIhlSoDByCzcpX92bKEorbgXuZrqxByte3JFk2xSW2JEeAcyLc9Ru9pqcNNW+Ob7ntsk2oT/Xw==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.9.1", + "@rollup/rollup-android-arm64": "4.9.1", + "@rollup/rollup-darwin-arm64": "4.9.1", + "@rollup/rollup-darwin-x64": "4.9.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.9.1", + "@rollup/rollup-linux-arm64-gnu": "4.9.1", + "@rollup/rollup-linux-arm64-musl": "4.9.1", + "@rollup/rollup-linux-riscv64-gnu": "4.9.1", + "@rollup/rollup-linux-x64-gnu": "4.9.1", + "@rollup/rollup-linux-x64-musl": "4.9.1", + "@rollup/rollup-win32-arm64-msvc": "4.9.1", + "@rollup/rollup-win32-ia32-msvc": "4.9.1", + "@rollup/rollup-win32-x64-msvc": "4.9.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/vite": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz", + "integrity": "sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.32", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100755 index 0000000..171230b --- /dev/null +++ b/package.json @@ -0,0 +1,119 @@ +{ + "name": "cloudreve-frontend", + "private": true, + "version": "4.0.0-next", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "build-prod": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview", + "format": "prettier --write .", + "format:check": "prettier --check .", + "prepare": "husky" + }, + "dependencies": { + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@excalidraw/excalidraw": "^0.18.0", + "@fontsource/roboto": "^5.0.8", + "@giscus/react": "^3.1.0", + "@iconify/react": "^6.0.0", + "@marsidev/react-turnstile": "^1.1.0", + "@mdxeditor/editor": "^3.39.0", + "@mui/icons-material": "^6.0.0", + "@mui/lab": "^6.0.0-beta.30", + "@mui/material": "^6.4.6", + "@mui/x-date-pickers": "^6.20.2", + "@mui/x-tree-view": "^6.17.0", + "@reduxjs/toolkit": "^2.0.1", + "@types/path-browserify": "^1.0.2", + "@types/streamsaver": "^2.0.4", + "@uiw/color-convert": "^2.1.1", + "@uiw/react-color-sketch": "^2.1.1", + "artplayer": "5.2.2", + "artplayer-plugin-chapter": "^1.0.0", + "artplayer-plugin-hls-control": "^1.0.1", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "fuse.js": "^7.0.0", + "heic-to": "^1.1.14", + "hls.js": "^1.6.2", + "i18next": "^23.7.11", + "i18next-browser-languagedetector": "^7.2.0", + "i18next-chained-backend": "^4.6.2", + "i18next-http-backend": "^2.4.2", + "i18next-localstorage-backend": "^4.2.0", + "leaflet": "^1.9.4", + "livephotoskit": "^1.5.6", + "lodash": "^4.17.21", + "mapbox-gl": "^3.15.0", + "material-ui-popup-state": "^5.0.10", + "monaco-editor": "^0.49.0", + "mpegts.js": "^1.8.0", + "mui-one-time-password-input": "^2.0.1", + "notistack": "^3.0.1", + "nuqs": "^2.3.1", + "path-browserify": "^1.0.1", + "pdfjs-dist": "4.10.38", + "qrcode.react": "^4.1.0", + "react": "^18.2.0", + "react-animate-height": "^3.2.3", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", + "react-dom": "^18.2.0", + "react-draggable": "^4.4.6", + "react-filerobot-image-editor": "^4.8.1", + "react-google-recaptcha": "^3.1.0", + "react-highlight-words": "^0.20.0", + "react-hotkeys-hook": "^4.5.1", + "react-i18next": "^14.0.0", + "react-image-crop": "^11.0.7", + "react-intersection-observer": "^9.5.3", + "react-konva": "^18.2.10", + "react-leaflet": "^4.2.1", + "react-reader": "^2.0.10", + "react-redux": "^9.0.4", + "react-router-dom": "^6.21.0", + "react-transition-group": "^4.4.5", + "react-virtuoso": "^4.10.4", + "recharts": "^2.15.1", + "redux": "^5.0.0", + "streamsaver": "^2.0.6", + "styled-components": "^6.1.11", + "timeago-react": "^3.0.6" + }, + "devDependencies": { + "@types/leaflet": "^1.9.12", + "@types/lodash": "^4.14.202", + "@types/node": "^20.10.5", + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@types/webappsec-credential-management": "^0.6.9", + "@types/wicg-file-system-access": "^2023.10.5", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^9.29.0", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "globals": "^16.2.0", + "husky": "^9.1.7", + "less": "^4.2.0", + "lint-staged": "^16.1.2", + "prettier": "3.1.1", + "typescript": "^5.2.2", + "typescript-eslint": "^8.34.1", + "vite": "5.4.6", + "vite-plugin-mkcert": "^1.17.6", + "vite-plugin-pwa": "^0.21.1", + "vite-plugin-static-copy": "^2.2.0" + }, + "lint-staged": { + "*.{js,jsx,ts,tsx,json,css,scss,md}": [ + "prettier --write" + ] + } +} diff --git a/public/assets/pdfjs/cmaps/78-EUC-H.bcmap b/public/assets/pdfjs/cmaps/78-EUC-H.bcmap new file mode 100755 index 0000000..2655fc7 Binary files /dev/null and b/public/assets/pdfjs/cmaps/78-EUC-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/78-EUC-V.bcmap b/public/assets/pdfjs/cmaps/78-EUC-V.bcmap new file mode 100755 index 0000000..f1ed853 Binary files /dev/null and b/public/assets/pdfjs/cmaps/78-EUC-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/78-H.bcmap b/public/assets/pdfjs/cmaps/78-H.bcmap new file mode 100755 index 0000000..39e89d3 Binary files /dev/null and b/public/assets/pdfjs/cmaps/78-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/78-RKSJ-H.bcmap b/public/assets/pdfjs/cmaps/78-RKSJ-H.bcmap new file mode 100755 index 0000000..e4167cb Binary files /dev/null and b/public/assets/pdfjs/cmaps/78-RKSJ-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/78-RKSJ-V.bcmap b/public/assets/pdfjs/cmaps/78-RKSJ-V.bcmap new file mode 100755 index 0000000..50b1646 Binary files /dev/null and b/public/assets/pdfjs/cmaps/78-RKSJ-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/78-V.bcmap b/public/assets/pdfjs/cmaps/78-V.bcmap new file mode 100755 index 0000000..d7af99b Binary files /dev/null and b/public/assets/pdfjs/cmaps/78-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/78ms-RKSJ-H.bcmap b/public/assets/pdfjs/cmaps/78ms-RKSJ-H.bcmap new file mode 100755 index 0000000..37077d0 Binary files /dev/null and b/public/assets/pdfjs/cmaps/78ms-RKSJ-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/78ms-RKSJ-V.bcmap b/public/assets/pdfjs/cmaps/78ms-RKSJ-V.bcmap new file mode 100755 index 0000000..acf2323 Binary files /dev/null and b/public/assets/pdfjs/cmaps/78ms-RKSJ-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/83pv-RKSJ-H.bcmap b/public/assets/pdfjs/cmaps/83pv-RKSJ-H.bcmap new file mode 100755 index 0000000..2359bc5 Binary files /dev/null and b/public/assets/pdfjs/cmaps/83pv-RKSJ-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/90ms-RKSJ-H.bcmap b/public/assets/pdfjs/cmaps/90ms-RKSJ-H.bcmap new file mode 100755 index 0000000..af82938 Binary files /dev/null and b/public/assets/pdfjs/cmaps/90ms-RKSJ-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/90ms-RKSJ-V.bcmap b/public/assets/pdfjs/cmaps/90ms-RKSJ-V.bcmap new file mode 100755 index 0000000..780549d Binary files /dev/null and b/public/assets/pdfjs/cmaps/90ms-RKSJ-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/90msp-RKSJ-H.bcmap b/public/assets/pdfjs/cmaps/90msp-RKSJ-H.bcmap new file mode 100755 index 0000000..bfd3119 Binary files /dev/null and b/public/assets/pdfjs/cmaps/90msp-RKSJ-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/90msp-RKSJ-V.bcmap b/public/assets/pdfjs/cmaps/90msp-RKSJ-V.bcmap new file mode 100755 index 0000000..25ef14a Binary files /dev/null and b/public/assets/pdfjs/cmaps/90msp-RKSJ-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/90pv-RKSJ-H.bcmap b/public/assets/pdfjs/cmaps/90pv-RKSJ-H.bcmap new file mode 100755 index 0000000..02f713b Binary files /dev/null and b/public/assets/pdfjs/cmaps/90pv-RKSJ-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/90pv-RKSJ-V.bcmap b/public/assets/pdfjs/cmaps/90pv-RKSJ-V.bcmap new file mode 100755 index 0000000..d08e0cc Binary files /dev/null and b/public/assets/pdfjs/cmaps/90pv-RKSJ-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Add-H.bcmap b/public/assets/pdfjs/cmaps/Add-H.bcmap new file mode 100755 index 0000000..59442ac Binary files /dev/null and b/public/assets/pdfjs/cmaps/Add-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Add-RKSJ-H.bcmap b/public/assets/pdfjs/cmaps/Add-RKSJ-H.bcmap new file mode 100755 index 0000000..a3065e4 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Add-RKSJ-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Add-RKSJ-V.bcmap b/public/assets/pdfjs/cmaps/Add-RKSJ-V.bcmap new file mode 100755 index 0000000..040014c Binary files /dev/null and b/public/assets/pdfjs/cmaps/Add-RKSJ-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Add-V.bcmap b/public/assets/pdfjs/cmaps/Add-V.bcmap new file mode 100755 index 0000000..2f816d3 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Add-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-CNS1-0.bcmap b/public/assets/pdfjs/cmaps/Adobe-CNS1-0.bcmap new file mode 100755 index 0000000..88ec04a Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-CNS1-0.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-CNS1-1.bcmap b/public/assets/pdfjs/cmaps/Adobe-CNS1-1.bcmap new file mode 100755 index 0000000..03a5014 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-CNS1-1.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-CNS1-2.bcmap b/public/assets/pdfjs/cmaps/Adobe-CNS1-2.bcmap new file mode 100755 index 0000000..2aa9514 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-CNS1-2.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-CNS1-3.bcmap b/public/assets/pdfjs/cmaps/Adobe-CNS1-3.bcmap new file mode 100755 index 0000000..86d8b8c Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-CNS1-3.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-CNS1-4.bcmap b/public/assets/pdfjs/cmaps/Adobe-CNS1-4.bcmap new file mode 100755 index 0000000..f50fc6c Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-CNS1-4.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-CNS1-5.bcmap b/public/assets/pdfjs/cmaps/Adobe-CNS1-5.bcmap new file mode 100755 index 0000000..6caf4a8 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-CNS1-5.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-CNS1-6.bcmap b/public/assets/pdfjs/cmaps/Adobe-CNS1-6.bcmap new file mode 100755 index 0000000..b77fb07 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-CNS1-6.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap b/public/assets/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap new file mode 100755 index 0000000..69d79a2 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-GB1-0.bcmap b/public/assets/pdfjs/cmaps/Adobe-GB1-0.bcmap new file mode 100755 index 0000000..3610108 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-GB1-0.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-GB1-1.bcmap b/public/assets/pdfjs/cmaps/Adobe-GB1-1.bcmap new file mode 100755 index 0000000..707bb10 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-GB1-1.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-GB1-2.bcmap b/public/assets/pdfjs/cmaps/Adobe-GB1-2.bcmap new file mode 100755 index 0000000..f7648cc Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-GB1-2.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-GB1-3.bcmap b/public/assets/pdfjs/cmaps/Adobe-GB1-3.bcmap new file mode 100755 index 0000000..8521458 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-GB1-3.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-GB1-4.bcmap b/public/assets/pdfjs/cmaps/Adobe-GB1-4.bcmap new file mode 100755 index 0000000..e40c63a Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-GB1-4.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-GB1-5.bcmap b/public/assets/pdfjs/cmaps/Adobe-GB1-5.bcmap new file mode 100755 index 0000000..d7623b5 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-GB1-5.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap b/public/assets/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap new file mode 100755 index 0000000..7586525 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-Japan1-0.bcmap b/public/assets/pdfjs/cmaps/Adobe-Japan1-0.bcmap new file mode 100755 index 0000000..f0e94ec Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-Japan1-0.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-Japan1-1.bcmap b/public/assets/pdfjs/cmaps/Adobe-Japan1-1.bcmap new file mode 100755 index 0000000..dad42c5 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-Japan1-1.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-Japan1-2.bcmap b/public/assets/pdfjs/cmaps/Adobe-Japan1-2.bcmap new file mode 100755 index 0000000..090819a Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-Japan1-2.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-Japan1-3.bcmap b/public/assets/pdfjs/cmaps/Adobe-Japan1-3.bcmap new file mode 100755 index 0000000..087dfc1 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-Japan1-3.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-Japan1-4.bcmap b/public/assets/pdfjs/cmaps/Adobe-Japan1-4.bcmap new file mode 100755 index 0000000..46aa9bf Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-Japan1-4.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-Japan1-5.bcmap b/public/assets/pdfjs/cmaps/Adobe-Japan1-5.bcmap new file mode 100755 index 0000000..5b4b65c Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-Japan1-5.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-Japan1-6.bcmap b/public/assets/pdfjs/cmaps/Adobe-Japan1-6.bcmap new file mode 100755 index 0000000..e77d699 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-Japan1-6.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap b/public/assets/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap new file mode 100755 index 0000000..128a141 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-Korea1-0.bcmap b/public/assets/pdfjs/cmaps/Adobe-Korea1-0.bcmap new file mode 100755 index 0000000..cef1a99 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-Korea1-0.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-Korea1-1.bcmap b/public/assets/pdfjs/cmaps/Adobe-Korea1-1.bcmap new file mode 100755 index 0000000..11ffa36 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-Korea1-1.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-Korea1-2.bcmap b/public/assets/pdfjs/cmaps/Adobe-Korea1-2.bcmap new file mode 100755 index 0000000..3172308 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-Korea1-2.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap b/public/assets/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap new file mode 100755 index 0000000..f3371c0 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap differ diff --git a/public/assets/pdfjs/cmaps/B5-H.bcmap b/public/assets/pdfjs/cmaps/B5-H.bcmap new file mode 100755 index 0000000..beb4d22 Binary files /dev/null and b/public/assets/pdfjs/cmaps/B5-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/B5-V.bcmap b/public/assets/pdfjs/cmaps/B5-V.bcmap new file mode 100755 index 0000000..2d4f87d Binary files /dev/null and b/public/assets/pdfjs/cmaps/B5-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/B5pc-H.bcmap b/public/assets/pdfjs/cmaps/B5pc-H.bcmap new file mode 100755 index 0000000..ce00131 Binary files /dev/null and b/public/assets/pdfjs/cmaps/B5pc-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/B5pc-V.bcmap b/public/assets/pdfjs/cmaps/B5pc-V.bcmap new file mode 100755 index 0000000..73b99ff Binary files /dev/null and b/public/assets/pdfjs/cmaps/B5pc-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/CNS-EUC-H.bcmap b/public/assets/pdfjs/cmaps/CNS-EUC-H.bcmap new file mode 100755 index 0000000..61d1d0c Binary files /dev/null and b/public/assets/pdfjs/cmaps/CNS-EUC-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/CNS-EUC-V.bcmap b/public/assets/pdfjs/cmaps/CNS-EUC-V.bcmap new file mode 100755 index 0000000..1a393a5 Binary files /dev/null and b/public/assets/pdfjs/cmaps/CNS-EUC-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/CNS1-H.bcmap b/public/assets/pdfjs/cmaps/CNS1-H.bcmap new file mode 100755 index 0000000..f738e21 Binary files /dev/null and b/public/assets/pdfjs/cmaps/CNS1-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/CNS1-V.bcmap b/public/assets/pdfjs/cmaps/CNS1-V.bcmap new file mode 100755 index 0000000..9c3169f Binary files /dev/null and b/public/assets/pdfjs/cmaps/CNS1-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/CNS2-H.bcmap b/public/assets/pdfjs/cmaps/CNS2-H.bcmap new file mode 100755 index 0000000..c89b352 Binary files /dev/null and b/public/assets/pdfjs/cmaps/CNS2-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/CNS2-V.bcmap b/public/assets/pdfjs/cmaps/CNS2-V.bcmap new file mode 100755 index 0000000..7588cec --- /dev/null +++ b/public/assets/pdfjs/cmaps/CNS2-V.bcmap @@ -0,0 +1,3 @@ +àRCopyright 1990-2009 Adobe Systems Incorporated. +All rights reserved. +See ./LICENSEáCNS2-H \ No newline at end of file diff --git a/public/assets/pdfjs/cmaps/ETHK-B5-H.bcmap b/public/assets/pdfjs/cmaps/ETHK-B5-H.bcmap new file mode 100755 index 0000000..cb29415 Binary files /dev/null and b/public/assets/pdfjs/cmaps/ETHK-B5-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/ETHK-B5-V.bcmap b/public/assets/pdfjs/cmaps/ETHK-B5-V.bcmap new file mode 100755 index 0000000..f09aec6 Binary files /dev/null and b/public/assets/pdfjs/cmaps/ETHK-B5-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/ETen-B5-H.bcmap b/public/assets/pdfjs/cmaps/ETen-B5-H.bcmap new file mode 100755 index 0000000..c2d7746 Binary files /dev/null and b/public/assets/pdfjs/cmaps/ETen-B5-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/ETen-B5-V.bcmap b/public/assets/pdfjs/cmaps/ETen-B5-V.bcmap new file mode 100755 index 0000000..89bff15 Binary files /dev/null and b/public/assets/pdfjs/cmaps/ETen-B5-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/ETenms-B5-H.bcmap b/public/assets/pdfjs/cmaps/ETenms-B5-H.bcmap new file mode 100755 index 0000000..a7d69db --- /dev/null +++ b/public/assets/pdfjs/cmaps/ETenms-B5-H.bcmap @@ -0,0 +1,3 @@ +àRCopyright 1990-2009 Adobe Systems Incorporated. +All rights reserved. +See ./LICENSEá ETen-B5-H` ^ \ No newline at end of file diff --git a/public/assets/pdfjs/cmaps/ETenms-B5-V.bcmap b/public/assets/pdfjs/cmaps/ETenms-B5-V.bcmap new file mode 100755 index 0000000..adc5d61 Binary files /dev/null and b/public/assets/pdfjs/cmaps/ETenms-B5-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/EUC-H.bcmap b/public/assets/pdfjs/cmaps/EUC-H.bcmap new file mode 100755 index 0000000..e92ea5b Binary files /dev/null and b/public/assets/pdfjs/cmaps/EUC-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/EUC-V.bcmap b/public/assets/pdfjs/cmaps/EUC-V.bcmap new file mode 100755 index 0000000..7a7c183 Binary files /dev/null and b/public/assets/pdfjs/cmaps/EUC-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Ext-H.bcmap b/public/assets/pdfjs/cmaps/Ext-H.bcmap new file mode 100755 index 0000000..3b5cde4 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Ext-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Ext-RKSJ-H.bcmap b/public/assets/pdfjs/cmaps/Ext-RKSJ-H.bcmap new file mode 100755 index 0000000..ea4d2d9 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Ext-RKSJ-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Ext-RKSJ-V.bcmap b/public/assets/pdfjs/cmaps/Ext-RKSJ-V.bcmap new file mode 100755 index 0000000..3457c27 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Ext-RKSJ-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Ext-V.bcmap b/public/assets/pdfjs/cmaps/Ext-V.bcmap new file mode 100755 index 0000000..4999ca4 Binary files /dev/null and b/public/assets/pdfjs/cmaps/Ext-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GB-EUC-H.bcmap b/public/assets/pdfjs/cmaps/GB-EUC-H.bcmap new file mode 100755 index 0000000..e39908b Binary files /dev/null and b/public/assets/pdfjs/cmaps/GB-EUC-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GB-EUC-V.bcmap b/public/assets/pdfjs/cmaps/GB-EUC-V.bcmap new file mode 100755 index 0000000..d5be544 Binary files /dev/null and b/public/assets/pdfjs/cmaps/GB-EUC-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GB-H.bcmap b/public/assets/pdfjs/cmaps/GB-H.bcmap new file mode 100755 index 0000000..39189c5 --- /dev/null +++ b/public/assets/pdfjs/cmaps/GB-H.bcmap @@ -0,0 +1,4 @@ +àRCopyright 1990-2009 Adobe Systems Incorporated. +All rights reserved. +See ./LICENSE!!º]aX!!]`21> p z$]‚"R‚d-Uƒ7*„ 4„%+ „Z „{/…%…<9K…b1]†."‡ ‰`]‡,"]ˆ +"]ˆh"]‰F"]Š$"]‹"]‹`"]Œ>"]"]z"]ŽX"]6"]"]r"]‘P"]’."]“ "]“j"]”H"]•&"]–"]–b"]—@"]˜"]˜|"]™Z"]š8"]›"]›t"]œR"]0"]ž"]žl"]ŸJ"] ("]¡"]¡d"]¢B"]£ "X£~']¤W"]¥5"]¦"]¦q"]§O"]¨-"]© "]©i"]ªG"]«%"]¬"]¬a"]­?"]®"]®{"]¯Y"]°7"]±"]±s"]²Q"]³/"]´ "]´k"]µI"]¶'"]·"]·c"]¸A"]¹"]¹}"]º["]»9 \ No newline at end of file diff --git a/public/assets/pdfjs/cmaps/GB-V.bcmap b/public/assets/pdfjs/cmaps/GB-V.bcmap new file mode 100755 index 0000000..3108345 Binary files /dev/null and b/public/assets/pdfjs/cmaps/GB-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GBK-EUC-H.bcmap b/public/assets/pdfjs/cmaps/GBK-EUC-H.bcmap new file mode 100755 index 0000000..05fff7e Binary files /dev/null and b/public/assets/pdfjs/cmaps/GBK-EUC-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GBK-EUC-V.bcmap b/public/assets/pdfjs/cmaps/GBK-EUC-V.bcmap new file mode 100755 index 0000000..0cdf6be Binary files /dev/null and b/public/assets/pdfjs/cmaps/GBK-EUC-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GBK2K-H.bcmap b/public/assets/pdfjs/cmaps/GBK2K-H.bcmap new file mode 100755 index 0000000..46f6ba5 Binary files /dev/null and b/public/assets/pdfjs/cmaps/GBK2K-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GBK2K-V.bcmap b/public/assets/pdfjs/cmaps/GBK2K-V.bcmap new file mode 100755 index 0000000..d9a9479 Binary files /dev/null and b/public/assets/pdfjs/cmaps/GBK2K-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GBKp-EUC-H.bcmap b/public/assets/pdfjs/cmaps/GBKp-EUC-H.bcmap new file mode 100755 index 0000000..5cb0af6 Binary files /dev/null and b/public/assets/pdfjs/cmaps/GBKp-EUC-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GBKp-EUC-V.bcmap b/public/assets/pdfjs/cmaps/GBKp-EUC-V.bcmap new file mode 100755 index 0000000..bca93b8 Binary files /dev/null and b/public/assets/pdfjs/cmaps/GBKp-EUC-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GBT-EUC-H.bcmap b/public/assets/pdfjs/cmaps/GBT-EUC-H.bcmap new file mode 100755 index 0000000..4b4e2d3 Binary files /dev/null and b/public/assets/pdfjs/cmaps/GBT-EUC-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GBT-EUC-V.bcmap b/public/assets/pdfjs/cmaps/GBT-EUC-V.bcmap new file mode 100755 index 0000000..38f7066 Binary files /dev/null and b/public/assets/pdfjs/cmaps/GBT-EUC-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GBT-H.bcmap b/public/assets/pdfjs/cmaps/GBT-H.bcmap new file mode 100755 index 0000000..8437ac3 Binary files /dev/null and b/public/assets/pdfjs/cmaps/GBT-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GBT-V.bcmap b/public/assets/pdfjs/cmaps/GBT-V.bcmap new file mode 100755 index 0000000..697ab4a Binary files /dev/null and b/public/assets/pdfjs/cmaps/GBT-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GBTpc-EUC-H.bcmap b/public/assets/pdfjs/cmaps/GBTpc-EUC-H.bcmap new file mode 100755 index 0000000..f6e50e8 Binary files /dev/null and b/public/assets/pdfjs/cmaps/GBTpc-EUC-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GBTpc-EUC-V.bcmap b/public/assets/pdfjs/cmaps/GBTpc-EUC-V.bcmap new file mode 100755 index 0000000..6c0d71a Binary files /dev/null and b/public/assets/pdfjs/cmaps/GBTpc-EUC-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GBpc-EUC-H.bcmap b/public/assets/pdfjs/cmaps/GBpc-EUC-H.bcmap new file mode 100755 index 0000000..c9edf67 Binary files /dev/null and b/public/assets/pdfjs/cmaps/GBpc-EUC-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/GBpc-EUC-V.bcmap b/public/assets/pdfjs/cmaps/GBpc-EUC-V.bcmap new file mode 100755 index 0000000..31450c9 Binary files /dev/null and b/public/assets/pdfjs/cmaps/GBpc-EUC-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/H.bcmap b/public/assets/pdfjs/cmaps/H.bcmap new file mode 100755 index 0000000..7b24ea4 Binary files /dev/null and b/public/assets/pdfjs/cmaps/H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/HKdla-B5-H.bcmap b/public/assets/pdfjs/cmaps/HKdla-B5-H.bcmap new file mode 100755 index 0000000..7d30c05 Binary files /dev/null and b/public/assets/pdfjs/cmaps/HKdla-B5-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/HKdla-B5-V.bcmap b/public/assets/pdfjs/cmaps/HKdla-B5-V.bcmap new file mode 100755 index 0000000..7894694 Binary files /dev/null and b/public/assets/pdfjs/cmaps/HKdla-B5-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/HKdlb-B5-H.bcmap b/public/assets/pdfjs/cmaps/HKdlb-B5-H.bcmap new file mode 100755 index 0000000..d829a23 Binary files /dev/null and b/public/assets/pdfjs/cmaps/HKdlb-B5-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/HKdlb-B5-V.bcmap b/public/assets/pdfjs/cmaps/HKdlb-B5-V.bcmap new file mode 100755 index 0000000..2b572b5 Binary files /dev/null and b/public/assets/pdfjs/cmaps/HKdlb-B5-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/HKgccs-B5-H.bcmap b/public/assets/pdfjs/cmaps/HKgccs-B5-H.bcmap new file mode 100755 index 0000000..971a4f2 Binary files /dev/null and b/public/assets/pdfjs/cmaps/HKgccs-B5-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/HKgccs-B5-V.bcmap b/public/assets/pdfjs/cmaps/HKgccs-B5-V.bcmap new file mode 100755 index 0000000..d353ca2 Binary files /dev/null and b/public/assets/pdfjs/cmaps/HKgccs-B5-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/HKm314-B5-H.bcmap b/public/assets/pdfjs/cmaps/HKm314-B5-H.bcmap new file mode 100755 index 0000000..576dc01 Binary files /dev/null and b/public/assets/pdfjs/cmaps/HKm314-B5-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/HKm314-B5-V.bcmap b/public/assets/pdfjs/cmaps/HKm314-B5-V.bcmap new file mode 100755 index 0000000..0e96d0e Binary files /dev/null and b/public/assets/pdfjs/cmaps/HKm314-B5-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/HKm471-B5-H.bcmap b/public/assets/pdfjs/cmaps/HKm471-B5-H.bcmap new file mode 100755 index 0000000..11d170c Binary files /dev/null and b/public/assets/pdfjs/cmaps/HKm471-B5-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/HKm471-B5-V.bcmap b/public/assets/pdfjs/cmaps/HKm471-B5-V.bcmap new file mode 100755 index 0000000..54959bf Binary files /dev/null and b/public/assets/pdfjs/cmaps/HKm471-B5-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/HKscs-B5-H.bcmap b/public/assets/pdfjs/cmaps/HKscs-B5-H.bcmap new file mode 100755 index 0000000..6ef7857 Binary files /dev/null and b/public/assets/pdfjs/cmaps/HKscs-B5-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/HKscs-B5-V.bcmap b/public/assets/pdfjs/cmaps/HKscs-B5-V.bcmap new file mode 100755 index 0000000..1fb2fa2 Binary files /dev/null and b/public/assets/pdfjs/cmaps/HKscs-B5-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Hankaku.bcmap b/public/assets/pdfjs/cmaps/Hankaku.bcmap new file mode 100755 index 0000000..4b8ec7f Binary files /dev/null and b/public/assets/pdfjs/cmaps/Hankaku.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Hiragana.bcmap b/public/assets/pdfjs/cmaps/Hiragana.bcmap new file mode 100755 index 0000000..17e983e Binary files /dev/null and b/public/assets/pdfjs/cmaps/Hiragana.bcmap differ diff --git a/public/assets/pdfjs/cmaps/KSC-EUC-H.bcmap b/public/assets/pdfjs/cmaps/KSC-EUC-H.bcmap new file mode 100755 index 0000000..a45c65f Binary files /dev/null and b/public/assets/pdfjs/cmaps/KSC-EUC-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/KSC-EUC-V.bcmap b/public/assets/pdfjs/cmaps/KSC-EUC-V.bcmap new file mode 100755 index 0000000..0e7b21f Binary files /dev/null and b/public/assets/pdfjs/cmaps/KSC-EUC-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/KSC-H.bcmap b/public/assets/pdfjs/cmaps/KSC-H.bcmap new file mode 100755 index 0000000..b9b22b6 Binary files /dev/null and b/public/assets/pdfjs/cmaps/KSC-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/KSC-Johab-H.bcmap b/public/assets/pdfjs/cmaps/KSC-Johab-H.bcmap new file mode 100755 index 0000000..2531ffc Binary files /dev/null and b/public/assets/pdfjs/cmaps/KSC-Johab-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/KSC-Johab-V.bcmap b/public/assets/pdfjs/cmaps/KSC-Johab-V.bcmap new file mode 100755 index 0000000..367ceb2 Binary files /dev/null and b/public/assets/pdfjs/cmaps/KSC-Johab-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/KSC-V.bcmap b/public/assets/pdfjs/cmaps/KSC-V.bcmap new file mode 100755 index 0000000..6ae2f0b Binary files /dev/null and b/public/assets/pdfjs/cmaps/KSC-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/KSCms-UHC-H.bcmap b/public/assets/pdfjs/cmaps/KSCms-UHC-H.bcmap new file mode 100755 index 0000000..a8d4240 Binary files /dev/null and b/public/assets/pdfjs/cmaps/KSCms-UHC-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap b/public/assets/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap new file mode 100755 index 0000000..8b4ae18 Binary files /dev/null and b/public/assets/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap b/public/assets/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap new file mode 100755 index 0000000..b655dbc Binary files /dev/null and b/public/assets/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/KSCms-UHC-V.bcmap b/public/assets/pdfjs/cmaps/KSCms-UHC-V.bcmap new file mode 100755 index 0000000..21f97f6 Binary files /dev/null and b/public/assets/pdfjs/cmaps/KSCms-UHC-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/KSCpc-EUC-H.bcmap b/public/assets/pdfjs/cmaps/KSCpc-EUC-H.bcmap new file mode 100755 index 0000000..e06f361 Binary files /dev/null and b/public/assets/pdfjs/cmaps/KSCpc-EUC-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/KSCpc-EUC-V.bcmap b/public/assets/pdfjs/cmaps/KSCpc-EUC-V.bcmap new file mode 100755 index 0000000..f3c9113 Binary files /dev/null and b/public/assets/pdfjs/cmaps/KSCpc-EUC-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Katakana.bcmap b/public/assets/pdfjs/cmaps/Katakana.bcmap new file mode 100755 index 0000000..524303c Binary files /dev/null and b/public/assets/pdfjs/cmaps/Katakana.bcmap differ diff --git a/public/assets/pdfjs/cmaps/LICENSE b/public/assets/pdfjs/cmaps/LICENSE new file mode 100755 index 0000000..b1ad168 --- /dev/null +++ b/public/assets/pdfjs/cmaps/LICENSE @@ -0,0 +1,36 @@ +%%Copyright: ----------------------------------------------------------- +%%Copyright: Copyright 1990-2009 Adobe Systems Incorporated. +%%Copyright: All rights reserved. +%%Copyright: +%%Copyright: Redistribution and use in source and binary forms, with or +%%Copyright: without modification, are permitted provided that the +%%Copyright: following conditions are met: +%%Copyright: +%%Copyright: Redistributions of source code must retain the above +%%Copyright: copyright notice, this list of conditions and the following +%%Copyright: disclaimer. +%%Copyright: +%%Copyright: Redistributions in binary form must reproduce the above +%%Copyright: copyright notice, this list of conditions and the following +%%Copyright: disclaimer in the documentation and/or other materials +%%Copyright: provided with the distribution. +%%Copyright: +%%Copyright: Neither the name of Adobe Systems Incorporated nor the names +%%Copyright: of its contributors may be used to endorse or promote +%%Copyright: products derived from this software without specific prior +%%Copyright: written permission. +%%Copyright: +%%Copyright: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +%%Copyright: CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +%%Copyright: INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +%%Copyright: MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +%%Copyright: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +%%Copyright: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +%%Copyright: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +%%Copyright: NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +%%Copyright: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +%%Copyright: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +%%Copyright: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +%%Copyright: OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +%%Copyright: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +%%Copyright: ----------------------------------------------------------- diff --git a/public/assets/pdfjs/cmaps/NWP-H.bcmap b/public/assets/pdfjs/cmaps/NWP-H.bcmap new file mode 100755 index 0000000..afc5e4b Binary files /dev/null and b/public/assets/pdfjs/cmaps/NWP-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/NWP-V.bcmap b/public/assets/pdfjs/cmaps/NWP-V.bcmap new file mode 100755 index 0000000..bb5785e Binary files /dev/null and b/public/assets/pdfjs/cmaps/NWP-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/RKSJ-H.bcmap b/public/assets/pdfjs/cmaps/RKSJ-H.bcmap new file mode 100755 index 0000000..fb8d298 Binary files /dev/null and b/public/assets/pdfjs/cmaps/RKSJ-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/RKSJ-V.bcmap b/public/assets/pdfjs/cmaps/RKSJ-V.bcmap new file mode 100755 index 0000000..a2555a6 Binary files /dev/null and b/public/assets/pdfjs/cmaps/RKSJ-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/Roman.bcmap b/public/assets/pdfjs/cmaps/Roman.bcmap new file mode 100755 index 0000000..f896dcf Binary files /dev/null and b/public/assets/pdfjs/cmaps/Roman.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniCNS-UCS2-H.bcmap b/public/assets/pdfjs/cmaps/UniCNS-UCS2-H.bcmap new file mode 100755 index 0000000..d5db27c Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniCNS-UCS2-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniCNS-UCS2-V.bcmap b/public/assets/pdfjs/cmaps/UniCNS-UCS2-V.bcmap new file mode 100755 index 0000000..1dc9b7a Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniCNS-UCS2-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniCNS-UTF16-H.bcmap b/public/assets/pdfjs/cmaps/UniCNS-UTF16-H.bcmap new file mode 100755 index 0000000..961afef Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniCNS-UTF16-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniCNS-UTF16-V.bcmap b/public/assets/pdfjs/cmaps/UniCNS-UTF16-V.bcmap new file mode 100755 index 0000000..df0cffe Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniCNS-UTF16-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniCNS-UTF32-H.bcmap b/public/assets/pdfjs/cmaps/UniCNS-UTF32-H.bcmap new file mode 100755 index 0000000..1ab18a1 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniCNS-UTF32-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniCNS-UTF32-V.bcmap b/public/assets/pdfjs/cmaps/UniCNS-UTF32-V.bcmap new file mode 100755 index 0000000..ad14662 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniCNS-UTF32-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniCNS-UTF8-H.bcmap b/public/assets/pdfjs/cmaps/UniCNS-UTF8-H.bcmap new file mode 100755 index 0000000..83c6bd7 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniCNS-UTF8-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniCNS-UTF8-V.bcmap b/public/assets/pdfjs/cmaps/UniCNS-UTF8-V.bcmap new file mode 100755 index 0000000..22a27e4 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniCNS-UTF8-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniGB-UCS2-H.bcmap b/public/assets/pdfjs/cmaps/UniGB-UCS2-H.bcmap new file mode 100755 index 0000000..5bd6228 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniGB-UCS2-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniGB-UCS2-V.bcmap b/public/assets/pdfjs/cmaps/UniGB-UCS2-V.bcmap new file mode 100755 index 0000000..53c534b Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniGB-UCS2-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniGB-UTF16-H.bcmap b/public/assets/pdfjs/cmaps/UniGB-UTF16-H.bcmap new file mode 100755 index 0000000..b95045b Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniGB-UTF16-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniGB-UTF16-V.bcmap b/public/assets/pdfjs/cmaps/UniGB-UTF16-V.bcmap new file mode 100755 index 0000000..51f023e Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniGB-UTF16-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniGB-UTF32-H.bcmap b/public/assets/pdfjs/cmaps/UniGB-UTF32-H.bcmap new file mode 100755 index 0000000..f0dbd14 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniGB-UTF32-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniGB-UTF32-V.bcmap b/public/assets/pdfjs/cmaps/UniGB-UTF32-V.bcmap new file mode 100755 index 0000000..ce9c30a Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniGB-UTF32-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniGB-UTF8-H.bcmap b/public/assets/pdfjs/cmaps/UniGB-UTF8-H.bcmap new file mode 100755 index 0000000..982ca46 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniGB-UTF8-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniGB-UTF8-V.bcmap b/public/assets/pdfjs/cmaps/UniGB-UTF8-V.bcmap new file mode 100755 index 0000000..f78020d Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniGB-UTF8-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJIS-UCS2-H.bcmap b/public/assets/pdfjs/cmaps/UniJIS-UCS2-H.bcmap new file mode 100755 index 0000000..7daf56a Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJIS-UCS2-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap b/public/assets/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap new file mode 100755 index 0000000..ac9975c Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap b/public/assets/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap new file mode 100755 index 0000000..3da0a1c Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJIS-UCS2-V.bcmap b/public/assets/pdfjs/cmaps/UniJIS-UCS2-V.bcmap new file mode 100755 index 0000000..c50b9dd Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJIS-UCS2-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJIS-UTF16-H.bcmap b/public/assets/pdfjs/cmaps/UniJIS-UTF16-H.bcmap new file mode 100755 index 0000000..6761344 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJIS-UTF16-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJIS-UTF16-V.bcmap b/public/assets/pdfjs/cmaps/UniJIS-UTF16-V.bcmap new file mode 100755 index 0000000..70bf90c Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJIS-UTF16-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJIS-UTF32-H.bcmap b/public/assets/pdfjs/cmaps/UniJIS-UTF32-H.bcmap new file mode 100755 index 0000000..7a83d53 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJIS-UTF32-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJIS-UTF32-V.bcmap b/public/assets/pdfjs/cmaps/UniJIS-UTF32-V.bcmap new file mode 100755 index 0000000..7a87135 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJIS-UTF32-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJIS-UTF8-H.bcmap b/public/assets/pdfjs/cmaps/UniJIS-UTF8-H.bcmap new file mode 100755 index 0000000..9f0334c Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJIS-UTF8-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJIS-UTF8-V.bcmap b/public/assets/pdfjs/cmaps/UniJIS-UTF8-V.bcmap new file mode 100755 index 0000000..808a94f Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJIS-UTF8-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap b/public/assets/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap new file mode 100755 index 0000000..d768bf8 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap b/public/assets/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap new file mode 100755 index 0000000..3d5bf6f Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap b/public/assets/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap new file mode 100755 index 0000000..09eee10 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap b/public/assets/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap new file mode 100755 index 0000000..6c54600 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap b/public/assets/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap new file mode 100755 index 0000000..1b1a64f Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap b/public/assets/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap new file mode 100755 index 0000000..994aa9e Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap b/public/assets/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap new file mode 100755 index 0000000..643f921 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap b/public/assets/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap new file mode 100755 index 0000000..c148f67 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap b/public/assets/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap new file mode 100755 index 0000000..1849d80 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap b/public/assets/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap new file mode 100755 index 0000000..a83a677 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap b/public/assets/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap new file mode 100755 index 0000000..f527248 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap b/public/assets/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap new file mode 100755 index 0000000..e1a988d Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap b/public/assets/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap new file mode 100755 index 0000000..47e054a Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniKS-UCS2-H.bcmap b/public/assets/pdfjs/cmaps/UniKS-UCS2-H.bcmap new file mode 100755 index 0000000..b5b9485 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniKS-UCS2-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniKS-UCS2-V.bcmap b/public/assets/pdfjs/cmaps/UniKS-UCS2-V.bcmap new file mode 100755 index 0000000..026adca Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniKS-UCS2-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniKS-UTF16-H.bcmap b/public/assets/pdfjs/cmaps/UniKS-UTF16-H.bcmap new file mode 100755 index 0000000..fd4e66e Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniKS-UTF16-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniKS-UTF16-V.bcmap b/public/assets/pdfjs/cmaps/UniKS-UTF16-V.bcmap new file mode 100755 index 0000000..075efb7 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniKS-UTF16-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniKS-UTF32-H.bcmap b/public/assets/pdfjs/cmaps/UniKS-UTF32-H.bcmap new file mode 100755 index 0000000..769d214 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniKS-UTF32-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniKS-UTF32-V.bcmap b/public/assets/pdfjs/cmaps/UniKS-UTF32-V.bcmap new file mode 100755 index 0000000..bdab208 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniKS-UTF32-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniKS-UTF8-H.bcmap b/public/assets/pdfjs/cmaps/UniKS-UTF8-H.bcmap new file mode 100755 index 0000000..6ff8674 Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniKS-UTF8-H.bcmap differ diff --git a/public/assets/pdfjs/cmaps/UniKS-UTF8-V.bcmap b/public/assets/pdfjs/cmaps/UniKS-UTF8-V.bcmap new file mode 100755 index 0000000..8dfa76a Binary files /dev/null and b/public/assets/pdfjs/cmaps/UniKS-UTF8-V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/V.bcmap b/public/assets/pdfjs/cmaps/V.bcmap new file mode 100755 index 0000000..fdec990 Binary files /dev/null and b/public/assets/pdfjs/cmaps/V.bcmap differ diff --git a/public/assets/pdfjs/cmaps/WP-Symbol.bcmap b/public/assets/pdfjs/cmaps/WP-Symbol.bcmap new file mode 100755 index 0000000..46729bb Binary files /dev/null and b/public/assets/pdfjs/cmaps/WP-Symbol.bcmap differ diff --git a/public/assets/pdfjs/images/altText_add.svg b/public/assets/pdfjs/images/altText_add.svg new file mode 100755 index 0000000..3451b53 --- /dev/null +++ b/public/assets/pdfjs/images/altText_add.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/altText_disclaimer.svg b/public/assets/pdfjs/images/altText_disclaimer.svg new file mode 100755 index 0000000..6fe79e7 --- /dev/null +++ b/public/assets/pdfjs/images/altText_disclaimer.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/altText_done.svg b/public/assets/pdfjs/images/altText_done.svg new file mode 100755 index 0000000..f54924e --- /dev/null +++ b/public/assets/pdfjs/images/altText_done.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/altText_spinner.svg b/public/assets/pdfjs/images/altText_spinner.svg new file mode 100755 index 0000000..fedb472 --- /dev/null +++ b/public/assets/pdfjs/images/altText_spinner.svg @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/public/assets/pdfjs/images/altText_warning.svg b/public/assets/pdfjs/images/altText_warning.svg new file mode 100755 index 0000000..03014ce --- /dev/null +++ b/public/assets/pdfjs/images/altText_warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/annotation-check.svg b/public/assets/pdfjs/images/annotation-check.svg new file mode 100755 index 0000000..71cd16d --- /dev/null +++ b/public/assets/pdfjs/images/annotation-check.svg @@ -0,0 +1,11 @@ + + + + diff --git a/public/assets/pdfjs/images/annotation-comment.svg b/public/assets/pdfjs/images/annotation-comment.svg new file mode 100755 index 0000000..86f1f17 --- /dev/null +++ b/public/assets/pdfjs/images/annotation-comment.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/public/assets/pdfjs/images/annotation-help.svg b/public/assets/pdfjs/images/annotation-help.svg new file mode 100755 index 0000000..00938fe --- /dev/null +++ b/public/assets/pdfjs/images/annotation-help.svg @@ -0,0 +1,26 @@ + + + + + + + + + + diff --git a/public/assets/pdfjs/images/annotation-insert.svg b/public/assets/pdfjs/images/annotation-insert.svg new file mode 100755 index 0000000..519ef68 --- /dev/null +++ b/public/assets/pdfjs/images/annotation-insert.svg @@ -0,0 +1,10 @@ + + + + diff --git a/public/assets/pdfjs/images/annotation-key.svg b/public/assets/pdfjs/images/annotation-key.svg new file mode 100755 index 0000000..8d09d53 --- /dev/null +++ b/public/assets/pdfjs/images/annotation-key.svg @@ -0,0 +1,11 @@ + + + + diff --git a/public/assets/pdfjs/images/annotation-newparagraph.svg b/public/assets/pdfjs/images/annotation-newparagraph.svg new file mode 100755 index 0000000..38d2497 --- /dev/null +++ b/public/assets/pdfjs/images/annotation-newparagraph.svg @@ -0,0 +1,11 @@ + + + + diff --git a/public/assets/pdfjs/images/annotation-noicon.svg b/public/assets/pdfjs/images/annotation-noicon.svg new file mode 100755 index 0000000..c07d108 --- /dev/null +++ b/public/assets/pdfjs/images/annotation-noicon.svg @@ -0,0 +1,7 @@ + + + diff --git a/public/assets/pdfjs/images/annotation-note.svg b/public/assets/pdfjs/images/annotation-note.svg new file mode 100755 index 0000000..7017365 --- /dev/null +++ b/public/assets/pdfjs/images/annotation-note.svg @@ -0,0 +1,42 @@ + + + + + + + + diff --git a/public/assets/pdfjs/images/annotation-paperclip.svg b/public/assets/pdfjs/images/annotation-paperclip.svg new file mode 100755 index 0000000..2bed225 --- /dev/null +++ b/public/assets/pdfjs/images/annotation-paperclip.svg @@ -0,0 +1,6 @@ + + + + diff --git a/public/assets/pdfjs/images/annotation-paragraph.svg b/public/assets/pdfjs/images/annotation-paragraph.svg new file mode 100755 index 0000000..6ae5212 --- /dev/null +++ b/public/assets/pdfjs/images/annotation-paragraph.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/public/assets/pdfjs/images/annotation-pushpin.svg b/public/assets/pdfjs/images/annotation-pushpin.svg new file mode 100755 index 0000000..6e0896c --- /dev/null +++ b/public/assets/pdfjs/images/annotation-pushpin.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/public/assets/pdfjs/images/cursor-editorFreeHighlight.svg b/public/assets/pdfjs/images/cursor-editorFreeHighlight.svg new file mode 100755 index 0000000..513f6bd --- /dev/null +++ b/public/assets/pdfjs/images/cursor-editorFreeHighlight.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/assets/pdfjs/images/cursor-editorFreeText.svg b/public/assets/pdfjs/images/cursor-editorFreeText.svg new file mode 100755 index 0000000..de2838e --- /dev/null +++ b/public/assets/pdfjs/images/cursor-editorFreeText.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/cursor-editorInk.svg b/public/assets/pdfjs/images/cursor-editorInk.svg new file mode 100755 index 0000000..1dadb5c --- /dev/null +++ b/public/assets/pdfjs/images/cursor-editorInk.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/pdfjs/images/cursor-editorTextHighlight.svg b/public/assets/pdfjs/images/cursor-editorTextHighlight.svg new file mode 100755 index 0000000..800340c --- /dev/null +++ b/public/assets/pdfjs/images/cursor-editorTextHighlight.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/public/assets/pdfjs/images/editor-toolbar-delete.svg b/public/assets/pdfjs/images/editor-toolbar-delete.svg new file mode 100755 index 0000000..f84520d --- /dev/null +++ b/public/assets/pdfjs/images/editor-toolbar-delete.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/public/assets/pdfjs/images/findbarButton-next.svg b/public/assets/pdfjs/images/findbarButton-next.svg new file mode 100755 index 0000000..8cb39be --- /dev/null +++ b/public/assets/pdfjs/images/findbarButton-next.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/findbarButton-previous.svg b/public/assets/pdfjs/images/findbarButton-previous.svg new file mode 100755 index 0000000..b610879 --- /dev/null +++ b/public/assets/pdfjs/images/findbarButton-previous.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/gv-toolbarButton-download.svg b/public/assets/pdfjs/images/gv-toolbarButton-download.svg new file mode 100755 index 0000000..d56cf3c --- /dev/null +++ b/public/assets/pdfjs/images/gv-toolbarButton-download.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/loading-icon.gif b/public/assets/pdfjs/images/loading-icon.gif new file mode 100755 index 0000000..1c72ebb Binary files /dev/null and b/public/assets/pdfjs/images/loading-icon.gif differ diff --git a/public/assets/pdfjs/images/loading.svg b/public/assets/pdfjs/images/loading.svg new file mode 100755 index 0000000..0a15ff6 --- /dev/null +++ b/public/assets/pdfjs/images/loading.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/pdfjs/images/messageBar_closingButton.svg b/public/assets/pdfjs/images/messageBar_closingButton.svg new file mode 100755 index 0000000..8a40715 --- /dev/null +++ b/public/assets/pdfjs/images/messageBar_closingButton.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/messageBar_warning.svg b/public/assets/pdfjs/images/messageBar_warning.svg new file mode 100755 index 0000000..011cfcf --- /dev/null +++ b/public/assets/pdfjs/images/messageBar_warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/secondaryToolbarButton-documentProperties.svg b/public/assets/pdfjs/images/secondaryToolbarButton-documentProperties.svg new file mode 100755 index 0000000..dd3917b --- /dev/null +++ b/public/assets/pdfjs/images/secondaryToolbarButton-documentProperties.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/secondaryToolbarButton-firstPage.svg b/public/assets/pdfjs/images/secondaryToolbarButton-firstPage.svg new file mode 100755 index 0000000..f5c917f --- /dev/null +++ b/public/assets/pdfjs/images/secondaryToolbarButton-firstPage.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/secondaryToolbarButton-handTool.svg b/public/assets/pdfjs/images/secondaryToolbarButton-handTool.svg new file mode 100755 index 0000000..b7073b5 --- /dev/null +++ b/public/assets/pdfjs/images/secondaryToolbarButton-handTool.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/secondaryToolbarButton-lastPage.svg b/public/assets/pdfjs/images/secondaryToolbarButton-lastPage.svg new file mode 100755 index 0000000..c04f650 --- /dev/null +++ b/public/assets/pdfjs/images/secondaryToolbarButton-lastPage.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/secondaryToolbarButton-rotateCcw.svg b/public/assets/pdfjs/images/secondaryToolbarButton-rotateCcw.svg new file mode 100755 index 0000000..da73a1b --- /dev/null +++ b/public/assets/pdfjs/images/secondaryToolbarButton-rotateCcw.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/secondaryToolbarButton-rotateCw.svg b/public/assets/pdfjs/images/secondaryToolbarButton-rotateCw.svg new file mode 100755 index 0000000..c41ce73 --- /dev/null +++ b/public/assets/pdfjs/images/secondaryToolbarButton-rotateCw.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/secondaryToolbarButton-scrollHorizontal.svg b/public/assets/pdfjs/images/secondaryToolbarButton-scrollHorizontal.svg new file mode 100755 index 0000000..fb440b9 --- /dev/null +++ b/public/assets/pdfjs/images/secondaryToolbarButton-scrollHorizontal.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/secondaryToolbarButton-scrollPage.svg b/public/assets/pdfjs/images/secondaryToolbarButton-scrollPage.svg new file mode 100755 index 0000000..64a9f50 --- /dev/null +++ b/public/assets/pdfjs/images/secondaryToolbarButton-scrollPage.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/secondaryToolbarButton-scrollVertical.svg b/public/assets/pdfjs/images/secondaryToolbarButton-scrollVertical.svg new file mode 100755 index 0000000..dc7e805 --- /dev/null +++ b/public/assets/pdfjs/images/secondaryToolbarButton-scrollVertical.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/secondaryToolbarButton-scrollWrapped.svg b/public/assets/pdfjs/images/secondaryToolbarButton-scrollWrapped.svg new file mode 100755 index 0000000..75fe26b --- /dev/null +++ b/public/assets/pdfjs/images/secondaryToolbarButton-scrollWrapped.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/secondaryToolbarButton-selectTool.svg b/public/assets/pdfjs/images/secondaryToolbarButton-selectTool.svg new file mode 100755 index 0000000..94d5141 --- /dev/null +++ b/public/assets/pdfjs/images/secondaryToolbarButton-selectTool.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/secondaryToolbarButton-spreadEven.svg b/public/assets/pdfjs/images/secondaryToolbarButton-spreadEven.svg new file mode 100755 index 0000000..ce201e3 --- /dev/null +++ b/public/assets/pdfjs/images/secondaryToolbarButton-spreadEven.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/secondaryToolbarButton-spreadNone.svg b/public/assets/pdfjs/images/secondaryToolbarButton-spreadNone.svg new file mode 100755 index 0000000..e8d487f --- /dev/null +++ b/public/assets/pdfjs/images/secondaryToolbarButton-spreadNone.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/secondaryToolbarButton-spreadOdd.svg b/public/assets/pdfjs/images/secondaryToolbarButton-spreadOdd.svg new file mode 100755 index 0000000..9211a42 --- /dev/null +++ b/public/assets/pdfjs/images/secondaryToolbarButton-spreadOdd.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-bookmark.svg b/public/assets/pdfjs/images/toolbarButton-bookmark.svg new file mode 100755 index 0000000..c4c37c9 --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-bookmark.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-currentOutlineItem.svg b/public/assets/pdfjs/images/toolbarButton-currentOutlineItem.svg new file mode 100755 index 0000000..01e6762 --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-currentOutlineItem.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-download.svg b/public/assets/pdfjs/images/toolbarButton-download.svg new file mode 100755 index 0000000..e2e850a --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-download.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/pdfjs/images/toolbarButton-editorFreeText.svg b/public/assets/pdfjs/images/toolbarButton-editorFreeText.svg new file mode 100755 index 0000000..13a67bd --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-editorFreeText.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/pdfjs/images/toolbarButton-editorHighlight.svg b/public/assets/pdfjs/images/toolbarButton-editorHighlight.svg new file mode 100755 index 0000000..b3cd7fd --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-editorHighlight.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/public/assets/pdfjs/images/toolbarButton-editorInk.svg b/public/assets/pdfjs/images/toolbarButton-editorInk.svg new file mode 100755 index 0000000..b579eec --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-editorInk.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/pdfjs/images/toolbarButton-editorStamp.svg b/public/assets/pdfjs/images/toolbarButton-editorStamp.svg new file mode 100755 index 0000000..a1fef49 --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-editorStamp.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/public/assets/pdfjs/images/toolbarButton-menuArrow.svg b/public/assets/pdfjs/images/toolbarButton-menuArrow.svg new file mode 100755 index 0000000..82ffeaa --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-menuArrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-openFile.svg b/public/assets/pdfjs/images/toolbarButton-openFile.svg new file mode 100755 index 0000000..e773781 --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-openFile.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-pageDown.svg b/public/assets/pdfjs/images/toolbarButton-pageDown.svg new file mode 100755 index 0000000..1fc12e7 --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-pageDown.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-pageUp.svg b/public/assets/pdfjs/images/toolbarButton-pageUp.svg new file mode 100755 index 0000000..0936b9a --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-pageUp.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-presentationMode.svg b/public/assets/pdfjs/images/toolbarButton-presentationMode.svg new file mode 100755 index 0000000..901d567 --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-presentationMode.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-print.svg b/public/assets/pdfjs/images/toolbarButton-print.svg new file mode 100755 index 0000000..97a3904 --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-print.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-search.svg b/public/assets/pdfjs/images/toolbarButton-search.svg new file mode 100755 index 0000000..0cc7ae2 --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-search.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-secondaryToolbarToggle.svg b/public/assets/pdfjs/images/toolbarButton-secondaryToolbarToggle.svg new file mode 100755 index 0000000..cace863 --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-secondaryToolbarToggle.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-sidebarToggle.svg b/public/assets/pdfjs/images/toolbarButton-sidebarToggle.svg new file mode 100755 index 0000000..1d8d0e4 --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-sidebarToggle.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-viewAttachments.svg b/public/assets/pdfjs/images/toolbarButton-viewAttachments.svg new file mode 100755 index 0000000..ab73f6e --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-viewAttachments.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-viewLayers.svg b/public/assets/pdfjs/images/toolbarButton-viewLayers.svg new file mode 100755 index 0000000..1d72668 --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-viewLayers.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-viewOutline.svg b/public/assets/pdfjs/images/toolbarButton-viewOutline.svg new file mode 100755 index 0000000..7ed1bd9 --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-viewOutline.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-viewThumbnail.svg b/public/assets/pdfjs/images/toolbarButton-viewThumbnail.svg new file mode 100755 index 0000000..040d123 --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-viewThumbnail.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-zoomIn.svg b/public/assets/pdfjs/images/toolbarButton-zoomIn.svg new file mode 100755 index 0000000..30ec51a --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-zoomIn.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/toolbarButton-zoomOut.svg b/public/assets/pdfjs/images/toolbarButton-zoomOut.svg new file mode 100755 index 0000000..f273b59 --- /dev/null +++ b/public/assets/pdfjs/images/toolbarButton-zoomOut.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/pdfjs/images/treeitem-collapsed.svg b/public/assets/pdfjs/images/treeitem-collapsed.svg new file mode 100755 index 0000000..831cddf --- /dev/null +++ b/public/assets/pdfjs/images/treeitem-collapsed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/pdfjs/images/treeitem-expanded.svg b/public/assets/pdfjs/images/treeitem-expanded.svg new file mode 100755 index 0000000..2d45f0c --- /dev/null +++ b/public/assets/pdfjs/images/treeitem-expanded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/pdfjs/locale/ach/viewer.ftl b/public/assets/pdfjs/locale/ach/viewer.ftl new file mode 100755 index 0000000..36769b7 --- /dev/null +++ b/public/assets/pdfjs/locale/ach/viewer.ftl @@ -0,0 +1,225 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Pot buk mukato +pdfjs-previous-button-label = Mukato +pdfjs-next-button = + .title = Pot buk malubo +pdfjs-next-button-label = Malubo +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Pot buk +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = pi { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } me { $pagesCount }) +pdfjs-zoom-out-button = + .title = Jwik Matidi +pdfjs-zoom-out-button-label = Jwik Matidi +pdfjs-zoom-in-button = + .title = Kwot Madit +pdfjs-zoom-in-button-label = Kwot Madit +pdfjs-zoom-select = + .title = Kwoti +pdfjs-presentation-mode-button = + .title = Lokke i kit me tyer +pdfjs-presentation-mode-button-label = Kit me tyer +pdfjs-open-file-button = + .title = Yab Pwail +pdfjs-open-file-button-label = Yab +pdfjs-print-button = + .title = Go +pdfjs-print-button-label = Go + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Gintic +pdfjs-tools-button-label = Gintic +pdfjs-first-page-button = + .title = Cit i pot buk mukwongo +pdfjs-first-page-button-label = Cit i pot buk mukwongo +pdfjs-last-page-button = + .title = Cit i pot buk magiko +pdfjs-last-page-button-label = Cit i pot buk magiko +pdfjs-page-rotate-cw-button = + .title = Wire i tung lacuc +pdfjs-page-rotate-cw-button-label = Wire i tung lacuc +pdfjs-page-rotate-ccw-button = + .title = Wire i tung lacam +pdfjs-page-rotate-ccw-button-label = Wire i tung lacam +pdfjs-cursor-text-select-tool-button = + .title = Cak gitic me yero coc +pdfjs-cursor-text-select-tool-button-label = Gitic me yero coc +pdfjs-cursor-hand-tool-button = + .title = Cak gitic me cing +pdfjs-cursor-hand-tool-button-label = Gitic cing + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Jami me gin acoya… +pdfjs-document-properties-button-label = Jami me gin acoya… +pdfjs-document-properties-file-name = Nying pwail: +pdfjs-document-properties-file-size = Dit pa pwail: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Wiye: +pdfjs-document-properties-author = Ngat mucoyo: +pdfjs-document-properties-subject = Subjek: +pdfjs-document-properties-keywords = Lok mapire tek: +pdfjs-document-properties-creation-date = Nino dwe me cwec: +pdfjs-document-properties-modification-date = Nino dwe me yub: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Lacwec: +pdfjs-document-properties-producer = Layub PDF: +pdfjs-document-properties-version = Kit PDF: +pdfjs-document-properties-page-count = Kwan me pot buk: +pdfjs-document-properties-page-size = Dit pa potbuk: +pdfjs-document-properties-page-size-unit-inches = i +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = atir +pdfjs-document-properties-page-size-orientation-landscape = arii +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Waraga +pdfjs-document-properties-page-size-name-legal = Cik + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +pdfjs-document-properties-linearized-yes = Eyo +pdfjs-document-properties-linearized-no = Pe +pdfjs-document-properties-close-button = Lor + +## Print + +pdfjs-print-progress-message = Yubo coc me agoya… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Juki +pdfjs-printing-not-supported = Ciko: Layeny ma pe teno goyo liweng. +pdfjs-printing-not-ready = Ciko: PDF pe ocane weng me agoya. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Lok gintic ma inget +pdfjs-toggle-sidebar-button-label = Lok gintic ma inget +pdfjs-document-outline-button = + .title = Nyut Wiyewiye me Gin acoya (dii-kiryo me yaro/kano jami weng) +pdfjs-document-outline-button-label = Pek pa gin acoya +pdfjs-attachments-button = + .title = Nyut twec +pdfjs-attachments-button-label = Twec +pdfjs-thumbs-button = + .title = Nyut cal +pdfjs-thumbs-button-label = Cal +pdfjs-findbar-button = + .title = Nong iye gin acoya +pdfjs-findbar-button-label = Nong + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Pot buk { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Cal me pot buk { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Nong + .placeholder = Nong i dokumen… +pdfjs-find-previous-button = + .title = Nong timme pa lok mukato +pdfjs-find-previous-button-label = Mukato +pdfjs-find-next-button = + .title = Nong timme pa lok malubo +pdfjs-find-next-button-label = Malubo +pdfjs-find-highlight-checkbox = Ket Lanyut I Weng +pdfjs-find-match-case-checkbox-label = Lok marwate +pdfjs-find-reached-top = Oo iwi gin acoya, omede ki i tere +pdfjs-find-reached-bottom = Oo i agiki me gin acoya, omede ki iwiye +pdfjs-find-not-found = Lok pe ononge + +## Predefined zoom values + +pdfjs-page-scale-width = Lac me iye pot buk +pdfjs-page-scale-fit = Porre me pot buk +pdfjs-page-scale-auto = Kwot pire kene +pdfjs-page-scale-actual = Dite kikome +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = Bal otime kun cano PDF. +pdfjs-invalid-file-error = Pwail me PDF ma pe atir onyo obale woko. +pdfjs-missing-file-error = Pwail me PDF tye ka rem. +pdfjs-unexpected-response-error = Lagam mape kigeno pa lapok tic. +pdfjs-rendering-error = Bal otime i kare me nyuto pot buk. + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Lok angea manok] + +## Password + +pdfjs-password-label = Ket mung me donyo me yabo pwail me PDF man. +pdfjs-password-invalid = Mung me donyo pe atir. Tim ber i tem doki. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Juki +pdfjs-web-fonts-disabled = Kijuko dit pa coc me kakube woko: pe romo tic ki dit pa coc me PDF ma kiketo i kine. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/af/viewer.ftl b/public/assets/pdfjs/locale/af/viewer.ftl new file mode 100755 index 0000000..7c4346f --- /dev/null +++ b/public/assets/pdfjs/locale/af/viewer.ftl @@ -0,0 +1,212 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Vorige bladsy +pdfjs-previous-button-label = Vorige +pdfjs-next-button = + .title = Volgende bladsy +pdfjs-next-button-label = Volgende +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Bladsy +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = van { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } van { $pagesCount }) +pdfjs-zoom-out-button = + .title = Zoem uit +pdfjs-zoom-out-button-label = Zoem uit +pdfjs-zoom-in-button = + .title = Zoem in +pdfjs-zoom-in-button-label = Zoem in +pdfjs-zoom-select = + .title = Zoem +pdfjs-presentation-mode-button = + .title = Wissel na voorleggingsmodus +pdfjs-presentation-mode-button-label = Voorleggingsmodus +pdfjs-open-file-button = + .title = Open lêer +pdfjs-open-file-button-label = Open +pdfjs-print-button = + .title = Druk +pdfjs-print-button-label = Druk + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Nutsgoed +pdfjs-tools-button-label = Nutsgoed +pdfjs-first-page-button = + .title = Gaan na eerste bladsy +pdfjs-first-page-button-label = Gaan na eerste bladsy +pdfjs-last-page-button = + .title = Gaan na laaste bladsy +pdfjs-last-page-button-label = Gaan na laaste bladsy +pdfjs-page-rotate-cw-button = + .title = Roteer kloksgewys +pdfjs-page-rotate-cw-button-label = Roteer kloksgewys +pdfjs-page-rotate-ccw-button = + .title = Roteer anti-kloksgewys +pdfjs-page-rotate-ccw-button-label = Roteer anti-kloksgewys +pdfjs-cursor-text-select-tool-button = + .title = Aktiveer gereedskap om teks te merk +pdfjs-cursor-text-select-tool-button-label = Teksmerkgereedskap +pdfjs-cursor-hand-tool-button = + .title = Aktiveer handjie +pdfjs-cursor-hand-tool-button-label = Handjie + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Dokumenteienskappe… +pdfjs-document-properties-button-label = Dokumenteienskappe… +pdfjs-document-properties-file-name = Lêernaam: +pdfjs-document-properties-file-size = Lêergrootte: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } kG ({ $size_b } grepe) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MG ({ $size_b } grepe) +pdfjs-document-properties-title = Titel: +pdfjs-document-properties-author = Outeur: +pdfjs-document-properties-subject = Onderwerp: +pdfjs-document-properties-keywords = Sleutelwoorde: +pdfjs-document-properties-creation-date = Skeppingsdatum: +pdfjs-document-properties-modification-date = Wysigingsdatum: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Skepper: +pdfjs-document-properties-producer = PDF-vervaardiger: +pdfjs-document-properties-version = PDF-weergawe: +pdfjs-document-properties-page-count = Aantal bladsye: + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + + +## + +pdfjs-document-properties-close-button = Sluit + +## Print + +pdfjs-print-progress-message = Berei tans dokument voor om te druk… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Kanselleer +pdfjs-printing-not-supported = Waarskuwing: Dié blaaier ondersteun nie drukwerk ten volle nie. +pdfjs-printing-not-ready = Waarskuwing: Die PDF is nog nie volledig gelaai vir drukwerk nie. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Sypaneel aan/af +pdfjs-toggle-sidebar-button-label = Sypaneel aan/af +pdfjs-document-outline-button = + .title = Wys dokumentskema (dubbelklik om alle items oop/toe te vou) +pdfjs-document-outline-button-label = Dokumentoorsig +pdfjs-attachments-button = + .title = Wys aanhegsels +pdfjs-attachments-button-label = Aanhegsels +pdfjs-thumbs-button = + .title = Wys duimnaels +pdfjs-thumbs-button-label = Duimnaels +pdfjs-findbar-button = + .title = Soek in dokument +pdfjs-findbar-button-label = Vind + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Bladsy { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Duimnael van bladsy { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Vind + .placeholder = Soek in dokument… +pdfjs-find-previous-button = + .title = Vind die vorige voorkoms van die frase +pdfjs-find-previous-button-label = Vorige +pdfjs-find-next-button = + .title = Vind die volgende voorkoms van die frase +pdfjs-find-next-button-label = Volgende +pdfjs-find-highlight-checkbox = Verlig almal +pdfjs-find-match-case-checkbox-label = Kassensitief +pdfjs-find-reached-top = Bokant van dokument is bereik; gaan voort van onder af +pdfjs-find-reached-bottom = Einde van dokument is bereik; gaan voort van bo af +pdfjs-find-not-found = Frase nie gevind nie + +## Predefined zoom values + +pdfjs-page-scale-width = Bladsywydte +pdfjs-page-scale-fit = Pas bladsy +pdfjs-page-scale-auto = Outomatiese zoem +pdfjs-page-scale-actual = Werklike grootte +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = 'n Fout het voorgekom met die laai van die PDF. +pdfjs-invalid-file-error = Ongeldige of korrupte PDF-lêer. +pdfjs-missing-file-error = PDF-lêer is weg. +pdfjs-unexpected-response-error = Onverwagse antwoord van bediener. +pdfjs-rendering-error = 'n Fout het voorgekom toe die bladsy weergegee is. + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type }-annotasie] + +## Password + +pdfjs-password-label = Gee die wagwoord om dié PDF-lêer mee te open. +pdfjs-password-invalid = Ongeldige wagwoord. Probeer gerus weer. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Kanselleer +pdfjs-web-fonts-disabled = Webfonte is gedeaktiveer: kan nie PDF-fonte wat ingebed is, gebruik nie. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/an/viewer.ftl b/public/assets/pdfjs/locale/an/viewer.ftl new file mode 100755 index 0000000..6733147 --- /dev/null +++ b/public/assets/pdfjs/locale/an/viewer.ftl @@ -0,0 +1,257 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Pachina anterior +pdfjs-previous-button-label = Anterior +pdfjs-next-button = + .title = Pachina siguient +pdfjs-next-button-label = Siguient +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Pachina +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = de { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount }) +pdfjs-zoom-out-button = + .title = Achiquir +pdfjs-zoom-out-button-label = Achiquir +pdfjs-zoom-in-button = + .title = Agrandir +pdfjs-zoom-in-button-label = Agrandir +pdfjs-zoom-select = + .title = Grandaria +pdfjs-presentation-mode-button = + .title = Cambear t'o modo de presentación +pdfjs-presentation-mode-button-label = Modo de presentación +pdfjs-open-file-button = + .title = Ubrir o fichero +pdfjs-open-file-button-label = Ubrir +pdfjs-print-button = + .title = Imprentar +pdfjs-print-button-label = Imprentar + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Ferramientas +pdfjs-tools-button-label = Ferramientas +pdfjs-first-page-button = + .title = Ir ta la primer pachina +pdfjs-first-page-button-label = Ir ta la primer pachina +pdfjs-last-page-button = + .title = Ir ta la zaguer pachina +pdfjs-last-page-button-label = Ir ta la zaguer pachina +pdfjs-page-rotate-cw-button = + .title = Chirar enta la dreita +pdfjs-page-rotate-cw-button-label = Chira enta la dreita +pdfjs-page-rotate-ccw-button = + .title = Chirar enta la zurda +pdfjs-page-rotate-ccw-button-label = Chirar enta la zurda +pdfjs-cursor-text-select-tool-button = + .title = Activar la ferramienta de selección de texto +pdfjs-cursor-text-select-tool-button-label = Ferramienta de selección de texto +pdfjs-cursor-hand-tool-button = + .title = Activar la ferramienta man +pdfjs-cursor-hand-tool-button-label = Ferramienta man +pdfjs-scroll-vertical-button = + .title = Usar lo desplazamiento vertical +pdfjs-scroll-vertical-button-label = Desplazamiento vertical +pdfjs-scroll-horizontal-button = + .title = Usar lo desplazamiento horizontal +pdfjs-scroll-horizontal-button-label = Desplazamiento horizontal +pdfjs-scroll-wrapped-button = + .title = Activaar lo desplazamiento contino +pdfjs-scroll-wrapped-button-label = Desplazamiento contino +pdfjs-spread-none-button = + .title = No unir vistas de pachinas +pdfjs-spread-none-button-label = Una pachina nomás +pdfjs-spread-odd-button = + .title = Mostrar vista de pachinas, con as impars a la zurda +pdfjs-spread-odd-button-label = Doble pachina, impar a la zurda +pdfjs-spread-even-button = + .title = Amostrar vista de pachinas, con as pars a la zurda +pdfjs-spread-even-button-label = Doble pachina, para a la zurda + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Propiedatz d'o documento... +pdfjs-document-properties-button-label = Propiedatz d'o documento... +pdfjs-document-properties-file-name = Nombre de fichero: +pdfjs-document-properties-file-size = Grandaria d'o fichero: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Titol: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Afer: +pdfjs-document-properties-keywords = Parolas clau: +pdfjs-document-properties-creation-date = Calendata de creyación: +pdfjs-document-properties-modification-date = Calendata de modificación: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Creyador: +pdfjs-document-properties-producer = Creyador de PDF: +pdfjs-document-properties-version = Versión de PDF: +pdfjs-document-properties-page-count = Numero de pachinas: +pdfjs-document-properties-page-size = Mida de pachina: +pdfjs-document-properties-page-size-unit-inches = pulgadas +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = vertical +pdfjs-document-properties-page-size-orientation-landscape = horizontal +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Carta +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } x { $height } { $unit } { $orientation } +pdfjs-document-properties-page-size-dimension-name-string = { $width } x { $height } { $unit } { $name }, { $orientation } + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Vista web rapida: +pdfjs-document-properties-linearized-yes = Sí +pdfjs-document-properties-linearized-no = No +pdfjs-document-properties-close-button = Zarrar + +## Print + +pdfjs-print-progress-message = Se ye preparando la documentación pa imprentar… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Cancelar +pdfjs-printing-not-supported = Pare cuenta: Iste navegador no maneya totalment as impresions. +pdfjs-printing-not-ready = Aviso: Encara no se ha cargau completament o PDF ta imprentar-lo. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Amostrar u amagar a barra lateral +pdfjs-toggle-sidebar-notification-button = + .title = Cambiar barra lateral (lo documento contiene esquema/adchuntos/capas) +pdfjs-toggle-sidebar-button-label = Amostrar a barra lateral +pdfjs-document-outline-button = + .title = Amostrar esquema d'o documento (fer doble clic pa expandir/compactar totz los items) +pdfjs-document-outline-button-label = Esquema d'o documento +pdfjs-attachments-button = + .title = Amostrar os adchuntos +pdfjs-attachments-button-label = Adchuntos +pdfjs-layers-button = + .title = Amostrar capas (doble clic para reiniciar totas las capas a lo estau per defecto) +pdfjs-layers-button-label = Capas +pdfjs-thumbs-button = + .title = Amostrar as miniaturas +pdfjs-thumbs-button-label = Miniaturas +pdfjs-findbar-button = + .title = Trobar en o documento +pdfjs-findbar-button-label = Trobar +pdfjs-additional-layers = Capas adicionals + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Pachina { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura d'a pachina { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Trobar + .placeholder = Trobar en o documento… +pdfjs-find-previous-button = + .title = Trobar l'anterior coincidencia d'a frase +pdfjs-find-previous-button-label = Anterior +pdfjs-find-next-button = + .title = Trobar a siguient coincidencia d'a frase +pdfjs-find-next-button-label = Siguient +pdfjs-find-highlight-checkbox = Resaltar-lo tot +pdfjs-find-match-case-checkbox-label = Coincidencia de mayusclas/minusclas +pdfjs-find-entire-word-checkbox-label = Parolas completas +pdfjs-find-reached-top = S'ha plegau a l'inicio d'o documento, se contina dende baixo +pdfjs-find-reached-bottom = S'ha plegau a la fin d'o documento, se contina dende alto +pdfjs-find-not-found = No s'ha trobau a frase + +## Predefined zoom values + +pdfjs-page-scale-width = Amplaria d'a pachina +pdfjs-page-scale-fit = Achuste d'a pachina +pdfjs-page-scale-auto = Grandaria automatica +pdfjs-page-scale-actual = Grandaria actual +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = S'ha produciu una error en cargar o PDF. +pdfjs-invalid-file-error = O PDF no ye valido u ye estorbau. +pdfjs-missing-file-error = No i ha fichero PDF. +pdfjs-unexpected-response-error = Respuesta a lo servicio inasperada. +pdfjs-rendering-error = Ha ocurriu una error en renderizar a pachina. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Anotación { $type }] + +## Password + +pdfjs-password-label = Introduzca a clau ta ubrir iste fichero PDF. +pdfjs-password-invalid = Clau invalida. Torna a intentar-lo. +pdfjs-password-ok-button = Acceptar +pdfjs-password-cancel-button = Cancelar +pdfjs-web-fonts-disabled = As fuents web son desactivadas: no se puet incrustar fichers PDF. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/ar/viewer.ftl b/public/assets/pdfjs/locale/ar/viewer.ftl new file mode 100755 index 0000000..8d14767 --- /dev/null +++ b/public/assets/pdfjs/locale/ar/viewer.ftl @@ -0,0 +1,425 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Ø§Ù„ØµÙØ­Ø© السابقة +pdfjs-previous-button-label = السابقة +pdfjs-next-button = + .title = Ø§Ù„ØµÙØ­Ø© التالية +pdfjs-next-button-label = التالية +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = ØµÙØ­Ø© +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = من { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } من { $pagesCount }) +pdfjs-zoom-out-button = + .title = بعّد +pdfjs-zoom-out-button-label = بعّد +pdfjs-zoom-in-button = + .title = قرّب +pdfjs-zoom-in-button-label = قرّب +pdfjs-zoom-select = + .title = التقريب +pdfjs-presentation-mode-button = + .title = انتقل لوضع العرض التقديمي +pdfjs-presentation-mode-button-label = وضع العرض التقديمي +pdfjs-open-file-button = + .title = Ø§ÙØªØ­ ملÙًا +pdfjs-open-file-button-label = Ø§ÙØªØ­ +pdfjs-print-button = + .title = اطبع +pdfjs-print-button-label = اطبع +pdfjs-save-button = + .title = Ø§Ø­ÙØ¸ +pdfjs-save-button-label = Ø§Ø­ÙØ¸ +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = نزّل +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = نزّل +pdfjs-bookmark-button = + .title = Ø§Ù„ØµÙØ­Ø© الحالية (عرض URL من Ø§Ù„ØµÙØ­Ø© الحالية) +pdfjs-bookmark-button-label = Ø§Ù„ØµÙØ­Ø© الحالية + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = الأدوات +pdfjs-tools-button-label = الأدوات +pdfjs-first-page-button = + .title = انتقل إلى Ø§Ù„ØµÙØ­Ø© الأولى +pdfjs-first-page-button-label = انتقل إلى Ø§Ù„ØµÙØ­Ø© الأولى +pdfjs-last-page-button = + .title = انتقل إلى Ø§Ù„ØµÙØ­Ø© الأخيرة +pdfjs-last-page-button-label = انتقل إلى Ø§Ù„ØµÙØ­Ø© الأخيرة +pdfjs-page-rotate-cw-button = + .title = أدر باتجاه عقارب الساعة +pdfjs-page-rotate-cw-button-label = أدر باتجاه عقارب الساعة +pdfjs-page-rotate-ccw-button = + .title = أدر بعكس اتجاه عقارب الساعة +pdfjs-page-rotate-ccw-button-label = أدر بعكس اتجاه عقارب الساعة +pdfjs-cursor-text-select-tool-button = + .title = ÙØ¹Ù‘Ù„ أداة اختيار النص +pdfjs-cursor-text-select-tool-button-label = أداة اختيار النص +pdfjs-cursor-hand-tool-button = + .title = ÙØ¹Ù‘Ù„ أداة اليد +pdfjs-cursor-hand-tool-button-label = أداة اليد +pdfjs-scroll-page-button = + .title = استخدم تمرير Ø§Ù„ØµÙØ­Ø© +pdfjs-scroll-page-button-label = تمرير Ø§Ù„ØµÙØ­Ø© +pdfjs-scroll-vertical-button = + .title = استخدم التمرير الرأسي +pdfjs-scroll-vertical-button-label = التمرير الرأسي +pdfjs-scroll-horizontal-button = + .title = استخدم التمرير الأÙقي +pdfjs-scroll-horizontal-button-label = التمرير الأÙقي +pdfjs-scroll-wrapped-button = + .title = استخدم التمرير الملت٠+pdfjs-scroll-wrapped-button-label = التمرير الملت٠+pdfjs-spread-none-button = + .title = لا تدمج هوامش Ø§Ù„ØµÙØ­Ø§Øª مع بعضها البعض +pdfjs-spread-none-button-label = بلا هوامش +pdfjs-spread-odd-button = + .title = ادمج هوامش Ø§Ù„ØµÙØ­Ø§Øª Ø§Ù„ÙØ±Ø¯ÙŠØ© +pdfjs-spread-odd-button-label = هوامش Ø§Ù„ØµÙØ­Ø§Øª Ø§Ù„ÙØ±Ø¯ÙŠØ© +pdfjs-spread-even-button = + .title = ادمج هوامش Ø§Ù„ØµÙØ­Ø§Øª الزوجية +pdfjs-spread-even-button-label = هوامش Ø§Ù„ØµÙØ­Ø§Øª الزوجية + +## Document properties dialog + +pdfjs-document-properties-button = + .title = خصائص المستند… +pdfjs-document-properties-button-label = خصائص المستند… +pdfjs-document-properties-file-name = اسم الملÙ: +pdfjs-document-properties-file-size = حجم الملÙ: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } Ùƒ.بايت ({ $size_b } بايت) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } Ù….بايت ({ $size_b } بايت) +pdfjs-document-properties-title = العنوان: +pdfjs-document-properties-author = المؤلÙ: +pdfjs-document-properties-subject = الموضوع: +pdfjs-document-properties-keywords = الكلمات الأساسية: +pdfjs-document-properties-creation-date = تاريخ الإنشاء: +pdfjs-document-properties-modification-date = تاريخ التعديل: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }ØŒ { $time } +pdfjs-document-properties-creator = المنشئ: +pdfjs-document-properties-producer = منتج PDF: +pdfjs-document-properties-version = إصدارة PDF: +pdfjs-document-properties-page-count = عدد Ø§Ù„ØµÙØ­Ø§Øª: +pdfjs-document-properties-page-size = مقاس الورقة: +pdfjs-document-properties-page-size-unit-inches = بوصة +pdfjs-document-properties-page-size-unit-millimeters = ملم +pdfjs-document-properties-page-size-orientation-portrait = طوليّ +pdfjs-document-properties-page-size-orientation-landscape = عرضيّ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = خطاب +pdfjs-document-properties-page-size-name-legal = قانونيّ + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = â€{ $width } × â€{ $height } â€{ $unit } (â€{ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = â€{ $width } × â€{ $height } â€{ $unit } (â€{ $name }ØŒ { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = العرض السريع عبر Ø§Ù„ÙˆÙØ¨: +pdfjs-document-properties-linearized-yes = نعم +pdfjs-document-properties-linearized-no = لا +pdfjs-document-properties-close-button = أغلق + +## Print + +pdfjs-print-progress-message = ÙŠÙØ­Ø¶Ù‘ر المستند للطباعة… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }Ùª +pdfjs-print-progress-close-button = ألغ٠+pdfjs-printing-not-supported = تحذير: لا يدعم هذا Ø§Ù„Ù…ØªØµÙØ­ الطباعة بشكل كامل. +pdfjs-printing-not-ready = تحذير: مل٠PDF لم ÙŠÙØ­Ù…ّل كاملًا للطباعة. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = بدّل ظهور الشريط الجانبي +pdfjs-toggle-sidebar-notification-button = + .title = بدّل ظهور الشريط الجانبي (يحتوي المستند على مخطط أو مرÙقات أو طبقات) +pdfjs-toggle-sidebar-button-label = بدّل ظهور الشريط الجانبي +pdfjs-document-outline-button = + .title = اعرض Ùهرس المستند (نقر مزدوج لتمديد أو تقليص كل العناصر) +pdfjs-document-outline-button-label = مخطط المستند +pdfjs-attachments-button = + .title = اعرض المرÙقات +pdfjs-attachments-button-label = Ø§Ù„Ù…ÙØ±Ùقات +pdfjs-layers-button = + .title = اعرض الطبقات (انقر مرتين لتصÙير كل الطبقات إلى الحالة المبدئية) +pdfjs-layers-button-label = â€â€Ø§Ù„طبقات +pdfjs-thumbs-button = + .title = اعرض Ù…ÙØµØºØ±Ø§Øª +pdfjs-thumbs-button-label = Ù…ÙØµØºÙ‘رات +pdfjs-current-outline-item-button = + .title = ابحث عن عنصر المخطّط Ø§Ù„ØªÙØµÙŠÙ„ÙŠ الحالي +pdfjs-current-outline-item-button-label = عنصر المخطّط Ø§Ù„ØªÙØµÙŠÙ„ÙŠ الحالي +pdfjs-findbar-button = + .title = ابحث ÙÙŠ المستند +pdfjs-findbar-button-label = ابحث +pdfjs-additional-layers = الطبقات الإضاÙية + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = ØµÙØ­Ø© { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = مصغّرة ØµÙØ­Ø© { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = ابحث + .placeholder = ابحث ÙÙŠ المستند… +pdfjs-find-previous-button = + .title = ابحث عن التّواجد السّابق للعبارة +pdfjs-find-previous-button-label = السابق +pdfjs-find-next-button = + .title = ابحث عن التّواجد التّالي للعبارة +pdfjs-find-next-button-label = التالي +pdfjs-find-highlight-checkbox = Ø£Ø¨Ø±ÙØ² الكل +pdfjs-find-match-case-checkbox-label = طابق حالة الأحر٠+pdfjs-find-match-diacritics-checkbox-label = طابÙÙ‚ الحركات +pdfjs-find-entire-word-checkbox-label = كلمات كاملة +pdfjs-find-reached-top = تابعت من الأسÙÙ„ بعدما وصلت إلى بداية المستند +pdfjs-find-reached-bottom = تابعت من الأعلى بعدما وصلت إلى نهاية المستند +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [zero] لا مطابقة + [one] { $current } من أصل { $total } مطابقة + [two] { $current } من أصل { $total } مطابقة + [few] { $current } من أصل { $total } مطابقة + [many] { $current } من أصل { $total } مطابقة + *[other] { $current } من أصل { $total } مطابقة + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [zero] { $limit } مطابقة + [one] أكثر من { $limit } مطابقة + [two] أكثر من { $limit } مطابقة + [few] أكثر من { $limit } مطابقة + [many] أكثر من { $limit } مطابقة + *[other] أكثر من { $limit } مطابقات + } +pdfjs-find-not-found = لا وجود للعبارة + +## Predefined zoom values + +pdfjs-page-scale-width = عرض Ø§Ù„ØµÙØ­Ø© +pdfjs-page-scale-fit = ملائمة Ø§Ù„ØµÙØ­Ø© +pdfjs-page-scale-auto = تقريب تلقائي +pdfjs-page-scale-actual = الحجم Ø§Ù„ÙØ¹Ù„ÙŠ +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }Ùª + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = ØµÙØ­Ø© { $page } + +## Loading indicator messages + +pdfjs-loading-error = حدث عطل أثناء تحميل مل٠PDF. +pdfjs-invalid-file-error = مل٠PDF تال٠أو غير صحيح. +pdfjs-missing-file-error = مل٠PDF غير موجود. +pdfjs-unexpected-response-error = استجابة خادوم غير متوقعة. +pdfjs-rendering-error = حدث خطأ أثناء عرض Ø§Ù„ØµÙØ­Ø©. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }ØŒ { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [تعليق { $type }] + +## Password + +pdfjs-password-label = أدخل لكلمة السر Ù„ÙØªØ­ هذا الملÙ. +pdfjs-password-invalid = كلمة سر خطأ. من ÙØ¶Ù„Ùƒ أعد المحاولة. +pdfjs-password-ok-button = حسنا +pdfjs-password-cancel-button = ألغ٠+pdfjs-web-fonts-disabled = خطوط الوب Ù…ÙØ¹Ø·Ù‘لة: تعذّر استخدام خطوط PDF Ø§Ù„Ù…ÙØ¶Ù…ّنة. + +## Editing + +pdfjs-editor-free-text-button = + .title = نص +pdfjs-editor-free-text-button-label = نص +pdfjs-editor-ink-button = + .title = ارسم +pdfjs-editor-ink-button-label = ارسم +pdfjs-editor-stamp-button = + .title = أضÙ٠أو حرّر الصور +pdfjs-editor-stamp-button-label = أضÙ٠أو حرّر الصور +pdfjs-editor-highlight-button = + .title = Ø£Ø¨Ø±ÙØ² +pdfjs-editor-highlight-button-label = Ø£Ø¨Ø±ÙØ² +pdfjs-highlight-floating-button1 = + .title = Ø£Ø¨Ø±ÙØ² + .aria-label = Ø£Ø¨Ø±ÙØ² +pdfjs-highlight-floating-button-label = Ø£Ø¨Ø±ÙØ² + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = أزÙÙ„ الرسم +pdfjs-editor-remove-freetext-button = + .title = أزÙÙ„ النص +pdfjs-editor-remove-stamp-button = + .title = أزÙÙ„ الصورة +pdfjs-editor-remove-highlight-button = + .title = أزÙÙ„ الإبراز + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = اللون +pdfjs-editor-free-text-size-input = الحجم +pdfjs-editor-ink-color-input = اللون +pdfjs-editor-ink-thickness-input = السماكة +pdfjs-editor-ink-opacity-input = العتامة +pdfjs-editor-stamp-add-image-button = + .title = أضÙ٠صورة +pdfjs-editor-stamp-add-image-button-label = أضÙ٠صورة +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = السماكة +pdfjs-editor-free-highlight-thickness-title = + .title = غيّر السÙمك عند إبراز عناصر Ø£ÙØ®Ø±Ù‰ غير النص +pdfjs-free-text = + .aria-label = Ù…Ø­Ø±Ù‘ÙØ± النص +pdfjs-free-text-default-content = ابدأ الكتابة… +pdfjs-ink = + .aria-label = Ù…Ø­Ø±Ù‘ÙØ± الرسم +pdfjs-ink-canvas = + .aria-label = صورة أنشأها المستخدم + +## Alt-text dialog + +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button-label = نص بديل +pdfjs-editor-alt-text-edit-button-label = تحرير النص البديل +pdfjs-editor-alt-text-dialog-label = اختر خيار +pdfjs-editor-alt-text-dialog-description = يساعد النص البديل عندما لا يتمكن الأشخاص من رؤية الصورة أو عندما لا يتم تحميلها. +pdfjs-editor-alt-text-add-description-label = أضÙ٠وص٠+pdfjs-editor-alt-text-add-description-description = استهد٠جملتين ØªØµÙØ§Ù† الموضوع أو الإعداد أو الإجراءات. +pdfjs-editor-alt-text-mark-decorative-label = علّمها على أنها زخرÙية +pdfjs-editor-alt-text-mark-decorative-description = ÙŠÙØ³ØªØ®Ø¯Ù… هذا ÙÙŠ الصور Ø§Ù„Ù…Ø²Ø®Ø±ÙØ©ØŒ مثل الحدود أو العلامات المائية. +pdfjs-editor-alt-text-cancel-button = ألغ٠+pdfjs-editor-alt-text-save-button = Ø§Ø­ÙØ¸ +pdfjs-editor-alt-text-decorative-tooltip = عÙلّمت على أنها زخرÙية +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = على سبيل المثال، "يجلس شاب على الطاولة لتناول وجبة" + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = الزاوية Ø§Ù„ÙŠÙØ³Ø±Ù‰ العÙليا — غيّر الحجم +pdfjs-editor-resizer-label-top-middle = أعلى الوسط - غيّر الحجم +pdfjs-editor-resizer-label-top-right = الزاوية اليÙمنى العÙليا - غيّر الحجم +pdfjs-editor-resizer-label-middle-right = اليمين الأوسط - غيّر الحجم +pdfjs-editor-resizer-label-bottom-right = الزاوية اليÙمنى السÙÙلى - غيّر الحجم +pdfjs-editor-resizer-label-bottom-middle = أسÙÙ„ الوسط - غيّر الحجم +pdfjs-editor-resizer-label-bottom-left = الزاوية Ø§Ù„ÙŠÙØ³Ø±Ù‰ السÙÙلية - غيّر الحجم +pdfjs-editor-resizer-label-middle-left = Ù…Ùنتص٠اليسار - غيّر الحجم +pdfjs-editor-resizer-top-left = + .aria-label = الزاوية Ø§Ù„ÙŠÙØ³Ø±Ù‰ العÙليا — غيّر الحجم +pdfjs-editor-resizer-top-middle = + .aria-label = أعلى الوسط - غيّر الحجم +pdfjs-editor-resizer-top-right = + .aria-label = الزاوية اليÙمنى العÙليا - غيّر الحجم +pdfjs-editor-resizer-middle-right = + .aria-label = اليمين الأوسط - غيّر الحجم +pdfjs-editor-resizer-bottom-right = + .aria-label = الزاوية اليÙمنى السÙÙلى - غيّر الحجم +pdfjs-editor-resizer-bottom-middle = + .aria-label = أسÙÙ„ الوسط - غيّر الحجم +pdfjs-editor-resizer-bottom-left = + .aria-label = الزاوية Ø§Ù„ÙŠÙØ³Ø±Ù‰ السÙÙلية - غيّر الحجم +pdfjs-editor-resizer-middle-left = + .aria-label = Ù…Ùنتص٠اليسار - غيّر الحجم + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Ø£Ø¨Ø±ÙØ² اللون +pdfjs-editor-colorpicker-button = + .title = غيّر اللون +pdfjs-editor-colorpicker-dropdown = + .aria-label = اختيارات الألوان +pdfjs-editor-colorpicker-yellow = + .title = Ø£ØµÙØ± +pdfjs-editor-colorpicker-green = + .title = أخضر +pdfjs-editor-colorpicker-blue = + .title = أزرق +pdfjs-editor-colorpicker-pink = + .title = وردي +pdfjs-editor-colorpicker-red = + .title = أحمر + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Ø£Ø¸Ù‡ÙØ± الكل +pdfjs-editor-highlight-show-all-button = + .title = Ø£Ø¸Ù‡ÙØ± الكل + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + + +## Image alt-text settings + diff --git a/public/assets/pdfjs/locale/ast/viewer.ftl b/public/assets/pdfjs/locale/ast/viewer.ftl new file mode 100755 index 0000000..2503caf --- /dev/null +++ b/public/assets/pdfjs/locale/ast/viewer.ftl @@ -0,0 +1,201 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Páxina anterior +pdfjs-previous-button-label = Anterior +pdfjs-next-button = + .title = Páxina siguiente +pdfjs-next-button-label = Siguiente +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Páxina +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = de { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount }) +pdfjs-zoom-out-button = + .title = Alloñar +pdfjs-zoom-out-button-label = Alloña +pdfjs-zoom-in-button = + .title = Averar +pdfjs-zoom-in-button-label = Avera +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Cambiar al mou de presentación +pdfjs-presentation-mode-button-label = Mou de presentación +pdfjs-open-file-button-label = Abrir +pdfjs-print-button = + .title = Imprentar +pdfjs-print-button-label = Imprentar + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Ferramientes +pdfjs-tools-button-label = Ferramientes +pdfjs-first-page-button-label = Dir a la primer páxina +pdfjs-last-page-button-label = Dir a la última páxina +pdfjs-page-rotate-cw-button = + .title = Voltia a la derecha +pdfjs-page-rotate-cw-button-label = Voltiar a la derecha +pdfjs-page-rotate-ccw-button = + .title = Voltia a la esquierda +pdfjs-page-rotate-ccw-button-label = Voltiar a la esquierda +pdfjs-cursor-text-select-tool-button = + .title = Activa la ferramienta d'esbilla de testu +pdfjs-cursor-text-select-tool-button-label = Ferramienta d'esbilla de testu +pdfjs-cursor-hand-tool-button = + .title = Activa la ferramienta de mano +pdfjs-cursor-hand-tool-button-label = Ferramienta de mano +pdfjs-scroll-vertical-button = + .title = Usa'l desplazamientu vertical +pdfjs-scroll-vertical-button-label = Desplazamientu vertical +pdfjs-scroll-horizontal-button = + .title = Usa'l desplazamientu horizontal +pdfjs-scroll-horizontal-button-label = Desplazamientu horizontal +pdfjs-scroll-wrapped-button = + .title = Usa'l desplazamientu continuu +pdfjs-scroll-wrapped-button-label = Desplazamientu continuu +pdfjs-spread-none-button-label = Fueyes individuales +pdfjs-spread-odd-button-label = Fueyes pares +pdfjs-spread-even-button-label = Fueyes impares + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Propiedaes del documentu… +pdfjs-document-properties-button-label = Propiedaes del documentu… +pdfjs-document-properties-file-name = Nome del ficheru: +pdfjs-document-properties-file-size = Tamañu del ficheru: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Títulu: +pdfjs-document-properties-keywords = Pallabres clave: +pdfjs-document-properties-creation-date = Data de creación: +pdfjs-document-properties-modification-date = Data de modificación: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-producer = Productor del PDF: +pdfjs-document-properties-version = Versión del PDF: +pdfjs-document-properties-page-count = Númberu de páxines: +pdfjs-document-properties-page-size = Tamañu de páxina: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = vertical +pdfjs-document-properties-page-size-orientation-landscape = horizontal +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Vista web rápida: +pdfjs-document-properties-linearized-yes = Sí +pdfjs-document-properties-linearized-no = Non +pdfjs-document-properties-close-button = Zarrar + +## Print + +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Encaboxar + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Alternar la barra llateral +pdfjs-attachments-button = + .title = Amosar los axuntos +pdfjs-attachments-button-label = Axuntos +pdfjs-layers-button-label = Capes +pdfjs-thumbs-button = + .title = Amosar les miniatures +pdfjs-thumbs-button-label = Miniatures +pdfjs-findbar-button-label = Atopar +pdfjs-additional-layers = Capes adicionales + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Páxina { $page } + +## Find panel button title and messages + +pdfjs-find-previous-button-label = Anterior +pdfjs-find-next-button-label = Siguiente +pdfjs-find-entire-word-checkbox-label = Pallabres completes +pdfjs-find-reached-top = Algamóse'l comienzu de la páxina, síguese dende abaxo +pdfjs-find-reached-bottom = Algamóse la fin del documentu, síguese dende arriba + +## Predefined zoom values + +pdfjs-page-scale-auto = Zoom automáticu +pdfjs-page-scale-actual = Tamañu real +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Páxina { $page } + +## Loading indicator messages + +pdfjs-loading-error = Asocedió un fallu mentanto se cargaba'l PDF. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } + +## Password + +pdfjs-password-ok-button = Aceptar +pdfjs-password-cancel-button = Encaboxar + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/az/viewer.ftl b/public/assets/pdfjs/locale/az/viewer.ftl new file mode 100755 index 0000000..773aae4 --- /dev/null +++ b/public/assets/pdfjs/locale/az/viewer.ftl @@ -0,0 +1,257 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = ÆvvÉ™lki sÉ™hifÉ™ +pdfjs-previous-button-label = ÆvvÉ™lkini tap +pdfjs-next-button = + .title = NövbÉ™ti sÉ™hifÉ™ +pdfjs-next-button-label = İrÉ™li +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = SÉ™hifÉ™ +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = / { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } / { $pagesCount }) +pdfjs-zoom-out-button = + .title = UzaqlaÅŸ +pdfjs-zoom-out-button-label = UzaqlaÅŸ +pdfjs-zoom-in-button = + .title = YaxınlaÅŸ +pdfjs-zoom-in-button-label = YaxınlaÅŸ +pdfjs-zoom-select = + .title = YaxınlaÅŸdırma +pdfjs-presentation-mode-button = + .title = TÉ™qdimat RejiminÉ™ Keç +pdfjs-presentation-mode-button-label = TÉ™qdimat Rejimi +pdfjs-open-file-button = + .title = Fayl Aç +pdfjs-open-file-button-label = Aç +pdfjs-print-button = + .title = Yazdır +pdfjs-print-button-label = Yazdır + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = AlÉ™tlÉ™r +pdfjs-tools-button-label = AlÉ™tlÉ™r +pdfjs-first-page-button = + .title = İlk SÉ™hifÉ™yÉ™ get +pdfjs-first-page-button-label = İlk SÉ™hifÉ™yÉ™ get +pdfjs-last-page-button = + .title = Son SÉ™hifÉ™yÉ™ get +pdfjs-last-page-button-label = Son SÉ™hifÉ™yÉ™ get +pdfjs-page-rotate-cw-button = + .title = Saat İstiqamÉ™tindÉ™ Fırlat +pdfjs-page-rotate-cw-button-label = Saat İstiqamÉ™tindÉ™ Fırlat +pdfjs-page-rotate-ccw-button = + .title = Saat İstiqamÉ™tinin ÆksinÉ™ Fırlat +pdfjs-page-rotate-ccw-button-label = Saat İstiqamÉ™tinin ÆksinÉ™ Fırlat +pdfjs-cursor-text-select-tool-button = + .title = Yazı seçmÉ™ alÉ™tini aktivləşdir +pdfjs-cursor-text-select-tool-button-label = Yazı seçmÉ™ alÉ™ti +pdfjs-cursor-hand-tool-button = + .title = Æl alÉ™tini aktivləşdir +pdfjs-cursor-hand-tool-button-label = Æl alÉ™ti +pdfjs-scroll-vertical-button = + .title = Åžaquli sürüşdürmÉ™ iÅŸlÉ™t +pdfjs-scroll-vertical-button-label = Åžaquli sürüşdürmÉ™ +pdfjs-scroll-horizontal-button = + .title = Üfüqi sürüşdürmÉ™ iÅŸlÉ™t +pdfjs-scroll-horizontal-button-label = Üfüqi sürüşdürmÉ™ +pdfjs-scroll-wrapped-button = + .title = Bükülü sürüşdürmÉ™ iÅŸlÉ™t +pdfjs-scroll-wrapped-button-label = Bükülü sürüşdürmÉ™ +pdfjs-spread-none-button = + .title = Yan-yana birləşdirilmiÅŸ sÉ™hifÉ™lÉ™ri iÅŸlÉ™tmÉ™ +pdfjs-spread-none-button-label = BirləşdirmÉ™ +pdfjs-spread-odd-button = + .title = Yan-yana birləşdirilmiÅŸ sÉ™hifÉ™lÉ™ri tÉ™k nömrÉ™li sÉ™hifÉ™lÉ™rdÉ™n baÅŸlat +pdfjs-spread-odd-button-label = TÉ™k nömrÉ™li +pdfjs-spread-even-button = + .title = Yan-yana birləşdirilmiÅŸ sÉ™hifÉ™lÉ™ri cüt nömrÉ™li sÉ™hifÉ™lÉ™rdÉ™n baÅŸlat +pdfjs-spread-even-button-label = Cüt nömrÉ™li + +## Document properties dialog + +pdfjs-document-properties-button = + .title = SÉ™nÉ™d xüsusiyyÉ™tlÉ™ri… +pdfjs-document-properties-button-label = SÉ™nÉ™d xüsusiyyÉ™tlÉ™ri… +pdfjs-document-properties-file-name = Fayl adı: +pdfjs-document-properties-file-size = Fayl ölçüsü: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bayt) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bayt) +pdfjs-document-properties-title = BaÅŸlık: +pdfjs-document-properties-author = Müəllif: +pdfjs-document-properties-subject = Mövzu: +pdfjs-document-properties-keywords = Açar sözlÉ™r: +pdfjs-document-properties-creation-date = Yaradılış Tarixi : +pdfjs-document-properties-modification-date = DÉ™yiÅŸdirilmÉ™ Tarixi : +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Yaradan: +pdfjs-document-properties-producer = PDF yaradıcısı: +pdfjs-document-properties-version = PDF versiyası: +pdfjs-document-properties-page-count = SÉ™hifÉ™ sayı: +pdfjs-document-properties-page-size = SÉ™hifÉ™ Ölçüsü: +pdfjs-document-properties-page-size-unit-inches = inç +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = portret +pdfjs-document-properties-page-size-orientation-landscape = albom +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = MÉ™ktub +pdfjs-document-properties-page-size-name-legal = Hüquqi + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Fast Web View: +pdfjs-document-properties-linearized-yes = BÉ™li +pdfjs-document-properties-linearized-no = Xeyr +pdfjs-document-properties-close-button = Qapat + +## Print + +pdfjs-print-progress-message = SÉ™nÉ™d çap üçün hazırlanır… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Ləğv et +pdfjs-printing-not-supported = XÉ™bÉ™rdarlıq: Çap bu sÉ™yyah tÉ™rÉ™findÉ™n tam olaraq dÉ™stÉ™klÉ™nmir. +pdfjs-printing-not-ready = XÉ™bÉ™rdarlıq: PDF çap üçün tam yüklÉ™nmÉ™yib. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Yan Paneli Aç/BaÄŸla +pdfjs-toggle-sidebar-notification-button = + .title = Yan paneli çevir (sÉ™nÉ™ddÉ™ icmal/baÄŸlamalar/laylar mövcuddur) +pdfjs-toggle-sidebar-button-label = Yan Paneli Aç/BaÄŸla +pdfjs-document-outline-button = + .title = SÉ™nÉ™din eskizini göstÉ™r (bütün bÉ™ndlÉ™ri açmaq/yığmaq üçün iki dÉ™fÉ™ kliklÉ™yin) +pdfjs-document-outline-button-label = SÉ™nÉ™d strukturu +pdfjs-attachments-button = + .title = BaÄŸlamaları göstÉ™r +pdfjs-attachments-button-label = BaÄŸlamalar +pdfjs-layers-button = + .title = Layları göstÉ™r (bütün layları ilkin halına sıfırlamaq üçün iki dÉ™fÉ™ kliklÉ™yin) +pdfjs-layers-button-label = Laylar +pdfjs-thumbs-button = + .title = Kiçik ÅŸÉ™killÉ™ri göstÉ™r +pdfjs-thumbs-button-label = Kiçik ÅŸÉ™killÉ™r +pdfjs-findbar-button = + .title = SÉ™nÉ™ddÉ™ Tap +pdfjs-findbar-button-label = Tap +pdfjs-additional-layers = ÆlavÉ™ laylar + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = SÉ™hifÉ™{ $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = { $page } sÉ™hifÉ™sinin kiçik vÉ™ziyyÉ™ti + +## Find panel button title and messages + +pdfjs-find-input = + .title = Tap + .placeholder = SÉ™nÉ™ddÉ™ tap… +pdfjs-find-previous-button = + .title = Bir öncÉ™ki uyÄŸun gÉ™lÉ™n sözü tapır +pdfjs-find-previous-button-label = Geri +pdfjs-find-next-button = + .title = Bir sonrakı uyÄŸun gÉ™lÉ™n sözü tapır +pdfjs-find-next-button-label = İrÉ™li +pdfjs-find-highlight-checkbox = İşarÉ™lÉ™ +pdfjs-find-match-case-checkbox-label = Böyük/kiçik hÉ™rfÉ™ hÉ™ssaslıq +pdfjs-find-entire-word-checkbox-label = Tam sözlÉ™r +pdfjs-find-reached-top = SÉ™nÉ™din yuxarısına çatdı, aÅŸağıdan davam edir +pdfjs-find-reached-bottom = SÉ™nÉ™din sonuna çatdı, yuxarıdan davam edir +pdfjs-find-not-found = UyÄŸunlaÅŸma tapılmadı + +## Predefined zoom values + +pdfjs-page-scale-width = SÉ™hifÉ™ geniÅŸliyi +pdfjs-page-scale-fit = SÉ™hifÉ™ni sığdır +pdfjs-page-scale-auto = Avtomatik yaxınlaÅŸdır +pdfjs-page-scale-actual = Hazırkı HÉ™cm +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = PDF yüklenÉ™rkÉ™n bir sÉ™hv yarandı. +pdfjs-invalid-file-error = SÉ™hv vÉ™ ya zÉ™dÉ™lÉ™nmiÅŸ olmuÅŸ PDF fayl. +pdfjs-missing-file-error = PDF fayl yoxdur. +pdfjs-unexpected-response-error = GözlÉ™nilmÉ™z server cavabı. +pdfjs-rendering-error = SÉ™hifÉ™ göstÉ™rilÉ™rkÉ™n sÉ™hv yarandı. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Annotasiyası] + +## Password + +pdfjs-password-label = Bu PDF faylı açmaq üçün parolu daxil edin. +pdfjs-password-invalid = Parol sÉ™hvdir. Bir daha yoxlayın. +pdfjs-password-ok-button = Tamam +pdfjs-password-cancel-button = Ləğv et +pdfjs-web-fonts-disabled = Web ÅžriftlÉ™r söndürülüb: yerləşdirilmiÅŸ PDF ÅŸriftlÉ™rini istifadÉ™ etmÉ™k mümkün deyil. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/be/viewer.ftl b/public/assets/pdfjs/locale/be/viewer.ftl new file mode 100755 index 0000000..3f029d9 --- /dev/null +++ b/public/assets/pdfjs/locale/be/viewer.ftl @@ -0,0 +1,518 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = ПапÑÑ€ÑднÑÑ Ñтаронка +pdfjs-previous-button-label = ПапÑÑ€ÑднÑÑ +pdfjs-next-button = + .title = ÐаÑÑ‚ÑƒÐ¿Ð½Ð°Ñ Ñтаронка +pdfjs-next-button-label = ÐаÑÑ‚ÑƒÐ¿Ð½Ð°Ñ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Старонка +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = з { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } з { $pagesCount }) +pdfjs-zoom-out-button = + .title = Паменшыць +pdfjs-zoom-out-button-label = Паменшыць +pdfjs-zoom-in-button = + .title = ПавÑлічыць +pdfjs-zoom-in-button-label = ПавÑлічыць +pdfjs-zoom-select = + .title = ПавÑлічÑнне Ñ‚ÑкÑту +pdfjs-presentation-mode-button = + .title = Пераключыцца Ñž Ñ€Ñжым паказу +pdfjs-presentation-mode-button-label = РÑжым паказу +pdfjs-open-file-button = + .title = Ðдкрыць файл +pdfjs-open-file-button-label = Ðдкрыць +pdfjs-print-button = + .title = Друкаваць +pdfjs-print-button-label = Друкаваць +pdfjs-save-button = + .title = Захаваць +pdfjs-save-button-label = Захаваць +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = СцÑгнуць +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = СцÑгнуць +pdfjs-bookmark-button = + .title = Ð”Ð·ÐµÐ¹Ð½Ð°Ñ Ñтаронка (паглÑдзець URL-Ð°Ð´Ñ€Ð°Ñ Ð· дзейнай Ñтаронкі) +pdfjs-bookmark-button-label = ЦÑперашнÑÑ Ñтаронка + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Прылады +pdfjs-tools-button-label = Прылады +pdfjs-first-page-button = + .title = ПерайÑці на першую Ñтаронку +pdfjs-first-page-button-label = ПерайÑці на першую Ñтаронку +pdfjs-last-page-button = + .title = ПерайÑці на апошнюю Ñтаронку +pdfjs-last-page-button-label = ПерайÑці на апошнюю Ñтаронку +pdfjs-page-rotate-cw-button = + .title = ПавÑрнуць па Ñонцу +pdfjs-page-rotate-cw-button-label = ПавÑрнуць па Ñонцу +pdfjs-page-rotate-ccw-button = + .title = ПавÑрнуць Ñупраць Ñонца +pdfjs-page-rotate-ccw-button-label = ПавÑрнуць Ñупраць Ñонца +pdfjs-cursor-text-select-tool-button = + .title = Уключыць прыладу выбару Ñ‚ÑкÑту +pdfjs-cursor-text-select-tool-button-label = Прылада выбару Ñ‚ÑкÑту +pdfjs-cursor-hand-tool-button = + .title = Уключыць ручную прыладу +pdfjs-cursor-hand-tool-button-label = Ð ÑƒÑ‡Ð½Ð°Ñ Ð¿Ñ€Ñ‹Ð»Ð°Ð´Ð° +pdfjs-scroll-page-button = + .title = ВыкарыÑтоўваць пракрутку Ñтаронкi +pdfjs-scroll-page-button-label = Пракрутка Ñтаронкi +pdfjs-scroll-vertical-button = + .title = Ужываць вертыкальную пракрутку +pdfjs-scroll-vertical-button-label = Ð’ÐµÑ€Ñ‚Ñ‹ÐºÐ°Ð»ÑŒÐ½Ð°Ñ Ð¿Ñ€Ð°ÐºÑ€ÑƒÑ‚ÐºÐ° +pdfjs-scroll-horizontal-button = + .title = Ужываць гарызантальную пракрутку +pdfjs-scroll-horizontal-button-label = Ð“Ð°Ñ€Ñ‹Ð·Ð°Ð½Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ Ð¿Ñ€Ð°ÐºÑ€ÑƒÑ‚ÐºÐ° +pdfjs-scroll-wrapped-button = + .title = Ужываць маштабавальную пракрутку +pdfjs-scroll-wrapped-button-label = ÐœÐ°ÑˆÑ‚Ð°Ð±Ð°Ð²Ð°Ð»ÑŒÐ½Ð°Ñ Ð¿Ñ€Ð°ÐºÑ€ÑƒÑ‚ÐºÐ° +pdfjs-spread-none-button = + .title = Ðе выкарыÑтоўваць Ñ€Ð°Ð·Ð³Ð¾Ñ€Ð½ÑƒÑ‚Ñ‹Ñ Ñтаронкі +pdfjs-spread-none-button-label = Без разгорнутых Ñтаронак +pdfjs-spread-odd-button = + .title = Ð Ð°Ð·Ð³Ð¾Ñ€Ð½ÑƒÑ‚Ñ‹Ñ Ñтаронкі пачынаючы з нÑцотных нумароў +pdfjs-spread-odd-button-label = ÐÑÑ†Ð¾Ñ‚Ð½Ñ‹Ñ Ñтаронкі злева +pdfjs-spread-even-button = + .title = Ð Ð°Ð·Ð³Ð¾Ñ€Ð½ÑƒÑ‚Ñ‹Ñ Ñтаронкі пачынаючы з цотных нумароў +pdfjs-spread-even-button-label = Ð¦Ð¾Ñ‚Ð½Ñ‹Ñ Ñтаронкі злева + +## Document properties dialog + +pdfjs-document-properties-button = + .title = УлаÑціваÑці дакумента… +pdfjs-document-properties-button-label = УлаÑціваÑці дакумента… +pdfjs-document-properties-file-name = Ðазва файла: +pdfjs-document-properties-file-size = Памер файла: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } КБ ({ $b } байтаў) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } МБ ({ $b } байтаў) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } КБ ({ $size_b } байт) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } МБ ({ $size_b } байт) +pdfjs-document-properties-title = Загаловак: +pdfjs-document-properties-author = Ðўтар: +pdfjs-document-properties-subject = ТÑма: +pdfjs-document-properties-keywords = ÐšÐ»ÑŽÑ‡Ð°Ð²Ñ‹Ñ Ñловы: +pdfjs-document-properties-creation-date = Дата ÑтварÑннÑ: +pdfjs-document-properties-modification-date = Дата змÑненнÑ: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Стваральнік: +pdfjs-document-properties-producer = Вырабнік PDF: +pdfjs-document-properties-version = ВерÑÑ–Ñ PDF: +pdfjs-document-properties-page-count = КолькаÑць Ñтаронак: +pdfjs-document-properties-page-size = Памер Ñтаронкі: +pdfjs-document-properties-page-size-unit-inches = цалÑÑž +pdfjs-document-properties-page-size-unit-millimeters = мм +pdfjs-document-properties-page-size-orientation-portrait = ÐºÐ½Ñ–Ð¶Ð½Ð°Ñ +pdfjs-document-properties-page-size-orientation-landscape = Ð°Ð»ÑŒÐ±Ð¾Ð¼Ð½Ð°Ñ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Хуткі праглÑд у ІнтÑрнÑце: +pdfjs-document-properties-linearized-yes = Так +pdfjs-document-properties-linearized-no = Ðе +pdfjs-document-properties-close-button = Закрыць + +## Print + +pdfjs-print-progress-message = Падрыхтоўка дакумента да друку… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = СкаÑаваць +pdfjs-printing-not-supported = ПапÑÑ€Ñджанне: друк не падтрымліваецца цалкам гÑтым браўзерам. +pdfjs-printing-not-ready = Увага: PDF не ÑцÑгнуты цалкам Ð´Ð»Ñ Ð´Ñ€ÑƒÐºÐ°Ð²Ð°Ð½Ð½Ñ. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Паказаць/Ñхаваць бакавую панÑль +pdfjs-toggle-sidebar-notification-button = + .title = Паказаць/Ñхаваць бакавую панÑль (дакумент мае змеÑÑ‚/укладанні/плаÑты) +pdfjs-toggle-sidebar-button-label = Паказаць/Ñхаваць бакавую панÑль +pdfjs-document-outline-button = + .title = Паказаць Ñтруктуру дакумента (Ð´Ð²Ð°Ð¹Ð½Ð°Ñ Ð¿Ñтрычка, каб разгарнуць /згарнуць уÑе Ñлементы) +pdfjs-document-outline-button-label = Структура дакумента +pdfjs-attachments-button = + .title = Паказаць далучÑнні +pdfjs-attachments-button-label = ДалучÑнні +pdfjs-layers-button = + .title = Паказаць плаÑты (націÑніце двойчы, каб Ñкінуць уÑе плаÑты да прадвызначанага Ñтану) +pdfjs-layers-button-label = ПлаÑты +pdfjs-thumbs-button = + .title = Паказ мініÑцюр +pdfjs-thumbs-button-label = МініÑцюры +pdfjs-current-outline-item-button = + .title = ЗнайÑці бÑгучы Ñлемент Ñтруктуры +pdfjs-current-outline-item-button-label = БÑгучы Ñлемент Ñтруктуры +pdfjs-findbar-button = + .title = Пошук у дакуменце +pdfjs-findbar-button-label = ЗнайÑці +pdfjs-additional-layers = Ð”Ð°Ð´Ð°Ñ‚ÐºÐ¾Ð²Ñ‹Ñ Ð¿Ð»Ð°Ñты + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Старонка { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = МініÑцюра Ñтаронкі { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Шукаць + .placeholder = Шукаць у дакуменце… +pdfjs-find-previous-button = + .title = ЗнайÑці папÑÑ€Ñдні выпадак выразу +pdfjs-find-previous-button-label = ПапÑÑ€Ñдні +pdfjs-find-next-button = + .title = ЗнайÑці наÑтупны выпадак выразу +pdfjs-find-next-button-label = ÐаÑтупны +pdfjs-find-highlight-checkbox = Падфарбаваць уÑе +pdfjs-find-match-case-checkbox-label = Ðдрозніваць вÑлікіÑ/Ð¼Ð°Ð»Ñ‹Ñ Ð»Ñ–Ñ‚Ð°Ñ€Ñ‹ +pdfjs-find-match-diacritics-checkbox-label = З улікам дыÑкрытык +pdfjs-find-entire-word-checkbox-label = Словы цалкам +pdfjs-find-reached-top = ДаÑÑгнуты пачатак дакумента, працÑг з канца +pdfjs-find-reached-bottom = ДаÑÑгнуты канец дакумента, працÑг з пачатку +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } з { $total } ÑупадзеннÑÑž + [few] { $current } з { $total } ÑупадзеннÑÑž + *[many] { $current } з { $total } ÑупадзеннÑÑž + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Больш за { $limit } Ñупадзенне + [few] Больш за { $limit } Ñупадзенні + *[many] Больш за { $limit } ÑупадзеннÑÑž + } +pdfjs-find-not-found = Выраз не знойдзены + +## Predefined zoom values + +pdfjs-page-scale-width = Ð¨Ñ‹Ñ€Ñ‹Ð½Ñ Ñтаронкі +pdfjs-page-scale-fit = УціÑненне Ñтаронкі +pdfjs-page-scale-auto = Ðўтаматычнае павелічÑнне +pdfjs-page-scale-actual = Сапраўдны памер +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Старонка { $page } + +## Loading indicator messages + +pdfjs-loading-error = ЗдарылаÑÑ Ð¿Ð°Ð¼Ñ‹Ð»ÐºÐ° ў чаÑе загрузкі PDF. +pdfjs-invalid-file-error = ÐÑÑпраўны або пашкоджаны файл PDF. +pdfjs-missing-file-error = ÐдÑутны файл PDF. +pdfjs-unexpected-response-error = Ðечаканы адказ Ñервера. +pdfjs-rendering-error = ЗдарылаÑÑ Ð¿Ð°Ð¼Ñ‹Ð»ÐºÐ° Ð¿Ð°Ð´Ñ‡Ð°Ñ Ð°Ð´Ð»ÑŽÑÑ‚Ñ€Ð°Ð²Ð°Ð½Ð½Ñ Ñтаронкі. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Annotation] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = УвÑдзіце пароль, каб адкрыць гÑты файл PDF. +pdfjs-password-invalid = ÐÑдзейÑны пароль. ПаÑпрабуйце зноў. +pdfjs-password-ok-button = Добра +pdfjs-password-cancel-button = СкаÑаваць +pdfjs-web-fonts-disabled = Шрыфты Сеціва забаронены: немагчыма ўжываць ÑƒÐºÐ»Ð°Ð´Ð·ÐµÐ½Ñ‹Ñ ÑˆÑ€Ñ‹Ñ„Ñ‚Ñ‹ PDF. + +## Editing + +pdfjs-editor-free-text-button = + .title = ТÑкÑÑ‚ +pdfjs-editor-free-text-button-label = ТÑкÑÑ‚ +pdfjs-editor-ink-button = + .title = МалÑваць +pdfjs-editor-ink-button-label = МалÑваць +pdfjs-editor-stamp-button = + .title = Дадаць або змÑніць выÑвы +pdfjs-editor-stamp-button-label = Дадаць або змÑніць выÑвы +pdfjs-editor-highlight-button = + .title = ВылучÑнне +pdfjs-editor-highlight-button-label = ВылучÑнне +pdfjs-highlight-floating-button1 = + .title = Падфарбаваць + .aria-label = Падфарбаваць +pdfjs-highlight-floating-button-label = Падфарбаваць + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Выдаліць малюнак +pdfjs-editor-remove-freetext-button = + .title = Выдаліць Ñ‚ÑкÑÑ‚ +pdfjs-editor-remove-stamp-button = + .title = Выдаліць выÑву +pdfjs-editor-remove-highlight-button = + .title = Выдаліць падфарбоўку + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Колер +pdfjs-editor-free-text-size-input = Памер +pdfjs-editor-ink-color-input = Колер +pdfjs-editor-ink-thickness-input = Ð¢Ð°ÑžÑˆÑ‡Ñ‹Ð½Ñ +pdfjs-editor-ink-opacity-input = ÐепразрыÑтаÑць +pdfjs-editor-stamp-add-image-button = + .title = Дадаць выÑву +pdfjs-editor-stamp-add-image-button-label = Дадаць выÑву +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Ð¢Ð°ÑžÑˆÑ‡Ñ‹Ð½Ñ +pdfjs-editor-free-highlight-thickness-title = + .title = ЗмÑнÑць таўшчыню пры вылучÑнні іншых Ñлементаў, Ð°ÐºÑ€Ð°Ð¼Ñ Ñ‚ÑкÑту +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = ТÑкÑтавы Ñ€Ñдактар + .default-content = Пачніце ўводзіць… +pdfjs-free-text = + .aria-label = ТÑкÑтавы Ñ€Ñдактар +pdfjs-free-text-default-content = Пачніце набор Ñ‚ÑкÑту… +pdfjs-ink = + .aria-label = Графічны Ñ€Ñдактар +pdfjs-ink-canvas = + .aria-label = Ð’Ñ‹Ñва, ÑÑ‚Ð²Ð¾Ñ€Ð°Ð½Ð°Ñ ÐºÐ°Ñ€Ñ‹Ñтальнікам + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = ÐльтÑрнатыўны Ñ‚ÑкÑÑ‚ +pdfjs-editor-alt-text-edit-button = + .aria-label = ЗмÑніць альтÑрнатыўны Ñ‚ÑкÑÑ‚ +pdfjs-editor-alt-text-edit-button-label = ЗмÑніць альтÑрнатыўны Ñ‚ÑкÑÑ‚ +pdfjs-editor-alt-text-dialog-label = Выберыце варыÑнт +pdfjs-editor-alt-text-dialog-description = ÐльтÑрнатыўны Ñ‚ÑкÑÑ‚ дапамагае, калі людзі не бачаць выÑву або калі Ñна не загружаецца. +pdfjs-editor-alt-text-add-description-label = Дадаць апіÑанне +pdfjs-editor-alt-text-add-description-description = СтарайцеÑÑ ÑклаÑці 1-2 Ñказы, ÑÐºÑ–Ñ Ð°Ð¿Ñ–Ñваюць прадмет, абÑтаноўку або дзеÑнні. +pdfjs-editor-alt-text-mark-decorative-label = Пазначыць Ñк дÑкаратыўны +pdfjs-editor-alt-text-mark-decorative-description = ВыкарыÑтоўваецца Ð´Ð»Ñ Ð´Ñкаратыўных выÑваў, такіх Ñк рамкі або вадзÑÐ½Ñ‹Ñ Ð·Ð½Ð°ÐºÑ–. +pdfjs-editor-alt-text-cancel-button = СкаÑаваць +pdfjs-editor-alt-text-save-button = Захаваць +pdfjs-editor-alt-text-decorative-tooltip = Пазначаны Ñк дÑкаратыўны +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Ðапрыклад, «Малады чалавек Ñадзіцца за Ñтол еÑці» +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = ÐльтÑрнатыўны Ñ‚ÑкÑÑ‚ + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Верхні левы кут — змÑніць памер +pdfjs-editor-resizer-label-top-middle = УверÑе паÑÑÑ€Ñдзіне — змÑніць памер +pdfjs-editor-resizer-label-top-right = Верхні правы кут — змÑніць памер +pdfjs-editor-resizer-label-middle-right = ПаÑÑÑ€Ñдзіне Ñправа — змÑніць памер +pdfjs-editor-resizer-label-bottom-right = Правы ніжні кут — змÑніць памер +pdfjs-editor-resizer-label-bottom-middle = ПаÑÑÑ€Ñдзіне ўнізе — змÑніць памер +pdfjs-editor-resizer-label-bottom-left = Левы ніжні кут — змÑніць памер +pdfjs-editor-resizer-label-middle-left = ПаÑÑÑ€Ñдзіне злева — змÑніць памер +pdfjs-editor-resizer-top-left = + .aria-label = Верхні левы кут — змÑніць памер +pdfjs-editor-resizer-top-middle = + .aria-label = УверÑе паÑÑÑ€Ñдзіне — змÑніць памер +pdfjs-editor-resizer-top-right = + .aria-label = Верхні правы кут — змÑніць памер +pdfjs-editor-resizer-middle-right = + .aria-label = ПаÑÑÑ€Ñдзіне Ñправа — змÑніць памер +pdfjs-editor-resizer-bottom-right = + .aria-label = Правы ніжні кут — змÑніць памер +pdfjs-editor-resizer-bottom-middle = + .aria-label = ПаÑÑÑ€Ñдзіне ўнізе — змÑніць памер +pdfjs-editor-resizer-bottom-left = + .aria-label = Левы ніжні кут — змÑніць памер +pdfjs-editor-resizer-middle-left = + .aria-label = ПаÑÑÑ€Ñдзіне злева — змÑніць памер + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Колер падфарбоўкі +pdfjs-editor-colorpicker-button = + .title = ЗмÑніць колер +pdfjs-editor-colorpicker-dropdown = + .aria-label = Выбар колеру +pdfjs-editor-colorpicker-yellow = + .title = Жоўты +pdfjs-editor-colorpicker-green = + .title = ЗÑлёны +pdfjs-editor-colorpicker-blue = + .title = Блакітны +pdfjs-editor-colorpicker-pink = + .title = Ружовы +pdfjs-editor-colorpicker-red = + .title = Чырвоны + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Паказаць уÑе +pdfjs-editor-highlight-show-all-button = + .title = Паказаць уÑе + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = РÑдагаваць Ñ‚ÑкÑÑ‚ Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt (апіÑанне выÑвы) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Дадаць Ñ‚ÑкÑÑ‚ Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt (апіÑанне выÑвы) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Ðапішыце Ñваё апіÑанне тут… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Кароткае апіÑанне Ð´Ð»Ñ Ð»ÑŽÐ´Ð·ÐµÐ¹, ÑÐºÑ–Ñ Ð½Ðµ бачаць выÑву, ці калі выÑва не загружаецца. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = ГÑты Ñ‚ÑкÑÑ‚ Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt быў Ñтвораны аўтаматычна Ñ– можа быць недакладным +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Даведацца больш +pdfjs-editor-new-alt-text-create-automatically-button-label = Ствараць Ñ‚ÑкÑÑ‚ Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt аўтаматычна +pdfjs-editor-new-alt-text-not-now-button = Ðе зараз +pdfjs-editor-new-alt-text-error-title = Ðе ўдалоÑÑ Ð°ÑžÑ‚Ð°Ð¼Ð°Ñ‚Ñ‹Ñ‡Ð½Ð° Ñтварыць Ñ‚ÑкÑÑ‚ Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt +pdfjs-editor-new-alt-text-error-description = Калі лаÑка, напішыце ўлаÑны Ñ‚ÑкÑÑ‚ Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt або паўтарыце Ñпробу пазней. +pdfjs-editor-new-alt-text-error-close-button = Закрыць +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = СцÑгванне мадÑлі ШІ Ð´Ð»Ñ Ñ‚ÑкÑту Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt ({ $downloadedSize } з { $totalSize } МБ) + .aria-valuetext = СцÑгванне мадÑлі ШІ Ð´Ð»Ñ Ñ‚ÑкÑту Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt ({ $downloadedSize } з { $totalSize } МБ) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = ТÑкÑÑ‚ Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt дададзены +pdfjs-editor-new-alt-text-added-button-label = ТÑкÑÑ‚ Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt дададзены +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = ÐдÑутнічае Ñ‚ÑкÑÑ‚ Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt +pdfjs-editor-new-alt-text-missing-button-label = ÐдÑутнічае Ñ‚ÑкÑÑ‚ Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Водгук на Ñ‚ÑкÑÑ‚ Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt +pdfjs-editor-new-alt-text-to-review-button-label = Водгук на Ñ‚ÑкÑÑ‚ Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Створаны аўтаматычна: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Ðалады альтÑрнатыўнага Ñ‚ÑкÑту Ð´Ð»Ñ Ð²Ñ‹Ñвы +pdfjs-image-alt-text-settings-button-label = Ðалады альтÑрнатыўнага Ñ‚ÑкÑту Ð´Ð»Ñ Ð²Ñ‹Ñвы +pdfjs-editor-alt-text-settings-dialog-label = Ðалады альтÑрнатыўнага Ñ‚ÑкÑту Ð´Ð»Ñ Ð²Ñ‹Ñвы +pdfjs-editor-alt-text-settings-automatic-title = Ðўтаматычны Ñ‚ÑкÑÑ‚ Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt +pdfjs-editor-alt-text-settings-create-model-button-label = Ствараць Ñ‚ÑкÑÑ‚ Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt аўтаматычна +pdfjs-editor-alt-text-settings-create-model-description = Прапануе апіÑанні, каб дапамагчы людзÑм, ÑÐºÑ–Ñ Ð½Ðµ бачаць выÑву, ці калі выÑва не загружаецца. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = МадÑль ШІ Ð´Ð»Ñ Ñ‚ÑкÑту Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt ({ $totalSize } МБ) +pdfjs-editor-alt-text-settings-ai-model-description = Працуе лакальна на вашай прыладзе, таму вашы звеÑткі заÑтаюцца прыватнымі. Патрабуецца Ð´Ð»Ñ Ð°ÑžÑ‚Ð°Ð¼Ð°Ñ‚Ñ‹Ñ‡Ð½Ð°Ð³Ð° альтÑрнатыўнага Ñ‚ÑкÑту. +pdfjs-editor-alt-text-settings-delete-model-button = Выдаліць +pdfjs-editor-alt-text-settings-download-model-button = СцÑгнуць +pdfjs-editor-alt-text-settings-downloading-model-button = СцÑгванне… +pdfjs-editor-alt-text-settings-editor-title = РÑдактар Ñ‚ÑкÑту Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt +pdfjs-editor-alt-text-settings-show-dialog-button-label = Ðдразу паказваць Ñ€Ñдактар Ñ‚ÑкÑту Ð´Ð»Ñ Ð°Ñ‚Ñ€Ñ‹Ð±ÑƒÑ‚Ð° alt пры даданні выÑвы +pdfjs-editor-alt-text-settings-show-dialog-description = Дапамагае пераканацца, што ÑžÑе вашы выÑвы маюць альтÑрнатыўны Ñ‚ÑкÑÑ‚. +pdfjs-editor-alt-text-settings-close-button = Закрыць + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = ПадÑвÑтленне выдалена +pdfjs-editor-undo-bar-message-freetext = ТÑкÑÑ‚ выдалены +pdfjs-editor-undo-bar-message-ink = Малюнак выдалены +pdfjs-editor-undo-bar-message-stamp = Ð’Ñ–Ð´Ð°Ñ€Ñ‹Ñ Ð²Ñ‹Ð´Ð°Ð»ÐµÐ½Ñ‹ +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } Ð°Ð½Ð°Ñ‚Ð°Ñ†Ñ‹Ñ Ð²Ñ‹Ð´Ð°Ð»ÐµÐ½Ð° + [few] { $count } анатацыі выдалена + *[many] { $count } анатацый выдалена + } +pdfjs-editor-undo-bar-undo-button = + .title = ÐдмÑніць +pdfjs-editor-undo-bar-undo-button-label = ÐдмÑніць +pdfjs-editor-undo-bar-close-button = + .title = Закрыць +pdfjs-editor-undo-bar-close-button-label = Закрыць diff --git a/public/assets/pdfjs/locale/bg/viewer.ftl b/public/assets/pdfjs/locale/bg/viewer.ftl new file mode 100755 index 0000000..8b1124e --- /dev/null +++ b/public/assets/pdfjs/locale/bg/viewer.ftl @@ -0,0 +1,418 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Предишна Ñтраница +pdfjs-previous-button-label = Предишна +pdfjs-next-button = + .title = Следваща Ñтраница +pdfjs-next-button-label = Следваща +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Страница +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = от { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } от { $pagesCount }) +pdfjs-zoom-out-button = + .title = ÐамалÑване +pdfjs-zoom-out-button-label = ÐамалÑване +pdfjs-zoom-in-button = + .title = Увеличаване +pdfjs-zoom-in-button-label = Увеличаване +pdfjs-zoom-select = + .title = Мащабиране +pdfjs-presentation-mode-button = + .title = Превключване към режим на предÑтавÑне +pdfjs-presentation-mode-button-label = Режим на предÑтавÑне +pdfjs-open-file-button = + .title = ОтварÑне на файл +pdfjs-open-file-button-label = ОтварÑне +pdfjs-print-button = + .title = Отпечатване +pdfjs-print-button-label = Отпечатване +pdfjs-save-button = + .title = Запазване +pdfjs-save-button-label = Запазване +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = ИзтеглÑне +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = ИзтеглÑне +pdfjs-bookmark-button = + .title = Текуща Ñтраница (преглед на адреÑа на Ñтраницата) +pdfjs-bookmark-button-label = Текуща Ñтраница + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = ИнÑтрументи +pdfjs-tools-button-label = ИнÑтрументи +pdfjs-first-page-button = + .title = Към първата Ñтраница +pdfjs-first-page-button-label = Към първата Ñтраница +pdfjs-last-page-button = + .title = Към поÑледната Ñтраница +pdfjs-last-page-button-label = Към поÑледната Ñтраница +pdfjs-page-rotate-cw-button = + .title = Завъртане по чаÑ. Ñтрелка +pdfjs-page-rotate-cw-button-label = Завъртане по чаÑовниковата Ñтрелка +pdfjs-page-rotate-ccw-button = + .title = Завъртане обратно на чаÑ. Ñтрелка +pdfjs-page-rotate-ccw-button-label = Завъртане обратно на чаÑовниковата Ñтрелка +pdfjs-cursor-text-select-tool-button = + .title = Включване на инÑтрумента за избор на текÑÑ‚ +pdfjs-cursor-text-select-tool-button-label = ИнÑтрумент за избор на текÑÑ‚ +pdfjs-cursor-hand-tool-button = + .title = Включване на инÑтрумента ръка +pdfjs-cursor-hand-tool-button-label = ИнÑтрумент ръка +pdfjs-scroll-page-button = + .title = Използване на плъзгане на Ñтраници +pdfjs-scroll-page-button-label = Плъзгане на Ñтраници +pdfjs-scroll-vertical-button = + .title = Използване на вертикално плъзгане +pdfjs-scroll-vertical-button-label = Вертикално плъзгане +pdfjs-scroll-horizontal-button = + .title = Използване на хоризонтално +pdfjs-scroll-horizontal-button-label = Хоризонтално плъзгане +pdfjs-scroll-wrapped-button = + .title = Използване на мащабируемо плъзгане +pdfjs-scroll-wrapped-button-label = Мащабируемо плъзгане +pdfjs-spread-none-button = + .title = Режимът на ÑдвоÑване е изключен +pdfjs-spread-none-button-label = Без ÑдвоÑване +pdfjs-spread-odd-button = + .title = СдвоÑване, започвайки от нечетните Ñтраници +pdfjs-spread-odd-button-label = Ðечетните отлÑво +pdfjs-spread-even-button = + .title = СдвоÑване, започвайки от четните Ñтраници +pdfjs-spread-even-button-label = Четните отлÑво + +## Document properties dialog + +pdfjs-document-properties-button = + .title = СвойÑтва на документа… +pdfjs-document-properties-button-label = СвойÑтва на документа… +pdfjs-document-properties-file-name = Име на файл: +pdfjs-document-properties-file-size = Големина на файл: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } КБ ({ $b } байта) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } МБ ({ $b } байта) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } КБ ({ $size_b } байта) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } МБ ({ $size_b } байта) +pdfjs-document-properties-title = Заглавие: +pdfjs-document-properties-author = Ðвтор: +pdfjs-document-properties-subject = Тема: +pdfjs-document-properties-keywords = Ключови думи: +pdfjs-document-properties-creation-date = Дата на Ñъздаване: +pdfjs-document-properties-modification-date = Дата на промÑна: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Създател: +pdfjs-document-properties-producer = PDF произведен от: +pdfjs-document-properties-version = Издание на PDF: +pdfjs-document-properties-page-count = Брой Ñтраници: +pdfjs-document-properties-page-size = Размер на Ñтраницата: +pdfjs-document-properties-page-size-unit-inches = инч +pdfjs-document-properties-page-size-unit-millimeters = мм +pdfjs-document-properties-page-size-orientation-portrait = портрет +pdfjs-document-properties-page-size-orientation-landscape = пейзаж +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Правни въпроÑи + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Бърз преглед: +pdfjs-document-properties-linearized-yes = Да +pdfjs-document-properties-linearized-no = Ðе +pdfjs-document-properties-close-button = ЗатварÑне + +## Print + +pdfjs-print-progress-message = ПодготвÑне на документа за отпечатване… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Отказ +pdfjs-printing-not-supported = Внимание: Този четец нÑма пълна поддръжка на отпечатване. +pdfjs-printing-not-ready = Внимание: Този PDF файл не е напълно зареден за печат. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Превключване на Ñтраничната лента +pdfjs-toggle-sidebar-notification-button = + .title = Превключване на Ñтраничната лента (документът има Ñтруктура/прикачени файлове/Ñлоеве) +pdfjs-toggle-sidebar-button-label = Превключване на Ñтраничната лента +pdfjs-document-outline-button = + .title = Показване на Ñтруктурата на документа (двукратно щракване за Ñвиване/разгъване на вÑичко) +pdfjs-document-outline-button-label = Структура на документа +pdfjs-attachments-button = + .title = Показване на притурките +pdfjs-attachments-button-label = Притурки +pdfjs-layers-button = + .title = Показване на Ñлоевете (двукратно щракване за възÑтановÑване на вÑички Ñлоеве към ÑÑŠÑтоÑнието по подразбиране) +pdfjs-layers-button-label = Слоеве +pdfjs-thumbs-button = + .title = Показване на миниатюрите +pdfjs-thumbs-button-label = Миниатюри +pdfjs-current-outline-item-button = + .title = Ðамиране на Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ ÐµÐ»ÐµÐ¼ÐµÐ½Ñ‚ от Ñтруктурата +pdfjs-current-outline-item-button-label = Текущ елемент от Ñтруктурата +pdfjs-findbar-button = + .title = Ðамиране в документа +pdfjs-findbar-button-label = ТърÑене +pdfjs-additional-layers = Допълнителни Ñлоеве + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Страница { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Миниатюра на Ñтраница { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = ТърÑене + .placeholder = ТърÑене в документа… +pdfjs-find-previous-button = + .title = Ðамиране на предишно Ñъвпадение на фразата +pdfjs-find-previous-button-label = Предишна +pdfjs-find-next-button = + .title = Ðамиране на Ñледващо Ñъвпадение на фразата +pdfjs-find-next-button-label = Следваща +pdfjs-find-highlight-checkbox = ОткроÑване на вÑички +pdfjs-find-match-case-checkbox-label = Съвпадение на региÑтъра +pdfjs-find-match-diacritics-checkbox-label = Без производни букви +pdfjs-find-entire-word-checkbox-label = Цели думи +pdfjs-find-reached-top = ДоÑтигнато е началото на документа, продължаване от ÐºÑ€Ð°Ñ +pdfjs-find-reached-bottom = ДоÑтигнат е краÑÑ‚ на документа, продължаване от началото +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } от { $total } Ñъвпадение + *[other] { $current } от { $total } ÑÑŠÐ²Ð¿Ð°Ð´ÐµÐ½Ð¸Ñ + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Повече от { $limit } Ñъвпадение + *[other] Повече от { $limit } ÑÑŠÐ²Ð¿Ð°Ð´ÐµÐ½Ð¸Ñ + } +pdfjs-find-not-found = Фразата не е намерена + +## Predefined zoom values + +pdfjs-page-scale-width = Ширина на Ñтраницата +pdfjs-page-scale-fit = ВмеÑтване в Ñтраницата +pdfjs-page-scale-auto = Ðвтоматично мащабиране +pdfjs-page-scale-actual = ДейÑтвителен размер +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Страница { $page } + +## Loading indicator messages + +pdfjs-loading-error = Получи Ñе грешка при зареждане на PDF-а. +pdfjs-invalid-file-error = Ðевалиден или повреден PDF файл. +pdfjs-missing-file-error = ЛипÑващ PDF файл. +pdfjs-unexpected-response-error = Ðеочакван отговор от Ñървъра. +pdfjs-rendering-error = Грешка при изчертаване на Ñтраницата. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [ÐÐ½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Въведете парола за отварÑне на този PDF файл. +pdfjs-password-invalid = Ðевалидна парола. МолÑ, опитайте отново. +pdfjs-password-ok-button = Добре +pdfjs-password-cancel-button = Отказ +pdfjs-web-fonts-disabled = Уеб-шрифтовете Ñа забранени: разрешаване на използването на вградените PDF шрифтове. + +## Editing + +pdfjs-editor-free-text-button = + .title = ТекÑÑ‚ +pdfjs-editor-free-text-button-label = ТекÑÑ‚ +pdfjs-editor-ink-button = + .title = РиÑуване +pdfjs-editor-ink-button-label = РиÑуване +pdfjs-editor-stamp-button = + .title = ДобавÑне или променÑне на Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ +pdfjs-editor-stamp-button-label = ДобавÑне или променÑне на Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Премахване на риÑунката +pdfjs-editor-remove-freetext-button = + .title = Премахване на текÑта +pdfjs-editor-remove-stamp-button = + .title = Пермахване на изображението +pdfjs-editor-remove-highlight-button = + .title = Премахване на откроÑването + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = ЦвÑÑ‚ +pdfjs-editor-free-text-size-input = Размер +pdfjs-editor-ink-color-input = ЦвÑÑ‚ +pdfjs-editor-ink-thickness-input = Дебелина +pdfjs-editor-ink-opacity-input = ПрозрачноÑÑ‚ +pdfjs-editor-stamp-add-image-button = + .title = ДобавÑне на изображение +pdfjs-editor-stamp-add-image-button-label = ДобавÑне на изображение +pdfjs-free-text = + .aria-label = ТекÑтов редактор +pdfjs-free-text-default-content = Започнете да пишете… +pdfjs-ink = + .aria-label = ПромÑна на риÑунка +pdfjs-ink-canvas = + .aria-label = Изображение, Ñъздадено от потребител + +## Alt-text dialog + +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button-label = Ðлтернативен текÑÑ‚ +pdfjs-editor-alt-text-edit-button-label = ПромÑна на Ð°Ð»Ñ‚ÐµÑ€Ð½Ð°Ñ‚Ð¸Ð²Ð½Ð¸Ñ Ñ‚ÐµÐºÑÑ‚ +pdfjs-editor-alt-text-dialog-label = Изберете от възможноÑтите +pdfjs-editor-alt-text-dialog-description = ÐлтернативниÑÑ‚ текÑÑ‚ помага на потребителите, когато не могат да видÑÑ‚ изображението или то не Ñе зарежда. +pdfjs-editor-alt-text-add-description-label = ДобавÑне на опиÑание +pdfjs-editor-alt-text-add-description-description = Стремете Ñе към 1-2 изречениÑ, опиÑващи предмета, наÑтройката или дейÑтвиÑта. +pdfjs-editor-alt-text-mark-decorative-label = ОтбелÑзване като декоративно +pdfjs-editor-alt-text-mark-decorative-description = Използва Ñе за орнаменти или декоративни изображениÑ, като контури и водни знаци. +pdfjs-editor-alt-text-cancel-button = Отказ +pdfjs-editor-alt-text-save-button = Запазване +pdfjs-editor-alt-text-decorative-tooltip = ОтбелÑзване като декоративно +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Ðапример, „Млад мъж Ñеди на маÑа и Ñе храни“ + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Горен лÑв ъгъл — преоразмерÑване +pdfjs-editor-resizer-label-top-middle = Горе в Ñредата — преоразмерÑване +pdfjs-editor-resizer-label-top-right = Горен деÑен ъгъл — преоразмерÑване +pdfjs-editor-resizer-label-middle-right = ДÑÑно в Ñредата — преоразмерÑване +pdfjs-editor-resizer-label-bottom-right = Долен деÑен ъгъл — преоразмерÑване +pdfjs-editor-resizer-label-bottom-middle = Долу в Ñредата — преоразмерÑване +pdfjs-editor-resizer-label-bottom-left = Долен лÑв ъгъл — преоразмерÑване +pdfjs-editor-resizer-label-middle-left = ЛÑво в Ñредата — преоразмерÑване +pdfjs-editor-resizer-top-left = + .aria-label = Горен лÑв ъгъл — преоразмерÑване +pdfjs-editor-resizer-top-middle = + .aria-label = Горе в Ñредата — преоразмерÑване +pdfjs-editor-resizer-top-right = + .aria-label = Горен деÑен ъгъл — преоразмерÑване +pdfjs-editor-resizer-middle-right = + .aria-label = ДÑÑно в Ñредата — преоразмерÑване +pdfjs-editor-resizer-bottom-right = + .aria-label = Долен деÑен ъгъл — преоразмерÑване +pdfjs-editor-resizer-bottom-middle = + .aria-label = Долу в Ñредата — преоразмерÑване +pdfjs-editor-resizer-bottom-left = + .aria-label = Долен лÑв ъгъл — преоразмерÑване +pdfjs-editor-resizer-middle-left = + .aria-label = ЛÑво в Ñредата — преоразмерÑване + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = ЦвÑÑ‚ на откроÑване +pdfjs-editor-colorpicker-button = + .title = ПромÑна на цвÑÑ‚ +pdfjs-editor-colorpicker-dropdown = + .aria-label = Избор на цвÑÑ‚ +pdfjs-editor-colorpicker-yellow = + .title = Жълто +pdfjs-editor-colorpicker-green = + .title = Зелено +pdfjs-editor-colorpicker-blue = + .title = Синьо +pdfjs-editor-colorpicker-pink = + .title = Розово +pdfjs-editor-colorpicker-red = + .title = Червено + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +pdfjs-editor-new-alt-text-not-now-button = Ðе Ñега + +## Image alt-text settings + diff --git a/public/assets/pdfjs/locale/bn/viewer.ftl b/public/assets/pdfjs/locale/bn/viewer.ftl new file mode 100755 index 0000000..1e20ecb --- /dev/null +++ b/public/assets/pdfjs/locale/bn/viewer.ftl @@ -0,0 +1,247 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = পূরà§à¦¬à¦¬à¦°à§à¦¤à§€ পাতা +pdfjs-previous-button-label = পূরà§à¦¬à¦¬à¦°à§à¦¤à§€ +pdfjs-next-button = + .title = পরবরà§à¦¤à§€ পাতা +pdfjs-next-button-label = পরবরà§à¦¤à§€ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = পাতা +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = { $pagesCount } à¦à¦° +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pagesCount } à¦à¦° { $pageNumber }) +pdfjs-zoom-out-button = + .title = ছোট আকারে পà§à¦°à¦¦à¦°à§à¦¶à¦¨ +pdfjs-zoom-out-button-label = ছোট আকারে পà§à¦°à¦¦à¦°à§à¦¶à¦¨ +pdfjs-zoom-in-button = + .title = বড় আকারে পà§à¦°à¦¦à¦°à§à¦¶à¦¨ +pdfjs-zoom-in-button-label = বড় আকারে পà§à¦°à¦¦à¦°à§à¦¶à¦¨ +pdfjs-zoom-select = + .title = বড় আকারে পà§à¦°à¦¦à¦°à§à¦¶à¦¨ +pdfjs-presentation-mode-button = + .title = উপসà§à¦¥à¦¾à¦ªà¦¨à¦¾ মোডে সà§à¦¯à§à¦‡à¦š করà§à¦¨ +pdfjs-presentation-mode-button-label = উপসà§à¦¥à¦¾à¦ªà¦¨à¦¾ মোড +pdfjs-open-file-button = + .title = ফাইল খà§à¦²à§à¦¨ +pdfjs-open-file-button-label = খà§à¦²à§à¦¨ +pdfjs-print-button = + .title = মà§à¦¦à§à¦°à¦£ +pdfjs-print-button-label = মà§à¦¦à§à¦°à¦£ + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = টà§à¦² +pdfjs-tools-button-label = টà§à¦² +pdfjs-first-page-button = + .title = পà§à¦°à¦¥à¦® পাতায় যাও +pdfjs-first-page-button-label = পà§à¦°à¦¥à¦® পাতায় যাও +pdfjs-last-page-button = + .title = শেষ পাতায় যাও +pdfjs-last-page-button-label = শেষ পাতায় যাও +pdfjs-page-rotate-cw-button = + .title = ঘড়ির কাà¦à¦Ÿà¦¾à¦° দিকে ঘোরাও +pdfjs-page-rotate-cw-button-label = ঘড়ির কাà¦à¦Ÿà¦¾à¦° দিকে ঘোরাও +pdfjs-page-rotate-ccw-button = + .title = ঘড়ির কাà¦à¦Ÿà¦¾à¦° বিপরীতে ঘোরাও +pdfjs-page-rotate-ccw-button-label = ঘড়ির কাà¦à¦Ÿà¦¾à¦° বিপরীতে ঘোরাও +pdfjs-cursor-text-select-tool-button = + .title = লেখা নিরà§à¦¬à¦¾à¦šà¦• টà§à¦² সকà§à¦°à¦¿à§Ÿ করà§à¦¨ +pdfjs-cursor-text-select-tool-button-label = লেখা নিরà§à¦¬à¦¾à¦šà¦• টà§à¦² +pdfjs-cursor-hand-tool-button = + .title = হà§à¦¯à¦¾à¦¨à§à¦¡ টà§à¦² সকà§à¦°à¦¿à¦¯à¦¼ করà§à¦¨ +pdfjs-cursor-hand-tool-button-label = হà§à¦¯à¦¾à¦¨à§à¦¡ টà§à¦² +pdfjs-scroll-vertical-button = + .title = উলমà§à¦¬ সà§à¦•à§à¦°à¦²à¦¿à¦‚ বà§à¦¯à¦¬à¦¹à¦¾à¦° করà§à¦¨ +pdfjs-scroll-vertical-button-label = উলমà§à¦¬ সà§à¦•à§à¦°à¦²à¦¿à¦‚ +pdfjs-scroll-horizontal-button = + .title = অনà§à¦­à§‚মিক সà§à¦•à§à¦°à¦²à¦¿à¦‚ বà§à¦¯à¦¬à¦¹à¦¾à¦° করà§à¦¨ +pdfjs-scroll-horizontal-button-label = অনà§à¦­à§‚মিক সà§à¦•à§à¦°à¦²à¦¿à¦‚ +pdfjs-scroll-wrapped-button = + .title = Wrapped সà§à¦•à§à¦°à§‹à¦²à¦¿à¦‚ বà§à¦¯à¦¬à¦¹à¦¾à¦° করà§à¦¨ +pdfjs-scroll-wrapped-button-label = Wrapped সà§à¦•à§à¦°à§‹à¦²à¦¿à¦‚ +pdfjs-spread-none-button = + .title = পেজ সà§à¦ªà§à¦°à§‡à¦¡à¦—à§à¦²à§‹à¦¤à§‡ যোগদান করবেন না +pdfjs-spread-none-button-label = Spreads নেই +pdfjs-spread-odd-button-label = বিজোড় Spreads +pdfjs-spread-even-button-label = জোড় Spreads + +## Document properties dialog + +pdfjs-document-properties-button = + .title = নথি বৈশিষà§à¦Ÿà§à¦¯â€¦ +pdfjs-document-properties-button-label = নথি বৈশিষà§à¦Ÿà§à¦¯â€¦ +pdfjs-document-properties-file-name = ফাইলের নাম: +pdfjs-document-properties-file-size = ফাইলের আকার: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } কেবি ({ $size_b } বাইট) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } à¦à¦®à¦¬à¦¿ ({ $size_b } বাইট) +pdfjs-document-properties-title = শিরোনাম: +pdfjs-document-properties-author = লেখক: +pdfjs-document-properties-subject = বিষয়: +pdfjs-document-properties-keywords = কীওয়ারà§à¦¡: +pdfjs-document-properties-creation-date = তৈরির তারিখ: +pdfjs-document-properties-modification-date = পরিবরà§à¦¤à¦¨à§‡à¦° তারিখ: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = পà§à¦°à¦¸à§à¦¤à§à¦¤à¦•ারক: +pdfjs-document-properties-producer = পিডিà¦à¦« পà§à¦°à¦¸à§à¦¤à§à¦¤à¦•ারক: +pdfjs-document-properties-version = পিডিà¦à¦« সংষà§à¦•রণ: +pdfjs-document-properties-page-count = মোট পাতা: +pdfjs-document-properties-page-size = পাতার সাইজ: +pdfjs-document-properties-page-size-unit-inches = à¦à¦° মধà§à¦¯à§‡ +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = উলমà§à¦¬ +pdfjs-document-properties-page-size-orientation-landscape = অনà§à¦­à§‚মিক +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = লেটার +pdfjs-document-properties-page-size-name-legal = লীগাল + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Fast Web View: +pdfjs-document-properties-linearized-yes = হà§à¦¯à¦¾à¦ +pdfjs-document-properties-linearized-no = না +pdfjs-document-properties-close-button = বনà§à¦§ + +## Print + +pdfjs-print-progress-message = মà§à¦¦à§à¦°à¦£à§‡à¦° জনà§à¦¯ নথি পà§à¦°à¦¸à§à¦¤à§à¦¤ করা হচà§à¦›à§‡â€¦ +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = বাতিল +pdfjs-printing-not-supported = সতরà§à¦•তা: à¦à¦‡ বà§à¦°à¦¾à¦‰à¦œà¦¾à¦°à§‡ মà§à¦¦à§à¦°à¦£ সমà§à¦ªà§‚রà§à¦£à¦­à¦¾à¦¬à§‡ সমরà§à¦¥à¦¿à¦¤ নয়। +pdfjs-printing-not-ready = সতরà§à¦•ীকরণ: পিডিà¦à¦«à¦Ÿà¦¿ মà§à¦¦à§à¦°à¦£à§‡à¦° জনà§à¦¯ সমà§à¦ªà§‚রà§à¦£ লোড হয়নি। + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = সাইডবার টগল করà§à¦¨ +pdfjs-toggle-sidebar-button-label = সাইডবার টগল করà§à¦¨ +pdfjs-document-outline-button = + .title = নথির আউটলাইন দেখাও (সব আইটেম পà§à¦°à¦¸à¦¾à¦°à¦¿à¦¤/সঙà§à¦•à§à¦šà¦¿à¦¤ করতে ডবল কà§à¦²à¦¿à¦• করà§à¦¨) +pdfjs-document-outline-button-label = নথির রূপরেখা +pdfjs-attachments-button = + .title = সংযà§à¦•à§à¦¤à¦¿ দেখাও +pdfjs-attachments-button-label = সংযà§à¦•à§à¦¤à¦¿ +pdfjs-thumbs-button = + .title = থামà§à¦¬à¦¨à§‡à¦‡à¦² সমূহ পà§à¦°à¦¦à¦°à§à¦¶à¦¨ করà§à¦¨ +pdfjs-thumbs-button-label = থামà§à¦¬à¦¨à§‡à¦‡à¦² সমূহ +pdfjs-findbar-button = + .title = নথির মধà§à¦¯à§‡ খà§à¦à¦œà§à¦¨ +pdfjs-findbar-button-label = খà§à¦à¦œà§à¦¨ + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = পাতা { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = { $page } পাতার থামà§à¦¬à¦¨à§‡à¦‡à¦² + +## Find panel button title and messages + +pdfjs-find-input = + .title = খà§à¦à¦œà§à¦¨ + .placeholder = নথির মধà§à¦¯à§‡ খà§à¦à¦œà§à¦¨â€¦ +pdfjs-find-previous-button = + .title = বাকà§à¦¯à¦¾à¦‚শের পূরà§à¦¬à¦¬à¦°à§à¦¤à§€ উপসà§à¦¥à¦¿à¦¤à¦¿ অনà§à¦¸à¦¨à§à¦§à¦¾à¦¨ +pdfjs-find-previous-button-label = পূরà§à¦¬à¦¬à¦°à§à¦¤à§€ +pdfjs-find-next-button = + .title = বাকà§à¦¯à¦¾à¦‚শের পরবরà§à¦¤à§€ উপসà§à¦¥à¦¿à¦¤à¦¿ অনà§à¦¸à¦¨à§à¦§à¦¾à¦¨ +pdfjs-find-next-button-label = পরবরà§à¦¤à§€ +pdfjs-find-highlight-checkbox = সব হাইলাইট করà§à¦¨ +pdfjs-find-match-case-checkbox-label = অকà§à¦·à¦°à§‡à¦° ছাà¦à¦¦ মেলানো +pdfjs-find-entire-word-checkbox-label = সমà§à¦ªà§‚রà§à¦£ শবà§à¦¦ +pdfjs-find-reached-top = পাতার শà§à¦°à§à¦¤à§‡ পৌছে গেছে, নীচ থেকে আরমà§à¦­ করা হয়েছে +pdfjs-find-reached-bottom = পাতার শেষে পৌছে গেছে, উপর থেকে আরমà§à¦­ করা হয়েছে +pdfjs-find-not-found = বাকà§à¦¯à¦¾à¦‚শ পাওয়া যায়নি + +## Predefined zoom values + +pdfjs-page-scale-width = পাতার পà§à¦°à¦¸à§à¦¥ +pdfjs-page-scale-fit = পাতা ফিট করà§à¦¨ +pdfjs-page-scale-auto = সà§à¦¬à§Ÿà¦‚কà§à¦°à¦¿à§Ÿ জà§à¦® +pdfjs-page-scale-actual = পà§à¦°à¦•ৃত আকার +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = পিডিà¦à¦« লোড করার সময় তà§à¦°à§à¦Ÿà¦¿ দেখা দিয়েছে। +pdfjs-invalid-file-error = অকারà§à¦¯à¦•র অথবা কà§à¦·à¦¤à¦¿à¦—à§à¦°à¦¸à§à¦¤ পিডিà¦à¦« ফাইল। +pdfjs-missing-file-error = নিখোà¦à¦œ PDF ফাইল। +pdfjs-unexpected-response-error = অপà§à¦°à¦¤à§à¦¯à¦¾à¦¶à§€à¦¤ সারà§à¦­à¦¾à¦° পà§à¦°à¦¤à¦¿à¦•à§à¦°à¦¿à§Ÿà¦¾à¥¤ +pdfjs-rendering-error = পাতা উপসà§à¦¥à¦¾à¦ªà¦¨à¦¾à¦° সময় তà§à¦°à§à¦Ÿà¦¿ দেখা দিয়েছে। + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } টীকা] + +## Password + +pdfjs-password-label = পিডিà¦à¦« ফাইলটি ওপেন করতে পাসওয়ারà§à¦¡ দিন। +pdfjs-password-invalid = ভà§à¦² পাসওয়ারà§à¦¡à¥¤ অনà§à¦—à§à¦°à¦¹ করে আবার চেষà§à¦Ÿà¦¾ করà§à¦¨à¥¤ +pdfjs-password-ok-button = ঠিক আছে +pdfjs-password-cancel-button = বাতিল +pdfjs-web-fonts-disabled = ওয়েব ফনà§à¦Ÿ নিষà§à¦•à§à¦°à¦¿à§Ÿ: সংযà§à¦•à§à¦¤ পিডিà¦à¦« ফনà§à¦Ÿ বà§à¦¯à¦¬à¦¹à¦¾à¦° করা যাচà§à¦›à§‡ না। + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/bo/viewer.ftl b/public/assets/pdfjs/locale/bo/viewer.ftl new file mode 100755 index 0000000..824eab4 --- /dev/null +++ b/public/assets/pdfjs/locale/bo/viewer.ftl @@ -0,0 +1,247 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = དྲ་ངོས་སྔོན་མ +pdfjs-previous-button-label = སྔོན་མ +pdfjs-next-button = + .title = དྲ་ངོས་རྗེས་མ +pdfjs-next-button-label = རྗེས་མ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = ཤོག་ངོས +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = of { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount }) +pdfjs-zoom-out-button = + .title = Zoom Out +pdfjs-zoom-out-button-label = Zoom Out +pdfjs-zoom-in-button = + .title = Zoom In +pdfjs-zoom-in-button-label = Zoom In +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Switch to Presentation Mode +pdfjs-presentation-mode-button-label = Presentation Mode +pdfjs-open-file-button = + .title = Open File +pdfjs-open-file-button-label = Open +pdfjs-print-button = + .title = Print +pdfjs-print-button-label = Print + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Tools +pdfjs-tools-button-label = Tools +pdfjs-first-page-button = + .title = Go to First Page +pdfjs-first-page-button-label = Go to First Page +pdfjs-last-page-button = + .title = Go to Last Page +pdfjs-last-page-button-label = Go to Last Page +pdfjs-page-rotate-cw-button = + .title = Rotate Clockwise +pdfjs-page-rotate-cw-button-label = Rotate Clockwise +pdfjs-page-rotate-ccw-button = + .title = Rotate Counterclockwise +pdfjs-page-rotate-ccw-button-label = Rotate Counterclockwise +pdfjs-cursor-text-select-tool-button = + .title = Enable Text Selection Tool +pdfjs-cursor-text-select-tool-button-label = Text Selection Tool +pdfjs-cursor-hand-tool-button = + .title = Enable Hand Tool +pdfjs-cursor-hand-tool-button-label = Hand Tool +pdfjs-scroll-vertical-button = + .title = Use Vertical Scrolling +pdfjs-scroll-vertical-button-label = Vertical Scrolling +pdfjs-scroll-horizontal-button = + .title = Use Horizontal Scrolling +pdfjs-scroll-horizontal-button-label = Horizontal Scrolling +pdfjs-scroll-wrapped-button = + .title = Use Wrapped Scrolling +pdfjs-scroll-wrapped-button-label = Wrapped Scrolling +pdfjs-spread-none-button = + .title = Do not join page spreads +pdfjs-spread-none-button-label = No Spreads +pdfjs-spread-odd-button = + .title = Join page spreads starting with odd-numbered pages +pdfjs-spread-odd-button-label = Odd Spreads +pdfjs-spread-even-button = + .title = Join page spreads starting with even-numbered pages +pdfjs-spread-even-button-label = Even Spreads + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Document Properties… +pdfjs-document-properties-button-label = Document Properties… +pdfjs-document-properties-file-name = File name: +pdfjs-document-properties-file-size = File size: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Title: +pdfjs-document-properties-author = Author: +pdfjs-document-properties-subject = Subject: +pdfjs-document-properties-keywords = Keywords: +pdfjs-document-properties-creation-date = Creation Date: +pdfjs-document-properties-modification-date = Modification Date: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Creator: +pdfjs-document-properties-producer = PDF Producer: +pdfjs-document-properties-version = PDF Version: +pdfjs-document-properties-page-count = Page Count: +pdfjs-document-properties-page-size = Page Size: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = portrait +pdfjs-document-properties-page-size-orientation-landscape = landscape +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Fast Web View: +pdfjs-document-properties-linearized-yes = Yes +pdfjs-document-properties-linearized-no = No +pdfjs-document-properties-close-button = Close + +## Print + +pdfjs-print-progress-message = Preparing document for printing… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Cancel +pdfjs-printing-not-supported = Warning: Printing is not fully supported by this browser. +pdfjs-printing-not-ready = Warning: The PDF is not fully loaded for printing. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Toggle Sidebar +pdfjs-toggle-sidebar-button-label = Toggle Sidebar +pdfjs-document-outline-button = + .title = Show Document Outline (double-click to expand/collapse all items) +pdfjs-document-outline-button-label = Document Outline +pdfjs-attachments-button = + .title = Show Attachments +pdfjs-attachments-button-label = Attachments +pdfjs-thumbs-button = + .title = Show Thumbnails +pdfjs-thumbs-button-label = Thumbnails +pdfjs-findbar-button = + .title = Find in Document +pdfjs-findbar-button-label = Find + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Page { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Thumbnail of Page { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Find + .placeholder = Find in document… +pdfjs-find-previous-button = + .title = Find the previous occurrence of the phrase +pdfjs-find-previous-button-label = Previous +pdfjs-find-next-button = + .title = Find the next occurrence of the phrase +pdfjs-find-next-button-label = Next +pdfjs-find-highlight-checkbox = Highlight all +pdfjs-find-match-case-checkbox-label = Match case +pdfjs-find-entire-word-checkbox-label = Whole words +pdfjs-find-reached-top = Reached top of document, continued from bottom +pdfjs-find-reached-bottom = Reached end of document, continued from top +pdfjs-find-not-found = Phrase not found + +## Predefined zoom values + +pdfjs-page-scale-width = Page Width +pdfjs-page-scale-fit = Page Fit +pdfjs-page-scale-auto = Automatic Zoom +pdfjs-page-scale-actual = Actual Size +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = An error occurred while loading the PDF. +pdfjs-invalid-file-error = Invalid or corrupted PDF file. +pdfjs-missing-file-error = Missing PDF file. +pdfjs-unexpected-response-error = Unexpected server response. +pdfjs-rendering-error = An error occurred while rendering the page. + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Annotation] + +## Password + +pdfjs-password-label = Enter the password to open this PDF file. +pdfjs-password-invalid = Invalid password. Please try again. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Cancel +pdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fonts. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/br/viewer.ftl b/public/assets/pdfjs/locale/br/viewer.ftl new file mode 100755 index 0000000..60a3df0 --- /dev/null +++ b/public/assets/pdfjs/locale/br/viewer.ftl @@ -0,0 +1,340 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Pajenn a-raok +pdfjs-previous-button-label = A-raok +pdfjs-next-button = + .title = Pajenn war-lerc'h +pdfjs-next-button-label = War-lerc'h +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Pajenn +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = eus { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } war { $pagesCount }) +pdfjs-zoom-out-button = + .title = Zoum bihanaat +pdfjs-zoom-out-button-label = Zoum bihanaat +pdfjs-zoom-in-button = + .title = Zoum brasaat +pdfjs-zoom-in-button-label = Zoum brasaat +pdfjs-zoom-select = + .title = Zoum +pdfjs-presentation-mode-button = + .title = Trec'haoliñ etrezek ar mod kinnigadenn +pdfjs-presentation-mode-button-label = Mod kinnigadenn +pdfjs-open-file-button = + .title = Digeriñ ur restr +pdfjs-open-file-button-label = Digeriñ ur restr +pdfjs-print-button = + .title = Moullañ +pdfjs-print-button-label = Moullañ +pdfjs-save-button = + .title = Enrollañ +pdfjs-save-button-label = Enrollañ +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Pellgargañ +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Pellgargañ +pdfjs-bookmark-button-label = Pajenn a-vremañ + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Ostilhoù +pdfjs-tools-button-label = Ostilhoù +pdfjs-first-page-button = + .title = Mont d'ar bajenn gentañ +pdfjs-first-page-button-label = Mont d'ar bajenn gentañ +pdfjs-last-page-button = + .title = Mont d'ar bajenn diwezhañ +pdfjs-last-page-button-label = Mont d'ar bajenn diwezhañ +pdfjs-page-rotate-cw-button = + .title = C'hwelañ gant roud ar bizied +pdfjs-page-rotate-cw-button-label = C'hwelañ gant roud ar bizied +pdfjs-page-rotate-ccw-button = + .title = C'hwelañ gant roud gin ar bizied +pdfjs-page-rotate-ccw-button-label = C'hwelañ gant roud gin ar bizied +pdfjs-cursor-text-select-tool-button = + .title = Gweredekaat an ostilh diuzañ testenn +pdfjs-cursor-text-select-tool-button-label = Ostilh diuzañ testenn +pdfjs-cursor-hand-tool-button = + .title = Gweredekaat an ostilh dorn +pdfjs-cursor-hand-tool-button-label = Ostilh dorn +pdfjs-scroll-vertical-button = + .title = Arverañ an dibunañ a-blom +pdfjs-scroll-vertical-button-label = Dibunañ a-serzh +pdfjs-scroll-horizontal-button = + .title = Arverañ an dibunañ a-blaen +pdfjs-scroll-horizontal-button-label = Dibunañ a-blaen +pdfjs-scroll-wrapped-button = + .title = Arverañ an dibunañ paket +pdfjs-scroll-wrapped-button-label = Dibunañ paket +pdfjs-spread-none-button = + .title = Chom hep stagañ ar skignadurioù +pdfjs-spread-none-button-label = Skignadenn ebet +pdfjs-spread-odd-button = + .title = Lakaat ar pajennadoù en ur gregiñ gant ar pajennoù ampar +pdfjs-spread-odd-button-label = Pajennoù ampar +pdfjs-spread-even-button = + .title = Lakaat ar pajennadoù en ur gregiñ gant ar pajennoù par +pdfjs-spread-even-button-label = Pajennoù par + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Perzhioù an teul… +pdfjs-document-properties-button-label = Perzhioù an teul… +pdfjs-document-properties-file-name = Anv restr: +pdfjs-document-properties-file-size = Ment ar restr: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } Ke ({ $size_b } eizhbit) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } Me ({ $size_b } eizhbit) +pdfjs-document-properties-title = Titl: +pdfjs-document-properties-author = Aozer: +pdfjs-document-properties-subject = Danvez: +pdfjs-document-properties-keywords = Gerioù-alc'hwez: +pdfjs-document-properties-creation-date = Deiziad krouiñ: +pdfjs-document-properties-modification-date = Deiziad kemmañ: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Krouer: +pdfjs-document-properties-producer = Kenderc'her PDF: +pdfjs-document-properties-version = Handelv PDF: +pdfjs-document-properties-page-count = Niver a bajennoù: +pdfjs-document-properties-page-size = Ment ar bajenn: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = poltred +pdfjs-document-properties-page-size-orientation-landscape = gweledva +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Lizher +pdfjs-document-properties-page-size-name-legal = Lezennel + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Gwel Web Herrek: +pdfjs-document-properties-linearized-yes = Ya +pdfjs-document-properties-linearized-no = Ket +pdfjs-document-properties-close-button = Serriñ + +## Print + +pdfjs-print-progress-message = O prientiñ an teul evit moullañ... +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Nullañ +pdfjs-printing-not-supported = Kemenn: N'eo ket skoret penn-da-benn ar moullañ gant ar merdeer-mañ. +pdfjs-printing-not-ready = Kemenn: N'hall ket bezañ moullet ar restr PDF rak n'eo ket karget penn-da-benn. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Diskouez/kuzhat ar varrenn gostez +pdfjs-toggle-sidebar-notification-button = + .title = Trec'haoliñ ar varrenn-gostez (ur steuñv pe stagadennoù a zo en teul) +pdfjs-toggle-sidebar-button-label = Diskouez/kuzhat ar varrenn gostez +pdfjs-document-outline-button = + .title = Diskouez steuñv an teul (daouglikit evit brasaat/bihanaat an holl elfennoù) +pdfjs-document-outline-button-label = Sinedoù an teuliad +pdfjs-attachments-button = + .title = Diskouez ar c'henstagadurioù +pdfjs-attachments-button-label = Kenstagadurioù +pdfjs-layers-button = + .title = Diskouez ar gwiskadoù (daou-glikañ evit adderaouekaat an holl gwiskadoù d'o stad dre ziouer) +pdfjs-layers-button-label = Gwiskadoù +pdfjs-thumbs-button = + .title = Diskouez ar melvennoù +pdfjs-thumbs-button-label = Melvennoù +pdfjs-findbar-button = + .title = Klask e-barzh an teuliad +pdfjs-findbar-button-label = Klask +pdfjs-additional-layers = Gwiskadoù ouzhpenn + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Pajenn { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Melvenn ar bajenn { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Klask + .placeholder = Klask e-barzh an teuliad +pdfjs-find-previous-button = + .title = Kavout an tamm frazenn kent o klotañ ganti +pdfjs-find-previous-button-label = Kent +pdfjs-find-next-button = + .title = Kavout an tamm frazenn war-lerc'h o klotañ ganti +pdfjs-find-next-button-label = War-lerc'h +pdfjs-find-highlight-checkbox = Usskediñ pep tra +pdfjs-find-match-case-checkbox-label = Teurel evezh ouzh ar pennlizherennoù +pdfjs-find-match-diacritics-checkbox-label = Doujañ d’an tiredoù +pdfjs-find-entire-word-checkbox-label = Gerioù a-bezh +pdfjs-find-reached-top = Tizhet eo bet derou ar bajenn, kenderc'hel diouzh an diaz +pdfjs-find-reached-bottom = Tizhet eo bet dibenn ar bajenn, kenderc'hel diouzh ar c'hrec'h +pdfjs-find-not-found = N'haller ket kavout ar frazenn + +## Predefined zoom values + +pdfjs-page-scale-width = Led ar bajenn +pdfjs-page-scale-fit = Pajenn a-bezh +pdfjs-page-scale-auto = Zoum emgefreek +pdfjs-page-scale-actual = Ment wir +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Pajenn { $page } + +## Loading indicator messages + +pdfjs-loading-error = Degouezhet ez eus bet ur fazi e-pad kargañ ar PDF. +pdfjs-invalid-file-error = Restr PDF didalvoudek pe kontronet. +pdfjs-missing-file-error = Restr PDF o vankout. +pdfjs-unexpected-response-error = Respont dic'hortoz a-berzh an dafariad +pdfjs-rendering-error = Degouezhet ez eus bet ur fazi e-pad skrammañ ar bajennad. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Notennañ] + +## Password + +pdfjs-password-label = Enankit ar ger-tremen evit digeriñ ar restr PDF-mañ. +pdfjs-password-invalid = Ger-tremen didalvoudek. Klaskit en-dro mar plij. +pdfjs-password-ok-button = Mat eo +pdfjs-password-cancel-button = Nullañ +pdfjs-web-fonts-disabled = Diweredekaet eo an nodrezhoù web: n'haller ket arverañ an nodrezhoù PDF enframmet. + +## Editing + +pdfjs-editor-free-text-button = + .title = Testenn +pdfjs-editor-free-text-button-label = Testenn +pdfjs-editor-ink-button = + .title = Tresañ +pdfjs-editor-ink-button-label = Tresañ +pdfjs-editor-stamp-button = + .title = Ouzhpennañ pe aozañ skeudennoù +pdfjs-editor-stamp-button-label = Ouzhpennañ pe aozañ skeudennoù + +## Remove button for the various kind of editor. + + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Liv +pdfjs-editor-free-text-size-input = Ment +pdfjs-editor-ink-color-input = Liv +pdfjs-editor-ink-thickness-input = Tevder +pdfjs-editor-ink-opacity-input = Boullder +pdfjs-editor-stamp-add-image-button = + .title = Ouzhpennañ ur skeudenn +pdfjs-editor-stamp-add-image-button-label = Ouzhpennañ ur skeudenn +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Tevded +pdfjs-free-text = + .aria-label = Aozer testennoù +pdfjs-ink = + .aria-label = Aozer tresoù +pdfjs-ink-canvas = + .aria-label = Skeudenn bet krouet gant an implijer·ez + +## Alt-text dialog + +pdfjs-editor-alt-text-add-description-label = Ouzhpennañ un deskrivadur +pdfjs-editor-alt-text-cancel-button = Nullañ +pdfjs-editor-alt-text-save-button = Enrollañ + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + + +## Color picker + +pdfjs-editor-colorpicker-button = + .title = Cheñch liv +pdfjs-editor-colorpicker-yellow = + .title = Melen +pdfjs-editor-colorpicker-blue = + .title = Glas +pdfjs-editor-colorpicker-pink = + .title = Roz +pdfjs-editor-colorpicker-red = + .title = Ruz + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Diskouez pep tra +pdfjs-editor-highlight-show-all-button = + .title = Diskouez pep tra + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Gouzout hiroc’h +pdfjs-editor-new-alt-text-error-close-button = Serriñ + +## Image alt-text settings + +pdfjs-editor-alt-text-settings-delete-model-button = Dilemel +pdfjs-editor-alt-text-settings-download-model-button = Pellgargañ +pdfjs-editor-alt-text-settings-downloading-model-button = O pellgargañ… +pdfjs-editor-alt-text-settings-close-button = Serriñ diff --git a/public/assets/pdfjs/locale/brx/viewer.ftl b/public/assets/pdfjs/locale/brx/viewer.ftl new file mode 100755 index 0000000..53ff72c --- /dev/null +++ b/public/assets/pdfjs/locale/brx/viewer.ftl @@ -0,0 +1,218 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = आगोलनि बिलाइ +pdfjs-previous-button-label = आगोलनि +pdfjs-next-button = + .title = उननि बिलाइ +pdfjs-next-button-label = उननि +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = बिलाइ +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = { $pagesCount } नि +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pagesCount } नि { $pageNumber }) +pdfjs-zoom-out-button = + .title = फिसायै जà¥à¤® खालाम +pdfjs-zoom-out-button-label = फिसायै जà¥à¤® खालाम +pdfjs-zoom-in-button = + .title = गेदेरै जà¥à¤® खालाम +pdfjs-zoom-in-button-label = गेदेरै जà¥à¤® खालाम +pdfjs-zoom-select = + .title = जà¥à¤® खालाम +pdfjs-presentation-mode-button = + .title = दिनà¥à¤¥à¤¿à¤«à¥à¤‚नाय म'डआव थां +pdfjs-presentation-mode-button-label = दिनà¥à¤¥à¤¿à¤«à¥à¤‚नाय म'ड +pdfjs-open-file-button = + .title = फाइलखौ खेव +pdfjs-open-file-button-label = खेव +pdfjs-print-button = + .title = साफाय +pdfjs-print-button-label = साफाय + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = टà¥à¤² +pdfjs-tools-button-label = टà¥à¤² +pdfjs-first-page-button = + .title = गिबि बिलाइआव थां +pdfjs-first-page-button-label = गिबि बिलाइआव थां +pdfjs-last-page-button = + .title = जोबथा बिलाइआव थां +pdfjs-last-page-button-label = जोबथा बिलाइआव थां +pdfjs-page-rotate-cw-button = + .title = घरि गिदिंनाय फारà¥à¤¸à¥‡ फिदिं +pdfjs-page-rotate-cw-button-label = घरि गिदिंनाय फारà¥à¤¸à¥‡ फिदिं +pdfjs-page-rotate-ccw-button = + .title = घरि गिदिंनाय उलà¥à¤¥à¤¾ फारà¥à¤¸à¥‡ फिदिं +pdfjs-page-rotate-ccw-button-label = घरि गिदिंनाय उलà¥à¤¥à¤¾ फारà¥à¤¸à¥‡ फिदिं + +## Document properties dialog + +pdfjs-document-properties-button = + .title = फोरमान बिलाइनि आखà¥à¤¥à¤¾à¤¯... +pdfjs-document-properties-button-label = फोरमान बिलाइनि आखà¥à¤¥à¤¾à¤¯... +pdfjs-document-properties-file-name = फाइलनि मà¥à¤‚: +pdfjs-document-properties-file-size = फाइलनि महर: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } बाइट) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } बाइट) +pdfjs-document-properties-title = बिमà¥à¤‚: +pdfjs-document-properties-author = लिरगिरि: +pdfjs-document-properties-subject = आयदा: +pdfjs-document-properties-keywords = गाहाय सोदोब: +pdfjs-document-properties-creation-date = सोरजिनाय अकà¥à¤Ÿ': +pdfjs-document-properties-modification-date = सà¥à¤¦à¥à¤°à¤¾à¤¯à¤¨à¤¾à¤¯ अकà¥à¤Ÿ': +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = सोरजिगà¥à¤°à¤¾: +pdfjs-document-properties-producer = PDF दिहà¥à¤¨à¤—à¥à¤°à¤¾: +pdfjs-document-properties-version = PDF बिसान: +pdfjs-document-properties-page-count = बिलाइनि हिसाब: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = प'रà¥à¤Ÿà¥à¤°à¥‡à¤Ÿ +pdfjs-document-properties-page-size-orientation-landscape = लेणà¥à¤¡à¤¸à¥à¤•ेप +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = लायजाम + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +pdfjs-document-properties-linearized-yes = नंगौ +pdfjs-document-properties-linearized-no = नङा +pdfjs-document-properties-close-button = बनà¥à¤¦ खालाम + +## Print + +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = नेवसि +pdfjs-printing-not-supported = सांगà¥à¤°à¤¾à¤‚थि: साफायनाया बे बà¥à¤°à¤¾à¤‰à¤œà¤¾à¤°à¤œà¥‹à¤‚ आबà¥à¤™à¥ˆ हेफाजाब होजाया। +pdfjs-printing-not-ready = सांगà¥à¤°à¤¾à¤‚थि: PDF खौ साफायनायनि थाखाय फà¥à¤°à¤¾à¤¯à¥ˆ ल'ड खालामाखै। + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = टगà¥à¤—ल साइडबार +pdfjs-toggle-sidebar-button-label = टगà¥à¤—ल साइडबार +pdfjs-document-outline-button-label = फोरमान बिलाइ सिमा हांखो +pdfjs-attachments-button = + .title = नांजाब होनायखौ दिनà¥à¤¥à¤¿ +pdfjs-attachments-button-label = नांजाब होनाय +pdfjs-thumbs-button = + .title = थामनेइलखौ दिनà¥à¤¥à¤¿ +pdfjs-thumbs-button-label = थामनेइल +pdfjs-findbar-button = + .title = फोरमान बिलाइआव नागिरना दिहà¥à¤¨ +pdfjs-findbar-button-label = नायगिरना दिहà¥à¤¨ + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = बिलाइ { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = बिलाइ { $page } नि थामनेइल + +## Find panel button title and messages + +pdfjs-find-input = + .title = नायगिरना दिहà¥à¤¨ + .placeholder = फोरमान बिलाइआव नागिरना दिहà¥à¤¨... +pdfjs-find-previous-button = + .title = बाथà¥à¤°à¤¾ खोनà¥à¤¦à¥‹à¤¬à¤¨à¤¿ सिगांनि नà¥à¤œà¤¾à¤¥à¤¿à¤¨à¤¾à¤¯à¤–ौ नागिर +pdfjs-find-previous-button-label = आगोलनि +pdfjs-find-next-button = + .title = बाथà¥à¤°à¤¾ खोनà¥à¤¦à¥‹à¤¬à¤¨à¤¿ उननि नà¥à¤œà¤¾à¤¥à¤¿à¤¨à¤¾à¤¯à¤–ौ नागिर +pdfjs-find-next-button-label = उननि +pdfjs-find-highlight-checkbox = गासैखौबो हाइलाइट खालाम +pdfjs-find-match-case-checkbox-label = गोरोबनाय केस +pdfjs-find-reached-top = थालो निफà¥à¤°à¤¾à¤¯ जागायनानै फोरमान बिलाइनि बिजौआव सौहैबाय +pdfjs-find-reached-bottom = बिजौ निफà¥à¤°à¤¾à¤¯ जागायनानै फोरमान बिलाइनि बिजौआव सौहैबाय +pdfjs-find-not-found = बाथà¥à¤°à¤¾ खोनà¥à¤¦à¥‹à¤¬ मोनाखै + +## Predefined zoom values + +pdfjs-page-scale-width = बिलाइनि गà¥à¤µà¤¾à¤° +pdfjs-page-scale-fit = बिलाइ गोरोबनाय +pdfjs-page-scale-auto = गावनोगाव जà¥à¤® +pdfjs-page-scale-actual = थार महर +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = PDF ल'ड खालामनाय समाव मोनसे गोरोनà¥à¤¥à¤¿ जाबाय। +pdfjs-invalid-file-error = बाहायजायै à¤à¤¬à¤¾ गाजà¥à¤°à¤¿ जानाय PDF फाइल +pdfjs-missing-file-error = गोमानाय PDF फाइल +pdfjs-unexpected-response-error = मिजिंथियै सारà¥à¤­à¤¾à¤° फिननाय। +pdfjs-rendering-error = बिलाइखौ राव सोलायनाय समाव मोनसे गोरोनà¥à¤¥à¤¿ जादों। + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } सोदोब बेखेवनाय] + +## Password + +pdfjs-password-label = बे PDF फाइलखौ खेवनो पासवारà¥à¤¡ हाबहो। +pdfjs-password-invalid = बाहायजायै पासवारà¥à¤¡à¥¤ अननानै फिन नाजा। +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = नेवसि +pdfjs-web-fonts-disabled = वेब फनà¥à¤Ÿà¤–ौ लोरबां खालामबाय: अरजाबहोनाय PDF फनà¥à¤Ÿà¤–ौ बाहायनो हायाखै। + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/bs/viewer.ftl b/public/assets/pdfjs/locale/bs/viewer.ftl new file mode 100755 index 0000000..3944042 --- /dev/null +++ b/public/assets/pdfjs/locale/bs/viewer.ftl @@ -0,0 +1,223 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Prethodna strana +pdfjs-previous-button-label = Prethodna +pdfjs-next-button = + .title = Sljedeća strna +pdfjs-next-button-label = Sljedeća +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Strana +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = od { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } od { $pagesCount }) +pdfjs-zoom-out-button = + .title = Umanji +pdfjs-zoom-out-button-label = Umanji +pdfjs-zoom-in-button = + .title = Uvećaj +pdfjs-zoom-in-button-label = Uvećaj +pdfjs-zoom-select = + .title = Uvećanje +pdfjs-presentation-mode-button = + .title = Prebaci se u prezentacijski režim +pdfjs-presentation-mode-button-label = Prezentacijski režim +pdfjs-open-file-button = + .title = Otvori fajl +pdfjs-open-file-button-label = Otvori +pdfjs-print-button = + .title = Å tampaj +pdfjs-print-button-label = Å tampaj + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Alati +pdfjs-tools-button-label = Alati +pdfjs-first-page-button = + .title = Idi na prvu stranu +pdfjs-first-page-button-label = Idi na prvu stranu +pdfjs-last-page-button = + .title = Idi na zadnju stranu +pdfjs-last-page-button-label = Idi na zadnju stranu +pdfjs-page-rotate-cw-button = + .title = Rotiraj u smjeru kazaljke na satu +pdfjs-page-rotate-cw-button-label = Rotiraj u smjeru kazaljke na satu +pdfjs-page-rotate-ccw-button = + .title = Rotiraj suprotno smjeru kazaljke na satu +pdfjs-page-rotate-ccw-button-label = Rotiraj suprotno smjeru kazaljke na satu +pdfjs-cursor-text-select-tool-button = + .title = Omogući alat za oznaÄavanje teksta +pdfjs-cursor-text-select-tool-button-label = Alat za oznaÄavanje teksta +pdfjs-cursor-hand-tool-button = + .title = Omogući ruÄni alat +pdfjs-cursor-hand-tool-button-label = RuÄni alat + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Svojstva dokumenta... +pdfjs-document-properties-button-label = Svojstva dokumenta... +pdfjs-document-properties-file-name = Naziv fajla: +pdfjs-document-properties-file-size = VeliÄina fajla: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bajta) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bajta) +pdfjs-document-properties-title = Naslov: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Predmet: +pdfjs-document-properties-keywords = KljuÄne rijeÄi: +pdfjs-document-properties-creation-date = Datum kreiranja: +pdfjs-document-properties-modification-date = Datum promjene: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Kreator: +pdfjs-document-properties-producer = PDF stvaratelj: +pdfjs-document-properties-version = PDF verzija: +pdfjs-document-properties-page-count = Broj stranica: +pdfjs-document-properties-page-size = VeliÄina stranice: +pdfjs-document-properties-page-size-unit-inches = u +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = uspravno +pdfjs-document-properties-page-size-orientation-landscape = vodoravno +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Pismo +pdfjs-document-properties-page-size-name-legal = Pravni + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +pdfjs-document-properties-close-button = Zatvori + +## Print + +pdfjs-print-progress-message = Pripremam dokument za Å¡tampu… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Otkaži +pdfjs-printing-not-supported = Upozorenje: Å tampanje nije u potpunosti podržano u ovom browseru. +pdfjs-printing-not-ready = Upozorenje: PDF nije u potpunosti uÄitan za Å¡tampanje. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = UkljuÄi/iskljuÄi boÄnu traku +pdfjs-toggle-sidebar-button-label = UkljuÄi/iskljuÄi boÄnu traku +pdfjs-document-outline-button = + .title = Prikaži outline dokumenta (dvoklik za skupljanje/Å¡irenje svih stavki) +pdfjs-document-outline-button-label = Konture dokumenta +pdfjs-attachments-button = + .title = Prikaži priloge +pdfjs-attachments-button-label = Prilozi +pdfjs-thumbs-button = + .title = Prikaži thumbnailove +pdfjs-thumbs-button-label = Thumbnailovi +pdfjs-findbar-button = + .title = PronaÄ‘i u dokumentu +pdfjs-findbar-button-label = PronaÄ‘i + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Strana { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Thumbnail strane { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = PronaÄ‘i + .placeholder = PronaÄ‘i u dokumentu… +pdfjs-find-previous-button = + .title = PronaÄ‘i prethodno pojavljivanje fraze +pdfjs-find-previous-button-label = Prethodno +pdfjs-find-next-button = + .title = PronaÄ‘i sljedeće pojavljivanje fraze +pdfjs-find-next-button-label = Sljedeće +pdfjs-find-highlight-checkbox = OznaÄi sve +pdfjs-find-match-case-checkbox-label = Osjetljivost na karaktere +pdfjs-find-reached-top = Dostigao sam vrh dokumenta, nastavljam sa dna +pdfjs-find-reached-bottom = Dostigao sam kraj dokumenta, nastavljam sa vrha +pdfjs-find-not-found = Fraza nije pronaÄ‘ena + +## Predefined zoom values + +pdfjs-page-scale-width = Å irina strane +pdfjs-page-scale-fit = Uklopi stranu +pdfjs-page-scale-auto = Automatsko uvećanje +pdfjs-page-scale-actual = Stvarna veliÄina +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = DoÅ¡lo je do greÅ¡ke prilikom uÄitavanja PDF-a. +pdfjs-invalid-file-error = Neispravan ili oÅ¡tećen PDF fajl. +pdfjs-missing-file-error = Nedostaje PDF fajl. +pdfjs-unexpected-response-error = NeoÄekivani odgovor servera. +pdfjs-rendering-error = DoÅ¡lo je do greÅ¡ke prilikom renderiranja strane. + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } pribiljeÅ¡ka] + +## Password + +pdfjs-password-label = UpiÅ¡ite lozinku da biste otvorili ovaj PDF fajl. +pdfjs-password-invalid = PogreÅ¡na lozinka. PokuÅ¡ajte ponovo. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Otkaži +pdfjs-web-fonts-disabled = Web fontovi su onemogućeni: nemoguće koristiti ubaÄene PDF fontove. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/ca/viewer.ftl b/public/assets/pdfjs/locale/ca/viewer.ftl new file mode 100755 index 0000000..7417741 --- /dev/null +++ b/public/assets/pdfjs/locale/ca/viewer.ftl @@ -0,0 +1,313 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Pàgina anterior +pdfjs-previous-button-label = Anterior +pdfjs-next-button = + .title = Pàgina següent +pdfjs-next-button-label = Següent +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Pàgina +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = de { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount }) +pdfjs-zoom-out-button = + .title = Redueix +pdfjs-zoom-out-button-label = Redueix +pdfjs-zoom-in-button = + .title = Amplia +pdfjs-zoom-in-button-label = Amplia +pdfjs-zoom-select = + .title = Escala +pdfjs-presentation-mode-button = + .title = Canvia al mode de presentació +pdfjs-presentation-mode-button-label = Mode de presentació +pdfjs-open-file-button = + .title = Obre el fitxer +pdfjs-open-file-button-label = Obre +pdfjs-print-button = + .title = Imprimeix +pdfjs-print-button-label = Imprimeix +pdfjs-save-button = + .title = Desa +pdfjs-save-button-label = Desa +pdfjs-bookmark-button = + .title = Pàgina actual (mostra l'URL de la pàgina actual) +pdfjs-bookmark-button-label = Pàgina actual + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Eines +pdfjs-tools-button-label = Eines +pdfjs-first-page-button = + .title = Vés a la primera pàgina +pdfjs-first-page-button-label = Vés a la primera pàgina +pdfjs-last-page-button = + .title = Vés a l'última pàgina +pdfjs-last-page-button-label = Vés a l'última pàgina +pdfjs-page-rotate-cw-button = + .title = Gira cap a la dreta +pdfjs-page-rotate-cw-button-label = Gira cap a la dreta +pdfjs-page-rotate-ccw-button = + .title = Gira cap a l'esquerra +pdfjs-page-rotate-ccw-button-label = Gira cap a l'esquerra +pdfjs-cursor-text-select-tool-button = + .title = Habilita l'eina de selecció de text +pdfjs-cursor-text-select-tool-button-label = Eina de selecció de text +pdfjs-cursor-hand-tool-button = + .title = Habilita l'eina de mà +pdfjs-cursor-hand-tool-button-label = Eina de mà +pdfjs-scroll-page-button = + .title = Usa el desplaçament de pàgina +pdfjs-scroll-page-button-label = Desplaçament de pàgina +pdfjs-scroll-vertical-button = + .title = Utilitza el desplaçament vertical +pdfjs-scroll-vertical-button-label = Desplaçament vertical +pdfjs-scroll-horizontal-button = + .title = Utilitza el desplaçament horitzontal +pdfjs-scroll-horizontal-button-label = Desplaçament horitzontal +pdfjs-scroll-wrapped-button = + .title = Activa el desplaçament continu +pdfjs-scroll-wrapped-button-label = Desplaçament continu +pdfjs-spread-none-button = + .title = No agrupis les pàgines de dues en dues +pdfjs-spread-none-button-label = Una sola pàgina +pdfjs-spread-odd-button = + .title = Mostra dues pàgines començant per les pàgines de numeració senar +pdfjs-spread-odd-button-label = Doble pàgina (senar) +pdfjs-spread-even-button = + .title = Mostra dues pàgines començant per les pàgines de numeració parell +pdfjs-spread-even-button-label = Doble pàgina (parell) + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Propietats del document… +pdfjs-document-properties-button-label = Propietats del document… +pdfjs-document-properties-file-name = Nom del fitxer: +pdfjs-document-properties-file-size = Mida del fitxer: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Títol: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Assumpte: +pdfjs-document-properties-keywords = Paraules clau: +pdfjs-document-properties-creation-date = Data de creació: +pdfjs-document-properties-modification-date = Data de modificació: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Creador: +pdfjs-document-properties-producer = Generador de PDF: +pdfjs-document-properties-version = Versió de PDF: +pdfjs-document-properties-page-count = Nombre de pàgines: +pdfjs-document-properties-page-size = Mida de la pàgina: +pdfjs-document-properties-page-size-unit-inches = polzades +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = vertical +pdfjs-document-properties-page-size-orientation-landscape = apaïsat +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Carta +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Vista web ràpida: +pdfjs-document-properties-linearized-yes = Sí +pdfjs-document-properties-linearized-no = No +pdfjs-document-properties-close-button = Tanca + +## Print + +pdfjs-print-progress-message = S'està preparant la impressió del document… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Cancel·la +pdfjs-printing-not-supported = Avís: la impressió no és plenament funcional en aquest navegador. +pdfjs-printing-not-ready = Atenció: el PDF no s'ha acabat de carregar per imprimir-lo. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Mostra/amaga la barra lateral +pdfjs-toggle-sidebar-notification-button = + .title = Mostra/amaga la barra lateral (el document conté un esquema, adjuncions o capes) +pdfjs-toggle-sidebar-button-label = Mostra/amaga la barra lateral +pdfjs-document-outline-button = + .title = Mostra l'esquema del document (doble clic per ampliar/reduir tots els elements) +pdfjs-document-outline-button-label = Esquema del document +pdfjs-attachments-button = + .title = Mostra les adjuncions +pdfjs-attachments-button-label = Adjuncions +pdfjs-layers-button = + .title = Mostra les capes (doble clic per restablir totes les capes al seu estat per defecte) +pdfjs-layers-button-label = Capes +pdfjs-thumbs-button = + .title = Mostra les miniatures +pdfjs-thumbs-button-label = Miniatures +pdfjs-current-outline-item-button = + .title = Cerca l'element d'esquema actual +pdfjs-current-outline-item-button-label = Element d'esquema actual +pdfjs-findbar-button = + .title = Cerca al document +pdfjs-findbar-button-label = Cerca +pdfjs-additional-layers = Capes addicionals + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Pàgina { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura de la pàgina { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Cerca + .placeholder = Cerca al document… +pdfjs-find-previous-button = + .title = Cerca l'anterior coincidència de l'expressió +pdfjs-find-previous-button-label = Anterior +pdfjs-find-next-button = + .title = Cerca la següent coincidència de l'expressió +pdfjs-find-next-button-label = Següent +pdfjs-find-highlight-checkbox = Ressalta-ho tot +pdfjs-find-match-case-checkbox-label = Distingeix entre majúscules i minúscules +pdfjs-find-match-diacritics-checkbox-label = Respecta els diacrítics +pdfjs-find-entire-word-checkbox-label = Paraules senceres +pdfjs-find-reached-top = S'ha arribat al principi del document, es continua pel final +pdfjs-find-reached-bottom = S'ha arribat al final del document, es continua pel principi +pdfjs-find-not-found = No s'ha trobat l'expressió + +## Predefined zoom values + +pdfjs-page-scale-width = Amplada de la pàgina +pdfjs-page-scale-fit = Ajusta la pàgina +pdfjs-page-scale-auto = Zoom automàtic +pdfjs-page-scale-actual = Mida real +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Pàgina { $page } + +## Loading indicator messages + +pdfjs-loading-error = S'ha produït un error en carregar el PDF. +pdfjs-invalid-file-error = El fitxer PDF no és vàlid o està malmès. +pdfjs-missing-file-error = Falta el fitxer PDF. +pdfjs-unexpected-response-error = Resposta inesperada del servidor. +pdfjs-rendering-error = S'ha produït un error mentre es renderitzava la pàgina. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Anotació { $type }] + +## Password + +pdfjs-password-label = Introduïu la contrasenya per obrir aquest fitxer PDF. +pdfjs-password-invalid = La contrasenya no és vàlida. Torneu-ho a provar. +pdfjs-password-ok-button = D'acord +pdfjs-password-cancel-button = Cancel·la +pdfjs-web-fonts-disabled = Els tipus de lletra web estan desactivats: no es poden utilitzar els tipus de lletra incrustats al PDF. + +## Editing + +pdfjs-editor-free-text-button = + .title = Text +pdfjs-editor-free-text-button-label = Text +pdfjs-editor-ink-button = + .title = Dibuixa +pdfjs-editor-ink-button-label = Dibuixa + +## Remove button for the various kind of editor. + + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Color +pdfjs-editor-free-text-size-input = Mida +pdfjs-editor-ink-color-input = Color +pdfjs-editor-ink-thickness-input = Gruix +pdfjs-editor-ink-opacity-input = Opacitat +pdfjs-free-text = + .aria-label = Editor de text +pdfjs-free-text-default-content = Escriviu… +pdfjs-ink = + .aria-label = Editor de dibuix +pdfjs-ink-canvas = + .aria-label = Imatge creada per l'usuari + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + + +## Color picker + + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + + +## Image alt-text settings + diff --git a/public/assets/pdfjs/locale/cak/viewer.ftl b/public/assets/pdfjs/locale/cak/viewer.ftl new file mode 100755 index 0000000..f40c1e9 --- /dev/null +++ b/public/assets/pdfjs/locale/cak/viewer.ftl @@ -0,0 +1,291 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Jun kan ruxaq +pdfjs-previous-button-label = Jun kan +pdfjs-next-button = + .title = Jun chik ruxaq +pdfjs-next-button-label = Jun chik +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Ruxaq +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = richin { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } richin { $pagesCount }) +pdfjs-zoom-out-button = + .title = Tich'utinirisäx +pdfjs-zoom-out-button-label = Tich'utinirisäx +pdfjs-zoom-in-button = + .title = Tinimirisäx +pdfjs-zoom-in-button-label = Tinimirisäx +pdfjs-zoom-select = + .title = Sum +pdfjs-presentation-mode-button = + .title = Tijal ri rub'anikil niwachin +pdfjs-presentation-mode-button-label = Pa rub'eyal niwachin +pdfjs-open-file-button = + .title = Tijaq Yakb'äl +pdfjs-open-file-button-label = Tijaq +pdfjs-print-button = + .title = Titz'ajb'äx +pdfjs-print-button-label = Titz'ajb'äx +pdfjs-save-button = + .title = Tiyak +pdfjs-save-button-label = Tiyak +pdfjs-bookmark-button-label = Ruxaq k'o wakami + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Samajib'äl +pdfjs-tools-button-label = Samajib'äl +pdfjs-first-page-button = + .title = Tib'e pa nab'ey ruxaq +pdfjs-first-page-button-label = Tib'e pa nab'ey ruxaq +pdfjs-last-page-button = + .title = Tib'e pa ruk'isib'äl ruxaq +pdfjs-last-page-button-label = Tib'e pa ruk'isib'äl ruxaq +pdfjs-page-rotate-cw-button = + .title = Tisutïx pan ajkiq'a' +pdfjs-page-rotate-cw-button-label = Tisutïx pan ajkiq'a' +pdfjs-page-rotate-ccw-button = + .title = Tisutïx pan ajxokon +pdfjs-page-rotate-ccw-button-label = Tisutïx pan ajxokon +pdfjs-cursor-text-select-tool-button = + .title = Titzij ri rusamajib'al Rucha'ik Rucholajem Tzij +pdfjs-cursor-text-select-tool-button-label = Rusamajib'al Rucha'ik Rucholajem Tzij +pdfjs-cursor-hand-tool-button = + .title = Titzij ri q'ab'aj samajib'äl +pdfjs-cursor-hand-tool-button-label = Q'ab'aj Samajib'äl +pdfjs-scroll-page-button = + .title = Tokisäx Ruxaq Q'axanem +pdfjs-scroll-page-button-label = Ruxaq Q'axanem +pdfjs-scroll-vertical-button = + .title = Tokisäx Pa'äl Q'axanem +pdfjs-scroll-vertical-button-label = Pa'äl Q'axanem +pdfjs-scroll-horizontal-button = + .title = Tokisäx Kotz'öl Q'axanem +pdfjs-scroll-horizontal-button-label = Kotz'öl Q'axanem +pdfjs-scroll-wrapped-button = + .title = Tokisäx Tzub'aj Q'axanem +pdfjs-scroll-wrapped-button-label = Tzub'aj Q'axanem +pdfjs-spread-none-button = + .title = Man ketun taq ruxaq pa rub'eyal wuj +pdfjs-spread-none-button-label = Majun Rub'eyal +pdfjs-spread-odd-button = + .title = Ke'atunu' ri taq ruxaq rik'in natikirisaj rik'in jun man k'ulaj ta rajilab'al +pdfjs-spread-odd-button-label = Man K'ulaj Ta Rub'eyal +pdfjs-spread-even-button = + .title = Ke'atunu' ri taq ruxaq rik'in natikirisaj rik'in jun k'ulaj rajilab'al +pdfjs-spread-even-button-label = K'ulaj Rub'eyal + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Taq richinil wuj… +pdfjs-document-properties-button-label = Taq richinil wuj… +pdfjs-document-properties-file-name = Rub'i' yakb'äl: +pdfjs-document-properties-file-size = Runimilem yakb'äl: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = B'i'aj: +pdfjs-document-properties-author = B'anel: +pdfjs-document-properties-subject = Taqikil: +pdfjs-document-properties-keywords = Kixe'el taq tzij: +pdfjs-document-properties-creation-date = Ruq'ijul xtz'uk: +pdfjs-document-properties-modification-date = Ruq'ijul xjalwachïx: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Q'inonel: +pdfjs-document-properties-producer = PDF b'anöy: +pdfjs-document-properties-version = PDF ruwäch: +pdfjs-document-properties-page-count = Jarupe' ruxaq: +pdfjs-document-properties-page-size = Runimilem ri Ruxaq: +pdfjs-document-properties-page-size-unit-inches = pa +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = rupalem +pdfjs-document-properties-page-size-orientation-landscape = rukotz'olem +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Loman wuj +pdfjs-document-properties-page-size-name-legal = Taqanel tzijol + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Anin Rutz'etik Ajk'amaya'l: +pdfjs-document-properties-linearized-yes = Ja' +pdfjs-document-properties-linearized-no = Mani +pdfjs-document-properties-close-button = Titz'apïx + +## Print + +pdfjs-print-progress-message = Ruchojmirisaxik wuj richin nitz'ajb'äx… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Tiq'at +pdfjs-printing-not-supported = Rutzijol k'ayewal: Ri rutz'ajb'axik man koch'el ta ronojel pa re okik'amaya'l re'. +pdfjs-printing-not-ready = Rutzijol k'ayewal: Ri PDF man xusamajij ta ronojel richin nitz'ajb'äx. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Tijal ri ajxikin kajtz'ik +pdfjs-toggle-sidebar-notification-button = + .title = Tik'ex ri ajxikin yuqkajtz'ik (ri wuj eruk'wan taq ruchi'/taqo/kuchuj) +pdfjs-toggle-sidebar-button-label = Tijal ri ajxikin kajtz'ik +pdfjs-document-outline-button = + .title = Tik'ut pe ruch'akulal wuj (kamul-pitz'oj richin nirik'/nich'utinirisäx ronojel ruch'akulal) +pdfjs-document-outline-button-label = Ruch'akulal wuj +pdfjs-attachments-button = + .title = Kek'ut pe ri taq taqoj +pdfjs-attachments-button-label = Taq taqoj +pdfjs-layers-button = + .title = Kek'ut taq Kuchuj (ka'i'-pitz' richin yetzolïx ronojel ri taq kuchuj e k'o wi) +pdfjs-layers-button-label = Taq kuchuj +pdfjs-thumbs-button = + .title = Kek'ut pe taq ch'utiq +pdfjs-thumbs-button-label = Koköj +pdfjs-current-outline-item-button = + .title = Kekanöx Taq Ch'akulal Kik'wan Chib'äl +pdfjs-current-outline-item-button-label = Taq Ch'akulal Kik'wan Chib'äl +pdfjs-findbar-button = + .title = Tikanöx chupam ri wuj +pdfjs-findbar-button-label = Tikanöx +pdfjs-additional-layers = Tz'aqat ta Kuchuj + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Ruxaq { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Ruch'utinirisaxik ruxaq { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Tikanöx + .placeholder = Tikanöx pa wuj… +pdfjs-find-previous-button = + .title = Tib'an b'enam pa ri jun kan q'aptzij xilitäj +pdfjs-find-previous-button-label = Jun kan +pdfjs-find-next-button = + .title = Tib'e pa ri jun chik pajtzij xilitäj +pdfjs-find-next-button-label = Jun chik +pdfjs-find-highlight-checkbox = Tiya' retal ronojel +pdfjs-find-match-case-checkbox-label = Tuk'äm ri' kik'in taq nimatz'ib' chuqa' taq ch'utitz'ib' +pdfjs-find-match-diacritics-checkbox-label = Tiya' Kikojol Tz'aqat taq Tz'ib' +pdfjs-find-entire-word-checkbox-label = Tz'aqät taq tzij +pdfjs-find-reached-top = Xb'eq'i' ri rutikirib'al wuj, xtikanöx k'a pa ruk'isib'äl +pdfjs-find-reached-bottom = Xb'eq'i' ri ruk'isib'äl wuj, xtikanöx pa rutikirib'al +pdfjs-find-not-found = Man xilitäj ta ri pajtzij + +## Predefined zoom values + +pdfjs-page-scale-width = Ruwa ruxaq +pdfjs-page-scale-fit = Tinuk' ruxaq +pdfjs-page-scale-auto = Yonil chi nimilem +pdfjs-page-scale-actual = Runimilem Wakami +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Ruxaq { $page } + +## Loading indicator messages + +pdfjs-loading-error = Xk'ulwachitäj jun sach'oj toq xnuk'ux ri PDF . +pdfjs-invalid-file-error = Man oke ta o yujtajinäq ri PDF yakb'äl. +pdfjs-missing-file-error = Man xilitäj ta ri PDF yakb'äl. +pdfjs-unexpected-response-error = Man oyob'en ta tz'olin rutzij ruk'u'x samaj. +pdfjs-rendering-error = Xk'ulwachitäj jun sachoj toq ninuk'wachij ri ruxaq. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Tz'ib'anïk] + +## Password + +pdfjs-password-label = Tatz'ib'aj ri ewan tzij richin najäq re yakb'äl re' pa PDF. +pdfjs-password-invalid = Man okel ta ri ewan tzij: Tatojtob'ej chik. +pdfjs-password-ok-button = Ütz +pdfjs-password-cancel-button = Tiq'at +pdfjs-web-fonts-disabled = E chupül ri taq ajk'amaya'l tz'ib': man tikirel ta nokisäx ri taq tz'ib' PDF pa ch'ikenïk + +## Editing + +pdfjs-editor-free-text-button = + .title = Rucholajem tz'ib' +pdfjs-editor-free-text-button-label = Rucholajem tz'ib' +pdfjs-editor-ink-button = + .title = Tiwachib'ëx +pdfjs-editor-ink-button-label = Tiwachib'ëx +# Editor Parameters +pdfjs-editor-free-text-color-input = B'onil +pdfjs-editor-free-text-size-input = Nimilem +pdfjs-editor-ink-color-input = B'onil +pdfjs-editor-ink-thickness-input = Rupimil +pdfjs-editor-ink-opacity-input = Q'equmal +pdfjs-free-text = + .aria-label = Nuk'unel tz'ib'atzij +pdfjs-free-text-default-content = Titikitisäx rutz'ib'axik… +pdfjs-ink = + .aria-label = Nuk'unel wachib'äl +pdfjs-ink-canvas = + .aria-label = Wachib'äl nuk'un ruma okisaxel + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/ckb/viewer.ftl b/public/assets/pdfjs/locale/ckb/viewer.ftl new file mode 100755 index 0000000..ae87335 --- /dev/null +++ b/public/assets/pdfjs/locale/ckb/viewer.ftl @@ -0,0 +1,242 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Ù¾Û•Ú•Û•ÛŒ پێشوو +pdfjs-previous-button-label = پێشوو +pdfjs-next-button = + .title = Ù¾Û•Ú•Û•ÛŒ دوواتر +pdfjs-next-button-label = دوواتر +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = پەرە +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = Ù„Û• { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } Ù„Û• { $pagesCount }) +pdfjs-zoom-out-button = + .title = ڕۆچوونی +pdfjs-zoom-out-button-label = ڕۆچوونی +pdfjs-zoom-in-button = + .title = هێنانەپێش +pdfjs-zoom-in-button-label = هێنانەپێش +pdfjs-zoom-select = + .title = زووم +pdfjs-presentation-mode-button = + .title = گۆڕین بۆ دۆخی پێشکەشکردن +pdfjs-presentation-mode-button-label = دۆخی پێشکەشکردن +pdfjs-open-file-button = + .title = Ù¾Û•Ú•Ú¯Û• بکەرەوە +pdfjs-open-file-button-label = کردنەوە +pdfjs-print-button = + .title = چاپکردن +pdfjs-print-button-label = چاپکردن + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = ئامرازەکان +pdfjs-tools-button-label = ئامرازەکان +pdfjs-first-page-button = + .title = برۆ بۆ یەکەم Ù¾Û•Ú•Û• +pdfjs-first-page-button-label = بڕۆ بۆ یەکەم Ù¾Û•Ú•Û• +pdfjs-last-page-button = + .title = بڕۆ بۆ کۆتا Ù¾Û•Ú•Û• +pdfjs-last-page-button-label = بڕۆ بۆ کۆتا Ù¾Û•Ú•Û• +pdfjs-page-rotate-cw-button = + .title = ئاڕاستەی میلی کاتژمێر +pdfjs-page-rotate-cw-button-label = ئاڕاستەی میلی کاتژمێر +pdfjs-page-rotate-ccw-button = + .title = پێچەوانەی میلی کاتژمێر +pdfjs-page-rotate-ccw-button-label = پێچەوانەی میلی کاتژمێر +pdfjs-cursor-text-select-tool-button = + .title = توڵامرازی نیشانکەری دەق چالاک بکە +pdfjs-cursor-text-select-tool-button-label = توڵامرازی نیشانکەری دەق +pdfjs-cursor-hand-tool-button = + .title = توڵامرازی دەستی چالاک بکە +pdfjs-cursor-hand-tool-button-label = توڵامرازی دەستی +pdfjs-scroll-vertical-button = + .title = ناردنی ئەستوونی بەکاربێنە +pdfjs-scroll-vertical-button-label = ناردنی ئەستوونی +pdfjs-scroll-horizontal-button = + .title = ناردنی ئاسۆیی بەکاربێنە +pdfjs-scroll-horizontal-button-label = ناردنی ئاسۆیی +pdfjs-scroll-wrapped-button = + .title = ناردنی لوولکراو بەکاربێنە +pdfjs-scroll-wrapped-button-label = ناردنی لوولکراو + +## Document properties dialog + +pdfjs-document-properties-button = + .title = تایبەتمەندییەکانی بەڵگەنامە... +pdfjs-document-properties-button-label = تایبەتمەندییەکانی بەڵگەنامە... +pdfjs-document-properties-file-name = ناوی Ù¾Û•Ú•Ú¯Û•: +pdfjs-document-properties-file-size = قەبارەی Ù¾Û•Ú•Ú¯Û•: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } کب ({ $size_b } بایت) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } مب ({ $size_b } بایت) +pdfjs-document-properties-title = سەردێڕ: +pdfjs-document-properties-author = نووسەر +pdfjs-document-properties-subject = بابەت: +pdfjs-document-properties-keywords = کلیلەوشە: +pdfjs-document-properties-creation-date = بەرواری درووستکردن: +pdfjs-document-properties-modification-date = بەرواری دەستکاریکردن: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = درووستکەر: +pdfjs-document-properties-producer = بەرهەمهێنەری PDF: +pdfjs-document-properties-version = وەشانی PDF: +pdfjs-document-properties-page-count = ژمارەی پەرەکان: +pdfjs-document-properties-page-size = قەبارەی Ù¾Û•Ú•Û•: +pdfjs-document-properties-page-size-unit-inches = ئینچ +pdfjs-document-properties-page-size-unit-millimeters = ملم +pdfjs-document-properties-page-size-orientation-portrait = پۆرترەیت(درێژ) +pdfjs-document-properties-page-size-orientation-landscape = پانیی +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = نامە +pdfjs-document-properties-page-size-name-legal = یاسایی + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = پیشاندانی وێبی خێرا: +pdfjs-document-properties-linearized-yes = بەڵێ +pdfjs-document-properties-linearized-no = نەخێر +pdfjs-document-properties-close-button = داخستن + +## Print + +pdfjs-print-progress-message = بەڵگەنامە ئامادەدەکرێت بۆ چاپکردن... +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = پاشگەزبوونەوە +pdfjs-printing-not-supported = ئاگاداربە: چاپکردن بە تەواوی پشتگیر ناکرێت Ù„Û•Ù… وێبگەڕە. +pdfjs-printing-not-ready = ئاگاداربە: PDF بە تەواوی بارنەبووە بۆ چاپکردن. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = لاتەنیشت پیشاندان/شاردنەوە +pdfjs-toggle-sidebar-button-label = لاتەنیشت پیشاندان/شاردنەوە +pdfjs-document-outline-button-label = سنووری چوارچێوە +pdfjs-attachments-button = + .title = پاشکۆکان پیشان بدە +pdfjs-attachments-button-label = پاشکۆکان +pdfjs-layers-button-label = چینەکان +pdfjs-thumbs-button = + .title = ÙˆÛŽÙ†Û†Ú†Ú©Û• پیشان بدە +pdfjs-thumbs-button-label = ÙˆÛŽÙ†Û†Ú†Ú©Û• +pdfjs-findbar-button = + .title = Ù„Û• بەڵگەنامە بگەرێ +pdfjs-findbar-button-label = دۆزینەوە +pdfjs-additional-layers = چینی زیاتر + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Ù¾Û•Ú•Û•ÛŒ { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = ÙˆÛŽÙ†Û†Ú†Ú©Û•ÛŒ Ù¾Û•Ú•Û•ÛŒ { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = دۆزینەوە + .placeholder = Ù„Û• بەڵگەنامە بگەرێ... +pdfjs-find-previous-button = + .title = هەبوونی پێشوو بدۆزرەوە Ù„Û• ڕستەکەدا +pdfjs-find-previous-button-label = پێشوو +pdfjs-find-next-button = + .title = هەبوونی داهاتوو بدۆزەرەوە Ù„Û• ڕستەکەدا +pdfjs-find-next-button-label = دوواتر +pdfjs-find-highlight-checkbox = هەمووی نیشانە بکە +pdfjs-find-match-case-checkbox-label = دۆخی لەیەکچوون +pdfjs-find-entire-word-checkbox-label = هەموو وشەکان +pdfjs-find-reached-top = گەشتیتە سەرەوەی بەڵگەنامە، Ù„Û• خوارەوە دەستت پێکرد +pdfjs-find-reached-bottom = گەشتیتە کۆتایی بەڵگەنامە. لەسەرەوە دەستت پێکرد +pdfjs-find-not-found = نووسین نەدۆزرایەوە + +## Predefined zoom values + +pdfjs-page-scale-width = پانی Ù¾Û•Ú•Û• +pdfjs-page-scale-fit = پڕبوونی Ù¾Û•Ú•Û• +pdfjs-page-scale-auto = زوومی خۆکار +pdfjs-page-scale-actual = قەبارەی ڕاستی +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = هەڵەیەک ڕوویدا Ù„Û• کاتی بارکردنی PDF. +pdfjs-invalid-file-error = Ù¾Û•Ú•Ú¯Û•ÛŒ pdf تێکچووە یان نەگونجاوە. +pdfjs-missing-file-error = Ù¾Û•Ú•Ú¯Û•ÛŒ pdf بوونی نیە. +pdfjs-unexpected-response-error = وەڵامی ڕاژەخوازی نەخوازراو. +pdfjs-rendering-error = هەڵەیەک ڕوویدا Ù„Û• کاتی پوختەکردنی (ڕێندەر) Ù¾Û•Ú•Û•. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } سەرنج] + +## Password + +pdfjs-password-label = وشەی تێپەڕ بنووسە بۆ کردنەوەی Ù¾Û•Ú•Ú¯Û•ÛŒ pdf. +pdfjs-password-invalid = وشەی تێپەڕ هەڵەیە. تکایە دووبارە Ù‡Û•ÙˆÚµ بدەرەوە. +pdfjs-password-ok-button = باشە +pdfjs-password-cancel-button = پاشگەزبوونەوە +pdfjs-web-fonts-disabled = جۆرەپیتی وێب ناچالاکە: نەتوانی جۆرەپیتی تێخراوی ناو pdfÙ€Û•Ú©Û• بەکاربێت. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/cs/viewer.ftl b/public/assets/pdfjs/locale/cs/viewer.ftl new file mode 100755 index 0000000..696fe30 --- /dev/null +++ b/public/assets/pdfjs/locale/cs/viewer.ftl @@ -0,0 +1,521 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = PÅ™ejde na pÅ™edchozí stránku +pdfjs-previous-button-label = PÅ™edchozí +pdfjs-next-button = + .title = PÅ™ejde na následující stránku +pdfjs-next-button-label = Další +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Stránka +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = z { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } z { $pagesCount }) +pdfjs-zoom-out-button = + .title = Zmenší velikost +pdfjs-zoom-out-button-label = ZmenÅ¡it +pdfjs-zoom-in-button = + .title = ZvÄ›tší velikost +pdfjs-zoom-in-button-label = ZvÄ›tÅ¡it +pdfjs-zoom-select = + .title = Nastaví velikost +pdfjs-presentation-mode-button = + .title = PÅ™epne do režimu prezentace +pdfjs-presentation-mode-button-label = Režim prezentace +pdfjs-open-file-button = + .title = OtevÅ™e soubor +pdfjs-open-file-button-label = Otevřít +pdfjs-print-button = + .title = Vytiskne dokument +pdfjs-print-button-label = Vytisknout +pdfjs-save-button = + .title = Uložit +pdfjs-save-button-label = Uložit +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Stáhnout +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Stáhnout +pdfjs-bookmark-button = + .title = Aktuální stránka (zobrazit URL od aktuální stránky) +pdfjs-bookmark-button-label = Aktuální stránka + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Nástroje +pdfjs-tools-button-label = Nástroje +pdfjs-first-page-button = + .title = PÅ™ejde na první stránku +pdfjs-first-page-button-label = PÅ™ejít na první stránku +pdfjs-last-page-button = + .title = PÅ™ejde na poslední stránku +pdfjs-last-page-button-label = PÅ™ejít na poslední stránku +pdfjs-page-rotate-cw-button = + .title = OtoÄí po smÄ›ru hodin +pdfjs-page-rotate-cw-button-label = OtoÄit po smÄ›ru hodin +pdfjs-page-rotate-ccw-button = + .title = OtoÄí proti smÄ›ru hodin +pdfjs-page-rotate-ccw-button-label = OtoÄit proti smÄ›ru hodin +pdfjs-cursor-text-select-tool-button = + .title = Povolí výbÄ›r textu +pdfjs-cursor-text-select-tool-button-label = VýbÄ›r textu +pdfjs-cursor-hand-tool-button = + .title = Povolí nástroj ruÄiÄka +pdfjs-cursor-hand-tool-button-label = Nástroj ruÄiÄka +pdfjs-scroll-page-button = + .title = Posouvat po stránkách +pdfjs-scroll-page-button-label = Posouvání po stránkách +pdfjs-scroll-vertical-button = + .title = Použít svislé posouvání +pdfjs-scroll-vertical-button-label = Svislé posouvání +pdfjs-scroll-horizontal-button = + .title = Použít vodorovné posouvání +pdfjs-scroll-horizontal-button-label = Vodorovné posouvání +pdfjs-scroll-wrapped-button = + .title = Použít postupné posouvání +pdfjs-scroll-wrapped-button-label = Postupné posouvání +pdfjs-spread-none-button = + .title = Nesdružovat stránky +pdfjs-spread-none-button-label = Žádné sdružení +pdfjs-spread-odd-button = + .title = Sdruží stránky s umístÄ›ním lichých vlevo +pdfjs-spread-odd-button-label = Sdružení stránek (liché vlevo) +pdfjs-spread-even-button = + .title = Sdruží stránky s umístÄ›ním sudých vlevo +pdfjs-spread-even-button-label = Sdružení stránek (sudé vlevo) + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Vlastnosti dokumentu… +pdfjs-document-properties-button-label = Vlastnosti dokumentu… +pdfjs-document-properties-file-name = Název souboru: +pdfjs-document-properties-file-size = Velikost souboru: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kB ({ $b } bajtů) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bajtů) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bajtů) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bajtů) +pdfjs-document-properties-title = Název stránky: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = PÅ™edmÄ›t: +pdfjs-document-properties-keywords = KlíÄová slova: +pdfjs-document-properties-creation-date = Datum vytvoÅ™ení: +pdfjs-document-properties-modification-date = Datum úpravy: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = VytvoÅ™il: +pdfjs-document-properties-producer = Tvůrce PDF: +pdfjs-document-properties-version = Verze PDF: +pdfjs-document-properties-page-count = PoÄet stránek: +pdfjs-document-properties-page-size = Velikost stránky: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = na výšku +pdfjs-document-properties-page-size-orientation-landscape = na šířku +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Dopis +pdfjs-document-properties-page-size-name-legal = Právní dokument + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Rychlé zobrazování z webu: +pdfjs-document-properties-linearized-yes = Ano +pdfjs-document-properties-linearized-no = Ne +pdfjs-document-properties-close-button = Zavřít + +## Print + +pdfjs-print-progress-message = Příprava dokumentu pro tisk… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress } % +pdfjs-print-progress-close-button = ZruÅ¡it +pdfjs-printing-not-supported = UpozornÄ›ní: Tisk není v tomto prohlížeÄi plnÄ› podporován. +pdfjs-printing-not-ready = UpozornÄ›ní: Dokument PDF není kompletnÄ› naÄten. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Postranní liÅ¡ta +pdfjs-toggle-sidebar-notification-button = + .title = PÅ™epnout postranní liÅ¡tu (dokument obsahuje osnovu/přílohy/vrstvy) +pdfjs-toggle-sidebar-button-label = Postranní liÅ¡ta +pdfjs-document-outline-button = + .title = Zobrazí osnovu dokumentu (poklepání pÅ™epne zobrazení vÅ¡ech položek) +pdfjs-document-outline-button-label = Osnova dokumentu +pdfjs-attachments-button = + .title = Zobrazí přílohy +pdfjs-attachments-button-label = Přílohy +pdfjs-layers-button = + .title = Zobrazit vrstvy (poklepáním obnovíte vÅ¡echny vrstvy do výchozího stavu) +pdfjs-layers-button-label = Vrstvy +pdfjs-thumbs-button = + .title = Zobrazí náhledy +pdfjs-thumbs-button-label = Náhledy +pdfjs-current-outline-item-button = + .title = Najít aktuální položku v osnovÄ› +pdfjs-current-outline-item-button-label = Aktuální položka v osnovÄ› +pdfjs-findbar-button = + .title = Najde v dokumentu +pdfjs-findbar-button-label = Najít +pdfjs-additional-layers = Další vrstvy + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Strana { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Náhled strany { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Najít + .placeholder = Najít v dokumentu… +pdfjs-find-previous-button = + .title = Najde pÅ™edchozí výskyt hledaného textu +pdfjs-find-previous-button-label = PÅ™edchozí +pdfjs-find-next-button = + .title = Najde další výskyt hledaného textu +pdfjs-find-next-button-label = Další +pdfjs-find-highlight-checkbox = Zvýraznit +pdfjs-find-match-case-checkbox-label = RozliÅ¡ovat velikost +pdfjs-find-match-diacritics-checkbox-label = RozliÅ¡ovat diakritiku +pdfjs-find-entire-word-checkbox-label = Celá slova +pdfjs-find-reached-top = Dosažen zaÄátek dokumentu, pokraÄuje se od konce +pdfjs-find-reached-bottom = Dosažen konec dokumentu, pokraÄuje se od zaÄátku +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current }. z { $total } výskytu + [few] { $current }. z { $total } výskytů + [many] { $current }. z { $total } výskytů + *[other] { $current }. z { $total } výskytů + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Více než { $limit } výskyt + [few] Více než { $limit } výskyty + [many] Více než { $limit } výskytů + *[other] Více než { $limit } výskytů + } +pdfjs-find-not-found = Hledaný text nenalezen + +## Predefined zoom values + +pdfjs-page-scale-width = Podle šířky +pdfjs-page-scale-fit = Podle výšky +pdfjs-page-scale-auto = Automatická velikost +pdfjs-page-scale-actual = SkuteÄná velikost +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale } % + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Strana { $page } + +## Loading indicator messages + +pdfjs-loading-error = PÅ™i nahrávání PDF nastala chyba. +pdfjs-invalid-file-error = Neplatný nebo chybný soubor PDF. +pdfjs-missing-file-error = Chybí soubor PDF. +pdfjs-unexpected-response-error = NeoÄekávaná odpovÄ›Ä serveru. +pdfjs-rendering-error = PÅ™i vykreslování stránky nastala chyba. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Anotace typu { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Pro otevÅ™ení PDF souboru vložte heslo. +pdfjs-password-invalid = Neplatné heslo. Zkuste to znovu. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = ZruÅ¡it +pdfjs-web-fonts-disabled = Webová písma jsou zakázána, proto není možné použít vložená písma PDF. + +## Editing + +pdfjs-editor-free-text-button = + .title = Text +pdfjs-editor-free-text-button-label = Text +pdfjs-editor-ink-button = + .title = Kreslení +pdfjs-editor-ink-button-label = Kreslení +pdfjs-editor-stamp-button = + .title = PÅ™idání Äi úprava obrázků +pdfjs-editor-stamp-button-label = PÅ™idání Äi úprava obrázků +pdfjs-editor-highlight-button = + .title = ZvýraznÄ›ní +pdfjs-editor-highlight-button-label = ZvýraznÄ›ní +pdfjs-highlight-floating-button1 = + .title = Zvýraznit + .aria-label = Zvýraznit +pdfjs-highlight-floating-button-label = Zvýraznit + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Odebrat kresbu +pdfjs-editor-remove-freetext-button = + .title = Odebrat text +pdfjs-editor-remove-stamp-button = + .title = Odebrat obrázek +pdfjs-editor-remove-highlight-button = + .title = Odebrat zvýraznÄ›ní + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Barva +pdfjs-editor-free-text-size-input = Velikost +pdfjs-editor-ink-color-input = Barva +pdfjs-editor-ink-thickness-input = Tloušťka +pdfjs-editor-ink-opacity-input = Průhlednost +pdfjs-editor-stamp-add-image-button = + .title = PÅ™idat obrázek +pdfjs-editor-stamp-add-image-button-label = PÅ™idat obrázek +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Tloušťka +pdfjs-editor-free-highlight-thickness-title = + .title = ZmÄ›na tloušťky pÅ™i zvýrazňování jiných položek než textu +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Textový editor + .default-content = ZaÄnÄ›te psát... +pdfjs-free-text = + .aria-label = Textový editor +pdfjs-free-text-default-content = ZaÄnÄ›te psát… +pdfjs-ink = + .aria-label = Editor kreslení +pdfjs-ink-canvas = + .aria-label = Uživatelem vytvoÅ™ený obrázek + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Náhradní popis +pdfjs-editor-alt-text-edit-button = + .aria-label = Upravit alternativní text +pdfjs-editor-alt-text-edit-button-label = Upravit náhradní popis +pdfjs-editor-alt-text-dialog-label = Vyberte možnost +pdfjs-editor-alt-text-dialog-description = Náhradní popis pomáhá, když lidé obrázek nevidí nebo když se nenaÄítá. +pdfjs-editor-alt-text-add-description-label = PÅ™idat popis +pdfjs-editor-alt-text-add-description-description = Snažte se o 1-2 vÄ›ty, které popisují pÅ™edmÄ›t, prostÅ™edí nebo Äinnosti. +pdfjs-editor-alt-text-mark-decorative-label = OznaÄit jako dekorativní +pdfjs-editor-alt-text-mark-decorative-description = Používá se pro okrasné obrázky, jako jsou rámeÄky nebo vodoznaky. +pdfjs-editor-alt-text-cancel-button = ZruÅ¡it +pdfjs-editor-alt-text-save-button = Uložit +pdfjs-editor-alt-text-decorative-tooltip = OznaÄen jako dekorativní +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Například: “Mladý muž si sedá ke stolu, aby se najedl.†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alternativní text + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Levý horní roh — zmÄ›na velikosti +pdfjs-editor-resizer-label-top-middle = Horní stÅ™ed — zmÄ›na velikosti +pdfjs-editor-resizer-label-top-right = Pravý horní roh — zmÄ›na velikosti +pdfjs-editor-resizer-label-middle-right = Vpravo uprostÅ™ed — zmÄ›na velikosti +pdfjs-editor-resizer-label-bottom-right = Pravý dolní roh — zmÄ›na velikosti +pdfjs-editor-resizer-label-bottom-middle = StÅ™ed dole — zmÄ›na velikosti +pdfjs-editor-resizer-label-bottom-left = Levý dolní roh — zmÄ›na velikosti +pdfjs-editor-resizer-label-middle-left = Vlevo uprostÅ™ed — zmÄ›na velikosti +pdfjs-editor-resizer-top-left = + .aria-label = Levý horní roh — zmÄ›na velikosti +pdfjs-editor-resizer-top-middle = + .aria-label = Horní stÅ™ed — zmÄ›na velikosti +pdfjs-editor-resizer-top-right = + .aria-label = Pravý horní roh — zmÄ›na velikosti +pdfjs-editor-resizer-middle-right = + .aria-label = Vpravo uprostÅ™ed — zmÄ›na velikosti +pdfjs-editor-resizer-bottom-right = + .aria-label = Pravý dolní roh — zmÄ›na velikosti +pdfjs-editor-resizer-bottom-middle = + .aria-label = StÅ™ed dole — zmÄ›na velikosti +pdfjs-editor-resizer-bottom-left = + .aria-label = Levý dolní roh — zmÄ›na velikosti +pdfjs-editor-resizer-middle-left = + .aria-label = Vlevo uprostÅ™ed — zmÄ›na velikosti + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Barva zvýraznÄ›ní +pdfjs-editor-colorpicker-button = + .title = ZmÄ›na barvy +pdfjs-editor-colorpicker-dropdown = + .aria-label = VýbÄ›r barev +pdfjs-editor-colorpicker-yellow = + .title = Žlutá +pdfjs-editor-colorpicker-green = + .title = Zelená +pdfjs-editor-colorpicker-blue = + .title = Modrá +pdfjs-editor-colorpicker-pink = + .title = Růžová +pdfjs-editor-colorpicker-red = + .title = ÄŒervená + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Zobrazit vÅ¡e +pdfjs-editor-highlight-show-all-button = + .title = Zobrazit vÅ¡e + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Upravit alternativní text (popis obrázku) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = PÅ™idat alternativní text (popis obrázku) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Sem napiÅ¡te svůj popis… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Krátký popis pro lidi, kteří neuvidí obrázek nebo když se obrázek nenaÄítá. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Tento alternativní text byl vytvoÅ™en automaticky a může být nepÅ™esný. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Více informací +pdfjs-editor-new-alt-text-create-automatically-button-label = VytvoÅ™it alternativní text automaticky +pdfjs-editor-new-alt-text-not-now-button = TeÄ ne +pdfjs-editor-new-alt-text-error-title = NepodaÅ™ilo se automaticky vytvoÅ™it alternativní text +pdfjs-editor-new-alt-text-error-description = NapiÅ¡te prosím vlastní alternativní text nebo to zkuste znovu pozdÄ›ji. +pdfjs-editor-new-alt-text-error-close-button = Zavřít +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Stahuje se model AI pro alternativní texty ({ $downloadedSize } z { $totalSize } MB) + .aria-valuetext = Stahuje se model AI pro alternativní texty ({ $downloadedSize } z { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alternativní text byl pÅ™idán +pdfjs-editor-new-alt-text-added-button-label = Alternativní text byl pÅ™idán +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Chybí alternativní text +pdfjs-editor-new-alt-text-missing-button-label = Chybí alternativní text +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Zkontrolovat alternativní text +pdfjs-editor-new-alt-text-to-review-button-label = Zkontrolovat alternativní text +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = VytvoÅ™eno automaticky: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Nastavení alternativního textu obrázku +pdfjs-image-alt-text-settings-button-label = Nastavení alternativního textu obrázku +pdfjs-editor-alt-text-settings-dialog-label = Nastavení alternativního textu obrázku +pdfjs-editor-alt-text-settings-automatic-title = Automatický alternativní text +pdfjs-editor-alt-text-settings-create-model-button-label = VytvoÅ™it alternativní text automaticky +pdfjs-editor-alt-text-settings-create-model-description = Navrhuje popisy, které pomohou lidem, kteří nevidí obrázek nebo když se obrázek nenaÄte. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model AI pro alternativní text ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Běží lokálnÄ› na vaÅ¡em zařízení, takže vaÅ¡e data zůstávají v bezpeÄí. Vyžadováno pro automatický alternativní text. +pdfjs-editor-alt-text-settings-delete-model-button = Smazat +pdfjs-editor-alt-text-settings-download-model-button = Stáhnout +pdfjs-editor-alt-text-settings-downloading-model-button = Probíhá stahování... +pdfjs-editor-alt-text-settings-editor-title = Editor alternativního textu +pdfjs-editor-alt-text-settings-show-dialog-button-label = PÅ™i pÅ™idávání obrázku hned zobrazit editor alternativního textu +pdfjs-editor-alt-text-settings-show-dialog-description = Pomůže vám zajistit, aby vÅ¡echny vaÅ¡e obrázky obsahovaly alternativní text. +pdfjs-editor-alt-text-settings-close-button = Zavřít + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = ZvýraznÄ›ní odebráno +pdfjs-editor-undo-bar-message-freetext = Text odstranÄ›n +pdfjs-editor-undo-bar-message-ink = Kresba odstranÄ›na +pdfjs-editor-undo-bar-message-stamp = Obrázek odebrán +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } anotace odebrána + [few] { $count } anotace odebrány + [many] { $count } anotací odebráno + *[other] { $count } anotací odebráno + } +pdfjs-editor-undo-bar-undo-button = + .title = ZpÄ›t +pdfjs-editor-undo-bar-undo-button-label = ZpÄ›t +pdfjs-editor-undo-bar-close-button = + .title = Zavřít +pdfjs-editor-undo-bar-close-button-label = Zavřít diff --git a/public/assets/pdfjs/locale/cy/viewer.ftl b/public/assets/pdfjs/locale/cy/viewer.ftl new file mode 100755 index 0000000..8363835 --- /dev/null +++ b/public/assets/pdfjs/locale/cy/viewer.ftl @@ -0,0 +1,527 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Tudalen Flaenorol +pdfjs-previous-button-label = Blaenorol +pdfjs-next-button = + .title = Tudalen Nesaf +pdfjs-next-button-label = Nesaf +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Tudalen +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = o { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } o { $pagesCount }) +pdfjs-zoom-out-button = + .title = Lleihau +pdfjs-zoom-out-button-label = Lleihau +pdfjs-zoom-in-button = + .title = Cynyddu +pdfjs-zoom-in-button-label = Cynyddu +pdfjs-zoom-select = + .title = Chwyddo +pdfjs-presentation-mode-button = + .title = Newid i'r Modd Cyflwyno +pdfjs-presentation-mode-button-label = Modd Cyflwyno +pdfjs-open-file-button = + .title = Agor Ffeil +pdfjs-open-file-button-label = Agor +pdfjs-print-button = + .title = Argraffu +pdfjs-print-button-label = Argraffu +pdfjs-save-button = + .title = Cadw +pdfjs-save-button-label = Cadw +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Llwytho i lawr +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Llwytho i lawr +pdfjs-bookmark-button = + .title = Tudalen Gyfredol (Gweld URL o'r Dudalen Gyfredol) +pdfjs-bookmark-button-label = Tudalen Gyfredol + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Offer +pdfjs-tools-button-label = Offer +pdfjs-first-page-button = + .title = Mynd i'r Dudalen Gyntaf +pdfjs-first-page-button-label = Mynd i'r Dudalen Gyntaf +pdfjs-last-page-button = + .title = Mynd i'r Dudalen Olaf +pdfjs-last-page-button-label = Mynd i'r Dudalen Olaf +pdfjs-page-rotate-cw-button = + .title = Cylchdroi Clocwedd +pdfjs-page-rotate-cw-button-label = Cylchdroi Clocwedd +pdfjs-page-rotate-ccw-button = + .title = Cylchdroi Gwrthglocwedd +pdfjs-page-rotate-ccw-button-label = Cylchdroi Gwrthglocwedd +pdfjs-cursor-text-select-tool-button = + .title = Galluogi Dewis Offeryn Testun +pdfjs-cursor-text-select-tool-button-label = Offeryn Dewis Testun +pdfjs-cursor-hand-tool-button = + .title = Galluogi Offeryn Llaw +pdfjs-cursor-hand-tool-button-label = Offeryn Llaw +pdfjs-scroll-page-button = + .title = Defnyddio Sgrolio Tudalen +pdfjs-scroll-page-button-label = Sgrolio Tudalen +pdfjs-scroll-vertical-button = + .title = Defnyddio Sgrolio Fertigol +pdfjs-scroll-vertical-button-label = Sgrolio Fertigol +pdfjs-scroll-horizontal-button = + .title = Defnyddio Sgrolio Llorweddol +pdfjs-scroll-horizontal-button-label = Sgrolio Llorweddol +pdfjs-scroll-wrapped-button = + .title = Defnyddio Sgrolio Amlapio +pdfjs-scroll-wrapped-button-label = Sgrolio Amlapio +pdfjs-spread-none-button = + .title = Peidio uno trawsdaleniadau +pdfjs-spread-none-button-label = Dim Trawsdaleniadau +pdfjs-spread-odd-button = + .title = Uno trawsdaleniadau gan gychwyn gyda thudalennau odrif +pdfjs-spread-odd-button-label = Trawsdaleniadau Odrif +pdfjs-spread-even-button = + .title = Uno trawsdaleniadau gan gychwyn gyda thudalennau eilrif +pdfjs-spread-even-button-label = Trawsdaleniadau Eilrif + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Priodweddau Dogfen… +pdfjs-document-properties-button-label = Priodweddau Dogfen… +pdfjs-document-properties-file-name = Enw ffeil: +pdfjs-document-properties-file-size = Maint ffeil: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } beit) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } beit) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } beit) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } beit) +pdfjs-document-properties-title = Teitl: +pdfjs-document-properties-author = Awdur: +pdfjs-document-properties-subject = Pwnc: +pdfjs-document-properties-keywords = Allweddair: +pdfjs-document-properties-creation-date = Dyddiad Creu: +pdfjs-document-properties-modification-date = Dyddiad Addasu: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Crewr: +pdfjs-document-properties-producer = Cynhyrchydd PDF: +pdfjs-document-properties-version = Fersiwn PDF: +pdfjs-document-properties-page-count = Cyfrif Tudalen: +pdfjs-document-properties-page-size = Maint Tudalen: +pdfjs-document-properties-page-size-unit-inches = o fewn +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = portread +pdfjs-document-properties-page-size-orientation-landscape = tirlun +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Llythyr +pdfjs-document-properties-page-size-name-legal = Cyfreithiol + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Golwg Gwe Cyflym: +pdfjs-document-properties-linearized-yes = Iawn +pdfjs-document-properties-linearized-no = Na +pdfjs-document-properties-close-button = Cau + +## Print + +pdfjs-print-progress-message = Paratoi dogfen ar gyfer ei hargraffu… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Diddymu +pdfjs-printing-not-supported = Rhybudd: Nid yw argraffu yn cael ei gynnal yn llawn gan y porwr. +pdfjs-printing-not-ready = Rhybudd: Nid yw'r PDF wedi ei lwytho'n llawn ar gyfer argraffu. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Toglo'r Bar Ochr +pdfjs-toggle-sidebar-notification-button = + .title = Toglo'r Bar Ochr (mae'r ddogfen yn cynnwys amlinelliadau/atodiadau/haenau) +pdfjs-toggle-sidebar-button-label = Toglo'r Bar Ochr +pdfjs-document-outline-button = + .title = Dangos Amlinell Dogfen (clic dwbl i ymestyn/cau pob eitem) +pdfjs-document-outline-button-label = Amlinelliad Dogfen +pdfjs-attachments-button = + .title = Dangos Atodiadau +pdfjs-attachments-button-label = Atodiadau +pdfjs-layers-button = + .title = Dangos Haenau (cliciwch ddwywaith i ailosod yr holl haenau i'r cyflwr rhagosodedig) +pdfjs-layers-button-label = Haenau +pdfjs-thumbs-button = + .title = Dangos Lluniau Bach +pdfjs-thumbs-button-label = Lluniau Bach +pdfjs-current-outline-item-button = + .title = Canfod yr Eitem Amlinellol Gyfredol +pdfjs-current-outline-item-button-label = Yr Eitem Amlinellol Gyfredol +pdfjs-findbar-button = + .title = Canfod yn y Ddogfen +pdfjs-findbar-button-label = Canfod +pdfjs-additional-layers = Haenau Ychwanegol + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Tudalen { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Llun Bach Tudalen { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Canfod + .placeholder = Canfod yn y ddogfen… +pdfjs-find-previous-button = + .title = Canfod enghraifft flaenorol o'r ymadrodd +pdfjs-find-previous-button-label = Blaenorol +pdfjs-find-next-button = + .title = Canfod enghraifft nesaf yr ymadrodd +pdfjs-find-next-button-label = Nesaf +pdfjs-find-highlight-checkbox = Amlygu Popeth +pdfjs-find-match-case-checkbox-label = Cydweddu Maint +pdfjs-find-match-diacritics-checkbox-label = Diacritigau Cyfatebol +pdfjs-find-entire-word-checkbox-label = Geiriau Cyfan +pdfjs-find-reached-top = Wedi cyrraedd brig y dudalen, parhau o'r gwaelod +pdfjs-find-reached-bottom = Wedi cyrraedd diwedd y dudalen, parhau o'r brig +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [zero] { $current } o { $total } cydweddiadau + [one] { $current } o { $total } cydweddiad + [two] { $current } o { $total } gydweddiad + [few] { $current } o { $total } cydweddiad + [many] { $current } o { $total } chydweddiad + *[other] { $current } o { $total } cydweddiad + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [zero] Mwy nag { $limit } cydweddiadau + [one] Mwy nag { $limit } cydweddiad + [two] Mwy nag { $limit } gydweddiad + [few] Mwy nag { $limit } cydweddiad + [many] Mwy nag { $limit } chydweddiad + *[other] Mwy nag { $limit } cydweddiad + } +pdfjs-find-not-found = Heb ganfod ymadrodd + +## Predefined zoom values + +pdfjs-page-scale-width = Lled Tudalen +pdfjs-page-scale-fit = Ffit Tudalen +pdfjs-page-scale-auto = Chwyddo Awtomatig +pdfjs-page-scale-actual = Maint Gwirioneddol +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Tudalen { $page } + +## Loading indicator messages + +pdfjs-loading-error = Digwyddodd gwall wrth lwytho'r PDF. +pdfjs-invalid-file-error = Ffeil PDF annilys neu llwgr. +pdfjs-missing-file-error = Ffeil PDF coll. +pdfjs-unexpected-response-error = Ymateb annisgwyl gan y gweinydd. +pdfjs-rendering-error = Digwyddodd gwall wrth adeiladu'r dudalen. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Anodiad { $type } ] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Rhowch gyfrinair i agor y PDF. +pdfjs-password-invalid = Cyfrinair annilys. Ceisiwch eto. +pdfjs-password-ok-button = Iawn +pdfjs-password-cancel-button = Diddymu +pdfjs-web-fonts-disabled = Ffontiau gwe wedi eu hanalluogi: methu defnyddio ffontiau PDF mewnblanedig. + +## Editing + +pdfjs-editor-free-text-button = + .title = Testun +pdfjs-editor-free-text-button-label = Testun +pdfjs-editor-ink-button = + .title = Lluniadu +pdfjs-editor-ink-button-label = Lluniadu +pdfjs-editor-stamp-button = + .title = Ychwanegu neu olygu delweddau +pdfjs-editor-stamp-button-label = Ychwanegu neu olygu delweddau +pdfjs-editor-highlight-button = + .title = Amlygu +pdfjs-editor-highlight-button-label = Amlygu +pdfjs-highlight-floating-button1 = + .title = Amlygu + .aria-label = Amlygu +pdfjs-highlight-floating-button-label = Amlygu + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Dileu lluniad +pdfjs-editor-remove-freetext-button = + .title = Dileu testun +pdfjs-editor-remove-stamp-button = + .title = Dileu delwedd +pdfjs-editor-remove-highlight-button = + .title = Tynnu amlygiad + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Lliw +pdfjs-editor-free-text-size-input = Maint +pdfjs-editor-ink-color-input = Lliw +pdfjs-editor-ink-thickness-input = Trwch +pdfjs-editor-ink-opacity-input = Didreiddedd +pdfjs-editor-stamp-add-image-button = + .title = Ychwanegu delwedd +pdfjs-editor-stamp-add-image-button-label = Ychwanegu delwedd +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Trwch +pdfjs-editor-free-highlight-thickness-title = + .title = Newid trwch wrth amlygu eitemau heblaw testun +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Golygydd Testun + .default-content = Cychwyn teipio… +pdfjs-free-text = + .aria-label = Golygydd Testun +pdfjs-free-text-default-content = Cychwyn teipio… +pdfjs-ink = + .aria-label = Golygydd Lluniadu +pdfjs-ink-canvas = + .aria-label = Delwedd wedi'i chreu gan ddefnyddwyr + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Testun amgen (alt) +pdfjs-editor-alt-text-edit-button = + .aria-label = Golygu testun amgen +pdfjs-editor-alt-text-edit-button-label = Golygu testun amgen +pdfjs-editor-alt-text-dialog-label = Dewisiadau +pdfjs-editor-alt-text-dialog-description = Mae testun amgen (testun alt) yn helpu pan na all pobl weld y ddelwedd neu pan nad yw'n llwytho. +pdfjs-editor-alt-text-add-description-label = Ychwanegu disgrifiad +pdfjs-editor-alt-text-add-description-description = Anelwch at 1-2 frawddeg sy'n disgrifio'r pwnc, y cefndir neu'r gweithredoedd. +pdfjs-editor-alt-text-mark-decorative-label = Marcio fel addurniadol +pdfjs-editor-alt-text-mark-decorative-description = Mae'n cael ei ddefnyddio ar gyfer delweddau addurniadol, fel borderi neu farciau dŵr. +pdfjs-editor-alt-text-cancel-button = Diddymu +pdfjs-editor-alt-text-save-button = Cadw +pdfjs-editor-alt-text-decorative-tooltip = Marcio fel addurniadol +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Er enghraifft, “Mae dyn ifanc yn eistedd wrth fwrdd i fwyta pryd bwyd†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Testun amgen (alt) + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Y gornel chwith uchaf — newid maint +pdfjs-editor-resizer-label-top-middle = Canol uchaf - newid maint +pdfjs-editor-resizer-label-top-right = Y gornel dde uchaf - newid maint +pdfjs-editor-resizer-label-middle-right = De canol - newid maint +pdfjs-editor-resizer-label-bottom-right = Y gornel dde isaf — newid maint +pdfjs-editor-resizer-label-bottom-middle = Canol gwaelod — newid maint +pdfjs-editor-resizer-label-bottom-left = Y gornel chwith isaf — newid maint +pdfjs-editor-resizer-label-middle-left = Chwith canol — newid maint +pdfjs-editor-resizer-top-left = + .aria-label = Y gornel chwith uchaf — newid maint +pdfjs-editor-resizer-top-middle = + .aria-label = Canol uchaf - newid maint +pdfjs-editor-resizer-top-right = + .aria-label = Y gornel dde uchaf - newid maint +pdfjs-editor-resizer-middle-right = + .aria-label = De canol - newid maint +pdfjs-editor-resizer-bottom-right = + .aria-label = Y gornel dde isaf — newid maint +pdfjs-editor-resizer-bottom-middle = + .aria-label = Canol gwaelod — newid maint +pdfjs-editor-resizer-bottom-left = + .aria-label = Y gornel chwith isaf — newid maint +pdfjs-editor-resizer-middle-left = + .aria-label = Chwith canol — newid maint + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Lliw amlygu +pdfjs-editor-colorpicker-button = + .title = Newid lliw +pdfjs-editor-colorpicker-dropdown = + .aria-label = Dewisiadau lliw +pdfjs-editor-colorpicker-yellow = + .title = Melyn +pdfjs-editor-colorpicker-green = + .title = Gwyrdd +pdfjs-editor-colorpicker-blue = + .title = Glas +pdfjs-editor-colorpicker-pink = + .title = Pinc +pdfjs-editor-colorpicker-red = + .title = Coch + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Dangos y cyfan +pdfjs-editor-highlight-show-all-button = + .title = Dangos y cyfan + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Golygu testun amgen (disgrifiad o ddelwedd) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Ychwanegwch destun amgen (disgrifiad delwedd) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Ysgrifennwch eich disgrifiad yma… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Disgrifiad byr ar gyfer pobl sydd ddim yn gallu gweld y ddelwedd neu pan nad yw'r ddelwedd yn llwytho. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Cafodd y testun amgen hwn ei greu'n awtomatig a gall fod yn anghywir. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Rhagor +pdfjs-editor-new-alt-text-create-automatically-button-label = Creu testun amgen yn awtomatig +pdfjs-editor-new-alt-text-not-now-button = Nid nawr +pdfjs-editor-new-alt-text-error-title = Methu â chreu testun amgen yn awtomatig +pdfjs-editor-new-alt-text-error-description = Ysgrifennwch eich testun amgen eich hun neu ceisiwch eto yn nes ymlaen. +pdfjs-editor-new-alt-text-error-close-button = Cau +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Wrthi'n llwytho i lawr model AI testun amgen ( { $downloadedSize } o { $totalSize } MB) + .aria-valuetext = Wrthi'n llwytho i lawr model AI testun amgen ( { $downloadedSize } o { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Ychwanegwyd testun amgen +pdfjs-editor-new-alt-text-added-button-label = Ychwanegwyd testun amgen +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Testun amgen coll +pdfjs-editor-new-alt-text-missing-button-label = Testun amgen coll +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Adolygu'r testun amgen +pdfjs-editor-new-alt-text-to-review-button-label = Adolygu'r testun amgen +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Crëwyd yn awtomatig: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Gosodiadau testun amgen delwedd +pdfjs-image-alt-text-settings-button-label = Gosodiadau testun amgen delwedd +pdfjs-editor-alt-text-settings-dialog-label = Gosodiadau testun amgen delwedd +pdfjs-editor-alt-text-settings-automatic-title = Testun amgen awtomatig +pdfjs-editor-alt-text-settings-create-model-button-label = Creu testun amgen yn awtomatig +pdfjs-editor-alt-text-settings-create-model-description = Yn awgrymu disgrifiadau i helpu pobl sydd ddim yn gallu gweld y ddelwedd neu pan nad yw'r ddelwedd yn llwytho. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model AI testun amgen ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Yn rhedeg yn lleol ar eich dyfais fel bod eich data'n aros yn breifat. Yn ofynnol ar gyfer testun amgen awtomatig. +pdfjs-editor-alt-text-settings-delete-model-button = Dileu +pdfjs-editor-alt-text-settings-download-model-button = Llwytho i Lawr +pdfjs-editor-alt-text-settings-downloading-model-button = Wrthi'n llwytho i lawr… +pdfjs-editor-alt-text-settings-editor-title = Golygydd testun amgen +pdfjs-editor-alt-text-settings-show-dialog-button-label = Dangoswch y golygydd testun amgen yn syth wrth ychwanegu delwedd +pdfjs-editor-alt-text-settings-show-dialog-description = Yn eich helpu i wneud yn siŵr bod gan eich holl ddelweddau destun amgen. +pdfjs-editor-alt-text-settings-close-button = Cau + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Tynnwyd yr amlygu +pdfjs-editor-undo-bar-message-freetext = Tynnwyd y testun +pdfjs-editor-undo-bar-message-ink = Tynnwyd y lluniad +pdfjs-editor-undo-bar-message-stamp = Tynnwyd y ddelwedd +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [zero] { $count } anodiad wedi'u tynnu + [one] { $count } anodiad wedi'i dynnu + [two] { $count } anodiad wedi'u tynnu + [few] { $count } anodiad wedi'u tynnu + [many] { $count } anodiad wedi'u tynnu + *[other] { $count } anodiad wedi'u tynnu + } +pdfjs-editor-undo-bar-undo-button = + .title = Dadwneud +pdfjs-editor-undo-bar-undo-button-label = Dadwneud +pdfjs-editor-undo-bar-close-button = + .title = Cau +pdfjs-editor-undo-bar-close-button-label = Cau diff --git a/public/assets/pdfjs/locale/da/viewer.ftl b/public/assets/pdfjs/locale/da/viewer.ftl new file mode 100755 index 0000000..224eac1 --- /dev/null +++ b/public/assets/pdfjs/locale/da/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Forrige side +pdfjs-previous-button-label = Forrige +pdfjs-next-button = + .title = Næste side +pdfjs-next-button-label = Næste +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Side +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = af { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } af { $pagesCount }) +pdfjs-zoom-out-button = + .title = Zoom ud +pdfjs-zoom-out-button-label = Zoom ud +pdfjs-zoom-in-button = + .title = Zoom ind +pdfjs-zoom-in-button-label = Zoom ind +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Skift til fuldskærmsvisning +pdfjs-presentation-mode-button-label = Fuldskærmsvisning +pdfjs-open-file-button = + .title = Ã…bn fil +pdfjs-open-file-button-label = Ã…bn +pdfjs-print-button = + .title = Udskriv +pdfjs-print-button-label = Udskriv +pdfjs-save-button = + .title = Gem +pdfjs-save-button-label = Gem +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Hent +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Hent +pdfjs-bookmark-button = + .title = Aktuel side (vis URL fra den aktuelle side) +pdfjs-bookmark-button-label = Aktuel side + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Funktioner +pdfjs-tools-button-label = Funktioner +pdfjs-first-page-button = + .title = GÃ¥ til første side +pdfjs-first-page-button-label = GÃ¥ til første side +pdfjs-last-page-button = + .title = GÃ¥ til sidste side +pdfjs-last-page-button-label = GÃ¥ til sidste side +pdfjs-page-rotate-cw-button = + .title = Roter med uret +pdfjs-page-rotate-cw-button-label = Roter med uret +pdfjs-page-rotate-ccw-button = + .title = Roter mod uret +pdfjs-page-rotate-ccw-button-label = Roter mod uret +pdfjs-cursor-text-select-tool-button = + .title = Aktiver markeringsværktøj +pdfjs-cursor-text-select-tool-button-label = Markeringsværktøj +pdfjs-cursor-hand-tool-button = + .title = Aktiver hÃ¥ndværktøj +pdfjs-cursor-hand-tool-button-label = HÃ¥ndværktøj +pdfjs-scroll-page-button = + .title = Brug sidescrolling +pdfjs-scroll-page-button-label = Sidescrolling +pdfjs-scroll-vertical-button = + .title = Brug vertikal scrolling +pdfjs-scroll-vertical-button-label = Vertikal scrolling +pdfjs-scroll-horizontal-button = + .title = Brug horisontal scrolling +pdfjs-scroll-horizontal-button-label = Horisontal scrolling +pdfjs-scroll-wrapped-button = + .title = Brug ombrudt scrolling +pdfjs-scroll-wrapped-button-label = Ombrudt scrolling +pdfjs-spread-none-button = + .title = Vis enkeltsider +pdfjs-spread-none-button-label = Enkeltsider +pdfjs-spread-odd-button = + .title = Vis opslag med ulige sidenumre til venstre +pdfjs-spread-odd-button-label = Opslag med forside +pdfjs-spread-even-button = + .title = Vis opslag med lige sidenumre til venstre +pdfjs-spread-even-button-label = Opslag uden forside + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Dokumentegenskaber… +pdfjs-document-properties-button-label = Dokumentegenskaber… +pdfjs-document-properties-file-name = Filnavn: +pdfjs-document-properties-file-size = Filstørrelse: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Titel: +pdfjs-document-properties-author = Forfatter: +pdfjs-document-properties-subject = Emne: +pdfjs-document-properties-keywords = Nøgleord: +pdfjs-document-properties-creation-date = Oprettet: +pdfjs-document-properties-modification-date = Redigeret: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Program: +pdfjs-document-properties-producer = PDF-producent: +pdfjs-document-properties-version = PDF-version: +pdfjs-document-properties-page-count = Antal sider: +pdfjs-document-properties-page-size = Sidestørrelse: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = stÃ¥ende +pdfjs-document-properties-page-size-orientation-landscape = liggende +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Hurtig web-visning: +pdfjs-document-properties-linearized-yes = Ja +pdfjs-document-properties-linearized-no = Nej +pdfjs-document-properties-close-button = Luk + +## Print + +pdfjs-print-progress-message = Forbereder dokument til udskrivning… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Annuller +pdfjs-printing-not-supported = Advarsel: Udskrivning er ikke fuldt understøttet af browseren. +pdfjs-printing-not-ready = Advarsel: PDF-filen er ikke fuldt indlæst til udskrivning. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = SlÃ¥ sidepanel til eller fra +pdfjs-toggle-sidebar-notification-button = + .title = SlÃ¥ sidepanel til eller fra (dokumentet indeholder disposition/vedhæftede filer/lag) +pdfjs-toggle-sidebar-button-label = SlÃ¥ sidepanel til eller fra +pdfjs-document-outline-button = + .title = Vis dokumentets disposition (dobbeltklik for at udvide/sammenfolde alle elementer) +pdfjs-document-outline-button-label = Dokument-disposition +pdfjs-attachments-button = + .title = Vis vedhæftede filer +pdfjs-attachments-button-label = Vedhæftede filer +pdfjs-layers-button = + .title = Vis lag (dobbeltklik for at nulstille alle lag til standard-tilstanden) +pdfjs-layers-button-label = Lag +pdfjs-thumbs-button = + .title = Vis miniaturer +pdfjs-thumbs-button-label = Miniaturer +pdfjs-current-outline-item-button = + .title = Find det aktuelle dispositions-element +pdfjs-current-outline-item-button-label = Aktuelt dispositions-element +pdfjs-findbar-button = + .title = Find i dokument +pdfjs-findbar-button-label = Find +pdfjs-additional-layers = Yderligere lag + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Side { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniature af side { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Find + .placeholder = Find i dokument… +pdfjs-find-previous-button = + .title = Find den forrige forekomst +pdfjs-find-previous-button-label = Forrige +pdfjs-find-next-button = + .title = Find den næste forekomst +pdfjs-find-next-button-label = Næste +pdfjs-find-highlight-checkbox = Fremhæv alle +pdfjs-find-match-case-checkbox-label = Forskel pÃ¥ store og smÃ¥ bogstaver +pdfjs-find-match-diacritics-checkbox-label = Diakritiske tegn +pdfjs-find-entire-word-checkbox-label = Hele ord +pdfjs-find-reached-top = Toppen af siden blev nÃ¥et, fortsatte fra bunden +pdfjs-find-reached-bottom = Bunden af siden blev nÃ¥et, fortsatte fra toppen +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } af { $total } forekomst + *[other] { $current } af { $total } forekomster + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Mere end { $limit } forekomst + *[other] Mere end { $limit } forekomster + } +pdfjs-find-not-found = Der blev ikke fundet noget + +## Predefined zoom values + +pdfjs-page-scale-width = Sidebredde +pdfjs-page-scale-fit = Tilpas til side +pdfjs-page-scale-auto = Automatisk zoom +pdfjs-page-scale-actual = Faktisk størrelse +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Side { $page } + +## Loading indicator messages + +pdfjs-loading-error = Der opstod en fejl ved indlæsning af PDF-filen. +pdfjs-invalid-file-error = PDF-filen er ugyldig eller ødelagt. +pdfjs-missing-file-error = Manglende PDF-fil. +pdfjs-unexpected-response-error = Uventet svar fra serveren. +pdfjs-rendering-error = Der opstod en fejl ved generering af siden. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type }kommentar] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Angiv adgangskode til at Ã¥bne denne PDF-fil. +pdfjs-password-invalid = Ugyldig adgangskode. Prøv igen. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Fortryd +pdfjs-web-fonts-disabled = Webskrifttyper er deaktiverede. De indlejrede skrifttyper i PDF-filen kan ikke anvendes. + +## Editing + +pdfjs-editor-free-text-button = + .title = Tekst +pdfjs-editor-free-text-button-label = Tekst +pdfjs-editor-ink-button = + .title = Tegn +pdfjs-editor-ink-button-label = Tegn +pdfjs-editor-stamp-button = + .title = Tilføj eller rediger billeder +pdfjs-editor-stamp-button-label = Tilføj eller rediger billeder +pdfjs-editor-highlight-button = + .title = Fremhæv +pdfjs-editor-highlight-button-label = Fremhæv +pdfjs-highlight-floating-button1 = + .title = Fremhæv + .aria-label = Fremhæv +pdfjs-highlight-floating-button-label = Fremhæv + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Fjern tegning +pdfjs-editor-remove-freetext-button = + .title = Fjern tekst +pdfjs-editor-remove-stamp-button = + .title = Fjern billede +pdfjs-editor-remove-highlight-button = + .title = Fjern fremhævning + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Farve +pdfjs-editor-free-text-size-input = Størrelse +pdfjs-editor-ink-color-input = Farve +pdfjs-editor-ink-thickness-input = Tykkelse +pdfjs-editor-ink-opacity-input = Uigennemsigtighed +pdfjs-editor-stamp-add-image-button = + .title = Tilføj billede +pdfjs-editor-stamp-add-image-button-label = Tilføj billede +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Tykkelse +pdfjs-editor-free-highlight-thickness-title = + .title = Ændr tykkelse, nÃ¥r andre elementer end tekst fremhæves +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Teksteditor + .default-content = Begynd at skrive… +pdfjs-free-text = + .aria-label = Teksteditor +pdfjs-free-text-default-content = Begynd at skrive… +pdfjs-ink = + .aria-label = Tegnings-editor +pdfjs-ink-canvas = + .aria-label = Brugeroprettet billede + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alternativ tekst +pdfjs-editor-alt-text-edit-button = + .aria-label = Rediger alternativ tekst +pdfjs-editor-alt-text-edit-button-label = Rediger alternativ tekst +pdfjs-editor-alt-text-dialog-label = Vælg en indstilling +pdfjs-editor-alt-text-dialog-description = Alternativ tekst hjælper folk, som ikke kan se billedet eller nÃ¥r det ikke indlæses. +pdfjs-editor-alt-text-add-description-label = Tilføj en beskrivelse +pdfjs-editor-alt-text-add-description-description = Sigt efter en eller to sætninger, der beskriver emnet, omgivelserne eller handlinger. +pdfjs-editor-alt-text-mark-decorative-label = Marker som dekorativ +pdfjs-editor-alt-text-mark-decorative-description = Dette bruges for dekorative billeder som rammer eller vandmærker. +pdfjs-editor-alt-text-cancel-button = Annuller +pdfjs-editor-alt-text-save-button = Gem +pdfjs-editor-alt-text-decorative-tooltip = Markeret som dekorativ +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = For eksempel: "En ung mand sætter sig ved et bord for at spise et mÃ¥ltid mad" +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alternativ tekst + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Øverste venstre hjørne — tilpas størrelse +pdfjs-editor-resizer-label-top-middle = Øverste i midten — tilpas størrelse +pdfjs-editor-resizer-label-top-right = Øverste højre hjørne — tilpas størrelse +pdfjs-editor-resizer-label-middle-right = Midten til højre — tilpas størrelse +pdfjs-editor-resizer-label-bottom-right = Nederste højre hjørne - tilpas størrelse +pdfjs-editor-resizer-label-bottom-middle = Nederst i midten - tilpas størrelse +pdfjs-editor-resizer-label-bottom-left = Nederste venstre hjørne - tilpas størrelse +pdfjs-editor-resizer-label-middle-left = Midten til venstre — tilpas størrelse +pdfjs-editor-resizer-top-left = + .aria-label = Øverste venstre hjørne — tilpas størrelse +pdfjs-editor-resizer-top-middle = + .aria-label = Øverste i midten — tilpas størrelse +pdfjs-editor-resizer-top-right = + .aria-label = Øverste højre hjørne — tilpas størrelse +pdfjs-editor-resizer-middle-right = + .aria-label = Midten til højre — tilpas størrelse +pdfjs-editor-resizer-bottom-right = + .aria-label = Nederste højre hjørne - tilpas størrelse +pdfjs-editor-resizer-bottom-middle = + .aria-label = Nederst i midten - tilpas størrelse +pdfjs-editor-resizer-bottom-left = + .aria-label = Nederste venstre hjørne - tilpas størrelse +pdfjs-editor-resizer-middle-left = + .aria-label = Midten til venstre — tilpas størrelse + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Fremhævningsfarve +pdfjs-editor-colorpicker-button = + .title = Skift farve +pdfjs-editor-colorpicker-dropdown = + .aria-label = Farvevalg +pdfjs-editor-colorpicker-yellow = + .title = Gul +pdfjs-editor-colorpicker-green = + .title = Grøn +pdfjs-editor-colorpicker-blue = + .title = BlÃ¥ +pdfjs-editor-colorpicker-pink = + .title = Lyserød +pdfjs-editor-colorpicker-red = + .title = Rød + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Vis alle +pdfjs-editor-highlight-show-all-button = + .title = Vis alle + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Rediger alternativ tekst (billedbeskrivelse) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Tilføj alternativ tekst (billedbeskrivelse) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Skriv din beskrivelse her... +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Kort beskrivelse til personer, der ikke kan se billedet, eller nÃ¥r billedet ikke indlæses. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Denne alternative tekst blev oprettet automatisk og kan være upræcis. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Læs mere +pdfjs-editor-new-alt-text-create-automatically-button-label = Opret alternativ tekst automatisk +pdfjs-editor-new-alt-text-not-now-button = Ikke nu +pdfjs-editor-new-alt-text-error-title = Kunne ikke oprette alternativ tekst automatisk +pdfjs-editor-new-alt-text-error-description = Skriv din egen alternative tekst, eller prøv igen senere. +pdfjs-editor-new-alt-text-error-close-button = Luk +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Henter alternativ tekst AI-model ({ $downloadedSize } af { $totalSize } MB) + .aria-valuetext = Henter alternativ tekst AI-model ({ $downloadedSize } af { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alternativ tekst tilføjet +pdfjs-editor-new-alt-text-added-button-label = Alternativ tekst tilføjet +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Mangler alternativ tekst +pdfjs-editor-new-alt-text-missing-button-label = Mangler alternativ tekst +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = GennemgÃ¥ alternativ tekst +pdfjs-editor-new-alt-text-to-review-button-label = GennemgÃ¥ alternativ tekst +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Oprettet automatisk: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Indstillinger for alternativ tekst til billeder +pdfjs-image-alt-text-settings-button-label = Indstillinger for alternativ tekst til billeder +pdfjs-editor-alt-text-settings-dialog-label = Indstillinger for alternativ tekst til billeder +pdfjs-editor-alt-text-settings-automatic-title = Automatisk alternativ tekst +pdfjs-editor-alt-text-settings-create-model-button-label = Opret alternativ tekst automatisk +pdfjs-editor-alt-text-settings-create-model-description = ForeslÃ¥r beskrivelser for at hjælpe folk, der ikke kan se billedet, eller nÃ¥r billedet ikke indlæses. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = AI-model til at oprette alternative tekster ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Kører lokalt pÃ¥ din enhed, sÃ¥ dine data forbliver private. PÃ¥krævet for at anvende automatisk alternativ tekst. +pdfjs-editor-alt-text-settings-delete-model-button = Slet +pdfjs-editor-alt-text-settings-download-model-button = Hent +pdfjs-editor-alt-text-settings-downloading-model-button = Henter… +pdfjs-editor-alt-text-settings-editor-title = Redigering af alternativ tekst +pdfjs-editor-alt-text-settings-show-dialog-button-label = Vis redigering af alternativ tekst med det samme, nÃ¥r et billede tilføjes +pdfjs-editor-alt-text-settings-show-dialog-description = Hjælper dig med at sikre, at alle dine billeder har alternativ tekst. +pdfjs-editor-alt-text-settings-close-button = Luk + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Fremhævning fjernet +pdfjs-editor-undo-bar-message-freetext = Tekst fjernet +pdfjs-editor-undo-bar-message-ink = Tegning fjernet +pdfjs-editor-undo-bar-message-stamp = Billede fjernet +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } kommentar fjernet + *[other] { $count } kommentarer fjernet + } +pdfjs-editor-undo-bar-undo-button = + .title = Fortryd +pdfjs-editor-undo-bar-undo-button-label = Fortryd +pdfjs-editor-undo-bar-close-button = + .title = Luk +pdfjs-editor-undo-bar-close-button-label = Luk diff --git a/public/assets/pdfjs/locale/de/viewer.ftl b/public/assets/pdfjs/locale/de/viewer.ftl new file mode 100755 index 0000000..ee26455 --- /dev/null +++ b/public/assets/pdfjs/locale/de/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Eine Seite zurück +pdfjs-previous-button-label = Zurück +pdfjs-next-button = + .title = Eine Seite vor +pdfjs-next-button-label = Vor +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Seite +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = von { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } von { $pagesCount }) +pdfjs-zoom-out-button = + .title = Verkleinern +pdfjs-zoom-out-button-label = Verkleinern +pdfjs-zoom-in-button = + .title = Vergrößern +pdfjs-zoom-in-button-label = Vergrößern +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = In Präsentationsmodus wechseln +pdfjs-presentation-mode-button-label = Präsentationsmodus +pdfjs-open-file-button = + .title = Datei öffnen +pdfjs-open-file-button-label = Öffnen +pdfjs-print-button = + .title = Drucken +pdfjs-print-button-label = Drucken +pdfjs-save-button = + .title = Speichern +pdfjs-save-button-label = Speichern +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Herunterladen +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Herunterladen +pdfjs-bookmark-button = + .title = Aktuelle Seite (URL von aktueller Seite anzeigen) +pdfjs-bookmark-button-label = Aktuelle Seite + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Werkzeuge +pdfjs-tools-button-label = Werkzeuge +pdfjs-first-page-button = + .title = Erste Seite anzeigen +pdfjs-first-page-button-label = Erste Seite anzeigen +pdfjs-last-page-button = + .title = Letzte Seite anzeigen +pdfjs-last-page-button-label = Letzte Seite anzeigen +pdfjs-page-rotate-cw-button = + .title = Im Uhrzeigersinn drehen +pdfjs-page-rotate-cw-button-label = Im Uhrzeigersinn drehen +pdfjs-page-rotate-ccw-button = + .title = Gegen Uhrzeigersinn drehen +pdfjs-page-rotate-ccw-button-label = Gegen Uhrzeigersinn drehen +pdfjs-cursor-text-select-tool-button = + .title = Textauswahl-Werkzeug aktivieren +pdfjs-cursor-text-select-tool-button-label = Textauswahl-Werkzeug +pdfjs-cursor-hand-tool-button = + .title = Hand-Werkzeug aktivieren +pdfjs-cursor-hand-tool-button-label = Hand-Werkzeug +pdfjs-scroll-page-button = + .title = Seiten einzeln anordnen +pdfjs-scroll-page-button-label = Einzelseitenanordnung +pdfjs-scroll-vertical-button = + .title = Seiten übereinander anordnen +pdfjs-scroll-vertical-button-label = Vertikale Seitenanordnung +pdfjs-scroll-horizontal-button = + .title = Seiten nebeneinander anordnen +pdfjs-scroll-horizontal-button-label = Horizontale Seitenanordnung +pdfjs-scroll-wrapped-button = + .title = Seiten neben- und übereinander anordnen, abhängig vom Platz +pdfjs-scroll-wrapped-button-label = Kombinierte Seitenanordnung +pdfjs-spread-none-button = + .title = Seiten nicht nebeneinander anzeigen +pdfjs-spread-none-button-label = Einzelne Seiten +pdfjs-spread-odd-button = + .title = Jeweils eine ungerade und eine gerade Seite nebeneinander anzeigen +pdfjs-spread-odd-button-label = Ungerade + gerade Seite +pdfjs-spread-even-button = + .title = Jeweils eine gerade und eine ungerade Seite nebeneinander anzeigen +pdfjs-spread-even-button-label = Gerade + ungerade Seite + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Dokumenteigenschaften +pdfjs-document-properties-button-label = Dokumenteigenschaften… +pdfjs-document-properties-file-name = Dateiname: +pdfjs-document-properties-file-size = Dateigröße: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } Bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } Bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } Bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } Bytes) +pdfjs-document-properties-title = Titel: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Thema: +pdfjs-document-properties-keywords = Stichwörter: +pdfjs-document-properties-creation-date = Erstelldatum: +pdfjs-document-properties-modification-date = Bearbeitungsdatum: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date } { $time } +pdfjs-document-properties-creator = Anwendung: +pdfjs-document-properties-producer = PDF erstellt mit: +pdfjs-document-properties-version = PDF-Version: +pdfjs-document-properties-page-count = Seitenzahl: +pdfjs-document-properties-page-size = Seitengröße: +pdfjs-document-properties-page-size-unit-inches = Zoll +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = Hochformat +pdfjs-document-properties-page-size-orientation-landscape = Querformat +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Schnelle Webanzeige: +pdfjs-document-properties-linearized-yes = Ja +pdfjs-document-properties-linearized-no = Nein +pdfjs-document-properties-close-button = Schließen + +## Print + +pdfjs-print-progress-message = Dokument wird für Drucken vorbereitet… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress } % +pdfjs-print-progress-close-button = Abbrechen +pdfjs-printing-not-supported = Warnung: Die Drucken-Funktion wird durch diesen Browser nicht vollständig unterstützt. +pdfjs-printing-not-ready = Warnung: Die PDF-Datei ist nicht vollständig geladen, dies ist für das Drucken aber empfohlen. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Sidebar umschalten +pdfjs-toggle-sidebar-notification-button = + .title = Sidebar umschalten (Dokument enthält Dokumentstruktur/Anhänge/Ebenen) +pdfjs-toggle-sidebar-button-label = Sidebar umschalten +pdfjs-document-outline-button = + .title = Dokumentstruktur anzeigen (Doppelklicken, um alle Einträge aus- bzw. einzuklappen) +pdfjs-document-outline-button-label = Dokumentstruktur +pdfjs-attachments-button = + .title = Anhänge anzeigen +pdfjs-attachments-button-label = Anhänge +pdfjs-layers-button = + .title = Ebenen anzeigen (Doppelklicken, um alle Ebenen auf den Standardzustand zurückzusetzen) +pdfjs-layers-button-label = Ebenen +pdfjs-thumbs-button = + .title = Miniaturansichten anzeigen +pdfjs-thumbs-button-label = Miniaturansichten +pdfjs-current-outline-item-button = + .title = Aktuelles Struktur-Element finden +pdfjs-current-outline-item-button-label = Aktuelles Struktur-Element +pdfjs-findbar-button = + .title = Dokument durchsuchen +pdfjs-findbar-button-label = Suchen +pdfjs-additional-layers = Zusätzliche Ebenen + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Seite { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniaturansicht von Seite { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Suchen + .placeholder = Dokument durchsuchen… +pdfjs-find-previous-button = + .title = Vorheriges Vorkommen des Suchbegriffs finden +pdfjs-find-previous-button-label = Zurück +pdfjs-find-next-button = + .title = Nächstes Vorkommen des Suchbegriffs finden +pdfjs-find-next-button-label = Weiter +pdfjs-find-highlight-checkbox = Alle hervorheben +pdfjs-find-match-case-checkbox-label = Groß-/Kleinschreibung beachten +pdfjs-find-match-diacritics-checkbox-label = Akzente +pdfjs-find-entire-word-checkbox-label = Ganze Wörter +pdfjs-find-reached-top = Anfang des Dokuments erreicht, fahre am Ende fort +pdfjs-find-reached-bottom = Ende des Dokuments erreicht, fahre am Anfang fort +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } von { $total } Übereinstimmung + *[other] { $current } von { $total } Übereinstimmungen + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Mehr als { $limit } Übereinstimmung + *[other] Mehr als { $limit } Übereinstimmungen + } +pdfjs-find-not-found = Suchbegriff nicht gefunden + +## Predefined zoom values + +pdfjs-page-scale-width = Seitenbreite +pdfjs-page-scale-fit = Seitengröße +pdfjs-page-scale-auto = Automatischer Zoom +pdfjs-page-scale-actual = Originalgröße +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale } % + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Seite { $page } + +## Loading indicator messages + +pdfjs-loading-error = Beim Laden der PDF-Datei trat ein Fehler auf. +pdfjs-invalid-file-error = Ungültige oder beschädigte PDF-Datei +pdfjs-missing-file-error = Fehlende PDF-Datei +pdfjs-unexpected-response-error = Unerwartete Antwort des Servers +pdfjs-rendering-error = Beim Darstellen der Seite trat ein Fehler auf. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Anlage: { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Geben Sie zum Öffnen der PDF-Datei deren Passwort ein. +pdfjs-password-invalid = Falsches Passwort. Bitte versuchen Sie es erneut. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Abbrechen +pdfjs-web-fonts-disabled = Web-Schriftarten sind deaktiviert: Eingebettete PDF-Schriftarten konnten nicht geladen werden. + +## Editing + +pdfjs-editor-free-text-button = + .title = Text +pdfjs-editor-free-text-button-label = Text +pdfjs-editor-ink-button = + .title = Zeichnen +pdfjs-editor-ink-button-label = Zeichnen +pdfjs-editor-stamp-button = + .title = Grafiken hinzufügen oder bearbeiten +pdfjs-editor-stamp-button-label = Grafiken hinzufügen oder bearbeiten +pdfjs-editor-highlight-button = + .title = Hervorheben +pdfjs-editor-highlight-button-label = Hervorheben +pdfjs-highlight-floating-button1 = + .title = Hervorheben + .aria-label = Hervorheben +pdfjs-highlight-floating-button-label = Hervorheben + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Zeichnung entfernen +pdfjs-editor-remove-freetext-button = + .title = Text entfernen +pdfjs-editor-remove-stamp-button = + .title = Grafik entfernen +pdfjs-editor-remove-highlight-button = + .title = Hervorhebung entfernen + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Farbe +pdfjs-editor-free-text-size-input = Größe +pdfjs-editor-ink-color-input = Farbe +pdfjs-editor-ink-thickness-input = Linienstärke +pdfjs-editor-ink-opacity-input = Deckkraft +pdfjs-editor-stamp-add-image-button = + .title = Grafik hinzufügen +pdfjs-editor-stamp-add-image-button-label = Grafik hinzufügen +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Linienstärke +pdfjs-editor-free-highlight-thickness-title = + .title = Linienstärke beim Hervorheben anderer Elemente als Text ändern +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Texteditor + .default-content = Schreiben beginnen… +pdfjs-free-text = + .aria-label = Texteditor +pdfjs-free-text-default-content = Schreiben beginnen… +pdfjs-ink = + .aria-label = Zeichnungseditor +pdfjs-ink-canvas = + .aria-label = Vom Benutzer erstelltes Bild + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alternativ-Text +pdfjs-editor-alt-text-edit-button = + .aria-label = Alternativ-Text bearbeiten +pdfjs-editor-alt-text-edit-button-label = Alternativ-Text bearbeiten +pdfjs-editor-alt-text-dialog-label = Option wählen +pdfjs-editor-alt-text-dialog-description = Alt-Text (Alternativtext) hilft, wenn Personen die Grafik nicht sehen können oder wenn sie nicht geladen wird. +pdfjs-editor-alt-text-add-description-label = Beschreibung hinzufügen +pdfjs-editor-alt-text-add-description-description = Ziel sind 1-2 Sätze, die das Thema, das Szenario oder Aktionen beschreiben. +pdfjs-editor-alt-text-mark-decorative-label = Als dekorativ markieren +pdfjs-editor-alt-text-mark-decorative-description = Dies wird für Ziergrafiken wie Ränder oder Wasserzeichen verwendet. +pdfjs-editor-alt-text-cancel-button = Abbrechen +pdfjs-editor-alt-text-save-button = Speichern +pdfjs-editor-alt-text-decorative-tooltip = Als dekorativ markiert +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Zum Beispiel: "Ein junger Mann setzt sich an einen Tisch, um zu essen." +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alternativ-Text + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Linke obere Ecke - Größe ändern +pdfjs-editor-resizer-label-top-middle = Oben mittig - Größe ändern +pdfjs-editor-resizer-label-top-right = Rechts oben - Größe ändern +pdfjs-editor-resizer-label-middle-right = Mitte rechts - Größe ändern +pdfjs-editor-resizer-label-bottom-right = Rechte untere Ecke - Größe ändern +pdfjs-editor-resizer-label-bottom-middle = Unten mittig - Größe ändern +pdfjs-editor-resizer-label-bottom-left = Linke untere Ecke - Größe ändern +pdfjs-editor-resizer-label-middle-left = Mitte links - Größe ändern +pdfjs-editor-resizer-top-left = + .aria-label = Linke obere Ecke - Größe ändern +pdfjs-editor-resizer-top-middle = + .aria-label = Oben mittig - Größe ändern +pdfjs-editor-resizer-top-right = + .aria-label = Rechts oben - Größe ändern +pdfjs-editor-resizer-middle-right = + .aria-label = Mitte rechts - Größe ändern +pdfjs-editor-resizer-bottom-right = + .aria-label = Rechte untere Ecke - Größe ändern +pdfjs-editor-resizer-bottom-middle = + .aria-label = Unten mittig - Größe ändern +pdfjs-editor-resizer-bottom-left = + .aria-label = Linke untere Ecke - Größe ändern +pdfjs-editor-resizer-middle-left = + .aria-label = Mitte links - Größe ändern + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Hervorhebungsfarbe +pdfjs-editor-colorpicker-button = + .title = Farbe ändern +pdfjs-editor-colorpicker-dropdown = + .aria-label = Farbauswahl +pdfjs-editor-colorpicker-yellow = + .title = Gelb +pdfjs-editor-colorpicker-green = + .title = Grün +pdfjs-editor-colorpicker-blue = + .title = Blau +pdfjs-editor-colorpicker-pink = + .title = Pink +pdfjs-editor-colorpicker-red = + .title = Rot + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Alle anzeigen +pdfjs-editor-highlight-show-all-button = + .title = Alle anzeigen + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Alternativ-Text (Grafikbeschreibung) bearbeiten +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Alternativ-Text (Grafikbeschreibung) hinzufügen +pdfjs-editor-new-alt-text-textarea = + .placeholder = Schreiben Sie Ihre Beschreibung hier… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Kurze Beschreibung für Personen, die die Grafik nicht sehen können, oder wenn die Grafik nicht geladen wird. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Dieser Alternativ-Text wurde automatisch erstellt und könnte ungenau sein. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Weitere Informationen +pdfjs-editor-new-alt-text-create-automatically-button-label = Alternativ-Text automatisch erstellen +pdfjs-editor-new-alt-text-not-now-button = Nicht jetzt +pdfjs-editor-new-alt-text-error-title = Alternativ-Text konnte nicht automatisch erstellt werden +pdfjs-editor-new-alt-text-error-description = Bitte schreiben Sie Ihren eigenen Alternativ-Text oder versuchen Sie es später erneut. +pdfjs-editor-new-alt-text-error-close-button = Schließen +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Alternativ-Text-KI-Modell wird heruntergeladen ({ $downloadedSize } von { $totalSize } MB) + .aria-valuetext = Alternativ-Text-KI-Modell wird heruntergeladen ({ $downloadedSize } von { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alternativ-Text hinzugefügt +pdfjs-editor-new-alt-text-added-button-label = Alternativ-Text hinzugefügt +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Fehlender Alternativ-Text +pdfjs-editor-new-alt-text-missing-button-label = Fehlender Alternativ-Text +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Alternativ-Text überprüfen +pdfjs-editor-new-alt-text-to-review-button-label = Alternativ-Text überprüfen +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automatisch erstellt: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Alternativ-Text-Einstellungen für Grafiken +pdfjs-image-alt-text-settings-button-label = Alternativ-Text-Einstellungen für Grafiken +pdfjs-editor-alt-text-settings-dialog-label = Alternativ-Text-Einstellungen für Grafiken +pdfjs-editor-alt-text-settings-automatic-title = Automatischer Alternativ-Text +pdfjs-editor-alt-text-settings-create-model-button-label = Alternativ-Text automatisch erstellen +pdfjs-editor-alt-text-settings-create-model-description = Schlägt Beschreibungen vor, um Personen zu helfen, die die Grafik nicht sehen können, oder wenn die Grafik nicht geladen wird. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Alternativ-Text-KI-Modell ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Wird lokal auf Ihrem Gerät ausgeführt, sodass Ihre Daten privat bleiben. Erforderlich für automatischen Alternativ-Text. +pdfjs-editor-alt-text-settings-delete-model-button = Löschen +pdfjs-editor-alt-text-settings-download-model-button = Herunterladen +pdfjs-editor-alt-text-settings-downloading-model-button = Wird heruntergeladen… +pdfjs-editor-alt-text-settings-editor-title = Alternativ-Texteditor +pdfjs-editor-alt-text-settings-show-dialog-button-label = Alternativ-Texteditor beim Hinzufügen einer Grafik anzeigen +pdfjs-editor-alt-text-settings-show-dialog-description = Hilft Ihnen, sicherzustellen, dass alle Ihre Grafiken Alternativ-Text haben. +pdfjs-editor-alt-text-settings-close-button = Schließen + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Hervorhebung entfernt +pdfjs-editor-undo-bar-message-freetext = Text entfernt +pdfjs-editor-undo-bar-message-ink = Zeichnung entfernt +pdfjs-editor-undo-bar-message-stamp = Grafik entfernt +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } Anmerkung entfernt + *[other] { $count } Anmerkungen entfernt + } +pdfjs-editor-undo-bar-undo-button = + .title = Rückgängig +pdfjs-editor-undo-bar-undo-button-label = Rückgängig +pdfjs-editor-undo-bar-close-button = + .title = Schließen +pdfjs-editor-undo-bar-close-button-label = Schließen diff --git a/public/assets/pdfjs/locale/dsb/viewer.ftl b/public/assets/pdfjs/locale/dsb/viewer.ftl new file mode 100755 index 0000000..24ac94f --- /dev/null +++ b/public/assets/pdfjs/locale/dsb/viewer.ftl @@ -0,0 +1,521 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = PjerwjejÅ¡ny bok +pdfjs-previous-button-label = SlÄ›dk +pdfjs-next-button = + .title = PÅ›iducy bok +pdfjs-next-button-label = Dalej +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Bok +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = z { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } z { $pagesCount }) +pdfjs-zoom-out-button = + .title = PómjeńšyÅ› +pdfjs-zoom-out-button-label = PómjeńšyÅ› +pdfjs-zoom-in-button = + .title = PówÄ›tÅ¡yÅ› +pdfjs-zoom-in-button-label = PówÄ›tÅ¡yÅ› +pdfjs-zoom-select = + .title = SkalÄ›rowanje +pdfjs-presentation-mode-button = + .title = Do prezentaciskego modusa pÅ›ejÅ› +pdfjs-presentation-mode-button-label = Prezentaciski modus +pdfjs-open-file-button = + .title = Dataju wócyniÅ› +pdfjs-open-file-button-label = WócyniÅ› +pdfjs-print-button = + .title = ÅšišćaÅ› +pdfjs-print-button-label = ÅšišćaÅ› +pdfjs-save-button = + .title = SkÅ‚adowaÅ› +pdfjs-save-button-label = SkÅ‚adowaÅ› +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = ZeśěgnuÅ› +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = ZeśěgnuÅ› +pdfjs-bookmark-button = + .title = Aktualny bok (URL z aktualnego boka pokazaÅ›) +pdfjs-bookmark-button-label = Aktualny bok + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = RÄ›dy +pdfjs-tools-button-label = RÄ›dy +pdfjs-first-page-button = + .title = K prÄ›dnemu bokoju +pdfjs-first-page-button-label = K prÄ›dnemu bokoju +pdfjs-last-page-button = + .title = K slÄ›dnemu bokoju +pdfjs-last-page-button-label = K slÄ›dnemu bokoju +pdfjs-page-rotate-cw-button = + .title = WobwjertnuÅ› ako Å¡pÄ›ra źo +pdfjs-page-rotate-cw-button-label = WobwjertnuÅ› ako Å¡pÄ›ra źo +pdfjs-page-rotate-ccw-button = + .title = WobwjertnuÅ› nawopaki ako Å¡pÄ›ra źo +pdfjs-page-rotate-ccw-button-label = WobwjertnuÅ› nawopaki ako Å¡pÄ›ra źo +pdfjs-cursor-text-select-tool-button = + .title = RÄ›d za wubÄ›ranje teksta zmóžniÅ› +pdfjs-cursor-text-select-tool-button-label = RÄ›d za wubÄ›ranje teksta +pdfjs-cursor-hand-tool-button = + .title = Rucny rÄ›d zmóžniÅ› +pdfjs-cursor-hand-tool-button-label = Rucny rÄ›d +pdfjs-scroll-page-button = + .title = Kulanje boka wužywaÅ› +pdfjs-scroll-page-button-label = Kulanje boka +pdfjs-scroll-vertical-button = + .title = Wertikalne suwanje wužywaÅ› +pdfjs-scroll-vertical-button-label = Wertikalne suwanje +pdfjs-scroll-horizontal-button = + .title = Horicontalne suwanje wužywaÅ› +pdfjs-scroll-horizontal-button-label = Horicontalne suwanje +pdfjs-scroll-wrapped-button = + .title = Pózlažke suwanje wužywaÅ› +pdfjs-scroll-wrapped-button-label = Pózlažke suwanje +pdfjs-spread-none-button = + .title = Boki njezwÄ›zaÅ› +pdfjs-spread-none-button-label = Žeden dwójny bok +pdfjs-spread-odd-button = + .title = Boki zachopinajucy z njerownymi bokami zwÄ›zaÅ› +pdfjs-spread-odd-button-label = Njerowne boki +pdfjs-spread-even-button = + .title = Boki zachopinajucy z rownymi bokami zwÄ›zaÅ› +pdfjs-spread-even-button-label = Rowne boki + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Dokumentowe kakosći… +pdfjs-document-properties-button-label = Dokumentowe kakosći… +pdfjs-document-properties-file-name = MÄ› dataje: +pdfjs-document-properties-file-size = Wjelikosć dataje: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bajtow) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bajtow) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bajtow) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bajtow) +pdfjs-document-properties-title = Titel: +pdfjs-document-properties-author = Awtor: +pdfjs-document-properties-subject = Tema: +pdfjs-document-properties-keywords = Klucowe sÅ‚owa: +pdfjs-document-properties-creation-date = Datum napóranja: +pdfjs-document-properties-modification-date = Datum zmÄ›ny: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Awtor: +pdfjs-document-properties-producer = PDF-gótowaÅ•: +pdfjs-document-properties-version = PDF-wersija: +pdfjs-document-properties-page-count = Licba bokow: +pdfjs-document-properties-page-size = Wjelikosć boka: +pdfjs-document-properties-page-size-unit-inches = col +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = wusoki format +pdfjs-document-properties-page-size-orientation-landscape = prÄ›cny format +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Fast Web View: +pdfjs-document-properties-linearized-yes = Jo +pdfjs-document-properties-linearized-no = NÄ› +pdfjs-document-properties-close-button = ZacyniÅ› + +## Print + +pdfjs-print-progress-message = Dokument pÅ›igótujo se za Å›išćanje… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = PÅ›etergnuÅ› +pdfjs-printing-not-supported = Warnowanje: Åšišćanje njepódpÄ›ra se poÅ‚nje pÅ›ez toÅ› ten wobglÄ›dowak. +pdfjs-printing-not-ready = Warnowanje: PDF njejo se za Å›išćanje dopoÅ‚nje zacytaÅ‚. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Bócnicu pokazaÅ›/schowaÅ› +pdfjs-toggle-sidebar-notification-button = + .title = Bocnicu pÅ›eÅ¡altowaÅ› (dokument rozrÄ›dowanje/pÅ›ipiski/warstwy wopÅ›imujo) +pdfjs-toggle-sidebar-button-label = Bócnicu pokazaÅ›/schowaÅ› +pdfjs-document-outline-button = + .title = Dokumentowe naraźenje pokazaÅ› (dwójne kliknjenje, aby se wÅ¡ykne zapiski pokazali/schowali) +pdfjs-document-outline-button-label = Dokumentowa struktura +pdfjs-attachments-button = + .title = PÅ›idanki pokazaÅ› +pdfjs-attachments-button-label = PÅ›idanki +pdfjs-layers-button = + .title = Warstwy pokazaÅ› (klikniÅ›o dwójcy, aby wÅ¡ykne warstwy na standardny staw slÄ›dk stajiÅ‚) +pdfjs-layers-button-label = Warstwy +pdfjs-thumbs-button = + .title = Miniatury pokazaÅ› +pdfjs-thumbs-button-label = Miniatury +pdfjs-current-outline-item-button = + .title = Aktualny rozrÄ›dowaÅ„ski zapisk pytaÅ› +pdfjs-current-outline-item-button-label = Aktualny rozrÄ›dowaÅ„ski zapisk +pdfjs-findbar-button = + .title = W dokumenÅ›e pytaÅ› +pdfjs-findbar-button-label = PytaÅ› +pdfjs-additional-layers = DalÅ¡ne warstwy + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Bok { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura boka { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = PytaÅ› + .placeholder = W dokumenÅ›e pytaś… +pdfjs-find-previous-button = + .title = PjerwjejÅ¡ne wustupowanje pytaÅ„skego wuraza pytaÅ› +pdfjs-find-previous-button-label = SlÄ›dk +pdfjs-find-next-button = + .title = PÅ›idujuce wustupowanje pytaÅ„skego wuraza pytaÅ› +pdfjs-find-next-button-label = Dalej +pdfjs-find-highlight-checkbox = WÅ¡ykne wuzwignuÅ› +pdfjs-find-match-case-checkbox-label = Na wjelikopisanje źiwaÅ› +pdfjs-find-match-diacritics-checkbox-label = Diakritiske znamuÅ¡ka wužywaÅ› +pdfjs-find-entire-word-checkbox-label = CeÅ‚e sÅ‚owa +pdfjs-find-reached-top = ZachopjeÅ„k dokumenta dostany, pókÅ¡acujo se z kóńcom +pdfjs-find-reached-bottom = Kóńc dokumenta dostany, pókÅ¡acujo se ze zachopjeÅ„kom +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } z { $total } wótpowÄ›dnika + [two] { $current } z { $total } wótpowÄ›dnikowu + [few] { $current } z { $total } wótpowÄ›dnikow + *[other] { $current } z { $total } wótpowÄ›dnikow + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] WuÅ¡ej { $limit } wótpowÄ›dnik + [two] WuÅ¡ej { $limit } wótpowÄ›dnika + [few] WuÅ¡ej { $limit } wótpowÄ›dniki + *[other] WuÅ¡ej { $limit } wótpowÄ›dniki + } +pdfjs-find-not-found = PytaÅ„ski wuraz njejo se namakaÅ‚ + +## Predefined zoom values + +pdfjs-page-scale-width = Å yrokosć boka +pdfjs-page-scale-fit = Wjelikosć boka +pdfjs-page-scale-auto = Awtomatiske skalÄ›rowanje +pdfjs-page-scale-actual = Aktualna wjelikosć +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Bok { $page } + +## Loading indicator messages + +pdfjs-loading-error = PÅ›i zacytowanju PDF jo zmólka nastaÅ‚a. +pdfjs-invalid-file-error = NjepÅ‚aÅ›iwa abo wobÅ¡kóźona PDF-dataja. +pdfjs-missing-file-error = Felujuca PDF-dataja. +pdfjs-unexpected-response-error = Njewócakane serwerowe wótegrono. +pdfjs-rendering-error = PÅ›i zwobraznjanju boka jo zmólka nastaÅ‚a. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Typ pÅ›ipiskow: { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = ZapódajÅ›o gronidÅ‚o, aby PDF-dataju wócyniÅ‚. +pdfjs-password-invalid = NjepÅ‚aÅ›iwe gronidÅ‚o. PÅ¡osym wopytajÅ›o hyšći raz. +pdfjs-password-ok-button = W pórěźe +pdfjs-password-cancel-button = PÅ›etergnuÅ› +pdfjs-web-fonts-disabled = Webpisma su znjemóžnjone: njejo móžno, zasajźone PDF-pisma wužywaÅ›. + +## Editing + +pdfjs-editor-free-text-button = + .title = Tekst +pdfjs-editor-free-text-button-label = Tekst +pdfjs-editor-ink-button = + .title = KresliÅ› +pdfjs-editor-ink-button-label = KresliÅ› +pdfjs-editor-stamp-button = + .title = Wobraze pÅ›idaÅ› abo wobźěłaÅ› +pdfjs-editor-stamp-button-label = Wobraze pÅ›idaÅ› abo wobźěłaÅ› +pdfjs-editor-highlight-button = + .title = WuzwignuÅ› +pdfjs-editor-highlight-button-label = WuzwignuÅ› +pdfjs-highlight-floating-button1 = + .title = WuzwignuÅ› + .aria-label = WuzwignuÅ› +pdfjs-highlight-floating-button-label = WuzwignuÅ› + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Kreslanku wótwónoźeÅ› +pdfjs-editor-remove-freetext-button = + .title = Tekst wótwónoźeÅ› +pdfjs-editor-remove-stamp-button = + .title = Wobraz wótwónoźeÅ› +pdfjs-editor-remove-highlight-button = + .title = Wuzwignjenje wótpóraÅ› + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Barwa +pdfjs-editor-free-text-size-input = Wjelikosć +pdfjs-editor-ink-color-input = Barwa +pdfjs-editor-ink-thickness-input = TÅ‚ustosć +pdfjs-editor-ink-opacity-input = Opacita +pdfjs-editor-stamp-add-image-button = + .title = Wobraz pÅ›idaÅ› +pdfjs-editor-stamp-add-image-button-label = Wobraz pÅ›idaÅ› +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = TÅ‚ustosć +pdfjs-editor-free-highlight-thickness-title = + .title = TÅ‚ustosć zmÄ›niÅ›, gaž se zapiski wuzwiguju, kótarež tekst njejsu +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Tekstowy editor + .default-content = ZachopÅ›o pisaÅ› … +pdfjs-free-text = + .aria-label = Tekstowy editor +pdfjs-free-text-default-content = ZachopÅ›o pisaś… +pdfjs-ink = + .aria-label = KresleÅ„ski editor +pdfjs-ink-canvas = + .aria-label = Wobraz napórany wót wužywarja + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alternatiwny tekst +pdfjs-editor-alt-text-edit-button = + .aria-label = Alternatiwny tekst wobźěłaÅ› +pdfjs-editor-alt-text-edit-button-label = Alternatiwny tekst wobźěłaÅ› +pdfjs-editor-alt-text-dialog-label = Nastajenje wubraÅ› +pdfjs-editor-alt-text-dialog-description = Alternatiwny tekst pomaga, gaž luźe njamógu wobraz wiźeÅ› abo gaž se wobraz njezacytajo. +pdfjs-editor-alt-text-add-description-label = Wopisanje pÅ›idaÅ› +pdfjs-editor-alt-text-add-description-description = Pišćo 1 sadu abo 2 saźe, kótarejž temu, nastajenje abo akcije wopisujotej. +pdfjs-editor-alt-text-mark-decorative-label = Ako dekoratiwny markÄ›rowaÅ› +pdfjs-editor-alt-text-mark-decorative-description = To se za pyÅ¡njece wobraze wužywa, na pÅ›ikÅ‚ad ramiki abo wódowe znamjenja. +pdfjs-editor-alt-text-cancel-button = PÅ›etergnuÅ› +pdfjs-editor-alt-text-save-button = SkÅ‚adowaÅ› +pdfjs-editor-alt-text-decorative-tooltip = Ako dekoratiwny markÄ›rowany +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Na pÅ›ikÅ‚ad, „MÅ‚ody muski za blidom sejźi, aby jěź jÄ›dł“ +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alternatiwny tekst + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Górjejce nalÄ›wo – wjelikosć zmÄ›niÅ› +pdfjs-editor-resizer-label-top-middle = Górjejce wesrjejź – wjelikosć zmÄ›niÅ› +pdfjs-editor-resizer-label-top-right = Górjejce napÅ¡awo – wjelikosć zmÄ›niÅ› +pdfjs-editor-resizer-label-middle-right = Wesrjejź napÅ¡awo – wjelikosć zmÄ›niÅ› +pdfjs-editor-resizer-label-bottom-right = DoÅ‚ojce napÅ¡awo – wjelikosć zmÄ›niÅ› +pdfjs-editor-resizer-label-bottom-middle = DoÅ‚ojce wesrjejź – wjelikosć zmÄ›niÅ› +pdfjs-editor-resizer-label-bottom-left = DoÅ‚ojce nalÄ›wo – wjelikosć zmÄ›niÅ› +pdfjs-editor-resizer-label-middle-left = Wesrjejź nalÄ›wo – wjelikosć zmÄ›niÅ› +pdfjs-editor-resizer-top-left = + .aria-label = Górjejce nalÄ›wo – wjelikosć zmÄ›niÅ› +pdfjs-editor-resizer-top-middle = + .aria-label = Górjejce wesrjejź – wjelikosć zmÄ›niÅ› +pdfjs-editor-resizer-top-right = + .aria-label = Górjejce napÅ¡awo – wjelikosć zmÄ›niÅ› +pdfjs-editor-resizer-middle-right = + .aria-label = Wesrjejź napÅ¡awo – wjelikosć zmÄ›niÅ› +pdfjs-editor-resizer-bottom-right = + .aria-label = DoÅ‚ojce napÅ¡awo – wjelikosć zmÄ›niÅ› +pdfjs-editor-resizer-bottom-middle = + .aria-label = DoÅ‚ojce wesrjejź – wjelikosć zmÄ›niÅ› +pdfjs-editor-resizer-bottom-left = + .aria-label = DoÅ‚ojce nalÄ›wo – wjelikosć zmÄ›niÅ› +pdfjs-editor-resizer-middle-left = + .aria-label = Wesrjejź nalÄ›wo – wjelikosć zmÄ›niÅ› + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Barwa wuzwignjenja +pdfjs-editor-colorpicker-button = + .title = Barwu zmÄ›niÅ› +pdfjs-editor-colorpicker-dropdown = + .aria-label = WubÄ›rk barwow +pdfjs-editor-colorpicker-yellow = + .title = ŽoÅ‚ty +pdfjs-editor-colorpicker-green = + .title = Zeleny +pdfjs-editor-colorpicker-blue = + .title = Módry +pdfjs-editor-colorpicker-pink = + .title = Pink +pdfjs-editor-colorpicker-red = + .title = Cerwjeny + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = WÅ¡ykne pokazaÅ› +pdfjs-editor-highlight-show-all-button = + .title = WÅ¡ykne pokazaÅ› + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Alternatiwny tekst wobźěłaÅ› (wobrazowe wopisanje) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Alternatiwny tekst pÅ›idaÅ› (wobrazowe wopisanje) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Pišćo how swójo wopisanje… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Krotke wopisanje za luźe, kótarež njamóžoÅ›o wobraz wiźeÅ› abo gaž se wobraz njezacytajo. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = ToÅ› ten alternatiwny tekst jo se awtomatiski napóraÅ‚ a jo snaź njedokradny. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = DalÅ¡ne informacije +pdfjs-editor-new-alt-text-create-automatically-button-label = Alternatiwny tekst awtomatiski napóraÅ› +pdfjs-editor-new-alt-text-not-now-button = Nic nÄ›nto +pdfjs-editor-new-alt-text-error-title = Alternatiwny tekst njedajo se awtomatiski napóraÅ› +pdfjs-editor-new-alt-text-error-description = PÅ¡osym pišćo swój alternatiwny tekst abo wopytajÅ›o pózdźej hyšći raz. +pdfjs-editor-new-alt-text-error-close-button = ZacyniÅ› +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Model KI za alternatiwny tekst se ześěgujo ({ $downloadedSize } z { $totalSize } MB) + .aria-valuetext = Model KI za alternatiwny tekst se ześěgujo ({ $downloadedSize } z { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alternatiwny tekst jo se pÅ›idaÅ‚ +pdfjs-editor-new-alt-text-added-button-label = Alternatiwny tekst jo se pÅ›idaÅ‚ +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Alternatiwny tekst felujo +pdfjs-editor-new-alt-text-missing-button-label = Alternatiwny tekst felujo +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Alternatiwny tekst pÅ›eglÄ›dowaÅ› +pdfjs-editor-new-alt-text-to-review-button-label = Alternatiwny tekst pÅ›eglÄ›dowaÅ› +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Awtomatiski napórany: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Nastajenja alternatiwnego wobrazowego teksta +pdfjs-image-alt-text-settings-button-label = Nastajenja alternatiwnego wobrazowego teksta +pdfjs-editor-alt-text-settings-dialog-label = Nastajenja alternatiwnego wobrazowego teksta +pdfjs-editor-alt-text-settings-automatic-title = Awtomatiski alternatiwny tekst +pdfjs-editor-alt-text-settings-create-model-button-label = Alternatiwny tekst awtomatiski napóraÅ› +pdfjs-editor-alt-text-settings-create-model-description = Naraźujo wopisanja, aby pomagaÅ‚ ludam, kótarež njamóžoÅ›o wobraz wiźeÅ› abo gaž se wobraz njezacytajo. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model KI alternatiwnego teksta ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Běžy lokalnje na waÅ¡om rěźe, aby waÅ¡e daty priwatne wóstali. Za awtomatiski alternatiwny tekst trjebny. +pdfjs-editor-alt-text-settings-delete-model-button = LaÅ¡owaÅ› +pdfjs-editor-alt-text-settings-download-model-button = ZeśěgnuÅ› +pdfjs-editor-alt-text-settings-downloading-model-button = Ześěgujo se… +pdfjs-editor-alt-text-settings-editor-title = Editor za alternatiwny tekst +pdfjs-editor-alt-text-settings-show-dialog-button-label = Editor alternatiwnego teksta ned pokazaÅ›, gaž se wobraz pÅ›idawa +pdfjs-editor-alt-text-settings-show-dialog-description = Pomaga, wam wÅ¡ym swójim wobrazam alternatiwny tekst pÅ›idaÅ›. +pdfjs-editor-alt-text-settings-close-button = ZacyniÅ› + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Wótwónoźone wuzwignuÅ› +pdfjs-editor-undo-bar-message-freetext = Tekst jo se wótwónoźeÅ‚ +pdfjs-editor-undo-bar-message-ink = Kreslanka jo se wótwónoźeÅ‚a +pdfjs-editor-undo-bar-message-stamp = Wobraz jo se wótwónoźeÅ‚ +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } pÅ›ipisk jo se wótwónoźeÅ‚ + [two] { $count } pÅ›ipiska stej se wótwónoźeÅ‚ej + [few] { $count } pÅ›ipiski su se wótwónoźeli + *[other] { $count } pÅ›ipiskow jo se wótwónoźeÅ‚o + } +pdfjs-editor-undo-bar-undo-button = + .title = AnulÄ›rowaÅ› +pdfjs-editor-undo-bar-undo-button-label = AnulÄ›rowaÅ› +pdfjs-editor-undo-bar-close-button = + .title = ZacyniÅ› +pdfjs-editor-undo-bar-close-button-label = ZacyniÅ› diff --git a/public/assets/pdfjs/locale/el/viewer.ftl b/public/assets/pdfjs/locale/el/viewer.ftl new file mode 100755 index 0000000..5a04bd8 --- /dev/null +++ b/public/assets/pdfjs/locale/el/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = ΠÏοηγοÏμενη σελίδα +pdfjs-previous-button-label = ΠÏοηγοÏμενη +pdfjs-next-button = + .title = Επόμενη σελίδα +pdfjs-next-button-label = Επόμενη +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Σελίδα +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = από { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } από { $pagesCount }) +pdfjs-zoom-out-button = + .title = ΣμίκÏυνση +pdfjs-zoom-out-button-label = ΣμίκÏυνση +pdfjs-zoom-in-button = + .title = Μεγέθυνση +pdfjs-zoom-in-button-label = Μεγέθυνση +pdfjs-zoom-select = + .title = Ζουμ +pdfjs-presentation-mode-button = + .title = Εναλλαγή σε λειτουÏγία παÏουσίασης +pdfjs-presentation-mode-button-label = ΛειτουÏγία παÏουσίασης +pdfjs-open-file-button = + .title = Άνοιγμα αÏχείου +pdfjs-open-file-button-label = Άνοιγμα +pdfjs-print-button = + .title = ΕκτÏπωση +pdfjs-print-button-label = ΕκτÏπωση +pdfjs-save-button = + .title = Αποθήκευση +pdfjs-save-button-label = Αποθήκευση +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Λήψη +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Λήψη +pdfjs-bookmark-button = + .title = ΤÏέχουσα σελίδα (ΠÏοβολή URL από Ï„Ïέχουσα σελίδα) +pdfjs-bookmark-button-label = ΤÏέχουσα σελίδα + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = ΕÏγαλεία +pdfjs-tools-button-label = ΕÏγαλεία +pdfjs-first-page-button = + .title = Μετάβαση στην Ï€Ïώτη σελίδα +pdfjs-first-page-button-label = Μετάβαση στην Ï€Ïώτη σελίδα +pdfjs-last-page-button = + .title = Μετάβαση στην τελευταία σελίδα +pdfjs-last-page-button-label = Μετάβαση στην τελευταία σελίδα +pdfjs-page-rotate-cw-button = + .title = ΔεξιόστÏοφη πεÏιστÏοφή +pdfjs-page-rotate-cw-button-label = ΔεξιόστÏοφη πεÏιστÏοφή +pdfjs-page-rotate-ccw-button = + .title = ΑÏιστεÏόστÏοφη πεÏιστÏοφή +pdfjs-page-rotate-ccw-button-label = ΑÏιστεÏόστÏοφη πεÏιστÏοφή +pdfjs-cursor-text-select-tool-button = + .title = ΕνεÏγοποίηση εÏγαλείου επιλογής κειμένου +pdfjs-cursor-text-select-tool-button-label = ΕÏγαλείο επιλογής κειμένου +pdfjs-cursor-hand-tool-button = + .title = ΕνεÏγοποίηση εÏγαλείου χεÏÎ¹Î¿Ï +pdfjs-cursor-hand-tool-button-label = ΕÏγαλείο χεÏÎ¹Î¿Ï +pdfjs-scroll-page-button = + .title = ΧÏήση κÏλισης σελίδας +pdfjs-scroll-page-button-label = ΚÏλιση σελίδας +pdfjs-scroll-vertical-button = + .title = ΧÏήση κάθετης κÏλισης +pdfjs-scroll-vertical-button-label = Κάθετη κÏλιση +pdfjs-scroll-horizontal-button = + .title = ΧÏήση οÏιζόντιας κÏλισης +pdfjs-scroll-horizontal-button-label = ΟÏιζόντια κÏλιση +pdfjs-scroll-wrapped-button = + .title = ΧÏήση κυκλικής κÏλισης +pdfjs-scroll-wrapped-button-label = Κυκλική κÏλιση +pdfjs-spread-none-button = + .title = Îα μη γίνει σÏνδεση επεκτάσεων σελίδων +pdfjs-spread-none-button-label = ΧωÏίς επεκτάσεις +pdfjs-spread-odd-button = + .title = ΣÏνδεση επεκτάσεων σελίδων ξεκινώντας από τις μονές σελίδες +pdfjs-spread-odd-button-label = Μονές επεκτάσεις +pdfjs-spread-even-button = + .title = ΣÏνδεση επεκτάσεων σελίδων ξεκινώντας από τις ζυγές σελίδες +pdfjs-spread-even-button-label = Ζυγές επεκτάσεις + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Ιδιότητες εγγÏάφου… +pdfjs-document-properties-button-label = Ιδιότητες εγγÏάφου… +pdfjs-document-properties-file-name = Όνομα αÏχείου: +pdfjs-document-properties-file-size = Μέγεθος αÏχείου: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Τίτλος: +pdfjs-document-properties-author = ΣυγγÏαφέας: +pdfjs-document-properties-subject = Θέμα: +pdfjs-document-properties-keywords = Λέξεις-κλειδιά: +pdfjs-document-properties-creation-date = ΗμεÏομηνία δημιουÏγίας: +pdfjs-document-properties-modification-date = ΗμεÏομηνία Ï„Ïοποποίησης: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = ΔημιουÏγός: +pdfjs-document-properties-producer = ΠαÏαγωγός PDF: +pdfjs-document-properties-version = Έκδοση PDF: +pdfjs-document-properties-page-count = ΑÏιθμός σελίδων: +pdfjs-document-properties-page-size = Μέγεθος σελίδας: +pdfjs-document-properties-page-size-unit-inches = ίντσες +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = κατακόÏυφα +pdfjs-document-properties-page-size-orientation-landscape = οÏιζόντια +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Επιστολή +pdfjs-document-properties-page-size-name-legal = ΤÏπου Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Ταχεία Ï€Ïοβολή ιστοÏ: +pdfjs-document-properties-linearized-yes = Îαι +pdfjs-document-properties-linearized-no = Όχι +pdfjs-document-properties-close-button = Κλείσιμο + +## Print + +pdfjs-print-progress-message = ΠÏοετοιμασία του εγγÏάφου για εκτÏπωση… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = ΑκÏÏωση +pdfjs-printing-not-supported = ΠÏοειδοποίηση: Η εκτÏπωση δεν υποστηÏίζεται πλήÏως από το Ï€ÏόγÏαμμα πεÏιήγησης. +pdfjs-printing-not-ready = ΠÏοειδοποίηση: Το PDF δεν φοÏτώθηκε πλήÏως για εκτÏπωση. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = (Απ)ενεÏγοποίηση πλαϊνής γÏαμμής +pdfjs-toggle-sidebar-notification-button = + .title = (Απ)ενεÏγοποίηση πλαϊνής γÏαμμής (το έγγÏαφο πεÏιέχει πεÏίγÏαμμα/συνημμένα/επίπεδα) +pdfjs-toggle-sidebar-button-label = (Απ)ενεÏγοποίηση πλαϊνής γÏαμμής +pdfjs-document-outline-button = + .title = Εμφάνιση διάÏθÏωσης εγγÏάφου (διπλό κλικ για ανάπτυξη/σÏμπτυξη όλων των στοιχείων) +pdfjs-document-outline-button-label = ΔιάÏθÏωση εγγÏάφου +pdfjs-attachments-button = + .title = Εμφάνιση συνημμένων +pdfjs-attachments-button-label = Συνημμένα +pdfjs-layers-button = + .title = Εμφάνιση επιπέδων (διπλό κλικ για επαναφοÏά όλων των επιπέδων στην Ï€Ïοεπιλεγμένη κατάσταση) +pdfjs-layers-button-label = Επίπεδα +pdfjs-thumbs-button = + .title = Εμφάνιση μικÏογÏαφιών +pdfjs-thumbs-button-label = ΜικÏογÏαφίες +pdfjs-current-outline-item-button = + .title = ΕÏÏεση Ï„Ïέχοντος στοιχείου διάÏθÏωσης +pdfjs-current-outline-item-button-label = ΤÏέχον στοιχείο διάÏθÏωσης +pdfjs-findbar-button = + .title = ΕÏÏεση στο έγγÏαφο +pdfjs-findbar-button-label = ΕÏÏεση +pdfjs-additional-layers = ΕπιπÏόσθετα επίπεδα + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Σελίδα { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = ΜικÏογÏαφία σελίδας { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = ΕÏÏεση + .placeholder = ΕÏÏεση στο έγγÏαφο… +pdfjs-find-previous-button = + .title = ΕÏÏεση της Ï€ÏοηγοÏμενης εμφάνισης της φÏάσης +pdfjs-find-previous-button-label = ΠÏοηγοÏμενο +pdfjs-find-next-button = + .title = ΕÏÏεση της επόμενης εμφάνισης της φÏάσης +pdfjs-find-next-button-label = Επόμενο +pdfjs-find-highlight-checkbox = Επισήμανση όλων +pdfjs-find-match-case-checkbox-label = Συμφωνία πεζών/κεφαλαίων +pdfjs-find-match-diacritics-checkbox-label = Αντιστοίχιση διακÏιτικών +pdfjs-find-entire-word-checkbox-label = ΟλόκληÏες λέξεις +pdfjs-find-reached-top = Φτάσατε στην αÏχή του εγγÏάφου, συνέχεια από το τέλος +pdfjs-find-reached-bottom = Φτάσατε στο τέλος του εγγÏάφου, συνέχεια από την αÏχή +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } από { $total } αντιστοιχία + *[other] { $current } από { $total } αντιστοιχίες + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] ΠεÏισσότεÏες από { $limit } αντιστοιχία + *[other] ΠεÏισσότεÏες από { $limit } αντιστοιχίες + } +pdfjs-find-not-found = Η φÏάση δεν βÏέθηκε + +## Predefined zoom values + +pdfjs-page-scale-width = Πλάτος σελίδας +pdfjs-page-scale-fit = Μέγεθος σελίδας +pdfjs-page-scale-auto = Αυτόματο ζουμ +pdfjs-page-scale-actual = ΠÏαγματικό μέγεθος +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Σελίδα { $page } + +## Loading indicator messages + +pdfjs-loading-error = ΠÏοέκυψε σφάλμα κατά τη φόÏτωση του PDF. +pdfjs-invalid-file-error = Μη έγκυÏο ή κατεστÏαμμένο αÏχείο PDF. +pdfjs-missing-file-error = Λείπει αÏχείο PDF. +pdfjs-unexpected-response-error = Μη αναμενόμενη απόκÏιση από το διακομιστή. +pdfjs-rendering-error = ΠÏοέκυψε σφάλμα κατά την εμφάνιση της σελίδας. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Σχόλιο «{ $type }»] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Εισαγάγετε τον κωδικό Ï€Ïόσβασης για να ανοίξετε αυτό το αÏχείο PDF. +pdfjs-password-invalid = Μη έγκυÏος κωδικός Ï€Ïόσβασης. ΠαÏακαλώ δοκιμάστε ξανά. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = ΑκÏÏωση +pdfjs-web-fonts-disabled = Οι γÏαμματοσειÏές Î¹ÏƒÏ„Î¿Ï ÎµÎ¯Î½Î±Î¹ ανενεÏγές: δεν είναι δυνατή η χÏήση των ενσωματωμένων γÏαμματοσειÏών PDF. + +## Editing + +pdfjs-editor-free-text-button = + .title = Κείμενο +pdfjs-editor-free-text-button-label = Κείμενο +pdfjs-editor-ink-button = + .title = Σχέδιο +pdfjs-editor-ink-button-label = Σχέδιο +pdfjs-editor-stamp-button = + .title = ΠÏοσθήκη ή επεξεÏγασία εικόνων +pdfjs-editor-stamp-button-label = ΠÏοσθήκη ή επεξεÏγασία εικόνων +pdfjs-editor-highlight-button = + .title = Επισήμανση +pdfjs-editor-highlight-button-label = Επισήμανση +pdfjs-highlight-floating-button1 = + .title = Επισήμανση + .aria-label = Επισήμανση +pdfjs-highlight-floating-button-label = Επισήμανση + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = ΑφαίÏεση σχεδίου +pdfjs-editor-remove-freetext-button = + .title = ΑφαίÏεση κειμένου +pdfjs-editor-remove-stamp-button = + .title = ΑφαίÏεση εικόνας +pdfjs-editor-remove-highlight-button = + .title = ΑφαίÏεση επισήμανσης + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = ΧÏώμα +pdfjs-editor-free-text-size-input = Μέγεθος +pdfjs-editor-ink-color-input = ΧÏώμα +pdfjs-editor-ink-thickness-input = Πάχος +pdfjs-editor-ink-opacity-input = Αδιαφάνεια +pdfjs-editor-stamp-add-image-button = + .title = ΠÏοσθήκη εικόνας +pdfjs-editor-stamp-add-image-button-label = ΠÏοσθήκη εικόνας +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Πάχος +pdfjs-editor-free-highlight-thickness-title = + .title = Αλλαγή πάχους κατά την επισήμανση στοιχείων εκτός κειμένου +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = ΕπεξεÏγασία κειμένου + .default-content = Ξεκινήστε να πληκτÏολογείτε… +pdfjs-free-text = + .aria-label = ΕπεξεÏγασία κειμένου +pdfjs-free-text-default-content = Ξεκινήστε να πληκτÏολογείτε… +pdfjs-ink = + .aria-label = ΕπεξεÏγασία σχεδίων +pdfjs-ink-canvas = + .aria-label = Εικόνα από τον χÏήστη + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Εναλλακτικό κείμενο +pdfjs-editor-alt-text-edit-button = + .aria-label = ΕπεξεÏγασία ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… +pdfjs-editor-alt-text-edit-button-label = ΕπεξεÏγασία ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… +pdfjs-editor-alt-text-dialog-label = Διαλέξτε μια επιλογή +pdfjs-editor-alt-text-dialog-description = Το εναλλακτικό κείμενο είναι χÏήσιμο όταν οι άνθÏωποι δεν μποÏοÏν να δουν την εικόνα ή όταν αυτή δεν φοÏτώνεται. +pdfjs-editor-alt-text-add-description-label = ΠÏοσθήκη πεÏιγÏαφής +pdfjs-editor-alt-text-add-description-description = ΣτοχεÏστε σε μία ή δÏο Ï€Ïοτάσεις που πεÏιγÏάφουν το θέμα, τη ÏÏθμιση ή τις ενέÏγειες. +pdfjs-editor-alt-text-mark-decorative-label = Επισήμανση ως διακοσμητικό +pdfjs-editor-alt-text-mark-decorative-description = ΧÏησιμοποιείται για διακοσμητικές εικόνες, όπως πεÏιγÏάμματα ή υδατογÏαφήματα. +pdfjs-editor-alt-text-cancel-button = ΑκÏÏωση +pdfjs-editor-alt-text-save-button = Αποθήκευση +pdfjs-editor-alt-text-decorative-tooltip = Επισημασμένο ως διακοσμητικό +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Για παÏάδειγμα, «Ένας νεαÏός άνδÏας κάθεται σε ένα Ï„Ïαπέζι για να φάει ένα γεÏμα» +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Εναλλακτικό κείμενο + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Επάνω αÏιστεÏή γωνία — αλλαγή μεγέθους +pdfjs-editor-resizer-label-top-middle = Μέσο επάνω πλευÏάς — αλλαγή μεγέθους +pdfjs-editor-resizer-label-top-right = Επάνω δεξιά γωνία — αλλαγή μεγέθους +pdfjs-editor-resizer-label-middle-right = Μέσο δεξιάς πλευÏάς — αλλαγή μεγέθους +pdfjs-editor-resizer-label-bottom-right = Κάτω δεξιά γωνία — αλλαγή μεγέθους +pdfjs-editor-resizer-label-bottom-middle = Μέσο κάτω πλευÏάς — αλλαγή μεγέθους +pdfjs-editor-resizer-label-bottom-left = Κάτω αÏιστεÏή γωνία — αλλαγή μεγέθους +pdfjs-editor-resizer-label-middle-left = Μέσο αÏιστεÏής πλευÏάς — αλλαγή μεγέθους +pdfjs-editor-resizer-top-left = + .aria-label = Επάνω αÏιστεÏή γωνία — αλλαγή μεγέθους +pdfjs-editor-resizer-top-middle = + .aria-label = Μέσο επάνω πλευÏάς — αλλαγή μεγέθους +pdfjs-editor-resizer-top-right = + .aria-label = Επάνω δεξιά γωνία — αλλαγή μεγέθους +pdfjs-editor-resizer-middle-right = + .aria-label = Μέσο δεξιάς πλευÏάς — αλλαγή μεγέθους +pdfjs-editor-resizer-bottom-right = + .aria-label = Κάτω δεξιά γωνία — αλλαγή μεγέθους +pdfjs-editor-resizer-bottom-middle = + .aria-label = Μέσο κάτω πλευÏάς — αλλαγή μεγέθους +pdfjs-editor-resizer-bottom-left = + .aria-label = Κάτω αÏιστεÏή γωνία — αλλαγή μεγέθους +pdfjs-editor-resizer-middle-left = + .aria-label = Μέσο αÏιστεÏής πλευÏάς — αλλαγή μεγέθους + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = ΧÏώμα επισήμανσης +pdfjs-editor-colorpicker-button = + .title = Αλλαγή χÏώματος +pdfjs-editor-colorpicker-dropdown = + .aria-label = Επιλογές χÏωμάτων +pdfjs-editor-colorpicker-yellow = + .title = ΚίτÏινο +pdfjs-editor-colorpicker-green = + .title = ΠÏάσινο +pdfjs-editor-colorpicker-blue = + .title = Μπλε +pdfjs-editor-colorpicker-pink = + .title = Ροζ +pdfjs-editor-colorpicker-red = + .title = Κόκκινο + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Εμφάνιση όλων +pdfjs-editor-highlight-show-all-button = + .title = Εμφάνιση όλων + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = ΕπεξεÏγασία ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… (πεÏιγÏαφή εικόνας) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = ΠÏοσθήκη ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… (πεÏιγÏαφή εικόνας) +pdfjs-editor-new-alt-text-textarea = + .placeholder = ΓÏάψτε την πεÏιγÏαφή σας εδώ… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = ΣÏντομη πεÏιγÏαφή για άτομα που δεν μποÏοÏν να δουν την εικόνα ή όταν η εικόνα δεν φοÏτώνεται. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Αυτό το εναλλακτικό κείμενο δημιουÏγήθηκε αυτόματα και ενδέχεται να είναι ανακÏιβές. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Μάθετε πεÏισσότεÏα +pdfjs-editor-new-alt-text-create-automatically-button-label = Αυτόματη δημιουÏγία ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… +pdfjs-editor-new-alt-text-not-now-button = Όχι τώÏα +pdfjs-editor-new-alt-text-error-title = Δεν ήταν δυνατή η αυτόματη δημιουÏγία ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… +pdfjs-editor-new-alt-text-error-description = ΓÏάψτε το δικό σας εναλλακτικό κείμενο ή δοκιμάστε ξανά αÏγότεÏα. +pdfjs-editor-new-alt-text-error-close-button = Κλείσιμο +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Λήψη μοντέλου AI ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… ({ $downloadedSize } από { $totalSize } MB) + .aria-valuetext = Λήψη μοντέλου AI ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… ({ $downloadedSize } από { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = ΠÏοστέθηκε εναλλακτικό κείμενο +pdfjs-editor-new-alt-text-added-button-label = ΠÏοστέθηκε εναλλακτικό κείμενο +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Απουσία ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… +pdfjs-editor-new-alt-text-missing-button-label = Απουσία ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Έλεγχος ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… +pdfjs-editor-new-alt-text-to-review-button-label = Έλεγχος ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Αυτόματη δημιουÏγία: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Ρυθμίσεις ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… εικόνας +pdfjs-image-alt-text-settings-button-label = Ρυθμίσεις ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… εικόνας +pdfjs-editor-alt-text-settings-dialog-label = Ρυθμίσεις ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… εικόνας +pdfjs-editor-alt-text-settings-automatic-title = Αυτόματο εναλλακτικό κείμενο +pdfjs-editor-alt-text-settings-create-model-button-label = Αυτόματη δημιουÏγία ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… +pdfjs-editor-alt-text-settings-create-model-description = ΠÏοτείνει πεÏιγÏαφές για άτομα που δεν μποÏοÏν να δουν την εικόνα ή όταν η εικόνα δεν φοÏτώνεται. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Μοντέλο AI ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Εκτελείται τοπικά στη συσκευή σας, ώστε τα δεδομένα σας να παÏαμένουν ιδιωτικά. Απαιτείται για τη δημιουÏγία του αυτόματου ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï…. +pdfjs-editor-alt-text-settings-delete-model-button = ΔιαγÏαφή +pdfjs-editor-alt-text-settings-download-model-button = Λήψη +pdfjs-editor-alt-text-settings-downloading-model-button = Λήψη… +pdfjs-editor-alt-text-settings-editor-title = ΕπεξεÏγασία ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… +pdfjs-editor-alt-text-settings-show-dialog-button-label = Άμεση εμφάνιση της επεξεÏγασίας ÎµÎ½Î±Î»Î»Î±ÎºÏ„Î¹ÎºÎ¿Ï ÎºÎµÎ¹Î¼Î­Î½Î¿Ï… κατά την Ï€Ïοσθήκη εικόνας +pdfjs-editor-alt-text-settings-show-dialog-description = Σας βοηθά να βεβαιωθείτε ότι όλες οι εικόνες σας έχουν εναλλακτικό κείμενο. +pdfjs-editor-alt-text-settings-close-button = Κλείσιμο + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Η επισήμανση αφαιÏέθηκε +pdfjs-editor-undo-bar-message-freetext = Το κείμενο αφαιÏέθηκε +pdfjs-editor-undo-bar-message-ink = Το σχέδιο αφαιÏέθηκε +pdfjs-editor-undo-bar-message-stamp = Η εικόνα αφαιÏέθηκε +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] ΑφαιÏέθηκε { $count } σχολιασμός + *[other] ΑφαιÏέθηκαν { $count } σχολιασμοί + } +pdfjs-editor-undo-bar-undo-button = + .title = ΑναίÏεση +pdfjs-editor-undo-bar-undo-button-label = ΑναίÏεση +pdfjs-editor-undo-bar-close-button = + .title = Κλείσιμο +pdfjs-editor-undo-bar-close-button-label = Κλείσιμο diff --git a/public/assets/pdfjs/locale/en-CA/viewer.ftl b/public/assets/pdfjs/locale/en-CA/viewer.ftl new file mode 100755 index 0000000..346e6e8 --- /dev/null +++ b/public/assets/pdfjs/locale/en-CA/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Previous Page +pdfjs-previous-button-label = Previous +pdfjs-next-button = + .title = Next Page +pdfjs-next-button-label = Next +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Page +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = of { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount }) +pdfjs-zoom-out-button = + .title = Zoom Out +pdfjs-zoom-out-button-label = Zoom Out +pdfjs-zoom-in-button = + .title = Zoom In +pdfjs-zoom-in-button-label = Zoom In +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Switch to Presentation Mode +pdfjs-presentation-mode-button-label = Presentation Mode +pdfjs-open-file-button = + .title = Open File +pdfjs-open-file-button-label = Open +pdfjs-print-button = + .title = Print +pdfjs-print-button-label = Print +pdfjs-save-button = + .title = Save +pdfjs-save-button-label = Save +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Download +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Download +pdfjs-bookmark-button = + .title = Current Page (View URL from Current Page) +pdfjs-bookmark-button-label = Current Page + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Tools +pdfjs-tools-button-label = Tools +pdfjs-first-page-button = + .title = Go to First Page +pdfjs-first-page-button-label = Go to First Page +pdfjs-last-page-button = + .title = Go to Last Page +pdfjs-last-page-button-label = Go to Last Page +pdfjs-page-rotate-cw-button = + .title = Rotate Clockwise +pdfjs-page-rotate-cw-button-label = Rotate Clockwise +pdfjs-page-rotate-ccw-button = + .title = Rotate Counterclockwise +pdfjs-page-rotate-ccw-button-label = Rotate Counterclockwise +pdfjs-cursor-text-select-tool-button = + .title = Enable Text Selection Tool +pdfjs-cursor-text-select-tool-button-label = Text Selection Tool +pdfjs-cursor-hand-tool-button = + .title = Enable Hand Tool +pdfjs-cursor-hand-tool-button-label = Hand Tool +pdfjs-scroll-page-button = + .title = Use Page Scrolling +pdfjs-scroll-page-button-label = Page Scrolling +pdfjs-scroll-vertical-button = + .title = Use Vertical Scrolling +pdfjs-scroll-vertical-button-label = Vertical Scrolling +pdfjs-scroll-horizontal-button = + .title = Use Horizontal Scrolling +pdfjs-scroll-horizontal-button-label = Horizontal Scrolling +pdfjs-scroll-wrapped-button = + .title = Use Wrapped Scrolling +pdfjs-scroll-wrapped-button-label = Wrapped Scrolling +pdfjs-spread-none-button = + .title = Do not join page spreads +pdfjs-spread-none-button-label = No Spreads +pdfjs-spread-odd-button = + .title = Join page spreads starting with odd-numbered pages +pdfjs-spread-odd-button-label = Odd Spreads +pdfjs-spread-even-button = + .title = Join page spreads starting with even-numbered pages +pdfjs-spread-even-button-label = Even Spreads + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Document Properties… +pdfjs-document-properties-button-label = Document Properties… +pdfjs-document-properties-file-name = File name: +pdfjs-document-properties-file-size = File size: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } kB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Title: +pdfjs-document-properties-author = Author: +pdfjs-document-properties-subject = Subject: +pdfjs-document-properties-keywords = Keywords: +pdfjs-document-properties-creation-date = Creation Date: +pdfjs-document-properties-modification-date = Modification Date: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Creator: +pdfjs-document-properties-producer = PDF Producer: +pdfjs-document-properties-version = PDF Version: +pdfjs-document-properties-page-count = Page Count: +pdfjs-document-properties-page-size = Page Size: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = portrait +pdfjs-document-properties-page-size-orientation-landscape = landscape +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Fast Web View: +pdfjs-document-properties-linearized-yes = Yes +pdfjs-document-properties-linearized-no = No +pdfjs-document-properties-close-button = Close + +## Print + +pdfjs-print-progress-message = Preparing document for printing… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Cancel +pdfjs-printing-not-supported = Warning: Printing is not fully supported by this browser. +pdfjs-printing-not-ready = Warning: The PDF is not fully loaded for printing. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Toggle Sidebar +pdfjs-toggle-sidebar-notification-button = + .title = Toggle Sidebar (document contains outline/attachments/layers) +pdfjs-toggle-sidebar-button-label = Toggle Sidebar +pdfjs-document-outline-button = + .title = Show Document Outline (double-click to expand/collapse all items) +pdfjs-document-outline-button-label = Document Outline +pdfjs-attachments-button = + .title = Show Attachments +pdfjs-attachments-button-label = Attachments +pdfjs-layers-button = + .title = Show Layers (double-click to reset all layers to the default state) +pdfjs-layers-button-label = Layers +pdfjs-thumbs-button = + .title = Show Thumbnails +pdfjs-thumbs-button-label = Thumbnails +pdfjs-current-outline-item-button = + .title = Find Current Outline Item +pdfjs-current-outline-item-button-label = Current Outline Item +pdfjs-findbar-button = + .title = Find in Document +pdfjs-findbar-button-label = Find +pdfjs-additional-layers = Additional Layers + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Page { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Thumbnail of Page { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Find + .placeholder = Find in document… +pdfjs-find-previous-button = + .title = Find the previous occurrence of the phrase +pdfjs-find-previous-button-label = Previous +pdfjs-find-next-button = + .title = Find the next occurrence of the phrase +pdfjs-find-next-button-label = Next +pdfjs-find-highlight-checkbox = Highlight All +pdfjs-find-match-case-checkbox-label = Match Case +pdfjs-find-match-diacritics-checkbox-label = Match Diacritics +pdfjs-find-entire-word-checkbox-label = Whole Words +pdfjs-find-reached-top = Reached top of document, continued from bottom +pdfjs-find-reached-bottom = Reached end of document, continued from top +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } of { $total } match + *[other] { $current } of { $total } matches + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] More than { $limit } match + *[other] More than { $limit } matches + } +pdfjs-find-not-found = Phrase not found + +## Predefined zoom values + +pdfjs-page-scale-width = Page Width +pdfjs-page-scale-fit = Page Fit +pdfjs-page-scale-auto = Automatic Zoom +pdfjs-page-scale-actual = Actual Size +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Page { $page } + +## Loading indicator messages + +pdfjs-loading-error = An error occurred while loading the PDF. +pdfjs-invalid-file-error = Invalid or corrupted PDF file. +pdfjs-missing-file-error = Missing PDF file. +pdfjs-unexpected-response-error = Unexpected server response. +pdfjs-rendering-error = An error occurred while rendering the page. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Annotation] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Enter the password to open this PDF file. +pdfjs-password-invalid = Invalid password. Please try again. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Cancel +pdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fonts. + +## Editing + +pdfjs-editor-free-text-button = + .title = Text +pdfjs-editor-free-text-button-label = Text +pdfjs-editor-ink-button = + .title = Draw +pdfjs-editor-ink-button-label = Draw +pdfjs-editor-stamp-button = + .title = Add or edit images +pdfjs-editor-stamp-button-label = Add or edit images +pdfjs-editor-highlight-button = + .title = Highlight +pdfjs-editor-highlight-button-label = Highlight +pdfjs-highlight-floating-button1 = + .title = Highlight + .aria-label = Highlight +pdfjs-highlight-floating-button-label = Highlight + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Remove drawing +pdfjs-editor-remove-freetext-button = + .title = Remove text +pdfjs-editor-remove-stamp-button = + .title = Remove image +pdfjs-editor-remove-highlight-button = + .title = Remove highlight + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Colour +pdfjs-editor-free-text-size-input = Size +pdfjs-editor-ink-color-input = Colour +pdfjs-editor-ink-thickness-input = Thickness +pdfjs-editor-ink-opacity-input = Opacity +pdfjs-editor-stamp-add-image-button = + .title = Add image +pdfjs-editor-stamp-add-image-button-label = Add image +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Thickness +pdfjs-editor-free-highlight-thickness-title = + .title = Change thickness when highlighting items other than text +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Text Editor + .default-content = Start typing… +pdfjs-free-text = + .aria-label = Text Editor +pdfjs-free-text-default-content = Start typing… +pdfjs-ink = + .aria-label = Draw Editor +pdfjs-ink-canvas = + .aria-label = User-created image + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alt text +pdfjs-editor-alt-text-edit-button = + .aria-label = Edit alt text +pdfjs-editor-alt-text-edit-button-label = Edit alt text +pdfjs-editor-alt-text-dialog-label = Choose an option +pdfjs-editor-alt-text-dialog-description = Alt text (alternative text) helps when people can’t see the image or when it doesn’t load. +pdfjs-editor-alt-text-add-description-label = Add a description +pdfjs-editor-alt-text-add-description-description = Aim for 1-2 sentences that describe the subject, setting, or actions. +pdfjs-editor-alt-text-mark-decorative-label = Mark as decorative +pdfjs-editor-alt-text-mark-decorative-description = This is used for ornamental images, like borders or watermarks. +pdfjs-editor-alt-text-cancel-button = Cancel +pdfjs-editor-alt-text-save-button = Save +pdfjs-editor-alt-text-decorative-tooltip = Marked as decorative +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = For example, “A young man sits down at a table to eat a meal†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alt text + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Top left corner — resize +pdfjs-editor-resizer-label-top-middle = Top middle — resize +pdfjs-editor-resizer-label-top-right = Top right corner — resize +pdfjs-editor-resizer-label-middle-right = Middle right — resize +pdfjs-editor-resizer-label-bottom-right = Bottom right corner — resize +pdfjs-editor-resizer-label-bottom-middle = Bottom middle — resize +pdfjs-editor-resizer-label-bottom-left = Bottom left corner — resize +pdfjs-editor-resizer-label-middle-left = Middle left — resize +pdfjs-editor-resizer-top-left = + .aria-label = Top left corner — resize +pdfjs-editor-resizer-top-middle = + .aria-label = Top middle — resize +pdfjs-editor-resizer-top-right = + .aria-label = Top right corner — resize +pdfjs-editor-resizer-middle-right = + .aria-label = Middle right — resize +pdfjs-editor-resizer-bottom-right = + .aria-label = Bottom right corner — resize +pdfjs-editor-resizer-bottom-middle = + .aria-label = Bottom middle — resize +pdfjs-editor-resizer-bottom-left = + .aria-label = Bottom left corner — resize +pdfjs-editor-resizer-middle-left = + .aria-label = Middle left — resize + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Highlight colour +pdfjs-editor-colorpicker-button = + .title = Change colour +pdfjs-editor-colorpicker-dropdown = + .aria-label = Colour choices +pdfjs-editor-colorpicker-yellow = + .title = Yellow +pdfjs-editor-colorpicker-green = + .title = Green +pdfjs-editor-colorpicker-blue = + .title = Blue +pdfjs-editor-colorpicker-pink = + .title = Pink +pdfjs-editor-colorpicker-red = + .title = Red + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Show all +pdfjs-editor-highlight-show-all-button = + .title = Show all + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Edit alt text (image description) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Add alt text (image description) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Write your description here… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Short description for people who can’t see the image or when the image doesn’t load. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = This alt text was created automatically and may be inaccurate. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Learn more +pdfjs-editor-new-alt-text-create-automatically-button-label = Create alt text automatically +pdfjs-editor-new-alt-text-not-now-button = Not now +pdfjs-editor-new-alt-text-error-title = Couldn’t create alt text automatically +pdfjs-editor-new-alt-text-error-description = Please write your own alt text or try again later. +pdfjs-editor-new-alt-text-error-close-button = Close +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB) + .aria-valuetext = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alt text added +pdfjs-editor-new-alt-text-added-button-label = Alt text added +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Missing alt text +pdfjs-editor-new-alt-text-missing-button-label = Missing alt text +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Review alt text +pdfjs-editor-new-alt-text-to-review-button-label = Review alt text +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Created automatically: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Image alt text settings +pdfjs-image-alt-text-settings-button-label = Image alt text settings +pdfjs-editor-alt-text-settings-dialog-label = Image alt text settings +pdfjs-editor-alt-text-settings-automatic-title = Automatic alt text +pdfjs-editor-alt-text-settings-create-model-button-label = Create alt text automatically +pdfjs-editor-alt-text-settings-create-model-description = Suggests descriptions to help people who can’t see the image or when the image doesn’t load. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Alt text AI model ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Runs locally on your device so your data stays private. Required for automatic alt text. +pdfjs-editor-alt-text-settings-delete-model-button = Delete +pdfjs-editor-alt-text-settings-download-model-button = Download +pdfjs-editor-alt-text-settings-downloading-model-button = Downloading… +pdfjs-editor-alt-text-settings-editor-title = Alt text editor +pdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor right away when adding an image +pdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text. +pdfjs-editor-alt-text-settings-close-button = Close + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Highlight removed +pdfjs-editor-undo-bar-message-freetext = Text removed +pdfjs-editor-undo-bar-message-ink = Drawing removed +pdfjs-editor-undo-bar-message-stamp = Image removed +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } annotation removed + *[other] { $count } annotations removed + } +pdfjs-editor-undo-bar-undo-button = + .title = Undo +pdfjs-editor-undo-bar-undo-button-label = Undo +pdfjs-editor-undo-bar-close-button = + .title = Close +pdfjs-editor-undo-bar-close-button-label = Close diff --git a/public/assets/pdfjs/locale/en-GB/viewer.ftl b/public/assets/pdfjs/locale/en-GB/viewer.ftl new file mode 100755 index 0000000..4222f6f --- /dev/null +++ b/public/assets/pdfjs/locale/en-GB/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Previous Page +pdfjs-previous-button-label = Previous +pdfjs-next-button = + .title = Next Page +pdfjs-next-button-label = Next +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Page +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = of { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount }) +pdfjs-zoom-out-button = + .title = Zoom Out +pdfjs-zoom-out-button-label = Zoom Out +pdfjs-zoom-in-button = + .title = Zoom In +pdfjs-zoom-in-button-label = Zoom In +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Switch to Presentation Mode +pdfjs-presentation-mode-button-label = Presentation Mode +pdfjs-open-file-button = + .title = Open File +pdfjs-open-file-button-label = Open +pdfjs-print-button = + .title = Print +pdfjs-print-button-label = Print +pdfjs-save-button = + .title = Save +pdfjs-save-button-label = Save +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Download +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Download +pdfjs-bookmark-button = + .title = Current Page (View URL from Current Page) +pdfjs-bookmark-button-label = Current Page + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Tools +pdfjs-tools-button-label = Tools +pdfjs-first-page-button = + .title = Go to First Page +pdfjs-first-page-button-label = Go to First Page +pdfjs-last-page-button = + .title = Go to Last Page +pdfjs-last-page-button-label = Go to Last Page +pdfjs-page-rotate-cw-button = + .title = Rotate Clockwise +pdfjs-page-rotate-cw-button-label = Rotate Clockwise +pdfjs-page-rotate-ccw-button = + .title = Rotate Anti-Clockwise +pdfjs-page-rotate-ccw-button-label = Rotate Anti-Clockwise +pdfjs-cursor-text-select-tool-button = + .title = Enable Text Selection Tool +pdfjs-cursor-text-select-tool-button-label = Text Selection Tool +pdfjs-cursor-hand-tool-button = + .title = Enable Hand Tool +pdfjs-cursor-hand-tool-button-label = Hand Tool +pdfjs-scroll-page-button = + .title = Use Page Scrolling +pdfjs-scroll-page-button-label = Page Scrolling +pdfjs-scroll-vertical-button = + .title = Use Vertical Scrolling +pdfjs-scroll-vertical-button-label = Vertical Scrolling +pdfjs-scroll-horizontal-button = + .title = Use Horizontal Scrolling +pdfjs-scroll-horizontal-button-label = Horizontal Scrolling +pdfjs-scroll-wrapped-button = + .title = Use Wrapped Scrolling +pdfjs-scroll-wrapped-button-label = Wrapped Scrolling +pdfjs-spread-none-button = + .title = Do not join page spreads +pdfjs-spread-none-button-label = No Spreads +pdfjs-spread-odd-button = + .title = Join page spreads starting with odd-numbered pages +pdfjs-spread-odd-button-label = Odd Spreads +pdfjs-spread-even-button = + .title = Join page spreads starting with even-numbered pages +pdfjs-spread-even-button-label = Even Spreads + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Document Properties… +pdfjs-document-properties-button-label = Document Properties… +pdfjs-document-properties-file-name = File name: +pdfjs-document-properties-file-size = File size: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } kB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Title: +pdfjs-document-properties-author = Author: +pdfjs-document-properties-subject = Subject: +pdfjs-document-properties-keywords = Keywords: +pdfjs-document-properties-creation-date = Creation Date: +pdfjs-document-properties-modification-date = Modification Date: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Creator: +pdfjs-document-properties-producer = PDF Producer: +pdfjs-document-properties-version = PDF Version: +pdfjs-document-properties-page-count = Page Count: +pdfjs-document-properties-page-size = Page Size: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = portrait +pdfjs-document-properties-page-size-orientation-landscape = landscape +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Fast Web View: +pdfjs-document-properties-linearized-yes = Yes +pdfjs-document-properties-linearized-no = No +pdfjs-document-properties-close-button = Close + +## Print + +pdfjs-print-progress-message = Preparing document for printing… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Cancel +pdfjs-printing-not-supported = Warning: Printing is not fully supported by this browser. +pdfjs-printing-not-ready = Warning: The PDF is not fully loaded for printing. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Toggle Sidebar +pdfjs-toggle-sidebar-notification-button = + .title = Toggle Sidebar (document contains outline/attachments/layers) +pdfjs-toggle-sidebar-button-label = Toggle Sidebar +pdfjs-document-outline-button = + .title = Show Document Outline (double-click to expand/collapse all items) +pdfjs-document-outline-button-label = Document Outline +pdfjs-attachments-button = + .title = Show Attachments +pdfjs-attachments-button-label = Attachments +pdfjs-layers-button = + .title = Show Layers (double-click to reset all layers to the default state) +pdfjs-layers-button-label = Layers +pdfjs-thumbs-button = + .title = Show Thumbnails +pdfjs-thumbs-button-label = Thumbnails +pdfjs-current-outline-item-button = + .title = Find Current Outline Item +pdfjs-current-outline-item-button-label = Current Outline Item +pdfjs-findbar-button = + .title = Find in Document +pdfjs-findbar-button-label = Find +pdfjs-additional-layers = Additional Layers + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Page { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Thumbnail of Page { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Find + .placeholder = Find in document… +pdfjs-find-previous-button = + .title = Find the previous occurrence of the phrase +pdfjs-find-previous-button-label = Previous +pdfjs-find-next-button = + .title = Find the next occurrence of the phrase +pdfjs-find-next-button-label = Next +pdfjs-find-highlight-checkbox = Highlight All +pdfjs-find-match-case-checkbox-label = Match Case +pdfjs-find-match-diacritics-checkbox-label = Match Diacritics +pdfjs-find-entire-word-checkbox-label = Whole Words +pdfjs-find-reached-top = Reached top of document, continued from bottom +pdfjs-find-reached-bottom = Reached end of document, continued from top +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } of { $total } match + *[other] { $current } of { $total } matches + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] More than { $limit } match + *[other] More than { $limit } matches + } +pdfjs-find-not-found = Phrase not found + +## Predefined zoom values + +pdfjs-page-scale-width = Page Width +pdfjs-page-scale-fit = Page Fit +pdfjs-page-scale-auto = Automatic Zoom +pdfjs-page-scale-actual = Actual Size +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Page { $page } + +## Loading indicator messages + +pdfjs-loading-error = An error occurred while loading the PDF. +pdfjs-invalid-file-error = Invalid or corrupted PDF file. +pdfjs-missing-file-error = Missing PDF file. +pdfjs-unexpected-response-error = Unexpected server response. +pdfjs-rendering-error = An error occurred while rendering the page. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Annotation] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Enter the password to open this PDF file. +pdfjs-password-invalid = Invalid password. Please try again. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Cancel +pdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fonts. + +## Editing + +pdfjs-editor-free-text-button = + .title = Text +pdfjs-editor-free-text-button-label = Text +pdfjs-editor-ink-button = + .title = Draw +pdfjs-editor-ink-button-label = Draw +pdfjs-editor-stamp-button = + .title = Add or edit images +pdfjs-editor-stamp-button-label = Add or edit images +pdfjs-editor-highlight-button = + .title = Highlight +pdfjs-editor-highlight-button-label = Highlight +pdfjs-highlight-floating-button1 = + .title = Highlight + .aria-label = Highlight +pdfjs-highlight-floating-button-label = Highlight + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Remove drawing +pdfjs-editor-remove-freetext-button = + .title = Remove text +pdfjs-editor-remove-stamp-button = + .title = Remove image +pdfjs-editor-remove-highlight-button = + .title = Remove highlight + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Colour +pdfjs-editor-free-text-size-input = Size +pdfjs-editor-ink-color-input = Colour +pdfjs-editor-ink-thickness-input = Thickness +pdfjs-editor-ink-opacity-input = Opacity +pdfjs-editor-stamp-add-image-button = + .title = Add image +pdfjs-editor-stamp-add-image-button-label = Add image +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Thickness +pdfjs-editor-free-highlight-thickness-title = + .title = Change thickness when highlighting items other than text +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Text Editor + .default-content = Start typing… +pdfjs-free-text = + .aria-label = Text Editor +pdfjs-free-text-default-content = Start typing… +pdfjs-ink = + .aria-label = Draw Editor +pdfjs-ink-canvas = + .aria-label = User-created image + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alt text +pdfjs-editor-alt-text-edit-button = + .aria-label = Edit alt text +pdfjs-editor-alt-text-edit-button-label = Edit alt text +pdfjs-editor-alt-text-dialog-label = Choose an option +pdfjs-editor-alt-text-dialog-description = Alt text (alternative text) helps when people can’t see the image or when it doesn’t load. +pdfjs-editor-alt-text-add-description-label = Add a description +pdfjs-editor-alt-text-add-description-description = Aim for 1-2 sentences that describe the subject, setting, or actions. +pdfjs-editor-alt-text-mark-decorative-label = Mark as decorative +pdfjs-editor-alt-text-mark-decorative-description = This is used for ornamental images, like borders or watermarks. +pdfjs-editor-alt-text-cancel-button = Cancel +pdfjs-editor-alt-text-save-button = Save +pdfjs-editor-alt-text-decorative-tooltip = Marked as decorative +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = For example, “A young man sits down at a table to eat a meal†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alt text + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Top left corner — resize +pdfjs-editor-resizer-label-top-middle = Top middle — resize +pdfjs-editor-resizer-label-top-right = Top right corner — resize +pdfjs-editor-resizer-label-middle-right = Middle right — resize +pdfjs-editor-resizer-label-bottom-right = Bottom right corner — resize +pdfjs-editor-resizer-label-bottom-middle = Bottom middle — resize +pdfjs-editor-resizer-label-bottom-left = Bottom left corner — resize +pdfjs-editor-resizer-label-middle-left = Middle left — resize +pdfjs-editor-resizer-top-left = + .aria-label = Top left corner — resize +pdfjs-editor-resizer-top-middle = + .aria-label = Top middle — resize +pdfjs-editor-resizer-top-right = + .aria-label = Top right corner — resize +pdfjs-editor-resizer-middle-right = + .aria-label = Middle right — resize +pdfjs-editor-resizer-bottom-right = + .aria-label = Bottom right corner — resize +pdfjs-editor-resizer-bottom-middle = + .aria-label = Bottom middle — resize +pdfjs-editor-resizer-bottom-left = + .aria-label = Bottom left corner — resize +pdfjs-editor-resizer-middle-left = + .aria-label = Middle left — resize + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Highlight colour +pdfjs-editor-colorpicker-button = + .title = Change colour +pdfjs-editor-colorpicker-dropdown = + .aria-label = Colour choices +pdfjs-editor-colorpicker-yellow = + .title = Yellow +pdfjs-editor-colorpicker-green = + .title = Green +pdfjs-editor-colorpicker-blue = + .title = Blue +pdfjs-editor-colorpicker-pink = + .title = Pink +pdfjs-editor-colorpicker-red = + .title = Red + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Show all +pdfjs-editor-highlight-show-all-button = + .title = Show all + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Edit alt text (image description) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Add alt text (image description) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Write your description here… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Short description for people who can’t see the image or when the image doesn’t load. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = This alt text was created automatically and may be inaccurate. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Learn more +pdfjs-editor-new-alt-text-create-automatically-button-label = Create alt text automatically +pdfjs-editor-new-alt-text-not-now-button = Not now +pdfjs-editor-new-alt-text-error-title = Couldn’t create alt text automatically +pdfjs-editor-new-alt-text-error-description = Please write your own alt text or try again later. +pdfjs-editor-new-alt-text-error-close-button = Close +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB) + .aria-valuetext = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alt text added +pdfjs-editor-new-alt-text-added-button-label = Alt text added +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Missing alt text +pdfjs-editor-new-alt-text-missing-button-label = Missing alt text +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Review alt text +pdfjs-editor-new-alt-text-to-review-button-label = Review alt text +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Created automatically: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Image alt text settings +pdfjs-image-alt-text-settings-button-label = Image alt text settings +pdfjs-editor-alt-text-settings-dialog-label = Image alt text settings +pdfjs-editor-alt-text-settings-automatic-title = Automatic alt text +pdfjs-editor-alt-text-settings-create-model-button-label = Create alt text automatically +pdfjs-editor-alt-text-settings-create-model-description = Suggests descriptions to help people who can’t see the image or when the image doesn’t load. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Alt text AI model ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Runs locally on your device so your data stays private. Required for automatic alt text. +pdfjs-editor-alt-text-settings-delete-model-button = Delete +pdfjs-editor-alt-text-settings-download-model-button = Download +pdfjs-editor-alt-text-settings-downloading-model-button = Downloading… +pdfjs-editor-alt-text-settings-editor-title = Alt text editor +pdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor right away when adding an image +pdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text. +pdfjs-editor-alt-text-settings-close-button = Close + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Highlight removed +pdfjs-editor-undo-bar-message-freetext = Text removed +pdfjs-editor-undo-bar-message-ink = Drawing removed +pdfjs-editor-undo-bar-message-stamp = Image removed +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } annotation removed + *[other] { $count } annotations removed + } +pdfjs-editor-undo-bar-undo-button = + .title = Undo +pdfjs-editor-undo-bar-undo-button-label = Undo +pdfjs-editor-undo-bar-close-button = + .title = Close +pdfjs-editor-undo-bar-close-button-label = Close diff --git a/public/assets/pdfjs/locale/en-US/viewer.ftl b/public/assets/pdfjs/locale/en-US/viewer.ftl new file mode 100755 index 0000000..3e4a351 --- /dev/null +++ b/public/assets/pdfjs/locale/en-US/viewer.ftl @@ -0,0 +1,526 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Previous Page +pdfjs-previous-button-label = Previous +pdfjs-next-button = + .title = Next Page +pdfjs-next-button-label = Next + +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Page + +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = of { $pagesCount } + +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount }) + +pdfjs-zoom-out-button = + .title = Zoom Out +pdfjs-zoom-out-button-label = Zoom Out +pdfjs-zoom-in-button = + .title = Zoom In +pdfjs-zoom-in-button-label = Zoom In +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Switch to Presentation Mode +pdfjs-presentation-mode-button-label = Presentation Mode +pdfjs-open-file-button = + .title = Open File +pdfjs-open-file-button-label = Open +pdfjs-print-button = + .title = Print +pdfjs-print-button-label = Print +pdfjs-save-button = + .title = Save +pdfjs-save-button-label = Save + +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Download + +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Download + +pdfjs-bookmark-button = + .title = Current Page (View URL from Current Page) +pdfjs-bookmark-button-label = Current Page + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Tools + +pdfjs-tools-button-label = Tools +pdfjs-first-page-button = + .title = Go to First Page +pdfjs-first-page-button-label = Go to First Page +pdfjs-last-page-button = + .title = Go to Last Page +pdfjs-last-page-button-label = Go to Last Page +pdfjs-page-rotate-cw-button = + .title = Rotate Clockwise +pdfjs-page-rotate-cw-button-label = Rotate Clockwise +pdfjs-page-rotate-ccw-button = + .title = Rotate Counterclockwise +pdfjs-page-rotate-ccw-button-label = Rotate Counterclockwise +pdfjs-cursor-text-select-tool-button = + .title = Enable Text Selection Tool +pdfjs-cursor-text-select-tool-button-label = Text Selection Tool +pdfjs-cursor-hand-tool-button = + .title = Enable Hand Tool +pdfjs-cursor-hand-tool-button-label = Hand Tool +pdfjs-scroll-page-button = + .title = Use Page Scrolling +pdfjs-scroll-page-button-label = Page Scrolling +pdfjs-scroll-vertical-button = + .title = Use Vertical Scrolling +pdfjs-scroll-vertical-button-label = Vertical Scrolling +pdfjs-scroll-horizontal-button = + .title = Use Horizontal Scrolling +pdfjs-scroll-horizontal-button-label = Horizontal Scrolling +pdfjs-scroll-wrapped-button = + .title = Use Wrapped Scrolling +pdfjs-scroll-wrapped-button-label = Wrapped Scrolling +pdfjs-spread-none-button = + .title = Do not join page spreads +pdfjs-spread-none-button-label = No Spreads +pdfjs-spread-odd-button = + .title = Join page spreads starting with odd-numbered pages +pdfjs-spread-odd-button-label = Odd Spreads +pdfjs-spread-even-button = + .title = Join page spreads starting with even-numbered pages +pdfjs-spread-even-button-label = Even Spreads + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Document Properties… +pdfjs-document-properties-button-label = Document Properties… +pdfjs-document-properties-file-name = File name: +pdfjs-document-properties-file-size = File size: + +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) + +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) + +pdfjs-document-properties-title = Title: +pdfjs-document-properties-author = Author: +pdfjs-document-properties-subject = Subject: +pdfjs-document-properties-keywords = Keywords: +pdfjs-document-properties-creation-date = Creation Date: +pdfjs-document-properties-modification-date = Modification Date: + +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +pdfjs-document-properties-creator = Creator: +pdfjs-document-properties-producer = PDF Producer: +pdfjs-document-properties-version = PDF Version: +pdfjs-document-properties-page-count = Page Count: +pdfjs-document-properties-page-size = Page Size: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = portrait +pdfjs-document-properties-page-size-orientation-landscape = landscape +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Fast Web View: +pdfjs-document-properties-linearized-yes = Yes +pdfjs-document-properties-linearized-no = No +pdfjs-document-properties-close-button = Close + +## Print + +pdfjs-print-progress-message = Preparing document for printing… + +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% + +pdfjs-print-progress-close-button = Cancel +pdfjs-printing-not-supported = Warning: Printing is not fully supported by this browser. +pdfjs-printing-not-ready = Warning: The PDF is not fully loaded for printing. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Toggle Sidebar +pdfjs-toggle-sidebar-notification-button = + .title = Toggle Sidebar (document contains outline/attachments/layers) +pdfjs-toggle-sidebar-button-label = Toggle Sidebar +pdfjs-document-outline-button = + .title = Show Document Outline (double-click to expand/collapse all items) +pdfjs-document-outline-button-label = Document Outline +pdfjs-attachments-button = + .title = Show Attachments +pdfjs-attachments-button-label = Attachments +pdfjs-layers-button = + .title = Show Layers (double-click to reset all layers to the default state) +pdfjs-layers-button-label = Layers +pdfjs-thumbs-button = + .title = Show Thumbnails +pdfjs-thumbs-button-label = Thumbnails +pdfjs-current-outline-item-button = + .title = Find Current Outline Item +pdfjs-current-outline-item-button-label = Current Outline Item +pdfjs-findbar-button = + .title = Find in Document +pdfjs-findbar-button-label = Find +pdfjs-additional-layers = Additional Layers + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Page { $page } + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Thumbnail of Page { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Find + .placeholder = Find in document… +pdfjs-find-previous-button = + .title = Find the previous occurrence of the phrase +pdfjs-find-previous-button-label = Previous +pdfjs-find-next-button = + .title = Find the next occurrence of the phrase +pdfjs-find-next-button-label = Next +pdfjs-find-highlight-checkbox = Highlight All +pdfjs-find-match-case-checkbox-label = Match Case +pdfjs-find-match-diacritics-checkbox-label = Match Diacritics +pdfjs-find-entire-word-checkbox-label = Whole Words +pdfjs-find-reached-top = Reached top of document, continued from bottom +pdfjs-find-reached-bottom = Reached end of document, continued from top + +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } of { $total } match + *[other] { $current } of { $total } matches + } + +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] More than { $limit } match + *[other] More than { $limit } matches + } + +pdfjs-find-not-found = Phrase not found + +## Predefined zoom values + +pdfjs-page-scale-width = Page Width +pdfjs-page-scale-fit = Page Fit +pdfjs-page-scale-auto = Automatic Zoom +pdfjs-page-scale-actual = Actual Size + +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Page { $page } + +## Loading indicator messages + +pdfjs-loading-error = An error occurred while loading the PDF. +pdfjs-invalid-file-error = Invalid or corrupted PDF file. +pdfjs-missing-file-error = Missing PDF file. +pdfjs-unexpected-response-error = Unexpected server response. +pdfjs-rendering-error = An error occurred while rendering the page. + +## Annotations + +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Annotation] + +## Password + +pdfjs-password-label = Enter the password to open this PDF file. +pdfjs-password-invalid = Invalid password. Please try again. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Cancel +pdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fonts. + +## Editing + +pdfjs-editor-free-text-button = + .title = Text +pdfjs-editor-free-text-button-label = Text +pdfjs-editor-ink-button = + .title = Draw +pdfjs-editor-ink-button-label = Draw +pdfjs-editor-stamp-button = + .title = Add or edit images +pdfjs-editor-stamp-button-label = Add or edit images +pdfjs-editor-highlight-button = + .title = Highlight +pdfjs-editor-highlight-button-label = Highlight +pdfjs-highlight-floating-button1 = + .title = Highlight + .aria-label = Highlight +pdfjs-highlight-floating-button-label = Highlight + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Remove drawing +pdfjs-editor-remove-freetext-button = + .title = Remove text +pdfjs-editor-remove-stamp-button = + .title = Remove image +pdfjs-editor-remove-highlight-button = + .title = Remove highlight + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Color +pdfjs-editor-free-text-size-input = Size +pdfjs-editor-ink-color-input = Color +pdfjs-editor-ink-thickness-input = Thickness +pdfjs-editor-ink-opacity-input = Opacity +pdfjs-editor-stamp-add-image-button = + .title = Add image +pdfjs-editor-stamp-add-image-button-label = Add image +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Thickness +pdfjs-editor-free-highlight-thickness-title = + .title = Change thickness when highlighting items other than text + +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Text Editor + .default-content = Start typing… +pdfjs-ink = + .aria-label = Draw Editor +pdfjs-ink-canvas = + .aria-label = User-created image + +## Alt-text dialog + +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alt text +pdfjs-editor-alt-text-button-label = Alt text + +pdfjs-editor-alt-text-edit-button = + .aria-label = Edit alt text +pdfjs-editor-alt-text-dialog-label = Choose an option +pdfjs-editor-alt-text-dialog-description = Alt text (alternative text) helps when people can’t see the image or when it doesn’t load. +pdfjs-editor-alt-text-add-description-label = Add a description +pdfjs-editor-alt-text-add-description-description = Aim for 1-2 sentences that describe the subject, setting, or actions. +pdfjs-editor-alt-text-mark-decorative-label = Mark as decorative +pdfjs-editor-alt-text-mark-decorative-description = This is used for ornamental images, like borders or watermarks. +pdfjs-editor-alt-text-cancel-button = Cancel +pdfjs-editor-alt-text-save-button = Save +pdfjs-editor-alt-text-decorative-tooltip = Marked as decorative + +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = For example, “A young man sits down at a table to eat a meal†+ +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-top-left = + .aria-label = Top left corner — resize +pdfjs-editor-resizer-top-middle = + .aria-label = Top middle — resize +pdfjs-editor-resizer-top-right = + .aria-label = Top right corner — resize +pdfjs-editor-resizer-middle-right = + .aria-label = Middle right — resize +pdfjs-editor-resizer-bottom-right = + .aria-label = Bottom right corner — resize +pdfjs-editor-resizer-bottom-middle = + .aria-label = Bottom middle — resize +pdfjs-editor-resizer-bottom-left = + .aria-label = Bottom left corner — resize +pdfjs-editor-resizer-middle-left = + .aria-label = Middle left — resize + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Highlight color + +pdfjs-editor-colorpicker-button = + .title = Change color +pdfjs-editor-colorpicker-dropdown = + .aria-label = Color choices +pdfjs-editor-colorpicker-yellow = + .title = Yellow +pdfjs-editor-colorpicker-green = + .title = Green +pdfjs-editor-colorpicker-blue = + .title = Blue +pdfjs-editor-colorpicker-pink = + .title = Pink +pdfjs-editor-colorpicker-red = + .title = Red + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Show all +pdfjs-editor-highlight-show-all-button = + .title = Show all + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Edit alt text (image description) + +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Add alt text (image description) + +pdfjs-editor-new-alt-text-textarea = + .placeholder = Write your description here… + +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Short description for people who can’t see the image or when the image doesn’t load. + +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = This alt text was created automatically and may be inaccurate. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Learn more + +pdfjs-editor-new-alt-text-create-automatically-button-label = Create alt text automatically +pdfjs-editor-new-alt-text-not-now-button = Not now +pdfjs-editor-new-alt-text-error-title = Couldn’t create alt text automatically +pdfjs-editor-new-alt-text-error-description = Please write your own alt text or try again later. +pdfjs-editor-new-alt-text-error-close-button = Close + +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB) + .aria-valuetext = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB) + +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alt text added +pdfjs-editor-new-alt-text-added-button-label = Alt text added + +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Missing alt text +pdfjs-editor-new-alt-text-missing-button-label = Missing alt text + +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Review alt text +pdfjs-editor-new-alt-text-to-review-button-label = Review alt text + +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Created automatically: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Image alt text settings +pdfjs-image-alt-text-settings-button-label = Image alt text settings + +pdfjs-editor-alt-text-settings-dialog-label = Image alt text settings +pdfjs-editor-alt-text-settings-automatic-title = Automatic alt text +pdfjs-editor-alt-text-settings-create-model-button-label = Create alt text automatically +pdfjs-editor-alt-text-settings-create-model-description = Suggests descriptions to help people who can’t see the image or when the image doesn’t load. + +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Alt text AI model ({ $totalSize } MB) + +pdfjs-editor-alt-text-settings-ai-model-description = Runs locally on your device so your data stays private. Required for automatic alt text. +pdfjs-editor-alt-text-settings-delete-model-button = Delete +pdfjs-editor-alt-text-settings-download-model-button = Download +pdfjs-editor-alt-text-settings-downloading-model-button = Downloading… + +pdfjs-editor-alt-text-settings-editor-title = Alt text editor +pdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor right away when adding an image +pdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text. +pdfjs-editor-alt-text-settings-close-button = Close + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Highlight removed +pdfjs-editor-undo-bar-message-freetext = Text removed +pdfjs-editor-undo-bar-message-ink = Drawing removed +pdfjs-editor-undo-bar-message-stamp = Image removed +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } annotation removed + *[other] { $count } annotations removed + } + +pdfjs-editor-undo-bar-undo-button = + .title = Undo +pdfjs-editor-undo-bar-undo-button-label = Undo +pdfjs-editor-undo-bar-close-button = + .title = Close +pdfjs-editor-undo-bar-close-button-label = Close diff --git a/public/assets/pdfjs/locale/eo/viewer.ftl b/public/assets/pdfjs/locale/eo/viewer.ftl new file mode 100755 index 0000000..ce45ebf --- /dev/null +++ b/public/assets/pdfjs/locale/eo/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = AntaÅ­a paÄo +pdfjs-previous-button-label = MalantaÅ­en +pdfjs-next-button = + .title = Venonta paÄo +pdfjs-next-button-label = AntaÅ­en +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = PaÄo +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = el { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } el { $pagesCount }) +pdfjs-zoom-out-button = + .title = Malpligrandigi +pdfjs-zoom-out-button-label = Malpligrandigi +pdfjs-zoom-in-button = + .title = Pligrandigi +pdfjs-zoom-in-button-label = Pligrandigi +pdfjs-zoom-select = + .title = Pligrandigilo +pdfjs-presentation-mode-button = + .title = Iri al prezenta reÄimo +pdfjs-presentation-mode-button-label = Prezenta reÄimo +pdfjs-open-file-button = + .title = Malfermi dosieron +pdfjs-open-file-button-label = Malfermi +pdfjs-print-button = + .title = Presi +pdfjs-print-button-label = Presi +pdfjs-save-button = + .title = Konservi +pdfjs-save-button-label = Konservi +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = ElÅuti +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = ElÅuti +pdfjs-bookmark-button = + .title = Nuna paÄo (Montri adreson de la nuna paÄo) +pdfjs-bookmark-button-label = Nuna paÄo + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Iloj +pdfjs-tools-button-label = Iloj +pdfjs-first-page-button = + .title = Iri al la unua paÄo +pdfjs-first-page-button-label = Iri al la unua paÄo +pdfjs-last-page-button = + .title = Iri al la lasta paÄo +pdfjs-last-page-button-label = Iri al la lasta paÄo +pdfjs-page-rotate-cw-button = + .title = Rotaciigi dekstrume +pdfjs-page-rotate-cw-button-label = Rotaciigi dekstrume +pdfjs-page-rotate-ccw-button = + .title = Rotaciigi maldekstrume +pdfjs-page-rotate-ccw-button-label = Rotaciigi maldekstrume +pdfjs-cursor-text-select-tool-button = + .title = Aktivigi tekstan elektilon +pdfjs-cursor-text-select-tool-button-label = Teksta elektilo +pdfjs-cursor-hand-tool-button = + .title = Aktivigi ilon de mano +pdfjs-cursor-hand-tool-button-label = Ilo de mano +pdfjs-scroll-page-button = + .title = Uzi rulumon de paÄo +pdfjs-scroll-page-button-label = Rulumo de paÄo +pdfjs-scroll-vertical-button = + .title = Uzi vertikalan rulumon +pdfjs-scroll-vertical-button-label = Vertikala rulumo +pdfjs-scroll-horizontal-button = + .title = Uzi horizontalan rulumon +pdfjs-scroll-horizontal-button-label = Horizontala rulumo +pdfjs-scroll-wrapped-button = + .title = Uzi ambaÅ­direktan rulumon +pdfjs-scroll-wrapped-button-label = AmbaÅ­direkta rulumo +pdfjs-spread-none-button = + .title = Ne montri paÄojn po du +pdfjs-spread-none-button-label = UnupaÄa vido +pdfjs-spread-odd-button = + .title = Kunigi paÄojn komencante per nepara paÄo +pdfjs-spread-odd-button-label = Po du paÄoj, neparaj maldekstre +pdfjs-spread-even-button = + .title = Kunigi paÄojn komencante per para paÄo +pdfjs-spread-even-button-label = Po du paÄoj, paraj maldekstre + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Atributoj de dokumento… +pdfjs-document-properties-button-label = Atributoj de dokumento… +pdfjs-document-properties-file-name = Nomo de dosiero: +pdfjs-document-properties-file-size = Grando de dosiero: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KO ({ $b } oktetoj) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } Mo ({ $b } oktetoj) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KO ({ $size_b } oktetoj) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MO ({ $size_b } oktetoj) +pdfjs-document-properties-title = Titolo: +pdfjs-document-properties-author = AÅ­toro: +pdfjs-document-properties-subject = Temo: +pdfjs-document-properties-keywords = Åœlosilvorto: +pdfjs-document-properties-creation-date = Dato de kreado: +pdfjs-document-properties-modification-date = Dato de modifo: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Kreinto: +pdfjs-document-properties-producer = Produktinto de PDF: +pdfjs-document-properties-version = Versio de PDF: +pdfjs-document-properties-page-count = Nombro de paÄoj: +pdfjs-document-properties-page-size = Grando de paÄo: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = vertikala +pdfjs-document-properties-page-size-orientation-landscape = horizontala +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letera +pdfjs-document-properties-page-size-name-legal = Jura + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Rapida tekstaĵa vido: +pdfjs-document-properties-linearized-yes = Jes +pdfjs-document-properties-linearized-no = Ne +pdfjs-document-properties-close-button = Fermi + +## Print + +pdfjs-print-progress-message = Preparo de dokumento por presi Äin … +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Nuligi +pdfjs-printing-not-supported = Averto: tiu ĉi retumilo ne plene subtenas presadon. +pdfjs-printing-not-ready = Averto: la PDF dosiero ne estas plene Åargita por presado. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Montri/kaÅi flankan strion +pdfjs-toggle-sidebar-notification-button = + .title = Montri/kaÅi flankan strion (la dokumento enhavas konturon/kunsendaĵojn/tavolojn) +pdfjs-toggle-sidebar-button-label = Montri/kaÅi flankan strion +pdfjs-document-outline-button = + .title = Montri la konturon de dokumento (alklaku duoble por faldi/malfaldi ĉiujn elementojn) +pdfjs-document-outline-button-label = Konturo de dokumento +pdfjs-attachments-button = + .title = Montri kunsendaĵojn +pdfjs-attachments-button-label = Kunsendaĵojn +pdfjs-layers-button = + .title = Montri tavolojn (duoble alklaku por remeti ĉiujn tavolojn en la norman staton) +pdfjs-layers-button-label = Tavoloj +pdfjs-thumbs-button = + .title = Montri miniaturojn +pdfjs-thumbs-button-label = Miniaturoj +pdfjs-current-outline-item-button = + .title = Trovi nunan konturan elementon +pdfjs-current-outline-item-button-label = Nuna kontura elemento +pdfjs-findbar-button = + .title = Serĉi en dokumento +pdfjs-findbar-button-label = Serĉi +pdfjs-additional-layers = Aldonaj tavoloj + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = PaÄo { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniaturo de paÄo { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Serĉi + .placeholder = Serĉi en dokumento… +pdfjs-find-previous-button = + .title = Serĉi la antaÅ­an aperon de la frazo +pdfjs-find-previous-button-label = MalantaÅ­en +pdfjs-find-next-button = + .title = Serĉi la venontan aperon de la frazo +pdfjs-find-next-button-label = AntaÅ­en +pdfjs-find-highlight-checkbox = Elstarigi ĉiujn +pdfjs-find-match-case-checkbox-label = Distingi inter majuskloj kaj minuskloj +pdfjs-find-match-diacritics-checkbox-label = Respekti supersignojn +pdfjs-find-entire-word-checkbox-label = Tutaj vortoj +pdfjs-find-reached-top = Komenco de la dokumento atingita, daÅ­rigado ekde la fino +pdfjs-find-reached-bottom = Fino de la dokumento atingita, daÅ­rigado ekde la komenco +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } el { $total } kongruo + *[other] { $current } el { $total } kongruoj + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Pli ol { $limit } kongruo + *[other] Pli ol { $limit } kongruoj + } +pdfjs-find-not-found = Frazo ne trovita + +## Predefined zoom values + +pdfjs-page-scale-width = LarÄo de paÄo +pdfjs-page-scale-fit = Adapti paÄon +pdfjs-page-scale-auto = AÅ­tomata skalo +pdfjs-page-scale-actual = Reala grando +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = PaÄo { $page } + +## Loading indicator messages + +pdfjs-loading-error = Okazis eraro dum la Åargado de la PDF dosiero. +pdfjs-invalid-file-error = Nevalida aÅ­ difektita PDF dosiero. +pdfjs-missing-file-error = Mankas dosiero PDF. +pdfjs-unexpected-response-error = Neatendita respondo de servilo. +pdfjs-rendering-error = Okazis eraro dum la montro de la paÄo. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Prinoto: { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Tajpu pasvorton por malfermi tiun ĉi dosieron PDF. +pdfjs-password-invalid = Nevalida pasvorto. Bonvolu provi denove. +pdfjs-password-ok-button = Akcepti +pdfjs-password-cancel-button = Nuligi +pdfjs-web-fonts-disabled = Neaktivaj teksaĵaj tiparoj: ne elbas uzi enmetitajn tiparojn de PDF. + +## Editing + +pdfjs-editor-free-text-button = + .title = Teksto +pdfjs-editor-free-text-button-label = Teksto +pdfjs-editor-ink-button = + .title = Desegni +pdfjs-editor-ink-button-label = Desegni +pdfjs-editor-stamp-button = + .title = Aldoni aÅ­ modifi bildojn +pdfjs-editor-stamp-button-label = Aldoni aÅ­ modifi bildojn +pdfjs-editor-highlight-button = + .title = Elstarigi +pdfjs-editor-highlight-button-label = Elstarigi +pdfjs-highlight-floating-button1 = + .title = Elstarigi + .aria-label = Elstarigi +pdfjs-highlight-floating-button-label = Elstarigi + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Forigi desegnon +pdfjs-editor-remove-freetext-button = + .title = Forigi tekston +pdfjs-editor-remove-stamp-button = + .title = Forigi bildon +pdfjs-editor-remove-highlight-button = + .title = Forigi elstaraĵon + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Koloro +pdfjs-editor-free-text-size-input = Grando +pdfjs-editor-ink-color-input = Koloro +pdfjs-editor-ink-thickness-input = Dikeco +pdfjs-editor-ink-opacity-input = Maldiafaneco +pdfjs-editor-stamp-add-image-button = + .title = Aldoni bildon +pdfjs-editor-stamp-add-image-button-label = Aldoni bildon +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Dikeco +pdfjs-editor-free-highlight-thickness-title = + .title = ÅœanÄi dikecon dum elstarigo de netekstaj elementoj +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Teksta redaktilo + .default-content = Komencu tajpi… +pdfjs-free-text = + .aria-label = Teksta redaktilo +pdfjs-free-text-default-content = Ektajpi… +pdfjs-ink = + .aria-label = Desegnan redaktilon +pdfjs-ink-canvas = + .aria-label = Bildo kreita de uzanto + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alternativa teksto +pdfjs-editor-alt-text-edit-button = + .aria-label = Redakti alternativan tekston +pdfjs-editor-alt-text-edit-button-label = Redakti alternativan tekston +pdfjs-editor-alt-text-dialog-label = Elektu eblon +pdfjs-editor-alt-text-dialog-description = Alternativa teksto helpas personojn, en la okazoj kiam ili ne povas vidi aÅ­ Åargi la bildon. +pdfjs-editor-alt-text-add-description-label = Aldoni priskribon +pdfjs-editor-alt-text-add-description-description = La celo estas unu aÅ­ du frazoj, kiuj priskribas la temon, etoson aÅ­ agojn. +pdfjs-editor-alt-text-mark-decorative-label = Marki kiel ornaman +pdfjs-editor-alt-text-mark-decorative-description = Tio ĉi estas uzita por ornamaj bildoj, kiel randoj aÅ­ fonaj bildoj. +pdfjs-editor-alt-text-cancel-button = Nuligi +pdfjs-editor-alt-text-save-button = Konservi +pdfjs-editor-alt-text-decorative-tooltip = Markita kiel ornama +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Ekzemple: “Juna persono sidiÄas ĉetable por ekmanÄi†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alternativa teksto + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Supra maldekstra angulo — Åangi grandon +pdfjs-editor-resizer-label-top-middle = Supra mezo — ÅanÄi grandon +pdfjs-editor-resizer-label-top-right = Supran dekstran angulon — ÅanÄi grandon +pdfjs-editor-resizer-label-middle-right = Dekstra mezo — ÅanÄi grandon +pdfjs-editor-resizer-label-bottom-right = Malsupra deksta angulo — ÅanÄi grandon +pdfjs-editor-resizer-label-bottom-middle = Malsupra mezo — ÅanÄi grandon +pdfjs-editor-resizer-label-bottom-left = Malsupra maldekstra angulo — ÅanÄi grandon +pdfjs-editor-resizer-label-middle-left = Maldekstra mezo — ÅanÄi grandon +pdfjs-editor-resizer-top-left = + .aria-label = Supra maldekstra angulo — Åangi grandon +pdfjs-editor-resizer-top-middle = + .aria-label = Supra mezo — ÅanÄi grandon +pdfjs-editor-resizer-top-right = + .aria-label = Supran dekstran angulon — ÅanÄi grandon +pdfjs-editor-resizer-middle-right = + .aria-label = Dekstra mezo — ÅanÄi grandon +pdfjs-editor-resizer-bottom-right = + .aria-label = Malsupra deksta angulo — ÅanÄi grandon +pdfjs-editor-resizer-bottom-middle = + .aria-label = Malsupra mezo — ÅanÄi grandon +pdfjs-editor-resizer-bottom-left = + .aria-label = Malsupra maldekstra angulo — ÅanÄi grandon +pdfjs-editor-resizer-middle-left = + .aria-label = Maldekstra mezo — ÅanÄi grandon + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Elstarigi koloron +pdfjs-editor-colorpicker-button = + .title = ÅœanÄi koloron +pdfjs-editor-colorpicker-dropdown = + .aria-label = Elekto de koloroj +pdfjs-editor-colorpicker-yellow = + .title = Flava +pdfjs-editor-colorpicker-green = + .title = Verda +pdfjs-editor-colorpicker-blue = + .title = Blua +pdfjs-editor-colorpicker-pink = + .title = Roza +pdfjs-editor-colorpicker-red = + .title = RuÄa + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Montri ĉiujn +pdfjs-editor-highlight-show-all-button = + .title = Montri ĉiujn + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Modifi alternativan tekston (priskribo de bildo) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Aldoni alternativan tekston (priskribo de bildo) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Skribu vian priskribon ĉi tie… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Mallonga priskribo por personoj kiuj ne povas vidi la bildon kaj por montri kiam la bildo ne Åargeblas. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Tiu ĉi alternativa teksto estis aÅ­tomate kreita kaj povus esti malÄusta. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Pli da informo +pdfjs-editor-new-alt-text-create-automatically-button-label = AÅ­tomate krei alternativan tekston +pdfjs-editor-new-alt-text-not-now-button = Ne nun +pdfjs-editor-new-alt-text-error-title = Ne eblis aÅ­tomate krei alternativan tekston +pdfjs-editor-new-alt-text-error-description = Bonvolu skribi vian propran alternativan tekston aÅ­ provi denove poste. +pdfjs-editor-new-alt-text-error-close-button = Fermi +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = ElÅuto de modelo de artefarita intelekto por alternativa teksto ({ $downloadedSize } el { $totalSize } MO) + .aria-valuetext = ElÅuto de modelo de artefarita intelekto por alternativa teksto ({ $downloadedSize } el { $totalSize } MO) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alternativa teksto aldonita +pdfjs-editor-new-alt-text-added-button-label = Alternativa teksto aldonita +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Mankas alternativa teksto +pdfjs-editor-new-alt-text-missing-button-label = Mankas alternativa teksto +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Kontroli alternativan tekston +pdfjs-editor-new-alt-text-to-review-button-label = Kontroli alternativan tekston +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = AÅ­tomate kreita: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Agordoj por alternativa teksto de bildoj +pdfjs-image-alt-text-settings-button-label = Agordoj por alternativa teksto de bildoj +pdfjs-editor-alt-text-settings-dialog-label = Agordoj por alternativa teksto de bildoj +pdfjs-editor-alt-text-settings-automatic-title = AÅ­tomata alternativa teksto +pdfjs-editor-alt-text-settings-create-model-button-label = AÅ­tomate krei alternativan tekston +pdfjs-editor-alt-text-settings-create-model-description = Tio ĉi sugestas priskribojn por helpi personojn kiuj ne povas vidi aÅ­ Åargi la bildon. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modelo de artefarita intelekto por alternativa teksto ({ $totalSize } MO) +pdfjs-editor-alt-text-settings-ai-model-description = Äœi funkcias en via aparato, do viaj datumoj restas privataj. Äœi estas postulata por aÅ­tomata kreado de alternativa teksto. +pdfjs-editor-alt-text-settings-delete-model-button = Forigi +pdfjs-editor-alt-text-settings-download-model-button = ElÅuti +pdfjs-editor-alt-text-settings-downloading-model-button = ElÅuto… +pdfjs-editor-alt-text-settings-editor-title = Redaktilo de alternativa teksto +pdfjs-editor-alt-text-settings-show-dialog-button-label = Montri redaktilon de alternativa teksto tuj post aldono de bildo +pdfjs-editor-alt-text-settings-show-dialog-description = Tio ĉi helpas vin kontroli ĉu ĉiuj bildoj havas alternativan tekston. +pdfjs-editor-alt-text-settings-close-button = Fermi + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Elstaraĵo forigita +pdfjs-editor-undo-bar-message-freetext = Teksto forigita +pdfjs-editor-undo-bar-message-ink = Desegno forigita +pdfjs-editor-undo-bar-message-stamp = Bildo forigita +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] unu prinoto forigita + *[other] { $count } prinotoj forigitaj + } +pdfjs-editor-undo-bar-undo-button = + .title = Malfari +pdfjs-editor-undo-bar-undo-button-label = Malfari +pdfjs-editor-undo-bar-close-button = + .title = Fermi +pdfjs-editor-undo-bar-close-button-label = Fermi diff --git a/public/assets/pdfjs/locale/es-AR/viewer.ftl b/public/assets/pdfjs/locale/es-AR/viewer.ftl new file mode 100755 index 0000000..ca73dc7 --- /dev/null +++ b/public/assets/pdfjs/locale/es-AR/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Página anterior +pdfjs-previous-button-label = Anterior +pdfjs-next-button = + .title = Página siguiente +pdfjs-next-button-label = Siguiente +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Página +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = de { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ( { $pageNumber } de { $pagesCount } ) +pdfjs-zoom-out-button = + .title = Alejar +pdfjs-zoom-out-button-label = Alejar +pdfjs-zoom-in-button = + .title = Acercar +pdfjs-zoom-in-button-label = Acercar +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Cambiar a modo presentación +pdfjs-presentation-mode-button-label = Modo presentación +pdfjs-open-file-button = + .title = Abrir archivo +pdfjs-open-file-button-label = Abrir +pdfjs-print-button = + .title = Imprimir +pdfjs-print-button-label = Imprimir +pdfjs-save-button = + .title = Guardar +pdfjs-save-button-label = Guardar +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Descargar +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Descargar +pdfjs-bookmark-button = + .title = Página actual (Ver URL de la página actual) +pdfjs-bookmark-button-label = Página actual + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Herramientas +pdfjs-tools-button-label = Herramientas +pdfjs-first-page-button = + .title = Ir a primera página +pdfjs-first-page-button-label = Ir a primera página +pdfjs-last-page-button = + .title = Ir a última página +pdfjs-last-page-button-label = Ir a última página +pdfjs-page-rotate-cw-button = + .title = Rotar horario +pdfjs-page-rotate-cw-button-label = Rotar horario +pdfjs-page-rotate-ccw-button = + .title = Rotar antihorario +pdfjs-page-rotate-ccw-button-label = Rotar antihorario +pdfjs-cursor-text-select-tool-button = + .title = Habilitar herramienta de selección de texto +pdfjs-cursor-text-select-tool-button-label = Herramienta de selección de texto +pdfjs-cursor-hand-tool-button = + .title = Habilitar herramienta mano +pdfjs-cursor-hand-tool-button-label = Herramienta mano +pdfjs-scroll-page-button = + .title = Usar desplazamiento de página +pdfjs-scroll-page-button-label = Desplazamiento de página +pdfjs-scroll-vertical-button = + .title = Usar desplazamiento vertical +pdfjs-scroll-vertical-button-label = Desplazamiento vertical +pdfjs-scroll-horizontal-button = + .title = Usar desplazamiento vertical +pdfjs-scroll-horizontal-button-label = Desplazamiento horizontal +pdfjs-scroll-wrapped-button = + .title = Usar desplazamiento encapsulado +pdfjs-scroll-wrapped-button-label = Desplazamiento encapsulado +pdfjs-spread-none-button = + .title = No unir páginas dobles +pdfjs-spread-none-button-label = Sin dobles +pdfjs-spread-odd-button = + .title = Unir páginas dobles comenzando con las impares +pdfjs-spread-odd-button-label = Dobles impares +pdfjs-spread-even-button = + .title = Unir páginas dobles comenzando con las pares +pdfjs-spread-even-button-label = Dobles pares + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Propiedades del documento… +pdfjs-document-properties-button-label = Propiedades del documento… +pdfjs-document-properties-file-name = Nombre de archivo: +pdfjs-document-properties-file-size = Tamaño de archovo: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Título: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Asunto: +pdfjs-document-properties-keywords = Palabras clave: +pdfjs-document-properties-creation-date = Fecha de creación: +pdfjs-document-properties-modification-date = Fecha de modificación: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Creador: +pdfjs-document-properties-producer = PDF Productor: +pdfjs-document-properties-version = Versión de PDF: +pdfjs-document-properties-page-count = Cantidad de páginas: +pdfjs-document-properties-page-size = Tamaño de página: +pdfjs-document-properties-page-size-unit-inches = en +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = normal +pdfjs-document-properties-page-size-orientation-landscape = apaisado +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Carta +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Vista rápida de la Web: +pdfjs-document-properties-linearized-yes = Sí +pdfjs-document-properties-linearized-no = No +pdfjs-document-properties-close-button = Cerrar + +## Print + +pdfjs-print-progress-message = Preparando documento para imprimir… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Cancelar +pdfjs-printing-not-supported = Advertencia: La impresión no está totalmente soportada por este navegador. +pdfjs-printing-not-ready = Advertencia: El PDF no está completamente cargado para impresión. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Alternar barra lateral +pdfjs-toggle-sidebar-notification-button = + .title = Alternar barra lateral (el documento contiene esquemas/adjuntos/capas) +pdfjs-toggle-sidebar-button-label = Alternar barra lateral +pdfjs-document-outline-button = + .title = Mostrar esquema del documento (doble clic para expandir/colapsar todos los ítems) +pdfjs-document-outline-button-label = Esquema del documento +pdfjs-attachments-button = + .title = Mostrar adjuntos +pdfjs-attachments-button-label = Adjuntos +pdfjs-layers-button = + .title = Mostrar capas (doble clic para restablecer todas las capas al estado predeterminado) +pdfjs-layers-button-label = Capas +pdfjs-thumbs-button = + .title = Mostrar miniaturas +pdfjs-thumbs-button-label = Miniaturas +pdfjs-current-outline-item-button = + .title = Buscar elemento de esquema actual +pdfjs-current-outline-item-button-label = Elemento de esquema actual +pdfjs-findbar-button = + .title = Buscar en documento +pdfjs-findbar-button-label = Buscar +pdfjs-additional-layers = Capas adicionales + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Página { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura de página { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Buscar + .placeholder = Buscar en documento… +pdfjs-find-previous-button = + .title = Buscar la aparición anterior de la frase +pdfjs-find-previous-button-label = Anterior +pdfjs-find-next-button = + .title = Buscar la siguiente aparición de la frase +pdfjs-find-next-button-label = Siguiente +pdfjs-find-highlight-checkbox = Resaltar todo +pdfjs-find-match-case-checkbox-label = Coincidir mayúsculas +pdfjs-find-match-diacritics-checkbox-label = Coincidir diacríticos +pdfjs-find-entire-word-checkbox-label = Palabras completas +pdfjs-find-reached-top = Inicio de documento alcanzado, continuando desde abajo +pdfjs-find-reached-bottom = Fin de documento alcanzando, continuando desde arriba +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } de { $total } coincidencia + *[other] { $current } de { $total } coincidencias + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Más de { $limit } coincidencia + *[other] Más de { $limit } coincidencias + } +pdfjs-find-not-found = Frase no encontrada + +## Predefined zoom values + +pdfjs-page-scale-width = Ancho de página +pdfjs-page-scale-fit = Ajustar página +pdfjs-page-scale-auto = Zoom automático +pdfjs-page-scale-actual = Tamaño real +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Página { $page } + +## Loading indicator messages + +pdfjs-loading-error = Ocurrió un error al cargar el PDF. +pdfjs-invalid-file-error = Archivo PDF no válido o cocrrupto. +pdfjs-missing-file-error = Archivo PDF faltante. +pdfjs-unexpected-response-error = Respuesta del servidor inesperada. +pdfjs-rendering-error = Ocurrió un error al dibujar la página. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Anotación] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Ingrese la contraseña para abrir este archivo PDF +pdfjs-password-invalid = Contraseña inválida. Intente nuevamente. +pdfjs-password-ok-button = Aceptar +pdfjs-password-cancel-button = Cancelar +pdfjs-web-fonts-disabled = Tipografía web deshabilitada: no se pueden usar tipos incrustados en PDF. + +## Editing + +pdfjs-editor-free-text-button = + .title = Texto +pdfjs-editor-free-text-button-label = Texto +pdfjs-editor-ink-button = + .title = Dibujar +pdfjs-editor-ink-button-label = Dibujar +pdfjs-editor-stamp-button = + .title = Agregar o editar imágenes +pdfjs-editor-stamp-button-label = Agregar o editar imágenes +pdfjs-editor-highlight-button = + .title = Resaltar +pdfjs-editor-highlight-button-label = Resaltar +pdfjs-highlight-floating-button1 = + .title = Resaltar + .aria-label = Resaltar +pdfjs-highlight-floating-button-label = Resaltar + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Eliminar dibujo +pdfjs-editor-remove-freetext-button = + .title = Eliminar texto +pdfjs-editor-remove-stamp-button = + .title = Eliminar imagen +pdfjs-editor-remove-highlight-button = + .title = Eliminar resaltado + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Color +pdfjs-editor-free-text-size-input = Tamaño +pdfjs-editor-ink-color-input = Color +pdfjs-editor-ink-thickness-input = Espesor +pdfjs-editor-ink-opacity-input = Opacidad +pdfjs-editor-stamp-add-image-button = + .title = Agregar una imagen +pdfjs-editor-stamp-add-image-button-label = Agregar una imagen +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Grosor +pdfjs-editor-free-highlight-thickness-title = + .title = Cambiar el grosor al resaltar elementos que no sean texto +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Editor de texto + .default-content = Comenzar a tipear… +pdfjs-free-text = + .aria-label = Editor de texto +pdfjs-free-text-default-content = Empezar a tipear… +pdfjs-ink = + .aria-label = Editor de dibujos +pdfjs-ink-canvas = + .aria-label = Imagen creada por el usuario + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Texto alternativo +pdfjs-editor-alt-text-edit-button = + .aria-label = Editar texto alternativo +pdfjs-editor-alt-text-edit-button-label = Editar el texto alternativo +pdfjs-editor-alt-text-dialog-label = Eligir una opción +pdfjs-editor-alt-text-dialog-description = El texto alternativo (texto alternativo) ayuda cuando las personas no pueden ver la imagen o cuando no se carga. +pdfjs-editor-alt-text-add-description-label = Agregar una descripción +pdfjs-editor-alt-text-add-description-description = Intente escribir 1 o 2 oraciones que describan el tema, el entorno o las acciones. +pdfjs-editor-alt-text-mark-decorative-label = Marcar como decorativo +pdfjs-editor-alt-text-mark-decorative-description = Esto se usa para imágenes ornamentales, como bordes o marcas de agua. +pdfjs-editor-alt-text-cancel-button = Cancelar +pdfjs-editor-alt-text-save-button = Guardar +pdfjs-editor-alt-text-decorative-tooltip = Marcado como decorativo +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Por ejemplo: “Un joven se sienta a la mesa a comer†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Texto alternativo + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Esquina superior izquierda — cambiar el tamaño +pdfjs-editor-resizer-label-top-middle = Arriba en el medio — cambiar el tamaño +pdfjs-editor-resizer-label-top-right = Esquina superior derecha — cambiar el tamaño +pdfjs-editor-resizer-label-middle-right = Al centro a la derecha — cambiar el tamaño +pdfjs-editor-resizer-label-bottom-right = Esquina inferior derecha — cambiar el tamaño +pdfjs-editor-resizer-label-bottom-middle = Abajo en el medio — cambiar el tamaño +pdfjs-editor-resizer-label-bottom-left = Esquina inferior izquierda — cambiar el tamaño +pdfjs-editor-resizer-label-middle-left = Al centro a la izquierda — cambiar el tamaño +pdfjs-editor-resizer-top-left = + .aria-label = Esquina superior izquierda — cambiar el tamaño +pdfjs-editor-resizer-top-middle = + .aria-label = Arriba en el medio — cambiar el tamaño +pdfjs-editor-resizer-top-right = + .aria-label = Esquina superior derecha — cambiar el tamaño +pdfjs-editor-resizer-middle-right = + .aria-label = Al centro a la derecha — cambiar el tamaño +pdfjs-editor-resizer-bottom-right = + .aria-label = Esquina inferior derecha — cambiar el tamaño +pdfjs-editor-resizer-bottom-middle = + .aria-label = Abajo en el medio — cambiar el tamaño +pdfjs-editor-resizer-bottom-left = + .aria-label = Esquina inferior izquierda — cambiar el tamaño +pdfjs-editor-resizer-middle-left = + .aria-label = Al centro a la izquierda — cambiar el tamaño + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Color de resaltado +pdfjs-editor-colorpicker-button = + .title = Cambiar el color +pdfjs-editor-colorpicker-dropdown = + .aria-label = Opciones de color +pdfjs-editor-colorpicker-yellow = + .title = Amarillo +pdfjs-editor-colorpicker-green = + .title = Verde +pdfjs-editor-colorpicker-blue = + .title = Azul +pdfjs-editor-colorpicker-pink = + .title = Rosado +pdfjs-editor-colorpicker-red = + .title = Rojo + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Mostrar todo +pdfjs-editor-highlight-show-all-button = + .title = Mostrar todo + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Editar texto alternativo (descripción de la imagen) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Agregar texto alternativo (descripción de la imagen) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Escribir la descripción aquí… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Descripción corta para las personas que no pueden ver la imagen o cuando la imagen no se carga. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Este texto alternativo fue creado automáticamente y puede ser incorrecto. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Conocer más +pdfjs-editor-new-alt-text-create-automatically-button-label = Crear texto alternativo automáticamente +pdfjs-editor-new-alt-text-not-now-button = No ahora +pdfjs-editor-new-alt-text-error-title = No se pudo crear el texto alternativo automáticamente +pdfjs-editor-new-alt-text-error-description = Escriba su propio texto alternativo o pruebe nuevamente más tarde. +pdfjs-editor-new-alt-text-error-close-button = Cerrar +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Descargando modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB) + .aria-valuetext = Descargando modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Texto alternativo agregado +pdfjs-editor-new-alt-text-added-button-label = Texto alternativo agregado +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Falta el texto alternativo +pdfjs-editor-new-alt-text-missing-button-label = Falta el texto alternativo +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Calificar el texto alternativo +pdfjs-editor-new-alt-text-to-review-button-label = Revisar el texto alternativo +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creado automáticamente: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Configuración de texto alternativo de la imagen +pdfjs-image-alt-text-settings-button-label = Configuración de texto alternativo de la imagen +pdfjs-editor-alt-text-settings-dialog-label = Configuración de texto alternativo de la imagen +pdfjs-editor-alt-text-settings-automatic-title = Texto alternativo automático +pdfjs-editor-alt-text-settings-create-model-button-label = Crear texto alternativo automáticamente +pdfjs-editor-alt-text-settings-create-model-description = Sugiere descripciones para ayudar a las personas que no pueden ver la imagen o cuando la imagen no se carga. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modelo de IA de texto alternativo ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Se ejecuta localmente en el dispositivo para que los datos se mantengan privados. Requerido para texto alternativo automático. +pdfjs-editor-alt-text-settings-delete-model-button = Borrar +pdfjs-editor-alt-text-settings-download-model-button = Descargar +pdfjs-editor-alt-text-settings-downloading-model-button = Descargando… +pdfjs-editor-alt-text-settings-editor-title = Editor de texto alternativo +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostrar el editor de texto alternativo inmediatamente al agregar una imagen +pdfjs-editor-alt-text-settings-show-dialog-description = Te ayuda a asegurarse de que todas las imágenes tengan texto alternativo. +pdfjs-editor-alt-text-settings-close-button = Cerrar + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Resaltado eliminado +pdfjs-editor-undo-bar-message-freetext = Texto eliminado +pdfjs-editor-undo-bar-message-ink = Dibujo eliminado +pdfjs-editor-undo-bar-message-stamp = Imagen eliminado +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } anotación eliminada + *[other] { $count } anotaciones eliminadas + } +pdfjs-editor-undo-bar-undo-button = + .title = Deshacer +pdfjs-editor-undo-bar-undo-button-label = Deshacer +pdfjs-editor-undo-bar-close-button = + .title = Cerrar +pdfjs-editor-undo-bar-close-button-label = Cerrar diff --git a/public/assets/pdfjs/locale/es-CL/viewer.ftl b/public/assets/pdfjs/locale/es-CL/viewer.ftl new file mode 100755 index 0000000..74389e4 --- /dev/null +++ b/public/assets/pdfjs/locale/es-CL/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Página anterior +pdfjs-previous-button-label = Anterior +pdfjs-next-button = + .title = Página siguiente +pdfjs-next-button-label = Siguiente +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Página +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = de { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount }) +pdfjs-zoom-out-button = + .title = Alejar +pdfjs-zoom-out-button-label = Alejar +pdfjs-zoom-in-button = + .title = Acercar +pdfjs-zoom-in-button-label = Acercar +pdfjs-zoom-select = + .title = Ampliación +pdfjs-presentation-mode-button = + .title = Cambiar al modo de presentación +pdfjs-presentation-mode-button-label = Modo de presentación +pdfjs-open-file-button = + .title = Abrir archivo +pdfjs-open-file-button-label = Abrir +pdfjs-print-button = + .title = Imprimir +pdfjs-print-button-label = Imprimir +pdfjs-save-button = + .title = Guardar +pdfjs-save-button-label = Guardar +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Descargar +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Descargar +pdfjs-bookmark-button = + .title = Página actual (Ver URL de la página actual) +pdfjs-bookmark-button-label = Página actual + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Herramientas +pdfjs-tools-button-label = Herramientas +pdfjs-first-page-button = + .title = Ir a la primera página +pdfjs-first-page-button-label = Ir a la primera página +pdfjs-last-page-button = + .title = Ir a la última página +pdfjs-last-page-button-label = Ir a la última página +pdfjs-page-rotate-cw-button = + .title = Girar a la derecha +pdfjs-page-rotate-cw-button-label = Girar a la derecha +pdfjs-page-rotate-ccw-button = + .title = Girar a la izquierda +pdfjs-page-rotate-ccw-button-label = Girar a la izquierda +pdfjs-cursor-text-select-tool-button = + .title = Activar la herramienta de selección de texto +pdfjs-cursor-text-select-tool-button-label = Herramienta de selección de texto +pdfjs-cursor-hand-tool-button = + .title = Activar la herramienta de mano +pdfjs-cursor-hand-tool-button-label = Herramienta de mano +pdfjs-scroll-page-button = + .title = Usar desplazamiento de página +pdfjs-scroll-page-button-label = Desplazamiento de página +pdfjs-scroll-vertical-button = + .title = Usar desplazamiento vertical +pdfjs-scroll-vertical-button-label = Desplazamiento vertical +pdfjs-scroll-horizontal-button = + .title = Usar desplazamiento horizontal +pdfjs-scroll-horizontal-button-label = Desplazamiento horizontal +pdfjs-scroll-wrapped-button = + .title = Usar desplazamiento en bloque +pdfjs-scroll-wrapped-button-label = Desplazamiento en bloque +pdfjs-spread-none-button = + .title = No juntar páginas a modo de libro +pdfjs-spread-none-button-label = Vista de una página +pdfjs-spread-odd-button = + .title = Junta las páginas partiendo con una de número impar +pdfjs-spread-odd-button-label = Vista de libro impar +pdfjs-spread-even-button = + .title = Junta las páginas partiendo con una de número par +pdfjs-spread-even-button-label = Vista de libro par + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Propiedades del documento… +pdfjs-document-properties-button-label = Propiedades del documento… +pdfjs-document-properties-file-name = Nombre de archivo: +pdfjs-document-properties-file-size = Tamaño del archivo: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Título: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Asunto: +pdfjs-document-properties-keywords = Palabras clave: +pdfjs-document-properties-creation-date = Fecha de creación: +pdfjs-document-properties-modification-date = Fecha de modificación: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Creador: +pdfjs-document-properties-producer = Productor del PDF: +pdfjs-document-properties-version = Versión de PDF: +pdfjs-document-properties-page-count = Cantidad de páginas: +pdfjs-document-properties-page-size = Tamaño de la página: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = vertical +pdfjs-document-properties-page-size-orientation-landscape = horizontal +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Carta +pdfjs-document-properties-page-size-name-legal = Oficio + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Vista rápida en Web: +pdfjs-document-properties-linearized-yes = Sí +pdfjs-document-properties-linearized-no = No +pdfjs-document-properties-close-button = Cerrar + +## Print + +pdfjs-print-progress-message = Preparando documento para impresión… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Cancelar +pdfjs-printing-not-supported = Advertencia: Imprimir no está soportado completamente por este navegador. +pdfjs-printing-not-ready = Advertencia: El PDF no está completamente cargado para ser impreso. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Barra lateral +pdfjs-toggle-sidebar-notification-button = + .title = Cambiar barra lateral (índice de contenidos del documento/adjuntos/capas) +pdfjs-toggle-sidebar-button-label = Mostrar u ocultar la barra lateral +pdfjs-document-outline-button = + .title = Mostrar esquema del documento (doble clic para expandir/contraer todos los elementos) +pdfjs-document-outline-button-label = Esquema del documento +pdfjs-attachments-button = + .title = Mostrar adjuntos +pdfjs-attachments-button-label = Adjuntos +pdfjs-layers-button = + .title = Mostrar capas (doble clic para restablecer todas las capas al estado predeterminado) +pdfjs-layers-button-label = Capas +pdfjs-thumbs-button = + .title = Mostrar miniaturas +pdfjs-thumbs-button-label = Miniaturas +pdfjs-current-outline-item-button = + .title = Buscar elemento de esquema actual +pdfjs-current-outline-item-button-label = Elemento de esquema actual +pdfjs-findbar-button = + .title = Buscar en el documento +pdfjs-findbar-button-label = Buscar +pdfjs-additional-layers = Capas adicionales + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Página { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura de la página { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Encontrar + .placeholder = Encontrar en el documento… +pdfjs-find-previous-button = + .title = Buscar la aparición anterior de la frase +pdfjs-find-previous-button-label = Previo +pdfjs-find-next-button = + .title = Buscar la siguiente aparición de la frase +pdfjs-find-next-button-label = Siguiente +pdfjs-find-highlight-checkbox = Destacar todos +pdfjs-find-match-case-checkbox-label = Coincidir mayús./minús. +pdfjs-find-match-diacritics-checkbox-label = Coincidir diacríticos +pdfjs-find-entire-word-checkbox-label = Palabras completas +pdfjs-find-reached-top = Se alcanzó el inicio del documento, continuando desde el final +pdfjs-find-reached-bottom = Se alcanzó el final del documento, continuando desde el inicio +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] Coincidencia { $current } de { $total } + *[other] Coincidencia { $current } de { $total } + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Más de { $limit } coincidencia + *[other] Más de { $limit } coincidencias + } +pdfjs-find-not-found = Frase no encontrada + +## Predefined zoom values + +pdfjs-page-scale-width = Ancho de página +pdfjs-page-scale-fit = Ajuste de página +pdfjs-page-scale-auto = Aumento automático +pdfjs-page-scale-actual = Tamaño actual +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Página { $page } + +## Loading indicator messages + +pdfjs-loading-error = Ocurrió un error al cargar el PDF. +pdfjs-invalid-file-error = Archivo PDF inválido o corrupto. +pdfjs-missing-file-error = Falta el archivo PDF. +pdfjs-unexpected-response-error = Respuesta del servidor inesperada. +pdfjs-rendering-error = Ocurrió un error al renderizar la página. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Anotación] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Ingresa la contraseña para abrir este archivo PDF. +pdfjs-password-invalid = Contraseña inválida. Por favor, vuelve a intentarlo. +pdfjs-password-ok-button = Aceptar +pdfjs-password-cancel-button = Cancelar +pdfjs-web-fonts-disabled = Las tipografías web están desactivadas: imposible usar las fuentes PDF embebidas. + +## Editing + +pdfjs-editor-free-text-button = + .title = Texto +pdfjs-editor-free-text-button-label = Texto +pdfjs-editor-ink-button = + .title = Dibujar +pdfjs-editor-ink-button-label = Dibujar +pdfjs-editor-stamp-button = + .title = Añadir o editar imágenes +pdfjs-editor-stamp-button-label = Añadir o editar imágenes +pdfjs-editor-highlight-button = + .title = Destacar +pdfjs-editor-highlight-button-label = Destacar +pdfjs-highlight-floating-button1 = + .title = Destacar + .aria-label = Destacar +pdfjs-highlight-floating-button-label = Destacar + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Eliminar dibujo +pdfjs-editor-remove-freetext-button = + .title = Eliminar texto +pdfjs-editor-remove-stamp-button = + .title = Eliminar imagen +pdfjs-editor-remove-highlight-button = + .title = Quitar resaltado + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Color +pdfjs-editor-free-text-size-input = Tamaño +pdfjs-editor-ink-color-input = Color +pdfjs-editor-ink-thickness-input = Grosor +pdfjs-editor-ink-opacity-input = Opacidad +pdfjs-editor-stamp-add-image-button = + .title = Añadir imagen +pdfjs-editor-stamp-add-image-button-label = Añadir imagen +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Grosor +pdfjs-editor-free-highlight-thickness-title = + .title = Cambia el grosor al resaltar elementos que no sean texto +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Editor de texto + .default-content = Empieza a escribir… +pdfjs-free-text = + .aria-label = Editor de texto +pdfjs-free-text-default-content = Empieza a escribir… +pdfjs-ink = + .aria-label = Editor de dibujos +pdfjs-ink-canvas = + .aria-label = Imagen creada por el usuario + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Texto alternativo +pdfjs-editor-alt-text-edit-button = + .aria-label = Editar texto alternativo +pdfjs-editor-alt-text-edit-button-label = Editar texto alternativo +pdfjs-editor-alt-text-dialog-label = Elige una opción +pdfjs-editor-alt-text-dialog-description = El texto alternativo (alt text) ayuda cuando las personas no pueden ver la imagen o cuando no se carga. +pdfjs-editor-alt-text-add-description-label = Añade una descripción +pdfjs-editor-alt-text-add-description-description = Intenta escribir 1 o 2 oraciones que describan el tema, el ambiente o las acciones. +pdfjs-editor-alt-text-mark-decorative-label = Marcar como decorativa +pdfjs-editor-alt-text-mark-decorative-description = Se utiliza para imágenes ornamentales, como bordes o marcas de agua. +pdfjs-editor-alt-text-cancel-button = Cancelar +pdfjs-editor-alt-text-save-button = Guardar +pdfjs-editor-alt-text-decorative-tooltip = Marcada como decorativa +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Por ejemplo: “Un joven se sienta a la mesa a comer†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Texto alternativo + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Esquina superior izquierda — cambiar el tamaño +pdfjs-editor-resizer-label-top-middle = Borde superior en el medio — cambiar el tamaño +pdfjs-editor-resizer-label-top-right = Esquina superior derecha — cambiar el tamaño +pdfjs-editor-resizer-label-middle-right = Borde derecho en el medio — cambiar el tamaño +pdfjs-editor-resizer-label-bottom-right = Esquina inferior derecha — cambiar el tamaño +pdfjs-editor-resizer-label-bottom-middle = Borde inferior en el medio — cambiar el tamaño +pdfjs-editor-resizer-label-bottom-left = Esquina inferior izquierda — cambiar el tamaño +pdfjs-editor-resizer-label-middle-left = Borde izquierdo en el medio — cambiar el tamaño +pdfjs-editor-resizer-top-left = + .aria-label = Esquina superior izquierda — cambiar el tamaño +pdfjs-editor-resizer-top-middle = + .aria-label = Borde superior en el medio — cambiar el tamaño +pdfjs-editor-resizer-top-right = + .aria-label = Esquina superior derecha — cambiar el tamaño +pdfjs-editor-resizer-middle-right = + .aria-label = Borde derecho en el medio — cambiar el tamaño +pdfjs-editor-resizer-bottom-right = + .aria-label = Esquina inferior derecha — cambiar el tamaño +pdfjs-editor-resizer-bottom-middle = + .aria-label = Borde inferior en el medio — cambiar el tamaño +pdfjs-editor-resizer-bottom-left = + .aria-label = Esquina inferior izquierda — cambiar el tamaño +pdfjs-editor-resizer-middle-left = + .aria-label = Borde izquierdo en el medio — cambiar el tamaño + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Color de resaltado +pdfjs-editor-colorpicker-button = + .title = Cambiar color +pdfjs-editor-colorpicker-dropdown = + .aria-label = Opciones de color +pdfjs-editor-colorpicker-yellow = + .title = Amarillo +pdfjs-editor-colorpicker-green = + .title = Verde +pdfjs-editor-colorpicker-blue = + .title = Azul +pdfjs-editor-colorpicker-pink = + .title = Rosa +pdfjs-editor-colorpicker-red = + .title = Rojo + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Mostrar todo +pdfjs-editor-highlight-show-all-button = + .title = Mostrar todo + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Editar texto alternativo (descripción de la imagen) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Añadir texto alternativo (descripción de la imagen) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Escribe tu descripción aquí… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Breve descripción para las personas que no pueden ver la imagen o cuando la imagen no se carga. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Este texto alternativo fue creado automáticamente y puede ser incorrecto. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Aprender más +pdfjs-editor-new-alt-text-create-automatically-button-label = Crear texto alternativo automáticamente +pdfjs-editor-new-alt-text-not-now-button = Ahora no +pdfjs-editor-new-alt-text-error-title = No se pudo crear el texto alternativo automáticamente +pdfjs-editor-new-alt-text-error-description = Escribe tu propio texto alternativo o vuelve a intentarlo más tarde. +pdfjs-editor-new-alt-text-error-close-button = Cerrar +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Descargando el modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB) + .aria-valuetext = Descargando el modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Se añadió el texto alternativo +pdfjs-editor-new-alt-text-added-button-label = Se añadió el texto alternativo +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Falta el texto alternativo +pdfjs-editor-new-alt-text-missing-button-label = Falta el texto alternativo +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Revisar el texto alternativo +pdfjs-editor-new-alt-text-to-review-button-label = Revisar el texto alternativo +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creado automáticamente: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Ajustes del texto alternativo de la imagen +pdfjs-image-alt-text-settings-button-label = Ajustes del texto alternativo de la imagen +pdfjs-editor-alt-text-settings-dialog-label = Ajustes del texto alternativo de la imagen +pdfjs-editor-alt-text-settings-automatic-title = Texto alternativo automático +pdfjs-editor-alt-text-settings-create-model-button-label = Crear texto alternativo automáticamente +pdfjs-editor-alt-text-settings-create-model-description = Sugiere descripciones para ayudar a las personas que no pueden ver la imagen o cuando la imagen no se carga. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modelo de IA de texto alternativo ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Se ejecuta localmente en tu dispositivo para que tus datos permanezcan privados. Necesario para el texto alternativo automático. +pdfjs-editor-alt-text-settings-delete-model-button = Eliminar +pdfjs-editor-alt-text-settings-download-model-button = Descargar +pdfjs-editor-alt-text-settings-downloading-model-button = Bajando… +pdfjs-editor-alt-text-settings-editor-title = Editor de texto alternativo +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostrar el editor de texto alternativo inmediatamente al añadir una imagen +pdfjs-editor-alt-text-settings-show-dialog-description = Te ayuda a asegurarte de que todas tus imágenes tengan texto alternativo. +pdfjs-editor-alt-text-settings-close-button = Cerrar + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Resaltado eliminado +pdfjs-editor-undo-bar-message-freetext = Texto eliminado +pdfjs-editor-undo-bar-message-ink = Dibujo eliminado +pdfjs-editor-undo-bar-message-stamp = Imagen eliminada +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } anotación eliminada + *[other] { $count } anotaciones eliminadas + } +pdfjs-editor-undo-bar-undo-button = + .title = Deshacer +pdfjs-editor-undo-bar-undo-button-label = Deshacer +pdfjs-editor-undo-bar-close-button = + .title = Cerrar +pdfjs-editor-undo-bar-close-button-label = Cerrar diff --git a/public/assets/pdfjs/locale/es-ES/viewer.ftl b/public/assets/pdfjs/locale/es-ES/viewer.ftl new file mode 100755 index 0000000..f610a17 --- /dev/null +++ b/public/assets/pdfjs/locale/es-ES/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Página anterior +pdfjs-previous-button-label = Anterior +pdfjs-next-button = + .title = Página siguiente +pdfjs-next-button-label = Siguiente +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Página +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = de { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount }) +pdfjs-zoom-out-button = + .title = Reducir +pdfjs-zoom-out-button-label = Reducir +pdfjs-zoom-in-button = + .title = Aumentar +pdfjs-zoom-in-button-label = Aumentar +pdfjs-zoom-select = + .title = Tamaño +pdfjs-presentation-mode-button = + .title = Cambiar al modo presentación +pdfjs-presentation-mode-button-label = Modo presentación +pdfjs-open-file-button = + .title = Abrir archivo +pdfjs-open-file-button-label = Abrir +pdfjs-print-button = + .title = Imprimir +pdfjs-print-button-label = Imprimir +pdfjs-save-button = + .title = Guardar +pdfjs-save-button-label = Guardar +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Descargar +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Descargar +pdfjs-bookmark-button = + .title = Página actual (Ver URL de la página actual) +pdfjs-bookmark-button-label = Página actual + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Herramientas +pdfjs-tools-button-label = Herramientas +pdfjs-first-page-button = + .title = Ir a la primera página +pdfjs-first-page-button-label = Ir a la primera página +pdfjs-last-page-button = + .title = Ir a la última página +pdfjs-last-page-button-label = Ir a la última página +pdfjs-page-rotate-cw-button = + .title = Rotar en sentido horario +pdfjs-page-rotate-cw-button-label = Rotar en sentido horario +pdfjs-page-rotate-ccw-button = + .title = Rotar en sentido antihorario +pdfjs-page-rotate-ccw-button-label = Rotar en sentido antihorario +pdfjs-cursor-text-select-tool-button = + .title = Activar herramienta de selección de texto +pdfjs-cursor-text-select-tool-button-label = Herramienta de selección de texto +pdfjs-cursor-hand-tool-button = + .title = Activar herramienta de mano +pdfjs-cursor-hand-tool-button-label = Herramienta de mano +pdfjs-scroll-page-button = + .title = Usar desplazamiento de página +pdfjs-scroll-page-button-label = Desplazamiento de página +pdfjs-scroll-vertical-button = + .title = Usar desplazamiento vertical +pdfjs-scroll-vertical-button-label = Desplazamiento vertical +pdfjs-scroll-horizontal-button = + .title = Usar desplazamiento horizontal +pdfjs-scroll-horizontal-button-label = Desplazamiento horizontal +pdfjs-scroll-wrapped-button = + .title = Usar desplazamiento en bloque +pdfjs-scroll-wrapped-button-label = Desplazamiento en bloque +pdfjs-spread-none-button = + .title = No juntar páginas en vista de libro +pdfjs-spread-none-button-label = Vista de libro +pdfjs-spread-odd-button = + .title = Juntar las páginas partiendo de una con número impar +pdfjs-spread-odd-button-label = Vista de libro impar +pdfjs-spread-even-button = + .title = Juntar las páginas partiendo de una con número par +pdfjs-spread-even-button-label = Vista de libro par + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Propiedades del documento… +pdfjs-document-properties-button-label = Propiedades del documento… +pdfjs-document-properties-file-name = Nombre de archivo: +pdfjs-document-properties-file-size = Tamaño de archivo: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Título: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Asunto: +pdfjs-document-properties-keywords = Palabras clave: +pdfjs-document-properties-creation-date = Fecha de creación: +pdfjs-document-properties-modification-date = Fecha de modificación: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Creador: +pdfjs-document-properties-producer = Productor PDF: +pdfjs-document-properties-version = Versión PDF: +pdfjs-document-properties-page-count = Número de páginas: +pdfjs-document-properties-page-size = Tamaño de la página: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = vertical +pdfjs-document-properties-page-size-orientation-landscape = horizontal +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Carta +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Vista rápida de la web: +pdfjs-document-properties-linearized-yes = Sí +pdfjs-document-properties-linearized-no = No +pdfjs-document-properties-close-button = Cerrar + +## Print + +pdfjs-print-progress-message = Preparando documento para impresión… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Cancelar +pdfjs-printing-not-supported = Advertencia: Imprimir no está totalmente soportado por este navegador. +pdfjs-printing-not-ready = Advertencia: Este PDF no se ha cargado completamente para poder imprimirse. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Cambiar barra lateral +pdfjs-toggle-sidebar-notification-button = + .title = Alternar barra lateral (el documento contiene esquemas/adjuntos/capas) +pdfjs-toggle-sidebar-button-label = Cambiar barra lateral +pdfjs-document-outline-button = + .title = Mostrar resumen del documento (doble clic para expandir/contraer todos los elementos) +pdfjs-document-outline-button-label = Resumen de documento +pdfjs-attachments-button = + .title = Mostrar adjuntos +pdfjs-attachments-button-label = Adjuntos +pdfjs-layers-button = + .title = Mostrar capas (doble clic para restablecer todas las capas al estado predeterminado) +pdfjs-layers-button-label = Capas +pdfjs-thumbs-button = + .title = Mostrar miniaturas +pdfjs-thumbs-button-label = Miniaturas +pdfjs-current-outline-item-button = + .title = Encontrar elemento de esquema actual +pdfjs-current-outline-item-button-label = Elemento de esquema actual +pdfjs-findbar-button = + .title = Buscar en el documento +pdfjs-findbar-button-label = Buscar +pdfjs-additional-layers = Capas adicionales + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Página { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura de la página { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Buscar + .placeholder = Buscar en el documento… +pdfjs-find-previous-button = + .title = Encontrar la anterior aparición de la frase +pdfjs-find-previous-button-label = Anterior +pdfjs-find-next-button = + .title = Encontrar la siguiente aparición de esta frase +pdfjs-find-next-button-label = Siguiente +pdfjs-find-highlight-checkbox = Resaltar todos +pdfjs-find-match-case-checkbox-label = Coincidencia de mayús./minús. +pdfjs-find-match-diacritics-checkbox-label = Coincidir diacríticos +pdfjs-find-entire-word-checkbox-label = Palabras completas +pdfjs-find-reached-top = Se alcanzó el inicio del documento, se continúa desde el final +pdfjs-find-reached-bottom = Se alcanzó el final del documento, se continúa desde el inicio +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } de { $total } coincidencia + *[other] { $current } de { $total } coincidencias + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Más de { $limit } coincidencia + *[other] Más de { $limit } coincidencias + } +pdfjs-find-not-found = Frase no encontrada + +## Predefined zoom values + +pdfjs-page-scale-width = Anchura de la página +pdfjs-page-scale-fit = Ajuste de la página +pdfjs-page-scale-auto = Tamaño automático +pdfjs-page-scale-actual = Tamaño real +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Página { $page } + +## Loading indicator messages + +pdfjs-loading-error = Ocurrió un error al cargar el PDF. +pdfjs-invalid-file-error = Fichero PDF no válido o corrupto. +pdfjs-missing-file-error = No hay fichero PDF. +pdfjs-unexpected-response-error = Respuesta inesperada del servidor. +pdfjs-rendering-error = Ocurrió un error al renderizar la página. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Anotación { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Introduzca la contraseña para abrir este archivo PDF. +pdfjs-password-invalid = Contraseña no válida. Vuelva a intentarlo. +pdfjs-password-ok-button = Aceptar +pdfjs-password-cancel-button = Cancelar +pdfjs-web-fonts-disabled = Las tipografías web están desactivadas: es imposible usar las tipografías PDF embebidas. + +## Editing + +pdfjs-editor-free-text-button = + .title = Texto +pdfjs-editor-free-text-button-label = Texto +pdfjs-editor-ink-button = + .title = Dibujar +pdfjs-editor-ink-button-label = Dibujar +pdfjs-editor-stamp-button = + .title = Añadir o editar imágenes +pdfjs-editor-stamp-button-label = Añadir o editar imágenes +pdfjs-editor-highlight-button = + .title = Resaltar +pdfjs-editor-highlight-button-label = Resaltar +pdfjs-highlight-floating-button1 = + .title = Resaltar + .aria-label = Resaltar +pdfjs-highlight-floating-button-label = Resaltar + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Eliminar dibujo +pdfjs-editor-remove-freetext-button = + .title = Eliminar texto +pdfjs-editor-remove-stamp-button = + .title = Eliminar imagen +pdfjs-editor-remove-highlight-button = + .title = Quitar resaltado + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Color +pdfjs-editor-free-text-size-input = Tamaño +pdfjs-editor-ink-color-input = Color +pdfjs-editor-ink-thickness-input = Grosor +pdfjs-editor-ink-opacity-input = Opacidad +pdfjs-editor-stamp-add-image-button = + .title = Añadir imagen +pdfjs-editor-stamp-add-image-button-label = Añadir imagen +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Grosor +pdfjs-editor-free-highlight-thickness-title = + .title = Cambiar el grosor al resaltar elementos que no sean texto +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Editor de texto + .default-content = Empiece a escribir… +pdfjs-free-text = + .aria-label = Editor de texto +pdfjs-free-text-default-content = Empezar a escribir… +pdfjs-ink = + .aria-label = Editor de dibujos +pdfjs-ink-canvas = + .aria-label = Imagen creada por el usuario + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Texto alternativo +pdfjs-editor-alt-text-edit-button = + .aria-label = Editar el texto alternativo +pdfjs-editor-alt-text-edit-button-label = Editar el texto alternativo +pdfjs-editor-alt-text-dialog-label = Eligir una opción +pdfjs-editor-alt-text-dialog-description = El texto alternativo (texto alternativo) ayuda cuando las personas no pueden ver la imagen o cuando no se carga. +pdfjs-editor-alt-text-add-description-label = Añadir una descripción +pdfjs-editor-alt-text-add-description-description = Intente escribir 1 o 2 frases que describan el tema, el entorno o las acciones. +pdfjs-editor-alt-text-mark-decorative-label = Marcar como decorativa +pdfjs-editor-alt-text-mark-decorative-description = Se utiliza para imágenes ornamentales, como bordes o marcas de agua. +pdfjs-editor-alt-text-cancel-button = Cancelar +pdfjs-editor-alt-text-save-button = Guardar +pdfjs-editor-alt-text-decorative-tooltip = Marcada como decorativa +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Por ejemplo: “Un joven se sienta a la mesa a comer†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Texto alternativo + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Esquina superior izquierda — redimensionar +pdfjs-editor-resizer-label-top-middle = Borde superior en el medio — redimensionar +pdfjs-editor-resizer-label-top-right = Esquina superior derecha — redimensionar +pdfjs-editor-resizer-label-middle-right = Borde derecho en el medio — redimensionar +pdfjs-editor-resizer-label-bottom-right = Esquina inferior derecha — redimensionar +pdfjs-editor-resizer-label-bottom-middle = Borde inferior en el medio — redimensionar +pdfjs-editor-resizer-label-bottom-left = Esquina inferior izquierda — redimensionar +pdfjs-editor-resizer-label-middle-left = Borde izquierdo en el medio — redimensionar +pdfjs-editor-resizer-top-left = + .aria-label = Esquina superior izquierda — redimensionar +pdfjs-editor-resizer-top-middle = + .aria-label = Borde superior en el medio — redimensionar +pdfjs-editor-resizer-top-right = + .aria-label = Esquina superior derecha — redimensionar +pdfjs-editor-resizer-middle-right = + .aria-label = Borde derecho en el medio — redimensionar +pdfjs-editor-resizer-bottom-right = + .aria-label = Esquina inferior derecha — redimensionar +pdfjs-editor-resizer-bottom-middle = + .aria-label = Borde inferior en el medio — redimensionar +pdfjs-editor-resizer-bottom-left = + .aria-label = Esquina inferior izquierda — redimensionar +pdfjs-editor-resizer-middle-left = + .aria-label = Borde izquierdo en el medio — redimensionar + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Color de resaltado +pdfjs-editor-colorpicker-button = + .title = Cambiar color +pdfjs-editor-colorpicker-dropdown = + .aria-label = Opciones de color +pdfjs-editor-colorpicker-yellow = + .title = Amarillo +pdfjs-editor-colorpicker-green = + .title = Verde +pdfjs-editor-colorpicker-blue = + .title = Azul +pdfjs-editor-colorpicker-pink = + .title = Rosa +pdfjs-editor-colorpicker-red = + .title = Rojo + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Mostrar todo +pdfjs-editor-highlight-show-all-button = + .title = Mostrar todo + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Editar texto alternativo (descripción de la imagen) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Añadir texto alternativo (descripción de la imagen) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Escribir la descripción aquí… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Breve descripción para las personas que no pueden ver la imagen o cuando la imagen no se carga. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Este texto alternativo fue creado automáticamente y puede ser inexacto. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Saber más +pdfjs-editor-new-alt-text-create-automatically-button-label = Crear texto alternativo automáticamente +pdfjs-editor-new-alt-text-not-now-button = Ahora no +pdfjs-editor-new-alt-text-error-title = No se ha podido crear el texto alternativo automáticamente +pdfjs-editor-new-alt-text-error-description = Escriba su propio texto alternativo o inténtelo de nuevo más tarde. +pdfjs-editor-new-alt-text-error-close-button = Cerrar +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Descargando el modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB) + .aria-valuetext = Descargando el modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Se añadió el texto alternativo +pdfjs-editor-new-alt-text-added-button-label = Se añadió el texto alternativo +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Falta el texto alternativo +pdfjs-editor-new-alt-text-missing-button-label = Falta el texto alternativo +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Revisar el texto alternativo +pdfjs-editor-new-alt-text-to-review-button-label = Revisar el texto alternativo +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creado automáticamente: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Ajustes del texto alternativo de la imagen +pdfjs-image-alt-text-settings-button-label = Ajustes del texto alternativo de la imagen +pdfjs-editor-alt-text-settings-dialog-label = Ajustes del texto alternativo de la imagen +pdfjs-editor-alt-text-settings-automatic-title = Texto alternativo automático +pdfjs-editor-alt-text-settings-create-model-button-label = Crear texto alternativo automáticamente +pdfjs-editor-alt-text-settings-create-model-description = Sugiere descripciones para ayudar a las personas que no pueden ver la imagen o cuando la imagen no se carga. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modelo de IA de texto alternativo ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Se ejecuta localmente en el dispositivo para que los datos se mantengan privados. Requerido para texto alternativo automático. +pdfjs-editor-alt-text-settings-delete-model-button = Eliminar +pdfjs-editor-alt-text-settings-download-model-button = Descargar +pdfjs-editor-alt-text-settings-downloading-model-button = Descargando… +pdfjs-editor-alt-text-settings-editor-title = Editor de texto alternativo +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostrar el editor de texto alternativo inmediatamente al añadir una imagen +pdfjs-editor-alt-text-settings-show-dialog-description = Le ayuda a asegurarse de que todas sus imágenes tengan texto alternativo. +pdfjs-editor-alt-text-settings-close-button = Cerrar + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Resaltado eliminado +pdfjs-editor-undo-bar-message-freetext = Texto eliminado +pdfjs-editor-undo-bar-message-ink = Dibujo eliminado +pdfjs-editor-undo-bar-message-stamp = Imagen eliminada +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } anotación eliminada + *[other] { $count } anotaciones eliminadas + } +pdfjs-editor-undo-bar-undo-button = + .title = Deshacer +pdfjs-editor-undo-bar-undo-button-label = Deshacer +pdfjs-editor-undo-bar-close-button = + .title = Cerrar +pdfjs-editor-undo-bar-close-button-label = Cerrar diff --git a/public/assets/pdfjs/locale/es-MX/viewer.ftl b/public/assets/pdfjs/locale/es-MX/viewer.ftl new file mode 100755 index 0000000..03afe7c --- /dev/null +++ b/public/assets/pdfjs/locale/es-MX/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Página anterior +pdfjs-previous-button-label = Anterior +pdfjs-next-button = + .title = Página siguiente +pdfjs-next-button-label = Siguiente +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Página +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = de { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount }) +pdfjs-zoom-out-button = + .title = Reducir +pdfjs-zoom-out-button-label = Reducir +pdfjs-zoom-in-button = + .title = Aumentar +pdfjs-zoom-in-button-label = Aumentar +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Cambiar al modo presentación +pdfjs-presentation-mode-button-label = Modo presentación +pdfjs-open-file-button = + .title = Abrir archivo +pdfjs-open-file-button-label = Abrir +pdfjs-print-button = + .title = Imprimir +pdfjs-print-button-label = Imprimir +pdfjs-save-button = + .title = Guardar +pdfjs-save-button-label = Guardar +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Descargar +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Descargar +pdfjs-bookmark-button = + .title = Página actual (Ver URL de la página actual) +pdfjs-bookmark-button-label = Página actual + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Herramientas +pdfjs-tools-button-label = Herramientas +pdfjs-first-page-button = + .title = Ir a la primera página +pdfjs-first-page-button-label = Ir a la primera página +pdfjs-last-page-button = + .title = Ir a la última página +pdfjs-last-page-button-label = Ir a la última página +pdfjs-page-rotate-cw-button = + .title = Girar a la derecha +pdfjs-page-rotate-cw-button-label = Girar a la derecha +pdfjs-page-rotate-ccw-button = + .title = Girar a la izquierda +pdfjs-page-rotate-ccw-button-label = Girar a la izquierda +pdfjs-cursor-text-select-tool-button = + .title = Activar la herramienta de selección de texto +pdfjs-cursor-text-select-tool-button-label = Herramienta de selección de texto +pdfjs-cursor-hand-tool-button = + .title = Activar la herramienta de mano +pdfjs-cursor-hand-tool-button-label = Herramienta de mano +pdfjs-scroll-page-button = + .title = Usar desplazamiento de página +pdfjs-scroll-page-button-label = Desplazamiento de página +pdfjs-scroll-vertical-button = + .title = Usar desplazamiento vertical +pdfjs-scroll-vertical-button-label = Desplazamiento vertical +pdfjs-scroll-horizontal-button = + .title = Usar desplazamiento horizontal +pdfjs-scroll-horizontal-button-label = Desplazamiento horizontal +pdfjs-scroll-wrapped-button = + .title = Usar desplazamiento encapsulado +pdfjs-scroll-wrapped-button-label = Desplazamiento encapsulado +pdfjs-spread-none-button = + .title = No unir páginas separadas +pdfjs-spread-none-button-label = Vista de una página +pdfjs-spread-odd-button = + .title = Unir las páginas partiendo con una de número impar +pdfjs-spread-odd-button-label = Vista de libro impar +pdfjs-spread-even-button = + .title = Juntar las páginas partiendo con una de número par +pdfjs-spread-even-button-label = Vista de libro par + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Propiedades del documento… +pdfjs-document-properties-button-label = Propiedades del documento… +pdfjs-document-properties-file-name = Nombre del archivo: +pdfjs-document-properties-file-size = Tamaño del archivo: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Título: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Asunto: +pdfjs-document-properties-keywords = Palabras claves: +pdfjs-document-properties-creation-date = Fecha de creación: +pdfjs-document-properties-modification-date = Fecha de modificación: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Creador: +pdfjs-document-properties-producer = Productor PDF: +pdfjs-document-properties-version = Versión PDF: +pdfjs-document-properties-page-count = Número de páginas: +pdfjs-document-properties-page-size = Tamaño de la página: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = vertical +pdfjs-document-properties-page-size-orientation-landscape = horizontal +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Carta +pdfjs-document-properties-page-size-name-legal = Oficio + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Vista rápida de la web: +pdfjs-document-properties-linearized-yes = Sí +pdfjs-document-properties-linearized-no = No +pdfjs-document-properties-close-button = Cerrar + +## Print + +pdfjs-print-progress-message = Preparando documento para impresión… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Cancelar +pdfjs-printing-not-supported = Advertencia: La impresión no esta completamente soportada por este navegador. +pdfjs-printing-not-ready = Advertencia: El PDF no cargo completamente para impresión. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Cambiar barra lateral +pdfjs-toggle-sidebar-notification-button = + .title = Alternar barra lateral (el documento contiene esquemas/adjuntos/capas) +pdfjs-toggle-sidebar-button-label = Cambiar barra lateral +pdfjs-document-outline-button = + .title = Mostrar esquema del documento (doble clic para expandir/contraer todos los elementos) +pdfjs-document-outline-button-label = Esquema del documento +pdfjs-attachments-button = + .title = Mostrar adjuntos +pdfjs-attachments-button-label = Adjuntos +pdfjs-layers-button = + .title = Mostrar capas (doble clic para restablecer todas las capas al estado predeterminado) +pdfjs-layers-button-label = Capas +pdfjs-thumbs-button = + .title = Mostrar miniaturas +pdfjs-thumbs-button-label = Miniaturas +pdfjs-current-outline-item-button = + .title = Buscar elemento de esquema actual +pdfjs-current-outline-item-button-label = Elemento de esquema actual +pdfjs-findbar-button = + .title = Buscar en el documento +pdfjs-findbar-button-label = Buscar +pdfjs-additional-layers = Capas adicionales + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Página { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura de la página { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Buscar + .placeholder = Buscar en el documento… +pdfjs-find-previous-button = + .title = Ir a la anterior frase encontrada +pdfjs-find-previous-button-label = Anterior +pdfjs-find-next-button = + .title = Ir a la siguiente frase encontrada +pdfjs-find-next-button-label = Siguiente +pdfjs-find-highlight-checkbox = Resaltar todo +pdfjs-find-match-case-checkbox-label = Coincidir con mayúsculas y minúsculas +pdfjs-find-match-diacritics-checkbox-label = Coincidir diacríticos +pdfjs-find-entire-word-checkbox-label = Palabras completas +pdfjs-find-reached-top = Se alcanzó el inicio del documento, se buscará al final +pdfjs-find-reached-bottom = Se alcanzó el final del documento, se buscará al inicio +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } de { $total } coincidencia + *[other] { $current } de { $total } coincidencias + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Más de { $limit } coincidencia + *[other] Más de { $limit } coincidencias + } +pdfjs-find-not-found = No se encontró la frase + +## Predefined zoom values + +pdfjs-page-scale-width = Ancho de página +pdfjs-page-scale-fit = Ajustar página +pdfjs-page-scale-auto = Zoom automático +pdfjs-page-scale-actual = Tamaño real +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Página { $page } + +## Loading indicator messages + +pdfjs-loading-error = Un error ocurrió al cargar el PDF. +pdfjs-invalid-file-error = Archivo PDF invalido o dañado. +pdfjs-missing-file-error = Archivo PDF no encontrado. +pdfjs-unexpected-response-error = Respuesta inesperada del servidor. +pdfjs-rendering-error = Un error ocurrió al renderizar la página. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } anotación] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Ingresa la contraseña para abrir este archivo PDF. +pdfjs-password-invalid = Contraseña inválida. Por favor intenta de nuevo. +pdfjs-password-ok-button = Aceptar +pdfjs-password-cancel-button = Cancelar +pdfjs-web-fonts-disabled = Las fuentes web están desactivadas: es imposible usar las fuentes PDF embebidas. + +## Editing + +pdfjs-editor-free-text-button = + .title = Texto +pdfjs-editor-free-text-button-label = Texto +pdfjs-editor-ink-button = + .title = Dibujar +pdfjs-editor-ink-button-label = Dibujar +pdfjs-editor-stamp-button = + .title = Agregar o editar imágenes +pdfjs-editor-stamp-button-label = Agregar o editar imágenes +pdfjs-editor-highlight-button = + .title = Destacar +pdfjs-editor-highlight-button-label = Destacar +pdfjs-highlight-floating-button1 = + .title = Destacados + .aria-label = Destacados +pdfjs-highlight-floating-button-label = Destacados + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Eliminar dibujo +pdfjs-editor-remove-freetext-button = + .title = Eliminar texto +pdfjs-editor-remove-stamp-button = + .title = Eliminar imagen +pdfjs-editor-remove-highlight-button = + .title = Eliminar destacado + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Color +pdfjs-editor-free-text-size-input = Tamaño +pdfjs-editor-ink-color-input = Color +pdfjs-editor-ink-thickness-input = Grossor +pdfjs-editor-ink-opacity-input = Opacidad +pdfjs-editor-stamp-add-image-button = + .title = Agregar imagen +pdfjs-editor-stamp-add-image-button-label = Agregar imagen +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Espesor +pdfjs-editor-free-highlight-thickness-title = + .title = Cambiar el grosor al resaltar elementos que no sean texto +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Editor de texto + .default-content = Comenzar a escribir… +pdfjs-free-text = + .aria-label = Editor de texto +pdfjs-free-text-default-content = Empieza a escribir… +pdfjs-ink = + .aria-label = Editor de dibujo +pdfjs-ink-canvas = + .aria-label = Imagen creada por el usuario + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Texto alternativo +pdfjs-editor-alt-text-edit-button = + .aria-label = Editar texto alternativo +pdfjs-editor-alt-text-edit-button-label = Editar texto alternativo +pdfjs-editor-alt-text-dialog-label = Elige una opción +pdfjs-editor-alt-text-dialog-description = El texto alternativo (texto alternativo) ayuda cuando las personas no pueden ver la imagen o cuando no se carga. +pdfjs-editor-alt-text-add-description-label = Añadir una descripción +pdfjs-editor-alt-text-add-description-description = Intente escribir 1 o 2 oraciones que describan el tema, el entorno o las acciones. +pdfjs-editor-alt-text-mark-decorative-label = Marcar como decorativo +pdfjs-editor-alt-text-mark-decorative-description = Se utiliza para imágenes ornamentales, como bordes o marcas de agua. +pdfjs-editor-alt-text-cancel-button = Cancelar +pdfjs-editor-alt-text-save-button = Guardar +pdfjs-editor-alt-text-decorative-tooltip = Marcado como decorativo +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Por ejemplo: “Un joven se sienta a la mesa a comer†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Texto alternativo + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Esquina superior izquierda: cambiar el tamaño +pdfjs-editor-resizer-label-top-middle = Arriba en el medio: cambiar el tamaño +pdfjs-editor-resizer-label-top-right = Esquina superior derecha: cambiar el tamaño +pdfjs-editor-resizer-label-middle-right = Centro derecha: cambiar el tamaño +pdfjs-editor-resizer-label-bottom-right = Esquina inferior derecha: cambiar el tamaño +pdfjs-editor-resizer-label-bottom-middle = Abajo en el medio: cambiar el tamaño +pdfjs-editor-resizer-label-bottom-left = Esquina inferior izquierda: cambiar el tamaño +pdfjs-editor-resizer-label-middle-left = Centro izquierda: cambiar el tamaño +pdfjs-editor-resizer-top-left = + .aria-label = Esquina superior izquierda — redimensionar +pdfjs-editor-resizer-top-middle = + .aria-label = Borde superior en el medio — redimensionar +pdfjs-editor-resizer-top-right = + .aria-label = Esquina superior derecha — redimensionar +pdfjs-editor-resizer-middle-right = + .aria-label = Borde derecho en el medio — redimensionar +pdfjs-editor-resizer-bottom-right = + .aria-label = Esquina inferior derecha — redimensionar +pdfjs-editor-resizer-bottom-middle = + .aria-label = Borde inferior en el medio — redimensionar +pdfjs-editor-resizer-bottom-left = + .aria-label = Esquina inferior izquierda — redimensionar +pdfjs-editor-resizer-middle-left = + .aria-label = Borde izquierdo en el medio — redimensionar + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Color de resaltado +pdfjs-editor-colorpicker-button = + .title = Cambiar color +pdfjs-editor-colorpicker-dropdown = + .aria-label = Opciones de color +pdfjs-editor-colorpicker-yellow = + .title = Amarillo +pdfjs-editor-colorpicker-green = + .title = Verde +pdfjs-editor-colorpicker-blue = + .title = Azul +pdfjs-editor-colorpicker-pink = + .title = Rosa +pdfjs-editor-colorpicker-red = + .title = Rojo + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Mostrar todo +pdfjs-editor-highlight-show-all-button = + .title = Mostrar todo + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Editar texto alternativo (descripción de la imagen) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Agregar texto alternativo (descripción de la imagen) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Escribe tu descripción aquí… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Breve descripción para las personas que no pueden ver la imagen o cuando la imagen no se carga. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Este texto alternativo fue creado automáticamente y puede ser inexacto. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Saber más +pdfjs-editor-new-alt-text-create-automatically-button-label = Crear texto alternativo automáticamente +pdfjs-editor-new-alt-text-not-now-button = Ahora no +pdfjs-editor-new-alt-text-error-title = No se pudo crear el texto alternativo automáticamente +pdfjs-editor-new-alt-text-error-description = Escribe tu propio texto alternativo o inténtalo de nuevo más tarde. +pdfjs-editor-new-alt-text-error-close-button = Cerrar +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Descargando el modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB) + .aria-valuetext = Descargando el modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Se agregó el texto alternativo +pdfjs-editor-new-alt-text-added-button-label = Se agregó el texto alternativo +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Falta el texto alternativo +pdfjs-editor-new-alt-text-missing-button-label = Falta texto alternativo +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Revisar el texto alternativo +pdfjs-editor-new-alt-text-to-review-button-label = Revisar el texto alternativo +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creado automáticamente: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Ajustes del texto alternativo de la imagen +pdfjs-image-alt-text-settings-button-label = Ajustes del texto alternativo de la imagen +pdfjs-editor-alt-text-settings-dialog-label = Ajustes del texto alternativo de la imagen +pdfjs-editor-alt-text-settings-automatic-title = Texto alternativo automático +pdfjs-editor-alt-text-settings-create-model-button-label = Crear texto alternativo automáticamente +pdfjs-editor-alt-text-settings-create-model-description = Sugiere descripciones para ayudar a las personas que no pueden ver la imagen o cuando la imagen no se carga. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modelo de IA de texto alternativo ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Se ejecuta localmente en el dispositivo para que los datos se mantengan privados. Requerido para texto alternativo automático. +pdfjs-editor-alt-text-settings-delete-model-button = Eliminar +pdfjs-editor-alt-text-settings-download-model-button = Descargar +pdfjs-editor-alt-text-settings-downloading-model-button = Descargando… +pdfjs-editor-alt-text-settings-editor-title = Editor de texto alternativo +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostrar el editor de texto alternativo inmediatamente al añadir una imagen +pdfjs-editor-alt-text-settings-show-dialog-description = Te ayuda a asegurarte de que todas tus imágenes tengan texto alternativo. +pdfjs-editor-alt-text-settings-close-button = Cerrar + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Resaltado eliminado +pdfjs-editor-undo-bar-message-freetext = Texto eliminado +pdfjs-editor-undo-bar-message-ink = Dibujo eliminado +pdfjs-editor-undo-bar-message-stamp = Imagen eliminada +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } anotación eliminada + *[other] { $count } anotaciones eliminadas + } +pdfjs-editor-undo-bar-undo-button = + .title = Deshacer +pdfjs-editor-undo-bar-undo-button-label = Deshacer +pdfjs-editor-undo-bar-close-button = + .title = Cerrar +pdfjs-editor-undo-bar-close-button-label = Cerrar diff --git a/public/assets/pdfjs/locale/et/viewer.ftl b/public/assets/pdfjs/locale/et/viewer.ftl new file mode 100755 index 0000000..b28c6d5 --- /dev/null +++ b/public/assets/pdfjs/locale/et/viewer.ftl @@ -0,0 +1,268 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Eelmine lehekülg +pdfjs-previous-button-label = Eelmine +pdfjs-next-button = + .title = Järgmine lehekülg +pdfjs-next-button-label = Järgmine +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Leht +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = / { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber }/{ $pagesCount }) +pdfjs-zoom-out-button = + .title = Vähenda +pdfjs-zoom-out-button-label = Vähenda +pdfjs-zoom-in-button = + .title = Suurenda +pdfjs-zoom-in-button-label = Suurenda +pdfjs-zoom-select = + .title = Suurendamine +pdfjs-presentation-mode-button = + .title = Lülitu esitlusrežiimi +pdfjs-presentation-mode-button-label = Esitlusrežiim +pdfjs-open-file-button = + .title = Ava fail +pdfjs-open-file-button-label = Ava +pdfjs-print-button = + .title = Prindi +pdfjs-print-button-label = Prindi + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Tööriistad +pdfjs-tools-button-label = Tööriistad +pdfjs-first-page-button = + .title = Mine esimesele leheküljele +pdfjs-first-page-button-label = Mine esimesele leheküljele +pdfjs-last-page-button = + .title = Mine viimasele leheküljele +pdfjs-last-page-button-label = Mine viimasele leheküljele +pdfjs-page-rotate-cw-button = + .title = Pööra päripäeva +pdfjs-page-rotate-cw-button-label = Pööra päripäeva +pdfjs-page-rotate-ccw-button = + .title = Pööra vastupäeva +pdfjs-page-rotate-ccw-button-label = Pööra vastupäeva +pdfjs-cursor-text-select-tool-button = + .title = Luba teksti valimise tööriist +pdfjs-cursor-text-select-tool-button-label = Teksti valimise tööriist +pdfjs-cursor-hand-tool-button = + .title = Luba sirvimistööriist +pdfjs-cursor-hand-tool-button-label = Sirvimistööriist +pdfjs-scroll-page-button = + .title = Kasutatakse lehe kaupa kerimist +pdfjs-scroll-page-button-label = Lehe kaupa kerimine +pdfjs-scroll-vertical-button = + .title = Kasuta vertikaalset kerimist +pdfjs-scroll-vertical-button-label = Vertikaalne kerimine +pdfjs-scroll-horizontal-button = + .title = Kasuta horisontaalset kerimist +pdfjs-scroll-horizontal-button-label = Horisontaalne kerimine +pdfjs-scroll-wrapped-button = + .title = Kasuta rohkem mahutavat kerimist +pdfjs-scroll-wrapped-button-label = Rohkem mahutav kerimine +pdfjs-spread-none-button = + .title = Ära kõrvuta lehekülgi +pdfjs-spread-none-button-label = Lehtede kõrvutamine puudub +pdfjs-spread-odd-button = + .title = Kõrvuta leheküljed, alustades paaritute numbritega lehekülgedega +pdfjs-spread-odd-button-label = Kõrvutamine paaritute numbritega alustades +pdfjs-spread-even-button = + .title = Kõrvuta leheküljed, alustades paarisnumbritega lehekülgedega +pdfjs-spread-even-button-label = Kõrvutamine paarisnumbritega alustades + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Dokumendi omadused… +pdfjs-document-properties-button-label = Dokumendi omadused… +pdfjs-document-properties-file-name = Faili nimi: +pdfjs-document-properties-file-size = Faili suurus: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KiB ({ $size_b } baiti) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MiB ({ $size_b } baiti) +pdfjs-document-properties-title = Pealkiri: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Teema: +pdfjs-document-properties-keywords = Märksõnad: +pdfjs-document-properties-creation-date = Loodud: +pdfjs-document-properties-modification-date = Muudetud: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date } { $time } +pdfjs-document-properties-creator = Looja: +pdfjs-document-properties-producer = Generaator: +pdfjs-document-properties-version = Generaatori versioon: +pdfjs-document-properties-page-count = Lehekülgi: +pdfjs-document-properties-page-size = Lehe suurus: +pdfjs-document-properties-page-size-unit-inches = tolli +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = vertikaalpaigutus +pdfjs-document-properties-page-size-orientation-landscape = rõhtpaigutus +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = "Fast Web View" tugi: +pdfjs-document-properties-linearized-yes = Jah +pdfjs-document-properties-linearized-no = Ei +pdfjs-document-properties-close-button = Sulge + +## Print + +pdfjs-print-progress-message = Dokumendi ettevalmistamine printimiseks… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Loobu +pdfjs-printing-not-supported = Hoiatus: printimine pole selle brauseri poolt täielikult toetatud. +pdfjs-printing-not-ready = Hoiatus: PDF pole printimiseks täielikult laaditud. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Näita külgriba +pdfjs-toggle-sidebar-notification-button = + .title = Näita külgriba (dokument sisaldab sisukorda/manuseid/kihte) +pdfjs-toggle-sidebar-button-label = Näita külgriba +pdfjs-document-outline-button = + .title = Näita sisukorda (kõigi punktide laiendamiseks/ahendamiseks topeltklõpsa) +pdfjs-document-outline-button-label = Näita sisukorda +pdfjs-attachments-button = + .title = Näita manuseid +pdfjs-attachments-button-label = Manused +pdfjs-layers-button = + .title = Näita kihte (kõikide kihtide vaikeolekusse lähtestamiseks topeltklõpsa) +pdfjs-layers-button-label = Kihid +pdfjs-thumbs-button = + .title = Näita pisipilte +pdfjs-thumbs-button-label = Pisipildid +pdfjs-current-outline-item-button = + .title = Otsi üles praegune kontuuriüksus +pdfjs-current-outline-item-button-label = Praegune kontuuriüksus +pdfjs-findbar-button = + .title = Otsi dokumendist +pdfjs-findbar-button-label = Otsi +pdfjs-additional-layers = Täiendavad kihid + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = { $page }. lehekülg +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = { $page }. lehekülje pisipilt + +## Find panel button title and messages + +pdfjs-find-input = + .title = Otsi + .placeholder = Otsi dokumendist… +pdfjs-find-previous-button = + .title = Otsi fraasi eelmine esinemiskoht +pdfjs-find-previous-button-label = Eelmine +pdfjs-find-next-button = + .title = Otsi fraasi järgmine esinemiskoht +pdfjs-find-next-button-label = Järgmine +pdfjs-find-highlight-checkbox = Too kõik esile +pdfjs-find-match-case-checkbox-label = Tõstutundlik +pdfjs-find-match-diacritics-checkbox-label = Otsitakse diakriitiliselt +pdfjs-find-entire-word-checkbox-label = Täissõnad +pdfjs-find-reached-top = Jõuti dokumendi algusesse, jätkati lõpust +pdfjs-find-reached-bottom = Jõuti dokumendi lõppu, jätkati algusest +pdfjs-find-not-found = Fraasi ei leitud + +## Predefined zoom values + +pdfjs-page-scale-width = Mahuta laiusele +pdfjs-page-scale-fit = Mahuta leheküljele +pdfjs-page-scale-auto = Automaatne suurendamine +pdfjs-page-scale-actual = Tegelik suurus +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Lehekülg { $page } + +## Loading indicator messages + +pdfjs-loading-error = PDFi laadimisel esines viga. +pdfjs-invalid-file-error = Vigane või rikutud PDF-fail. +pdfjs-missing-file-error = PDF-fail puudub. +pdfjs-unexpected-response-error = Ootamatu vastus serverilt. +pdfjs-rendering-error = Lehe renderdamisel esines viga. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date } { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Annotation] + +## Password + +pdfjs-password-label = PDF-faili avamiseks sisesta parool. +pdfjs-password-invalid = Vigane parool. Palun proovi uuesti. +pdfjs-password-ok-button = Sobib +pdfjs-password-cancel-button = Loobu +pdfjs-web-fonts-disabled = Veebifondid on keelatud: PDFiga kaasatud fonte pole võimalik kasutada. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/eu/viewer.ftl b/public/assets/pdfjs/locale/eu/viewer.ftl new file mode 100755 index 0000000..2b2660b --- /dev/null +++ b/public/assets/pdfjs/locale/eu/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Aurreko orria +pdfjs-previous-button-label = Aurrekoa +pdfjs-next-button = + .title = Hurrengo orria +pdfjs-next-button-label = Hurrengoa +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Orria +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = / { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = { $pagesCount }/{ $pageNumber } +pdfjs-zoom-out-button = + .title = Urrundu zooma +pdfjs-zoom-out-button-label = Urrundu zooma +pdfjs-zoom-in-button = + .title = Gerturatu zooma +pdfjs-zoom-in-button-label = Gerturatu zooma +pdfjs-zoom-select = + .title = Zooma +pdfjs-presentation-mode-button = + .title = Aldatu aurkezpen modura +pdfjs-presentation-mode-button-label = Arkezpen modua +pdfjs-open-file-button = + .title = Ireki fitxategia +pdfjs-open-file-button-label = Ireki +pdfjs-print-button = + .title = Inprimatu +pdfjs-print-button-label = Inprimatu +pdfjs-save-button = + .title = Gorde +pdfjs-save-button-label = Gorde +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Deskargatu +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Deskargatu +pdfjs-bookmark-button = + .title = Uneko orria (ikusi uneko orriaren URLa) +pdfjs-bookmark-button-label = Uneko orria + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Tresnak +pdfjs-tools-button-label = Tresnak +pdfjs-first-page-button = + .title = Joan lehen orrira +pdfjs-first-page-button-label = Joan lehen orrira +pdfjs-last-page-button = + .title = Joan azken orrira +pdfjs-last-page-button-label = Joan azken orrira +pdfjs-page-rotate-cw-button = + .title = Biratu erlojuaren norantzan +pdfjs-page-rotate-cw-button-label = Biratu erlojuaren norantzan +pdfjs-page-rotate-ccw-button = + .title = Biratu erlojuaren aurkako norantzan +pdfjs-page-rotate-ccw-button-label = Biratu erlojuaren aurkako norantzan +pdfjs-cursor-text-select-tool-button = + .title = Gaitu testuaren hautapen tresna +pdfjs-cursor-text-select-tool-button-label = Testuaren hautapen tresna +pdfjs-cursor-hand-tool-button = + .title = Gaitu eskuaren tresna +pdfjs-cursor-hand-tool-button-label = Eskuaren tresna +pdfjs-scroll-page-button = + .title = Erabili orriaren korritzea +pdfjs-scroll-page-button-label = Orriaren korritzea +pdfjs-scroll-vertical-button = + .title = Erabili korritze bertikala +pdfjs-scroll-vertical-button-label = Korritze bertikala +pdfjs-scroll-horizontal-button = + .title = Erabili korritze horizontala +pdfjs-scroll-horizontal-button-label = Korritze horizontala +pdfjs-scroll-wrapped-button = + .title = Erabili korritze egokitua +pdfjs-scroll-wrapped-button-label = Korritze egokitua +pdfjs-spread-none-button = + .title = Ez elkartu barreiatutako orriak +pdfjs-spread-none-button-label = Barreiatzerik ez +pdfjs-spread-odd-button = + .title = Elkartu barreiatutako orriak bakoiti zenbakidunekin hasita +pdfjs-spread-odd-button-label = Barreiatze bakoitia +pdfjs-spread-even-button = + .title = Elkartu barreiatutako orriak bikoiti zenbakidunekin hasita +pdfjs-spread-even-button-label = Barreiatze bikoitia + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Dokumentuaren propietateak… +pdfjs-document-properties-button-label = Dokumentuaren propietateak… +pdfjs-document-properties-file-name = Fitxategi-izena: +pdfjs-document-properties-file-size = Fitxategiaren tamaina: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } byte) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } byte) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } byte) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } byte) +pdfjs-document-properties-title = Izenburua: +pdfjs-document-properties-author = Egilea: +pdfjs-document-properties-subject = Gaia: +pdfjs-document-properties-keywords = Gako-hitzak: +pdfjs-document-properties-creation-date = Sortze-data: +pdfjs-document-properties-modification-date = Aldatze-data: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Sortzailea: +pdfjs-document-properties-producer = PDFaren ekoizlea: +pdfjs-document-properties-version = PDF bertsioa: +pdfjs-document-properties-page-count = Orrialde kopurua: +pdfjs-document-properties-page-size = Orriaren tamaina: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = bertikala +pdfjs-document-properties-page-size-orientation-landscape = horizontala +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Gutuna +pdfjs-document-properties-page-size-name-legal = Legala + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Webeko ikuspegi bizkorra: +pdfjs-document-properties-linearized-yes = Bai +pdfjs-document-properties-linearized-no = Ez +pdfjs-document-properties-close-button = Itxi + +## Print + +pdfjs-print-progress-message = Dokumentua inprimatzeko prestatzen… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = %{ $progress } +pdfjs-print-progress-close-button = Utzi +pdfjs-printing-not-supported = Abisua: inprimatzeko euskarria ez da erabatekoa nabigatzaile honetan. +pdfjs-printing-not-ready = Abisua: PDFa ez dago erabat kargatuta inprimatzeko. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Txandakatu alboko barra +pdfjs-toggle-sidebar-notification-button = + .title = Txandakatu alboko barra (dokumentuak eskema/eranskinak/geruzak ditu) +pdfjs-toggle-sidebar-button-label = Txandakatu alboko barra +pdfjs-document-outline-button = + .title = Erakutsi dokumentuaren eskema (klik bikoitza elementu guztiak zabaltzeko/tolesteko) +pdfjs-document-outline-button-label = Dokumentuaren eskema +pdfjs-attachments-button = + .title = Erakutsi eranskinak +pdfjs-attachments-button-label = Eranskinak +pdfjs-layers-button = + .title = Erakutsi geruzak (klik bikoitza geruza guztiak egoera lehenetsira berrezartzeko) +pdfjs-layers-button-label = Geruzak +pdfjs-thumbs-button = + .title = Erakutsi koadro txikiak +pdfjs-thumbs-button-label = Koadro txikiak +pdfjs-current-outline-item-button = + .title = Bilatu uneko eskemaren elementua +pdfjs-current-outline-item-button-label = Uneko eskemaren elementua +pdfjs-findbar-button = + .title = Bilatu dokumentuan +pdfjs-findbar-button-label = Bilatu +pdfjs-additional-layers = Geruza gehigarriak + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = { $page }. orria +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = { $page }. orriaren koadro txikia + +## Find panel button title and messages + +pdfjs-find-input = + .title = Bilatu + .placeholder = Bilatu dokumentuan… +pdfjs-find-previous-button = + .title = Bilatu esaldiaren aurreko parekatzea +pdfjs-find-previous-button-label = Aurrekoa +pdfjs-find-next-button = + .title = Bilatu esaldiaren hurrengo parekatzea +pdfjs-find-next-button-label = Hurrengoa +pdfjs-find-highlight-checkbox = Nabarmendu guztia +pdfjs-find-match-case-checkbox-label = Bat etorri maiuskulekin/minuskulekin +pdfjs-find-match-diacritics-checkbox-label = Bereizi diakritikoak +pdfjs-find-entire-word-checkbox-label = Hitz osoak +pdfjs-find-reached-top = Dokumentuaren hasierara heldu da, bukaeratik jarraitzen +pdfjs-find-reached-bottom = Dokumentuaren bukaerara heldu da, hasieratik jarraitzen +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $total }/{ $current }. bat-etortzea + *[other] { $total }/{ $current }. bat-etortzea + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Bat datorren { $limit } baino gehiago + *[other] Bat datozen { $limit } baino gehiago + } +pdfjs-find-not-found = Esaldia ez da aurkitu + +## Predefined zoom values + +pdfjs-page-scale-width = Orriaren zabalera +pdfjs-page-scale-fit = Doitu orrira +pdfjs-page-scale-auto = Zoom automatikoa +pdfjs-page-scale-actual = Benetako tamaina +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = %{ $scale } + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = { $page }. orria + +## Loading indicator messages + +pdfjs-loading-error = Errorea gertatu da PDFa kargatzean. +pdfjs-invalid-file-error = PDF fitxategi baliogabe edo hondatua. +pdfjs-missing-file-error = PDF fitxategia falta da. +pdfjs-unexpected-response-error = Espero gabeko zerbitzariaren erantzuna. +pdfjs-rendering-error = Errorea gertatu da orria errendatzean. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } ohartarazpena] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Idatzi PDF fitxategi hau irekitzeko pasahitza. +pdfjs-password-invalid = Pasahitz baliogabea. Saiatu berriro mesedez. +pdfjs-password-ok-button = Ados +pdfjs-password-cancel-button = Utzi +pdfjs-web-fonts-disabled = Webeko letra-tipoak desgaituta daude: ezin dira kapsulatutako PDF letra-tipoak erabili. + +## Editing + +pdfjs-editor-free-text-button = + .title = Testua +pdfjs-editor-free-text-button-label = Testua +pdfjs-editor-ink-button = + .title = Marrazkia +pdfjs-editor-ink-button-label = Marrazkia +pdfjs-editor-stamp-button = + .title = Gehitu edo editatu irudiak +pdfjs-editor-stamp-button-label = Gehitu edo editatu irudiak +pdfjs-editor-highlight-button = + .title = Nabarmendu +pdfjs-editor-highlight-button-label = Nabarmendu +pdfjs-highlight-floating-button1 = + .title = Nabarmendu + .aria-label = Nabarmendu +pdfjs-highlight-floating-button-label = Nabarmendu + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Kendu marrazkia +pdfjs-editor-remove-freetext-button = + .title = Kendu testua +pdfjs-editor-remove-stamp-button = + .title = Kendu irudia +pdfjs-editor-remove-highlight-button = + .title = Kendu nabarmentzea + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Kolorea +pdfjs-editor-free-text-size-input = Tamaina +pdfjs-editor-ink-color-input = Kolorea +pdfjs-editor-ink-thickness-input = Loditasuna +pdfjs-editor-ink-opacity-input = Opakutasuna +pdfjs-editor-stamp-add-image-button = + .title = Gehitu irudia +pdfjs-editor-stamp-add-image-button-label = Gehitu irudia +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Loditasuna +pdfjs-editor-free-highlight-thickness-title = + .title = Aldatu loditasuna testua ez beste elementuak nabarmentzean +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Testu-editorea + .default-content = Hasi idazten… +pdfjs-free-text = + .aria-label = Testu-editorea +pdfjs-free-text-default-content = Hasi idazten… +pdfjs-ink = + .aria-label = Marrazki-editorea +pdfjs-ink-canvas = + .aria-label = Erabiltzaileak sortutako irudia + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Testu alternatiboa +pdfjs-editor-alt-text-edit-button = + .aria-label = Editatu testu alternatiboa +pdfjs-editor-alt-text-edit-button-label = Editatu testu alternatiboa +pdfjs-editor-alt-text-dialog-label = Aukeratu aukera +pdfjs-editor-alt-text-dialog-description = Testu alternatiboak laguntzen du jendeak ezin duenean irudia ikusi edo ez denean kargatzen. +pdfjs-editor-alt-text-add-description-label = Gehitu azalpena +pdfjs-editor-alt-text-add-description-description = Saiatu idazten gaia, ezarpena edo ekintzak deskribatzen dituen esaldi 1 edo 2. +pdfjs-editor-alt-text-mark-decorative-label = Markatu apaingarri gisa +pdfjs-editor-alt-text-mark-decorative-description = Irudiak apaingarrientzat erabiltzen da, adibidez ertz edo ur-marketarako. +pdfjs-editor-alt-text-cancel-button = Utzi +pdfjs-editor-alt-text-save-button = Gorde +pdfjs-editor-alt-text-decorative-tooltip = Apaingarri gisa markatuta +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Adibidez, "gizon gaztea mahaian eserita dago bazkaltzeko" +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Testu alternatiboa + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Goiko ezkerreko izkina — aldatu tamaina +pdfjs-editor-resizer-label-top-middle = Goian erdian — aldatu tamaina +pdfjs-editor-resizer-label-top-right = Goiko eskuineko izkina — aldatu tamaina +pdfjs-editor-resizer-label-middle-right = Erdian eskuinean — aldatu tamaina +pdfjs-editor-resizer-label-bottom-right = Beheko eskuineko izkina — aldatu tamaina +pdfjs-editor-resizer-label-bottom-middle = Behean erdian — aldatu tamaina +pdfjs-editor-resizer-label-bottom-left = Beheko ezkerreko izkina — aldatu tamaina +pdfjs-editor-resizer-label-middle-left = Erdian ezkerrean — aldatu tamaina +pdfjs-editor-resizer-top-left = + .aria-label = Goiko ezkerreko izkina — aldatu tamaina +pdfjs-editor-resizer-top-middle = + .aria-label = Goian erdian — aldatu tamaina +pdfjs-editor-resizer-top-right = + .aria-label = Goiko eskuineko izkina — aldatu tamaina +pdfjs-editor-resizer-middle-right = + .aria-label = Erdian eskuinean — aldatu tamaina +pdfjs-editor-resizer-bottom-right = + .aria-label = Beheko eskuineko izkina — aldatu tamaina +pdfjs-editor-resizer-bottom-middle = + .aria-label = Behean erdian — aldatu tamaina +pdfjs-editor-resizer-bottom-left = + .aria-label = Beheko ezkerreko izkina — aldatu tamaina +pdfjs-editor-resizer-middle-left = + .aria-label = Erdian ezkerrean — aldatu tamaina + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Nabarmentze kolorea +pdfjs-editor-colorpicker-button = + .title = Aldatu kolorea +pdfjs-editor-colorpicker-dropdown = + .aria-label = Kolore-aukerak +pdfjs-editor-colorpicker-yellow = + .title = Horia +pdfjs-editor-colorpicker-green = + .title = Berdea +pdfjs-editor-colorpicker-blue = + .title = Urdina +pdfjs-editor-colorpicker-pink = + .title = Arrosa +pdfjs-editor-colorpicker-red = + .title = Gorria + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Erakutsi denak +pdfjs-editor-highlight-show-all-button = + .title = Erakutsi denak + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Editatu testu alternatiboa (irudiaren azalpena) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Gehitu testu alternatiboa (irudiaren azalpena) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Idatzi zure azalpena hemen… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Azalpen laburra irudia ikusi ezin duen jendearentzat edo irudia kargatu ezin denerako. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Testu alternatibo hau automatikoki sortu da eta okerra izan liteke. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Argibide gehiago +pdfjs-editor-new-alt-text-create-automatically-button-label = Sortu testu alternatiboa automatikoki +pdfjs-editor-new-alt-text-not-now-button = Une honetan ez +pdfjs-editor-new-alt-text-error-title = Ezin da testu alternatiboa automatikoki sortu +pdfjs-editor-new-alt-text-error-description = Idatzi zure testu alternatibo propioa edo saiatu berriro geroago. +pdfjs-editor-new-alt-text-error-close-button = Itxi +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Testu alternatiboaren AA modeloa deskargatzen ({ $totalSize }/{ $downloadedSize } MB) + .aria-valuetext = Testu alternatiboaren AA modeloa deskargatzen ({ $totalSize }/{ $downloadedSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Testu alternatiboa gehituta +pdfjs-editor-new-alt-text-added-button-label = Testu alternatiboa gehituta +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Testu alternatiboa falta da +pdfjs-editor-new-alt-text-missing-button-label = Testu alternatiboa falta da +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Berrikusi testu alternatiboa +pdfjs-editor-new-alt-text-to-review-button-label = Berrikusi testu alternatiboa +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automatikoki sortua: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Irudiaren testu alternatiboaren ezarpenak +pdfjs-image-alt-text-settings-button-label = Irudiaren testu alternatiboaren ezarpenak +pdfjs-editor-alt-text-settings-dialog-label = Irudiaren testu alternatiboaren ezarpenak +pdfjs-editor-alt-text-settings-automatic-title = Testu alternatibo automatikoa +pdfjs-editor-alt-text-settings-create-model-button-label = Sortu testu alternatiboa automatikoki +pdfjs-editor-alt-text-settings-create-model-description = Azalpenak iradokitzen ditu irudia ikusi ezin duen jendearentzat edo irudia kargatu ezin denerako. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Testu alternatiboaren AA modeloa ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Zure gailuan modu lokalean exekutatzen da eta zure datuak pribatu mantentzen dira. Testu alternatibo automatikorako beharrezkoa. +pdfjs-editor-alt-text-settings-delete-model-button = Ezabatu +pdfjs-editor-alt-text-settings-download-model-button = Deskargatu +pdfjs-editor-alt-text-settings-downloading-model-button = Deskargatzen… +pdfjs-editor-alt-text-settings-editor-title = Testu alternatiboaren editorea +pdfjs-editor-alt-text-settings-show-dialog-button-label = Erakutsi testu alternatiboa irudi bat gehitzean berehala +pdfjs-editor-alt-text-settings-show-dialog-description = Zure irudiek testu alternatiboa duela ziurtatzen laguntzen dizu. +pdfjs-editor-alt-text-settings-close-button = Itxi + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Nabarmentzea kenduta +pdfjs-editor-undo-bar-message-freetext = Testua kenduta +pdfjs-editor-undo-bar-message-ink = Marrazkia kenduta +pdfjs-editor-undo-bar-message-stamp = Irudia kenduta +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] Esku-ohar bat kenduta + *[other] { $count } esku-ohar kenduta + } +pdfjs-editor-undo-bar-undo-button = + .title = Desegin +pdfjs-editor-undo-bar-undo-button-label = Desegin +pdfjs-editor-undo-bar-close-button = + .title = Itxi +pdfjs-editor-undo-bar-close-button-label = Itxi diff --git a/public/assets/pdfjs/locale/fa/viewer.ftl b/public/assets/pdfjs/locale/fa/viewer.ftl new file mode 100755 index 0000000..4969209 --- /dev/null +++ b/public/assets/pdfjs/locale/fa/viewer.ftl @@ -0,0 +1,348 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = ØµÙØ­Ù‡Ù” قبلی +pdfjs-previous-button-label = قبلی +pdfjs-next-button = + .title = ØµÙØ­Ù‡Ù” بعدی +pdfjs-next-button-label = بعدی +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = ØµÙØ­Ù‡ +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = از { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber }از { $pagesCount }) +pdfjs-zoom-out-button = + .title = کوچک‌نمایی +pdfjs-zoom-out-button-label = کوچک‌نمایی +pdfjs-zoom-in-button = + .title = بزرگ‌نمایی +pdfjs-zoom-in-button-label = بزرگ‌نمایی +pdfjs-zoom-select = + .title = زوم +pdfjs-presentation-mode-button = + .title = تغییر به حالت ارائه +pdfjs-presentation-mode-button-label = حالت ارائه +pdfjs-open-file-button = + .title = باز کردن پرونده +pdfjs-open-file-button-label = باز کردن +pdfjs-print-button = + .title = چاپ +pdfjs-print-button-label = چاپ +pdfjs-save-button = + .title = ذخیره +pdfjs-save-button-label = ذخیره +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Ø¯Ø±ÛŒØ§ÙØª +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Ø¯Ø±ÛŒØ§ÙØª +pdfjs-bookmark-button = + .title = ØµÙØ­Ù‡ ÙØ¹Ù„ÛŒ (مشاهده نشانی اینترنتی از ØµÙØ­Ù‡ ÙØ¹Ù„ÛŒ) +pdfjs-bookmark-button-label = ØµÙØ­Ù‡ ÙØ¹Ù„ÛŒ + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = ابزارها +pdfjs-tools-button-label = ابزارها +pdfjs-first-page-button = + .title = برو به اولین ØµÙØ­Ù‡ +pdfjs-first-page-button-label = برو به اولین ØµÙØ­Ù‡ +pdfjs-last-page-button = + .title = برو به آخرین ØµÙØ­Ù‡ +pdfjs-last-page-button-label = برو به آخرین ØµÙØ­Ù‡ +pdfjs-page-rotate-cw-button = + .title = چرخش ساعتگرد +pdfjs-page-rotate-cw-button-label = چرخش ساعتگرد +pdfjs-page-rotate-ccw-button = + .title = چرخش پاد ساعتگرد +pdfjs-page-rotate-ccw-button-label = چرخش پاد ساعتگرد +pdfjs-cursor-text-select-tool-button = + .title = ÙØ¹Ø§Ù„ کردن ابزار٠انتخاب٠متن +pdfjs-cursor-text-select-tool-button-label = ابزار٠انتخاب٠متن +pdfjs-cursor-hand-tool-button = + .title = ÙØ¹Ø§Ù„ کردن ابزار٠دست +pdfjs-cursor-hand-tool-button-label = ابزار دست +pdfjs-scroll-page-button = + .title = Ø§Ø³ØªÙØ§Ø¯Ù‡ از پیمایش ØµÙØ­Ù‡ +pdfjs-scroll-page-button-label = پیمایش ØµÙØ­Ù‡ +pdfjs-scroll-vertical-button = + .title = Ø§Ø³ØªÙØ§Ø¯Ù‡ از پیمایش عمودی +pdfjs-scroll-vertical-button-label = پیمایش عمودی +pdfjs-scroll-horizontal-button = + .title = Ø§Ø³ØªÙØ§Ø¯Ù‡ از پیمایش اÙÙ‚ÛŒ +pdfjs-scroll-horizontal-button-label = پیمایش اÙÙ‚ÛŒ +pdfjs-spread-none-button = + .title = ØµÙØ­Ø§Øª پیوسته را یکی نکنید +pdfjs-spread-none-button-label = بدون ØµÙØ­Ø§Øª پیوسته + +## Document properties dialog + +pdfjs-document-properties-button = + .title = خصوصیات سند... +pdfjs-document-properties-button-label = خصوصیات سند... +pdfjs-document-properties-file-name = نام پرونده: +pdfjs-document-properties-file-size = حجم پرونده: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } کیلوبایت ({ $b } بایت) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } مگابایت ({ $b } بایت) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } کیلوبایت ({ $size_b } بایت) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } مگابایت ({ $size_b } بایت) +pdfjs-document-properties-title = عنوان: +pdfjs-document-properties-author = نویسنده: +pdfjs-document-properties-subject = موضوع: +pdfjs-document-properties-keywords = کلیدواژه‌ها: +pdfjs-document-properties-creation-date = تاریخ ایجاد: +pdfjs-document-properties-modification-date = تاریخ ویرایش: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }ØŒ { $time } +pdfjs-document-properties-creator = ایجاد کننده: +pdfjs-document-properties-producer = ایجاد کننده PDF: +pdfjs-document-properties-version = نسخه PDF: +pdfjs-document-properties-page-count = تعداد ØµÙØ­Ø§Øª: +pdfjs-document-properties-page-size = اندازه ØµÙØ­Ù‡: +pdfjs-document-properties-page-size-unit-inches = اینچ +pdfjs-document-properties-page-size-unit-millimeters = میلی‌متر +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = نامه +pdfjs-document-properties-page-size-name-legal = حقوقی + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +pdfjs-document-properties-linearized-yes = بله +pdfjs-document-properties-linearized-no = خیر +pdfjs-document-properties-close-button = بستن + +## Print + +pdfjs-print-progress-message = آماده سازی مدارک برای چاپ کردن… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = لغو +pdfjs-printing-not-supported = هشدار: قابلیت چاپ به‌طور کامل در این مرورگر پشتیبانی نمی‌شود. +pdfjs-printing-not-ready = اخطار: پرونده PDF بطور کامل بارگیری نشده Ùˆ امکان چاپ وجود ندارد. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = باز Ùˆ بسته کردن نوار کناری +pdfjs-toggle-sidebar-button-label = تغییرحالت نوارکناری +pdfjs-document-outline-button = + .title = نمایش رئوس مطالب مدارک(برای بازشدن/جمع شدن همه موارد دوبار کلیک کنید) +pdfjs-document-outline-button-label = طرح نوشتار +pdfjs-attachments-button = + .title = نمایش پیوست‌ها +pdfjs-attachments-button-label = پیوست‌ها +pdfjs-layers-button-label = لایه‌ها +pdfjs-thumbs-button = + .title = نمایش تصاویر بندانگشتی +pdfjs-thumbs-button-label = تصاویر بندانگشتی +pdfjs-findbar-button = + .title = جستجو در سند +pdfjs-findbar-button-label = پیدا کردن + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = ØµÙØ­Ù‡ { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = تصویر بند‌ انگشتی ØµÙØ­Ù‡ { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = پیدا کردن + .placeholder = پیدا کردن در سند… +pdfjs-find-previous-button = + .title = پیدا کردن رخداد قبلی عبارت +pdfjs-find-previous-button-label = قبلی +pdfjs-find-next-button = + .title = پیدا کردن رخداد بعدی عبارت +pdfjs-find-next-button-label = بعدی +pdfjs-find-highlight-checkbox = برجسته Ùˆ هایلایت کردن همه موارد +pdfjs-find-match-case-checkbox-label = تطبیق Ú©ÙˆÚ†Ú©ÛŒ Ùˆ بزرگی حرو٠+pdfjs-find-entire-word-checkbox-label = تمام کلمه‌ها +pdfjs-find-reached-top = به بالای ØµÙØ­Ù‡ رسیدیم، از پایین ادامه می‌دهیم +pdfjs-find-reached-bottom = به آخر ØµÙØ­Ù‡ رسیدیم، از بالا ادامه می‌دهیم +pdfjs-find-not-found = عبارت پیدا نشد + +## Predefined zoom values + +pdfjs-page-scale-width = عرض ØµÙØ­Ù‡ +pdfjs-page-scale-fit = اندازه کردن ØµÙØ­Ù‡ +pdfjs-page-scale-auto = بزرگنمایی خودکار +pdfjs-page-scale-actual = اندازه واقعی‌ +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = ØµÙØ­Ù‡Ù” { $page } + +## Loading indicator messages + +pdfjs-loading-error = هنگام بارگیری پرونده PDF خطایی رخ داد. +pdfjs-invalid-file-error = پرونده PDF نامعتبر یامعیوب می‌باشد. +pdfjs-missing-file-error = پرونده PDF ÛŒØ§ÙØª نشد. +pdfjs-unexpected-response-error = پاسخ پیش بینی نشده سرور +pdfjs-rendering-error = هنگام بارگیری ØµÙØ­Ù‡ خطایی رخ داد. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }ØŒ { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Annotation] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = جهت باز کردن پرونده PDF گذرواژه را وارد نمائید. +pdfjs-password-invalid = گذرواژه نامعتبر. Ù„Ø·ÙØ§ مجددا تلاش کنید. +pdfjs-password-ok-button = تأیید +pdfjs-password-cancel-button = لغو +pdfjs-web-fonts-disabled = Ùونت های تحت وب غیر ÙØ¹Ø§Ù„ شده اند: امکان Ø§Ø³ØªÙØ§Ø¯Ù‡ از نمایش دهنده داخلی PDF وجود ندارد. + +## Editing + +pdfjs-editor-free-text-button = + .title = متن +pdfjs-editor-free-text-button-label = متن +pdfjs-editor-ink-button = + .title = کشیدن +pdfjs-editor-ink-button-label = کشیدن +pdfjs-editor-stamp-button = + .title = Ø§ÙØ²ÙˆØ¯Ù† یا ویرایش تصاویر +pdfjs-editor-stamp-button-label = Ø§ÙØ²ÙˆØ¯Ù† یا ویرایش تصاویر +pdfjs-editor-highlight-button = + .title = برجسته کردن +pdfjs-editor-highlight-button-label = برجسته کردن +pdfjs-highlight-floating-button1 = + .title = برجسته کردن + .aria-label = برجسته کردن +pdfjs-highlight-floating-button-label = برجسته کردن + +## Remove button for the various kind of editor. + + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = رنگ +pdfjs-editor-free-text-size-input = اندازه +pdfjs-editor-ink-color-input = رنگ +pdfjs-editor-stamp-add-image-button = + .title = Ø§ÙØ²ÙˆØ¯Ù† تصویر +pdfjs-editor-stamp-add-image-button-label = Ø§ÙØ²ÙˆØ¯Ù† تصویر +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = ویرایشگر متن + .default-content = شروع به نوشتن کنید… +pdfjs-free-text = + .aria-label = ویرایشگر متن +pdfjs-free-text-default-content = شروع به نوشتن کنید… + +## Alt-text dialog + +pdfjs-editor-alt-text-add-description-label = Ø§ÙØ²ÙˆØ¯Ù† توضیحات +pdfjs-editor-alt-text-cancel-button = انصرا٠+pdfjs-editor-alt-text-save-button = ذخیره + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + + +## Color picker + +pdfjs-editor-colorpicker-button = + .title = تغییر رنگ +pdfjs-editor-colorpicker-dropdown = + .aria-label = انتخاب رنگ +pdfjs-editor-colorpicker-yellow = + .title = زرد +pdfjs-editor-colorpicker-green = + .title = سبز +pdfjs-editor-colorpicker-blue = + .title = آبی +pdfjs-editor-colorpicker-pink = + .title = صورتی +pdfjs-editor-colorpicker-red = + .title = قرمز + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = نمایش همه +pdfjs-editor-highlight-show-all-button = + .title = نمایش همه + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = بیشتر بدانید +pdfjs-editor-new-alt-text-not-now-button = اکنون نه +pdfjs-editor-new-alt-text-error-close-button = بستن + +## Image alt-text settings + +pdfjs-editor-alt-text-settings-delete-model-button = حذ٠+pdfjs-editor-alt-text-settings-download-model-button = Ø¯Ø±ÛŒØ§ÙØª +pdfjs-editor-alt-text-settings-downloading-model-button = در حال Ø¯Ø±ÛŒØ§ÙØªâ€¦ +pdfjs-editor-alt-text-settings-close-button = بستن diff --git a/public/assets/pdfjs/locale/ff/viewer.ftl b/public/assets/pdfjs/locale/ff/viewer.ftl new file mode 100755 index 0000000..d1419f5 --- /dev/null +++ b/public/assets/pdfjs/locale/ff/viewer.ftl @@ -0,0 +1,247 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Hello Æennungo +pdfjs-previous-button-label = ÆennuÉ—o +pdfjs-next-button = + .title = Hello faango +pdfjs-next-button-label = Yeeso +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Hello +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = e nder { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount }) +pdfjs-zoom-out-button = + .title = Lonngo WoÉ—É—a +pdfjs-zoom-out-button-label = Lonngo WoÉ—É—a +pdfjs-zoom-in-button = + .title = Lonngo Ara +pdfjs-zoom-in-button-label = Lonngo Ara +pdfjs-zoom-select = + .title = Lonngo +pdfjs-presentation-mode-button = + .title = Faytu to Presentation Mode +pdfjs-presentation-mode-button-label = Presentation Mode +pdfjs-open-file-button = + .title = Uddit Fiilde +pdfjs-open-file-button-label = Uddit +pdfjs-print-button = + .title = Winndito +pdfjs-print-button-label = Winndito + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = KuutorÉ—e +pdfjs-tools-button-label = KuutorÉ—e +pdfjs-first-page-button = + .title = Yah to hello adanngo +pdfjs-first-page-button-label = Yah to hello adanngo +pdfjs-last-page-button = + .title = Yah to hello wattindiingo +pdfjs-last-page-button-label = Yah to hello wattindiingo +pdfjs-page-rotate-cw-button = + .title = Yiiltu Faya Ñaamo +pdfjs-page-rotate-cw-button-label = Yiiltu Faya Ñaamo +pdfjs-page-rotate-ccw-button = + .title = Yiiltu Faya Nano +pdfjs-page-rotate-ccw-button-label = Yiiltu Faya Nano +pdfjs-cursor-text-select-tool-button = + .title = Gollin kaÉ“irgel cuÉ“irgel binndi +pdfjs-cursor-text-select-tool-button-label = KaÉ“irgel cuÉ“irgel binndi +pdfjs-cursor-hand-tool-button = + .title = Hurmin kuutorgal junngo +pdfjs-cursor-hand-tool-button-label = KaÉ“irgel junngo +pdfjs-scroll-vertical-button = + .title = Huutoro gorwitol daringol +pdfjs-scroll-vertical-button-label = Gorwitol daringol +pdfjs-scroll-horizontal-button = + .title = Huutoro gorwitol lelingol +pdfjs-scroll-horizontal-button-label = Gorwitol daringol +pdfjs-scroll-wrapped-button = + .title = Huutoro gorwitol coomingol +pdfjs-scroll-wrapped-button-label = Gorwitol coomingol +pdfjs-spread-none-button = + .title = Hoto tawtu kelle kelle +pdfjs-spread-none-button-label = Alaa Spreads +pdfjs-spread-odd-button = + .title = Tawtu kelle puÉ—É—ortooÉ—e kelle teelÉ—e +pdfjs-spread-odd-button-label = Kelle teelÉ—e +pdfjs-spread-even-button = + .title = Tawtu É—ereeji kelle puÉ—É—oriiÉ—i kelle teeltuÉ—e +pdfjs-spread-even-button-label = Kelle teeltuÉ—e + +## Document properties dialog + +pdfjs-document-properties-button = + .title = KeeroraaÉ—i Winndannde… +pdfjs-document-properties-button-label = KeeroraaÉ—i Winndannde… +pdfjs-document-properties-file-name = Innde fiilde: +pdfjs-document-properties-file-size = Æetol fiilde: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bite) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bite) +pdfjs-document-properties-title = Tiitoonde: +pdfjs-document-properties-author = BinnduÉ—o: +pdfjs-document-properties-subject = Toɓɓere: +pdfjs-document-properties-keywords = Kelmekele jiytirÉ—e: +pdfjs-document-properties-creation-date = Ñalnde Sosaa: +pdfjs-document-properties-modification-date = Ñalnde Waylaa: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = CosÉ—o: +pdfjs-document-properties-producer = PaggiiÉ—o PDF: +pdfjs-document-properties-version = Yamre PDF: +pdfjs-document-properties-page-count = Limoore Kelle: +pdfjs-document-properties-page-size = Æeto Hello: +pdfjs-document-properties-page-size-unit-inches = nder +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = dariingo +pdfjs-document-properties-page-size-orientation-landscape = wertiingo +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Æataake +pdfjs-document-properties-page-size-name-legal = Laawol + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = ÆŠisngo geese yaawngo: +pdfjs-document-properties-linearized-yes = Eey +pdfjs-document-properties-linearized-no = Alaa +pdfjs-document-properties-close-button = Uddu + +## Print + +pdfjs-print-progress-message = Nana heboo winnditaade fiilannde… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Haaytu +pdfjs-printing-not-supported = Reentino: Winnditagol tammbitaaka no feewi e ndee wanngorde. +pdfjs-printing-not-ready = Reentino: PDF oo loowaaki haa timmi ngam winnditagol. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Toggilo Palal Sawndo +pdfjs-toggle-sidebar-button-label = Toggilo Palal Sawndo +pdfjs-document-outline-button = + .title = Hollu Ƴiyal Fiilannde (dobdobo ngam wertude/taggude teme fof) +pdfjs-document-outline-button-label = Toɓɓe Fiilannde +pdfjs-attachments-button = + .title = Hollu ÆŠisanÉ—e +pdfjs-attachments-button-label = ÆŠisanÉ—e +pdfjs-thumbs-button = + .title = Hollu DooÉ“e +pdfjs-thumbs-button-label = DooÉ“e +pdfjs-findbar-button = + .title = Yiylo e fiilannde +pdfjs-findbar-button-label = Yiytu + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Hello { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = DooÉ“re Hello { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Yiytu + .placeholder = Yiylo nder dokimaa +pdfjs-find-previous-button = + .title = Yiylo cilol É“ennugol konngol ngol +pdfjs-find-previous-button-label = ÆennuÉ—o +pdfjs-find-next-button = + .title = Yiylo cilol garowol konngol ngol +pdfjs-find-next-button-label = Yeeso +pdfjs-find-highlight-checkbox = Jalbin fof +pdfjs-find-match-case-checkbox-label = JaaÉ“nu darnde +pdfjs-find-entire-word-checkbox-label = Kelme timmuÉ—e tan +pdfjs-find-reached-top = HeÉ“ii fuÉ—É—orde fiilannde, jokku faya les +pdfjs-find-reached-bottom = HeÉ“ii hoore fiilannde, jokku faya les +pdfjs-find-not-found = Konngi njiyataa + +## Predefined zoom values + +pdfjs-page-scale-width = Njaajeendi Hello +pdfjs-page-scale-fit = KeÆ´eendi Hello +pdfjs-page-scale-auto = Loongorde Jaajol +pdfjs-page-scale-actual = Æetol Jaati +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = Juumre waÉ—ii tuma nde loowata PDF oo. +pdfjs-invalid-file-error = Fiilde PDF moÆ´Æ´aani walla jiibii. +pdfjs-missing-file-error = Fiilde PDF ena Å‹akki. +pdfjs-unexpected-response-error = Jaabtol sarworde tijjinooka. +pdfjs-rendering-error = Juumre waÉ—ii tuma nde yoÅ‹kittoo hello. + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Siiftannde] + +## Password + +pdfjs-password-label = Naatu finnde ngam uddite ndee fiilde PDF. +pdfjs-password-invalid = Finnde moÆ´Æ´aani. TiiÉ—no eto kadi. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Haaytu +pdfjs-web-fonts-disabled = Ponte geese ko daaÆ´aaÉ—e: horiima huutoraade ponte PDF coomtoraaÉ—e. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/fi/viewer.ftl b/public/assets/pdfjs/locale/fi/viewer.ftl new file mode 100755 index 0000000..0819d0e --- /dev/null +++ b/public/assets/pdfjs/locale/fi/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Edellinen sivu +pdfjs-previous-button-label = Edellinen +pdfjs-next-button = + .title = Seuraava sivu +pdfjs-next-button-label = Seuraava +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Sivu +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = / { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } / { $pagesCount }) +pdfjs-zoom-out-button = + .title = Loitonna +pdfjs-zoom-out-button-label = Loitonna +pdfjs-zoom-in-button = + .title = Lähennä +pdfjs-zoom-in-button-label = Lähennä +pdfjs-zoom-select = + .title = Suurennus +pdfjs-presentation-mode-button = + .title = Siirry esitystilaan +pdfjs-presentation-mode-button-label = Esitystila +pdfjs-open-file-button = + .title = Avaa tiedosto +pdfjs-open-file-button-label = Avaa +pdfjs-print-button = + .title = Tulosta +pdfjs-print-button-label = Tulosta +pdfjs-save-button = + .title = Tallenna +pdfjs-save-button-label = Tallenna +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Lataa +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Lataa +pdfjs-bookmark-button = + .title = Nykyinen sivu (Näytä URL-osoite nykyiseltä sivulta) +pdfjs-bookmark-button-label = Nykyinen sivu + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Tools +pdfjs-tools-button-label = Tools +pdfjs-first-page-button = + .title = Siirry ensimmäiselle sivulle +pdfjs-first-page-button-label = Siirry ensimmäiselle sivulle +pdfjs-last-page-button = + .title = Siirry viimeiselle sivulle +pdfjs-last-page-button-label = Siirry viimeiselle sivulle +pdfjs-page-rotate-cw-button = + .title = Kierrä oikealle +pdfjs-page-rotate-cw-button-label = Kierrä oikealle +pdfjs-page-rotate-ccw-button = + .title = Kierrä vasemmalle +pdfjs-page-rotate-ccw-button-label = Kierrä vasemmalle +pdfjs-cursor-text-select-tool-button = + .title = Käytä tekstinvalintatyökalua +pdfjs-cursor-text-select-tool-button-label = Tekstinvalintatyökalu +pdfjs-cursor-hand-tool-button = + .title = Käytä käsityökalua +pdfjs-cursor-hand-tool-button-label = Käsityökalu +pdfjs-scroll-page-button = + .title = Käytä sivun vieritystä +pdfjs-scroll-page-button-label = Sivun vieritys +pdfjs-scroll-vertical-button = + .title = Käytä pystysuuntaista vieritystä +pdfjs-scroll-vertical-button-label = Pystysuuntainen vieritys +pdfjs-scroll-horizontal-button = + .title = Käytä vaakasuuntaista vieritystä +pdfjs-scroll-horizontal-button-label = Vaakasuuntainen vieritys +pdfjs-scroll-wrapped-button = + .title = Käytä rivittyvää vieritystä +pdfjs-scroll-wrapped-button-label = Rivittyvä vieritys +pdfjs-spread-none-button = + .title = Älä yhdistä sivuja aukeamiksi +pdfjs-spread-none-button-label = Ei aukeamia +pdfjs-spread-odd-button = + .title = Yhdistä sivut aukeamiksi alkaen parittomalta sivulta +pdfjs-spread-odd-button-label = Parittomalta alkavat aukeamat +pdfjs-spread-even-button = + .title = Yhdistä sivut aukeamiksi alkaen parilliselta sivulta +pdfjs-spread-even-button-label = Parilliselta alkavat aukeamat + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Dokumentin ominaisuudet… +pdfjs-document-properties-button-label = Dokumentin ominaisuudet… +pdfjs-document-properties-file-name = Tiedoston nimi: +pdfjs-document-properties-file-size = Tiedoston koko: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kt ({ $b } tavua) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } Mt ({ $b } tavua) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } kt ({ $size_b } tavua) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } Mt ({ $size_b } tavua) +pdfjs-document-properties-title = Otsikko: +pdfjs-document-properties-author = Tekijä: +pdfjs-document-properties-subject = Aihe: +pdfjs-document-properties-keywords = Avainsanat: +pdfjs-document-properties-creation-date = Luomispäivämäärä: +pdfjs-document-properties-modification-date = Muokkauspäivämäärä: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Luoja: +pdfjs-document-properties-producer = PDF-tuottaja: +pdfjs-document-properties-version = PDF-versio: +pdfjs-document-properties-page-count = Sivujen määrä: +pdfjs-document-properties-page-size = Sivun koko: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = pysty +pdfjs-document-properties-page-size-orientation-landscape = vaaka +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Nopea web-katselu: +pdfjs-document-properties-linearized-yes = Kyllä +pdfjs-document-properties-linearized-no = Ei +pdfjs-document-properties-close-button = Sulje + +## Print + +pdfjs-print-progress-message = Valmistellaan dokumenttia tulostamista varten… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress } % +pdfjs-print-progress-close-button = Peruuta +pdfjs-printing-not-supported = Varoitus: Selain ei tue kaikkia tulostustapoja. +pdfjs-printing-not-ready = Varoitus: PDF-tiedosto ei ole vielä latautunut kokonaan, eikä sitä voi vielä tulostaa. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Näytä/piilota sivupaneeli +pdfjs-toggle-sidebar-notification-button = + .title = Näytä/piilota sivupaneeli (dokumentissa on sisällys/liitteitä/tasoja) +pdfjs-toggle-sidebar-button-label = Näytä/piilota sivupaneeli +pdfjs-document-outline-button = + .title = Näytä dokumentin sisällys (laajenna tai kutista kohdat kaksoisnapsauttamalla) +pdfjs-document-outline-button-label = Dokumentin sisällys +pdfjs-attachments-button = + .title = Näytä liitteet +pdfjs-attachments-button-label = Liitteet +pdfjs-layers-button = + .title = Näytä tasot (kaksoisnapsauta palauttaaksesi kaikki tasot oletustilaan) +pdfjs-layers-button-label = Tasot +pdfjs-thumbs-button = + .title = Näytä pienoiskuvat +pdfjs-thumbs-button-label = Pienoiskuvat +pdfjs-current-outline-item-button = + .title = Etsi nykyinen sisällyksen kohta +pdfjs-current-outline-item-button-label = Nykyinen sisällyksen kohta +pdfjs-findbar-button = + .title = Etsi dokumentista +pdfjs-findbar-button-label = Etsi +pdfjs-additional-layers = Lisätasot + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Sivu { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Pienoiskuva sivusta { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Etsi + .placeholder = Etsi dokumentista… +pdfjs-find-previous-button = + .title = Etsi hakusanan edellinen osuma +pdfjs-find-previous-button-label = Edellinen +pdfjs-find-next-button = + .title = Etsi hakusanan seuraava osuma +pdfjs-find-next-button-label = Seuraava +pdfjs-find-highlight-checkbox = Korosta kaikki +pdfjs-find-match-case-checkbox-label = Huomioi kirjainkoko +pdfjs-find-match-diacritics-checkbox-label = Erota tarkkeet +pdfjs-find-entire-word-checkbox-label = Kokonaiset sanat +pdfjs-find-reached-top = Päästiin dokumentin alkuun, jatketaan lopusta +pdfjs-find-reached-bottom = Päästiin dokumentin loppuun, jatketaan alusta +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } / { $total } osuma + *[other] { $current } / { $total } osumaa + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Yli { $limit } osuma + *[other] Yli { $limit } osumaa + } +pdfjs-find-not-found = Hakusanaa ei löytynyt + +## Predefined zoom values + +pdfjs-page-scale-width = Sivun leveys +pdfjs-page-scale-fit = Koko sivu +pdfjs-page-scale-auto = Automaattinen suurennus +pdfjs-page-scale-actual = Todellinen koko +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale } % + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Sivu { $page } + +## Loading indicator messages + +pdfjs-loading-error = Tapahtui virhe ladattaessa PDF-tiedostoa. +pdfjs-invalid-file-error = Virheellinen tai vioittunut PDF-tiedosto. +pdfjs-missing-file-error = Puuttuva PDF-tiedosto. +pdfjs-unexpected-response-error = Odottamaton vastaus palvelimelta. +pdfjs-rendering-error = Tapahtui virhe piirrettäessä sivua. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type }-merkintä] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Kirjoita PDF-tiedoston salasana. +pdfjs-password-invalid = Virheellinen salasana. Yritä uudestaan. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Peruuta +pdfjs-web-fonts-disabled = Verkkosivujen omat kirjasinlajit on estetty: ei voida käyttää upotettuja PDF-kirjasinlajeja. + +## Editing + +pdfjs-editor-free-text-button = + .title = Teksti +pdfjs-editor-free-text-button-label = Teksti +pdfjs-editor-ink-button = + .title = Piirros +pdfjs-editor-ink-button-label = Piirros +pdfjs-editor-stamp-button = + .title = Lisää tai muokkaa kuvia +pdfjs-editor-stamp-button-label = Lisää tai muokkaa kuvia +pdfjs-editor-highlight-button = + .title = Korostus +pdfjs-editor-highlight-button-label = Korostus +pdfjs-highlight-floating-button1 = + .title = Korostus + .aria-label = Korostus +pdfjs-highlight-floating-button-label = Korostus + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Poista piirros +pdfjs-editor-remove-freetext-button = + .title = Poista teksti +pdfjs-editor-remove-stamp-button = + .title = Poista kuva +pdfjs-editor-remove-highlight-button = + .title = Poista korostus + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Väri +pdfjs-editor-free-text-size-input = Koko +pdfjs-editor-ink-color-input = Väri +pdfjs-editor-ink-thickness-input = Paksuus +pdfjs-editor-ink-opacity-input = Peittävyys +pdfjs-editor-stamp-add-image-button = + .title = Lisää kuva +pdfjs-editor-stamp-add-image-button-label = Lisää kuva +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Paksuus +pdfjs-editor-free-highlight-thickness-title = + .title = Muuta paksuutta korostaessasi muita kohteita kuin tekstiä +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Tekstimuokkain + .default-content = Aloita kirjoittaminen… +pdfjs-free-text = + .aria-label = Tekstimuokkain +pdfjs-free-text-default-content = Aloita kirjoittaminen… +pdfjs-ink = + .aria-label = Piirrustusmuokkain +pdfjs-ink-canvas = + .aria-label = Käyttäjän luoma kuva + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Vaihtoehtoinen teksti +pdfjs-editor-alt-text-edit-button = + .aria-label = Muokkaa vaihtoehtoista tekstiä +pdfjs-editor-alt-text-edit-button-label = Muokkaa vaihtoehtoista tekstiä +pdfjs-editor-alt-text-dialog-label = Valitse vaihtoehto +pdfjs-editor-alt-text-dialog-description = Vaihtoehtoinen teksti ("alt-teksti") auttaa ihmisiä, jotka eivät näe kuvaa tai kun kuva ei lataudu. +pdfjs-editor-alt-text-add-description-label = Lisää kuvaus +pdfjs-editor-alt-text-add-description-description = Pyri 1-2 lauseeseen, jotka kuvaavat aihetta, ympäristöä tai toimintaa. +pdfjs-editor-alt-text-mark-decorative-label = Merkitse koristeelliseksi +pdfjs-editor-alt-text-mark-decorative-description = Tätä käytetään koristekuville, kuten reunuksille tai vesileimoille. +pdfjs-editor-alt-text-cancel-button = Peruuta +pdfjs-editor-alt-text-save-button = Tallenna +pdfjs-editor-alt-text-decorative-tooltip = Merkitty koristeelliseksi +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Esimerkiksi "Nuori mies istuu pöytään syömään aterian" +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Vaihtoehtoinen teksti + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Vasen yläkulma - muuta kokoa +pdfjs-editor-resizer-label-top-middle = Ylhäällä keskellä - muuta kokoa +pdfjs-editor-resizer-label-top-right = Oikea yläkulma - muuta kokoa +pdfjs-editor-resizer-label-middle-right = Keskellä oikealla - muuta kokoa +pdfjs-editor-resizer-label-bottom-right = Oikea alakulma - muuta kokoa +pdfjs-editor-resizer-label-bottom-middle = Alhaalla keskellä - muuta kokoa +pdfjs-editor-resizer-label-bottom-left = Vasen alakulma - muuta kokoa +pdfjs-editor-resizer-label-middle-left = Keskellä vasemmalla - muuta kokoa +pdfjs-editor-resizer-top-left = + .aria-label = Vasen yläkulma - muuta kokoa +pdfjs-editor-resizer-top-middle = + .aria-label = Ylhäällä keskellä - muuta kokoa +pdfjs-editor-resizer-top-right = + .aria-label = Oikea yläkulma - muuta kokoa +pdfjs-editor-resizer-middle-right = + .aria-label = Keskellä oikealla - muuta kokoa +pdfjs-editor-resizer-bottom-right = + .aria-label = Oikea alakulma - muuta kokoa +pdfjs-editor-resizer-bottom-middle = + .aria-label = Alhaalla keskellä - muuta kokoa +pdfjs-editor-resizer-bottom-left = + .aria-label = Vasen alakulma - muuta kokoa +pdfjs-editor-resizer-middle-left = + .aria-label = Keskellä vasemmalla - muuta kokoa + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Korostusväri +pdfjs-editor-colorpicker-button = + .title = Vaihda väri +pdfjs-editor-colorpicker-dropdown = + .aria-label = Värivalinnat +pdfjs-editor-colorpicker-yellow = + .title = Keltainen +pdfjs-editor-colorpicker-green = + .title = Vihreä +pdfjs-editor-colorpicker-blue = + .title = Sininen +pdfjs-editor-colorpicker-pink = + .title = Pinkki +pdfjs-editor-colorpicker-red = + .title = Punainen + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Näytä kaikki +pdfjs-editor-highlight-show-all-button = + .title = Näytä kaikki + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Muokkaa vaihtoehtoista tekstiä (kuvan kuvaus) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Lisää vaihtoehtoinen teksti (kuvan kuvaus) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Kirjoita kuvaus tähän… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Lyhyt kuvaus ihmisille, jotka eivät näe kuvaa tai kun kuva ei lataudu. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Tämä vaihtoehtoinen teksti luotiin automaattisesti, ja se voi olla epätarkka. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Lue lisää +pdfjs-editor-new-alt-text-create-automatically-button-label = Luo vaihtoehtoinen teksti automaattisesti +pdfjs-editor-new-alt-text-not-now-button = Ei nyt +pdfjs-editor-new-alt-text-error-title = Vaihtoehtotekstiä ei voitu luoda automaattisesti +pdfjs-editor-new-alt-text-error-description = Kirjoita oma vaihtoehtoinen teksti tai yritä myöhemmin uudelleen. +pdfjs-editor-new-alt-text-error-close-button = Sulje +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Ladataan vaihtoehtoisen tekstin tekoälymallia ({ $downloadedSize } / { $totalSize } Mt) + .aria-valuetext = Ladataan vaihtoehtoisen tekstin tekoälymallia ({ $downloadedSize } / { $totalSize } Mt) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Vaihtoehtoinen teksti lisätty +pdfjs-editor-new-alt-text-added-button-label = Vaihtoehtoinen teksti lisätty +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Vaihtoehtoinen teksti puuttuu +pdfjs-editor-new-alt-text-missing-button-label = Vaihtoehtoinen teksti puuttuu +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Tarkista vaihtoehtoinen teksti +pdfjs-editor-new-alt-text-to-review-button-label = Tarkista vaihtoehtoinen teksti +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Luotu automaattisesti: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Kuvan vaihtoehtoisen tekstin asetukset +pdfjs-image-alt-text-settings-button-label = Kuvan vaihtoehtoisen tekstin asetukset +pdfjs-editor-alt-text-settings-dialog-label = Kuvan vaihtoehtoisen tekstin asetukset +pdfjs-editor-alt-text-settings-automatic-title = Automaattinen vaihtoehtoinen teksti +pdfjs-editor-alt-text-settings-create-model-button-label = Luo vaihtoehtoinen teksti automaattisesti +pdfjs-editor-alt-text-settings-create-model-description = Ehdottaa kuvauksia, jotka auttavat ihmisiä, jotka eivät näe kuvaa tai kun kuva ei lataudu. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Vaihtoehtoisen tekstin tekoälymalli ({ $totalSize } Mt) +pdfjs-editor-alt-text-settings-ai-model-description = Toimii paikallisesti laitteellasi, joten tietosi pysyvät yksityisinä. Vaadittu automaattiselle vaihtoehtoiselle tekstille. +pdfjs-editor-alt-text-settings-delete-model-button = Poista +pdfjs-editor-alt-text-settings-download-model-button = Lataa +pdfjs-editor-alt-text-settings-downloading-model-button = Ladataan… +pdfjs-editor-alt-text-settings-editor-title = Vaihtoehtoisen tekstin muokkain +pdfjs-editor-alt-text-settings-show-dialog-button-label = Näytä vaihtoehtoisen tekstin muokkain heti, kun lisäät kuvan +pdfjs-editor-alt-text-settings-show-dialog-description = Auttaa varmistamaan, että kaikissa kuvissasi on vaihtoehtoinen teksti. +pdfjs-editor-alt-text-settings-close-button = Sulje + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Korostus poistettu +pdfjs-editor-undo-bar-message-freetext = Teksti poistettu +pdfjs-editor-undo-bar-message-ink = Piirustus poistettu +pdfjs-editor-undo-bar-message-stamp = Kuva poistettu +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } merkintä poistettu + *[other] { $count } merkintää poistettu + } +pdfjs-editor-undo-bar-undo-button = + .title = Kumoa +pdfjs-editor-undo-bar-undo-button-label = Kumoa +pdfjs-editor-undo-bar-close-button = + .title = Sulje +pdfjs-editor-undo-bar-close-button-label = Sulje diff --git a/public/assets/pdfjs/locale/fr/viewer.ftl b/public/assets/pdfjs/locale/fr/viewer.ftl new file mode 100755 index 0000000..d0a778f --- /dev/null +++ b/public/assets/pdfjs/locale/fr/viewer.ftl @@ -0,0 +1,511 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Page précédente +pdfjs-previous-button-label = Précédent +pdfjs-next-button = + .title = Page suivante +pdfjs-next-button-label = Suivant +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Page +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = sur { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } sur { $pagesCount }) +pdfjs-zoom-out-button = + .title = Zoom arrière +pdfjs-zoom-out-button-label = Zoom arrière +pdfjs-zoom-in-button = + .title = Zoom avant +pdfjs-zoom-in-button-label = Zoom avant +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Basculer en mode présentation +pdfjs-presentation-mode-button-label = Mode présentation +pdfjs-open-file-button = + .title = Ouvrir le fichier +pdfjs-open-file-button-label = Ouvrir le fichier +pdfjs-print-button = + .title = Imprimer +pdfjs-print-button-label = Imprimer +pdfjs-save-button = + .title = Enregistrer +pdfjs-save-button-label = Enregistrer +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Télécharger +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Télécharger +pdfjs-bookmark-button = + .title = Page courante (montrer l’adresse de la page courante) +pdfjs-bookmark-button-label = Page courante + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Outils +pdfjs-tools-button-label = Outils +pdfjs-first-page-button = + .title = Aller à la première page +pdfjs-first-page-button-label = Aller à la première page +pdfjs-last-page-button = + .title = Aller à la dernière page +pdfjs-last-page-button-label = Aller à la dernière page +pdfjs-page-rotate-cw-button = + .title = Rotation horaire +pdfjs-page-rotate-cw-button-label = Rotation horaire +pdfjs-page-rotate-ccw-button = + .title = Rotation antihoraire +pdfjs-page-rotate-ccw-button-label = Rotation antihoraire +pdfjs-cursor-text-select-tool-button = + .title = Activer l’outil de sélection de texte +pdfjs-cursor-text-select-tool-button-label = Outil de sélection de texte +pdfjs-cursor-hand-tool-button = + .title = Activer l’outil main +pdfjs-cursor-hand-tool-button-label = Outil main +pdfjs-scroll-page-button = + .title = Utiliser le défilement par page +pdfjs-scroll-page-button-label = Défilement par page +pdfjs-scroll-vertical-button = + .title = Utiliser le défilement vertical +pdfjs-scroll-vertical-button-label = Défilement vertical +pdfjs-scroll-horizontal-button = + .title = Utiliser le défilement horizontal +pdfjs-scroll-horizontal-button-label = Défilement horizontal +pdfjs-scroll-wrapped-button = + .title = Utiliser le défilement par bloc +pdfjs-scroll-wrapped-button-label = Défilement par bloc +pdfjs-spread-none-button = + .title = Ne pas afficher les pages deux à deux +pdfjs-spread-none-button-label = Pas de double affichage +pdfjs-spread-odd-button = + .title = Afficher les pages par deux, impaires à gauche +pdfjs-spread-odd-button-label = Doubles pages, impaires à gauche +pdfjs-spread-even-button = + .title = Afficher les pages par deux, paires à gauche +pdfjs-spread-even-button-label = Doubles pages, paires à gauche + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Propriétés du document… +pdfjs-document-properties-button-label = Propriétés du document… +pdfjs-document-properties-file-name = Nom du fichier : +pdfjs-document-properties-file-size = Taille du fichier : +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } Ko ({ $b } octets) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } Mo ({ $b } octets) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } Ko ({ $size_b } octets) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } Mo ({ $size_b } octets) +pdfjs-document-properties-title = Titre : +pdfjs-document-properties-author = Auteur : +pdfjs-document-properties-subject = Sujet : +pdfjs-document-properties-keywords = Mots-clés : +pdfjs-document-properties-creation-date = Date de création : +pdfjs-document-properties-modification-date = Modifié le : +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date } à { $time } +pdfjs-document-properties-creator = Créé par : +pdfjs-document-properties-producer = Outil de conversion PDF : +pdfjs-document-properties-version = Version PDF : +pdfjs-document-properties-page-count = Nombre de pages : +pdfjs-document-properties-page-size = Taille de la page : +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = portrait +pdfjs-document-properties-page-size-orientation-landscape = paysage +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = lettre +pdfjs-document-properties-page-size-name-legal = document juridique + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Affichage rapide des pages web : +pdfjs-document-properties-linearized-yes = Oui +pdfjs-document-properties-linearized-no = Non +pdfjs-document-properties-close-button = Fermer + +## Print + +pdfjs-print-progress-message = Préparation du document pour l’impression… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress } % +pdfjs-print-progress-close-button = Annuler +pdfjs-printing-not-supported = Attention : l’impression n’est pas totalement prise en charge par ce navigateur. +pdfjs-printing-not-ready = Attention : le PDF n’est pas entièrement chargé pour pouvoir l’imprimer. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Afficher/Masquer le panneau latéral +pdfjs-toggle-sidebar-notification-button = + .title = Afficher/Masquer le panneau latéral (le document contient des signets/pièces jointes/calques) +pdfjs-toggle-sidebar-button-label = Afficher/Masquer le panneau latéral +pdfjs-document-outline-button = + .title = Afficher les signets du document (double-cliquer pour développer/réduire tous les éléments) +pdfjs-document-outline-button-label = Signets du document +pdfjs-attachments-button = + .title = Afficher les pièces jointes +pdfjs-attachments-button-label = Pièces jointes +pdfjs-layers-button = + .title = Afficher les calques (double-cliquer pour réinitialiser tous les calques à l’état par défaut) +pdfjs-layers-button-label = Calques +pdfjs-thumbs-button = + .title = Afficher les vignettes +pdfjs-thumbs-button-label = Vignettes +pdfjs-current-outline-item-button = + .title = Trouver l’élément de plan actuel +pdfjs-current-outline-item-button-label = Élément de plan actuel +pdfjs-findbar-button = + .title = Rechercher dans le document +pdfjs-findbar-button-label = Rechercher +pdfjs-additional-layers = Calques additionnels + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Page { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Vignette de la page { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Rechercher + .placeholder = Rechercher dans le document… +pdfjs-find-previous-button = + .title = Trouver l’occurrence précédente de l’expression +pdfjs-find-previous-button-label = Précédent +pdfjs-find-next-button = + .title = Trouver la prochaine occurrence de l’expression +pdfjs-find-next-button-label = Suivant +pdfjs-find-highlight-checkbox = Tout surligner +pdfjs-find-match-case-checkbox-label = Respecter la casse +pdfjs-find-match-diacritics-checkbox-label = Respecter les accents et diacritiques +pdfjs-find-entire-word-checkbox-label = Mots entiers +pdfjs-find-reached-top = Haut de la page atteint, poursuite depuis la fin +pdfjs-find-reached-bottom = Bas de la page atteint, poursuite au début +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = Occurrence { $current } sur { $total } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Plus d’{ $limit } occurrence + *[other] Plus de { $limit } occurrences + } +pdfjs-find-not-found = Expression non trouvée + +## Predefined zoom values + +pdfjs-page-scale-width = Pleine largeur +pdfjs-page-scale-fit = Page entière +pdfjs-page-scale-auto = Zoom automatique +pdfjs-page-scale-actual = Taille réelle +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale } % + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Page { $page } + +## Loading indicator messages + +pdfjs-loading-error = Une erreur s’est produite lors du chargement du fichier PDF. +pdfjs-invalid-file-error = Fichier PDF invalide ou corrompu. +pdfjs-missing-file-error = Fichier PDF manquant. +pdfjs-unexpected-response-error = Réponse inattendue du serveur. +pdfjs-rendering-error = Une erreur s’est produite lors de l’affichage de la page. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date } à { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Annotation { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Veuillez saisir le mot de passe pour ouvrir ce fichier PDF. +pdfjs-password-invalid = Mot de passe incorrect. Veuillez réessayer. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Annuler +pdfjs-web-fonts-disabled = Les polices web sont désactivées : impossible d’utiliser les polices intégrées au PDF. + +## Editing + +pdfjs-editor-free-text-button = + .title = Texte +pdfjs-editor-free-text-button-label = Texte +pdfjs-editor-ink-button = + .title = Dessiner +pdfjs-editor-ink-button-label = Dessiner +pdfjs-editor-stamp-button = + .title = Ajouter ou modifier des images +pdfjs-editor-stamp-button-label = Ajouter ou modifier des images +pdfjs-editor-highlight-button = + .title = Surligner +pdfjs-editor-highlight-button-label = Surligner +pdfjs-highlight-floating-button1 = + .title = Surligner + .aria-label = Surligner +pdfjs-highlight-floating-button-label = Surligner + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Supprimer le dessin +pdfjs-editor-remove-freetext-button = + .title = Supprimer le texte +pdfjs-editor-remove-stamp-button = + .title = Supprimer l’image +pdfjs-editor-remove-highlight-button = + .title = Supprimer le surlignage + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Couleur +pdfjs-editor-free-text-size-input = Taille +pdfjs-editor-ink-color-input = Couleur +pdfjs-editor-ink-thickness-input = Épaisseur +pdfjs-editor-ink-opacity-input = Opacité +pdfjs-editor-stamp-add-image-button = + .title = Ajouter une image +pdfjs-editor-stamp-add-image-button-label = Ajouter une image +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Épaisseur +pdfjs-editor-free-highlight-thickness-title = + .title = Modifier l’épaisseur pour le surlignage d’éléments non textuels +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Éditeur de texte + .default-content = Commencez à écrire… +pdfjs-free-text = + .aria-label = Éditeur de texte +pdfjs-free-text-default-content = Commencer à écrire… +pdfjs-ink = + .aria-label = Éditeur de dessin +pdfjs-ink-canvas = + .aria-label = Image créée par l’utilisateur·trice + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Texte alternatif +pdfjs-editor-alt-text-edit-button = + .aria-label = Modifier le texte alternatif +pdfjs-editor-alt-text-edit-button-label = Modifier le texte alternatif +pdfjs-editor-alt-text-dialog-label = Sélectionnez une option +pdfjs-editor-alt-text-dialog-description = Le texte alternatif est utile lorsque des personnes ne peuvent pas voir l’image ou que l’image ne se charge pas. +pdfjs-editor-alt-text-add-description-label = Ajouter une description +pdfjs-editor-alt-text-add-description-description = Il est conseillé de rédiger une ou deux phrases décrivant le sujet, le cadre ou les actions. +pdfjs-editor-alt-text-mark-decorative-label = Marquer comme décorative +pdfjs-editor-alt-text-mark-decorative-description = Cette option est utilisée pour les images décoratives, comme les bordures ou les filigranes. +pdfjs-editor-alt-text-cancel-button = Annuler +pdfjs-editor-alt-text-save-button = Enregistrer +pdfjs-editor-alt-text-decorative-tooltip = Marquée comme décorative +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Par exemple, « Un jeune homme est assis à une table pour prendre un repas » +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Texte alternatif + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Coin supérieur gauche — redimensionner +pdfjs-editor-resizer-label-top-middle = Milieu haut — redimensionner +pdfjs-editor-resizer-label-top-right = Coin supérieur droit — redimensionner +pdfjs-editor-resizer-label-middle-right = Milieu droit — redimensionner +pdfjs-editor-resizer-label-bottom-right = Coin inférieur droit — redimensionner +pdfjs-editor-resizer-label-bottom-middle = Centre bas — redimensionner +pdfjs-editor-resizer-label-bottom-left = Coin inférieur gauche — redimensionner +pdfjs-editor-resizer-label-middle-left = Milieu gauche — redimensionner +pdfjs-editor-resizer-top-left = + .aria-label = Coin supérieur gauche — redimensionner +pdfjs-editor-resizer-top-middle = + .aria-label = Milieu haut — redimensionner +pdfjs-editor-resizer-top-right = + .aria-label = Coin supérieur droit — redimensionner +pdfjs-editor-resizer-middle-right = + .aria-label = Milieu droit — redimensionner +pdfjs-editor-resizer-bottom-right = + .aria-label = Coin inférieur droit — redimensionner +pdfjs-editor-resizer-bottom-middle = + .aria-label = Centre bas — redimensionner +pdfjs-editor-resizer-bottom-left = + .aria-label = Coin inférieur gauche — redimensionner +pdfjs-editor-resizer-middle-left = + .aria-label = Milieu gauche — redimensionner + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Couleur de surlignage +pdfjs-editor-colorpicker-button = + .title = Changer de couleur +pdfjs-editor-colorpicker-dropdown = + .aria-label = Choix de couleurs +pdfjs-editor-colorpicker-yellow = + .title = Jaune +pdfjs-editor-colorpicker-green = + .title = Vert +pdfjs-editor-colorpicker-blue = + .title = Bleu +pdfjs-editor-colorpicker-pink = + .title = Rose +pdfjs-editor-colorpicker-red = + .title = Rouge + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Tout afficher +pdfjs-editor-highlight-show-all-button = + .title = Tout afficher + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Modifier le texte alternatif (description de l’image) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Ajouter du texte alternatif (description de l’image) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Rédigez votre description ici… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Courte description pour les personnes qui ne peuvent pas voir l’image ou lorsque l’image ne se charge pas. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Ce texte alternatif a été créé automatiquement et peut être inexact. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = En savoir plus +pdfjs-editor-new-alt-text-create-automatically-button-label = Créer automatiquement le texte alternatif +pdfjs-editor-new-alt-text-not-now-button = Pas maintenant +pdfjs-editor-new-alt-text-error-title = Impossible de créer automatiquement le texte alternatif +pdfjs-editor-new-alt-text-error-description = Veuillez rédiger votre propre texte alternatif ou réessayer plus tard. +pdfjs-editor-new-alt-text-error-close-button = Fermer +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Téléchargement du modèle d’IA de texte alternatif ({ $downloadedSize } sur { $totalSize } Mo) + .aria-valuetext = Téléchargement du modèle d’IA de texte alternatif ({ $downloadedSize } sur { $totalSize } Mo) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Texte alternatif ajouté +pdfjs-editor-new-alt-text-added-button-label = Texte alternatif ajouté +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Texte alternatif manquant +pdfjs-editor-new-alt-text-missing-button-label = Texte alternatif manquant +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Réviser le texte alternatif +pdfjs-editor-new-alt-text-to-review-button-label = Réviser le texte alternatif +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Créé automatiquement : { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Paramètres du texte alternatif des images +pdfjs-image-alt-text-settings-button-label = Paramètres du texte alternatif des images +pdfjs-editor-alt-text-settings-dialog-label = Paramètres du texte alternatif des images +pdfjs-editor-alt-text-settings-automatic-title = Texte alternatif automatique +pdfjs-editor-alt-text-settings-create-model-button-label = Créer automatiquement le texte alternatif +pdfjs-editor-alt-text-settings-create-model-description = Suggère des descriptions pour aider les personnes qui ne peuvent pas voir l’image ou lorsque l’image ne se charge pas. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modèle d’IA de texte alternatif ({ $totalSize } Mo) +pdfjs-editor-alt-text-settings-ai-model-description = Fonctionne localement sur votre appareil, vos données restent privées. Obligatoire pour la génération automatique de texte alternatif. +pdfjs-editor-alt-text-settings-delete-model-button = Supprimer +pdfjs-editor-alt-text-settings-download-model-button = Télécharger +pdfjs-editor-alt-text-settings-downloading-model-button = Téléchargement… +pdfjs-editor-alt-text-settings-editor-title = Éditeur de texte alternatif +pdfjs-editor-alt-text-settings-show-dialog-button-label = Afficher l’éditeur de texte alternatif immédiatement lors de l’ajout d’une image +pdfjs-editor-alt-text-settings-show-dialog-description = Vous aide à vous assurer que toutes vos images ont du texte alternatif. +pdfjs-editor-alt-text-settings-close-button = Fermer + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Surlignage supprimé +pdfjs-editor-undo-bar-message-freetext = Texte supprimé +pdfjs-editor-undo-bar-message-ink = Dessin supprimé +pdfjs-editor-undo-bar-message-stamp = Image supprimée +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } annotation supprimée + *[other] { $count } annotations supprimées + } +pdfjs-editor-undo-bar-undo-button = + .title = Annuler +pdfjs-editor-undo-bar-undo-button-label = Annuler +pdfjs-editor-undo-bar-close-button = + .title = Fermer +pdfjs-editor-undo-bar-close-button-label = Fermer diff --git a/public/assets/pdfjs/locale/fur/viewer.ftl b/public/assets/pdfjs/locale/fur/viewer.ftl new file mode 100755 index 0000000..370af3f --- /dev/null +++ b/public/assets/pdfjs/locale/fur/viewer.ftl @@ -0,0 +1,485 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Pagjine precedente +pdfjs-previous-button-label = Indaûr +pdfjs-next-button = + .title = Prossime pagjine +pdfjs-next-button-label = Indevant +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Pagjine +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = di { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } di { $pagesCount }) +pdfjs-zoom-out-button = + .title = Impiçulìs +pdfjs-zoom-out-button-label = Impiçulìs +pdfjs-zoom-in-button = + .title = Ingrandìs +pdfjs-zoom-in-button-label = Ingrandìs +pdfjs-zoom-select = + .title = Ingrandiment +pdfjs-presentation-mode-button = + .title = Passe ae modalitât presentazion +pdfjs-presentation-mode-button-label = Modalitât presentazion +pdfjs-open-file-button = + .title = Vierç un file +pdfjs-open-file-button-label = Vierç +pdfjs-print-button = + .title = Stampe +pdfjs-print-button-label = Stampe +pdfjs-save-button = + .title = Salve +pdfjs-save-button-label = Salve +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Discjame +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Discjame +pdfjs-bookmark-button = + .title = Pagjine corinte (mostre URL de pagjine atuâl) +pdfjs-bookmark-button-label = Pagjine corinte + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Struments +pdfjs-tools-button-label = Struments +pdfjs-first-page-button = + .title = Va ae prime pagjine +pdfjs-first-page-button-label = Va ae prime pagjine +pdfjs-last-page-button = + .title = Va ae ultime pagjine +pdfjs-last-page-button-label = Va ae ultime pagjine +pdfjs-page-rotate-cw-button = + .title = Zire in sens orari +pdfjs-page-rotate-cw-button-label = Zire in sens orari +pdfjs-page-rotate-ccw-button = + .title = Zire in sens antiorari +pdfjs-page-rotate-ccw-button-label = Zire in sens antiorari +pdfjs-cursor-text-select-tool-button = + .title = Ative il strument di selezion dal test +pdfjs-cursor-text-select-tool-button-label = Strument di selezion dal test +pdfjs-cursor-hand-tool-button = + .title = Ative il strument manute +pdfjs-cursor-hand-tool-button-label = Strument manute +pdfjs-scroll-page-button = + .title = Dopre il scoriment des pagjinis +pdfjs-scroll-page-button-label = Scoriment pagjinis +pdfjs-scroll-vertical-button = + .title = Dopre scoriment verticâl +pdfjs-scroll-vertical-button-label = Scoriment verticâl +pdfjs-scroll-horizontal-button = + .title = Dopre scoriment orizontâl +pdfjs-scroll-horizontal-button-label = Scoriment orizontâl +pdfjs-scroll-wrapped-button = + .title = Dopre scoriment par blocs +pdfjs-scroll-wrapped-button-label = Scoriment par blocs +pdfjs-spread-none-button = + .title = No sta meti dongje pagjinis in cubie +pdfjs-spread-none-button-label = No cubiis di pagjinis +pdfjs-spread-odd-button = + .title = Met dongje cubiis di pagjinis scomençant des pagjinis dispar +pdfjs-spread-odd-button-label = Cubiis di pagjinis, dispar a çampe +pdfjs-spread-even-button = + .title = Met dongje cubiis di pagjinis scomençant des pagjinis pâr +pdfjs-spread-even-button-label = Cubiis di pagjinis, pâr a çampe + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Proprietâts dal document… +pdfjs-document-properties-button-label = Proprietâts dal document… +pdfjs-document-properties-file-name = Non dal file: +pdfjs-document-properties-file-size = Dimension dal file: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Titul: +pdfjs-document-properties-author = Autôr: +pdfjs-document-properties-subject = Ogjet: +pdfjs-document-properties-keywords = Peraulis clâf: +pdfjs-document-properties-creation-date = Date di creazion: +pdfjs-document-properties-modification-date = Date di modifiche: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Creatôr +pdfjs-document-properties-producer = Gjeneradôr PDF: +pdfjs-document-properties-version = Version PDF: +pdfjs-document-properties-page-count = Numar di pagjinis: +pdfjs-document-properties-page-size = Dimension de pagjine: +pdfjs-document-properties-page-size-unit-inches = oncis +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = verticâl +pdfjs-document-properties-page-size-orientation-landscape = orizontâl +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letare +pdfjs-document-properties-page-size-name-legal = Legâl + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Visualizazion web svelte: +pdfjs-document-properties-linearized-yes = Sì +pdfjs-document-properties-linearized-no = No +pdfjs-document-properties-close-button = Siere + +## Print + +pdfjs-print-progress-message = Daûr a prontâ il document pe stampe… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Anule +pdfjs-printing-not-supported = Atenzion: la stampe no je supuartade ad implen di chest navigadôr. +pdfjs-printing-not-ready = Atenzion: il PDF nol è stât cjamât dal dut pe stampe. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Ative/Disative sbare laterâl +pdfjs-toggle-sidebar-notification-button = + .title = Ative/Disative sbare laterâl (il document al conten struture/zontis/strâts) +pdfjs-toggle-sidebar-button-label = Ative/Disative sbare laterâl +pdfjs-document-outline-button = + .title = Mostre la struture dal document (dopli clic par slargjâ/strenzi ducj i elements) +pdfjs-document-outline-button-label = Struture dal document +pdfjs-attachments-button = + .title = Mostre lis zontis +pdfjs-attachments-button-label = Zontis +pdfjs-layers-button = + .title = Mostre i strâts (dopli clic par ristabilî ducj i strâts al stât predefinît) +pdfjs-layers-button-label = Strâts +pdfjs-thumbs-button = + .title = Mostre miniaturis +pdfjs-thumbs-button-label = Miniaturis +pdfjs-current-outline-item-button = + .title = Cjate l'element de struture atuâl +pdfjs-current-outline-item-button-label = Element de struture atuâl +pdfjs-findbar-button = + .title = Cjate tal document +pdfjs-findbar-button-label = Cjate +pdfjs-additional-layers = Strâts adizionâi + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Pagjine { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniature de pagjine { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Cjate + .placeholder = Cjate tal document… +pdfjs-find-previous-button = + .title = Cjate il câs precedent dal test +pdfjs-find-previous-button-label = Precedent +pdfjs-find-next-button = + .title = Cjate il câs sucessîf dal test +pdfjs-find-next-button-label = Sucessîf +pdfjs-find-highlight-checkbox = Evidenzie dut +pdfjs-find-match-case-checkbox-label = Fâs distinzion tra maiusculis e minusculis +pdfjs-find-match-diacritics-checkbox-label = Corispondence diacritiche +pdfjs-find-entire-word-checkbox-label = Peraulis interiis +pdfjs-find-reached-top = Si è rivâts al inizi dal document e si à continuât de fin +pdfjs-find-reached-bottom = Si è rivât ae fin dal document e si à continuât dal inizi +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } di { $total } corispondence + *[other] { $current } di { $total } corispondencis + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Plui di { $limit } corispondence + *[other] Plui di { $limit } corispondencis + } +pdfjs-find-not-found = Test no cjatât + +## Predefined zoom values + +pdfjs-page-scale-width = Largjece de pagjine +pdfjs-page-scale-fit = Pagjine interie +pdfjs-page-scale-auto = Ingrandiment automatic +pdfjs-page-scale-actual = Dimension reâl +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Pagjine { $page } + +## Loading indicator messages + +pdfjs-loading-error = Al è vignût fûr un erôr intant che si cjariave il PDF. +pdfjs-invalid-file-error = File PDF no valit o ruvinât. +pdfjs-missing-file-error = Al mancje il file PDF. +pdfjs-unexpected-response-error = Rispueste dal servidôr inspietade. +pdfjs-rendering-error = Al è vignût fûr un erôr tal realizâ la visualizazion de pagjine. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Anotazion { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Inserìs la password par vierzi chest file PDF. +pdfjs-password-invalid = Password no valide. Par plasê torne prove. +pdfjs-password-ok-button = Va ben +pdfjs-password-cancel-button = Anule +pdfjs-web-fonts-disabled = I caratars dal Web a son disativâts: Impussibil doprâ i caratars PDF incorporâts. + +## Editing + +pdfjs-editor-free-text-button = + .title = Test +pdfjs-editor-free-text-button-label = Test +pdfjs-editor-ink-button = + .title = Dissen +pdfjs-editor-ink-button-label = Dissen +pdfjs-editor-stamp-button = + .title = Zonte o modifiche imagjins +pdfjs-editor-stamp-button-label = Zonte o modifiche imagjins +pdfjs-editor-highlight-button = + .title = Evidenzie +pdfjs-editor-highlight-button-label = Evidenzie +pdfjs-highlight-floating-button1 = + .title = Evidenzie + .aria-label = Evidenzie +pdfjs-highlight-floating-button-label = Evidenzie + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Gjave dissen +pdfjs-editor-remove-freetext-button = + .title = Gjave test +pdfjs-editor-remove-stamp-button = + .title = Gjave imagjin +pdfjs-editor-remove-highlight-button = + .title = Gjave evidenziazion + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Colôr +pdfjs-editor-free-text-size-input = Dimension +pdfjs-editor-ink-color-input = Colôr +pdfjs-editor-ink-thickness-input = Spessôr +pdfjs-editor-ink-opacity-input = Opacitât +pdfjs-editor-stamp-add-image-button = + .title = Zonte imagjin +pdfjs-editor-stamp-add-image-button-label = Zonte imagjin +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Spessôr +pdfjs-editor-free-highlight-thickness-title = + .title = Modifiche il spessôr de selezion pai elements che no son testuâi +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Editôr di test + .default-content = Scomence a scrivi… +pdfjs-free-text = + .aria-label = Editôr di test +pdfjs-free-text-default-content = Scomence a scrivi… +pdfjs-ink = + .aria-label = Editôr dissens +pdfjs-ink-canvas = + .aria-label = Imagjin creade dal utent + +## Alt-text dialog + +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button-label = Test alternatîf +pdfjs-editor-alt-text-edit-button-label = Modifiche test alternatîf +pdfjs-editor-alt-text-dialog-label = Sielç une opzion +pdfjs-editor-alt-text-dialog-description = Il test alternatîf (“alt textâ€) al jude cuant che lis personis no puedin viodi la imagjin o cuant che la imagjine no ven cjariade. +pdfjs-editor-alt-text-add-description-label = Zonte une descrizion +pdfjs-editor-alt-text-add-description-description = Ponte a une o dôs frasis che a descrivin l’argoment, la ambientazion o lis azions. +pdfjs-editor-alt-text-mark-decorative-label = Segne come decorative +pdfjs-editor-alt-text-mark-decorative-description = Chest al ven doprât pes imagjins ornamentâls, come i ôrs o lis filigranis. +pdfjs-editor-alt-text-cancel-button = Anule +pdfjs-editor-alt-text-save-button = Salve +pdfjs-editor-alt-text-decorative-tooltip = Segnade come decorative +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Par esempli, “Un zovin si sente a taule par mangjâ†+ +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Cjanton in alt a çampe — ridimensione +pdfjs-editor-resizer-label-top-middle = Bande superiôr tal mieç — ridimensione +pdfjs-editor-resizer-label-top-right = Cjanton in alt a diestre — ridimensione +pdfjs-editor-resizer-label-middle-right = Bande diestre tal mieç — ridimensione +pdfjs-editor-resizer-label-bottom-right = Cjanton in bas a diestre — ridimensione +pdfjs-editor-resizer-label-bottom-middle = Bande inferiôr tal mieç — ridimensione +pdfjs-editor-resizer-label-bottom-left = Cjanton in bas a çampe — ridimensione +pdfjs-editor-resizer-label-middle-left = Bande di çampe tal mieç — ridimensione +pdfjs-editor-resizer-top-left = + .aria-label = Cjanton in alt a çampe — ridimensione +pdfjs-editor-resizer-top-middle = + .aria-label = Bande superiôr tal mieç — ridimensione +pdfjs-editor-resizer-top-right = + .aria-label = Cjanton in alt a diestre — ridimensione +pdfjs-editor-resizer-middle-right = + .aria-label = Bande diestre tal mieç — ridimensione +pdfjs-editor-resizer-bottom-right = + .aria-label = Cjanton in bas a diestre — ridimensione +pdfjs-editor-resizer-bottom-middle = + .aria-label = Bande inferiôr tal mieç — ridimensione +pdfjs-editor-resizer-bottom-left = + .aria-label = Cjanton in bas a çampe — ridimensione +pdfjs-editor-resizer-middle-left = + .aria-label = Bande di çampe tal mieç — ridimensione + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Colôr par evidenziâ +pdfjs-editor-colorpicker-button = + .title = Cambie colôr +pdfjs-editor-colorpicker-dropdown = + .aria-label = Sieltis di colôr +pdfjs-editor-colorpicker-yellow = + .title = Zâl +pdfjs-editor-colorpicker-green = + .title = Vert +pdfjs-editor-colorpicker-blue = + .title = Blu +pdfjs-editor-colorpicker-pink = + .title = Rose +pdfjs-editor-colorpicker-red = + .title = Ros + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Mostre dut +pdfjs-editor-highlight-show-all-button = + .title = Mostre dut + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Modifiche test alternatîf (descrizion de imagjin) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Zonte test alternatîf (descrizion de imagjin) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Scrîf achì la tô descrizion… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Curte descrizion par personis che no rivin a viodi la imagjin, o che e ven mostrade cuant che no si rive a cjariâle. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Chest test alternatîf al è stât creât in automatic e al è pussibil che nol sedi cret. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Plui informazions +pdfjs-editor-new-alt-text-create-automatically-button-label = Cree test alternatîf in automatic +pdfjs-editor-new-alt-text-not-now-button = No cumò +pdfjs-editor-new-alt-text-error-title = Impussibil creâ test alternatîf in automatic +pdfjs-editor-new-alt-text-error-description = Scrîf il to test alternatîf o prove plui tart. +pdfjs-editor-new-alt-text-error-close-button = Siere +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Daûr a discjariâil model IA pal test alternatîf ({ $downloadedSize } di { $totalSize } MB) + .aria-valuetext = Daûr a discjariâ il model IA pal test alternatîf ({ $downloadedSize } di { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Test alternatîf zontât +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Al mancje il test alternatîf +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Verifiche test alternatîf +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creât in automatic: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Impostazions test alternatîf pes imagjins +pdfjs-image-alt-text-settings-button-label = Impostazions test alternatîf pes imagjins +pdfjs-editor-alt-text-settings-dialog-label = Impostazions test alternatîf pes imagjins +pdfjs-editor-alt-text-settings-automatic-title = Test alternatîf automatic +pdfjs-editor-alt-text-settings-create-model-button-label = Cree test alternatîf in automatic +pdfjs-editor-alt-text-settings-create-model-description = Al sugjerìs descrizions par judâ lis personis che no rivin a viodi la imagjin o cuant che la imagjin no ven cjariade. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model IA pal test alternatîf ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Al ven eseguît in locâl sul to dispositîf, cussì che i tiei dâts a restin riservâts. Al è necessari pe gjenerazion automatiche dal test alternatîf. +pdfjs-editor-alt-text-settings-delete-model-button = Elimine +pdfjs-editor-alt-text-settings-download-model-button = Discjame +pdfjs-editor-alt-text-settings-downloading-model-button = Daûr a discjariâ… +pdfjs-editor-alt-text-settings-editor-title = Modifiche test alternatîf +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostre l'editôr dal test alternatîf a pene che e ven zontade une imagjin +pdfjs-editor-alt-text-settings-show-dialog-description = Ti jude a sigurâti che dutis lis tôs imagjins a vedin il test alternatîf. +pdfjs-editor-alt-text-settings-close-button = Siere diff --git a/public/assets/pdfjs/locale/fy-NL/viewer.ftl b/public/assets/pdfjs/locale/fy-NL/viewer.ftl new file mode 100755 index 0000000..15850b4 --- /dev/null +++ b/public/assets/pdfjs/locale/fy-NL/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Foarige side +pdfjs-previous-button-label = Foarige +pdfjs-next-button = + .title = Folgjende side +pdfjs-next-button-label = Folgjende +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Side +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = fan { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } fan { $pagesCount }) +pdfjs-zoom-out-button = + .title = Utzoome +pdfjs-zoom-out-button-label = Utzoome +pdfjs-zoom-in-button = + .title = Ynzoome +pdfjs-zoom-in-button-label = Ynzoome +pdfjs-zoom-select = + .title = Zoome +pdfjs-presentation-mode-button = + .title = Wikselje nei presintaasjemodus +pdfjs-presentation-mode-button-label = Presintaasjemodus +pdfjs-open-file-button = + .title = Bestân iepenje +pdfjs-open-file-button-label = Iepenje +pdfjs-print-button = + .title = Ofdrukke +pdfjs-print-button-label = Ofdrukke +pdfjs-save-button = + .title = Bewarje +pdfjs-save-button-label = Bewarje +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Downloade +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Downloade +pdfjs-bookmark-button = + .title = Aktuele side (URL fan aktuele side besjen) +pdfjs-bookmark-button-label = Aktuele side + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Ark +pdfjs-tools-button-label = Ark +pdfjs-first-page-button = + .title = Gean nei earste side +pdfjs-first-page-button-label = Gean nei earste side +pdfjs-last-page-button = + .title = Gean nei lêste side +pdfjs-last-page-button-label = Gean nei lêste side +pdfjs-page-rotate-cw-button = + .title = Rjochtsom draaie +pdfjs-page-rotate-cw-button-label = Rjochtsom draaie +pdfjs-page-rotate-ccw-button = + .title = Linksom draaie +pdfjs-page-rotate-ccw-button-label = Linksom draaie +pdfjs-cursor-text-select-tool-button = + .title = Tekstseleksjehelpmiddel ynskeakelje +pdfjs-cursor-text-select-tool-button-label = Tekstseleksjehelpmiddel +pdfjs-cursor-hand-tool-button = + .title = Hânhelpmiddel ynskeakelje +pdfjs-cursor-hand-tool-button-label = Hânhelpmiddel +pdfjs-scroll-page-button = + .title = Sideskowen brûke +pdfjs-scroll-page-button-label = Sideskowen +pdfjs-scroll-vertical-button = + .title = Fertikaal skowe brûke +pdfjs-scroll-vertical-button-label = Fertikaal skowe +pdfjs-scroll-horizontal-button = + .title = Horizontaal skowe brûke +pdfjs-scroll-horizontal-button-label = Horizontaal skowe +pdfjs-scroll-wrapped-button = + .title = Skowe mei oersjoch brûke +pdfjs-scroll-wrapped-button-label = Skowe mei oersjoch +pdfjs-spread-none-button = + .title = Sidesprieding net gearfetsje +pdfjs-spread-none-button-label = Gjin sprieding +pdfjs-spread-odd-button = + .title = Sidesprieding gearfetsje te starten mei ûneven nûmers +pdfjs-spread-odd-button-label = Uneven sprieding +pdfjs-spread-even-button = + .title = Sidesprieding gearfetsje te starten mei even nûmers +pdfjs-spread-even-button-label = Even sprieding + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Dokuminteigenskippen… +pdfjs-document-properties-button-label = Dokuminteigenskippen… +pdfjs-document-properties-file-name = Bestânsnamme: +pdfjs-document-properties-file-size = Bestânsgrutte: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Titel: +pdfjs-document-properties-author = Auteur: +pdfjs-document-properties-subject = Underwerp: +pdfjs-document-properties-keywords = Kaaiwurden: +pdfjs-document-properties-creation-date = Oanmaakdatum: +pdfjs-document-properties-modification-date = Bewurkingsdatum: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Makker: +pdfjs-document-properties-producer = PDF-makker: +pdfjs-document-properties-version = PDF-ferzje: +pdfjs-document-properties-page-count = Siden: +pdfjs-document-properties-page-size = Sideformaat: +pdfjs-document-properties-page-size-unit-inches = yn +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = steand +pdfjs-document-properties-page-size-orientation-landscape = lizzend +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Juridysk + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Flugge webwerjefte: +pdfjs-document-properties-linearized-yes = Ja +pdfjs-document-properties-linearized-no = Nee +pdfjs-document-properties-close-button = Slute + +## Print + +pdfjs-print-progress-message = Dokumint tariede oar ôfdrukken… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Annulearje +pdfjs-printing-not-supported = Warning: Printen is net folslein stipe troch dizze browser. +pdfjs-printing-not-ready = Warning: PDF is net folslein laden om ôf te drukken. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Sidebalke yn-/útskeakelje +pdfjs-toggle-sidebar-notification-button = + .title = Sidebalke yn-/útskeakelje (dokumint befettet oersjoch/bylagen/lagen) +pdfjs-toggle-sidebar-button-label = Sidebalke yn-/útskeakelje +pdfjs-document-outline-button = + .title = Dokumintoersjoch toane (dûbelklik om alle items út/yn te klappen) +pdfjs-document-outline-button-label = Dokumintoersjoch +pdfjs-attachments-button = + .title = Bylagen toane +pdfjs-attachments-button-label = Bylagen +pdfjs-layers-button = + .title = Lagen toane (dûbelklik om alle lagen nei de standertsteat werom te setten) +pdfjs-layers-button-label = Lagen +pdfjs-thumbs-button = + .title = Foarbylden toane +pdfjs-thumbs-button-label = Foarbylden +pdfjs-current-outline-item-button = + .title = Aktueel item yn ynhâldsopjefte sykje +pdfjs-current-outline-item-button-label = Aktueel item yn ynhâldsopjefte +pdfjs-findbar-button = + .title = Sykje yn dokumint +pdfjs-findbar-button-label = Sykje +pdfjs-additional-layers = Oanfoljende lagen + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Side { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Foarbyld fan side { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Sykje + .placeholder = Sykje yn dokumint… +pdfjs-find-previous-button = + .title = It foarige foarkommen fan de tekst sykje +pdfjs-find-previous-button-label = Foarige +pdfjs-find-next-button = + .title = It folgjende foarkommen fan de tekst sykje +pdfjs-find-next-button-label = Folgjende +pdfjs-find-highlight-checkbox = Alles markearje +pdfjs-find-match-case-checkbox-label = Haadlettergefoelich +pdfjs-find-match-diacritics-checkbox-label = Diakrityske tekens brûke +pdfjs-find-entire-word-checkbox-label = Hiele wurden +pdfjs-find-reached-top = Boppekant fan dokumint berikt, trochgien fan ûnder ôf +pdfjs-find-reached-bottom = Ein fan dokumint berikt, trochgien fan boppe ôf +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } fan { $total } oerienkomst + *[other] { $current } fan { $total } oerienkomsten + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Mear as { $limit } oerienkomst + *[other] Mear as { $limit } oerienkomsten + } +pdfjs-find-not-found = Tekst net fûn + +## Predefined zoom values + +pdfjs-page-scale-width = Sidebreedte +pdfjs-page-scale-fit = Hiele side +pdfjs-page-scale-auto = Automatysk zoome +pdfjs-page-scale-actual = Werklike grutte +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Side { $page } + +## Loading indicator messages + +pdfjs-loading-error = Der is in flater bard by it laden fan de PDF. +pdfjs-invalid-file-error = Ynfalide of korruptearre PDF-bestân. +pdfjs-missing-file-error = PDF-bestân ûntbrekt. +pdfjs-unexpected-response-error = Unferwacht serverantwurd. +pdfjs-rendering-error = Der is in flater bard by it renderjen fan de side. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type }-annotaasje] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Jou it wachtwurd om dit PDF-bestân te iepenjen. +pdfjs-password-invalid = Ferkeard wachtwurd. Probearje opnij. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Annulearje +pdfjs-web-fonts-disabled = Weblettertypen binne útskeakele: gebrûk fan ynsluten PDF-lettertypen is net mooglik. + +## Editing + +pdfjs-editor-free-text-button = + .title = Tekst +pdfjs-editor-free-text-button-label = Tekst +pdfjs-editor-ink-button = + .title = Tekenje +pdfjs-editor-ink-button-label = Tekenje +pdfjs-editor-stamp-button = + .title = Ofbyldingen tafoegje of bewurkje +pdfjs-editor-stamp-button-label = Ofbyldingen tafoegje of bewurkje +pdfjs-editor-highlight-button = + .title = Markearje +pdfjs-editor-highlight-button-label = Markearje +pdfjs-highlight-floating-button1 = + .title = Markearje + .aria-label = Markearje +pdfjs-highlight-floating-button-label = Markearje + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Tekening fuortsmite +pdfjs-editor-remove-freetext-button = + .title = Tekst fuortsmite +pdfjs-editor-remove-stamp-button = + .title = Ofbylding fuortsmite +pdfjs-editor-remove-highlight-button = + .title = Markearring fuortsmite + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Kleur +pdfjs-editor-free-text-size-input = Grutte +pdfjs-editor-ink-color-input = Kleur +pdfjs-editor-ink-thickness-input = Tsjokte +pdfjs-editor-ink-opacity-input = Transparânsje +pdfjs-editor-stamp-add-image-button = + .title = Ofbylding tafoegje +pdfjs-editor-stamp-add-image-button-label = Ofbylding tafoegje +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Tsjokte +pdfjs-editor-free-highlight-thickness-title = + .title = Tsjokte wizigje by aksintuearring fan oare items as tekst +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Tekstbewurker + .default-content = Start mei typen… +pdfjs-free-text = + .aria-label = Tekstbewurker +pdfjs-free-text-default-content = Begjin mei typen… +pdfjs-ink = + .aria-label = Tekeningbewurker +pdfjs-ink-canvas = + .aria-label = Troch brûker makke ôfbylding + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alternative tekst +pdfjs-editor-alt-text-edit-button = + .aria-label = Alternative tekst bewurkje +pdfjs-editor-alt-text-edit-button-label = Alternative tekst bewurkje +pdfjs-editor-alt-text-dialog-label = Kies in opsje +pdfjs-editor-alt-text-dialog-description = Alternative tekst helpt wannear’t minsken de ôfbylding net sjen kinne of wannear’t dizze net laden wurdt. +pdfjs-editor-alt-text-add-description-label = Foegje in beskriuwing ta +pdfjs-editor-alt-text-add-description-description = Stribje nei 1-2 sinnen dy’t it ûnderwerp, de omjouwing of de aksjes beskriuwe. +pdfjs-editor-alt-text-mark-decorative-label = As dekoratyf markearje +pdfjs-editor-alt-text-mark-decorative-description = Dit wurdt brûkt foar sierlike ôfbyldingen, lykas rânen of wettermerken. +pdfjs-editor-alt-text-cancel-button = Annulearje +pdfjs-editor-alt-text-save-button = Bewarje +pdfjs-editor-alt-text-decorative-tooltip = As dekoratyf markearre +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Bygelyks, ‘In jonge man sit oan in tafel om te iten’ +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alternative tekst + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Linkerboppehoek – formaat wizigje +pdfjs-editor-resizer-label-top-middle = Midden boppe – formaat wizigje +pdfjs-editor-resizer-label-top-right = Rjochterboppehoek – formaat wizigje +pdfjs-editor-resizer-label-middle-right = Midden rjochts – formaat wizigje +pdfjs-editor-resizer-label-bottom-right = Rjochterûnderhoek – formaat wizigje +pdfjs-editor-resizer-label-bottom-middle = Midden ûnder – formaat wizigje +pdfjs-editor-resizer-label-bottom-left = Linkerûnderhoek – formaat wizigje +pdfjs-editor-resizer-label-middle-left = Links midden – formaat wizigje +pdfjs-editor-resizer-top-left = + .aria-label = Linkerboppehoek – formaat wizigje +pdfjs-editor-resizer-top-middle = + .aria-label = Midden boppe – formaat wizigje +pdfjs-editor-resizer-top-right = + .aria-label = Rjochterboppehoek – formaat wizigje +pdfjs-editor-resizer-middle-right = + .aria-label = Midden rjochts – formaat wizigje +pdfjs-editor-resizer-bottom-right = + .aria-label = Rjochterûnderhoek – formaat wizigje +pdfjs-editor-resizer-bottom-middle = + .aria-label = Midden ûnder – formaat wizigje +pdfjs-editor-resizer-bottom-left = + .aria-label = Linkerûnderhoek – formaat wizigje +pdfjs-editor-resizer-middle-left = + .aria-label = Links midden – formaat wizigje + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Markearringskleur +pdfjs-editor-colorpicker-button = + .title = Kleur wizigje +pdfjs-editor-colorpicker-dropdown = + .aria-label = Kleurkarren +pdfjs-editor-colorpicker-yellow = + .title = Giel +pdfjs-editor-colorpicker-green = + .title = Grien +pdfjs-editor-colorpicker-blue = + .title = Blau +pdfjs-editor-colorpicker-pink = + .title = Roze +pdfjs-editor-colorpicker-red = + .title = Read + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Alles toane +pdfjs-editor-highlight-show-all-button = + .title = Alles toane + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Alternative tekst (ôfbyldingsbeskriuwing) bewurkje +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Alternative tekst (ôfbyldingsbeskriuwing) tafoegje +pdfjs-editor-new-alt-text-textarea = + .placeholder = Skriuw hjir jo beskriuwing… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Koarte beskriuwing foar minsken dy’t de ôfbylding net sjen kinne of wannear’t de ôfbylding net laden wurdt. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Dizze alternative tekst is automatysk makke en is mooglik net korrekt. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Mear ynfo +pdfjs-editor-new-alt-text-create-automatically-button-label = Alternative tekst automatysk oanmeitsje +pdfjs-editor-new-alt-text-not-now-button = No net +pdfjs-editor-new-alt-text-error-title = Kin alternative tekst net automatysk oanmeitsje +pdfjs-editor-new-alt-text-error-description = Skriuw jo eigen alternative tekst of probearje it letter nochris. +pdfjs-editor-new-alt-text-error-close-button = Slute +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = AI-model foar alternative tekst downloade ({ $downloadedSize } fan { $totalSize } MB) + .aria-valuetext = AI-model foar alternative tekst downloade ({ $downloadedSize } fan { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alternative tekst tafoege +pdfjs-editor-new-alt-text-added-button-label = Alternative tekst tafoege +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Alternative tekst ûntbrekt +pdfjs-editor-new-alt-text-missing-button-label = Alternative tekst ûntbrekt +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Alternative tekst beoardiele +pdfjs-editor-new-alt-text-to-review-button-label = Alternative tekst beoardiele +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automatysk oanmakke: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Ynstellingen foar alternative tekst fan ôfbyldingen +pdfjs-image-alt-text-settings-button-label = Ynstellingen foar alternative tekst fan ôfbyldingen +pdfjs-editor-alt-text-settings-dialog-label = Ynstellingen foar alternative tekst fan ôfbyldingen +pdfjs-editor-alt-text-settings-automatic-title = Automatyske alternative tekst +pdfjs-editor-alt-text-settings-create-model-button-label = Alternative tekst automatysk oanmeitsje +pdfjs-editor-alt-text-settings-create-model-description = Stelt beskriuwingen foar om minsken te helpen dy’t de ôfbylding net sjen kinne of foar wa’t de ôfbylding net laden wurdt. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = AI-model foar alternative tekst ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Wurdt lokaal op jo apparaat útfierd, sadat jo gegevens privee bliuwe. Fereaske foar automatyske alternative tekst. +pdfjs-editor-alt-text-settings-delete-model-button = Fuortsmite +pdfjs-editor-alt-text-settings-download-model-button = Downloade +pdfjs-editor-alt-text-settings-downloading-model-button = Downloade… +pdfjs-editor-alt-text-settings-editor-title = Alternative-tekstbewurker +pdfjs-editor-alt-text-settings-show-dialog-button-label = Alternative-tekstbewurker daliks toane by tafoegjen fan in ôfbylding +pdfjs-editor-alt-text-settings-show-dialog-description = Helpt jo derfoar te soargjen dat al jo ôfbyldingen alternative tekst hawwe. +pdfjs-editor-alt-text-settings-close-button = Slute + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Markearring fuortsmiten +pdfjs-editor-undo-bar-message-freetext = Tekst fuortsmiten +pdfjs-editor-undo-bar-message-ink = Tekening fuortsmiten +pdfjs-editor-undo-bar-message-stamp = Ofbylding fuortsmiten +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } annotaasje fuortsmiten + *[other] { $count } annotaasjes fuortsmiten + } +pdfjs-editor-undo-bar-undo-button = + .title = Ungedien meitsje +pdfjs-editor-undo-bar-undo-button-label = Ungedien meitsje +pdfjs-editor-undo-bar-close-button = + .title = Slute +pdfjs-editor-undo-bar-close-button-label = Slute diff --git a/public/assets/pdfjs/locale/ga-IE/viewer.ftl b/public/assets/pdfjs/locale/ga-IE/viewer.ftl new file mode 100755 index 0000000..cb59308 --- /dev/null +++ b/public/assets/pdfjs/locale/ga-IE/viewer.ftl @@ -0,0 +1,213 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = An Leathanach Roimhe Seo +pdfjs-previous-button-label = Roimhe Seo +pdfjs-next-button = + .title = An Chéad Leathanach Eile +pdfjs-next-button-label = Ar Aghaidh +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Leathanach +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = as { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } as { $pagesCount }) +pdfjs-zoom-out-button = + .title = Súmáil Amach +pdfjs-zoom-out-button-label = Súmáil Amach +pdfjs-zoom-in-button = + .title = Súmáil Isteach +pdfjs-zoom-in-button-label = Súmáil Isteach +pdfjs-zoom-select = + .title = Súmáil +pdfjs-presentation-mode-button = + .title = Úsáid an Mód Láithreoireachta +pdfjs-presentation-mode-button-label = Mód Láithreoireachta +pdfjs-open-file-button = + .title = Oscail Comhad +pdfjs-open-file-button-label = Oscail +pdfjs-print-button = + .title = Priontáil +pdfjs-print-button-label = Priontáil + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Uirlisí +pdfjs-tools-button-label = Uirlisí +pdfjs-first-page-button = + .title = Go dtí an chéad leathanach +pdfjs-first-page-button-label = Go dtí an chéad leathanach +pdfjs-last-page-button = + .title = Go dtí an leathanach deiridh +pdfjs-last-page-button-label = Go dtí an leathanach deiridh +pdfjs-page-rotate-cw-button = + .title = Rothlaigh ar deiseal +pdfjs-page-rotate-cw-button-label = Rothlaigh ar deiseal +pdfjs-page-rotate-ccw-button = + .title = Rothlaigh ar tuathal +pdfjs-page-rotate-ccw-button-label = Rothlaigh ar tuathal +pdfjs-cursor-text-select-tool-button = + .title = Cumasaigh an Uirlis Roghnaithe Téacs +pdfjs-cursor-text-select-tool-button-label = Uirlis Roghnaithe Téacs +pdfjs-cursor-hand-tool-button = + .title = Cumasaigh an Uirlis Láimhe +pdfjs-cursor-hand-tool-button-label = Uirlis Láimhe + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Airíonna na Cáipéise… +pdfjs-document-properties-button-label = Airíonna na Cáipéise… +pdfjs-document-properties-file-name = Ainm an chomhaid: +pdfjs-document-properties-file-size = Méid an chomhaid: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } kB ({ $size_b } beart) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } beart) +pdfjs-document-properties-title = Teideal: +pdfjs-document-properties-author = Údar: +pdfjs-document-properties-subject = Ãbhar: +pdfjs-document-properties-keywords = Eochairfhocail: +pdfjs-document-properties-creation-date = Dáta Cruthaithe: +pdfjs-document-properties-modification-date = Dáta Athraithe: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Cruthaitheoir: +pdfjs-document-properties-producer = Cruthaitheoir an PDF: +pdfjs-document-properties-version = Leagan PDF: +pdfjs-document-properties-page-count = Líon Leathanach: + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + + +## + +pdfjs-document-properties-close-button = Dún + +## Print + +pdfjs-print-progress-message = Cáipéis á hullmhú le priontáil… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Cealaigh +pdfjs-printing-not-supported = Rabhadh: Ní thacaíonn an brabhsálaí le priontáil go hiomlán. +pdfjs-printing-not-ready = Rabhadh: Ní féidir an PDF a phriontáil go dtí go mbeidh an cháipéis iomlán lódáilte. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Scoránaigh an Barra Taoibh +pdfjs-toggle-sidebar-button-label = Scoránaigh an Barra Taoibh +pdfjs-document-outline-button = + .title = Taispeáin Imlíne na Cáipéise (déchliceáil chun chuile rud a leathnú nó a laghdú) +pdfjs-document-outline-button-label = Creatlach na Cáipéise +pdfjs-attachments-button = + .title = Taispeáin Iatáin +pdfjs-attachments-button-label = Iatáin +pdfjs-thumbs-button = + .title = Taispeáin Mionsamhlacha +pdfjs-thumbs-button-label = Mionsamhlacha +pdfjs-findbar-button = + .title = Aimsigh sa Cháipéis +pdfjs-findbar-button-label = Aimsigh + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Leathanach { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Mionsamhail Leathanaigh { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Aimsigh + .placeholder = Aimsigh sa cháipéis… +pdfjs-find-previous-button = + .title = Aimsigh an sampla roimhe seo den nath seo +pdfjs-find-previous-button-label = Roimhe seo +pdfjs-find-next-button = + .title = Aimsigh an chéad sampla eile den nath sin +pdfjs-find-next-button-label = Ar aghaidh +pdfjs-find-highlight-checkbox = Aibhsigh uile +pdfjs-find-match-case-checkbox-label = Cásíogair +pdfjs-find-entire-word-checkbox-label = Focail iomlána +pdfjs-find-reached-top = Ag barr na cáipéise, ag leanúint ón mbun +pdfjs-find-reached-bottom = Ag bun na cáipéise, ag leanúint ón mbarr +pdfjs-find-not-found = Frása gan aimsiú + +## Predefined zoom values + +pdfjs-page-scale-width = Leithead Leathanaigh +pdfjs-page-scale-fit = Laghdaigh go dtí an Leathanach +pdfjs-page-scale-auto = Súmáil Uathoibríoch +pdfjs-page-scale-actual = Fíormhéid +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = Tharla earráid agus an cháipéis PDF á lódáil. +pdfjs-invalid-file-error = Comhad neamhbhailí nó truaillithe PDF. +pdfjs-missing-file-error = Comhad PDF ar iarraidh. +pdfjs-unexpected-response-error = Freagra ón bhfreastalaí nach rabhthas ag súil leis. +pdfjs-rendering-error = Tharla earráid agus an leathanach á leagan amach. + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Anótáil { $type }] + +## Password + +pdfjs-password-label = Cuir an focal faire isteach chun an comhad PDF seo a oscailt. +pdfjs-password-invalid = Focal faire mícheart. Déan iarracht eile. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Cealaigh +pdfjs-web-fonts-disabled = Tá clófhoirne Gréasáin díchumasaithe: ní féidir clófhoirne leabaithe PDF a úsáid. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/gd/viewer.ftl b/public/assets/pdfjs/locale/gd/viewer.ftl new file mode 100755 index 0000000..a3d62a0 --- /dev/null +++ b/public/assets/pdfjs/locale/gd/viewer.ftl @@ -0,0 +1,313 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = An duilleag roimhe +pdfjs-previous-button-label = Air ais +pdfjs-next-button = + .title = An ath-dhuilleag +pdfjs-next-button-label = Air adhart +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Duilleag +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = à { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } à { $pagesCount }) +pdfjs-zoom-out-button = + .title = Sùm a-mach +pdfjs-zoom-out-button-label = Sùm a-mach +pdfjs-zoom-in-button = + .title = Sùm a-steach +pdfjs-zoom-in-button-label = Sùm a-steach +pdfjs-zoom-select = + .title = Sùm +pdfjs-presentation-mode-button = + .title = Gearr leum dhan mhodh taisbeanaidh +pdfjs-presentation-mode-button-label = Am modh taisbeanaidh +pdfjs-open-file-button = + .title = Fosgail faidhle +pdfjs-open-file-button-label = Fosgail +pdfjs-print-button = + .title = Clò-bhuail +pdfjs-print-button-label = Clò-bhuail +pdfjs-save-button = + .title = Sàbhail +pdfjs-save-button-label = Sàbhail +pdfjs-bookmark-button = + .title = An duilleag làithreach (Seall an URL on duilleag làithreach) +pdfjs-bookmark-button-label = An duilleag làithreach + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Innealan +pdfjs-tools-button-label = Innealan +pdfjs-first-page-button = + .title = Rach gun chiad duilleag +pdfjs-first-page-button-label = Rach gun chiad duilleag +pdfjs-last-page-button = + .title = Rach gun duilleag mu dheireadh +pdfjs-last-page-button-label = Rach gun duilleag mu dheireadh +pdfjs-page-rotate-cw-button = + .title = Cuairtich gu deiseil +pdfjs-page-rotate-cw-button-label = Cuairtich gu deiseil +pdfjs-page-rotate-ccw-button = + .title = Cuairtich gu tuathail +pdfjs-page-rotate-ccw-button-label = Cuairtich gu tuathail +pdfjs-cursor-text-select-tool-button = + .title = Cuir an comas inneal taghadh an teacsa +pdfjs-cursor-text-select-tool-button-label = Inneal taghadh an teacsa +pdfjs-cursor-hand-tool-button = + .title = Cuir inneal na làimhe an comas +pdfjs-cursor-hand-tool-button-label = Inneal na làimhe +pdfjs-scroll-page-button = + .title = Cleachd sgroladh duilleige +pdfjs-scroll-page-button-label = Sgroladh duilleige +pdfjs-scroll-vertical-button = + .title = Cleachd sgroladh inghearach +pdfjs-scroll-vertical-button-label = Sgroladh inghearach +pdfjs-scroll-horizontal-button = + .title = Cleachd sgroladh còmhnard +pdfjs-scroll-horizontal-button-label = Sgroladh còmhnard +pdfjs-scroll-wrapped-button = + .title = Cleachd sgroladh paisgte +pdfjs-scroll-wrapped-button-label = Sgroladh paisgte +pdfjs-spread-none-button = + .title = Na cuir còmhla sgoileadh dhuilleagan +pdfjs-spread-none-button-label = Gun sgaoileadh dhuilleagan +pdfjs-spread-odd-button = + .title = Cuir còmhla duilleagan sgaoilte a thòisicheas le duilleagan aig a bheil àireamh chorr +pdfjs-spread-odd-button-label = Sgaoileadh dhuilleagan corra +pdfjs-spread-even-button = + .title = Cuir còmhla duilleagan sgaoilte a thòisicheas le duilleagan aig a bheil àireamh chothrom +pdfjs-spread-even-button-label = Sgaoileadh dhuilleagan cothrom + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Roghainnean na sgrìobhainne… +pdfjs-document-properties-button-label = Roghainnean na sgrìobhainne… +pdfjs-document-properties-file-name = Ainm an fhaidhle: +pdfjs-document-properties-file-size = Meud an fhaidhle: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Tiotal: +pdfjs-document-properties-author = Ùghdar: +pdfjs-document-properties-subject = Cuspair: +pdfjs-document-properties-keywords = Faclan-luirg: +pdfjs-document-properties-creation-date = Latha a chruthachaidh: +pdfjs-document-properties-modification-date = Latha atharrachaidh: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Cruthadair: +pdfjs-document-properties-producer = Saothraiche a' PDF: +pdfjs-document-properties-version = Tionndadh a' PDF: +pdfjs-document-properties-page-count = Àireamh de dhuilleagan: +pdfjs-document-properties-page-size = Meud na duilleige: +pdfjs-document-properties-page-size-unit-inches = ann an +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = portraid +pdfjs-document-properties-page-size-orientation-landscape = dreach-tìre +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Litir +pdfjs-document-properties-page-size-name-legal = Laghail + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Grad shealladh-lìn: +pdfjs-document-properties-linearized-yes = Tha +pdfjs-document-properties-linearized-no = Chan eil +pdfjs-document-properties-close-button = Dùin + +## Print + +pdfjs-print-progress-message = Ag ullachadh na sgrìobhainn airson clò-bhualadh… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Sguir dheth +pdfjs-printing-not-supported = Rabhadh: Chan eil am brabhsair seo a' cur làn-taic ri clò-bhualadh. +pdfjs-printing-not-ready = Rabhadh: Cha deach am PDF a luchdadh gu tur airson clò-bhualadh. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Toglaich am bàr-taoibh +pdfjs-toggle-sidebar-notification-button = + .title = Toglaich am bàr-taoibh (tha oir-loidhne/ceanglachain/breathan aig an sgrìobhainn) +pdfjs-toggle-sidebar-button-label = Toglaich am bàr-taoibh +pdfjs-document-outline-button = + .title = Seall oir-loidhne na sgrìobhainn (dèan briogadh dùbailte airson a h-uile nì a leudachadh/a cho-theannadh) +pdfjs-document-outline-button-label = Oir-loidhne na sgrìobhainne +pdfjs-attachments-button = + .title = Seall na ceanglachain +pdfjs-attachments-button-label = Ceanglachain +pdfjs-layers-button = + .title = Seall na breathan (dèan briogadh dùbailte airson a h-uile breath ath-shuidheachadh dhan staid bhunaiteach) +pdfjs-layers-button-label = Breathan +pdfjs-thumbs-button = + .title = Seall na dealbhagan +pdfjs-thumbs-button-label = Dealbhagan +pdfjs-current-outline-item-button = + .title = Lorg nì làithreach na h-oir-loidhne +pdfjs-current-outline-item-button-label = Nì làithreach na h-oir-loidhne +pdfjs-findbar-button = + .title = Lorg san sgrìobhainn +pdfjs-findbar-button-label = Lorg +pdfjs-additional-layers = Barrachd breathan + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Duilleag a { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Dealbhag duilleag a { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Lorg + .placeholder = Lorg san sgrìobhainn... +pdfjs-find-previous-button = + .title = Lorg làthair roimhe na h-abairt seo +pdfjs-find-previous-button-label = Air ais +pdfjs-find-next-button = + .title = Lorg ath-làthair na h-abairt seo +pdfjs-find-next-button-label = Air adhart +pdfjs-find-highlight-checkbox = Soillsich a h-uile +pdfjs-find-match-case-checkbox-label = Aire do litrichean mòra is beaga +pdfjs-find-match-diacritics-checkbox-label = Aire do stràcan +pdfjs-find-entire-word-checkbox-label = Faclan-slàna +pdfjs-find-reached-top = Ràinig sinn barr na duilleige, a' leantainn air adhart o bhonn na duilleige +pdfjs-find-reached-bottom = Ràinig sinn bonn na duilleige, a' leantainn air adhart o bharr na duilleige +pdfjs-find-not-found = Cha deach an abairt a lorg + +## Predefined zoom values + +pdfjs-page-scale-width = Leud na duilleige +pdfjs-page-scale-fit = Freagair ri meud na duilleige +pdfjs-page-scale-auto = Sùm fèin-obrachail +pdfjs-page-scale-actual = Am fìor-mheud +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Duilleag { $page } + +## Loading indicator messages + +pdfjs-loading-error = Thachair mearachd rè luchdadh a' PDF. +pdfjs-invalid-file-error = Faidhle PDF a tha mì-dhligheach no coirbte. +pdfjs-missing-file-error = Faidhle PDF a tha a dhìth. +pdfjs-unexpected-response-error = Freagairt on fhrithealaiche ris nach robh dùil. +pdfjs-rendering-error = Thachair mearachd rè reandaradh na duilleige. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Nòtachadh { $type }] + +## Password + +pdfjs-password-label = Cuir a-steach am facal-faire gus am faidhle PDF seo fhosgladh. +pdfjs-password-invalid = Tha am facal-faire cearr. Nach fheuch thu ris a-rithist? +pdfjs-password-ok-button = Ceart ma-thà +pdfjs-password-cancel-button = Sguir dheth +pdfjs-web-fonts-disabled = Tha cruthan-clò lìn à comas: Chan urrainn dhuinn cruthan-clò PDF leabaichte a chleachdadh. + +## Editing + +pdfjs-editor-free-text-button = + .title = Teacsa +pdfjs-editor-free-text-button-label = Teacsa +pdfjs-editor-ink-button = + .title = Tarraing +pdfjs-editor-ink-button-label = Tarraing + +## Remove button for the various kind of editor. + + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Dath +pdfjs-editor-free-text-size-input = Meud +pdfjs-editor-ink-color-input = Dath +pdfjs-editor-ink-thickness-input = Tighead +pdfjs-editor-ink-opacity-input = Trìd-dhoilleireachd +pdfjs-free-text = + .aria-label = An deasaiche teacsa +pdfjs-free-text-default-content = Tòisich air sgrìobhadh… +pdfjs-ink = + .aria-label = An deasaiche tharraingean +pdfjs-ink-canvas = + .aria-label = Dealbh a chruthaich cleachdaiche + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + + +## Color picker + + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + + +## Image alt-text settings + diff --git a/public/assets/pdfjs/locale/gl/viewer.ftl b/public/assets/pdfjs/locale/gl/viewer.ftl new file mode 100755 index 0000000..641a607 --- /dev/null +++ b/public/assets/pdfjs/locale/gl/viewer.ftl @@ -0,0 +1,385 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Páxina anterior +pdfjs-previous-button-label = Anterior +pdfjs-next-button = + .title = Seguinte páxina +pdfjs-next-button-label = Seguinte +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Páxina +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = de { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount }) +pdfjs-zoom-out-button = + .title = Reducir +pdfjs-zoom-out-button-label = Reducir +pdfjs-zoom-in-button = + .title = Ampliar +pdfjs-zoom-in-button-label = Ampliar +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Cambiar ao modo presentación +pdfjs-presentation-mode-button-label = Modo presentación +pdfjs-open-file-button = + .title = Abrir ficheiro +pdfjs-open-file-button-label = Abrir +pdfjs-print-button = + .title = Imprimir +pdfjs-print-button-label = Imprimir +pdfjs-save-button = + .title = Gardar +pdfjs-save-button-label = Gardar +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Descargar +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Descargar +pdfjs-bookmark-button = + .title = Páxina actual (ver o URL da páxina actual) +pdfjs-bookmark-button-label = Páxina actual + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Ferramentas +pdfjs-tools-button-label = Ferramentas +pdfjs-first-page-button = + .title = Ir á primeira páxina +pdfjs-first-page-button-label = Ir á primeira páxina +pdfjs-last-page-button = + .title = Ir á última páxina +pdfjs-last-page-button-label = Ir á última páxina +pdfjs-page-rotate-cw-button = + .title = Rotar no sentido das agullas do reloxo +pdfjs-page-rotate-cw-button-label = Rotar no sentido das agullas do reloxo +pdfjs-page-rotate-ccw-button = + .title = Rotar no sentido contrario ás agullas do reloxo +pdfjs-page-rotate-ccw-button-label = Rotar no sentido contrario ás agullas do reloxo +pdfjs-cursor-text-select-tool-button = + .title = Activar a ferramenta de selección de texto +pdfjs-cursor-text-select-tool-button-label = Ferramenta de selección de texto +pdfjs-cursor-hand-tool-button = + .title = Activar a ferramenta de man +pdfjs-cursor-hand-tool-button-label = Ferramenta de man +pdfjs-scroll-page-button = + .title = Usar o desprazamento da páxina +pdfjs-scroll-page-button-label = Desprazamento da páxina +pdfjs-scroll-vertical-button = + .title = Usar o desprazamento vertical +pdfjs-scroll-vertical-button-label = Desprazamento vertical +pdfjs-scroll-horizontal-button = + .title = Usar o desprazamento horizontal +pdfjs-scroll-horizontal-button-label = Desprazamento horizontal +pdfjs-scroll-wrapped-button = + .title = Usar o desprazamento en bloque +pdfjs-scroll-wrapped-button-label = Desprazamento por bloque +pdfjs-spread-none-button = + .title = Non agrupar páxinas +pdfjs-spread-none-button-label = Ningún agrupamento +pdfjs-spread-odd-button = + .title = Crea grupo de páxinas que comezan con números de páxina impares +pdfjs-spread-odd-button-label = Agrupamento impar +pdfjs-spread-even-button = + .title = Crea grupo de páxinas que comezan con números de páxina pares +pdfjs-spread-even-button-label = Agrupamento par + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Propiedades do documento… +pdfjs-document-properties-button-label = Propiedades do documento… +pdfjs-document-properties-file-name = Nome do ficheiro: +pdfjs-document-properties-file-size = Tamaño do ficheiro: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Título: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Asunto: +pdfjs-document-properties-keywords = Palabras clave: +pdfjs-document-properties-creation-date = Data de creación: +pdfjs-document-properties-modification-date = Data de modificación: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Creado por: +pdfjs-document-properties-producer = Xenerador do PDF: +pdfjs-document-properties-version = Versión de PDF: +pdfjs-document-properties-page-count = Número de páxinas: +pdfjs-document-properties-page-size = Tamaño da páxina: +pdfjs-document-properties-page-size-unit-inches = pol +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = vertical +pdfjs-document-properties-page-size-orientation-landscape = horizontal +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Carta +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Visualización rápida das páxinas web: +pdfjs-document-properties-linearized-yes = Si +pdfjs-document-properties-linearized-no = Non +pdfjs-document-properties-close-button = Pechar + +## Print + +pdfjs-print-progress-message = Preparando o documento para imprimir… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Cancelar +pdfjs-printing-not-supported = Aviso: A impresión non é compatíbel de todo con este navegador. +pdfjs-printing-not-ready = Aviso: O PDF non se cargou completamente para imprimirse. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Amosar/agochar a barra lateral +pdfjs-toggle-sidebar-notification-button = + .title = Alternar barra lateral (o documento contén esquema/anexos/capas) +pdfjs-toggle-sidebar-button-label = Amosar/agochar a barra lateral +pdfjs-document-outline-button = + .title = Amosar a estrutura do documento (dobre clic para expandir/contraer todos os elementos) +pdfjs-document-outline-button-label = Estrutura do documento +pdfjs-attachments-button = + .title = Amosar anexos +pdfjs-attachments-button-label = Anexos +pdfjs-layers-button = + .title = Mostrar capas (prema dúas veces para restaurar todas as capas o estado predeterminado) +pdfjs-layers-button-label = Capas +pdfjs-thumbs-button = + .title = Amosar miniaturas +pdfjs-thumbs-button-label = Miniaturas +pdfjs-current-outline-item-button = + .title = Atopar o elemento delimitado actualmente +pdfjs-current-outline-item-button-label = Elemento delimitado actualmente +pdfjs-findbar-button = + .title = Atopar no documento +pdfjs-findbar-button-label = Atopar +pdfjs-additional-layers = Capas adicionais + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Páxina { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura da páxina { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Atopar + .placeholder = Atopar no documento… +pdfjs-find-previous-button = + .title = Atopar a anterior aparición da frase +pdfjs-find-previous-button-label = Anterior +pdfjs-find-next-button = + .title = Atopar a seguinte aparición da frase +pdfjs-find-next-button-label = Seguinte +pdfjs-find-highlight-checkbox = Realzar todo +pdfjs-find-match-case-checkbox-label = Diferenciar maiúsculas de minúsculas +pdfjs-find-match-diacritics-checkbox-label = Distinguir os diacríticos +pdfjs-find-entire-word-checkbox-label = Palabras completas +pdfjs-find-reached-top = Chegouse ao inicio do documento, continuar desde o final +pdfjs-find-reached-bottom = Chegouse ao final do documento, continuar desde o inicio +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] Coincidencia { $current } de { $total } + *[other] Coincidencia { $current } de { $total } + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Máis de { $limit } coincidencia + *[other] Máis de { $limit } coincidencias + } +pdfjs-find-not-found = Non se atopou a frase + +## Predefined zoom values + +pdfjs-page-scale-width = Largura da páxina +pdfjs-page-scale-fit = Axuste de páxina +pdfjs-page-scale-auto = Zoom automático +pdfjs-page-scale-actual = Tamaño actual +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Páxina { $page } + +## Loading indicator messages + +pdfjs-loading-error = Produciuse un erro ao cargar o PDF. +pdfjs-invalid-file-error = Ficheiro PDF danado ou non válido. +pdfjs-missing-file-error = Falta o ficheiro PDF. +pdfjs-unexpected-response-error = Resposta inesperada do servidor. +pdfjs-rendering-error = Produciuse un erro ao representar a páxina. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Anotación { $type }] + +## Password + +pdfjs-password-label = Escriba o contrasinal para abrir este ficheiro PDF. +pdfjs-password-invalid = Contrasinal incorrecto. Tente de novo. +pdfjs-password-ok-button = Aceptar +pdfjs-password-cancel-button = Cancelar +pdfjs-web-fonts-disabled = Desactiváronse as fontes web: foi imposíbel usar as fontes incrustadas no PDF. + +## Editing + +pdfjs-editor-free-text-button = + .title = Texto +pdfjs-editor-free-text-button-label = Texto +pdfjs-editor-ink-button = + .title = Debuxo +pdfjs-editor-ink-button-label = Debuxo +pdfjs-editor-stamp-button = + .title = Engadir ou editar imaxes +pdfjs-editor-stamp-button-label = Engadir ou editar imaxes + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-freetext-button = + .title = Eliminar o texto +pdfjs-editor-remove-stamp-button = + .title = Eliminar a imaxe +pdfjs-editor-remove-highlight-button = + .title = Eliminar o resaltado + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Cor +pdfjs-editor-free-text-size-input = Tamaño +pdfjs-editor-ink-color-input = Cor +pdfjs-editor-ink-thickness-input = Grosor +pdfjs-editor-ink-opacity-input = Opacidade +pdfjs-editor-stamp-add-image-button = + .title = Engadir imaxe +pdfjs-editor-stamp-add-image-button-label = Engadir imaxe +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Grosor +pdfjs-free-text = + .aria-label = Editor de texto +pdfjs-free-text-default-content = Comezar a teclear… +pdfjs-ink = + .aria-label = Editor de debuxos +pdfjs-ink-canvas = + .aria-label = Imaxe creada por unha usuaria + +## Alt-text dialog + +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button-label = Texto alternativo +pdfjs-editor-alt-text-edit-button-label = Editar o texto alternativo +pdfjs-editor-alt-text-dialog-label = Escoller unha opción +pdfjs-editor-alt-text-add-description-label = Engadir unha descrición +pdfjs-editor-alt-text-mark-decorative-label = Marcar como decorativo +pdfjs-editor-alt-text-mark-decorative-description = Utilízase para imaxes ornamentais, como bordos ou marcas de auga. +pdfjs-editor-alt-text-cancel-button = Cancelar +pdfjs-editor-alt-text-save-button = Gardar +pdfjs-editor-alt-text-decorative-tooltip = Marcado como decorativo +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Por exemplo, «Un mozo séntase á mesa para comer» + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Esquina superior esquerda: cambia o tamaño +pdfjs-editor-resizer-label-top-middle = Medio superior: cambia o tamaño +pdfjs-editor-resizer-label-top-right = Esquina superior dereita: cambia o tamaño +pdfjs-editor-resizer-label-middle-right = Medio dereito: cambia o tamaño +pdfjs-editor-resizer-label-bottom-right = Esquina inferior dereita: cambia o tamaño +pdfjs-editor-resizer-label-bottom-middle = Abaixo medio: cambia o tamaño +pdfjs-editor-resizer-label-bottom-left = Esquina inferior esquerda: cambia o tamaño +pdfjs-editor-resizer-label-middle-left = Medio esquerdo: cambia o tamaño +pdfjs-editor-resizer-top-left = + .aria-label = Esquina superior esquerda: cambia o tamaño +pdfjs-editor-resizer-top-middle = + .aria-label = Medio superior: cambia o tamaño +pdfjs-editor-resizer-top-right = + .aria-label = Esquina superior dereita: cambia o tamaño +pdfjs-editor-resizer-middle-right = + .aria-label = Medio dereito: cambia o tamaño +pdfjs-editor-resizer-bottom-right = + .aria-label = Esquina inferior dereita: cambia o tamaño +pdfjs-editor-resizer-bottom-middle = + .aria-label = Abaixo medio: cambia o tamaño +pdfjs-editor-resizer-bottom-left = + .aria-label = Esquina inferior esquerda: cambia o tamaño +pdfjs-editor-resizer-middle-left = + .aria-label = Medio esquerdo: cambia o tamaño + +## Color picker + + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + + +## Image alt-text settings + diff --git a/public/assets/pdfjs/locale/gn/viewer.ftl b/public/assets/pdfjs/locale/gn/viewer.ftl new file mode 100755 index 0000000..6402c6f --- /dev/null +++ b/public/assets/pdfjs/locale/gn/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Kuatiarogue mboyvegua +pdfjs-previous-button-label = Mboyvegua +pdfjs-next-button = + .title = Kuatiarogue upeigua +pdfjs-next-button-label = Upeigua +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Kuatiarogue +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = { $pagesCount } gui +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount }) +pdfjs-zoom-out-button = + .title = MomichÄ© +pdfjs-zoom-out-button-label = MomichÄ© +pdfjs-zoom-in-button = + .title = Mbotuicha +pdfjs-zoom-in-button-label = Mbotuicha +pdfjs-zoom-select = + .title = Tuichakue +pdfjs-presentation-mode-button = + .title = Jehechauka reko moambue +pdfjs-presentation-mode-button-label = Jehechauka reko +pdfjs-open-file-button = + .title = Marandurendápe jeike +pdfjs-open-file-button-label = Jeike +pdfjs-print-button = + .title = Monguatia +pdfjs-print-button-label = Monguatia +pdfjs-save-button = + .title = Ñongatu +pdfjs-save-button-label = Ñongatu +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Mboguejy +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Mboguejy +pdfjs-bookmark-button = + .title = Kuatiarogue ag̃agua (Ehecha URL kuatiarogue ag̃agua) +pdfjs-bookmark-button-label = Kuatiarogue Ag̃agua + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Tembiporu +pdfjs-tools-button-label = Tembiporu +pdfjs-first-page-button = + .title = Kuatiarogue ñepyrÅ©me jeho +pdfjs-first-page-button-label = Kuatiarogue ñepyrÅ©me jeho +pdfjs-last-page-button = + .title = Kuatiarogue pahápe jeho +pdfjs-last-page-button-label = Kuatiarogue pahápe jeho +pdfjs-page-rotate-cw-button = + .title = Aravóicha mbojere +pdfjs-page-rotate-cw-button-label = Aravóicha mbojere +pdfjs-page-rotate-ccw-button = + .title = Aravo rapykue gotyo mbojere +pdfjs-page-rotate-ccw-button-label = Aravo rapykue gotyo mbojere +pdfjs-cursor-text-select-tool-button = + .title = Emyandy moñe’ẽrã jeporavo rembiporu +pdfjs-cursor-text-select-tool-button-label = Moñe’ẽrã jeporavo rembiporu +pdfjs-cursor-hand-tool-button = + .title = Tembiporu po pegua myandy +pdfjs-cursor-hand-tool-button-label = Tembiporu po pegua +pdfjs-scroll-page-button = + .title = Eiporu kuatiarogue jeku’e +pdfjs-scroll-page-button-label = Kuatiarogue jeku’e +pdfjs-scroll-vertical-button = + .title = Eiporu jeku’e ykeguáva +pdfjs-scroll-vertical-button-label = Jeku’e ykeguáva +pdfjs-scroll-horizontal-button = + .title = Eiporu jeku’e yvate gotyo +pdfjs-scroll-horizontal-button-label = Jeku’e yvate gotyo +pdfjs-scroll-wrapped-button = + .title = Eiporu jeku’e mbohyrupyre +pdfjs-scroll-wrapped-button-label = Jeku’e mbohyrupyre +pdfjs-spread-none-button = + .title = Ani ejuaju spreads kuatiarogue ndive +pdfjs-spread-none-button-label = Spreads ỹre +pdfjs-spread-odd-button = + .title = Embojuaju kuatiarogue jepysokue eñepyrÅ©vo kuatiarogue impar-vagui +pdfjs-spread-odd-button-label = Spreads impar +pdfjs-spread-even-button = + .title = Embojuaju kuatiarogue jepysokue eñepyrÅ©vo kuatiarogue par-vagui +pdfjs-spread-even-button-label = Ipukuve uvei + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Kuatia mba’etee… +pdfjs-document-properties-button-label = Kuatia mba’etee… +pdfjs-document-properties-file-name = Marandurenda réra: +pdfjs-document-properties-file-size = Marandurenda tuichakue: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Teratee: +pdfjs-document-properties-author = Apohára: +pdfjs-document-properties-subject = Mba’egua: +pdfjs-document-properties-keywords = Jehero: +pdfjs-document-properties-creation-date = Teñoihague arange: +pdfjs-document-properties-modification-date = Iñambue hague arange: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Apo’ypyha: +pdfjs-document-properties-producer = PDF mbosako’iha: +pdfjs-document-properties-version = PDF mbojuehegua: +pdfjs-document-properties-page-count = Kuatiarogue papapy: +pdfjs-document-properties-page-size = Kuatiarogue tuichakue: +pdfjs-document-properties-page-size-unit-inches = Amo +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = OÄ©háicha +pdfjs-document-properties-page-size-orientation-landscape = apaisado +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Kuatiañe’ẽ +pdfjs-document-properties-page-size-name-legal = Tee + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Ñanduti jahecha pya’e: +pdfjs-document-properties-linearized-yes = Añete +pdfjs-document-properties-linearized-no = Ahániri +pdfjs-document-properties-close-button = Mboty + +## Print + +pdfjs-print-progress-message = Embosako’i kuatia emonguatia hag̃ua… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Heja +pdfjs-printing-not-supported = Kyhyjerã: Ñembokuatia ndojokupytypái ko kundahára ndive. +pdfjs-printing-not-ready = Kyhyjerã: Ko PDF nahenyhẽmbái oñembokuatia hag̃uáicha. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Tenda yke moambue +pdfjs-toggle-sidebar-notification-button = + .title = Embojopyru tenda ykegua (kuatia oguereko kuaakaha/moirÅ©ha/ñuãha) +pdfjs-toggle-sidebar-button-label = Tenda yke moambue +pdfjs-document-outline-button = + .title = Ehechauka kuatia rape (eikutu mokõi jey embotuicha/emomichÄ© hag̃ua opavavete mba’eporu) +pdfjs-document-outline-button-label = Kuatia apopyre +pdfjs-attachments-button = + .title = MoirÅ©ha jehechauka +pdfjs-attachments-button-label = MoirÅ©ha +pdfjs-layers-button = + .title = Ehechauka ñuãha (eikutu jo’a emomba’apo hag̃ua opaite ñuãha tekoypýpe) +pdfjs-layers-button-label = Ñuãha +pdfjs-thumbs-button = + .title = Mba’emirÄ© jehechauka +pdfjs-thumbs-button-label = Mba’emirÄ© +pdfjs-current-outline-item-button = + .title = Eheka mba’eporu ag̃aguaitéva +pdfjs-current-outline-item-button-label = Mba’eporu ag̃aguaitéva +pdfjs-findbar-button = + .title = Kuatiápe jeheka +pdfjs-findbar-button-label = Juhu +pdfjs-additional-layers = Ñuãha moirÅ©guáva + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Kuatiarogue { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Kuatiarogue mba’emirÄ© { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Juhu + .placeholder = Kuatiápe jejuhu… +pdfjs-find-previous-button = + .title = Ejuhu ñe’ẽrysýi osẽ’ypy hague +pdfjs-find-previous-button-label = Mboyvegua +pdfjs-find-next-button = + .title = Eho ñe’ẽ juhupyre upeiguávape +pdfjs-find-next-button-label = Upeigua +pdfjs-find-highlight-checkbox = Embojekuaavepa +pdfjs-find-match-case-checkbox-label = Ejesareko taiguasu/taimichÄ©re +pdfjs-find-match-diacritics-checkbox-label = Diacrítico moñondive +pdfjs-find-entire-word-checkbox-label = Ñe’ẽ oÄ©mbáva +pdfjs-find-reached-top = Ojehupyty kuatia ñepyrÅ©, oku’ejeýta kuatia paha guive +pdfjs-find-reached-bottom = Ojehupyty kuatia paha, oku’ejeýta kuatia ñepyrÅ© guive +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } ha { $total } ojueheguáva + *[other] { $current } ha { $total } ojueheguáva + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Hetave { $limit } ojueheguáva + *[other] Hetave { $limit } ojueheguáva + } +pdfjs-find-not-found = Ñe’ẽrysýi ojejuhu’ỹva + +## Predefined zoom values + +pdfjs-page-scale-width = Kuatiarogue pekue +pdfjs-page-scale-fit = Kuatiarogue ñemoÄ©porã +pdfjs-page-scale-auto = Tuichakue ijeheguíva +pdfjs-page-scale-actual = Tuichakue ag̃agua +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Kuatiarogue { $page } + +## Loading indicator messages + +pdfjs-loading-error = Oiko jejavy PDF oñemyeñyhẽnguévo. +pdfjs-invalid-file-error = PDF marandurenda ndoikóiva térã ivaipyréva. +pdfjs-missing-file-error = Ndaipóri PDF marandurenda +pdfjs-unexpected-response-error = Mohendahavusu mbohovái eha’ãrõ’ỹva. +pdfjs-rendering-error = Oiko jejavy ehechaukasévo kuatiarogue. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Jehaipy { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Emoinge ñe’ẽñemi eipe’a hag̃ua ko marandurenda PDF. +pdfjs-password-invalid = Ñe’ẽñemi ndoikóiva. Eha’ã jey. +pdfjs-password-ok-button = MONEĨ +pdfjs-password-cancel-button = Heja +pdfjs-web-fonts-disabled = Ñanduti taity oñemongéma: ndaikatumo’ãi eiporu PDF jehai’íva taity. + +## Editing + +pdfjs-editor-free-text-button = + .title = Moñe’ẽrã +pdfjs-editor-free-text-button-label = Moñe’ẽrã +pdfjs-editor-ink-button = + .title = Moha’ãnga +pdfjs-editor-ink-button-label = Moha’ãnga +pdfjs-editor-stamp-button = + .title = Embojuaju térã embosako’i ta’ãnga +pdfjs-editor-stamp-button-label = Embojuaju térã embosako’i ta’ãnga +pdfjs-editor-highlight-button = + .title = Mbosa’y +pdfjs-editor-highlight-button-label = Mbosa’y +pdfjs-highlight-floating-button1 = + .title = Mbosa’y + .aria-label = Mbosa’y +pdfjs-highlight-floating-button-label = Mbosa’y + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Emboguete ta’ãnga +pdfjs-editor-remove-freetext-button = + .title = Emboguete moñe’ẽrã +pdfjs-editor-remove-stamp-button = + .title = Emboguete ta’ãnga +pdfjs-editor-remove-highlight-button = + .title = Eipe’a jehechaveha + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Sa’y +pdfjs-editor-free-text-size-input = Tuichakue +pdfjs-editor-ink-color-input = Sa’y +pdfjs-editor-ink-thickness-input = Anambusu +pdfjs-editor-ink-opacity-input = PytÅ©ngy +pdfjs-editor-stamp-add-image-button = + .title = Embojuaju ta’ãnga +pdfjs-editor-stamp-add-image-button-label = Embojuaju ta’ãnga +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Anambusu +pdfjs-editor-free-highlight-thickness-title = + .title = Emoambue anambusukue embosa’ývo mba’eporu ha’e’ỹva moñe’ẽrã +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Moñe’ẽrã moheñoiha + .default-content = EñepyrÅ© ehai… +pdfjs-free-text = + .aria-label = Moñe’ẽrã moheñoiha +pdfjs-free-text-default-content = Ehai ñepyrũ… +pdfjs-ink = + .aria-label = Ta’ãnga moheñoiha +pdfjs-ink-canvas = + .aria-label = Ta’ãnga omoheñóiva poruhára + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Moñe’ẽrã mokõiháva +pdfjs-editor-alt-text-edit-button = + .aria-label = Embojuruja moñe’ẽrã mokõiháva +pdfjs-editor-alt-text-edit-button-label = Embojuruja moñe’ẽrã mokõiháva +pdfjs-editor-alt-text-dialog-label = Eiporavo poravorã +pdfjs-editor-alt-text-dialog-description = Moñe’ẽrã ykepegua (moñe’ẽrã ykepegua) nepytyvõ nderehecháiramo ta’ãnga térã nahenyhẽiramo. +pdfjs-editor-alt-text-add-description-label = Embojuaju ñemoha’ãnga +pdfjs-editor-alt-text-add-description-description = Ehaimi 1 térã 2 ñe’ẽjuaju oñe’ẽva pe téma rehe, ijere térã mba’eapóre. +pdfjs-editor-alt-text-mark-decorative-label = Emongurusu jeguakárõ +pdfjs-editor-alt-text-mark-decorative-description = Ojeporu ta’ãnga jeguakarã, tembe’y térã ta’ãnga ruguarãramo. +pdfjs-editor-alt-text-cancel-button = Heja +pdfjs-editor-alt-text-save-button = Ñongatu +pdfjs-editor-alt-text-decorative-tooltip = Jeguakárõ mongurusupyre +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Techapyrã: “PeteÄ© mitãrusu oguapy mesápe okaru hag̃ua†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Moñe’ẽrã mokõiháva + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Yvate asu gotyo — emoambue tuichakue +pdfjs-editor-resizer-label-top-middle = Yvate mbytépe — emoambue tuichakue +pdfjs-editor-resizer-label-top-right = Yvate akatúape — emoambue tuichakue +pdfjs-editor-resizer-label-middle-right = Mbyte akatúape — emoambue tuichakue +pdfjs-editor-resizer-label-bottom-right = Yvy gotyo akatúape — emoambue tuichakue +pdfjs-editor-resizer-label-bottom-middle = Yvy gotyo mbytépe — emoambue tuichakue +pdfjs-editor-resizer-label-bottom-left = Iguýpe asu gotyo — emoambue tuichakue +pdfjs-editor-resizer-label-middle-left = Mbyte asu gotyo — emoambue tuichakue +pdfjs-editor-resizer-top-left = + .aria-label = Yvate asu gotyo — emoambue tuichakue +pdfjs-editor-resizer-top-middle = + .aria-label = Yvate mbytépe — emoambue tuichakue +pdfjs-editor-resizer-top-right = + .aria-label = Yvate akatúape — emoambue tuichakue +pdfjs-editor-resizer-middle-right = + .aria-label = Mbyte akatúape — emoambue tuichakue +pdfjs-editor-resizer-bottom-right = + .aria-label = Yvy gotyo akatúape — emoambue tuichakue +pdfjs-editor-resizer-bottom-middle = + .aria-label = Yvy gotyo mbytépe — emoambue tuichakue +pdfjs-editor-resizer-bottom-left = + .aria-label = Iguýpe asu gotyo — emoambue tuichakue +pdfjs-editor-resizer-middle-left = + .aria-label = Mbyte asu gotyo — emoambue tuichakue + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Jehechaveha sa’y +pdfjs-editor-colorpicker-button = + .title = Emoambue sa’y +pdfjs-editor-colorpicker-dropdown = + .aria-label = Sa’y poravopyrã +pdfjs-editor-colorpicker-yellow = + .title = Sa’yju +pdfjs-editor-colorpicker-green = + .title = HovyÅ© +pdfjs-editor-colorpicker-blue = + .title = Hovy +pdfjs-editor-colorpicker-pink = + .title = Pytãngy +pdfjs-editor-colorpicker-red = + .title = Pyha + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Techaukapa +pdfjs-editor-highlight-show-all-button = + .title = Techaukapa + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Embosako’i moñe’ẽrã mokõiha (ta’ãngáre ñeñe’ẽ) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Embojuaju moñe’ẽrã mokõiha (ta’ãngáre ñeñe’ẽ) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Edescribi ko’ápe… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Ñemyesakã mbykymi opavave ohecha’ỹva upe ta’ãnga térã pe ta’ãnga nahenyhẽiramo. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Ko moñe’ẽrã mokõiha oñemoheñói ijehegui ha ikatu ndoikoporãi. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Eikuaave +pdfjs-editor-new-alt-text-create-automatically-button-label = Emoheñói moñe’ẽrã mokõiha ijeheguíva +pdfjs-editor-new-alt-text-not-now-button = Ani ko’ág̃a +pdfjs-editor-new-alt-text-error-title = Noñemoheñói moñe’ẽrã mokõiha ijeheguíva +pdfjs-editor-new-alt-text-error-description = Ehai ne moñe’ẽrã mokõiha térã eha’ã jey ag̃amieve. +pdfjs-editor-new-alt-text-error-close-button = Mboty +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Emboguejyhína IA moñe’ẽrã mokõiháva ({ $downloadedSize } { $totalSize } MB) mba’e + .aria-valuetext = Emboguejyhína IA moñe’ẽrã mokõiháva ({ $downloadedSize } { $totalSize } MB) mba’e +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Moñe’ẽrã mokõiha mbojuajupyre +pdfjs-editor-new-alt-text-added-button-label = Oñembojuaju moñe’ẽrã mokõiha +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Ndaipóri moñe’ẽrã mokõiha +pdfjs-editor-new-alt-text-missing-button-label = Ndaipóri moñe’ẽrã mokõiha +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Ehechajey moñe’ẽrã mokõiha +pdfjs-editor-new-alt-text-to-review-button-label = Ehechajey moñe’ẽrã mokõiha +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Heñóiva ijeheguiete: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Ta’ãnga moñe’ẽrã mokõiha ñemboheko +pdfjs-image-alt-text-settings-button-label = Ta’ãnga moñe’ẽrã mokõiha ñemboheko +pdfjs-editor-alt-text-settings-dialog-label = Ta’ãnga moñe’ẽrã mokõiha ñemboheko +pdfjs-editor-alt-text-settings-automatic-title = Moñe’ẽrã mokõiha ijeheguíva +pdfjs-editor-alt-text-settings-create-model-button-label = Emoheñói moñe’ẽrã mokõiha ijeheguíva +pdfjs-editor-alt-text-settings-create-model-description = Ñemyesakã mbykymi opavave tapicha ohecha’ỹva upe ta’ãnga térã pe ta’ãnga nahenyhẽiramo. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = PeteÄ©va IA moñe’ẽrã mokõiha ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Oku’e mba’e’okaitépe umi mba’ekuaarã hekoñemi hag̃ua. Tekotevẽva moñe’ẽrã ykegua ijeheguívape. +pdfjs-editor-alt-text-settings-delete-model-button = Mboguete +pdfjs-editor-alt-text-settings-download-model-button = Mboguejy +pdfjs-editor-alt-text-settings-downloading-model-button = Emboguejyhína… +pdfjs-editor-alt-text-settings-editor-title = Moñe’ẽrã mokõiha mbosako’iha +pdfjs-editor-alt-text-settings-show-dialog-button-label = Ehechauka moñe’ẽrã mokõiha mbosako’iha embojuajúvo ta’ãnga +pdfjs-editor-alt-text-settings-show-dialog-description = Nepytyvõta ta’ãngakuéra orekotaha moñe’ẽrã mokõiha. +pdfjs-editor-alt-text-settings-close-button = Mboty + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Mbosa’ýva mboguete +pdfjs-editor-undo-bar-message-freetext = Moñe’ẽrã mboguepyre +pdfjs-editor-undo-bar-message-ink = Ta’ãnga mboguepyre +pdfjs-editor-undo-bar-message-stamp = Ta’ãnga mboguepyre +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } jehaikue mboguepyre + *[other] { $count } jehaikue mboguepyre + } +pdfjs-editor-undo-bar-undo-button = + .title = Mboguevi +pdfjs-editor-undo-bar-undo-button-label = Mboguevi +pdfjs-editor-undo-bar-close-button = + .title = Mboty +pdfjs-editor-undo-bar-close-button-label = Mboty diff --git a/public/assets/pdfjs/locale/gu-IN/viewer.ftl b/public/assets/pdfjs/locale/gu-IN/viewer.ftl new file mode 100755 index 0000000..5d8bb54 --- /dev/null +++ b/public/assets/pdfjs/locale/gu-IN/viewer.ftl @@ -0,0 +1,247 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = પહેલાનૠપાનà«àª‚ +pdfjs-previous-button-label = પહેલાનૠ+pdfjs-next-button = + .title = આગળનૠપાનà«àª‚ +pdfjs-next-button-label = આગળનà«àª‚ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = પાનà«àª‚ +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = નો { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } નો { $pagesCount }) +pdfjs-zoom-out-button = + .title = મોટૠકરો +pdfjs-zoom-out-button-label = મોટૠકરો +pdfjs-zoom-in-button = + .title = નાનà«àª‚ કરો +pdfjs-zoom-in-button-label = નાનà«àª‚ કરો +pdfjs-zoom-select = + .title = નાનà«àª‚ મોટૠકરો +pdfjs-presentation-mode-button = + .title = રજૂઆત સà«àª¥àª¿àª¤àª¿àª®àª¾àª‚ જાવ +pdfjs-presentation-mode-button-label = રજૂઆત સà«àª¥àª¿àª¤àª¿ +pdfjs-open-file-button = + .title = ફાઇલ ખોલો +pdfjs-open-file-button-label = ખોલો +pdfjs-print-button = + .title = છાપો +pdfjs-print-button-label = છારો + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = સાધનો +pdfjs-tools-button-label = સાધનો +pdfjs-first-page-button = + .title = પહેલાં પાનામાં જાવ +pdfjs-first-page-button-label = પà«àª°àª¥àª® પાનાં પર જાવ +pdfjs-last-page-button = + .title = છેલà«àª²àª¾ પાનાં પર જાવ +pdfjs-last-page-button-label = છેલà«àª²àª¾ પાનાં પર જાવ +pdfjs-page-rotate-cw-button = + .title = ઘડિયાળનાં કાંટા તરફ ફેરવો +pdfjs-page-rotate-cw-button-label = ઘડિયાળનાં કાંટા તરફ ફેરવો +pdfjs-page-rotate-ccw-button = + .title = ઘડિયાળનાં કાંટાની ઉલટી દિશામાં ફેરવો +pdfjs-page-rotate-ccw-button-label = ઘડિયાળનાં કાંટાની વિરà«àª¦à«àª¦ ફેરવો +pdfjs-cursor-text-select-tool-button = + .title = ટેકà«àª¸à«àªŸ પસંદગી ટૂલ સકà«àª·àª® કરો +pdfjs-cursor-text-select-tool-button-label = ટેકà«àª¸à«àªŸ પસંદગી ટૂલ +pdfjs-cursor-hand-tool-button = + .title = હાથનાં સાધનને સકà«àª°àª¿àª¯ કરો +pdfjs-cursor-hand-tool-button-label = હેનà«àª¡ ટૂલ +pdfjs-scroll-vertical-button = + .title = ઊભી સà«àª•à«àª°à«‹àª²àª¿àª‚ગનો ઉપયોગ કરો +pdfjs-scroll-vertical-button-label = ઊભી સà«àª•à«àª°à«‹àª²àª¿àª‚ગ +pdfjs-scroll-horizontal-button = + .title = આડી સà«àª•à«àª°à«‹àª²àª¿àª‚ગનો ઉપયોગ કરો +pdfjs-scroll-horizontal-button-label = આડી સà«àª•à«àª°à«‹àª²àª¿àª‚ગ +pdfjs-scroll-wrapped-button = + .title = આવરિત સà«àª•à«àª°à«‹àª²àª¿àª‚ગનો ઉપયોગ કરો +pdfjs-scroll-wrapped-button-label = આવરિત સà«àª•à«àª°à«‹àª²àª¿àª‚ગ +pdfjs-spread-none-button = + .title = પૃષà«àª  સà«àªªà«àª°à«‡àª¡àª®àª¾àª‚ જોડાવશો નહીં +pdfjs-spread-none-button-label = કોઈ સà«àªªà«àª°à«‡àª¡ નથી +pdfjs-spread-odd-button = + .title = àªàª•à«€-કà«àª°àª®àª¾àª‚કિત પૃષà«àª à«‹ સાથે પà«àª°àª¾àª°àª‚ભ થતાં પૃષà«àª  સà«àªªà«àª°à«‡àª¡àª®àª¾àª‚ જોડાઓ +pdfjs-spread-odd-button-label = àªàª•à«€ સà«àªªà«àª°à«‡àª¡à«àª¸ +pdfjs-spread-even-button = + .title = નંબર-કà«àª°àª®àª¾àª‚કિત પૃષà«àª à«‹àª¥à«€ શરૂ થતાં પૃષà«àª  સà«àªªà«àª°à«‡àª¡àª®àª¾àª‚ જોડાઓ +pdfjs-spread-even-button-label = સરખà«àª‚ ફેલાવવà«àª‚ + +## Document properties dialog + +pdfjs-document-properties-button = + .title = દસà«àª¤àª¾àªµà«‡àªœ ગà«àª£àª§àª°à«àª®à«‹â€¦ +pdfjs-document-properties-button-label = દસà«àª¤àª¾àªµà«‡àªœ ગà«àª£àª§àª°à«àª®à«‹â€¦ +pdfjs-document-properties-file-name = ફાઇલ નામ: +pdfjs-document-properties-file-size = ફાઇલ માપ: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } બાઇટ) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } બાઇટ) +pdfjs-document-properties-title = શીરà«àª·àª•: +pdfjs-document-properties-author = લેખક: +pdfjs-document-properties-subject = વિષય: +pdfjs-document-properties-keywords = કિવરà«àª¡: +pdfjs-document-properties-creation-date = નિરà«àª®àª¾àª£ તારીખ: +pdfjs-document-properties-modification-date = ફેરફાર તારીખ: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = નિરà«àª®àª¾àª¤àª¾: +pdfjs-document-properties-producer = PDF નિરà«àª®àª¾àª¤àª¾: +pdfjs-document-properties-version = PDF આવૃતà«àª¤àª¿: +pdfjs-document-properties-page-count = પાનાં ગણતરી: +pdfjs-document-properties-page-size = પૃષà«àª àª¨à«àª‚ કદ: +pdfjs-document-properties-page-size-unit-inches = ઇંચ +pdfjs-document-properties-page-size-unit-millimeters = મીમી +pdfjs-document-properties-page-size-orientation-portrait = ઉભà«àª‚ +pdfjs-document-properties-page-size-orientation-landscape = આડૠ+pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = પતà«àª° +pdfjs-document-properties-page-size-name-legal = કાયદાકીય + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = àªàª¡àªªà«€ વૅબ દૃશà«àª¯: +pdfjs-document-properties-linearized-yes = હા +pdfjs-document-properties-linearized-no = ના +pdfjs-document-properties-close-button = બંધ કરો + +## Print + +pdfjs-print-progress-message = છાપકામ માટે દસà«àª¤àª¾àªµà«‡àªœ તૈયાર કરી રહà«àª¯àª¾ છે… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = રદ કરો +pdfjs-printing-not-supported = ચેતવણી: છાપવાનà«àª‚ આ બà«àª°àª¾àª‰àªàª° દà«àª¦àª¾àª°àª¾ સંપૂરà«àª£àªªàª£à«‡ આધારભૂત નથી. +pdfjs-printing-not-ready = Warning: PDF ઠછાપવા માટે સંપૂરà«àª£àªªàª£à«‡ લાવેલ છે. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = ટૉગલ બાજà«àªªàªŸà«àªŸà«€ +pdfjs-toggle-sidebar-button-label = ટૉગલ બાજà«àªªàªŸà«àªŸà«€ +pdfjs-document-outline-button = + .title = દસà«àª¤àª¾àªµà«‡àªœàª¨à«€ રૂપરેખા બતાવો(બધી આઇટમà«àª¸àª¨à«‡ વિસà«àª¤à«ƒàª¤/સંકà«àªšàª¿àª¤ કરવા માટે ડબલ-કà«àª²àª¿àª• કરો) +pdfjs-document-outline-button-label = દસà«àª¤àª¾àªµà«‡àªœ રૂપરેખા +pdfjs-attachments-button = + .title = જોડાણોને બતાવો +pdfjs-attachments-button-label = જોડાણો +pdfjs-thumbs-button = + .title = થંબનેલà«àª¸ બતાવો +pdfjs-thumbs-button-label = થંબનેલà«àª¸ +pdfjs-findbar-button = + .title = દસà«àª¤àª¾àªµà«‡àªœàª®àª¾àª‚ શોધો +pdfjs-findbar-button-label = શોધો + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = પાનà«àª‚ { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = પાનાં { $page } નà«àª‚ થંબનેલà«àª¸ + +## Find panel button title and messages + +pdfjs-find-input = + .title = શોધો + .placeholder = દસà«àª¤àª¾àªµà«‡àªœàª®àª¾àª‚ શોધો… +pdfjs-find-previous-button = + .title = શબà«àª¦àª¸àª®à«‚હની પાછલી ઘટનાને શોધો +pdfjs-find-previous-button-label = પહેલાંનૠ+pdfjs-find-next-button = + .title = શબà«àª¦àª¸àª®à«‚હની આગળની ઘટનાને શોધો +pdfjs-find-next-button-label = આગળનà«àª‚ +pdfjs-find-highlight-checkbox = બધૠપà«àª°àª•ાશિત કરો +pdfjs-find-match-case-checkbox-label = કેસ બંધબેસાડો +pdfjs-find-entire-word-checkbox-label = સંપૂરà«àª£ શબà«àª¦à«‹ +pdfjs-find-reached-top = દસà«àª¤àª¾àªµà«‡àªœàª¨àª¾àª‚ ટોચે પહોંચી ગયા, તળિયેથી ચાલૠકરેલ હતૠ+pdfjs-find-reached-bottom = દસà«àª¤àª¾àªµà«‡àªœàª¨àª¾àª‚ અંતે પહોંચી ગયા, ઉપરથી ચાલૠકરેલ હતૠ+pdfjs-find-not-found = શબà«àª¦àª¸àª®à«‚હ મળà«àª¯à« નથી + +## Predefined zoom values + +pdfjs-page-scale-width = પાનાની પહોળાઇ +pdfjs-page-scale-fit = પાનà«àª‚ બંધબેસતૠ+pdfjs-page-scale-auto = આપમેળે નાનà«àª‚મોટૠકરો +pdfjs-page-scale-actual = ચોકà«àª•સ માપ +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = ભૂલ ઉદà«àª­àªµà«€ જà«àª¯àª¾àª°à«‡ PDF ને લાવી રહà«àª¯àª¾ હોય. +pdfjs-invalid-file-error = અયોગà«àª¯ અથવા ભાંગેલ PDF ફાઇલ. +pdfjs-missing-file-error = ગà«àª® થયેલ PDF ફાઇલ. +pdfjs-unexpected-response-error = અનપેકà«àª·àª¿àª¤ સરà«àªµàª° પà«àª°àª¤àª¿àª¸àª¾àª¦. +pdfjs-rendering-error = ભૂલ ઉદà«àª­àªµà«€ જà«àª¯àª¾àª°à«‡ પાનાંનૠરેનà«àª¡ કરી રહà«àª¯àª¾ હોય. + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Annotation] + +## Password + +pdfjs-password-label = આ PDF ફાઇલને ખોલવા પાસવરà«àª¡àª¨à«‡ દાખલ કરો. +pdfjs-password-invalid = અયોગà«àª¯ પાસવરà«àª¡. મહેરબાની કરીને ફરી પà«àª°àª¯àª¤à«àª¨ કરો. +pdfjs-password-ok-button = બરાબર +pdfjs-password-cancel-button = રદ કરો +pdfjs-web-fonts-disabled = વેબ ફોનà«àªŸ નિષà«àª•à«àª°àª¿àª¯ થયેલ છે: àªàª®à«àª¬à«‡àª¡ થયેલ PDF ફોનà«àªŸàª¨à«‡ વાપરવાનà«àª‚ અસમરà«àª¥. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/he/viewer.ftl b/public/assets/pdfjs/locale/he/viewer.ftl new file mode 100755 index 0000000..08308c0 --- /dev/null +++ b/public/assets/pdfjs/locale/he/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = דף ×§×•×“× +pdfjs-previous-button-label = ×§×•×“× +pdfjs-next-button = + .title = דף ×”×‘× +pdfjs-next-button-label = ×”×‘× +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = דף +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = מתוך { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } מתוך { $pagesCount }) +pdfjs-zoom-out-button = + .title = התרחקות +pdfjs-zoom-out-button-label = התרחקות +pdfjs-zoom-in-button = + .title = התקרבות +pdfjs-zoom-in-button-label = התקרבות +pdfjs-zoom-select = + .title = מרחק מתצוגה +pdfjs-presentation-mode-button = + .title = מעבר למצב מצגת +pdfjs-presentation-mode-button-label = מצב מצגת +pdfjs-open-file-button = + .title = פתיחת קובץ +pdfjs-open-file-button-label = פתיחה +pdfjs-print-button = + .title = הדפסה +pdfjs-print-button-label = הדפסה +pdfjs-save-button = + .title = שמירה +pdfjs-save-button-label = שמירה +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = הורדה +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = הורדה +pdfjs-bookmark-button = + .title = עמוד נוכחי (הצגת כתובת ×”×תר מהעמוד הנוכחי) +pdfjs-bookmark-button-label = עמוד נוכחי + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = ×›×œ×™× +pdfjs-tools-button-label = ×›×œ×™× +pdfjs-first-page-button = + .title = מעבר לעמוד הר×שון +pdfjs-first-page-button-label = מעבר לעמוד הר×שון +pdfjs-last-page-button = + .title = מעבר לעמוד ×”×חרון +pdfjs-last-page-button-label = מעבר לעמוד ×”×חרון +pdfjs-page-rotate-cw-button = + .title = הטיה ×¢× ×›×™×•×•×Ÿ השעון +pdfjs-page-rotate-cw-button-label = הטיה ×¢× ×›×™×•×•×Ÿ השעון +pdfjs-page-rotate-ccw-button = + .title = הטיה כנגד כיוון השעון +pdfjs-page-rotate-ccw-button-label = הטיה כנגד כיוון השעון +pdfjs-cursor-text-select-tool-button = + .title = הפעלת כלי בחירת טקסט +pdfjs-cursor-text-select-tool-button-label = כלי בחירת טקסט +pdfjs-cursor-hand-tool-button = + .title = הפעלת כלי היד +pdfjs-cursor-hand-tool-button-label = כלי יד +pdfjs-scroll-page-button = + .title = שימוש בגלילת עמוד +pdfjs-scroll-page-button-label = גלילת עמוד +pdfjs-scroll-vertical-button = + .title = שימוש בגלילה ×נכית +pdfjs-scroll-vertical-button-label = גלילה ×נכית +pdfjs-scroll-horizontal-button = + .title = שימוש בגלילה ×ופקית +pdfjs-scroll-horizontal-button-label = גלילה ×ופקית +pdfjs-scroll-wrapped-button = + .title = שימוש בגלילה רציפה +pdfjs-scroll-wrapped-button-label = גלילה רציפה +pdfjs-spread-none-button = + .title = ×œ× ×œ×¦×¨×£ מפתחי ×¢×ž×•×“×™× +pdfjs-spread-none-button-label = ×œ×œ× ×ž×¤×ª×—×™× +pdfjs-spread-odd-button = + .title = צירוף מפתחי ×¢×ž×•×“×™× ×©×ž×ª×—×™×œ×™× ×‘×“×¤×™× ×¢× ×ž×¡×¤×¨×™× ××™Ö¾×–×•×’×™×™× +pdfjs-spread-odd-button-label = ×ž×¤×ª×—×™× ××™Ö¾×–×•×’×™×™× +pdfjs-spread-even-button = + .title = צירוף מפתחי ×¢×ž×•×“×™× ×©×ž×ª×—×™×œ×™× ×‘×“×¤×™× ×¢× ×ž×¡×¤×¨×™× ×–×•×’×™×™× +pdfjs-spread-even-button-label = ×ž×¤×ª×—×™× ×–×•×’×™×™× + +## Document properties dialog + +pdfjs-document-properties-button = + .title = מ×פייני מסמך… +pdfjs-document-properties-button-label = מ×פייני מסמך… +pdfjs-document-properties-file-name = ×©× ×§×•×‘×¥: +pdfjs-document-properties-file-size = גודל הקובץ: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } ק״ב ({ $b } בתי×) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } מ״ב ({ $b } בתי×) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } ק״ב ({ $size_b } בתי×) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } מ״ב ({ $size_b } בתי×) +pdfjs-document-properties-title = כותרת: +pdfjs-document-properties-author = מחבר: +pdfjs-document-properties-subject = נוש×: +pdfjs-document-properties-keywords = מילות מפתח: +pdfjs-document-properties-creation-date = ת×ריך יצירה: +pdfjs-document-properties-modification-date = ת×ריך שינוי: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = יוצר: +pdfjs-document-properties-producer = יצרן PDF: +pdfjs-document-properties-version = גרסת PDF: +pdfjs-document-properties-page-count = מספר דפי×: +pdfjs-document-properties-page-size = גודל העמוד: +pdfjs-document-properties-page-size-unit-inches = ×ינ׳ +pdfjs-document-properties-page-size-unit-millimeters = מ״מ +pdfjs-document-properties-page-size-orientation-portrait = ל×ורך +pdfjs-document-properties-page-size-orientation-landscape = לרוחב +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = מכתב +pdfjs-document-properties-page-size-name-legal = דף משפטי + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = תצוגת דף מהירה: +pdfjs-document-properties-linearized-yes = כן +pdfjs-document-properties-linearized-no = ×œ× +pdfjs-document-properties-close-button = סגירה + +## Print + +pdfjs-print-progress-message = מסמך בהכנה להדפסה… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = ביטול +pdfjs-printing-not-supported = ×זהרה: הדפסה ××™× ×” נתמכת במלו××” בדפדפן ×–×”. +pdfjs-printing-not-ready = ×זהרה: מסמך ×”Ö¾PDF ×œ× × ×˜×¢×Ÿ לחלוטין עד מצב שמ×פשר הדפסה. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = הצגה/הסתרה של סרגל הצד +pdfjs-toggle-sidebar-notification-button = + .title = החלפת תצוגת סרגל צד (מסמך שמכיל תוכן ×¢× ×™×™× ×™×/×§×‘×¦×™× ×ž×¦×•×¨×¤×™×/שכבות) +pdfjs-toggle-sidebar-button-label = הצגה/הסתרה של סרגל הצד +pdfjs-document-outline-button = + .title = הצגת תוכן ×”×¢× ×™×™× ×™× ×©×œ המסמך (לחיצה כפולה כדי להרחיב ×ו ×œ×¦×ž×¦× ×ת כל הפריטי×) +pdfjs-document-outline-button-label = תוכן ×”×¢× ×™×™× ×™× ×©×œ המסמך +pdfjs-attachments-button = + .title = הצגת צרופות +pdfjs-attachments-button-label = צרופות +pdfjs-layers-button = + .title = הצגת שכבות (יש ללחוץ לחיצה כפולה כדי ל×פס ×ת כל השכבות למצב ברירת המחדל) +pdfjs-layers-button-label = שכבות +pdfjs-thumbs-button = + .title = הצגת תצוגה מקדימה +pdfjs-thumbs-button-label = תצוגה מקדימה +pdfjs-current-outline-item-button = + .title = מצי×ת פריט תוכן ×”×¢× ×™×™× ×™× ×”× ×•×›×—×™ +pdfjs-current-outline-item-button-label = פריט תוכן ×”×¢× ×™×™× ×™× ×”× ×•×›×—×™ +pdfjs-findbar-button = + .title = חיפוש במסמך +pdfjs-findbar-button-label = חיפוש +pdfjs-additional-layers = שכבות נוספות + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = עמוד { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = תצוגה מקדימה של עמוד { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = חיפוש + .placeholder = חיפוש במסמך… +pdfjs-find-previous-button = + .title = מצי×ת המופע ×”×§×•×“× ×©×œ הביטוי +pdfjs-find-previous-button-label = ×§×•×“× +pdfjs-find-next-button = + .title = מצי×ת המופע ×”×‘× ×©×œ הביטוי +pdfjs-find-next-button-label = ×”×‘× +pdfjs-find-highlight-checkbox = הדגשת הכול +pdfjs-find-match-case-checkbox-label = הת×מת ×ותיות +pdfjs-find-match-diacritics-checkbox-label = הת×מה די×קריטית +pdfjs-find-entire-word-checkbox-label = ×ž×™×œ×™× ×©×œ×ž×•×ª +pdfjs-find-reached-top = ×”×’×™×¢ לר×ש הדף, ממשיך מלמטה +pdfjs-find-reached-bottom = ×”×’×™×¢ לסוף הדף, ממשיך מלמעלה +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } מתוך { $total } תוצ×ות + *[other] { $current } מתוך { $total } תוצ×ות + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] יותר מתוצ××” ×חת + *[other] יותר מ־{ $limit } תוצ×ות + } +pdfjs-find-not-found = הביטוי ×œ× × ×ž×¦× + +## Predefined zoom values + +pdfjs-page-scale-width = רוחב העמוד +pdfjs-page-scale-fit = הת×מה לעמוד +pdfjs-page-scale-auto = מרחק מתצוגה ×וטומטי +pdfjs-page-scale-actual = גודל ×מיתי +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = עמוד { $page } + +## Loading indicator messages + +pdfjs-loading-error = ×ירעה שגי××” בעת טעינת ×”Ö¾PDF. +pdfjs-invalid-file-error = קובץ PDF ×¤×’×•× ×ו ×œ× ×ª×§×™×Ÿ. +pdfjs-missing-file-error = קובץ PDF חסר. +pdfjs-unexpected-response-error = תגובת שרת ×œ× ×¦×¤×•×™×”. +pdfjs-rendering-error = ×ירעה שגי××” בעת עיבוד הדף. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [הערת { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = × × ×œ×”×›× ×™×¡ ×ת הססמה לפתיחת קובץ PDF ×–×”. +pdfjs-password-invalid = ססמה שגויה. × × ×œ× ×¡×•×ª שנית. +pdfjs-password-ok-button = ×ישור +pdfjs-password-cancel-button = ביטול +pdfjs-web-fonts-disabled = גופני רשת מנוטרלי×: ×œ× × ×™×ª×Ÿ להשתמש בגופני PDF מוטבעי×. + +## Editing + +pdfjs-editor-free-text-button = + .title = טקסט +pdfjs-editor-free-text-button-label = טקסט +pdfjs-editor-ink-button = + .title = ציור +pdfjs-editor-ink-button-label = ציור +pdfjs-editor-stamp-button = + .title = הוספה ×ו עריכת תמונות +pdfjs-editor-stamp-button-label = הוספה ×ו עריכת תמונות +pdfjs-editor-highlight-button = + .title = סימון +pdfjs-editor-highlight-button-label = סימון +pdfjs-highlight-floating-button1 = + .title = סימון + .aria-label = סימון +pdfjs-highlight-floating-button-label = סימון + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = הסרת ציור +pdfjs-editor-remove-freetext-button = + .title = הסרת טקסט +pdfjs-editor-remove-stamp-button = + .title = הסרת תמונה +pdfjs-editor-remove-highlight-button = + .title = הסרת סימון + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = צבע +pdfjs-editor-free-text-size-input = גודל +pdfjs-editor-ink-color-input = צבע +pdfjs-editor-ink-thickness-input = עובי +pdfjs-editor-ink-opacity-input = ×טימות +pdfjs-editor-stamp-add-image-button = + .title = הוספת תמונה +pdfjs-editor-stamp-add-image-button-label = הוספת תמונה +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = עובי +pdfjs-editor-free-highlight-thickness-title = + .title = שינוי עובי בעת סימון ×¤×¨×™×˜×™× ×©××™× × ×˜×§×¡×˜ +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = עורך טקסט + .default-content = × × ×œ×”×ª×—×™×œ להקליד… +pdfjs-free-text = + .aria-label = עורך טקסט +pdfjs-free-text-default-content = להתחיל להקליד… +pdfjs-ink = + .aria-label = עורך ציור +pdfjs-ink-canvas = + .aria-label = תמונה שנוצרה על־ידי משתמש + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = טקסט חלופי +pdfjs-editor-alt-text-edit-button = + .aria-label = עריכת טקסט חלופי +pdfjs-editor-alt-text-edit-button-label = עריכת טקסט חלופי +pdfjs-editor-alt-text-dialog-label = בחירת ×פשרות +pdfjs-editor-alt-text-dialog-description = טקסט חלופי עוזר כש×× ×©×™× ×œ× ×™×›×•×œ×™× ×œ×¨×ות ×ת התמונה ×ו ×›×©×”×™× ×œ× × ×˜×¢× ×ª. +pdfjs-editor-alt-text-add-description-label = הוספת תי×ור +pdfjs-editor-alt-text-add-description-description = כד××™ לת×ר במשפט ×חד ×ו ×©× ×™×™× ×ת הנוש×, התפ×ורה ×ו הפעולות. +pdfjs-editor-alt-text-mark-decorative-label = סימון כדקורטיבי +pdfjs-editor-alt-text-mark-decorative-description = ×–×” משמש לתמונות נוי, כמו גבולות ×ו סימני מי×. +pdfjs-editor-alt-text-cancel-button = ביטול +pdfjs-editor-alt-text-save-button = שמירה +pdfjs-editor-alt-text-decorative-tooltip = מסומן כדקורטיבי +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = לדוגמה, ״גבר צעיר מתיישב ליד שולחן ל×כול ×רוחה״ +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = טקסט חלופי + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = פינה שמ×לית עליונה - שינוי גודל +pdfjs-editor-resizer-label-top-middle = למעלה ב×מצע - שינוי גודל +pdfjs-editor-resizer-label-top-right = פינה ימנית עליונה - שינוי גודל +pdfjs-editor-resizer-label-middle-right = ימינה ב×מצע - שינוי גודל +pdfjs-editor-resizer-label-bottom-right = פינה ימנית תחתונה - שינוי גודל +pdfjs-editor-resizer-label-bottom-middle = למטה ב×מצע - שינוי גודל +pdfjs-editor-resizer-label-bottom-left = פינה שמ×לית תחתונה - שינוי גודל +pdfjs-editor-resizer-label-middle-left = שמ×לה ב×מצע - שינוי גודל +pdfjs-editor-resizer-top-left = + .aria-label = פינה שמ×לית עליונה - שינוי גודל +pdfjs-editor-resizer-top-middle = + .aria-label = למעלה ב×מצע - שינוי גודל +pdfjs-editor-resizer-top-right = + .aria-label = פינה ימנית עליונה - שינוי גודל +pdfjs-editor-resizer-middle-right = + .aria-label = ימינה ב×מצע - שינוי גודל +pdfjs-editor-resizer-bottom-right = + .aria-label = פינה ימנית תחתונה - שינוי גודל +pdfjs-editor-resizer-bottom-middle = + .aria-label = למטה ב×מצע - שינוי גודל +pdfjs-editor-resizer-bottom-left = + .aria-label = פינה שמ×לית תחתונה - שינוי גודל +pdfjs-editor-resizer-middle-left = + .aria-label = שמ×לה ב×מצע - שינוי גודל + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = צבע סימון +pdfjs-editor-colorpicker-button = + .title = שינוי צבע +pdfjs-editor-colorpicker-dropdown = + .aria-label = בחירת צבע +pdfjs-editor-colorpicker-yellow = + .title = צהוב +pdfjs-editor-colorpicker-green = + .title = ירוק +pdfjs-editor-colorpicker-blue = + .title = כחול +pdfjs-editor-colorpicker-pink = + .title = ורוד +pdfjs-editor-colorpicker-red = + .title = ××“×•× + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = הצגת הכול +pdfjs-editor-highlight-show-all-button = + .title = הצגת הכול + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = עריכת טקסט חלופי (תי×ור תמונה) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = הוספת טקסט חלופי (תי×ור תמונה) +pdfjs-editor-new-alt-text-textarea = + .placeholder = × × ×œ×›×ª×•×‘ ×ת התי×ור שלך ×›×ן… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = תי×ור קצר ל×× ×©×™× ×©××™× × ×™×›×•×œ×™× ×œ×¨×ות ×ת התמונה ×ו ×›×שר התמונה ××™× ×” נטענת. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = טקסט חלופי ×–×” נוצר ב×ופן ×וטומטי ועשוי להיות ×œ× ×ž×“×•×™×§. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = מידע נוסף +pdfjs-editor-new-alt-text-create-automatically-button-label = יצירת טקסט חלופי ב×ופן ×וטומטי +pdfjs-editor-new-alt-text-not-now-button = ×œ× ×›×¢×ª +pdfjs-editor-new-alt-text-error-title = ×œ× × ×™×ª×Ÿ ×”×™×” ליצור טקסט חלופי ב×ופן ×וטומטי +pdfjs-editor-new-alt-text-error-description = × × ×œ×›×ª×•×‘ טקסט חלופי משלך ×ו לנסות שוב מ×וחר יותר. +pdfjs-editor-new-alt-text-error-close-button = סגירה +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = בתהליך הורדת מודל AI של טקסט חלופי ({ $downloadedSize } מתוך { $totalSize } מ״ב) + .aria-valuetext = בתהליך הורדת מודל AI של טקסט חלופי ({ $downloadedSize } מתוך { $totalSize } מ״ב) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = נוסף טקסט חלופי +pdfjs-editor-new-alt-text-added-button-label = נוסף טקסט חלופי +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = חסר טקסט חלופי +pdfjs-editor-new-alt-text-missing-button-label = חסר טקסט חלופי +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = סקירת טקסט חלופי +pdfjs-editor-new-alt-text-to-review-button-label = סקירת טקסט חלופי +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = נוצר ב×ופן ×וטומטי: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = הגדרות טקסט חלופי של תמונה +pdfjs-image-alt-text-settings-button-label = הגדרות טקסט חלופי של תמונה +pdfjs-editor-alt-text-settings-dialog-label = הגדרות טקסט חלופי של תמונה +pdfjs-editor-alt-text-settings-automatic-title = טקסט חלופי ×וטומטי +pdfjs-editor-alt-text-settings-create-model-button-label = יצירת טקסט חלופי ב×ופן ×וטומטי +pdfjs-editor-alt-text-settings-create-model-description = הצעת תי××•×¨×™× ×›×“×™ לסייע ל×× ×©×™× ×©××™× × ×™×›×•×œ×™× ×œ×¨×ות ×ת התמונה ×ו ×›×שר התמונה ××™× ×” נטענת. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = מודל AI לטקסט חלופי ({ $totalSize } מ״ב) +pdfjs-editor-alt-text-settings-ai-model-description = פועל ב×ופן מקומי במכשיר שלך כך ×©×”× ×ª×•× ×™× ×©×œ×š נש××¨×™× ×¤×¨×˜×™×™×. נדרש עבור טקסט חלופי ×וטומטי. +pdfjs-editor-alt-text-settings-delete-model-button = מחיקה +pdfjs-editor-alt-text-settings-download-model-button = הורדה +pdfjs-editor-alt-text-settings-downloading-model-button = בהורדה… +pdfjs-editor-alt-text-settings-editor-title = עורך טקסט חלופי +pdfjs-editor-alt-text-settings-show-dialog-button-label = הצגת עורך טקסט חלופי מיד בעת הוספת תמונה +pdfjs-editor-alt-text-settings-show-dialog-description = מסייע לך ×œ×•×•×“× ×©×œ×›×œ התמונות שלך יש טקסט חלופי. +pdfjs-editor-alt-text-settings-close-button = סגירה + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = הסימון הוסר +pdfjs-editor-undo-bar-message-freetext = הטקסט הוסר +pdfjs-editor-undo-bar-message-ink = הציור הוסר +pdfjs-editor-undo-bar-message-stamp = התמונה הוסרה +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] הערה ×חת הוסרה + *[other] { $count } הערות הוסרו + } +pdfjs-editor-undo-bar-undo-button = + .title = ביטול פעולה +pdfjs-editor-undo-bar-undo-button-label = ביטול פעלה +pdfjs-editor-undo-bar-close-button = + .title = סגירה +pdfjs-editor-undo-bar-close-button-label = סגירה diff --git a/public/assets/pdfjs/locale/hi-IN/viewer.ftl b/public/assets/pdfjs/locale/hi-IN/viewer.ftl new file mode 100755 index 0000000..b6f378f --- /dev/null +++ b/public/assets/pdfjs/locale/hi-IN/viewer.ftl @@ -0,0 +1,267 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = पिछला पृषà¥à¤  +pdfjs-previous-button-label = पिछला +pdfjs-next-button = + .title = अगला पृषà¥à¤  +pdfjs-next-button-label = आगे +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = पृषà¥à¤ : +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = { $pagesCount } का +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount }) +pdfjs-zoom-out-button = + .title = छोटा करें +pdfjs-zoom-out-button-label = छोटा करें +pdfjs-zoom-in-button = + .title = बड़ा करें +pdfjs-zoom-in-button-label = बड़ा करें +pdfjs-zoom-select = + .title = बड़ा-छोटा करें +pdfjs-presentation-mode-button = + .title = पà¥à¤°à¤¸à¥à¤¤à¥à¤¤à¤¿ अवसà¥à¤¥à¤¾ में जाà¤à¤ +pdfjs-presentation-mode-button-label = पà¥à¤°à¤¸à¥à¤¤à¥à¤¤à¤¿ अवसà¥à¤¥à¤¾ +pdfjs-open-file-button = + .title = फ़ाइल खोलें +pdfjs-open-file-button-label = खोलें +pdfjs-print-button = + .title = छापें +pdfjs-print-button-label = छापें + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = औज़ार +pdfjs-tools-button-label = औज़ार +pdfjs-first-page-button = + .title = पà¥à¤°à¤¥à¤® पृषà¥à¤  पर जाà¤à¤ +pdfjs-first-page-button-label = पà¥à¤°à¤¥à¤® पृषà¥à¤  पर जाà¤à¤ +pdfjs-last-page-button = + .title = अंतिम पृषà¥à¤  पर जाà¤à¤ +pdfjs-last-page-button-label = अंतिम पृषà¥à¤  पर जाà¤à¤ +pdfjs-page-rotate-cw-button = + .title = घड़ी की दिशा में घà¥à¤®à¤¾à¤à¤ +pdfjs-page-rotate-cw-button-label = घड़ी की दिशा में घà¥à¤®à¤¾à¤à¤ +pdfjs-page-rotate-ccw-button = + .title = घड़ी की दिशा से उलà¥à¤Ÿà¤¾ घà¥à¤®à¤¾à¤à¤ +pdfjs-page-rotate-ccw-button-label = घड़ी की दिशा से उलà¥à¤Ÿà¤¾ घà¥à¤®à¤¾à¤à¤ +pdfjs-cursor-text-select-tool-button = + .title = पाठ चयन उपकरण सकà¥à¤·à¤® करें +pdfjs-cursor-text-select-tool-button-label = पाठ चयन उपकरण +pdfjs-cursor-hand-tool-button = + .title = हसà¥à¤¤ उपकरण सकà¥à¤·à¤® करें +pdfjs-cursor-hand-tool-button-label = हसà¥à¤¤ उपकरण +pdfjs-scroll-vertical-button = + .title = लंबवत सà¥à¤•à¥à¤°à¥‰à¤²à¤¿à¤‚ग का उपयोग करें +pdfjs-scroll-vertical-button-label = लंबवत सà¥à¤•à¥à¤°à¥‰à¤²à¤¿à¤‚ग +pdfjs-scroll-horizontal-button = + .title = कà¥à¤·à¤¿à¤¤à¤¿à¤œà¤¿à¤¯ सà¥à¤•à¥à¤°à¥‰à¤²à¤¿à¤‚ग का उपयोग करें +pdfjs-scroll-horizontal-button-label = कà¥à¤·à¤¿à¤¤à¤¿à¤œà¤¿à¤¯ सà¥à¤•à¥à¤°à¥‰à¤²à¤¿à¤‚ग +pdfjs-scroll-wrapped-button = + .title = वà¥à¤°à¤¾à¤ªà¥à¤ªà¥‡à¤¡ सà¥à¤•à¥à¤°à¥‰à¤²à¤¿à¤‚ग का उपयोग करें +pdfjs-spread-none-button-label = कोई सà¥à¤ªà¥à¤°à¥‡à¤¡ उपलबà¥à¤§ नहीं +pdfjs-spread-odd-button = + .title = विषम-कà¥à¤°à¤®à¤¾à¤‚कित पृषà¥à¤ à¥‹à¤‚ से पà¥à¤°à¤¾à¤°à¤‚भ होने वाले पृषà¥à¤  सà¥à¤ªà¥à¤°à¥‡à¤¡ में शामिल हों +pdfjs-spread-odd-button-label = विषम फैलाव + +## Document properties dialog + +pdfjs-document-properties-button = + .title = दसà¥à¤¤à¤¾à¤µà¥‡à¤œà¤¼ विशेषता... +pdfjs-document-properties-button-label = दसà¥à¤¤à¤¾à¤µà¥‡à¤œà¤¼ विशेषता... +pdfjs-document-properties-file-name = फ़ाइल नाम: +pdfjs-document-properties-file-size = फाइल आकारः +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = शीरà¥à¤·à¤•: +pdfjs-document-properties-author = लेखकः +pdfjs-document-properties-subject = विषय: +pdfjs-document-properties-keywords = कà¥à¤‚जी-शबà¥à¤¦: +pdfjs-document-properties-creation-date = निरà¥à¤®à¤¾à¤£ दिनांक: +pdfjs-document-properties-modification-date = संशोधन दिनांक: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = निरà¥à¤®à¤¾à¤¤à¤¾: +pdfjs-document-properties-producer = PDF उतà¥à¤ªà¤¾à¤¦à¤•: +pdfjs-document-properties-version = PDF संसà¥à¤•रण: +pdfjs-document-properties-page-count = पृषà¥à¤  गिनती: +pdfjs-document-properties-page-size = पृषà¥à¤  आकार: +pdfjs-document-properties-page-size-unit-inches = इंच +pdfjs-document-properties-page-size-unit-millimeters = मिमी +pdfjs-document-properties-page-size-orientation-portrait = पोरà¥à¤Ÿà¥à¤°à¥‡à¤Ÿ +pdfjs-document-properties-page-size-orientation-landscape = लैंडसà¥à¤•ेप +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = पतà¥à¤° +pdfjs-document-properties-page-size-name-legal = क़ानूनी + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = तीवà¥à¤° वेब वà¥à¤¯à¥‚: +pdfjs-document-properties-linearized-yes = हाठ+pdfjs-document-properties-linearized-no = नहीं +pdfjs-document-properties-close-button = बंद करें + +## Print + +pdfjs-print-progress-message = छपाई के लिठदसà¥à¤¤à¤¾à¤µà¥‡à¤œà¤¼ को तैयार किया जा रहा है... +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = रदà¥à¤¦ करें +pdfjs-printing-not-supported = चेतावनी: इस बà¥à¤°à¤¾à¤‰à¤œà¤¼à¤° पर छपाई पूरी तरह से समरà¥à¤¥à¤¿à¤¤ नहीं है. +pdfjs-printing-not-ready = चेतावनी: PDF छपाई के लिठपूरी तरह से लोड नहीं है. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = सà¥à¤²à¤¾à¤‡à¤¡à¤° टॉगल करें +pdfjs-toggle-sidebar-button-label = सà¥à¤²à¤¾à¤‡à¤¡à¤° टॉगल करें +pdfjs-document-outline-button = + .title = दसà¥à¤¤à¤¾à¤µà¥‡à¤œà¤¼ की रूपरेखा दिखाइठ(सारी वसà¥à¤¤à¥à¤“ं को फलने अथवा समेटने के लिठदो बार कà¥à¤²à¤¿à¤• करें) +pdfjs-document-outline-button-label = दसà¥à¤¤à¤¾à¤µà¥‡à¤œà¤¼ आउटलाइन +pdfjs-attachments-button = + .title = संलगà¥à¤¨à¤• दिखायें +pdfjs-attachments-button-label = संलगà¥à¤¨à¤• +pdfjs-thumbs-button = + .title = लघà¥à¤›à¤µà¤¿à¤¯à¤¾à¤ दिखाà¤à¤ +pdfjs-thumbs-button-label = लघॠछवि +pdfjs-findbar-button = + .title = दसà¥à¤¤à¤¾à¤µà¥‡à¤œà¤¼ में ढूà¤à¤¢à¤¼à¥‡à¤‚ +pdfjs-findbar-button-label = ढूà¤à¤¢à¥‡à¤‚ + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = पृषà¥à¤  { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = पृषà¥à¤  { $page } की लघà¥-छवि + +## Find panel button title and messages + +pdfjs-find-input = + .title = ढूà¤à¤¢à¥‡à¤‚ + .placeholder = दसà¥à¤¤à¤¾à¤µà¥‡à¤œà¤¼ में खोजें... +pdfjs-find-previous-button = + .title = वाकà¥à¤¯à¤¾à¤‚श की पिछली उपसà¥à¤¥à¤¿à¤¤à¤¿ ढूà¤à¤¢à¤¼à¥‡à¤‚ +pdfjs-find-previous-button-label = पिछला +pdfjs-find-next-button = + .title = वाकà¥à¤¯à¤¾à¤‚श की अगली उपसà¥à¤¥à¤¿à¤¤à¤¿ ढूà¤à¤¢à¤¼à¥‡à¤‚ +pdfjs-find-next-button-label = अगला +pdfjs-find-highlight-checkbox = सभी आलोकित करें +pdfjs-find-match-case-checkbox-label = मिलान सà¥à¤¥à¤¿à¤¤à¤¿ +pdfjs-find-entire-word-checkbox-label = संपूरà¥à¤£ शबà¥à¤¦ +pdfjs-find-reached-top = पृषà¥à¤  के ऊपर पहà¥à¤‚च गया, नीचे से जारी रखें +pdfjs-find-reached-bottom = पृषà¥à¤  के नीचे में जा पहà¥à¤à¤šà¤¾, ऊपर से जारी +pdfjs-find-not-found = वाकà¥à¤¯à¤¾à¤‚श नहीं मिला + +## Predefined zoom values + +pdfjs-page-scale-width = पृषà¥à¤  चौड़ाई +pdfjs-page-scale-fit = पृषà¥à¤  फिट +pdfjs-page-scale-auto = सà¥à¤µà¤šà¤¾à¤²à¤¿à¤¤ जूम +pdfjs-page-scale-actual = वासà¥à¤¤à¤µà¤¿à¤• आकार +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = PDF लोड करते समय à¤à¤• तà¥à¤°à¥à¤Ÿà¤¿ हà¥à¤ˆ. +pdfjs-invalid-file-error = अमानà¥à¤¯ या भà¥à¤°à¤·à¥à¤Ÿ PDF फ़ाइल. +pdfjs-missing-file-error = अनà¥à¤ªà¤¸à¥à¤¥à¤¿à¤¤ PDF फ़ाइल. +pdfjs-unexpected-response-error = अपà¥à¤°à¤¤à¥à¤¯à¤¾à¤¶à¤¿à¤¤ सरà¥à¤µà¤° पà¥à¤°à¤¤à¤¿à¤•à¥à¤°à¤¿à¤¯à¤¾. +pdfjs-rendering-error = पृषà¥à¤  रेंडरिंग के दौरान तà¥à¤°à¥à¤Ÿà¤¿ आई. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Annotation] + +## Password + +pdfjs-password-label = इस PDF फ़ाइल को खोलने के लिठकृपया कूटशबà¥à¤¦ भरें. +pdfjs-password-invalid = अवैध कूटशबà¥à¤¦, कृपया फिर कोशिश करें. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = रदà¥à¤¦ करें +pdfjs-web-fonts-disabled = वेब फॉनà¥à¤Ÿà¥à¤¸ निषà¥à¤•à¥à¤°à¤¿à¤¯ हैं: अंतःसà¥à¤¥à¤¾à¤ªà¤¿à¤¤ PDF फॉनà¥à¤Ÿà¤¸ के उपयोग में असमरà¥à¤¥. + +## Editing + + +## Remove button for the various kind of editor. + + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = रंग + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + + +## Color picker + + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + + +## Image alt-text settings + diff --git a/public/assets/pdfjs/locale/hr/viewer.ftl b/public/assets/pdfjs/locale/hr/viewer.ftl new file mode 100755 index 0000000..c081c6f --- /dev/null +++ b/public/assets/pdfjs/locale/hr/viewer.ftl @@ -0,0 +1,473 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Prethodna stranica +pdfjs-previous-button-label = Prethodna +pdfjs-next-button = + .title = Sljedeća stranica +pdfjs-next-button-label = Sljedeća +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Stranica +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = od { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } od { $pagesCount }) +pdfjs-zoom-out-button = + .title = Umanji +pdfjs-zoom-out-button-label = Umanji +pdfjs-zoom-in-button = + .title = Uvećaj +pdfjs-zoom-in-button-label = Uvećaj +pdfjs-zoom-select = + .title = Zumiranje +pdfjs-presentation-mode-button = + .title = Prebaci u modus prezentacija +pdfjs-presentation-mode-button-label = Modus prezentacija +pdfjs-open-file-button = + .title = Otvori datoteku +pdfjs-open-file-button-label = Otvori +pdfjs-print-button = + .title = IspiÅ¡i +pdfjs-print-button-label = IspiÅ¡i +pdfjs-save-button = + .title = Spremi +pdfjs-save-button-label = Spremi +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Preuzimanja +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Preuzimanja +pdfjs-bookmark-button = + .title = Trenutna stranica (pogledajte URL s trenutne stranice) +pdfjs-bookmark-button-label = Trenutna stranica + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Alati +pdfjs-tools-button-label = Alati +pdfjs-first-page-button = + .title = Idi na prvu stranicu +pdfjs-first-page-button-label = Idi na prvu stranicu +pdfjs-last-page-button = + .title = Idi na posljednju stranicu +pdfjs-last-page-button-label = Idi na posljednju stranicu +pdfjs-page-rotate-cw-button = + .title = Rotiraj u smjeru kazaljke na satu +pdfjs-page-rotate-cw-button-label = Rotiraj u smjeru kazaljke na satu +pdfjs-page-rotate-ccw-button = + .title = Rotiraj obrnutno od smjera kazaljke na satu +pdfjs-page-rotate-ccw-button-label = Rotiraj obrnutno od smjera kazaljke na satu +pdfjs-cursor-text-select-tool-button = + .title = Aktiviraj alat za biranje teksta +pdfjs-cursor-text-select-tool-button-label = Alat za oznaÄavanje teksta +pdfjs-cursor-hand-tool-button = + .title = Aktiviraj ruÄni alat +pdfjs-cursor-hand-tool-button-label = RuÄni alat +pdfjs-scroll-page-button = + .title = Koristi klizanje stranice +pdfjs-scroll-page-button-label = Klizanje stranice +pdfjs-scroll-vertical-button = + .title = Koristi okomito pomicanje +pdfjs-scroll-vertical-button-label = Okomito pomicanje +pdfjs-scroll-horizontal-button = + .title = Koristi vodoravno pomicanje +pdfjs-scroll-horizontal-button-label = Vodoravno pomicanje +pdfjs-scroll-wrapped-button = + .title = Koristi kontinuirani raspored stranica +pdfjs-scroll-wrapped-button-label = Kontinuirani raspored stranica +pdfjs-spread-none-button = + .title = Ne izraÄ‘uj duplerice +pdfjs-spread-none-button-label = PojedinaÄne stranice +pdfjs-spread-odd-button = + .title = Izradi duplerice koje poÄinju s neparnim stranicama +pdfjs-spread-odd-button-label = Neparne duplerice +pdfjs-spread-even-button = + .title = Izradi duplerice koje poÄinju s parnim stranicama +pdfjs-spread-even-button-label = Parne duplerice + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Svojstva dokumenta … +pdfjs-document-properties-button-label = Svojstva dokumenta … +pdfjs-document-properties-file-name = Ime datoteke: +pdfjs-document-properties-file-size = VeliÄina datoteke: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bajtova) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bajtova) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bajtova) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bajtova) +pdfjs-document-properties-title = Naslov: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Predmet: +pdfjs-document-properties-keywords = KljuÄne rijeÄi: +pdfjs-document-properties-creation-date = Datum stvaranja: +pdfjs-document-properties-modification-date = Datum promjene: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Stvaratelj: +pdfjs-document-properties-producer = PDF stvaratelj: +pdfjs-document-properties-version = PDF verzija: +pdfjs-document-properties-page-count = Broj stranica: +pdfjs-document-properties-page-size = Dimenzije stranice: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = uspravno +pdfjs-document-properties-page-size-orientation-landscape = položeno +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Brzi web pregled: +pdfjs-document-properties-linearized-yes = Da +pdfjs-document-properties-linearized-no = Ne +pdfjs-document-properties-close-button = Zatvori + +## Print + +pdfjs-print-progress-message = Pripremanje dokumenta za ispis… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Odustani +pdfjs-printing-not-supported = Upozorenje: Ovaj preglednik ne podržava u potpunosti ispisivanje. +pdfjs-printing-not-ready = Upozorenje: PDF nije u potpunosti uÄitan za ispis. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Prikaži/sakrij boÄnu traku +pdfjs-toggle-sidebar-notification-button = + .title = Prikazivanje i sklanjanje boÄne trake (dokument sadrži strukturu/privitke/slojeve) +pdfjs-toggle-sidebar-button-label = Prikaži/sakrij boÄnu traku +pdfjs-document-outline-button = + .title = Prikaži strukturu dokumenta (dvostruki klik za rasklapanje/sklapanje svih stavki) +pdfjs-document-outline-button-label = Struktura dokumenta +pdfjs-attachments-button = + .title = Prikaži privitke +pdfjs-attachments-button-label = Privitci +pdfjs-layers-button = + .title = Prikaži slojeve (dvoklik za vraćanje svih slojeva u standardno stanje) +pdfjs-layers-button-label = Slojevi +pdfjs-thumbs-button = + .title = Prikaži minijature +pdfjs-thumbs-button-label = Minijature +pdfjs-current-outline-item-button = + .title = PronaÄ‘i trenutaÄni element strukture +pdfjs-current-outline-item-button-label = TrenutaÄni element strukture +pdfjs-findbar-button = + .title = PronaÄ‘i u dokumentu +pdfjs-findbar-button-label = PronaÄ‘i +pdfjs-additional-layers = Dodatni slojevi + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Stranica { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Minijatura stranice { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = PronaÄ‘i + .placeholder = PronaÄ‘i u dokumentu … +pdfjs-find-previous-button = + .title = PronaÄ‘i prethodno pojavljivanje ovog izraza +pdfjs-find-previous-button-label = Prethodno +pdfjs-find-next-button = + .title = PronaÄ‘i sljedeće pojavljivanje ovog izraza +pdfjs-find-next-button-label = Dalje +pdfjs-find-highlight-checkbox = Istankni sve +pdfjs-find-match-case-checkbox-label = Razlikovanje velikih i malih slova +pdfjs-find-match-diacritics-checkbox-label = Razlikuj dijakritiÄke znakove +pdfjs-find-entire-word-checkbox-label = Cijele rijeÄi +pdfjs-find-reached-top = Dosegnut poÄetak dokumenta, nastavak s kraja +pdfjs-find-reached-bottom = Dosegnut kraj dokumenta, nastavak s poÄetka +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } od { $total } rezultata + [few] { $current } od { $total } rezultata + *[other] { $current } od { $total } rezultata + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] ViÅ¡e od { $limit } rezultat + [few] ViÅ¡e od { $limit } rezultata + *[other] ViÅ¡e od { $limit } rezultata + } +pdfjs-find-not-found = Izraz nije pronaÄ‘en + +## Predefined zoom values + +pdfjs-page-scale-width = Prilagodi Å¡irini prozora +pdfjs-page-scale-fit = Prilagodi veliÄini prozora +pdfjs-page-scale-auto = Automatsko zumiranje +pdfjs-page-scale-actual = Stvarna veliÄina +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale } % + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Stranica { $page } + +## Loading indicator messages + +pdfjs-loading-error = DoÅ¡lo je do greÅ¡ke pri uÄitavanju PDF-a. +pdfjs-invalid-file-error = Neispravna ili oÅ¡tećena PDF datoteka. +pdfjs-missing-file-error = Nedostaje PDF datoteka. +pdfjs-unexpected-response-error = NeoÄekivani odgovor servera. +pdfjs-rendering-error = DoÅ¡lo je do greÅ¡ke prilikom iscrtavanja stranice. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } BiljeÅ¡ka] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Za otvoranje ove PDF datoteku upiÅ¡i lozinku. +pdfjs-password-invalid = Neispravna lozinka. PokuÅ¡aj ponovo. +pdfjs-password-ok-button = U redu +pdfjs-password-cancel-button = Odustani +pdfjs-web-fonts-disabled = Web fontovi su deaktivirani: nije moguće koristiti ugraÄ‘ene PDF fontove. + +## Editing + +pdfjs-editor-free-text-button = + .title = Tekst +pdfjs-editor-free-text-button-label = Tekst +pdfjs-editor-ink-button = + .title = Crtanje +pdfjs-editor-ink-button-label = Crtanje +pdfjs-editor-stamp-button = + .title = Dodajte ili uredite slike +pdfjs-editor-stamp-button-label = Dodajte ili uredite slike +pdfjs-editor-highlight-button = + .title = Istakni +pdfjs-editor-highlight-button-label = Istakni +pdfjs-highlight-floating-button1 = + .title = Istakni + .aria-label = Istakni +pdfjs-highlight-floating-button-label = Istakni + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Ukloni crtež +pdfjs-editor-remove-freetext-button = + .title = Ukloni tekst +pdfjs-editor-remove-stamp-button = + .title = Ukloni sliku +pdfjs-editor-remove-highlight-button = + .title = Ukloni isticanje + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Boja +pdfjs-editor-free-text-size-input = VeliÄina +pdfjs-editor-ink-color-input = Boja +pdfjs-editor-ink-thickness-input = Debljina +pdfjs-editor-ink-opacity-input = Neprozirnost +pdfjs-editor-stamp-add-image-button = + .title = Dodaj sliku +pdfjs-editor-stamp-add-image-button-label = Dodaj sliku +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Debljina +pdfjs-editor-free-highlight-thickness-title = + .title = Promjeni debljinu pri isticanju drugih stavki osim teksta +pdfjs-free-text = + .aria-label = UreÄ‘ivaÄ teksta +pdfjs-free-text-default-content = PoÄni tipkati … +pdfjs-ink = + .aria-label = UreÄ‘ivaÄ crteža +pdfjs-ink-canvas = + .aria-label = Slika koju je izradio korisnik + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alternativni tekst +pdfjs-editor-alt-text-edit-button-label = Uredi alternativni tekst +pdfjs-editor-alt-text-dialog-label = Odaberi jednu opciju +pdfjs-editor-alt-text-dialog-description = Alternativni tekst pomaže slijepim osobama ili kada se slika ne uÄita. +pdfjs-editor-alt-text-add-description-label = Dodaj opis +pdfjs-editor-alt-text-add-description-description = Sažmi sadržaj predmeta, okruženje ili radnje u jednoj ili dvije reÄenice. +pdfjs-editor-alt-text-mark-decorative-label = OznaÄi kao ukrasno +pdfjs-editor-alt-text-mark-decorative-description = Ovo se koristi za ukrasne slike, poput rubova ili vodenih žigova. +pdfjs-editor-alt-text-cancel-button = Odustani +pdfjs-editor-alt-text-save-button = Spremi +pdfjs-editor-alt-text-decorative-tooltip = OznaÄeno kao ukrasno +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Na primjer, „Mladić sjeda za stol kako bi jeo†+ +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Gornji lijevi kut – promijeni veliÄinu +pdfjs-editor-resizer-label-top-middle = Sredina gore – promijeni veliÄinu +pdfjs-editor-resizer-label-top-right = Gornji desni kut – promijeni veliÄinu +pdfjs-editor-resizer-label-middle-right = Sredina desno – promijeni veliÄinu +pdfjs-editor-resizer-label-bottom-right = Donji desni kut – promijeni veliÄinu +pdfjs-editor-resizer-label-bottom-middle = Sredina dolje – promjeni veliÄinu +pdfjs-editor-resizer-label-bottom-left = Donji lijevi kut – promijeni veliÄinu +pdfjs-editor-resizer-label-middle-left = Sredina lijevo – promijeni veliÄinu +pdfjs-editor-resizer-top-left = + .aria-label = Gornji lijevi kut – promijeni veliÄinu +pdfjs-editor-resizer-top-middle = + .aria-label = Sredina gore – promijeni veliÄinu +pdfjs-editor-resizer-top-right = + .aria-label = Gornji desni kut – promijeni veliÄinu +pdfjs-editor-resizer-middle-right = + .aria-label = Sredina desno – promijeni veliÄinu +pdfjs-editor-resizer-bottom-right = + .aria-label = Donji desni kut – promijeni veliÄinu +pdfjs-editor-resizer-bottom-middle = + .aria-label = Sredina dolje – promjeni veliÄinu +pdfjs-editor-resizer-bottom-left = + .aria-label = Donji lijevi kut – promijeni veliÄinu +pdfjs-editor-resizer-middle-left = + .aria-label = Sredina lijevo – promijeni veliÄinu + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Boja isticanja +pdfjs-editor-colorpicker-button = + .title = Promjeni boju +pdfjs-editor-colorpicker-dropdown = + .aria-label = Izbor boja +pdfjs-editor-colorpicker-yellow = + .title = Žuta +pdfjs-editor-colorpicker-green = + .title = Zelena +pdfjs-editor-colorpicker-blue = + .title = Plava +pdfjs-editor-colorpicker-pink = + .title = RužiÄasta +pdfjs-editor-colorpicker-red = + .title = Crvena + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Prikaži sve +pdfjs-editor-highlight-show-all-button = + .title = Prikaži sve + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +pdfjs-editor-new-alt-text-textarea = + .placeholder = Ovdje upiÅ¡i tvoj opis … +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Ovaj je alternativni tekst stvoren automatski i može biti netoÄan. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Saznaj viÅ¡e +pdfjs-editor-new-alt-text-create-automatically-button-label = Automatski stvori alternativni tekst +pdfjs-editor-new-alt-text-error-title = Nije bilo moguće automatski izraditi alternativni tekst +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Preuzimanje alternativnog teksta UI modela ({ $downloadedSize } od { $totalSize } MB) + .aria-valuetext = Preuzimanje alternativnog teksta UI modela ({ $downloadedSize } od { $totalSize } MB) +pdfjs-editor-new-alt-text-added-button-label = Alternativni tekst je dodan +pdfjs-editor-new-alt-text-missing-button-label = Nedostaje alternativni tekst +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Pregledaj alternativni tekst +pdfjs-editor-new-alt-text-to-review-button-label = Pregledaj alternativni tekst +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Stvoreno automatski: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Postavke alternativnog teksta slike +pdfjs-image-alt-text-settings-button-label = Postavke alternativnog teksta slike +pdfjs-editor-alt-text-settings-dialog-label = Postavke alternativnog teksta slike +pdfjs-editor-alt-text-settings-automatic-title = Automatski alternativni tekst +pdfjs-editor-alt-text-settings-create-model-button-label = Stvori alternativni tekst automatski +pdfjs-editor-alt-text-settings-create-model-description = Predlaže opise koji pomažu osobama koji ne mogu vidjeti sliku ili kada se slika ne uÄita. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Alternativni tekst UI modela ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Radi lokalno na tvom ureÄ‘aju kako bi tvoji podaci ostali privatni. Potrebno za automatski alternativni tekst. +pdfjs-editor-alt-text-settings-delete-model-button = IzbriÅ¡i +pdfjs-editor-alt-text-settings-download-model-button = Preuzmi +pdfjs-editor-alt-text-settings-downloading-model-button = Preuzimanje … +pdfjs-editor-alt-text-settings-editor-title = UreÄ‘ivaÄ alternativnog teksta +pdfjs-editor-alt-text-settings-show-dialog-button-label = Prikaži ureÄ‘ivaÄ alternativnog teksta odmah pri dodavanju slike +pdfjs-editor-alt-text-settings-show-dialog-description = Pomaže osigurati da sve tvoje slike imaju alternativni tekst. +pdfjs-editor-alt-text-settings-close-button = Zatvori diff --git a/public/assets/pdfjs/locale/hsb/viewer.ftl b/public/assets/pdfjs/locale/hsb/viewer.ftl new file mode 100755 index 0000000..065ab8d --- /dev/null +++ b/public/assets/pdfjs/locale/hsb/viewer.ftl @@ -0,0 +1,521 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = PÅ™edchadna strona +pdfjs-previous-button-label = Wróćo +pdfjs-next-button = + .title = PÅ™ichodna strona +pdfjs-next-button-label = Dale +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Strona +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = z { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } z { $pagesCount }) +pdfjs-zoom-out-button = + .title = Pomjeńšić +pdfjs-zoom-out-button-label = Pomjeńšić +pdfjs-zoom-in-button = + .title = PowjetÅ¡ić +pdfjs-zoom-in-button-label = PowjetÅ¡ić +pdfjs-zoom-select = + .title = Skalowanje +pdfjs-presentation-mode-button = + .title = Do prezentaciskeho modusa pÅ™eńć +pdfjs-presentation-mode-button-label = Prezentaciski modus +pdfjs-open-file-button = + .title = Dataju woÄinić +pdfjs-open-file-button-label = WoÄinić +pdfjs-print-button = + .title = Ćišćeć +pdfjs-print-button-label = Ćišćeć +pdfjs-save-button = + .title = SkÅ‚adować +pdfjs-save-button-label = SkÅ‚adować +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Sćahnyć +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Sćahnyć +pdfjs-bookmark-button = + .title = Aktualna strona (URL z aktualneje strony pokazać) +pdfjs-bookmark-button-label = Aktualna strona + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Nastroje +pdfjs-tools-button-label = Nastroje +pdfjs-first-page-button = + .title = K prÄ›njej stronje +pdfjs-first-page-button-label = K prÄ›njej stronje +pdfjs-last-page-button = + .title = K poslednjej stronje +pdfjs-last-page-button-label = K poslednjej stronje +pdfjs-page-rotate-cw-button = + .title = K smÄ›rej Äasnika wjerćeć +pdfjs-page-rotate-cw-button-label = K smÄ›rej Äasnika wjerćeć +pdfjs-page-rotate-ccw-button = + .title = PÅ™ećiwo smÄ›rej Äasnika wjerćeć +pdfjs-page-rotate-ccw-button-label = PÅ™ećiwo smÄ›rej Äasnika wjerćeć +pdfjs-cursor-text-select-tool-button = + .title = Nastroj za wubÄ›ranje teksta zmóžnić +pdfjs-cursor-text-select-tool-button-label = Nastroj za wubÄ›ranje teksta +pdfjs-cursor-hand-tool-button = + .title = RuÄny nastroj zmóžnić +pdfjs-cursor-hand-tool-button-label = RuÄny nastroj +pdfjs-scroll-page-button = + .title = Kulenje strony wužiwać +pdfjs-scroll-page-button-label = Kulenje strony +pdfjs-scroll-vertical-button = + .title = Wertikalne suwanje wužiwać +pdfjs-scroll-vertical-button-label = Wertikalne suwanje +pdfjs-scroll-horizontal-button = + .title = Horicontalne suwanje wužiwać +pdfjs-scroll-horizontal-button-label = Horicontalne suwanje +pdfjs-scroll-wrapped-button = + .title = Postupne suwanje wužiwać +pdfjs-scroll-wrapped-button-label = Postupne suwanje +pdfjs-spread-none-button = + .title = Strony njezwjazać +pdfjs-spread-none-button-label = Žana dwójna strona +pdfjs-spread-odd-button = + .title = Strony zapoÄinajo z njerunymi stronami zwjazać +pdfjs-spread-odd-button-label = Njerune strony +pdfjs-spread-even-button = + .title = Strony zapoÄinajo z runymi stronami zwjazać +pdfjs-spread-even-button-label = Rune strony + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Dokumentowe kajkosće… +pdfjs-document-properties-button-label = Dokumentowe kajkosće… +pdfjs-document-properties-file-name = Mjeno dataje: +pdfjs-document-properties-file-size = Wulkosć dataje: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bajtow) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bajtow) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bajtow) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bajtow) +pdfjs-document-properties-title = Titul: +pdfjs-document-properties-author = Awtor: +pdfjs-document-properties-subject = PÅ™edmjet: +pdfjs-document-properties-keywords = KluÄowe sÅ‚owa: +pdfjs-document-properties-creation-date = Datum wutworjenja: +pdfjs-document-properties-modification-date = Datum zmÄ›ny: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Awtor: +pdfjs-document-properties-producer = PDF-zhotowjer: +pdfjs-document-properties-version = PDF-wersija: +pdfjs-document-properties-page-count = LiÄba stronow: +pdfjs-document-properties-page-size = Wulkosć strony: +pdfjs-document-properties-page-size-unit-inches = cól +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = wysoki format +pdfjs-document-properties-page-size-orientation-landscape = prÄ›Äny format +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Fast Web View: +pdfjs-document-properties-linearized-yes = Haj +pdfjs-document-properties-linearized-no = NÄ› +pdfjs-document-properties-close-button = ZaÄinić + +## Print + +pdfjs-print-progress-message = Dokument so za ćišćenje pÅ™ihotuje… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = PÅ™etorhnyć +pdfjs-printing-not-supported = Warnowanje: Ćišćenje so pÅ™ez tutón wobhladowak poÅ‚nje njepodpÄ›ruje. +pdfjs-printing-not-ready = Warnowanje: PDF njeje so za ćišćenje dospoÅ‚nje zaÄitaÅ‚. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = BóÄnicu pokazać/schować +pdfjs-toggle-sidebar-notification-button = + .title = BóÄnicu pÅ™epinać (dokument rozrjad/pÅ™iwěški/worÅ¡ty wobsahuje) +pdfjs-toggle-sidebar-button-label = BóÄnicu pokazać/schować +pdfjs-document-outline-button = + .title = Dokumentowy naćisk pokazać (dwójne kliknjenje, zo bychu so wšě zapiski pokazali/schowali) +pdfjs-document-outline-button-label = Dokumentowa struktura +pdfjs-attachments-button = + .title = PÅ™iwěški pokazać +pdfjs-attachments-button-label = PÅ™iwěški +pdfjs-layers-button = + .title = WorÅ¡ty pokazać (klikńće dwójce, zo byšće wšě worÅ¡ty na standardny staw wróćo stajiÅ‚) +pdfjs-layers-button-label = WorÅ¡ty +pdfjs-thumbs-button = + .title = Miniatury pokazać +pdfjs-thumbs-button-label = Miniatury +pdfjs-current-outline-item-button = + .title = Aktualny rozrjadowy zapisk pytać +pdfjs-current-outline-item-button-label = Aktualny rozrjadowy zapisk +pdfjs-findbar-button = + .title = W dokumenće pytać +pdfjs-findbar-button-label = Pytać +pdfjs-additional-layers = DalÅ¡e worÅ¡ty + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Strona { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura strony { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Pytać + .placeholder = W dokumenće pytać… +pdfjs-find-previous-button = + .title = PÅ™edchadne wustupowanje pytanskeho wuraza pytać +pdfjs-find-previous-button-label = Wróćo +pdfjs-find-next-button = + .title = PÅ™ichodne wustupowanje pytanskeho wuraza pytać +pdfjs-find-next-button-label = Dale +pdfjs-find-highlight-checkbox = Wšě wuzbÄ›hnyć +pdfjs-find-match-case-checkbox-label = Wulkopisanje wobkedźbować +pdfjs-find-match-diacritics-checkbox-label = Diakritiske znamjeÅ¡ka wužiwać +pdfjs-find-entire-word-checkbox-label = CyÅ‚e sÅ‚owa +pdfjs-find-reached-top = SpoÄatk dokumenta docpÄ›ty, pokroÄuje so z kóncom +pdfjs-find-reached-bottom = Kónc dokument docpÄ›ty, pokroÄuje so ze spoÄatkom +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } z { $total } wotpowÄ›dnika + [two] { $current } z { $total } wotpowÄ›dnikow + [few] { $current } z { $total } wotpowÄ›dnikow + *[other] { $current } z { $total } wotpowÄ›dnikow + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] WyÅ¡e { $limit } wotpowÄ›dnik + [two] WyÅ¡e { $limit } wotpowÄ›dnikaj + [few] WyÅ¡e { $limit } wotpowÄ›dniki + *[other] WyÅ¡e { $limit } wotpowÄ›dnikow + } +pdfjs-find-not-found = Pytanski wuraz njeje so namakaÅ‚ + +## Predefined zoom values + +pdfjs-page-scale-width = Å Ä›rokosć strony +pdfjs-page-scale-fit = Wulkosć strony +pdfjs-page-scale-auto = Awtomatiske skalowanje +pdfjs-page-scale-actual = Aktualna wulkosć +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Strona { $page } + +## Loading indicator messages + +pdfjs-loading-error = PÅ™i zaÄitowanju PDF je zmylk wustupiÅ‚. +pdfjs-invalid-file-error = NjepÅ‚aćiwa abo wobÅ¡kodźena PDF-dataja. +pdfjs-missing-file-error = Falowaca PDF-dataja. +pdfjs-unexpected-response-error = NjewoÄakowana serwerowa wotmoÅ‚wa. +pdfjs-rendering-error = PÅ™i zwobraznjenju strony je zmylk wustupiÅ‚. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Typ pÅ™ispomnjenki: { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Zapodajće hesÅ‚o, zo byšće PDF-dataju woÄiniÅ‚. +pdfjs-password-invalid = NjepÅ‚aćiwe hesÅ‚o. ProÅ¡u spytajće hišće raz. +pdfjs-password-ok-button = W porjadku +pdfjs-password-cancel-button = PÅ™etorhnyć +pdfjs-web-fonts-disabled = Webpisma su znjemóžnjene: njeje móžno, zasadźene PDF-pisma wužiwać. + +## Editing + +pdfjs-editor-free-text-button = + .title = Tekst +pdfjs-editor-free-text-button-label = Tekst +pdfjs-editor-ink-button = + .title = Rysować +pdfjs-editor-ink-button-label = Rysować +pdfjs-editor-stamp-button = + .title = Wobrazy pÅ™idać abo wobdźěłać +pdfjs-editor-stamp-button-label = Wobrazy pÅ™idać abo wobdźěłać +pdfjs-editor-highlight-button = + .title = WuzbÄ›hnyć +pdfjs-editor-highlight-button-label = WuzbÄ›hnyć +pdfjs-highlight-floating-button1 = + .title = WuzbÄ›hnjenje + .aria-label = WuzbÄ›hnjenje +pdfjs-highlight-floating-button-label = WuzbÄ›hnjenje + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Rysowanku wotstronić +pdfjs-editor-remove-freetext-button = + .title = Tekst wotstronić +pdfjs-editor-remove-stamp-button = + .title = Wobraz wotstronić +pdfjs-editor-remove-highlight-button = + .title = WuzbÄ›hnjenje wotstronić + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Barba +pdfjs-editor-free-text-size-input = Wulkosć +pdfjs-editor-ink-color-input = Barba +pdfjs-editor-ink-thickness-input = ToÅ‚stosć +pdfjs-editor-ink-opacity-input = Opacita +pdfjs-editor-stamp-add-image-button = + .title = Wobraz pÅ™idać +pdfjs-editor-stamp-add-image-button-label = Wobraz pÅ™idać +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = ToÅ‚stosć +pdfjs-editor-free-highlight-thickness-title = + .title = ToÅ‚stosć zmÄ›nić, hdyž so zapiski wuzbÄ›huja, kotrež tekst njejsu +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Tekstowy editor + .default-content = ZapoÄńće pisać … +pdfjs-free-text = + .aria-label = Tekstowy editor +pdfjs-free-text-default-content = ZapoÄńće pisać… +pdfjs-ink = + .aria-label = Rysowanski editor +pdfjs-ink-canvas = + .aria-label = Wobraz wutworjeny wot wužiwarja + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alternatiwny tekst +pdfjs-editor-alt-text-edit-button = + .aria-label = Alternatiwny tekst wobdźěłać +pdfjs-editor-alt-text-edit-button-label = Alternatiwny tekst wobdźěłać +pdfjs-editor-alt-text-dialog-label = Nastajenje wubrać +pdfjs-editor-alt-text-dialog-description = Alternatiwny tekst pomha, hdyž ludźo njemóža wobraz widźeć abo hdyž so wobraz njezaÄita. +pdfjs-editor-alt-text-add-description-label = Wopisanje pÅ™idać +pdfjs-editor-alt-text-add-description-description = Pisajće 1 sadu abo 2 sadźe, kotrejž temu, nastajenje abo akcije wopisujetej. +pdfjs-editor-alt-text-mark-decorative-label = Jako dekoratiwny markÄ›rować +pdfjs-editor-alt-text-mark-decorative-description = To so za pyÅ¡ace wobrazy wužiwa, na pÅ™ikÅ‚ad ramiki abo wodowe znamjenja. +pdfjs-editor-alt-text-cancel-button = PÅ™etorhnyć +pdfjs-editor-alt-text-save-button = SkÅ‚adować +pdfjs-editor-alt-text-decorative-tooltip = Jako dekoratiwny markÄ›rowany +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Na pÅ™ikÅ‚ad, „MÅ‚ody muž za blidom sedźi, zo by jÄ›dź jÄ›dł“ +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alternatiwny tekst + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Horjeka nalÄ›wo – wulkosć zmÄ›nić +pdfjs-editor-resizer-label-top-middle = Horjeka wosrjedź – wulkosć zmÄ›nić +pdfjs-editor-resizer-label-top-right = Horjeka naprawo – wulkosć zmÄ›nić +pdfjs-editor-resizer-label-middle-right = Wosrjedź naprawo – wulkosć zmÄ›nić +pdfjs-editor-resizer-label-bottom-right = Deleka naprawo – wulkosć zmÄ›nić +pdfjs-editor-resizer-label-bottom-middle = Deleka wosrjedź – wulkosć zmÄ›nić +pdfjs-editor-resizer-label-bottom-left = Deleka nalÄ›wo – wulkosć zmÄ›nić +pdfjs-editor-resizer-label-middle-left = Wosrjedź nalÄ›wo – wulkosć zmÄ›nić +pdfjs-editor-resizer-top-left = + .aria-label = Horjeka nalÄ›wo – wulkosć zmÄ›nić +pdfjs-editor-resizer-top-middle = + .aria-label = Horjeka wosrjedź – wulkosć zmÄ›nić +pdfjs-editor-resizer-top-right = + .aria-label = Horjeka naprawo – wulkosć zmÄ›nić +pdfjs-editor-resizer-middle-right = + .aria-label = Wosrjedź naprawo – wulkosć zmÄ›nić +pdfjs-editor-resizer-bottom-right = + .aria-label = Deleka naprawo – wulkosć zmÄ›nić +pdfjs-editor-resizer-bottom-middle = + .aria-label = Deleka wosrjedź – wulkosć zmÄ›nić +pdfjs-editor-resizer-bottom-left = + .aria-label = Deleka nalÄ›wo – wulkosć zmÄ›nić +pdfjs-editor-resizer-middle-left = + .aria-label = Wosrjedź nalÄ›wo – wulkosć zmÄ›nić + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Barba wuzbÄ›hnjenja +pdfjs-editor-colorpicker-button = + .title = Barbu zmÄ›nić +pdfjs-editor-colorpicker-dropdown = + .aria-label = WubÄ›r barbow +pdfjs-editor-colorpicker-yellow = + .title = ŽoÅ‚ty +pdfjs-editor-colorpicker-green = + .title = Zeleny +pdfjs-editor-colorpicker-blue = + .title = Módry +pdfjs-editor-colorpicker-pink = + .title = Pink +pdfjs-editor-colorpicker-red = + .title = ÄŒerwjeny + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Wšě pokazać +pdfjs-editor-highlight-show-all-button = + .title = Wšě pokazać + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Alternatiwny tekst wobdźěłać (wobrazowe wopisanje) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Alternatiwny tekst pÅ™idać (wobrazowe wopisanje) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Pisajće tu swoje wopisanje… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Krótke wopisanje za ludźi, kotÅ™iž njemóžeće wobraz widźeć abo hdyž so wobraz njezaÄita. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Tutón alternatiwny tekst je so awtomatisce wutworiÅ‚ a je snano njedokÅ‚adny. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = DalÅ¡e informacije +pdfjs-editor-new-alt-text-create-automatically-button-label = Alternatiwny tekst awtomatisce wutworić +pdfjs-editor-new-alt-text-not-now-button = Nic nÄ›tko +pdfjs-editor-new-alt-text-error-title = Alternatiwny tekst njeda so awtomatisce wutworić +pdfjs-editor-new-alt-text-error-description = ProÅ¡u pisajće swój alternatiwny tekst abo spytajće pozdźiÅ¡o hišće raz. +pdfjs-editor-new-alt-text-error-close-button = ZaÄinić +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Model KI za alternatiwny tekst so sćahuje ({ $downloadedSize } z { $totalSize } MB) + .aria-valuetext = Model KI za alternatiwny tekst so sćahuje ({ $downloadedSize } z { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alternatiwny tekst je so pÅ™idaÅ‚ +pdfjs-editor-new-alt-text-added-button-label = Alternatiwny tekst je so pÅ™idaÅ‚ +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Alternatiwny tekst faluje +pdfjs-editor-new-alt-text-missing-button-label = Alternatiwny tekst faluje +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Alternatiwny tekst pÅ™epruwować +pdfjs-editor-new-alt-text-to-review-button-label = Alternatiwny tekst pÅ™epruwować +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Awtomatisce wutworjeny: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Nastajenja alternatiwneho wobrazoweho teksta +pdfjs-image-alt-text-settings-button-label = Nastajenja alternatiwneho wobrazoweho teksta +pdfjs-editor-alt-text-settings-dialog-label = Nastajenja alternatiwneho wobrazoweho teksta +pdfjs-editor-alt-text-settings-automatic-title = Awtomatiski alternatiwny tekst +pdfjs-editor-alt-text-settings-create-model-button-label = Alternatiwny tekst awtomatisce wutworić +pdfjs-editor-alt-text-settings-create-model-description = Namjetuje wopisanja, zo by ludźom pomhaÅ‚, kotÅ™iž njemóžeće wobraz widźeć abo hdyž so wobraz njezaÄita. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model KI alternatiwneho teksta ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Běži lokalnje na waÅ¡im graće, zo bychu waÅ¡e daty priwatne wostali. Za awtomatiski alternatiwny tekst trÄ›bny. +pdfjs-editor-alt-text-settings-delete-model-button = ZhaÅ¡eć +pdfjs-editor-alt-text-settings-download-model-button = Sćahnyć +pdfjs-editor-alt-text-settings-downloading-model-button = Sćahuje so… +pdfjs-editor-alt-text-settings-editor-title = Editor za alternatiwny tekst +pdfjs-editor-alt-text-settings-show-dialog-button-label = Editor alternatiwneho teksta hnydom pokazać, hdyž so wobraz pÅ™idawa +pdfjs-editor-alt-text-settings-show-dialog-description = Pomha, wam wšěm swojim wobrazam alternatiwny tekst pÅ™idać. +pdfjs-editor-alt-text-settings-close-button = ZaÄinić + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Wotstronjene wuzbÄ›hnyć +pdfjs-editor-undo-bar-message-freetext = Tekst je so wotstroniÅ‚ +pdfjs-editor-undo-bar-message-ink = Rysowanka je so wotstroniÅ‚a +pdfjs-editor-undo-bar-message-stamp = Wobraz je so wotstroniÅ‚ +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } pÅ™ispomnjenka je so wotstroniÅ‚a + [two] { $count } pÅ™ispomnjence stej so wotstroniÅ‚oj + [few] { $count } pÅ™ispomnjenki su so wotstronili + *[other] { $count } pÅ™ispomnjenkow je so wotstroniÅ‚o + } +pdfjs-editor-undo-bar-undo-button = + .title = Cofnyć +pdfjs-editor-undo-bar-undo-button-label = Cofnyć +pdfjs-editor-undo-bar-close-button = + .title = ZaÄinić +pdfjs-editor-undo-bar-close-button-label = ZaÄinić diff --git a/public/assets/pdfjs/locale/hu/viewer.ftl b/public/assets/pdfjs/locale/hu/viewer.ftl new file mode 100755 index 0000000..76307a2 --- /dev/null +++ b/public/assets/pdfjs/locale/hu/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = ElÅ‘zÅ‘ oldal +pdfjs-previous-button-label = ElÅ‘zÅ‘ +pdfjs-next-button = + .title = KövetkezÅ‘ oldal +pdfjs-next-button-label = Tovább +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Oldal +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = összesen: { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } / { $pagesCount }) +pdfjs-zoom-out-button = + .title = Kicsinyítés +pdfjs-zoom-out-button-label = Kicsinyítés +pdfjs-zoom-in-button = + .title = Nagyítás +pdfjs-zoom-in-button-label = Nagyítás +pdfjs-zoom-select = + .title = Nagyítás +pdfjs-presentation-mode-button = + .title = Váltás bemutató módba +pdfjs-presentation-mode-button-label = Bemutató mód +pdfjs-open-file-button = + .title = Fájl megnyitása +pdfjs-open-file-button-label = Megnyitás +pdfjs-print-button = + .title = Nyomtatás +pdfjs-print-button-label = Nyomtatás +pdfjs-save-button = + .title = Mentés +pdfjs-save-button-label = Mentés +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Letöltés +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Letöltés +pdfjs-bookmark-button = + .title = Jelenlegi oldal (webcím megtekintése a jelenlegi oldalról) +pdfjs-bookmark-button-label = Jelenlegi oldal + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Eszközök +pdfjs-tools-button-label = Eszközök +pdfjs-first-page-button = + .title = Ugrás az elsÅ‘ oldalra +pdfjs-first-page-button-label = Ugrás az elsÅ‘ oldalra +pdfjs-last-page-button = + .title = Ugrás az utolsó oldalra +pdfjs-last-page-button-label = Ugrás az utolsó oldalra +pdfjs-page-rotate-cw-button = + .title = Forgatás az óramutató járásával egyezÅ‘en +pdfjs-page-rotate-cw-button-label = Forgatás az óramutató járásával egyezÅ‘en +pdfjs-page-rotate-ccw-button = + .title = Forgatás az óramutató járásával ellentétesen +pdfjs-page-rotate-ccw-button-label = Forgatás az óramutató járásával ellentétesen +pdfjs-cursor-text-select-tool-button = + .title = SzövegkijelölÅ‘ eszköz bekapcsolása +pdfjs-cursor-text-select-tool-button-label = SzövegkijelölÅ‘ eszköz +pdfjs-cursor-hand-tool-button = + .title = Kéz eszköz bekapcsolása +pdfjs-cursor-hand-tool-button-label = Kéz eszköz +pdfjs-scroll-page-button = + .title = Oldalgörgetés használata +pdfjs-scroll-page-button-label = Oldalgörgetés +pdfjs-scroll-vertical-button = + .title = FüggÅ‘leges görgetés használata +pdfjs-scroll-vertical-button-label = FüggÅ‘leges görgetés +pdfjs-scroll-horizontal-button = + .title = Vízszintes görgetés használata +pdfjs-scroll-horizontal-button-label = Vízszintes görgetés +pdfjs-scroll-wrapped-button = + .title = Rácsos elrendezés használata +pdfjs-scroll-wrapped-button-label = Rácsos elrendezés +pdfjs-spread-none-button = + .title = Ne tapassza össze az oldalakat +pdfjs-spread-none-button-label = Nincs összetapasztás +pdfjs-spread-odd-button = + .title = Lapok összetapasztása, a páratlan számú oldalakkal kezdve +pdfjs-spread-odd-button-label = Összetapasztás: páratlan +pdfjs-spread-even-button = + .title = Lapok összetapasztása, a páros számú oldalakkal kezdve +pdfjs-spread-even-button-label = Összetapasztás: páros + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Dokumentum tulajdonságai… +pdfjs-document-properties-button-label = Dokumentum tulajdonságai… +pdfjs-document-properties-file-name = Fájlnév: +pdfjs-document-properties-file-size = Fájlméret: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kB ({ $b } bájt) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bájt) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bájt) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bájt) +pdfjs-document-properties-title = Cím: +pdfjs-document-properties-author = SzerzÅ‘: +pdfjs-document-properties-subject = Tárgy: +pdfjs-document-properties-keywords = Kulcsszavak: +pdfjs-document-properties-creation-date = Létrehozás dátuma: +pdfjs-document-properties-modification-date = Módosítás dátuma: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Létrehozta: +pdfjs-document-properties-producer = PDF előállító: +pdfjs-document-properties-version = PDF verzió: +pdfjs-document-properties-page-count = Oldalszám: +pdfjs-document-properties-page-size = Lapméret: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = álló +pdfjs-document-properties-page-size-orientation-landscape = fekvÅ‘ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Jogi információk + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Gyors webes nézet: +pdfjs-document-properties-linearized-yes = Igen +pdfjs-document-properties-linearized-no = Nem +pdfjs-document-properties-close-button = Bezárás + +## Print + +pdfjs-print-progress-message = Dokumentum elÅ‘készítése nyomtatáshoz… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Mégse +pdfjs-printing-not-supported = Figyelmeztetés: Ez a böngészÅ‘ nem teljesen támogatja a nyomtatást. +pdfjs-printing-not-ready = Figyelmeztetés: A PDF nincs teljesen betöltve a nyomtatáshoz. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Oldalsáv be/ki +pdfjs-toggle-sidebar-notification-button = + .title = Oldalsáv be/ki (a dokumentum vázlatot/mellékleteket/rétegeket tartalmaz) +pdfjs-toggle-sidebar-button-label = Oldalsáv be/ki +pdfjs-document-outline-button = + .title = Dokumentum megjelenítése online (dupla kattintás minden elem kinyitásához/összecsukásához) +pdfjs-document-outline-button-label = Dokumentumvázlat +pdfjs-attachments-button = + .title = Mellékletek megjelenítése +pdfjs-attachments-button-label = Van melléklet +pdfjs-layers-button = + .title = Rétegek megjelenítése (dupla kattintás az összes réteg alapértelmezett állapotra visszaállításához) +pdfjs-layers-button-label = Rétegek +pdfjs-thumbs-button = + .title = Bélyegképek megjelenítése +pdfjs-thumbs-button-label = Bélyegképek +pdfjs-current-outline-item-button = + .title = Jelenlegi vázlatelem megkeresése +pdfjs-current-outline-item-button-label = Jelenlegi vázlatelem +pdfjs-findbar-button = + .title = Keresés a dokumentumban +pdfjs-findbar-button-label = Keresés +pdfjs-additional-layers = További rétegek + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = { $page }. oldal +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = { $page }. oldal bélyegképe + +## Find panel button title and messages + +pdfjs-find-input = + .title = Keresés + .placeholder = Keresés a dokumentumban… +pdfjs-find-previous-button = + .title = A kifejezés elÅ‘zÅ‘ elÅ‘fordulásának keresése +pdfjs-find-previous-button-label = ElÅ‘zÅ‘ +pdfjs-find-next-button = + .title = A kifejezés következÅ‘ elÅ‘fordulásának keresése +pdfjs-find-next-button-label = Tovább +pdfjs-find-highlight-checkbox = Összes kiemelése +pdfjs-find-match-case-checkbox-label = Kis- és nagybetűk megkülönböztetése +pdfjs-find-match-diacritics-checkbox-label = Diakritikus jelek +pdfjs-find-entire-word-checkbox-label = Teljes szavak +pdfjs-find-reached-top = A dokumentum eleje elérve, folytatás a végétÅ‘l +pdfjs-find-reached-bottom = A dokumentum vége elérve, folytatás az elejétÅ‘l +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } / { $total } találat + *[other] { $current } / { $total } találat + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Több mint { $limit } találat + *[other] Több mint { $limit } találat + } +pdfjs-find-not-found = A kifejezés nem található + +## Predefined zoom values + +pdfjs-page-scale-width = Oldalszélesség +pdfjs-page-scale-fit = Teljes oldal +pdfjs-page-scale-auto = Automatikus nagyítás +pdfjs-page-scale-actual = Valódi méret +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = { $page }. oldal + +## Loading indicator messages + +pdfjs-loading-error = Hiba történt a PDF betöltésekor. +pdfjs-invalid-file-error = Érvénytelen vagy sérült PDF fájl. +pdfjs-missing-file-error = Hiányzó PDF fájl. +pdfjs-unexpected-response-error = Váratlan kiszolgálóválasz. +pdfjs-rendering-error = Hiba történt az oldal feldolgozása közben. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } megjegyzés] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Adja meg a jelszót a PDF fájl megnyitásához. +pdfjs-password-invalid = Helytelen jelszó. Próbálja újra. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Mégse +pdfjs-web-fonts-disabled = Webes betűkészletek letiltva: nem használhatók a beágyazott PDF betűkészletek. + +## Editing + +pdfjs-editor-free-text-button = + .title = Szöveg +pdfjs-editor-free-text-button-label = Szöveg +pdfjs-editor-ink-button = + .title = Rajzolás +pdfjs-editor-ink-button-label = Rajzolás +pdfjs-editor-stamp-button = + .title = Képek hozzáadása vagy szerkesztése +pdfjs-editor-stamp-button-label = Képek hozzáadása vagy szerkesztése +pdfjs-editor-highlight-button = + .title = Kiemelés +pdfjs-editor-highlight-button-label = Kiemelés +pdfjs-highlight-floating-button1 = + .title = Kiemelés + .aria-label = Kiemelés +pdfjs-highlight-floating-button-label = Kiemelés + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Rajz eltávolítása +pdfjs-editor-remove-freetext-button = + .title = Szöveg eltávolítása +pdfjs-editor-remove-stamp-button = + .title = Kép eltávolítása +pdfjs-editor-remove-highlight-button = + .title = Kiemelés eltávolítása + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Szín +pdfjs-editor-free-text-size-input = Méret +pdfjs-editor-ink-color-input = Szín +pdfjs-editor-ink-thickness-input = Vastagság +pdfjs-editor-ink-opacity-input = Ãtlátszatlanság +pdfjs-editor-stamp-add-image-button = + .title = Kép hozzáadása +pdfjs-editor-stamp-add-image-button-label = Kép hozzáadása +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Vastagság +pdfjs-editor-free-highlight-thickness-title = + .title = Vastagság módosítása, ha nem szöveges elemeket emel ki +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = SzövegszerkesztÅ‘ + .default-content = Kezdjen gépelni… +pdfjs-free-text = + .aria-label = SzövegszerkesztÅ‘ +pdfjs-free-text-default-content = Kezdjen el gépelni… +pdfjs-ink = + .aria-label = RajzszerkesztÅ‘ +pdfjs-ink-canvas = + .aria-label = Felhasználó által készített kép + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alternatív szöveg +pdfjs-editor-alt-text-edit-button = + .aria-label = Alternatív szöveg szerkesztése +pdfjs-editor-alt-text-edit-button-label = Alternatív szöveg szerkesztése +pdfjs-editor-alt-text-dialog-label = Válasszon egy lehetÅ‘séget +pdfjs-editor-alt-text-dialog-description = Az alternatív szöveg segít, ha az emberek nem látják a képet, vagy ha az nem töltÅ‘dik be. +pdfjs-editor-alt-text-add-description-label = Leírás hozzáadása +pdfjs-editor-alt-text-add-description-description = Törekedjen 1-2 mondatra, amely jellemzi a témát, környezetet vagy cselekvést. +pdfjs-editor-alt-text-mark-decorative-label = Megjelölés dekoratívként +pdfjs-editor-alt-text-mark-decorative-description = Ez a díszítÅ‘képeknél használatos, mint a szegélyek vagy a vízjelek. +pdfjs-editor-alt-text-cancel-button = Mégse +pdfjs-editor-alt-text-save-button = Mentés +pdfjs-editor-alt-text-decorative-tooltip = Megjelölve dekoratívként +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Például: „Egy fiatal férfi leül enni egy asztalhoz†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alternatív szöveg + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Bal felsÅ‘ sarok – átméretezés +pdfjs-editor-resizer-label-top-middle = Felül középen – átméretezés +pdfjs-editor-resizer-label-top-right = Jobb felsÅ‘ sarok – átméretezés +pdfjs-editor-resizer-label-middle-right = Jobbra középen – átméretezés +pdfjs-editor-resizer-label-bottom-right = Jobb alsó sarok – átméretezés +pdfjs-editor-resizer-label-bottom-middle = Alul középen – átméretezés +pdfjs-editor-resizer-label-bottom-left = Bal alsó sarok – átméretezés +pdfjs-editor-resizer-label-middle-left = Balra középen – átméretezés +pdfjs-editor-resizer-top-left = + .aria-label = Bal felsÅ‘ sarok – átméretezés +pdfjs-editor-resizer-top-middle = + .aria-label = Felül középen – átméretezés +pdfjs-editor-resizer-top-right = + .aria-label = Jobb felsÅ‘ sarok – átméretezés +pdfjs-editor-resizer-middle-right = + .aria-label = Jobbra középen – átméretezés +pdfjs-editor-resizer-bottom-right = + .aria-label = Jobb alsó sarok – átméretezés +pdfjs-editor-resizer-bottom-middle = + .aria-label = Alul középen – átméretezés +pdfjs-editor-resizer-bottom-left = + .aria-label = Bal alsó sarok – átméretezés +pdfjs-editor-resizer-middle-left = + .aria-label = Balra középen – átméretezés + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Kiemelés színe +pdfjs-editor-colorpicker-button = + .title = Szín módosítása +pdfjs-editor-colorpicker-dropdown = + .aria-label = Színválasztások +pdfjs-editor-colorpicker-yellow = + .title = Sárga +pdfjs-editor-colorpicker-green = + .title = Zöld +pdfjs-editor-colorpicker-blue = + .title = Kék +pdfjs-editor-colorpicker-pink = + .title = Rózsaszín +pdfjs-editor-colorpicker-red = + .title = Vörös + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Összes megjelenítése +pdfjs-editor-highlight-show-all-button = + .title = Összes megjelenítése + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Alternatív szöveg szerkesztése (képleírás) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Alternatív szöveg hozzáadása (képleírás) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Ãrja ide a leírását… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Rövid leírás azoknak, akik nem látják a képet, vagy arra az esetre, ha a kép nem tölt be. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Ez az alternatív szöveg automatikusan lett létrehozva, és pontatlan lehet. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = További tudnivalók +pdfjs-editor-new-alt-text-create-automatically-button-label = Alternatív szöveg automatikus létrehozása +pdfjs-editor-new-alt-text-not-now-button = Most nem +pdfjs-editor-new-alt-text-error-title = Az alternatív szöveg automatikus létrehozása nem sikerült +pdfjs-editor-new-alt-text-error-description = Ãrja meg a saját alternatív szövegét, vagy próbálja újra késÅ‘bb. +pdfjs-editor-new-alt-text-error-close-button = Bezárás +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Alternatív szöveg MI modell letöltése ({ $downloadedSize } / { $totalSize } MB) + .aria-valuetext = Alternatív szöveg MI modell letöltése ({ $downloadedSize } / { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alternatív szöveg hozzáadva +pdfjs-editor-new-alt-text-added-button-label = Alternatív szöveg hozzáadva +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Hiányzó alternatív szöveg +pdfjs-editor-new-alt-text-missing-button-label = Hiányzó alternatív szöveg +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Alternatív szöveg áttekintése +pdfjs-editor-new-alt-text-to-review-button-label = Alternatív szöveg szerkesztése +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automatikusan létrehozva: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Kép alternatív szövegének beállításai +pdfjs-image-alt-text-settings-button-label = Kép alternatív szövegének beállításai +pdfjs-editor-alt-text-settings-dialog-label = Kép alternatív szövegének beállításai +pdfjs-editor-alt-text-settings-automatic-title = Automatikus alternatív szöveg +pdfjs-editor-alt-text-settings-create-model-button-label = Alternatív szöveg automatikus létrehozása +pdfjs-editor-alt-text-settings-create-model-description = Leírásokat javasol, hogy segítsen azoknak, akik nem látják a képet, vagy arra az esetre, ha a kép nem tölt be. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Alternatív szöveg MI modellje ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Helyben fut az eszközén, így az adatai privátok maradnak. Az automatikus alternatív szövegekhez szükséges. +pdfjs-editor-alt-text-settings-delete-model-button = Törlés +pdfjs-editor-alt-text-settings-download-model-button = Letöltés +pdfjs-editor-alt-text-settings-downloading-model-button = Letöltés… +pdfjs-editor-alt-text-settings-editor-title = Alternatív szöveg szerkesztÅ‘je +pdfjs-editor-alt-text-settings-show-dialog-button-label = Az alternatív szöveg szerkesztÅ‘jének azonnali megjelenítése egy kép hozzáadásakor +pdfjs-editor-alt-text-settings-show-dialog-description = Segít elérni, hogy az összes képén legyen alternatív szöveg. +pdfjs-editor-alt-text-settings-close-button = Bezárás + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Kiemelés eltávolítva +pdfjs-editor-undo-bar-message-freetext = Szöveg eltávolítva +pdfjs-editor-undo-bar-message-ink = Rajz eltávolítva +pdfjs-editor-undo-bar-message-stamp = Kép eltávolítva +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } kommentár eltávolítva + *[other] { $count } kommentár eltávolítva + } +pdfjs-editor-undo-bar-undo-button = + .title = Visszavonás +pdfjs-editor-undo-bar-undo-button-label = Visszavonás +pdfjs-editor-undo-bar-close-button = + .title = Bezárás +pdfjs-editor-undo-bar-close-button-label = Bezárás diff --git a/public/assets/pdfjs/locale/hy-AM/viewer.ftl b/public/assets/pdfjs/locale/hy-AM/viewer.ftl new file mode 100755 index 0000000..5c9dd27 --- /dev/null +++ b/public/assets/pdfjs/locale/hy-AM/viewer.ftl @@ -0,0 +1,272 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Õ†Õ¡Õ­Õ¸Ö€Õ¤ Õ§Õ»Õ¨ +pdfjs-previous-button-label = Õ†Õ¡Õ­Õ¸Ö€Õ¤Õ¨ +pdfjs-next-button = + .title = Õ€Õ¡Õ»Õ¸Ö€Õ¤ Õ§Õ»Õ¨ +pdfjs-next-button-label = Õ€Õ¡Õ»Õ¸Ö€Õ¤Õ¨ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Ô·Õ». +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = -Õ¨Õ { $pagesCount }-Õ«Ö +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber }-Õ¨ { $pagesCount })-Õ«Ö +pdfjs-zoom-out-button = + .title = Õ“Õ¸Ö„Ö€Õ¡ÖÕ¶Õ¥Õ¬ +pdfjs-zoom-out-button-label = Õ“Õ¸Ö„Ö€Õ¡ÖÕ¶Õ¥Õ¬ +pdfjs-zoom-in-button = + .title = Ô½Õ¸Õ·Õ¸Ö€Õ¡ÖÕ¶Õ¥Õ¬ +pdfjs-zoom-in-button-label = Ô½Õ¸Õ·Õ¸Ö€Õ¡ÖÕ¶Õ¥Õ¬ +pdfjs-zoom-select = + .title = Ô´Õ«Õ¿Õ¡ÖƒÕ¸Õ­Õ¸Ö‚Õ´ +pdfjs-presentation-mode-button = + .title = Ô±Õ¶ÖÕ¶Õ¥Õ¬ Õ†Õ¥Ö€Õ¯Õ¡ÕµÕ¡ÖÕ´Õ¡Õ¶ Õ¥Õ²Õ¡Õ¶Õ¡Õ¯Õ«Õ¶ +pdfjs-presentation-mode-button-label = Õ†Õ¥Ö€Õ¯Õ¡ÕµÕ¡ÖÕ´Õ¡Õ¶ Õ¥Õ²Õ¡Õ¶Õ¡Õ¯ +pdfjs-open-file-button = + .title = Ô²Õ¡ÖÕ¥Õ¬ Õ¶Õ«Õ·Ö„ +pdfjs-open-file-button-label = Ô²Õ¡ÖÕ¥Õ¬ +pdfjs-print-button = + .title = ÕÕºÕ¥Õ¬ +pdfjs-print-button-label = ÕÕºÕ¥Õ¬ +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Õ†Õ¥Ö€Õ¢Õ¥Õ¼Õ¶Õ¥Õ¬ +pdfjs-bookmark-button-label = Ô¸Õ¶Õ©Õ¡ÖÕ«Õ¯ Õ§Õ» + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Ô³Õ¸Ö€Õ®Õ«Ö„Õ¶Õ¥Ö€ +pdfjs-tools-button-label = Ô³Õ¸Ö€Õ®Õ«Ö„Õ¶Õ¥Ö€ +pdfjs-first-page-button = + .title = Ô±Õ¶ÖÕ¶Õ¥Õ¬ Õ¡Õ¼Õ¡Õ»Õ«Õ¶ Õ§Õ»Õ«Õ¶ +pdfjs-first-page-button-label = Ô±Õ¶ÖÕ¶Õ¥Õ¬ Õ¡Õ¼Õ¡Õ»Õ«Õ¶ Õ§Õ»Õ«Õ¶ +pdfjs-last-page-button = + .title = Ô±Õ¶ÖÕ¶Õ¥Õ¬ Õ¾Õ¥Ö€Õ»Õ«Õ¶ Õ§Õ»Õ«Õ¶ +pdfjs-last-page-button-label = Ô±Õ¶ÖÕ¶Õ¥Õ¬ Õ¾Õ¥Ö€Õ»Õ«Õ¶ Õ§Õ»Õ«Õ¶ +pdfjs-page-rotate-cw-button = + .title = ÕŠÕ¿Õ¿Õ¥Õ¬ Õ¨Õ½Õ¿ ÕªÕ¡Õ´Õ¡ÖÕ¸Ö‚ÕµÖÕ« Õ½Õ¬Õ¡Ö„Õ« +pdfjs-page-rotate-cw-button-label = ÕŠÕ¿Õ¿Õ¥Õ¬ Õ¨Õ½Õ¿ ÕªÕ¡Õ´Õ¡ÖÕ¸Ö‚ÕµÖÕ« Õ½Õ¬Õ¡Ö„Õ« +pdfjs-page-rotate-ccw-button = + .title = ÕŠÕ¿Õ¿Õ¥Õ¬ Õ°Õ¡Õ¯Õ¡Õ¼Õ¡Õ¯ ÕªÕ¡Õ´Õ¡ÖÕ¸Ö‚ÕµÖÕ« Õ½Õ¬Õ¡Ö„Õ« +pdfjs-page-rotate-ccw-button-label = ÕŠÕ¿Õ¿Õ¥Õ¬ Õ°Õ¡Õ¯Õ¡Õ¼Õ¡Õ¯ ÕªÕ¡Õ´Õ¡ÖÕ¸Ö‚ÕµÖÕ« Õ½Õ¬Õ¡Ö„Õ« +pdfjs-cursor-text-select-tool-button = + .title = Õ„Õ«Õ¡ÖÕ¶Õ¥Õ¬ Õ£Ö€Õ¸Ö‚ÕµÕ© Õ¨Õ¶Õ¿Ö€Õ¥Õ¬Õ¸Ö‚ Õ£Õ¸Ö€Õ®Õ«Ö„Õ¨ +pdfjs-cursor-text-select-tool-button-label = Ô³Ö€Õ¸Ö‚ÕµÕ©Õ¨ Õ¨Õ¶Õ¿Ö€Õ¥Õ¬Õ¸Ö‚ Õ£Õ¸Ö€Õ®Õ«Ö„ +pdfjs-cursor-hand-tool-button = + .title = Õ„Õ«Õ¡ÖÕ¶Õ¥Õ¬ ÕÕ¥Õ¼Ö„Õ« Õ£Õ¸Ö€Õ®Õ«Ö„Õ¨ +pdfjs-cursor-hand-tool-button-label = ÕÕ¥Õ¼Ö„Õ« Õ£Õ¸Ö€Õ®Õ«Ö„ +pdfjs-scroll-vertical-button = + .title = Õ•Õ£Õ¿Õ¡Õ£Õ¸Ö€Õ®Õ¥Õ¬ Õ¸Ö‚Õ²Õ²Õ¡Õ°Õ¡ÕµÕ¡Ö Õ¸Õ¬Õ¸Ö€Õ¸Ö‚Õ´ +pdfjs-scroll-vertical-button-label = ÕˆÖ‚Õ²Õ²Õ¡Õ°Õ¡ÕµÕ¡Ö Õ¸Õ¬Õ¸Ö€Õ¸Ö‚Õ´ +pdfjs-scroll-horizontal-button = + .title = Õ•Õ£Õ¿Õ¡Õ£Õ¸Ö€Õ®Õ¥Õ¬ Õ°Õ¸Ö€Õ«Õ¦Õ¸Õ¶Õ¡Õ¯Õ¡Õ¶ Õ¸Õ¬Õ¸Ö€Õ¸Ö‚Õ´ +pdfjs-scroll-horizontal-button-label = Õ€Õ¸Ö€Õ«Õ¦Õ¸Õ¶Õ¡Õ¯Õ¡Õ¶ Õ¸Õ¬Õ¸Ö€Õ¸Ö‚Õ´ +pdfjs-scroll-wrapped-button = + .title = Õ•Õ£Õ¿Õ¡Õ£Õ¸Ö€Õ®Õ¥Õ¬ ÖƒÕ¡Õ©Õ¡Õ©Õ¾Õ¡Õ® Õ¸Õ¬Õ¸Ö€Õ¸Ö‚Õ´ +pdfjs-scroll-wrapped-button-label = Õ“Õ¡Õ©Õ¡Õ©Õ¾Õ¡Õ® Õ¸Õ¬Õ¸Ö€Õ¸Ö‚Õ´ +pdfjs-spread-none-button = + .title = Õ„Õ« Õ´Õ«Õ¡ÖÕ¥Ö„ Õ§Õ»Õ« Õ¾Õ¥Ö€Õ¡Õ®Õ¡Õ®Õ¯Õ¥Ö€Õ«Õ¶ +pdfjs-spread-none-button-label = Õ‰Õ¯Õ¡ Õ¾Õ¥Ö€Õ¡Õ®Õ¡Õ®Õ¯Õ¥Ö€ +pdfjs-spread-odd-button = + .title = Õ„Õ«Õ¡ÖÕ¥Ö„ Õ§Õ»Õ« Õ¾Õ¥Ö€Õ¡Õ®Õ¡Õ®Õ¯Õ¥Ö€Õ«Õ¶ Õ½Õ¯Õ½Õ¥Õ¬Õ¸Õ¾Õ Õ¯Õ¥Õ¶Õ¿ Õ°Õ¡Õ´Õ¡Ö€Õ¡Õ¯Õ¡Õ¬Õ¾Õ¡Õ® Õ§Õ»Õ¥Ö€Õ¸Õ¾ +pdfjs-spread-odd-button-label = Ô¿Õ¥Õ¶Õ¿ Õ¾Õ¥Ö€Õ¡Õ®Õ¡Õ®Õ¯Õ¥Ö€ +pdfjs-spread-even-button = + .title = Õ„Õ«Õ¡ÖÕ¥Ö„ Õ§Õ»Õ« Õ¾Õ¥Ö€Õ¡Õ®Õ¡Õ®Õ¯Õ¥Ö€Õ«Õ¶ Õ½Õ¯Õ½Õ¥Õ¬Õ¸Õ¾Õ Õ¦Õ¸Ö‚ÕµÕ£ Õ°Õ¡Õ´Õ¡Ö€Õ¡Õ¯Õ¡Õ¬Õ¾Õ¡Õ® Õ§Õ»Õ¥Ö€Õ¸Õ¾ +pdfjs-spread-even-button-label = Ô¶Õ¸Ö‚ÕµÕ£ Õ¾Õ¥Ö€Õ¡Õ®Õ¡Õ®Õ¯Õ¥Ö€ + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Õ“Õ¡Õ½Õ¿Õ¡Õ©Õ²Õ©Õ« հատկությունները… +pdfjs-document-properties-button-label = Õ“Õ¡Õ½Õ¿Õ¡Õ©Õ²Õ©Õ« հատկությունները… +pdfjs-document-properties-file-name = Õ†Õ«Õ·Ö„Õ« Õ¡Õ¶Õ¸Ö‚Õ¶Õ¨. +pdfjs-document-properties-file-size = Õ†Õ«Õ·Ö„ Õ¹Õ¡ÖƒÕ¨. +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } Ô¿Ô² ({ $size_b } Õ¢Õ¡ÕµÕ©) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } Õ„Ô² ({ $size_b } Õ¢Õ¡ÕµÕ©) +pdfjs-document-properties-title = ÕŽÕ¥Ö€Õ¶Õ¡Õ£Õ«Ö€. +pdfjs-document-properties-author = Հեղինակ․ +pdfjs-document-properties-subject = ÕŽÕ¥Ö€Õ¶Õ¡Õ£Õ«Ö€. +pdfjs-document-properties-keywords = Õ€Õ«Õ´Õ¶Õ¡Õ¢Õ¡Õ¼. +pdfjs-document-properties-creation-date = ÕÕ¿Õ¥Õ²Õ®Õ¥Õ¬Õ¸Ö‚ Õ¡Õ´Õ½Õ¡Õ©Õ«Õ¾Õ¨. +pdfjs-document-properties-modification-date = Õ“Õ¸ÖƒÕ¸Õ­Õ¥Õ¬Õ¸Ö‚ Õ¡Õ´Õ½Õ¡Õ©Õ«Õ¾Õ¨. +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = ÕÕ¿Õ¥Õ²Õ®Õ¸Õ². +pdfjs-document-properties-producer = PDF-Õ« Õ°Õ¥Õ²Õ«Õ¶Õ¡Õ¯Õ¨. +pdfjs-document-properties-version = PDF-Õ« Õ¿Õ¡Ö€Õ¢Õ¥Ö€Õ¡Õ¯Õ¨. +pdfjs-document-properties-page-count = Ô·Õ»Õ¥Ö€Õ« Ö„Õ¡Õ¶Õ¡Õ¯Õ¨. +pdfjs-document-properties-page-size = Ô·Õ»Õ« Õ¹Õ¡ÖƒÕ¨. +pdfjs-document-properties-page-size-unit-inches = Õ¸Ö‚Õ´ +pdfjs-document-properties-page-size-unit-millimeters = Õ´Õ´ +pdfjs-document-properties-page-size-orientation-portrait = Õ¸Ö‚Õ²Õ²Õ¡Õ±Õ«Õ£ +pdfjs-document-properties-page-size-orientation-landscape = Õ°Õ¸Ö€Õ«Õ¦Õ¸Õ¶Õ¡Õ¯Õ¡Õ¶ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Õ†Õ¡Õ´Õ¡Õ¯ +pdfjs-document-properties-page-size-name-legal = Õ•Ö€Õ«Õ¶Õ¡Õ¯Õ¡Õ¶ + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Ô±Ö€Õ¡Õ£ Õ¾Õ¥Õ¢ դիտում․ +pdfjs-document-properties-linearized-yes = Ô±ÕµÕ¸ +pdfjs-document-properties-linearized-no = ÕˆÕ¹ +pdfjs-document-properties-close-button = Õ“Õ¡Õ¯Õ¥Õ¬ + +## Print + +pdfjs-print-progress-message = Õ†Õ¡Õ­Õ¡ÕºÕ¡Õ¿Ö€Õ¡Õ½Õ¿Õ¸Ö‚Õ´ Õ§ ÖƒÕ¡Õ½Õ¿Õ¡Õ©Õ¸Ö‚Õ²Õ©Õ¨ Õ¿ÕºÕ¥Õ¬Õ¸Ö‚Õ¶... +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Õ‰Õ¥Õ²Õ¡Ö€Õ¯Õ¥Õ¬ +pdfjs-printing-not-supported = Ô¶Õ£Õ¸Ö‚Õ·Õ¡ÖÕ¸Ö‚Õ´. ÕÕºÕ¥Õ¬Õ¨ Õ¡Õ´Õ¢Õ¸Õ²Õ»Õ¸Ö‚Õ©ÕµÕ¡Õ´Õ¢ Õ¹Õ« Õ¡Õ»Õ¡Õ¯ÖÕ¾Õ¸Ö‚Õ´ Õ¤Õ«Õ¿Õ¡Ö€Õ¯Õ«Õ¹Õ« Õ¯Õ¸Õ²Õ´Õ«ÖÖ‰ +pdfjs-printing-not-ready = Ô¶Õ£Õ¸Ö‚Õ·Õ¡ÖÕ¸Ö‚Õ´. PDF-Õ¨ Õ¡Õ´Õ¢Õ¸Õ²Õ»Õ¸Ö‚Õ©ÕµÕ¡Õ´Õ¢ Õ¹Õ« Õ¢Õ¥Õ¼Õ¶Õ¡Õ¾Õ¸Ö€Õ¾Õ¥Õ¬ Õ¿ÕºÕ¥Õ¬Õ¸Ö‚ Õ°Õ¡Õ´Õ¡Ö€: + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Ô²Õ¡ÖÕ¥Õ¬/Õ“Õ¡Õ¯Õ¥Õ¬ Ô¿Õ¸Õ²Õ¡ÕµÕ«Õ¶ Õ¾Õ¡Õ°Õ¡Õ¶Õ¡Õ¯Õ¨ +pdfjs-toggle-sidebar-button-label = Ô²Õ¡ÖÕ¥Õ¬/Õ“Õ¡Õ¯Õ¥Õ¬ Ô¿Õ¸Õ²Õ¡ÕµÕ«Õ¶ Õ¾Õ¡Õ°Õ¡Õ¶Õ¡Õ¯Õ¨ +pdfjs-document-outline-button = + .title = Õ‘Õ¸Ö‚ÖÕ¡Õ¤Ö€Õ¥Õ¬ ÖƒÕ¡Õ½Õ¿Õ¡Õ©Õ²Õ©Õ« Õ¸Ö‚Ö€Õ¾Õ¡Õ£Õ«Õ®Õ¨ (Õ¯Ö€Õ¯Õ¶Õ¡Õ¯Õ« Õ½Õ¥Õ²Õ´Õ¥Ö„Õ Õ´Õ«Õ¡Õ¾Õ¸Ö€Õ¶Õ¥Ö€Õ¨ Õ¨Õ¶Õ¤Õ¡Ö€Õ±Õ¡Õ¯Õ¥Õ¬Õ¸Ö‚/Õ¯Õ¸Õ®Õ¯Õ¥Õ¬Õ¸Ö‚ Õ°Õ¡Õ´Õ¡Ö€) +pdfjs-document-outline-button-label = Õ“Õ¡Õ½Õ¿Õ¡Õ©Õ²Õ©Õ« Õ¢Õ¸Õ¾Õ¡Õ¶Õ¤Õ¡Õ¯Õ¸Ö‚Õ©ÕµÕ¸Ö‚Õ¶Õ¨ +pdfjs-attachments-button = + .title = Õ‘Õ¸Ö‚ÖÕ¡Õ¤Ö€Õ¥Õ¬ Õ¯ÖÕ¸Ö€Õ¤Õ¶Õ¥Ö€Õ¨ +pdfjs-attachments-button-label = Ô¿ÖÕ¸Ö€Õ¤Õ¶Õ¥Ö€ +pdfjs-thumbs-button = + .title = Õ‘Õ¸Ö‚ÖÕ¡Õ¤Ö€Õ¥Õ¬ Õ„Õ¡Õ¶Ö€Õ¡ÕºÕ¡Õ¿Õ¯Õ¥Ö€Õ¨ +pdfjs-thumbs-button-label = Õ„Õ¡Õ¶Ö€Õ¡ÕºÕ¡Õ¿Õ¯Õ¥Ö€Õ¨ +pdfjs-findbar-button = + .title = Ô³Õ¿Õ¶Õ¥Õ¬ ÖƒÕ¡Õ½Õ¿Õ¡Õ©Õ²Õ©Õ¸Ö‚Õ´ +pdfjs-findbar-button-label = ÕˆÖ€Õ¸Õ¶Õ¸Ö‚Õ´ + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Ô·Õ»Õ¨ { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Ô·Õ»Õ« Õ´Õ¡Õ¶Ö€Õ¡ÕºÕ¡Õ¿Õ¯Õ¥Ö€Õ¨ { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = ÕˆÖ€Õ¸Õ¶Õ¸Ö‚Õ´ + .placeholder = Ô³Õ¿Õ¶Õ¥Õ¬ ÖƒÕ¡Õ½Õ¿Õ¡Õ©Õ²Õ©Õ¸Ö‚Õ´... +pdfjs-find-previous-button = + .title = Ô³Õ¿Õ¶Õ¥Õ¬ Õ¡Õ¶Ö€Õ¡Õ°Õ¡ÕµÕ¿Õ¸Ö‚Õ©ÕµÕ¡Õ¶ Õ¶Õ¡Õ­Õ¸Ö€Õ¤ Õ°Õ¡Õ¶Õ¤Õ«ÕºÕ¸Ö‚Õ´Õ¨ +pdfjs-find-previous-button-label = Õ†Õ¡Õ­Õ¸Ö€Õ¤Õ¨ +pdfjs-find-next-button = + .title = Ô³Õ¿Õ«Ö€ Õ¡Ö€Õ¿Õ¡Õ°Õ¡ÕµÕ¿Õ¸Ö‚Õ©ÕµÕ¡Õ¶ Õ°Õ¡Õ»Õ¸Ö€Õ¤ Õ°Õ¡Õ¶Õ¤Õ«ÕºÕ¸Ö‚Õ´Õ¨ +pdfjs-find-next-button-label = Õ€Õ¡Õ»Õ¸Ö€Õ¤Õ¨ +pdfjs-find-highlight-checkbox = Ô³Õ¸Ö‚Õ¶Õ¡Õ¶Õ·Õ¥Õ¬ Õ¢Õ¸Õ¬Õ¸Ö€Õ¨ +pdfjs-find-match-case-checkbox-label = Õ„Õ¥Õ®(ÖƒÕ¸Ö„Ö€)Õ¡Õ¿Õ¡Õ¼ Õ°Õ¡Õ·Õ¾Õ« Õ¡Õ¼Õ¶Õ¥Õ¬ +pdfjs-find-entire-word-checkbox-label = Ô±Õ´Õ¢Õ¸Õ²Õ» Õ¢Õ¡Õ¼Õ¥Ö€Õ¨ +pdfjs-find-reached-top = Õ€Õ¡Õ½Õ¥Õ¬ Õ¥Ö„ ÖƒÕ¡Õ½Õ¿Õ¡Õ©Õ²Õ©Õ« Õ¾Õ¥Ö€Ö‡Õ«Õ¶, Õ¯Õ·Õ¡Ö€Õ¸Ö‚Õ¶Õ¡Õ¯Õ¾Õ« Õ¶Õ¥Ö€Ö„Ö‡Õ«Ö +pdfjs-find-reached-bottom = Õ€Õ¡Õ½Õ¥Õ¬ Õ¥Ö„ ÖƒÕ¡Õ½Õ¿Õ¡Õ©Õ²Õ©Õ« Õ¾Õ¥Ö€Õ»Õ«Õ¶, Õ¯Õ·Õ¡Ö€Õ¸Ö‚Õ¶Õ¡Õ¯Õ¾Õ« Õ¾Õ¥Ö€Ö‡Õ«Ö +pdfjs-find-not-found = Ô±Ö€Õ¿Õ¡Õ°Õ¡ÕµÕ¿Õ¸Ö‚Õ©ÕµÕ¸Ö‚Õ¶Õ¨ Õ¹Õ£Õ¿Õ¶Õ¾Õ¥Ö + +## Predefined zoom values + +pdfjs-page-scale-width = Ô·Õ»Õ« Õ¬Õ¡ÕµÕ¶Ö„Õ¨ +pdfjs-page-scale-fit = ÕÕ£Õ¥Õ¬ Õ§Õ»Õ¨ +pdfjs-page-scale-auto = Ô»Õ¶Ö„Õ¶Õ¡Õ·Õ­Õ¡Õ¿ +pdfjs-page-scale-actual = Ô»Ö€Õ¡Õ¯Õ¡Õ¶ Õ¹Õ¡ÖƒÕ¨ +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = ÕÕ­Õ¡Õ¬Õ PDF Ö†Õ¡ÕµÕ¬Õ¨ Õ¢Õ¡ÖÕ¥Õ¬Õ«Õ½Ö‰ +pdfjs-invalid-file-error = ÕÕ­Õ¡Õ¬ Õ¯Õ¡Õ´ Õ¾Õ¶Õ¡Õ½Õ¾Õ¡Õ® PDF Ö†Õ¡ÕµÕ¬: +pdfjs-missing-file-error = PDF Ö†Õ¡ÕµÕ¬Õ¨ Õ¢Õ¡ÖÕ¡Õ¯Õ¡ÕµÕ¸Ö‚Õ´ Õ§: +pdfjs-unexpected-response-error = ÕÕºÕ¡Õ½Õ¡Ö€Õ¯Õ«Õ¹Õ« Õ¡Õ¶Õ½ÕºÕ¡Õ½Õ¥Õ¬Õ« ÕºÕ¡Õ¿Õ¡Õ½Õ­Õ¡Õ¶: +pdfjs-rendering-error = ÕÕ­Õ¡Õ¬Õ Õ§Õ»Õ¨ Õ½Õ¿Õ¥Õ²Õ®Õ¥Õ¬Õ«Õ½: + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Ô¾Õ¡Õ¶Õ¸Õ©Õ¸Ö‚Õ©ÕµÕ¸Ö‚Õ¶] + +## Password + +pdfjs-password-label = Õ„Õ¸Ö‚Õ¿Ö„Õ¡Õ£Ö€Õ¥Ö„ PDF-Õ« Õ£Õ¡Õ²Õ¿Õ¶Õ¡Õ¢Õ¡Õ¼Õ¨: +pdfjs-password-invalid = Ô³Õ¡Õ²Õ¿Õ¶Õ¡Õ¢Õ¡Õ¼Õ¨ Õ½Õ­Õ¡Õ¬ Õ§: Ô¿Ö€Õ¯Õ«Õ¶ ÖƒÕ¸Ö€Õ±Õ¥Ö„: +pdfjs-password-ok-button = Ô¼Õ¡Õ¾ +pdfjs-password-cancel-button = Õ‰Õ¥Õ²Õ¡Ö€Õ¯Õ¥Õ¬ +pdfjs-web-fonts-disabled = ÕŽÕ¥Õ¢-Õ¿Õ¡Õ¼Õ¡Õ¿Õ¥Õ½Õ¡Õ¯Õ¶Õ¥Ö€Õ¨ Õ¡Õ¶Õ»Õ¡Õ¿Õ¾Õ¡Õ® Õ¥Õ¶. Õ°Õ¶Õ¡Ö€Õ¡Õ¾Õ¸Ö€ Õ¹Õ§ Ö…Õ£Õ¿Õ¡Õ£Õ¸Ö€Õ®Õ¥Õ¬ Õ¶Õ¥Ö€Õ¯Õ¡Õ¼Õ¸Ö‚ÖÕ¾Õ¡Õ® PDF Õ¿Õ¡Õ¼Õ¡Õ¿Õ¥Õ½Õ¡Õ¯Õ¶Õ¥Ö€Õ¨: + +## Editing + + +## Remove button for the various kind of editor. + + +## + +pdfjs-free-text-default-content = ÕÕ¯Õ½Õ¥Õ¬ մուտքագրումը… + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + + +## Color picker + + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Õ‘Õ¸Ö‚ÖÕ¡Õ¤Ö€Õ¥Õ¬ Õ¢Õ¸Õ¬Õ¸Ö€Õ¨ +pdfjs-editor-highlight-show-all-button = + .title = Õ‘Õ¸Ö‚ÖÕ¡Õ¤Ö€Õ¥Õ¬ Õ¢Õ¸Õ¬Õ¸Ö€Õ¨ diff --git a/public/assets/pdfjs/locale/hye/viewer.ftl b/public/assets/pdfjs/locale/hye/viewer.ftl new file mode 100755 index 0000000..75cdc06 --- /dev/null +++ b/public/assets/pdfjs/locale/hye/viewer.ftl @@ -0,0 +1,268 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Õ†Õ¡Õ­Õ¸Ö€Õ¤ Õ§Õ» +pdfjs-previous-button-label = Õ†Õ¡Õ­Õ¸Ö€Õ¤Õ¨ +pdfjs-next-button = + .title = Õ…Õ¡Õ»Õ¸Ö€Õ¤ Õ§Õ» +pdfjs-next-button-label = Õ…Õ¡Õ»Õ¸Ö€Õ¤Õ¨ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Õ§Õ» +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = { $pagesCount }-Õ«Ö +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber }-Õ¨ { $pagesCount })-Õ«Ö +pdfjs-zoom-out-button = + .title = Õ“Õ¸Ö„Ö€Õ¡ÖÕ¶Õ¥Õ¬ +pdfjs-zoom-out-button-label = Õ“Õ¸Ö„Ö€Õ¡ÖÕ¶Õ¥Õ¬ +pdfjs-zoom-in-button = + .title = Ô½Õ¸Õ·Õ¸Ö€Õ¡ÖÕ¶Õ¥Õ¬ +pdfjs-zoom-in-button-label = Ô½Õ¸Õ·Õ¸Ö€Õ¡ÖÕ¶Õ¥Õ¬ +pdfjs-zoom-select = + .title = Ô½Õ¸Õ·Õ¸Ö€Õ¡ÖÕ¸Ö‚Õ´ +pdfjs-presentation-mode-button = + .title = Ô±Õ¶ÖÕ¶Õ¥Õ¬ Õ¶Õ¥Ö€Õ¯Õ¡ÕµÕ¡ÖÕ´Õ¡Õ¶ Õ¥Õ²Õ¡Õ¶Õ¡Õ¯Õ«Õ¶ +pdfjs-presentation-mode-button-label = Õ†Õ¥Ö€Õ¯Õ¡ÕµÕ¡ÖÕ´Õ¡Õ¶ Õ¥Õ²Õ¡Õ¶Õ¡Õ¯ +pdfjs-open-file-button = + .title = Ô²Õ¡ÖÕ¥Õ¬ Õ¶Õ«Õ·Ö„Õ¨ +pdfjs-open-file-button-label = Ô²Õ¡ÖÕ¥Õ¬ +pdfjs-print-button = + .title = ÕÕºÕ¥Õ¬ +pdfjs-print-button-label = ÕÕºÕ¥Õ¬ + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Ô³Õ¸Ö€Õ®Õ«Ö„Õ¶Õ¥Ö€ +pdfjs-tools-button-label = Ô³Õ¸Ö€Õ®Õ«Ö„Õ¶Õ¥Ö€ +pdfjs-first-page-button = + .title = Ô³Õ¶Õ¡Õ¬ Õ¤Õ§ÕºÕ« Õ¡Õ¼Õ¡Õ»Õ«Õ¶ Õ§Õ» +pdfjs-first-page-button-label = Ô³Õ¶Õ¡Õ¬ Õ¤Õ§ÕºÕ« Õ¡Õ¼Õ¡Õ»Õ«Õ¶ Õ§Õ» +pdfjs-last-page-button = + .title = Ô³Õ¶Õ¡Õ¬ Õ¤Õ§ÕºÕ« Õ¾Õ¥Ö€Õ»Õ«Õ¶ Õ§Õ» +pdfjs-last-page-button-label = Ô³Õ¶Õ¡Õ¬ Õ¤Õ§ÕºÕ« Õ¾Õ¥Ö€Õ»Õ«Õ¶ Õ§Õ» +pdfjs-page-rotate-cw-button = + .title = ÕŠÕ¿Õ¿Õ¥Õ¬ ÕªÕ¡Õ´Õ¡ÖÕ¸ÕµÖÕ« Õ½Õ¬Õ¡Ö„Õ« Õ¸Ö‚Õ²Õ²Õ¸Ö‚Õ©Õ¥Õ¡Õ´Õ¢ +pdfjs-page-rotate-cw-button-label = ÕŠÕ¿Õ¿Õ¥Õ¬ ÕªÕ¡Õ´Õ¡ÖÕ¸ÕµÖÕ« Õ½Õ¬Õ¡Ö„Õ« Õ¸Ö‚Õ²Õ²Õ¸Ö‚Õ©Õ¥Õ¡Õ´Õ¢ +pdfjs-page-rotate-ccw-button = + .title = ÕŠÕ¿Õ¿Õ¥Õ¬ ÕªÕ¡Õ´Õ¡ÖÕ¸ÕµÖÕ« Õ½Õ¬Õ¡Ö„Õ« Õ°Õ¡Õ¯Õ¡Õ¼Õ¡Õ¯ Õ¸Ö‚Õ²Õ²Õ¸Ö‚Õ©Õ¥Õ¡Õ´Õ¢ +pdfjs-page-rotate-ccw-button-label = ÕŠÕ¿Õ¿Õ¥Õ¬ ÕªÕ¡Õ´Õ¡ÖÕ¸ÕµÖÕ« Õ½Õ¬Õ¡Ö„Õ« Õ°Õ¡Õ¯Õ¡Õ¼Õ¡Õ¯ Õ¸Ö‚Õ²Õ²Õ¸Ö‚Õ©Õ¥Õ¡Õ´Õ¢ +pdfjs-cursor-text-select-tool-button = + .title = Õ„Õ«Õ¡ÖÕ¶Õ¥Õ¬ Õ£Ö€Õ¸ÕµÕ© Õ¨Õ¶Õ¿Ö€Õ¥Õ¬Õ¸Ö‚ Õ£Õ¸Ö€Õ®Õ«Ö„Õ¨ +pdfjs-cursor-text-select-tool-button-label = Ô³Ö€Õ¸Ö‚Õ¡Õ®Ö„ Õ¨Õ¶Õ¿Ö€Õ¥Õ¬Õ¸Ö‚ Õ£Õ¸Ö€Õ®Õ«Ö„ +pdfjs-cursor-hand-tool-button = + .title = Õ„Õ«Õ¡ÖÕ¶Õ¥Õ¬ Õ±Õ¥Õ¼Ö„Õ« Õ£Õ¸Ö€Õ®Õ«Ö„Õ¨ +pdfjs-cursor-hand-tool-button-label = ÕÕ¥Õ¼Ö„Õ« Õ£Õ¸Ö€Õ®Õ«Ö„ +pdfjs-scroll-page-button = + .title = Ô±Ö‚Õ£Õ¿Õ¡Õ£Õ¸Ö€Õ®Õ¥Õ¬ Õ§Õ»Õ« Õ¸Õ¬Õ¸Ö€Õ¸Ö‚Õ´ +pdfjs-scroll-page-button-label = Ô·Õ»Õ« Õ¸Õ¬Õ¸Ö€Õ¸Ö‚Õ´ +pdfjs-scroll-vertical-button = + .title = Ô±Ö‚Õ£Õ¿Õ¡Õ£Õ¸Ö€Õ®Õ¥Õ¬ Õ¸Ö‚Õ²Õ²Õ¡Õ°Õ¡ÕµÕ¥Õ¡Ö Õ¸Õ¬Õ¸Ö€Õ¸Ö‚Õ´ +pdfjs-scroll-vertical-button-label = ÕˆÖ‚Õ²Õ²Õ¡Õ°Õ¡ÕµÕ¥Õ¡Ö Õ¸Õ¬Õ¸Ö€Õ¸Ö‚Õ´ +pdfjs-scroll-horizontal-button = + .title = Ô±Ö‚Õ£Õ¿Õ¡Õ£Õ¸Ö€Õ®Õ¥Õ¬ Õ°Õ¸Ö€Õ«Õ¦Õ¸Õ¶Õ¡Õ¯Õ¡Õ¶ Õ¸Õ¬Õ¸Ö€Õ¸Ö‚Õ´ +pdfjs-scroll-horizontal-button-label = Õ€Õ¸Ö€Õ«Õ¦Õ¸Õ¶Õ¡Õ¯Õ¡Õ¶ Õ¸Õ¬Õ¸Ö€Õ¸Ö‚Õ´ +pdfjs-scroll-wrapped-button = + .title = Ô±Ö‚Õ£Õ¿Õ¡Õ£Õ¸Ö€Õ®Õ¥Õ¬ ÖƒÕ¡Õ©Õ¡Õ©Õ¸Ö‚Õ¡Õ® Õ¸Õ¬Õ¸Ö€Õ¸Ö‚Õ´ +pdfjs-scroll-wrapped-button-label = Õ“Õ¡Õ©Õ¡Õ©Õ¸Ö‚Õ¡Õ® Õ¸Õ¬Õ¸Ö€Õ¸Ö‚Õ´ +pdfjs-spread-none-button = + .title = Õ„Õ« Õ´Õ«Õ¡ÖÕ§Ö„ Õ§Õ»Õ« Õ¯Õ¸Õ¶Õ¿Õ¥Ö„Õ½Õ¿Õ¸Ö‚Õ´ +pdfjs-spread-none-button-label = Õ‰Õ¯Õ¡Õµ Õ¯Õ¸Õ¶Õ¿Õ¥Ö„Õ½Õ¿ +pdfjs-spread-odd-button = + .title = Õ„Õ«Õ¡ÖÕ§Ö„ Õ§Õ»Õ« Õ¯Õ¸Õ¶Õ¿Õ¥Ö„Õ½Õ¿Õ«Õ¶ Õ½Õ¯Õ½Õ¥Õ¬Õ¸Õ¾Õ Õ¯Õ¥Õ¶Õ¿ Õ°Õ¡Õ´Õ¡Ö€Õ¡Õ¯Õ¡Õ¬Õ¸Ö‚Õ¡Õ® Õ§Õ»Õ¥Ö€Õ¸Õ¾ +pdfjs-spread-odd-button-label = ÕÕ¡Ö€Õ¡Ö‚Ö€Õ«Õ¶Õ¡Õ¯ Õ¯Õ¸Õ¶Õ¿Õ¥Ö„Õ½Õ¿ +pdfjs-spread-even-button = + .title = Õ„Õ«Õ¡ÖÕ§Ö„ Õ§Õ»Õ« Õ¯Õ¸Õ¶Õ¿Õ¥Ö„Õ½Õ¿Õ«Õ¶ Õ½Õ¯Õ½Õ¥Õ¬Õ¸Õ¾Õ Õ¦Õ¸ÕµÕ£ Õ°Õ¡Õ´Õ¡Ö€Õ¡Õ¯Õ¡Õ¬Õ¸Ö‚Õ¡Õ® Õ§Õ»Õ¥Ö€Õ¸Õ¾ +pdfjs-spread-even-button-label = Õ€Õ¡Ö‚Õ¡Õ½Õ¡Ö€ Õ¾Õ¥Ö€Õ¡Õ®Õ¡Õ®Õ¯Õ¥Ö€ + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Õ“Õ¡Õ½Õ¿Õ¡Õ©Õ²Õ©Õ« հատկութիւնները… +pdfjs-document-properties-button-label = Õ“Õ¡Õ½Õ¿Õ¡Õ©Õ²Õ©Õ« յատկութիւնները… +pdfjs-document-properties-file-name = Õ†Õ«Õ·Ö„Õ« անունը․ +pdfjs-document-properties-file-size = Õ†Õ«Õ·Ö„ Õ¹Õ¡ÖƒÕ¨. +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } Ô¿Ô² ({ $size_b } Õ¢Õ¡ÕµÕ©) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } Õ„Ô² ({ $size_b } Õ¢Õ¡ÕµÕ©) +pdfjs-document-properties-title = ÕŽÕ¥Ö€Õ¶Õ¡Õ£Õ«Ö€ +pdfjs-document-properties-author = Հեղինակ․ +pdfjs-document-properties-subject = Õ¡Õ¼Õ¡Ö€Õ¯Õ¡Õµ +pdfjs-document-properties-keywords = Õ€Õ«Õ´Õ¶Õ¡Õ¢Õ¡Õ¼Õ¥Ö€ +pdfjs-document-properties-creation-date = ÕÕ¿Õ¥Õ²Õ®Õ´Õ¡Õ¶ Õ¡Õ´Õ½Õ¡Õ©Õ«Ö‚ +pdfjs-document-properties-modification-date = Õ“Õ¸ÖƒÕ¸Õ­Õ¸Ö‚Õ©Õ¥Õ¡Õ¶ Õ¡Õ´Õ½Õ¡Õ©Õ«Ö‚. +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = ÕÕ¿Õ¥Õ²Õ®Õ¸Õ² +pdfjs-document-properties-producer = PDF-Õ« Ô±Ö€Õ¿Õ¡Õ¤Ö€Õ¸Õ²Õ¨. +pdfjs-document-properties-version = PDF-Õ« Õ¿Õ¡Ö€Õ¢Õ¥Ö€Õ¡Õ¯Õ¨. +pdfjs-document-properties-page-count = Ô·Õ»Õ¥Ö€Õ« Ö„Õ¡Õ¶Õ¡Õ¯Õ¨. +pdfjs-document-properties-page-size = Ô·Õ»Õ« Õ¹Õ¡ÖƒÕ¨. +pdfjs-document-properties-page-size-unit-inches = Õ¸Ö‚Õ´ +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = Õ¸Ö‚Õ²Õ²Õ¡Õ±Õ«Õ£ +pdfjs-document-properties-page-size-orientation-landscape = Õ°Õ¸Ö€Õ«Õ¦Õ¸Õ¶Õ¡Õ¯Õ¡Õ¶ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Õ†Õ¡Õ´Õ¡Õ¯ +pdfjs-document-properties-page-size-name-legal = Ô±Ö‚Ö€Õ«Õ¶Õ¡Õ¯Õ¡Õ¶ + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Ô±Ö€Õ¡Õ£ Õ¾Õ¥Õ¢ դիտում․ +pdfjs-document-properties-linearized-yes = Ô±ÕµÕ¸ +pdfjs-document-properties-linearized-no = ÕˆÕ¹ +pdfjs-document-properties-close-button = Õ“Õ¡Õ¯Õ¥Õ¬ + +## Print + +pdfjs-print-progress-message = Õ†Õ¡Õ­Õ¡ÕºÕ¡Õ¿Ö€Õ¡Õ½Õ¿Õ¸Ö‚Õ´ Õ§ ÖƒÕ¡Õ½Õ¿Õ¡Õ©Õ¸Ö‚Õ²Õ©Õ¨ տպելուն… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Õ‰Õ¥Õ²Õ¡Ö€Õ¯Õ¥Õ¬ +pdfjs-printing-not-supported = Ô¶Õ£Õ¸Ö‚Õ·Õ¡ÖÕ¸Ö‚Õ´. ÕÕºÕ¥Õ¬Õ¨ Õ¡Õ´Õ¢Õ¸Õ²Õ»Õ¸Ö‚Õ©Õ¥Õ¡Õ´Õ¢ Õ¹Õ« Õ¡Õ»Õ¡Õ¯ÖÕ¸Ö‚Õ¸Ö‚Õ´ Õ¦Õ¶Õ¶Õ¡Ö€Õ¯Õ«Õ¹Õ« Õ¯Õ¸Õ²Õ´Õ«ÖÖ‰ +pdfjs-printing-not-ready = Ô¶Õ£Õ¸Ö‚Õ·Õ¡ÖÕ¸Ö‚Õ´. PDFÖŠÕ¨ Õ¡Õ´Õ¢Õ¸Õ²Õ»Õ¸Ö‚Õ©Õ¥Õ¡Õ´Õ¢ Õ¹Õ« Õ¢Õ¥Õ¼Õ¶Õ¡Ö‚Õ¸Ö€Õ¸Ö‚Õ¥Õ¬ Õ¿ÕºÕ¥Õ¬Õ¸Ö‚ Õ°Õ¡Õ´Õ¡Ö€Ö‰ + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Õ“Õ¸Õ­Õ¡Ö€Õ¯Õ¥Õ¬ Õ¯Õ¸Õ²Õ¡ÕµÕ«Õ¶ Õ¾Õ¡Õ°Õ¡Õ¶Õ¡Õ¯Õ¨ +pdfjs-toggle-sidebar-notification-button = + .title = Õ“Õ¸Õ­Õ¡Õ¶Õ»Õ¡Õ¿Õ¥Õ¬ Õ¯Õ¸Õ²Õ´Õ¶Õ¡Õ½Õ«Ö‚Õ¶Õ¨ (ÖƒÕ¡Õ½Õ¿Õ¡Õ©Õ¸Ö‚Õ²Õ©Õ¨ ÕºÕ¡Ö€Õ¸Ö‚Õ¶Õ¡Õ¯Õ¸Ö‚Õ´ Õ§ Õ¸Ö‚Ö€Õ¸Ö‚Õ¡Õ£Õ«Õ®/Õ¯ÖÕ¸Ö€Õ¤Õ¶Õ¥Ö€/Õ·Õ¥Ö€Õ¿Õ¥Ö€) +pdfjs-toggle-sidebar-button-label = Õ“Õ¸Õ­Õ¡Ö€Õ¯Õ¥Õ¬ Õ¯Õ¸Õ²Õ¡ÕµÕ«Õ¶ Õ¾Õ¡Õ°Õ¡Õ¶Õ¡Õ¯Õ¨ +pdfjs-document-outline-button = + .title = Õ‘Õ¸Ö‚ÖÕ¡Õ¤Ö€Õ¥Õ¬ ÖƒÕ¡Õ½Õ¿Õ¡Õ©Õ²Õ©Õ« Õ¸Ö‚Ö€Õ¸Ö‚Õ¡Õ£Õ«Õ®Õ¨ (Õ¯Ö€Õ¯Õ¶Õ¡Õ¯Õ« Õ½Õ¥Õ²Õ´Õ§Ö„Õ Õ´Õ«Õ¡Ö‚Õ¸Ö€Õ¶Õ¥Ö€Õ¨ Õ¨Õ¶Õ¤Õ¡Ö€Õ±Õ¡Õ¯Õ¥Õ¬Õ¸Ö‚/Õ¯Õ¸Õ®Õ¯Õ¥Õ¬Õ¸Ö‚ Õ°Õ¡Õ´Õ¡Ö€) +pdfjs-document-outline-button-label = Õ“Õ¡Õ½Õ¿Õ¡Õ©Õ²Õ©Õ« Õ¸Ö‚Ö€Õ¸Ö‚Õ¡Õ£Õ«Õ® +pdfjs-attachments-button = + .title = Õ‘Õ¸Ö‚ÖÕ¡Õ¤Ö€Õ¥Õ¬ Õ¯ÖÕ¸Ö€Õ¤Õ¶Õ¥Ö€Õ¨ +pdfjs-attachments-button-label = Ô¿ÖÕ¸Ö€Õ¤Õ¶Õ¥Ö€ +pdfjs-layers-button = + .title = Õ‘Õ¸Ö‚ÖÕ¡Õ¤Ö€Õ¥Õ¬ Õ·Õ¥Ö€Õ¿Õ¥Ö€Õ¨ (Õ¯Ö€Õ¯Õ¶Õ¡Õ°ÕºÕ¥Õ¬ Õ¾Õ¥Ö€Õ¡Õ¯Õ¡ÕµÕ¥Õ¬Õ¸Ö‚ Õ¢Õ¸Õ¬Õ¸Ö€ Õ·Õ¥Ö€Õ¿Õ¥Ö€Õ¨ Õ½Õ¯Õ¦Õ¢Õ¶Õ¡Õ¤Õ«Ö€ Õ¾Õ«Õ³Õ¡Õ¯Õ«) +pdfjs-layers-button-label = Õ‡Õ¥Ö€Õ¿Õ¥Ö€ +pdfjs-thumbs-button = + .title = Õ‘Õ¸Ö‚ÖÕ¡Õ¤Ö€Õ¥Õ¬ Õ´Õ¡Õ¶Ö€Õ¡ÕºÕ¡Õ¿Õ¯Õ¥Ö€Õ¨ +pdfjs-thumbs-button-label = Õ„Õ¡Õ¶Ö€Õ¡ÕºÕ¡Õ¿Õ¯Õ¥Ö€ +pdfjs-current-outline-item-button = + .title = Ô³Õ¿Õ§Ö„ Õ¨Õ¶Õ©Õ¡ÖÕ«Õ¯ Õ£Õ®Õ¡Õ£Ö€Õ´Õ¡Õ¶ Õ¿Õ¡Ö€Ö€Õ¨ +pdfjs-current-outline-item-button-label = Ô¸Õ¶Õ©Õ¡ÖÕ«Õ¯ Õ£Õ®Õ¡Õ£Ö€Õ´Õ¡Õ¶ Õ¿Õ¡Ö€Ö€ +pdfjs-findbar-button = + .title = Ô³Õ¿Õ¶Õ¥Õ¬ ÖƒÕ¡Õ½Õ¿Õ¡Õ©Õ²Õ©Õ¸Ö‚Õ´ +pdfjs-findbar-button-label = ÕˆÖ€Õ¸Õ¶Õ¸Ö‚Õ´ +pdfjs-additional-layers = Ô¼Ö€Õ¡ÖÕ¸Ö‚ÖÕ«Õ¹ Õ·Õ¥Ö€Õ¿Õ¥Ö€ + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Ô·Õ»Õ¨ { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Ô·Õ»Õ« Õ´Õ¡Õ¶Ö€Õ¡ÕºÕ¡Õ¿Õ¯Õ¥Ö€Õ¨ { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = ÕˆÖ€Õ¸Õ¶Õ¸Ö‚Õ´ + .placeholder = Ô³Õ¿Õ¶Õ¥Õ¬ փաստաթղթում… +pdfjs-find-previous-button = + .title = Ô³Õ¿Õ¶Õ¥Õ¬ Õ¡Ö€Õ¿Õ¡ÕµÕ¡ÕµÕ¿Õ¸Ö‚Õ©Õ¥Õ¡Õ¶ Õ¶Õ¡Õ­Õ¸Ö€Õ¤ Õ¡Ö€Õ¿Õ¡ÕµÕ¡ÕµÕ¿Õ¸Ö‚Õ©Õ«Ö‚Õ¶Õ¨ +pdfjs-find-previous-button-label = Õ†Õ¡Õ­Õ¸Ö€Õ¤Õ¨ +pdfjs-find-next-button = + .title = Ô³Õ¿Õ«Ö€ Õ¡Ö€Õ¿Õ¡ÕµÕ¡ÕµÕ¿Õ¸Ö‚Õ©Õ¥Õ¡Õ¶ ÕµÕ¡Õ»Õ¸Ö€Õ¤ Õ¡Ö€Õ¿Õ¡ÕµÕ¡ÕµÕ¿Õ¸Ö‚Õ©Õ«Ö‚Õ¶Õ¨ +pdfjs-find-next-button-label = Õ€Õ¡Õ»Õ¸Ö€Õ¤Õ¨ +pdfjs-find-highlight-checkbox = Ô³Õ¸Ö‚Õ¶Õ¡Õ¶Õ·Õ¥Õ¬ Õ¢Õ¸Õ¬Õ¸Ö€Õ¨ +pdfjs-find-match-case-checkbox-label = Õ€Õ¡Õ·Õ¸Ö‚Õ« Õ¡Õ¼Õ¶Õ¥Õ¬ Õ°Õ¡Õ¶Õ£Õ¡Õ´Õ¡Õ¶Ö„Õ¨ +pdfjs-find-match-diacritics-checkbox-label = Õ€Õ¶Õ¹Õ«Ö‚Õ¶Õ¡Õ¿Õ¡Ö€Õ¢Õ¥Ö€Õ«Õ¹ Õ¶Õ·Õ¡Õ¶Õ¶Õ¥Ö€Õ« Õ°Õ¡Õ´Õ¡ÕºÕ¡Õ¿Õ¡Õ½Õ­Õ¡Õ¶Õ¥ÖÕ¸Ö‚Õ´ +pdfjs-find-entire-word-checkbox-label = Ô±Õ´Õ¢Õ¸Õ²Õ» Õ¢Õ¡Õ¼Õ¥Ö€Õ¨ +pdfjs-find-reached-top = Õ€Õ¡Õ½Õ¥Õ¬ Õ¥Ö„ ÖƒÕ¡Õ½Õ¿Õ¡Õ©Õ²Õ©Õ« Õ¾Õ¥Ö€Õ¥Ö‚Õ«Õ¶,Õ·Õ¡Ö€Õ¸Ö‚Õ¶Õ¡Õ¯Õ¥Õ¬ Õ¶Õ¥Ö€Ö„Õ¥Ö‚Õ«Ö +pdfjs-find-reached-bottom = Õ€Õ¡Õ½Õ¥Õ¬ Õ§Ö„ ÖƒÕ¡Õ½Õ¿Õ¡Õ©Õ²Õ©Õ« Õ¾Õ¥Ö€Õ»Õ«Õ¶, Õ·Õ¡Ö€Õ¸Ö‚Õ¶Õ¡Õ¯Õ¥Õ¬ Õ¾Õ¥Ö€Õ¥Ö‚Õ«Ö +pdfjs-find-not-found = Ô±Ö€Õ¿Õ¡ÕµÕ¡ÕµÕ¿Õ¸Ö‚Õ©Õ«Ö‚Õ¶Õ¨ Õ¹Õ£Õ¿Õ¶Õ¸Ö‚Õ¥Ö + +## Predefined zoom values + +pdfjs-page-scale-width = Ô·Õ»Õ« Õ¬Õ¡ÕµÕ¶Õ¸Ö‚Õ©Õ«Ö‚Õ¶ +pdfjs-page-scale-fit = Õ€Õ¡Ö€Õ´Õ¡Ö€Õ¥ÖÕ¶Õ¥Õ¬ Õ§Õ»Õ¨ +pdfjs-page-scale-auto = Ô»Õ¶Ö„Õ¶Õ¡Õ·Õ­Õ¡Õ¿ Õ­Õ¸Õ·Õ¸Ö€Õ¡ÖÕ¸Ö‚Õ´ +pdfjs-page-scale-actual = Ô»Ö€Õ¡Õ¯Õ¡Õ¶ Õ¹Õ¡ÖƒÕ¨ +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Ô·Õ» { $page } + +## Loading indicator messages + +pdfjs-loading-error = PDF Õ¶Õ«Õ·Ö„Õ¨ Õ¢Õ¡ÖÕ¥Õ¬Õ«Õ½ Õ½Õ­Õ¡Õ¬ Õ§ Õ¿Õ¥Õ²Õ« Õ¸Ö‚Õ¶Õ¥ÖÕ¥Õ¬Ö‰ +pdfjs-invalid-file-error = ÕÕ­Õ¡Õ¬ Õ¯Õ¡Õ´ Õ¾Õ¶Õ¡Õ½Õ¸Ö‚Õ¡Õ® PDF Õ¶Õ«Õ·Ö„Ö‰ +pdfjs-missing-file-error = PDF Õ¶Õ«Õ·Ö„Õ¨ Õ¢Õ¡ÖÕ¡Õ¯Õ¡Õ«Ö‚Õ´ Õ§Ö‰ +pdfjs-unexpected-response-error = ÕÕºÕ¡Õ½Õ¡Ö€Õ¯Õ«Õ¹Õ« Õ¡Õ¶Õ½ÕºÕ¡Õ½Õ¥Õ¬Õ« ÕºÕ¡Õ¿Õ¡Õ½Õ­Õ¡Õ¶Ö‰ +pdfjs-rendering-error = ÕÕ­Õ¡Õ¬ Õ§ Õ¿Õ¥Õ²Õ« Õ¸Ö‚Õ¶Õ¥ÖÕ¥Õ¬ Õ§Õ»Õ« Õ´Õ¥Õ¯Õ¶Õ¡Õ¢Õ¡Õ¶Õ´Õ¡Õ¶ ÕªÕ¡Õ´Õ¡Õ¶Õ¡Õ¯ + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Ô¾Õ¡Õ¶Õ¸Õ©Õ¸Ö‚Õ©Õ«Ö‚Õ¶] + +## Password + +pdfjs-password-label = Õ„Õ¸Ö‚Õ¿Ö„Õ¡Õ£Ö€Õ§Ö„ Õ£Õ¡Õ²Õ¿Õ¶Õ¡Õ¢Õ¡Õ¼Õ¨ Õ¡ÕµÕ½ PDF Õ¶Õ«Õ·Ö„Õ¨ Õ¢Õ¡ÖÕ¥Õ¬Õ¸Ö‚ Õ°Õ¡Õ´Õ¡Ö€ +pdfjs-password-invalid = Ô³Õ¡Õ²Õ¿Õ¶Õ¡Õ¢Õ¡Õ¼Õ¨ Õ½Õ­Õ¡Õ¬ Õ§: Ô¿Ö€Õ¯Õ«Õ¶ ÖƒÕ¸Ö€Õ±Õ§Ö„: +pdfjs-password-ok-button = Ô¼Õ¡Ö‚ +pdfjs-password-cancel-button = Õ‰Õ¥Õ²Õ¡Ö€Õ¯Õ¥Õ¬ +pdfjs-web-fonts-disabled = ÕŽÕ¥Õ¢-Õ¿Õ¡Õ¼Õ¡Õ¿Õ¥Õ½Õ¡Õ¯Õ¶Õ¥Ö€Õ¨ Õ¡Õ¶Õ»Õ¡Õ¿Õ¸Ö‚Õ¡Õ® Õ¥Õ¶. Õ°Õ¶Õ¡Ö€Õ¡Ö‚Õ¸Ö€ Õ¹Õ§ Õ¡Ö‚Õ£Õ¿Õ¡Õ£Õ¸Ö€Õ®Õ¥Õ¬ Õ¶Õ¥Ö€Õ¯Õ¡Õ¼Õ¸Ö‚ÖÕ¸Ö‚Õ¡Õ® PDF Õ¿Õ¡Õ¼Õ¡Õ¿Õ¥Õ½Õ¡Õ¯Õ¶Õ¥Ö€Õ¨Ö‰ + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/ia/viewer.ftl b/public/assets/pdfjs/locale/ia/viewer.ftl new file mode 100755 index 0000000..91fbaf9 --- /dev/null +++ b/public/assets/pdfjs/locale/ia/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Pagina previe +pdfjs-previous-button-label = Previe +pdfjs-next-button = + .title = Pagina sequente +pdfjs-next-button-label = Sequente +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Pagina +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = de { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount }) +pdfjs-zoom-out-button = + .title = Distantiar +pdfjs-zoom-out-button-label = Distantiar +pdfjs-zoom-in-button = + .title = Approximar +pdfjs-zoom-in-button-label = Approximar +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Excambiar a modo presentation +pdfjs-presentation-mode-button-label = Modo presentation +pdfjs-open-file-button = + .title = Aperir le file +pdfjs-open-file-button-label = Aperir +pdfjs-print-button = + .title = Imprimer +pdfjs-print-button-label = Imprimer +pdfjs-save-button = + .title = Salvar +pdfjs-save-button-label = Salvar +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Discargar +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Discargar +pdfjs-bookmark-button = + .title = Pagina actual (vide le URL del pagina actual) +pdfjs-bookmark-button-label = Pagina actual + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Instrumentos +pdfjs-tools-button-label = Instrumentos +pdfjs-first-page-button = + .title = Ir al prime pagina +pdfjs-first-page-button-label = Ir al prime pagina +pdfjs-last-page-button = + .title = Ir al ultime pagina +pdfjs-last-page-button-label = Ir al ultime pagina +pdfjs-page-rotate-cw-button = + .title = Rotar in senso horari +pdfjs-page-rotate-cw-button-label = Rotar in senso horari +pdfjs-page-rotate-ccw-button = + .title = Rotar in senso antihorari +pdfjs-page-rotate-ccw-button-label = Rotar in senso antihorari +pdfjs-cursor-text-select-tool-button = + .title = Activar le instrumento de selection de texto +pdfjs-cursor-text-select-tool-button-label = Instrumento de selection de texto +pdfjs-cursor-hand-tool-button = + .title = Activar le instrumento mano +pdfjs-cursor-hand-tool-button-label = Instrumento mano +pdfjs-scroll-page-button = + .title = Usar rolamento de pagina +pdfjs-scroll-page-button-label = Rolamento de pagina +pdfjs-scroll-vertical-button = + .title = Usar rolamento vertical +pdfjs-scroll-vertical-button-label = Rolamento vertical +pdfjs-scroll-horizontal-button = + .title = Usar rolamento horizontal +pdfjs-scroll-horizontal-button-label = Rolamento horizontal +pdfjs-scroll-wrapped-button = + .title = Usar rolamento incapsulate +pdfjs-scroll-wrapped-button-label = Rolamento incapsulate +pdfjs-spread-none-button = + .title = Non junger paginas dual +pdfjs-spread-none-button-label = Sin paginas dual +pdfjs-spread-odd-button = + .title = Junger paginas dual a partir de paginas con numeros impar +pdfjs-spread-odd-button-label = Paginas dual impar +pdfjs-spread-even-button = + .title = Junger paginas dual a partir de paginas con numeros par +pdfjs-spread-even-button-label = Paginas dual par + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Proprietates del documento… +pdfjs-document-properties-button-label = Proprietates del documento… +pdfjs-document-properties-file-name = Nomine del file: +pdfjs-document-properties-file-size = Dimension de file: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Titulo: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Subjecto: +pdfjs-document-properties-keywords = Parolas clave: +pdfjs-document-properties-creation-date = Data de creation: +pdfjs-document-properties-modification-date = Data de modification: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Creator: +pdfjs-document-properties-producer = Productor PDF: +pdfjs-document-properties-version = Version PDF: +pdfjs-document-properties-page-count = Numero de paginas: +pdfjs-document-properties-page-size = Dimension del pagina: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = vertical +pdfjs-document-properties-page-size-orientation-landscape = horizontal +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Littera +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Vista web rapide: +pdfjs-document-properties-linearized-yes = Si +pdfjs-document-properties-linearized-no = No +pdfjs-document-properties-close-button = Clauder + +## Print + +pdfjs-print-progress-message = Preparation del documento pro le impression… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Cancellar +pdfjs-printing-not-supported = Attention : le impression non es totalmente supportate per ce navigator. +pdfjs-printing-not-ready = Attention: le file PDF non es integremente cargate pro lo poter imprimer. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Monstrar/celar le barra lateral +pdfjs-toggle-sidebar-notification-button = + .title = Monstrar/celar le barra lateral (le documento contine structura/attachamentos/stratos) +pdfjs-toggle-sidebar-button-label = Monstrar/celar le barra lateral +pdfjs-document-outline-button = + .title = Monstrar le schema del documento (clic duple pro expander/contraher tote le elementos) +pdfjs-document-outline-button-label = Schema del documento +pdfjs-attachments-button = + .title = Monstrar le annexos +pdfjs-attachments-button-label = Annexos +pdfjs-layers-button = + .title = Monstrar stratos (clicca duple pro remontar tote le stratos al stato predefinite) +pdfjs-layers-button-label = Stratos +pdfjs-thumbs-button = + .title = Monstrar le vignettes +pdfjs-thumbs-button-label = Vignettes +pdfjs-current-outline-item-button = + .title = Trovar le elemento de structura actual +pdfjs-current-outline-item-button-label = Elemento de structura actual +pdfjs-findbar-button = + .title = Cercar in le documento +pdfjs-findbar-button-label = Cercar +pdfjs-additional-layers = Altere stratos + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Pagina { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Vignette del pagina { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Cercar + .placeholder = Cercar in le documento… +pdfjs-find-previous-button = + .title = Trovar le previe occurrentia del phrase +pdfjs-find-previous-button-label = Previe +pdfjs-find-next-button = + .title = Trovar le successive occurrentia del phrase +pdfjs-find-next-button-label = Sequente +pdfjs-find-highlight-checkbox = Evidentiar toto +pdfjs-find-match-case-checkbox-label = Distinguer majusculas/minusculas +pdfjs-find-match-diacritics-checkbox-label = Differentiar diacriticos +pdfjs-find-entire-word-checkbox-label = Parolas integre +pdfjs-find-reached-top = Initio del documento attingite, continuation ab fin +pdfjs-find-reached-bottom = Fin del documento attingite, continuation ab initio +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } de { $total } correspondentia + *[other] { $current } de { $total } correspondentias + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Plus de { $limit } correspondentia + *[other] Plus de { $limit } correspondentias + } +pdfjs-find-not-found = Phrase non trovate + +## Predefined zoom values + +pdfjs-page-scale-width = Plen largor del pagina +pdfjs-page-scale-fit = Pagina integre +pdfjs-page-scale-auto = Zoom automatic +pdfjs-page-scale-actual = Dimension real +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Pagina { $page } + +## Loading indicator messages + +pdfjs-loading-error = Un error occurreva durante que on cargava le file PDF. +pdfjs-invalid-file-error = File PDF corrumpite o non valide. +pdfjs-missing-file-error = File PDF mancante. +pdfjs-unexpected-response-error = Responsa del servitor inexpectate. +pdfjs-rendering-error = Un error occurreva durante que on processava le pagina. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Annotation] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Insere le contrasigno pro aperir iste file PDF. +pdfjs-password-invalid = Contrasigno invalide. Per favor retenta. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Cancellar +pdfjs-web-fonts-disabled = Le typos de litteras web es disactivate: impossibile usar le typos de litteras PDF incorporate. + +## Editing + +pdfjs-editor-free-text-button = + .title = Texto +pdfjs-editor-free-text-button-label = Texto +pdfjs-editor-ink-button = + .title = Designar +pdfjs-editor-ink-button-label = Designar +pdfjs-editor-stamp-button = + .title = Adder o rediger imagines +pdfjs-editor-stamp-button-label = Adder o rediger imagines +pdfjs-editor-highlight-button = + .title = Evidentia +pdfjs-editor-highlight-button-label = Evidentia +pdfjs-highlight-floating-button1 = + .title = Evidentiar + .aria-label = Evidentiar +pdfjs-highlight-floating-button-label = Evidentiar + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Remover le designo +pdfjs-editor-remove-freetext-button = + .title = Remover texto +pdfjs-editor-remove-stamp-button = + .title = Remover imagine +pdfjs-editor-remove-highlight-button = + .title = Remover evidentia + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Color +pdfjs-editor-free-text-size-input = Dimension +pdfjs-editor-ink-color-input = Color +pdfjs-editor-ink-thickness-input = Spissor +pdfjs-editor-ink-opacity-input = Opacitate +pdfjs-editor-stamp-add-image-button = + .title = Adder imagine +pdfjs-editor-stamp-add-image-button-label = Adder imagine +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Spissor +pdfjs-editor-free-highlight-thickness-title = + .title = Cambiar spissor evidentiante elementos differente de texto +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Editor de texto + .default-content = Initiar a inserer… +pdfjs-free-text = + .aria-label = Editor de texto +pdfjs-free-text-default-content = Comenciar a scriber… +pdfjs-ink = + .aria-label = Editor de designos +pdfjs-ink-canvas = + .aria-label = Imagine create per le usator + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Texto alternative +pdfjs-editor-alt-text-edit-button = + .aria-label = Rediger texto alternative +pdfjs-editor-alt-text-edit-button-label = Rediger texto alternative +pdfjs-editor-alt-text-dialog-label = Elige un option +pdfjs-editor-alt-text-dialog-description = Le texto alternative (alt text) adjuta quando le personas non pote vider le imagine o quando illo non carga. +pdfjs-editor-alt-text-add-description-label = Adder un description +pdfjs-editor-alt-text-add-description-description = Mira a 1-2 phrases que describe le subjecto, parametro, o actiones. +pdfjs-editor-alt-text-mark-decorative-label = Marcar como decorative +pdfjs-editor-alt-text-mark-decorative-description = Isto es usate pro imagines ornamental, como bordaturas o filigranas. +pdfjs-editor-alt-text-cancel-button = Cancellar +pdfjs-editor-alt-text-save-button = Salvar +pdfjs-editor-alt-text-decorative-tooltip = Marcate como decorative +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Per exemplo, “Un juvene sede a un tabula pro mangiar un repasto†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Texto alternative + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Angulo superior sinistre — redimensionar +pdfjs-editor-resizer-label-top-middle = Medio superior — redimensionar +pdfjs-editor-resizer-label-top-right = Angulo superior dextre — redimensionar +pdfjs-editor-resizer-label-middle-right = Medio dextre — redimensionar +pdfjs-editor-resizer-label-bottom-right = Angulo inferior dextre — redimensionar +pdfjs-editor-resizer-label-bottom-middle = Medio inferior — redimensionar +pdfjs-editor-resizer-label-bottom-left = Angulo inferior sinistre — redimensionar +pdfjs-editor-resizer-label-middle-left = Medio sinistre — redimensionar +pdfjs-editor-resizer-top-left = + .aria-label = Angulo superior sinistre — redimensionar +pdfjs-editor-resizer-top-middle = + .aria-label = Medio superior — redimensionar +pdfjs-editor-resizer-top-right = + .aria-label = Angulo superior dextre — redimensionar +pdfjs-editor-resizer-middle-right = + .aria-label = Medio dextre — redimensionar +pdfjs-editor-resizer-bottom-right = + .aria-label = Angulo inferior dextre — redimensionar +pdfjs-editor-resizer-bottom-middle = + .aria-label = Medio inferior — redimensionar +pdfjs-editor-resizer-bottom-left = + .aria-label = Angulo inferior sinistre — redimensionar +pdfjs-editor-resizer-middle-left = + .aria-label = Medio sinistre — redimensionar + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Color pro evidentiar +pdfjs-editor-colorpicker-button = + .title = Cambiar color +pdfjs-editor-colorpicker-dropdown = + .aria-label = Electiones del color +pdfjs-editor-colorpicker-yellow = + .title = Jalne +pdfjs-editor-colorpicker-green = + .title = Verde +pdfjs-editor-colorpicker-blue = + .title = Blau +pdfjs-editor-colorpicker-pink = + .title = Rosate +pdfjs-editor-colorpicker-red = + .title = Rubie + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Monstrar toto +pdfjs-editor-highlight-show-all-button = + .title = Monstrar toto + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Rediger texto alternative (description del imagine) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Adder texto alternative (description del imagine) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Scribe tu description ci… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Breve description pro personas qui non pote vider le imagine o quando le imagine non se carga. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Iste texto alternative ha essite create automaticamente e pote esser inexacte. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Pro saper plus +pdfjs-editor-new-alt-text-create-automatically-button-label = Crear texto alternative automaticamente +pdfjs-editor-new-alt-text-not-now-button = Non ora +pdfjs-editor-new-alt-text-error-title = Impossibile crear texto alternative automaticamente +pdfjs-editor-new-alt-text-error-description = Scribe tu proprie texto alternative o retenta plus tarde. +pdfjs-editor-new-alt-text-error-close-button = Clauder +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Discargante modello de intelligentia artificial del texto alternative ({ $downloadedSize } de { $totalSize } MB) + .aria-valuetext = Discargante modello de intelligentia artificial del texto alternative ({ $downloadedSize } de { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Texto alternative addite +pdfjs-editor-new-alt-text-added-button-label = Texto alternative addite +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Texto alternative mancante +pdfjs-editor-new-alt-text-missing-button-label = Texto alternative mancante +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Revider texto alternative +pdfjs-editor-new-alt-text-to-review-button-label = Revider texto alternative +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automaticamente create: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Parametros del texto alternative del imagine +pdfjs-image-alt-text-settings-button-label = Parametros del texto alternative del imagine +pdfjs-editor-alt-text-settings-dialog-label = Parametros del texto alternative del imagine +pdfjs-editor-alt-text-settings-automatic-title = Texto alternative automatic +pdfjs-editor-alt-text-settings-create-model-button-label = Crear texto alternative automaticamente +pdfjs-editor-alt-text-settings-create-model-description = Suggere descriptiones pro adjutar le personas qui non pote vider le imagine o quando le imagine non carga. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modello de intelligentia artificial del texto alternative ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Flue localmente sur tu apparato assi tu datos remane private. Necessari pro texto alternative automatic. +pdfjs-editor-alt-text-settings-delete-model-button = Deler +pdfjs-editor-alt-text-settings-download-model-button = Discargar +pdfjs-editor-alt-text-settings-downloading-model-button = Discargante… +pdfjs-editor-alt-text-settings-editor-title = Rediger texto alternative +pdfjs-editor-alt-text-settings-show-dialog-button-label = Monstrar le redactor de texto alternative a pena on adde un imagine +pdfjs-editor-alt-text-settings-show-dialog-description = Te adjuta a verifica que tote tu imagines ha un texto alternative. +pdfjs-editor-alt-text-settings-close-button = Clauder + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Evidentiation removite +pdfjs-editor-undo-bar-message-freetext = Texto removite +pdfjs-editor-undo-bar-message-ink = Designo removite +pdfjs-editor-undo-bar-message-stamp = Imagine removite +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } annotation removite + *[other] { $count } annotationes removite + } +pdfjs-editor-undo-bar-undo-button = + .title = Disfacer +pdfjs-editor-undo-bar-undo-button-label = Disfacer +pdfjs-editor-undo-bar-close-button = + .title = Clauder +pdfjs-editor-undo-bar-close-button-label = Clauder diff --git a/public/assets/pdfjs/locale/id/viewer.ftl b/public/assets/pdfjs/locale/id/viewer.ftl new file mode 100755 index 0000000..c985a33 --- /dev/null +++ b/public/assets/pdfjs/locale/id/viewer.ftl @@ -0,0 +1,374 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Laman Sebelumnya +pdfjs-previous-button-label = Sebelumnya +pdfjs-next-button = + .title = Laman Selanjutnya +pdfjs-next-button-label = Selanjutnya +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Halaman +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = dari { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } dari { $pagesCount }) +pdfjs-zoom-out-button = + .title = Perkecil +pdfjs-zoom-out-button-label = Perkecil +pdfjs-zoom-in-button = + .title = Perbesar +pdfjs-zoom-in-button-label = Perbesar +pdfjs-zoom-select = + .title = Perbesaran +pdfjs-presentation-mode-button = + .title = Ganti ke Mode Presentasi +pdfjs-presentation-mode-button-label = Mode Presentasi +pdfjs-open-file-button = + .title = Buka Berkas +pdfjs-open-file-button-label = Buka +pdfjs-print-button = + .title = Cetak +pdfjs-print-button-label = Cetak +pdfjs-save-button = + .title = Simpan +pdfjs-save-button-label = Simpan +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Unduh +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Unduh +pdfjs-bookmark-button = + .title = Laman Saat Ini (Lihat URL dari Laman Sekarang) +pdfjs-bookmark-button-label = Laman Saat Ini + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Alat +pdfjs-tools-button-label = Alat +pdfjs-first-page-button = + .title = Buka Halaman Pertama +pdfjs-first-page-button-label = Buka Halaman Pertama +pdfjs-last-page-button = + .title = Buka Halaman Terakhir +pdfjs-last-page-button-label = Buka Halaman Terakhir +pdfjs-page-rotate-cw-button = + .title = Putar Searah Jarum Jam +pdfjs-page-rotate-cw-button-label = Putar Searah Jarum Jam +pdfjs-page-rotate-ccw-button = + .title = Putar Berlawanan Arah Jarum Jam +pdfjs-page-rotate-ccw-button-label = Putar Berlawanan Arah Jarum Jam +pdfjs-cursor-text-select-tool-button = + .title = Aktifkan Alat Seleksi Teks +pdfjs-cursor-text-select-tool-button-label = Alat Seleksi Teks +pdfjs-cursor-hand-tool-button = + .title = Aktifkan Alat Tangan +pdfjs-cursor-hand-tool-button-label = Alat Tangan +pdfjs-scroll-page-button = + .title = Gunakan Pengguliran Laman +pdfjs-scroll-page-button-label = Pengguliran Laman +pdfjs-scroll-vertical-button = + .title = Gunakan Penggeseran Vertikal +pdfjs-scroll-vertical-button-label = Penggeseran Vertikal +pdfjs-scroll-horizontal-button = + .title = Gunakan Penggeseran Horizontal +pdfjs-scroll-horizontal-button-label = Penggeseran Horizontal +pdfjs-scroll-wrapped-button = + .title = Gunakan Penggeseran Terapit +pdfjs-scroll-wrapped-button-label = Penggeseran Terapit +pdfjs-spread-none-button = + .title = Jangan gabungkan lembar halaman +pdfjs-spread-none-button-label = Tidak Ada Lembaran +pdfjs-spread-odd-button = + .title = Gabungkan lembar lamanan mulai dengan halaman ganjil +pdfjs-spread-odd-button-label = Lembaran Ganjil +pdfjs-spread-even-button = + .title = Gabungkan lembar halaman dimulai dengan halaman genap +pdfjs-spread-even-button-label = Lembaran Genap + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Properti Dokumen… +pdfjs-document-properties-button-label = Properti Dokumen… +pdfjs-document-properties-file-name = Nama berkas: +pdfjs-document-properties-file-size = Ukuran berkas: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } byte) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } byte) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } byte) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } byte) +pdfjs-document-properties-title = Judul: +pdfjs-document-properties-author = Penyusun: +pdfjs-document-properties-subject = Subjek: +pdfjs-document-properties-keywords = Kata Kunci: +pdfjs-document-properties-creation-date = Tanggal Dibuat: +pdfjs-document-properties-modification-date = Tanggal Dimodifikasi: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Pembuat: +pdfjs-document-properties-producer = Pemroduksi PDF: +pdfjs-document-properties-version = Versi PDF: +pdfjs-document-properties-page-count = Jumlah Halaman: +pdfjs-document-properties-page-size = Ukuran Laman: +pdfjs-document-properties-page-size-unit-inches = inci +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = tegak +pdfjs-document-properties-page-size-orientation-landscape = mendatar +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Tampilan Web Kilat: +pdfjs-document-properties-linearized-yes = Ya +pdfjs-document-properties-linearized-no = Tidak +pdfjs-document-properties-close-button = Tutup + +## Print + +pdfjs-print-progress-message = Menyiapkan dokumen untuk pencetakan… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Batalkan +pdfjs-printing-not-supported = Peringatan: Pencetakan tidak didukung secara lengkap pada peramban ini. +pdfjs-printing-not-ready = Peringatan: Berkas PDF masih belum dimuat secara lengkap untuk dapat dicetak. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Aktif/Nonaktifkan Bilah Samping +pdfjs-toggle-sidebar-notification-button = + .title = Aktif/Nonaktifkan Bilah Samping (dokumen berisi kerangka/lampiran/lapisan) +pdfjs-toggle-sidebar-button-label = Aktif/Nonaktifkan Bilah Samping +pdfjs-document-outline-button = + .title = Tampilkan Kerangka Dokumen (klik ganda untuk membentangkan/menciutkan semua item) +pdfjs-document-outline-button-label = Kerangka Dokumen +pdfjs-attachments-button = + .title = Tampilkan Lampiran +pdfjs-attachments-button-label = Lampiran +pdfjs-layers-button = + .title = Tampilkan Lapisan (klik ganda untuk mengatur ulang semua lapisan ke keadaan baku) +pdfjs-layers-button-label = Lapisan +pdfjs-thumbs-button = + .title = Tampilkan Miniatur +pdfjs-thumbs-button-label = Miniatur +pdfjs-current-outline-item-button = + .title = Cari Butir Ikhtisar Saat Ini +pdfjs-current-outline-item-button-label = Butir Ikhtisar Saat Ini +pdfjs-findbar-button = + .title = Temukan di Dokumen +pdfjs-findbar-button-label = Temukan +pdfjs-additional-layers = Lapisan Tambahan + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Laman { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatur Laman { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Temukan + .placeholder = Temukan di dokumen… +pdfjs-find-previous-button = + .title = Temukan kata sebelumnya +pdfjs-find-previous-button-label = Sebelumnya +pdfjs-find-next-button = + .title = Temukan lebih lanjut +pdfjs-find-next-button-label = Selanjutnya +pdfjs-find-highlight-checkbox = Sorot semuanya +pdfjs-find-match-case-checkbox-label = Cocokkan BESAR/kecil +pdfjs-find-match-diacritics-checkbox-label = Pencocokan Diakritik +pdfjs-find-entire-word-checkbox-label = Seluruh teks +pdfjs-find-reached-top = Sampai di awal dokumen, dilanjutkan dari bawah +pdfjs-find-reached-bottom = Sampai di akhir dokumen, dilanjutkan dari atas +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = { $current } dari { $total } yang cocok +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = Lebih dari { $limit } kecocokan +pdfjs-find-not-found = Frasa tidak ditemukan + +## Predefined zoom values + +pdfjs-page-scale-width = Lebar Laman +pdfjs-page-scale-fit = Muat Laman +pdfjs-page-scale-auto = Perbesaran Otomatis +pdfjs-page-scale-actual = Ukuran Asli +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Halaman { $page } + +## Loading indicator messages + +pdfjs-loading-error = Galat terjadi saat memuat PDF. +pdfjs-invalid-file-error = Berkas PDF tidak valid atau rusak. +pdfjs-missing-file-error = Berkas PDF tidak ada. +pdfjs-unexpected-response-error = Balasan server yang tidak diharapkan. +pdfjs-rendering-error = Galat terjadi saat merender laman. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Anotasi { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Masukkan sandi untuk membuka berkas PDF ini. +pdfjs-password-invalid = Sandi tidak valid. Silakan coba lagi. +pdfjs-password-ok-button = Oke +pdfjs-password-cancel-button = Batal +pdfjs-web-fonts-disabled = Font web dinonaktifkan: tidak dapat menggunakan font PDF yang tersemat. + +## Editing + +pdfjs-editor-free-text-button = + .title = Teks +pdfjs-editor-free-text-button-label = Teks +pdfjs-editor-ink-button = + .title = Gambar +pdfjs-editor-ink-button-label = Gambar +pdfjs-editor-stamp-button = + .title = Tambah atau edit gambar +pdfjs-editor-stamp-button-label = Tambah atau edit gambar +pdfjs-editor-highlight-button = + .title = Sorot +pdfjs-editor-highlight-button-label = Sorot +pdfjs-highlight-floating-button1 = + .title = Sorot + .aria-label = Sorot +pdfjs-highlight-floating-button-label = Sorot + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Hapus gambar +pdfjs-editor-remove-freetext-button = + .title = Hapus teks +pdfjs-editor-remove-stamp-button = + .title = Hapus gambar +pdfjs-editor-remove-highlight-button = + .title = Hapus sorotan + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Warna +pdfjs-editor-free-text-size-input = Ukuran +pdfjs-editor-ink-color-input = Warna +pdfjs-editor-ink-thickness-input = Ketebalan +pdfjs-editor-ink-opacity-input = Opasitas +pdfjs-editor-stamp-add-image-button = + .title = Tambahkan gambar +pdfjs-editor-stamp-add-image-button-label = Tambahkan gambar +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Ketebalan +pdfjs-editor-free-highlight-thickness-title = + .title = Ubah ketebalan saat menyorot item selain teks +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Editor Teks + .default-content = Mulai mengetik… +pdfjs-free-text = + .aria-label = Editor Teks +pdfjs-free-text-default-content = Mulai mengetik… +pdfjs-ink = + .aria-label = Editor Gambar +pdfjs-ink-canvas = + .aria-label = Gambar yang dibuat pengguna + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Teks alternatif +pdfjs-editor-alt-text-edit-button = + .aria-label = Edit teks alternatif +pdfjs-editor-alt-text-edit-button-label = Edit teks alternatif +pdfjs-editor-alt-text-dialog-label = Pilih opsi + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + + +## Color picker + + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + + +## Image alt-text settings + diff --git a/public/assets/pdfjs/locale/is/viewer.ftl b/public/assets/pdfjs/locale/is/viewer.ftl new file mode 100755 index 0000000..deda510 --- /dev/null +++ b/public/assets/pdfjs/locale/is/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Fyrri síða +pdfjs-previous-button-label = Fyrri +pdfjs-next-button = + .title = Næsta síða +pdfjs-next-button-label = Næsti +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Síða +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = af { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } af { $pagesCount }) +pdfjs-zoom-out-button = + .title = Minnka aðdrátt +pdfjs-zoom-out-button-label = Minnka aðdrátt +pdfjs-zoom-in-button = + .title = Auka aðdrátt +pdfjs-zoom-in-button-label = Auka aðdrátt +pdfjs-zoom-select = + .title = Aðdráttur +pdfjs-presentation-mode-button = + .title = Skipta yfir á kynningarham +pdfjs-presentation-mode-button-label = Kynningarhamur +pdfjs-open-file-button = + .title = Opna skrá +pdfjs-open-file-button-label = Opna +pdfjs-print-button = + .title = Prenta +pdfjs-print-button-label = Prenta +pdfjs-save-button = + .title = Vista +pdfjs-save-button-label = Vista +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Sækja +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Sækja +pdfjs-bookmark-button = + .title = Núverandi síða (Skoða vefslóð frá núverandi síðu) +pdfjs-bookmark-button-label = Núverandi síða + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Verkfæri +pdfjs-tools-button-label = Verkfæri +pdfjs-first-page-button = + .title = Fara á fyrstu síðu +pdfjs-first-page-button-label = Fara á fyrstu síðu +pdfjs-last-page-button = + .title = Fara á síðustu síðu +pdfjs-last-page-button-label = Fara á síðustu síðu +pdfjs-page-rotate-cw-button = + .title = Snúa réttsælis +pdfjs-page-rotate-cw-button-label = Snúa réttsælis +pdfjs-page-rotate-ccw-button = + .title = Snúa rangsælis +pdfjs-page-rotate-ccw-button-label = Snúa rangsælis +pdfjs-cursor-text-select-tool-button = + .title = Virkja textavalsáhald +pdfjs-cursor-text-select-tool-button-label = Textavalsáhald +pdfjs-cursor-hand-tool-button = + .title = Virkja handarverkfæri +pdfjs-cursor-hand-tool-button-label = Handarverkfæri +pdfjs-scroll-page-button = + .title = Nota síðuskrun +pdfjs-scroll-page-button-label = Síðuskrun +pdfjs-scroll-vertical-button = + .title = Nota lóðrétt skrun +pdfjs-scroll-vertical-button-label = Lóðrétt skrun +pdfjs-scroll-horizontal-button = + .title = Nota lárétt skrun +pdfjs-scroll-horizontal-button-label = Lárétt skrun +pdfjs-scroll-wrapped-button = + .title = Nota línuskipt síðuskrun +pdfjs-scroll-wrapped-button-label = Línuskipt síðuskrun +pdfjs-spread-none-button = + .title = Ekki taka þátt í dreifingu síðna +pdfjs-spread-none-button-label = Engin dreifing +pdfjs-spread-odd-button = + .title = Taka þátt í dreifingu síðna með oddatölum +pdfjs-spread-odd-button-label = Oddatöludreifing +pdfjs-spread-even-button = + .title = Taktu þátt í dreifingu síðna með jöfnuntölum +pdfjs-spread-even-button-label = Jafnatöludreifing + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Eiginleikar skjals… +pdfjs-document-properties-button-label = Eiginleikar skjals… +pdfjs-document-properties-file-name = Skráarnafn: +pdfjs-document-properties-file-size = Skrárstærð: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bæti) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bæti) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Titill: +pdfjs-document-properties-author = Hönnuður: +pdfjs-document-properties-subject = Efni: +pdfjs-document-properties-keywords = Stikkorð: +pdfjs-document-properties-creation-date = Búið til: +pdfjs-document-properties-modification-date = Dags breytingar: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Höfundur: +pdfjs-document-properties-producer = PDF framleiðandi: +pdfjs-document-properties-version = PDF útgáfa: +pdfjs-document-properties-page-count = Blaðsíðufjöldi: +pdfjs-document-properties-page-size = Stærð síðu: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = skammsnið +pdfjs-document-properties-page-size-orientation-landscape = langsnið +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Fljótleg vefskoðun: +pdfjs-document-properties-linearized-yes = Já +pdfjs-document-properties-linearized-no = Nei +pdfjs-document-properties-close-button = Loka + +## Print + +pdfjs-print-progress-message = Undirbý skjal fyrir prentun… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Hætta við +pdfjs-printing-not-supported = Aðvörun: Prentun er ekki með fyllilegan stuðning á þessum vafra. +pdfjs-printing-not-ready = Aðvörun: Ekki er búið að hlaða inn allri PDF skránni fyrir prentun. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Víxla hliðarspjaldi af/á +pdfjs-toggle-sidebar-notification-button = + .title = Víxla hliðarslá (skjal inniheldur yfirlit/viðhengi/lög) +pdfjs-toggle-sidebar-button-label = Víxla hliðarspjaldi af/á +pdfjs-document-outline-button = + .title = Sýna yfirlit skjals (tvísmelltu til að opna/loka öllum hlutum) +pdfjs-document-outline-button-label = Efnisskipan skjals +pdfjs-attachments-button = + .title = Sýna viðhengi +pdfjs-attachments-button-label = Viðhengi +pdfjs-layers-button = + .title = Birta lög (tvísmelltu til að endurstilla öll lög í sjálfgefna stöðu) +pdfjs-layers-button-label = Lög +pdfjs-thumbs-button = + .title = Sýna smámyndir +pdfjs-thumbs-button-label = Smámyndir +pdfjs-current-outline-item-button = + .title = Finna núverandi atriði efnisskipunar +pdfjs-current-outline-item-button-label = Núverandi atriði efnisskipunar +pdfjs-findbar-button = + .title = Leita í skjali +pdfjs-findbar-button-label = Leita +pdfjs-additional-layers = Viðbótarlög + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Síða { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Smámynd af síðu { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Leita + .placeholder = Leita í skjali… +pdfjs-find-previous-button = + .title = Leita að fyrra tilfelli þessara orða +pdfjs-find-previous-button-label = Fyrri +pdfjs-find-next-button = + .title = Leita að næsta tilfelli þessara orða +pdfjs-find-next-button-label = Næsti +pdfjs-find-highlight-checkbox = Lita allt +pdfjs-find-match-case-checkbox-label = Passa við stafstöðu +pdfjs-find-match-diacritics-checkbox-label = Passa við broddstafi +pdfjs-find-entire-word-checkbox-label = Heil orð +pdfjs-find-reached-top = Náði efst í skjal, held áfram neðst +pdfjs-find-reached-bottom = Náði enda skjals, held áfram efst +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } af { $total } passar við + *[other] { $current } af { $total } passa við + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Fleiri en { $limit } passar við + *[other] Fleiri en { $limit } passa við + } +pdfjs-find-not-found = Fann ekki orðið + +## Predefined zoom values + +pdfjs-page-scale-width = Síðubreidd +pdfjs-page-scale-fit = Passa á síðu +pdfjs-page-scale-auto = Sjálfvirkur aðdráttur +pdfjs-page-scale-actual = Raunstærð +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Síða { $page } + +## Loading indicator messages + +pdfjs-loading-error = Villa kom upp við að hlaða inn PDF. +pdfjs-invalid-file-error = Ógild eða skemmd PDF skrá. +pdfjs-missing-file-error = Vantar PDF skrá. +pdfjs-unexpected-response-error = Óvænt svar frá netþjóni. +pdfjs-rendering-error = Upp kom villa við að birta síðuna. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Skýring] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Settu inn lykilorð til að opna þessa PDF-skrá. +pdfjs-password-invalid = Ógilt lykilorð. Reyndu aftur. +pdfjs-password-ok-button = à lagi +pdfjs-password-cancel-button = Hætta við +pdfjs-web-fonts-disabled = Vef leturgerðir eru óvirkar: get ekki notað innbyggðar PDF leturgerðir. + +## Editing + +pdfjs-editor-free-text-button = + .title = Texti +pdfjs-editor-free-text-button-label = Texti +pdfjs-editor-ink-button = + .title = Teikna +pdfjs-editor-ink-button-label = Teikna +pdfjs-editor-stamp-button = + .title = Bæta við eða breyta myndum +pdfjs-editor-stamp-button-label = Bæta við eða breyta myndum +pdfjs-editor-highlight-button = + .title = Ãherslulita +pdfjs-editor-highlight-button-label = Ãherslulita +pdfjs-highlight-floating-button1 = + .title = Ãherslulita + .aria-label = Ãherslulita +pdfjs-highlight-floating-button-label = Ãherslulita + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Fjarlægja teikningu +pdfjs-editor-remove-freetext-button = + .title = Fjarlægja texta +pdfjs-editor-remove-stamp-button = + .title = Fjarlægja mynd +pdfjs-editor-remove-highlight-button = + .title = Fjarlægja áherslulit + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Litur +pdfjs-editor-free-text-size-input = Stærð +pdfjs-editor-ink-color-input = Litur +pdfjs-editor-ink-thickness-input = Þykkt +pdfjs-editor-ink-opacity-input = Ógegnsæi +pdfjs-editor-stamp-add-image-button = + .title = Bæta við mynd +pdfjs-editor-stamp-add-image-button-label = Bæta við mynd +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Þykkt +pdfjs-editor-free-highlight-thickness-title = + .title = Breyta þykkt við áherslulitun annarra atriða en texta +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Textaritill + .default-content = Byrjaðu að skrifa… +pdfjs-free-text = + .aria-label = Textaritill +pdfjs-free-text-default-content = Byrjaðu að skrifa… +pdfjs-ink = + .aria-label = Teikniritill +pdfjs-ink-canvas = + .aria-label = Mynd gerð af notanda + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alt-varatexti +pdfjs-editor-alt-text-edit-button = + .aria-label = Breyta alt-myndatexta +pdfjs-editor-alt-text-edit-button-label = Breyta alt-varatexta +pdfjs-editor-alt-text-dialog-label = Veldu valkost +pdfjs-editor-alt-text-dialog-description = Alt-varatexti (auka-myndatexti) hjálpar þegar fólk getur ekki séð myndina eða þegar hún hleðst ekki inn. +pdfjs-editor-alt-text-add-description-label = Bættu við lýsingu +pdfjs-editor-alt-text-add-description-description = Reyndu að takmarka þetta við 1-2 setningar sem lýsa efninu, umhverfi eða aðgerðum. +pdfjs-editor-alt-text-mark-decorative-label = Merkja sem skraut +pdfjs-editor-alt-text-mark-decorative-description = Þetta er notað fyrir skrautmyndir, eins og borða eða vatnsmerki. +pdfjs-editor-alt-text-cancel-button = Hætta við +pdfjs-editor-alt-text-save-button = Vista +pdfjs-editor-alt-text-decorative-tooltip = Merkt sem skraut +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Til dæmis: „Ungur maður sest við borð til að snæða máltíð“ +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alt-myndatexti + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Efst í vinstra horni - breyta stærð +pdfjs-editor-resizer-label-top-middle = Efst á miðju - breyta stærð +pdfjs-editor-resizer-label-top-right = Efst í hægra horni - breyta stærð +pdfjs-editor-resizer-label-middle-right = Miðja til hægri - breyta stærð +pdfjs-editor-resizer-label-bottom-right = Neðst í hægra horni - breyta stærð +pdfjs-editor-resizer-label-bottom-middle = Neðst á miðju - breyta stærð +pdfjs-editor-resizer-label-bottom-left = Neðst í vinstra horni - breyta stærð +pdfjs-editor-resizer-label-middle-left = Miðja til vinstri - breyta stærð +pdfjs-editor-resizer-top-left = + .aria-label = Efst í vinstra horni - breyta stærð +pdfjs-editor-resizer-top-middle = + .aria-label = Efst á miðju - breyta stærð +pdfjs-editor-resizer-top-right = + .aria-label = Efst í hægra horni - breyta stærð +pdfjs-editor-resizer-middle-right = + .aria-label = Miðja til hægri - breyta stærð +pdfjs-editor-resizer-bottom-right = + .aria-label = Neðst í hægra horni - breyta stærð +pdfjs-editor-resizer-bottom-middle = + .aria-label = Neðst á miðju - breyta stærð +pdfjs-editor-resizer-bottom-left = + .aria-label = Neðst í vinstra horni - breyta stærð +pdfjs-editor-resizer-middle-left = + .aria-label = Miðja til vinstri - breyta stærð + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Ãherslulitur +pdfjs-editor-colorpicker-button = + .title = Skipta um lit +pdfjs-editor-colorpicker-dropdown = + .aria-label = Val lita +pdfjs-editor-colorpicker-yellow = + .title = Gult +pdfjs-editor-colorpicker-green = + .title = Grænt +pdfjs-editor-colorpicker-blue = + .title = Blátt +pdfjs-editor-colorpicker-pink = + .title = Bleikt +pdfjs-editor-colorpicker-red = + .title = Rautt + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Birta allt +pdfjs-editor-highlight-show-all-button = + .title = Birta allt + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Breyta alt-myndatexta (lýsingu á mynd) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Bæta við alt-myndatexta (lýsingu á mynd) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Skrifaðu lýsinguna þína hér… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Stutt lýsing fyrir fólk sem getur ekki séð myndina eða þegar myndin hleðst ekki inn. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Þessi alt-myndatexti var búinn til sjálfvirkt og gæti verið ónákvæmur. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Kanna nánar +pdfjs-editor-new-alt-text-create-automatically-button-label = Útbúa alt-myndatexta sjálfvirkt +pdfjs-editor-new-alt-text-not-now-button = Ekki núna +pdfjs-editor-new-alt-text-error-title = Gat ekki búið til alt-myndatexta sjálfkrafa +pdfjs-editor-new-alt-text-error-description = Skrifaðu þinn eiginn alt-myndatexta eða reyndu aftur síðar. +pdfjs-editor-new-alt-text-error-close-button = Loka +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Sækir gervigreindarlíkan með alt-myndatextum ({ $downloadedSize } af { $totalSize } MB) + .aria-valuetext = Sækir gervigreindarlíkan með alt-myndatextum ({ $downloadedSize } af { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alt-myndatexta bætt við +pdfjs-editor-new-alt-text-added-button-label = Alt-myndatexta bætt við +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Vantar alt-myndatexta +pdfjs-editor-new-alt-text-missing-button-label = Vantar alt-myndatexta +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Yfirfara alt-myndatexta +pdfjs-editor-new-alt-text-to-review-button-label = Yfirfara myndatexta +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Útbúið sjálfvirkt: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Stillingar fyrir alt-texta myndar +pdfjs-image-alt-text-settings-button-label = Stillingar fyrir alt-texta myndar +pdfjs-editor-alt-text-settings-dialog-label = Stillingar fyrir alt-texta myndar +pdfjs-editor-alt-text-settings-automatic-title = Sjálfvirkur alt-myndatexti +pdfjs-editor-alt-text-settings-create-model-button-label = Útbúa alt-myndatexta sjálfvirkt +pdfjs-editor-alt-text-settings-create-model-description = Stingur upp á lýsingum til að hjálpa fólki sem getur ekki séð myndina eða þegar myndin hleðst ekki inn. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Gervigreindarlíkan alt-myndatexta ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Keyrir staðbundið á tækinu þínu svo gögnin þín haldast undir þinni stjórn. Nauðsynlegt fyrir sjálfvirka alt-myndatexta. +pdfjs-editor-alt-text-settings-delete-model-button = Eyða +pdfjs-editor-alt-text-settings-download-model-button = Sækja +pdfjs-editor-alt-text-settings-downloading-model-button = Sæki… +pdfjs-editor-alt-text-settings-editor-title = Ritill fyrir alt-myndatexta +pdfjs-editor-alt-text-settings-show-dialog-button-label = Sýna alt-myndatextaritil strax þegar mynd er bætt við +pdfjs-editor-alt-text-settings-show-dialog-description = Hjálpar þér að tryggja að allar myndirnar þínar séu með alt-myndatexta. +pdfjs-editor-alt-text-settings-close-button = Loka + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Ãherslulitun fjarlægð +pdfjs-editor-undo-bar-message-freetext = Texti fjarlægður +pdfjs-editor-undo-bar-message-ink = Teikning fjarlægð +pdfjs-editor-undo-bar-message-stamp = Mynd fjarlægð +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } glósa fjarlægð + *[other] { $count } glósur fjarlægðar + } +pdfjs-editor-undo-bar-undo-button = + .title = Afturkalla +pdfjs-editor-undo-bar-undo-button-label = Afturkalla +pdfjs-editor-undo-bar-close-button = + .title = Loka +pdfjs-editor-undo-bar-close-button-label = Loka diff --git a/public/assets/pdfjs/locale/it/viewer.ftl b/public/assets/pdfjs/locale/it/viewer.ftl new file mode 100755 index 0000000..d1de7e1 --- /dev/null +++ b/public/assets/pdfjs/locale/it/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Pagina precedente +pdfjs-previous-button-label = Precedente +pdfjs-next-button = + .title = Pagina successiva +pdfjs-next-button-label = Successiva +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Pagina +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = di { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } di { $pagesCount }) +pdfjs-zoom-out-button = + .title = Riduci zoom +pdfjs-zoom-out-button-label = Riduci zoom +pdfjs-zoom-in-button = + .title = Aumenta zoom +pdfjs-zoom-in-button-label = Aumenta zoom +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Passa alla modalità presentazione +pdfjs-presentation-mode-button-label = Modalità presentazione +pdfjs-open-file-button = + .title = Apri file +pdfjs-open-file-button-label = Apri +pdfjs-print-button = + .title = Stampa +pdfjs-print-button-label = Stampa +pdfjs-save-button = + .title = Salva +pdfjs-save-button-label = Salva +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Scarica +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Scarica +pdfjs-bookmark-button = + .title = Pagina corrente (mostra URL della pagina corrente) +pdfjs-bookmark-button-label = Pagina corrente + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Strumenti +pdfjs-tools-button-label = Strumenti +pdfjs-first-page-button = + .title = Vai alla prima pagina +pdfjs-first-page-button-label = Vai alla prima pagina +pdfjs-last-page-button = + .title = Vai all’ultima pagina +pdfjs-last-page-button-label = Vai all’ultima pagina +pdfjs-page-rotate-cw-button = + .title = Ruota in senso orario +pdfjs-page-rotate-cw-button-label = Ruota in senso orario +pdfjs-page-rotate-ccw-button = + .title = Ruota in senso antiorario +pdfjs-page-rotate-ccw-button-label = Ruota in senso antiorario +pdfjs-cursor-text-select-tool-button = + .title = Attiva strumento di selezione testo +pdfjs-cursor-text-select-tool-button-label = Strumento di selezione testo +pdfjs-cursor-hand-tool-button = + .title = Attiva strumento mano +pdfjs-cursor-hand-tool-button-label = Strumento mano +pdfjs-scroll-page-button = + .title = Utilizza scorrimento pagine +pdfjs-scroll-page-button-label = Scorrimento pagine +pdfjs-scroll-vertical-button = + .title = Scorri le pagine in verticale +pdfjs-scroll-vertical-button-label = Scorrimento verticale +pdfjs-scroll-horizontal-button = + .title = Scorri le pagine in orizzontale +pdfjs-scroll-horizontal-button-label = Scorrimento orizzontale +pdfjs-scroll-wrapped-button = + .title = Scorri le pagine in verticale, disponendole da sinistra a destra e andando a capo automaticamente +pdfjs-scroll-wrapped-button-label = Scorrimento con a capo automatico +pdfjs-spread-none-button = + .title = Non raggruppare pagine +pdfjs-spread-none-button-label = Nessun raggruppamento +pdfjs-spread-odd-button = + .title = Crea gruppi di pagine che iniziano con numeri di pagina dispari +pdfjs-spread-odd-button-label = Raggruppamento dispari +pdfjs-spread-even-button = + .title = Crea gruppi di pagine che iniziano con numeri di pagina pari +pdfjs-spread-even-button-label = Raggruppamento pari + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Proprietà del documento… +pdfjs-document-properties-button-label = Proprietà del documento… +pdfjs-document-properties-file-name = Nome file: +pdfjs-document-properties-file-size = Dimensione file: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } byte) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } byte) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } kB ({ $size_b } byte) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } byte) +pdfjs-document-properties-title = Titolo: +pdfjs-document-properties-author = Autore: +pdfjs-document-properties-subject = Oggetto: +pdfjs-document-properties-keywords = Parole chiave: +pdfjs-document-properties-creation-date = Data creazione: +pdfjs-document-properties-modification-date = Data modifica: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Autore originale: +pdfjs-document-properties-producer = Produttore PDF: +pdfjs-document-properties-version = Versione PDF: +pdfjs-document-properties-page-count = Conteggio pagine: +pdfjs-document-properties-page-size = Dimensioni pagina: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = verticale +pdfjs-document-properties-page-size-orientation-landscape = orizzontale +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Lettera +pdfjs-document-properties-page-size-name-legal = Legale + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Visualizzazione web veloce: +pdfjs-document-properties-linearized-yes = Sì +pdfjs-document-properties-linearized-no = No +pdfjs-document-properties-close-button = Chiudi + +## Print + +pdfjs-print-progress-message = Preparazione documento per la stampa… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Annulla +pdfjs-printing-not-supported = Attenzione: la stampa non è completamente supportata da questo browser. +pdfjs-printing-not-ready = Attenzione: il PDF non è ancora stato caricato completamente per la stampa. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Attiva/disattiva barra laterale +pdfjs-toggle-sidebar-notification-button = + .title = Attiva/disattiva barra laterale (il documento contiene struttura/allegati/livelli) +pdfjs-toggle-sidebar-button-label = Attiva/disattiva barra laterale +pdfjs-document-outline-button = + .title = Visualizza la struttura del documento (doppio clic per visualizzare/comprimere tutti gli elementi) +pdfjs-document-outline-button-label = Struttura documento +pdfjs-attachments-button = + .title = Visualizza allegati +pdfjs-attachments-button-label = Allegati +pdfjs-layers-button = + .title = Visualizza livelli (doppio clic per ripristinare tutti i livelli allo stato predefinito) +pdfjs-layers-button-label = Livelli +pdfjs-thumbs-button = + .title = Mostra le miniature +pdfjs-thumbs-button-label = Miniature +pdfjs-current-outline-item-button = + .title = Trova elemento struttura corrente +pdfjs-current-outline-item-button-label = Elemento struttura corrente +pdfjs-findbar-button = + .title = Trova nel documento +pdfjs-findbar-button-label = Trova +pdfjs-additional-layers = Livelli aggiuntivi + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Pagina { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura della pagina { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Trova + .placeholder = Trova nel documento… +pdfjs-find-previous-button = + .title = Trova l’occorrenza precedente del testo da cercare +pdfjs-find-previous-button-label = Precedente +pdfjs-find-next-button = + .title = Trova l’occorrenza successiva del testo da cercare +pdfjs-find-next-button-label = Successivo +pdfjs-find-highlight-checkbox = Evidenzia +pdfjs-find-match-case-checkbox-label = Maiuscole/minuscole +pdfjs-find-match-diacritics-checkbox-label = Segni diacritici +pdfjs-find-entire-word-checkbox-label = Parole intere +pdfjs-find-reached-top = Raggiunto l’inizio della pagina, continua dalla fine +pdfjs-find-reached-bottom = Raggiunta la fine della pagina, continua dall’inizio +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } di { $total } corrispondenza + *[other] { $current } di { $total } corrispondenze + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Più di una { $limit } corrispondenza + *[other] Più di { $limit } corrispondenze + } +pdfjs-find-not-found = Testo non trovato + +## Predefined zoom values + +pdfjs-page-scale-width = Larghezza pagina +pdfjs-page-scale-fit = Adatta a una pagina +pdfjs-page-scale-auto = Zoom automatico +pdfjs-page-scale-actual = Dimensioni effettive +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Pagina { $page } + +## Loading indicator messages + +pdfjs-loading-error = Si è verificato un errore durante il caricamento del PDF. +pdfjs-invalid-file-error = File PDF non valido o danneggiato. +pdfjs-missing-file-error = File PDF non disponibile. +pdfjs-unexpected-response-error = Risposta imprevista del server +pdfjs-rendering-error = Si è verificato un errore durante il rendering della pagina. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Annotazione: { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Inserire la password per aprire questo file PDF. +pdfjs-password-invalid = Password non corretta. Riprovare. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Annulla +pdfjs-web-fonts-disabled = I web font risultano disattivati: impossibile utilizzare i caratteri incorporati nel PDF. + +## Editing + +pdfjs-editor-free-text-button = + .title = Testo +pdfjs-editor-free-text-button-label = Testo +pdfjs-editor-ink-button = + .title = Disegno +pdfjs-editor-ink-button-label = Disegno +pdfjs-editor-stamp-button = + .title = Aggiungi o rimuovi immagine +pdfjs-editor-stamp-button-label = Aggiungi o rimuovi immagine +pdfjs-editor-highlight-button = + .title = Evidenzia +pdfjs-editor-highlight-button-label = Evidenzia +pdfjs-highlight-floating-button1 = + .title = Evidenzia + .aria-label = Evidenzia +pdfjs-highlight-floating-button-label = Evidenzia + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Rimuovi disegno +pdfjs-editor-remove-freetext-button = + .title = Rimuovi testo +pdfjs-editor-remove-stamp-button = + .title = Rimuovi immagine +pdfjs-editor-remove-highlight-button = + .title = Rimuovi evidenziazione + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Colore +pdfjs-editor-free-text-size-input = Dimensione +pdfjs-editor-ink-color-input = Colore +pdfjs-editor-ink-thickness-input = Spessore +pdfjs-editor-ink-opacity-input = Opacità +pdfjs-editor-stamp-add-image-button = + .title = Aggiungi immagine +pdfjs-editor-stamp-add-image-button-label = Aggiungi immagine +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Spessore +pdfjs-editor-free-highlight-thickness-title = + .title = Modifica lo spessore della selezione per elementi non testuali +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Editor di testo + .default-content = Inizia a digitare… +pdfjs-free-text = + .aria-label = Editor di testo +pdfjs-free-text-default-content = Inizia a digitare… +pdfjs-ink = + .aria-label = Editor disegni +pdfjs-ink-canvas = + .aria-label = Immagine creata dall’utente + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Testo alternativo +pdfjs-editor-alt-text-edit-button = + .aria-label = Modifica testo alternativo +pdfjs-editor-alt-text-edit-button-label = Modifica testo alternativo +pdfjs-editor-alt-text-dialog-label = Scegli un’opzione +pdfjs-editor-alt-text-dialog-description = Il testo alternativo (“alt textâ€) aiuta quando le persone non possono vedere l’immagine o quando l’immagine non viene caricata. +pdfjs-editor-alt-text-add-description-label = Aggiungi una descrizione +pdfjs-editor-alt-text-add-description-description = Punta a una o due frasi che descrivono l’argomento, l’ambientazione o le azioni. +pdfjs-editor-alt-text-mark-decorative-label = Contrassegna come decorativa +pdfjs-editor-alt-text-mark-decorative-description = Viene utilizzato per immagini ornamentali, come bordi o filigrane. +pdfjs-editor-alt-text-cancel-button = Annulla +pdfjs-editor-alt-text-save-button = Salva +pdfjs-editor-alt-text-decorative-tooltip = Contrassegnata come decorativa +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Ad esempio, “Un giovane si siede a tavola per mangiare†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Testo alternativo + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Angolo in alto a sinistra — ridimensiona +pdfjs-editor-resizer-label-top-middle = Lato superiore nel mezzo — ridimensiona +pdfjs-editor-resizer-label-top-right = Angolo in alto a destra — ridimensiona +pdfjs-editor-resizer-label-middle-right = Lato destro nel mezzo — ridimensiona +pdfjs-editor-resizer-label-bottom-right = Angolo in basso a destra — ridimensiona +pdfjs-editor-resizer-label-bottom-middle = Lato inferiore nel mezzo — ridimensiona +pdfjs-editor-resizer-label-bottom-left = Angolo in basso a sinistra — ridimensiona +pdfjs-editor-resizer-label-middle-left = Lato sinistro nel mezzo — ridimensiona +pdfjs-editor-resizer-top-left = + .aria-label = Angolo in alto a sinistra — ridimensiona +pdfjs-editor-resizer-top-middle = + .aria-label = Lato superiore nel mezzo — ridimensiona +pdfjs-editor-resizer-top-right = + .aria-label = Angolo in alto a destra — ridimensiona +pdfjs-editor-resizer-middle-right = + .aria-label = Lato destro nel mezzo — ridimensiona +pdfjs-editor-resizer-bottom-right = + .aria-label = Angolo in basso a destra — ridimensiona +pdfjs-editor-resizer-bottom-middle = + .aria-label = Lato inferiore nel mezzo — ridimensiona +pdfjs-editor-resizer-bottom-left = + .aria-label = Angolo in basso a sinistra — ridimensiona +pdfjs-editor-resizer-middle-left = + .aria-label = Lato sinistro nel mezzo — ridimensiona + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Colore evidenziatore +pdfjs-editor-colorpicker-button = + .title = Cambia colore +pdfjs-editor-colorpicker-dropdown = + .aria-label = Colori disponibili +pdfjs-editor-colorpicker-yellow = + .title = Giallo +pdfjs-editor-colorpicker-green = + .title = Verde +pdfjs-editor-colorpicker-blue = + .title = Blu +pdfjs-editor-colorpicker-pink = + .title = Rosa +pdfjs-editor-colorpicker-red = + .title = Rosso + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Mostra tutto +pdfjs-editor-highlight-show-all-button = + .title = Mostra tutto + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Modifica testo alternativo (descrizione dell’immagine) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Aggiungi testo alternativo (descrizione dell’immagine) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Scrivi qui la tua descrizione… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Breve descrizione per le persone che non possono vedere l’immagine, o mostrata quando l’immagine non si carica. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Questo testo alternativo è stato creato automaticamente e potrebbe non essere accurato. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Ulteriori informazioni +pdfjs-editor-new-alt-text-create-automatically-button-label = Crea automaticamente testo alternativo +pdfjs-editor-new-alt-text-not-now-button = Non adesso +pdfjs-editor-new-alt-text-error-title = Impossibile creare automaticamente il testo alternativo +pdfjs-editor-new-alt-text-error-description = Scrivi il testo alternativo o riprova più tardi. +pdfjs-editor-new-alt-text-error-close-button = Chiudi +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Download in corso del modello IA per il testo alternativo ({ $downloadedSize } di { $totalSize } MB) + .aria-valuetext = Download in corso del modello IA per il testo alternativo ({ $downloadedSize } di { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Aggiunto testo alternativo +pdfjs-editor-new-alt-text-added-button-label = Aggiunto testo alternativo +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Testo alternativo mancante +pdfjs-editor-new-alt-text-missing-button-label = Testo alternativo mancante +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Verifica testo alternativo +pdfjs-editor-new-alt-text-to-review-button-label = Verifica testo alternativo +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creato automaticamente: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Impostazioni testo alternativo per le immagini +pdfjs-image-alt-text-settings-button-label = Impostazioni testo alternativo per le immagini +pdfjs-editor-alt-text-settings-dialog-label = Impostazioni testo alternativo per le immagini +pdfjs-editor-alt-text-settings-automatic-title = Testo alternativo automatico +pdfjs-editor-alt-text-settings-create-model-button-label = Crea testo alternativo automaticamente +pdfjs-editor-alt-text-settings-create-model-description = Suggerisce una descrizione per le persone che non possono vedere l’immagine, o mostrata quando l’immagine non si carica. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modello IA per il testo alternativo ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Viene eseguito localmente sul tuo dispositivo in modo che i tuoi dati rimangano riservati. È richiesto per la generazione automatica del testo alternativo. +pdfjs-editor-alt-text-settings-delete-model-button = Elimina +pdfjs-editor-alt-text-settings-download-model-button = Scarica +pdfjs-editor-alt-text-settings-downloading-model-button = Download… +pdfjs-editor-alt-text-settings-editor-title = Modifica testo alternativo +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostra l’editor del testo alternativo non appena si aggiunge un’immagine +pdfjs-editor-alt-text-settings-show-dialog-description = Ti aiuta ad assicurarti che tutte le tue immagini abbiano il testo alternativo. +pdfjs-editor-alt-text-settings-close-button = Chiudi + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Evidenziazione rimossa +pdfjs-editor-undo-bar-message-freetext = Testo rimosso +pdfjs-editor-undo-bar-message-ink = Disegno rimosso +pdfjs-editor-undo-bar-message-stamp = Immagine rimossa +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } annotazione rimossa + *[other] { $count } annotazioni rimosse + } +pdfjs-editor-undo-bar-undo-button = + .title = Annulla +pdfjs-editor-undo-bar-undo-button-label = Annulla +pdfjs-editor-undo-bar-close-button = + .title = Chiudi +pdfjs-editor-undo-bar-close-button-label = Chiudi diff --git a/public/assets/pdfjs/locale/ja/viewer.ftl b/public/assets/pdfjs/locale/ja/viewer.ftl new file mode 100755 index 0000000..0f37f2a --- /dev/null +++ b/public/assets/pdfjs/locale/ja/viewer.ftl @@ -0,0 +1,503 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = å‰ã®ãƒšãƒ¼ã‚¸ã¸æˆ»ã‚Šã¾ã™ +pdfjs-previous-button-label = å‰ã¸ +pdfjs-next-button = + .title = 次ã®ãƒšãƒ¼ã‚¸ã¸é€²ã¿ã¾ã™ +pdfjs-next-button-label = 次㸠+# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = ページ +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = / { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } / { $pagesCount }) +pdfjs-zoom-out-button = + .title = 表示を縮å°ã—ã¾ã™ +pdfjs-zoom-out-button-label = ç¸®å° +pdfjs-zoom-in-button = + .title = 表示を拡大ã—ã¾ã™ +pdfjs-zoom-in-button-label = 拡大 +pdfjs-zoom-select = + .title = 拡大/ç¸®å° +pdfjs-presentation-mode-button = + .title = プレゼンテーションモードã«åˆ‡ã‚Šæ›¿ãˆã¾ã™ +pdfjs-presentation-mode-button-label = プレゼンテーションモード +pdfjs-open-file-button = + .title = ファイルを開ãã¾ã™ +pdfjs-open-file-button-label = é–‹ã +pdfjs-print-button = + .title = å°åˆ·ã—ã¾ã™ +pdfjs-print-button-label = å°åˆ· +pdfjs-save-button = + .title = ä¿å­˜ã—ã¾ã™ +pdfjs-save-button-label = ä¿å­˜ +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = ダウンロードã—ã¾ã™ +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = ダウンロード +pdfjs-bookmark-button = + .title = ç¾åœ¨ã®ãƒšãƒ¼ã‚¸ã® URL ã§ã™ (ç¾åœ¨ã®ãƒšãƒ¼ã‚¸ã‚’表示ã™ã‚‹ URL) +pdfjs-bookmark-button-label = ç¾åœ¨ã®ãƒšãƒ¼ã‚¸ + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = ツール +pdfjs-tools-button-label = ツール +pdfjs-first-page-button = + .title = 最åˆã®ãƒšãƒ¼ã‚¸ã¸ç§»å‹•ã—ã¾ã™ +pdfjs-first-page-button-label = 最åˆã®ãƒšãƒ¼ã‚¸ã¸ç§»å‹• +pdfjs-last-page-button = + .title = 最後ã®ãƒšãƒ¼ã‚¸ã¸ç§»å‹•ã—ã¾ã™ +pdfjs-last-page-button-label = 最後ã®ãƒšãƒ¼ã‚¸ã¸ç§»å‹• +pdfjs-page-rotate-cw-button = + .title = ページをå³ã¸å›žè»¢ã—ã¾ã™ +pdfjs-page-rotate-cw-button-label = å³å›žè»¢ +pdfjs-page-rotate-ccw-button = + .title = ページを左ã¸å›žè»¢ã—ã¾ã™ +pdfjs-page-rotate-ccw-button-label = 左回転 +pdfjs-cursor-text-select-tool-button = + .title = ãƒ†ã‚­ã‚¹ãƒˆé¸æŠžãƒ„ãƒ¼ãƒ«ã‚’æœ‰åŠ¹ã«ã—ã¾ã™ +pdfjs-cursor-text-select-tool-button-label = ãƒ†ã‚­ã‚¹ãƒˆé¸æŠžãƒ„ãƒ¼ãƒ« +pdfjs-cursor-hand-tool-button = + .title = 手ã®ã²ã‚‰ãƒ„ールを有効ã«ã—ã¾ã™ +pdfjs-cursor-hand-tool-button-label = 手ã®ã²ã‚‰ãƒ„ール +pdfjs-scroll-page-button = + .title = ページå˜ä½ã§ã‚¹ã‚¯ãƒ­ãƒ¼ãƒ«ã—ã¾ã™ +pdfjs-scroll-page-button-label = ページå˜ä½ã§ã‚¹ã‚¯ãƒ­ãƒ¼ãƒ« +pdfjs-scroll-vertical-button = + .title = 縦スクロールã«ã—ã¾ã™ +pdfjs-scroll-vertical-button-label = 縦スクロール +pdfjs-scroll-horizontal-button = + .title = 横スクロールã«ã—ã¾ã™ +pdfjs-scroll-horizontal-button-label = 横スクロール +pdfjs-scroll-wrapped-button = + .title = 折り返ã—スクロールã«ã—ã¾ã™ +pdfjs-scroll-wrapped-button-label = 折り返ã—スクロール +pdfjs-spread-none-button = + .title = 見開ãã«ã—ã¾ã›ã‚“ +pdfjs-spread-none-button-label = 見開ãã«ã—ãªã„ +pdfjs-spread-odd-button = + .title = 奇数ページ開始ã§è¦‹é–‹ãã«ã—ã¾ã™ +pdfjs-spread-odd-button-label = 奇数ページ見開ã +pdfjs-spread-even-button = + .title = å¶æ•°ãƒšãƒ¼ã‚¸é–‹å§‹ã§è¦‹é–‹ãã«ã—ã¾ã™ +pdfjs-spread-even-button-label = å¶æ•°ãƒšãƒ¼ã‚¸è¦‹é–‹ã + +## Document properties dialog + +pdfjs-document-properties-button = + .title = 文書ã®ãƒ—ロパティ... +pdfjs-document-properties-button-label = 文書ã®ãƒ—ロパティ... +pdfjs-document-properties-file-name = ファイルå: +pdfjs-document-properties-file-size = ファイルサイズ: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } ãƒã‚¤ãƒˆ) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } ãƒã‚¤ãƒˆ) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } ãƒã‚¤ãƒˆ) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } ãƒã‚¤ãƒˆ) +pdfjs-document-properties-title = タイトル: +pdfjs-document-properties-author = 作æˆè€…: +pdfjs-document-properties-subject = ä»¶å: +pdfjs-document-properties-keywords = キーワード: +pdfjs-document-properties-creation-date = ä½œæˆæ—¥: +pdfjs-document-properties-modification-date = æ›´æ–°æ—¥: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = アプリケーション: +pdfjs-document-properties-producer = PDF 作æˆ: +pdfjs-document-properties-version = PDF ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³: +pdfjs-document-properties-page-count = ページ数: +pdfjs-document-properties-page-size = ページサイズ: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = 縦 +pdfjs-document-properties-page-size-orientation-landscape = 横 +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = レター +pdfjs-document-properties-page-size-name-legal = リーガル + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = ã‚¦ã‚§ãƒ–è¡¨ç¤ºç”¨ã«æœ€é©åŒ–: +pdfjs-document-properties-linearized-yes = ã¯ã„ +pdfjs-document-properties-linearized-no = ã„ã„㈠+pdfjs-document-properties-close-button = é–‰ã˜ã‚‹ + +## Print + +pdfjs-print-progress-message = 文書ã®å°åˆ·ã‚’準備ã—ã¦ã„ã¾ã™... +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = キャンセル +pdfjs-printing-not-supported = 警告: ã“ã®ãƒ–ラウザーã§ã¯å°åˆ·ãŒå®Œå…¨ã«ã‚µãƒãƒ¼ãƒˆã•れã¦ã„ã¾ã›ã‚“。 +pdfjs-printing-not-ready = 警告: PDF ã‚’å°åˆ·ã™ã‚‹ãŸã‚ã®èª­ã¿è¾¼ã¿ãŒçµ‚了ã—ã¦ã„ã¾ã›ã‚“。 + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = サイドãƒãƒ¼è¡¨ç¤ºã‚’切り替ãˆã¾ã™ +pdfjs-toggle-sidebar-notification-button = + .title = サイドãƒãƒ¼è¡¨ç¤ºã‚’切り替ãˆã¾ã™ (文書ã«å«ã¾ã‚Œã‚‹ã‚¢ã‚¦ãƒˆãƒ©ã‚¤ãƒ³ / 添付 / レイヤー) +pdfjs-toggle-sidebar-button-label = サイドãƒãƒ¼ã®åˆ‡ã‚Šæ›¿ãˆ +pdfjs-document-outline-button = + .title = 文書ã®ç›®æ¬¡ã‚’表示ã—ã¾ã™ (ダブルクリックã§é …目を開閉ã—ã¾ã™) +pdfjs-document-outline-button-label = 文書ã®ç›®æ¬¡ +pdfjs-attachments-button = + .title = 添付ファイルを表示ã—ã¾ã™ +pdfjs-attachments-button-label = 添付ファイル +pdfjs-layers-button = + .title = レイヤーを表示ã—ã¾ã™ (ダブルクリックã§ã™ã¹ã¦ã®ãƒ¬ã‚¤ãƒ¤ãƒ¼ãŒåˆæœŸçŠ¶æ…‹ã«æˆ»ã‚Šã¾ã™) +pdfjs-layers-button-label = レイヤー +pdfjs-thumbs-button = + .title = 縮å°ç‰ˆã‚’表示ã—ã¾ã™ +pdfjs-thumbs-button-label = 縮å°ç‰ˆ +pdfjs-current-outline-item-button = + .title = ç¾åœ¨ã®ã‚¢ã‚¦ãƒˆãƒ©ã‚¤ãƒ³é …目を検索 +pdfjs-current-outline-item-button-label = ç¾åœ¨ã®ã‚¢ã‚¦ãƒˆãƒ©ã‚¤ãƒ³é …ç›® +pdfjs-findbar-button = + .title = 文書内を検索ã—ã¾ã™ +pdfjs-findbar-button-label = 検索 +pdfjs-additional-layers = 追加レイヤー + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = { $page } ページ +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = { $page } ページã®ç¸®å°ç‰ˆ + +## Find panel button title and messages + +pdfjs-find-input = + .title = 検索 + .placeholder = 文書内を検索... +pdfjs-find-previous-button = + .title = ç¾åœ¨ã‚ˆã‚Šå‰ã®ä½ç½®ã§æŒ‡å®šæ–‡å­—列ãŒç¾ã‚Œã‚‹éƒ¨åˆ†ã‚’検索ã—ã¾ã™ +pdfjs-find-previous-button-label = å‰ã¸ +pdfjs-find-next-button = + .title = ç¾åœ¨ã‚ˆã‚Šå¾Œã®ä½ç½®ã§æŒ‡å®šæ–‡å­—列ãŒç¾ã‚Œã‚‹éƒ¨åˆ†ã‚’検索ã—ã¾ã™ +pdfjs-find-next-button-label = 次㸠+pdfjs-find-highlight-checkbox = ã™ã¹ã¦å¼·èª¿è¡¨ç¤º +pdfjs-find-match-case-checkbox-label = 大文字/å°æ–‡å­—を区別 +pdfjs-find-match-diacritics-checkbox-label = 発音区別符å·ã‚’区別 +pdfjs-find-entire-word-checkbox-label = å˜èªžä¸€è‡´ +pdfjs-find-reached-top = 文書先頭ã«åˆ°é”ã—ãŸã®ã§æœ«å°¾ã‹ã‚‰ç¶šã‘ã¦æ¤œç´¢ã—ã¾ã™ +pdfjs-find-reached-bottom = 文書末尾ã«åˆ°é”ã—ãŸã®ã§å…ˆé ­ã‹ã‚‰ç¶šã‘ã¦æ¤œç´¢ã—ã¾ã™ +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = { $total } 件中 { $current } ä»¶ç›® +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = { $limit } 件以上一致 +pdfjs-find-not-found = 見ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—㟠+ +## Predefined zoom values + +pdfjs-page-scale-width = å¹…ã«åˆã‚ã›ã‚‹ +pdfjs-page-scale-fit = ページã®ã‚µã‚¤ã‚ºã«åˆã‚ã›ã‚‹ +pdfjs-page-scale-auto = 自動ズーム +pdfjs-page-scale-actual = 実際ã®ã‚µã‚¤ã‚º +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = { $page } ページ + +## Loading indicator messages + +pdfjs-loading-error = PDF ã®èª­ã¿è¾¼ã¿ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ +pdfjs-invalid-file-error = 無効ã¾ãŸã¯ç ´æã—㟠PDF ファイル。 +pdfjs-missing-file-error = PDF ファイルãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。 +pdfjs-unexpected-response-error = サーãƒãƒ¼ã‹ã‚‰äºˆæœŸã›ã¬å¿œç­”ãŒã‚りã¾ã—ãŸã€‚ +pdfjs-rendering-error = ページã®ãƒ¬ãƒ³ãƒ€ãƒªãƒ³ã‚°ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } 注釈] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = ã“ã® PDF ファイルを開ããŸã‚ã®ãƒ‘スワードを入力ã—ã¦ãã ã•ã„。 +pdfjs-password-invalid = ãƒ‘ã‚¹ãƒ¯ãƒ¼ãƒ‰ãŒæ­£ã—ãã‚りã¾ã›ã‚“。もã†ä¸€åº¦è©¦ã—ã¦ãã ã•ã„。 +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = キャンセル +pdfjs-web-fonts-disabled = ウェブフォントãŒç„¡åйã«ãªã£ã¦ã„ã¾ã™: 埋ã‚è¾¼ã¾ã‚ŒãŸ PDF ã®ãƒ•ォントを使用ã§ãã¾ã›ã‚“。 + +## Editing + +pdfjs-editor-free-text-button = + .title = フリーテキスト注釈を追加ã—ã¾ã™ +pdfjs-editor-free-text-button-label = フリーテキスト注釈 +pdfjs-editor-ink-button = + .title = インク注釈を追加ã—ã¾ã™ +pdfjs-editor-ink-button-label = インク注釈 +pdfjs-editor-stamp-button = + .title = ç”»åƒã‚’追加ã¾ãŸã¯ç·¨é›†ã—ã¾ã™ +pdfjs-editor-stamp-button-label = ç”»åƒã‚’追加ã¾ãŸã¯ç·¨é›† +pdfjs-editor-highlight-button = + .title = 強調ã—ã¾ã™ +pdfjs-editor-highlight-button-label = 強調 +pdfjs-highlight-floating-button1 = + .title = 強調 + .aria-label = 強調ã—ã¾ã™ +pdfjs-highlight-floating-button-label = 強調 + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = インク注釈を削除ã—ã¾ã™ +pdfjs-editor-remove-freetext-button = + .title = テキストを削除ã—ã¾ã™ +pdfjs-editor-remove-stamp-button = + .title = ç”»åƒã‚’削除ã—ã¾ã™ +pdfjs-editor-remove-highlight-button = + .title = 強調を削除ã—ã¾ã™ + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = 色 +pdfjs-editor-free-text-size-input = サイズ +pdfjs-editor-ink-color-input = 色 +pdfjs-editor-ink-thickness-input = 太㕠+pdfjs-editor-ink-opacity-input = ä¸é€æ˜Žåº¦ +pdfjs-editor-stamp-add-image-button = + .title = ç”»åƒã‚’追加ã—ã¾ã™ +pdfjs-editor-stamp-add-image-button-label = ç”»åƒã‚’追加 +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = 太㕠+pdfjs-editor-free-highlight-thickness-title = + .title = テキスト以外ã®ã‚¢ã‚¤ãƒ†ãƒ ã‚’強調ã™ã‚‹æ™‚ã®å¤ªã•を変更ã—ã¾ã™ +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = フリーテキスト注釈エディター + .default-content = テキストを入力ã—ã¦ãã ã•ã„... +pdfjs-free-text = + .aria-label = フリーテキスト注釈エディター +pdfjs-free-text-default-content = テキストを入力ã—ã¦ãã ã•ã„... +pdfjs-ink = + .aria-label = インク注釈エディター +pdfjs-ink-canvas = + .aria-label = ユーザー作æˆç”»åƒ + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = 代替テキスト +pdfjs-editor-alt-text-edit-button = + .aria-label = 代替テキストを編集 +pdfjs-editor-alt-text-edit-button-label = 代替テキストを編集 +pdfjs-editor-alt-text-dialog-label = オプションã®é¸æŠž +pdfjs-editor-alt-text-dialog-description = 代替テキストã¯ç”»åƒãŒè¡¨ç¤ºã•れãªã„å ´åˆã‚„読ã¿è¾¼ã¾ã‚Œãªã„å ´åˆã«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®åŠ©ã‘ã«ãªã‚Šã¾ã™ã€‚ +pdfjs-editor-alt-text-add-description-label = 説明を追加 +pdfjs-editor-alt-text-add-description-description = 対象や設定ã€å‹•作を説明ã™ã‚‹çŸ­ã„文章を記入ã—ã¦ãã ã•ã„。 +pdfjs-editor-alt-text-mark-decorative-label = 装飾マークを付ã‘ã‚‹ +pdfjs-editor-alt-text-mark-decorative-description = ã“れã¯åŒºåˆ‡ã‚Šç·šã‚„ウォーターマークãªã©ã®è£…飾画åƒã«ä½¿ç”¨ã•れã¾ã™ã€‚ +pdfjs-editor-alt-text-cancel-button = キャンセル +pdfjs-editor-alt-text-save-button = ä¿å­˜ +pdfjs-editor-alt-text-decorative-tooltip = 装飾マークãŒä»˜ã„ã¦ã„ã¾ã™ +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = 例:「若ã„人ãŒãƒ†ãƒ¼ãƒ–ルã®å¸­ã«ã¤ã„ã¦é£Ÿäº‹ã‚’ã—ã¦ã„ã¾ã™ã€ +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = 代替テキスト + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = 左上隅 — サイズ変更 +pdfjs-editor-resizer-label-top-middle = 上中央 — サイズ変更 +pdfjs-editor-resizer-label-top-right = å³ä¸Šéš… — サイズ変更 +pdfjs-editor-resizer-label-middle-right = å³ä¸­å¤® — サイズ変更 +pdfjs-editor-resizer-label-bottom-right = å³ä¸‹éš… — サイズ変更 +pdfjs-editor-resizer-label-bottom-middle = 下中央 — サイズ変更 +pdfjs-editor-resizer-label-bottom-left = 左下隅 — サイズ変更 +pdfjs-editor-resizer-label-middle-left = 左中央 — サイズ変更 +pdfjs-editor-resizer-top-left = + .aria-label = 左上隅 — サイズ変更 +pdfjs-editor-resizer-top-middle = + .aria-label = 上中央 — サイズ変更 +pdfjs-editor-resizer-top-right = + .aria-label = å³ä¸Šéš… — サイズ変更 +pdfjs-editor-resizer-middle-right = + .aria-label = å³ä¸­å¤® — サイズ変更 +pdfjs-editor-resizer-bottom-right = + .aria-label = å³ä¸‹éš… — サイズ変更 +pdfjs-editor-resizer-bottom-middle = + .aria-label = 下中央 — サイズ変更 +pdfjs-editor-resizer-bottom-left = + .aria-label = 左下隅 — サイズ変更 +pdfjs-editor-resizer-middle-left = + .aria-label = 左中央 — サイズ変更 + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = 強調色 +pdfjs-editor-colorpicker-button = + .title = 色を変更ã—ã¾ã™ +pdfjs-editor-colorpicker-dropdown = + .aria-label = 色ã®é¸æŠž +pdfjs-editor-colorpicker-yellow = + .title = 黄色 +pdfjs-editor-colorpicker-green = + .title = 緑色 +pdfjs-editor-colorpicker-blue = + .title = é’色 +pdfjs-editor-colorpicker-pink = + .title = ピンク色 +pdfjs-editor-colorpicker-red = + .title = 赤色 + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = ã™ã¹ã¦è¡¨ç¤º +# (^m^) en-US: .title = Show all +pdfjs-editor-highlight-show-all-button = + .title = 強調ã®è¡¨ç¤ºã‚’切り替ãˆã¾ã™ + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = 代替テキストを編集 (ç”»åƒã®èª¬æ˜Ž) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = 代替テキストを追加 (ç”»åƒã®èª¬æ˜Ž) +pdfjs-editor-new-alt-text-textarea = + .placeholder = ã“ã“ã«èª¬æ˜Žã‚’記入ã—ã¦ãã ã•ã„... +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = ç”»åƒãŒèª­ã¿è¾¼ã¾ã‚Œãªã„å ´åˆã‚„見ãˆãªã„人ã®ãŸã‚ã®çŸ­ã„説明ã§ã™ã€‚ +pdfjs-editor-new-alt-text-disclaimer1 = ã“ã®ä»£æ›¿ãƒ†ã‚­ã‚¹ãƒˆã¯è‡ªå‹•çš„ã«ç”Ÿæˆã•れãŸãŸã‚正確ã§ãªã„å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚ +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = 詳細情報 +pdfjs-editor-new-alt-text-create-automatically-button-label = ä»£æ›¿ãƒ†ã‚­ã‚¹ãƒˆã‚’è‡ªå‹•ç”Ÿæˆ +pdfjs-editor-new-alt-text-not-now-button = 後㧠+pdfjs-editor-new-alt-text-error-title = 代替テキストを自動生æˆã§ãã¾ã›ã‚“ã§ã—㟠+pdfjs-editor-new-alt-text-error-description = ã”自分ã§ä»£æ›¿ãƒ†ã‚­ã‚¹ãƒˆã‚’書ãã‹å¾Œã§ã‚‚ã†ä¸€åº¦è©¦ã—ã¦ãã ã•ã„。 +pdfjs-editor-new-alt-text-error-close-button = é–‰ã˜ã‚‹ +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = 代替テキスト AI モデルをダウンロードã—ã¦ã„ã¾ã™ ({ $downloadedSize } / { $totalSize } MB) + .aria-valuetext = 代替テキスト AI モデルをダウンロードã—ã¦ã„ã¾ã™ ({ $downloadedSize } / { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = 代替テキストを追加ã—ã¾ã—㟠+pdfjs-editor-new-alt-text-added-button-label = 代替テキストを追加ã—ã¾ã—㟠+# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = 代替テキストãŒã‚りã¾ã›ã‚“ +pdfjs-editor-new-alt-text-missing-button-label = 代替テキストãŒã‚りã¾ã›ã‚“ +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = 代替テキストをレビュー +pdfjs-editor-new-alt-text-to-review-button-label = 代替テキストをレビュー +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = 自動生æˆã•れã¾ã—ãŸ: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = ç”»åƒã®ä»£æ›¿ãƒ†ã‚­ã‚¹ãƒˆè¨­å®š +pdfjs-image-alt-text-settings-button-label = ç”»åƒã®ä»£æ›¿ãƒ†ã‚­ã‚¹ãƒˆè¨­å®š +pdfjs-editor-alt-text-settings-dialog-label = ç”»åƒã®ä»£æ›¿ãƒ†ã‚­ã‚¹ãƒˆè¨­å®š +pdfjs-editor-alt-text-settings-automatic-title = 自動代替テキスト +pdfjs-editor-alt-text-settings-create-model-button-label = ä»£æ›¿ãƒ†ã‚­ã‚¹ãƒˆã‚’è‡ªå‹•ç”Ÿæˆ +pdfjs-editor-alt-text-settings-create-model-description = ç”»åƒãŒèª­ã¿è¾¼ã¾ã‚Œãªã„å ´åˆã‚„見ãˆãªã„人ã®ãŸã‚ã«èª¬æ˜Žã‚’ææ¡ˆã—ã¾ã™ã€‚ +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = 代替テキスト AI モデル ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = ローカルã®ç«¯æœ«ä¸Šã§å®Ÿè¡Œã•れるãŸã‚データã¯éžå…¬é–‹ã«ãªã‚Šã¾ã™ã€‚代替テキストã®è‡ªå‹•生æˆã«å¿…è¦ã§ã™ã€‚ +pdfjs-editor-alt-text-settings-delete-model-button = 削除 +pdfjs-editor-alt-text-settings-download-model-button = ダウンロード +pdfjs-editor-alt-text-settings-downloading-model-button = ダウンロード中... +pdfjs-editor-alt-text-settings-editor-title = 代替テキストエディター +pdfjs-editor-alt-text-settings-show-dialog-button-label = ç”»åƒã®è¿½åŠ æ™‚ã«ä»£æ›¿ãƒ†ã‚­ã‚¹ãƒˆã‚¨ãƒ‡ã‚£ã‚¿ãƒ¼ã‚’表示ã™ã‚‹ +pdfjs-editor-alt-text-settings-show-dialog-description = ã™ã¹ã¦ã®ç”»åƒã«ä»£æ›¿ãƒ†ã‚­ã‚¹ãƒˆã‚’追加ã™ã‚‹åŠ©ã‘ã«ãªã‚Šã¾ã™ã€‚ +pdfjs-editor-alt-text-settings-close-button = é–‰ã˜ã‚‹ + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = 強調表示ãŒå‰Šé™¤ã•れã¾ã—㟠+pdfjs-editor-undo-bar-message-freetext = フリーテキスト注釈ãŒå‰Šé™¤ã•れã¾ã—㟠+pdfjs-editor-undo-bar-message-ink = インク注釈ãŒå‰Šé™¤ã•れã¾ã—㟠+pdfjs-editor-undo-bar-message-stamp = ç”»åƒãŒå‰Šé™¤ã•れã¾ã—㟠+# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = { $count } å€‹ã®æ³¨é‡ˆãŒå‰Šé™¤ã•れã¾ã—㟠+pdfjs-editor-undo-bar-undo-button = + .title = å…ƒã«æˆ»ã™ +pdfjs-editor-undo-bar-undo-button-label = å…ƒã«æˆ»ã™ +pdfjs-editor-undo-bar-close-button = + .title = é–‰ã˜ã‚‹ +pdfjs-editor-undo-bar-close-button-label = é–‰ã˜ã‚‹ diff --git a/public/assets/pdfjs/locale/ka/viewer.ftl b/public/assets/pdfjs/locale/ka/viewer.ftl new file mode 100755 index 0000000..d500f3e --- /dev/null +++ b/public/assets/pdfjs/locale/ka/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = წინრგვერდი +pdfjs-previous-button-label = წინრ+pdfjs-next-button = + .title = შემდეგი გვერდი +pdfjs-next-button-label = შემდეგი +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = გვერდი +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = { $pagesCount }-დáƒáƒœ +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } { $pagesCount }-დáƒáƒœ) +pdfjs-zoom-out-button = + .title = ზáƒáƒ›áƒ˜áƒ¡ შემცირებრ+pdfjs-zoom-out-button-label = დáƒáƒ¨áƒáƒ áƒ”ბრ+pdfjs-zoom-in-button = + .title = ზáƒáƒ›áƒ˜áƒ¡ გáƒáƒ–რდრ+pdfjs-zoom-in-button-label = მáƒáƒáƒ®áƒšáƒáƒ”ბრ+pdfjs-zoom-select = + .title = ზáƒáƒ›áƒ +pdfjs-presentation-mode-button = + .title = წáƒáƒ áƒ“გენის რეჟიმზე გáƒáƒ“áƒáƒ áƒ—ვრ+pdfjs-presentation-mode-button-label = წáƒáƒ áƒ“გენის რეჟიმი +pdfjs-open-file-button = + .title = ფáƒáƒ˜áƒšáƒ˜áƒ¡ გáƒáƒ®áƒ¡áƒœáƒ +pdfjs-open-file-button-label = გáƒáƒ®áƒ¡áƒœáƒ +pdfjs-print-button = + .title = áƒáƒ›áƒáƒ‘ეჭდვრ+pdfjs-print-button-label = áƒáƒ›áƒáƒ‘ეჭდვრ+pdfjs-save-button = + .title = შენáƒáƒ®áƒ•რ+pdfjs-save-button-label = შენáƒáƒ®áƒ•რ+# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = ჩáƒáƒ›áƒáƒ¢áƒ•ირთვრ+# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = ჩáƒáƒ›áƒáƒ¢áƒ•ირთვრ+pdfjs-bookmark-button = + .title = მიმდინáƒáƒ áƒ” გვერდი (ბმული áƒáƒ› გვერდისთვის) +pdfjs-bookmark-button-label = მიმდინáƒáƒ áƒ” გვერდი + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = ხელსáƒáƒ¬áƒ§áƒáƒ”ბი +pdfjs-tools-button-label = ხელსáƒáƒ¬áƒ§áƒáƒ”ბი +pdfjs-first-page-button = + .title = პირველ გვერდზე გáƒáƒ“áƒáƒ¡áƒ•ლრ+pdfjs-first-page-button-label = პირველ გვერდზე გáƒáƒ“áƒáƒ¡áƒ•ლრ+pdfjs-last-page-button = + .title = ბáƒáƒšáƒ გვერდზე გáƒáƒ“áƒáƒ¡áƒ•ლრ+pdfjs-last-page-button-label = ბáƒáƒšáƒ გვერდზე გáƒáƒ“áƒáƒ¡áƒ•ლრ+pdfjs-page-rotate-cw-button = + .title = სáƒáƒáƒ—ის ისრის მიმáƒáƒ áƒ—ულებით შებრუნებრ+pdfjs-page-rotate-cw-button-label = მáƒáƒ áƒ¯áƒ•ნივ გáƒáƒ“áƒáƒ‘რუნებრ+pdfjs-page-rotate-ccw-button = + .title = სáƒáƒáƒ—ის ისრის სáƒáƒžáƒ˜áƒ áƒ˜áƒ¡áƒžáƒ˜áƒ áƒáƒ“ შებრუნებრ+pdfjs-page-rotate-ccw-button-label = მáƒáƒ áƒªáƒ®áƒœáƒ˜áƒ• გáƒáƒ“áƒáƒ‘რუნებრ+pdfjs-cursor-text-select-tool-button = + .title = მáƒáƒ¡áƒáƒœáƒ˜áƒ¨áƒœáƒ˜ მáƒáƒ©áƒ•ენებლის გáƒáƒ›áƒáƒ§áƒ”ნებრ+pdfjs-cursor-text-select-tool-button-label = მáƒáƒ¡áƒáƒœáƒ˜áƒ¨áƒœáƒ˜ მáƒáƒ©áƒ•ენებელი +pdfjs-cursor-hand-tool-button = + .title = გáƒáƒ“áƒáƒ¡áƒáƒáƒ“გილებელი მáƒáƒ©áƒ•ენებლის გáƒáƒ›áƒáƒ§áƒ”ნებრ+pdfjs-cursor-hand-tool-button-label = გáƒáƒ“áƒáƒ¡áƒáƒáƒ“გილებელი +pdfjs-scroll-page-button = + .title = გვერდზე გáƒáƒ“áƒáƒáƒ“გილების გáƒáƒ›áƒáƒ§áƒ”ნებრ+pdfjs-scroll-page-button-label = გვერდშივე გáƒáƒ“áƒáƒáƒ“გილებრ+pdfjs-scroll-vertical-button = + .title = გვერდების შვეულáƒáƒ“ ჩვენებრ+pdfjs-scroll-vertical-button-label = შვეული გáƒáƒ“áƒáƒáƒ“გილებრ+pdfjs-scroll-horizontal-button = + .title = გვერდების თáƒáƒ áƒáƒ–ულáƒáƒ“ ჩვენებრ+pdfjs-scroll-horizontal-button-label = გáƒáƒœáƒ˜áƒ•ი გáƒáƒ“áƒáƒáƒ“გილებრ+pdfjs-scroll-wrapped-button = + .title = გვერდების ცხრილურáƒáƒ“ ჩვენებრ+pdfjs-scroll-wrapped-button-label = ცხრილური გáƒáƒ“áƒáƒáƒ“გილებრ+pdfjs-spread-none-button = + .title = áƒáƒ  გვერდზე გáƒáƒ¨áƒšáƒ˜áƒ¡ გáƒáƒ áƒ”შე +pdfjs-spread-none-button-label = ცáƒáƒšáƒ’ვერდიáƒáƒœáƒ˜ ჩვენებრ+pdfjs-spread-odd-button = + .title = áƒáƒ  გვერდზე გáƒáƒ¨áƒšáƒ კენტი გვერდიდáƒáƒœ +pdfjs-spread-odd-button-label = áƒáƒ  გვერდზე კენტიდáƒáƒœ +pdfjs-spread-even-button = + .title = áƒáƒ  გვერდზე გáƒáƒ¨áƒšáƒ ლუწი გვერდიდáƒáƒœ +pdfjs-spread-even-button-label = áƒáƒ  გვერდზე ლუწიდáƒáƒœ + +## Document properties dialog + +pdfjs-document-properties-button = + .title = დáƒáƒ™áƒ£áƒ›áƒ”ნტის შესáƒáƒ®áƒ”ბ… +pdfjs-document-properties-button-label = დáƒáƒ™áƒ£áƒ›áƒ”ნტის შესáƒáƒ®áƒ”ბ… +pdfjs-document-properties-file-name = ფáƒáƒ˜áƒšáƒ˜áƒ¡ სáƒáƒ®áƒ”ლი: +pdfjs-document-properties-file-size = ფáƒáƒ˜áƒšáƒ˜áƒ¡ მáƒáƒªáƒ£áƒšáƒáƒ‘áƒ: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } კბáƒáƒ˜áƒ¢áƒ˜ ({ $b } ბáƒáƒ˜áƒ¢áƒ˜) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } მბáƒáƒ˜áƒ¢áƒ˜ ({ $b } ბáƒáƒ˜áƒ¢áƒ˜) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } კბ ({ $size_b } ბáƒáƒ˜áƒ¢áƒ˜) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } მბ ({ $size_b } ბáƒáƒ˜áƒ¢áƒ˜) +pdfjs-document-properties-title = სáƒáƒ—áƒáƒ£áƒ áƒ˜: +pdfjs-document-properties-author = შემქმნელი: +pdfjs-document-properties-subject = თემáƒ: +pdfjs-document-properties-keywords = სáƒáƒ™áƒ•áƒáƒœáƒ«áƒ სიტყვები: +pdfjs-document-properties-creation-date = შექმნის დრáƒ: +pdfjs-document-properties-modification-date = ჩáƒáƒ¡áƒ¬áƒáƒ áƒ”ბის დრáƒ: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = შემდგენელი: +pdfjs-document-properties-producer = PDF-შემდგენელი: +pdfjs-document-properties-version = PDF-ვერსიáƒ: +pdfjs-document-properties-page-count = გვერდები: +pdfjs-document-properties-page-size = გვერდის ზáƒáƒ›áƒ: +pdfjs-document-properties-page-size-unit-inches = დუიმი +pdfjs-document-properties-page-size-unit-millimeters = მმ +pdfjs-document-properties-page-size-orientation-portrait = შვეულáƒáƒ“ +pdfjs-document-properties-page-size-orientation-landscape = თáƒáƒ áƒáƒ–ულáƒáƒ“ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = მსუბუქი ვებჩვენებáƒ: +pdfjs-document-properties-linearized-yes = დიáƒáƒ® +pdfjs-document-properties-linearized-no = áƒáƒ áƒ +pdfjs-document-properties-close-button = დáƒáƒ®áƒ£áƒ áƒ•რ+ +## Print + +pdfjs-print-progress-message = დáƒáƒ™áƒ£áƒ›áƒ”ნტი მზáƒáƒ“დებრáƒáƒ›áƒáƒ¡áƒáƒ‘ეჭდáƒáƒ“… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = გáƒáƒ£áƒ¥áƒ›áƒ”ბრ+pdfjs-printing-not-supported = გáƒáƒ¤áƒ áƒ—ხილებáƒ: áƒáƒ›áƒáƒ‘ეჭდვრáƒáƒ› ბრáƒáƒ£áƒ–ერში áƒáƒ áƒáƒ სრულáƒáƒ“ მხáƒáƒ áƒ“áƒáƒ­áƒ”რილი. +pdfjs-printing-not-ready = გáƒáƒ¤áƒ áƒ—ხილებáƒ: PDF სრულáƒáƒ“ ჩáƒáƒ¢áƒ•ირთული áƒáƒ áƒáƒ, áƒáƒ›áƒáƒ‘ეჭდვის დáƒáƒ¡áƒáƒ¬áƒ§áƒ”ბáƒáƒ“. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = გვერდითრზáƒáƒšáƒ˜áƒ¡ გáƒáƒ›áƒáƒ©áƒ”ნáƒ/დáƒáƒ›áƒáƒšáƒ•რ+pdfjs-toggle-sidebar-notification-button = + .title = გვერდითი ზáƒáƒšáƒ˜áƒ¡ გáƒáƒ›áƒáƒ©áƒ”ნრ(შეიცáƒáƒ•ს სáƒáƒ áƒ©áƒ”ვს/დáƒáƒœáƒáƒ áƒ—ს/შრეებს) +pdfjs-toggle-sidebar-button-label = გვერდითრზáƒáƒšáƒ˜áƒ¡ გáƒáƒ›áƒáƒ©áƒ”ნáƒ/დáƒáƒ›áƒáƒšáƒ•რ+pdfjs-document-outline-button = + .title = დáƒáƒ™áƒ£áƒ›áƒ”ნტის სáƒáƒ áƒ©áƒ”ვის ჩვენებრ(áƒáƒ áƒ›áƒáƒ’ი წკáƒáƒžáƒ˜áƒ— თითáƒáƒ”ულის ჩáƒáƒ›áƒáƒ¨áƒšáƒ/áƒáƒ™áƒ”ცვáƒ) +pdfjs-document-outline-button-label = დáƒáƒ™áƒ£áƒ›áƒ”ნტის სáƒáƒ áƒ©áƒ”ვი +pdfjs-attachments-button = + .title = დáƒáƒœáƒáƒ áƒ—ების ჩვენებრ+pdfjs-attachments-button-label = დáƒáƒœáƒáƒ áƒ—ები +pdfjs-layers-button = + .title = შრეების გáƒáƒ›áƒáƒ©áƒ”ნრ(áƒáƒ áƒ›áƒáƒ’ი წკáƒáƒžáƒ˜áƒ— ყველრშრის ნáƒáƒ’ულისხმევზე დáƒáƒ‘რუნებáƒ) +pdfjs-layers-button-label = შრეები +pdfjs-thumbs-button = + .title = შეთვáƒáƒšáƒ˜áƒ”რებრ+pdfjs-thumbs-button-label = ესკიზები +pdfjs-current-outline-item-button = + .title = მიმდინáƒáƒ áƒ” გვერდის მáƒáƒœáƒáƒ®áƒ•რსáƒáƒ áƒ©áƒ”ვში +pdfjs-current-outline-item-button-label = მიმდინáƒáƒ áƒ” გვერდი სáƒáƒ áƒ©áƒ”ვში +pdfjs-findbar-button = + .title = პáƒáƒ•ნრდáƒáƒ™áƒ£áƒ›áƒ”ნტში +pdfjs-findbar-button-label = ძიებრ+pdfjs-additional-layers = დáƒáƒ›áƒáƒ¢áƒ”ბითი შრეები + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = გვერდი { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = გვერდის შეთვáƒáƒšáƒ˜áƒ”რებრ{ $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = ძიებრ+ .placeholder = პáƒáƒ•ნრდáƒáƒ™áƒ£áƒ›áƒ”ნტში… +pdfjs-find-previous-button = + .title = წინრდáƒáƒ›áƒ—ხვევის პáƒáƒ•ნრ+pdfjs-find-previous-button-label = წინრ+pdfjs-find-next-button = + .title = მáƒáƒ›áƒ“ევნრდáƒáƒ›áƒ—ხვევის პáƒáƒ•ნრ+pdfjs-find-next-button-label = შემდეგი +pdfjs-find-highlight-checkbox = ყველáƒáƒ¤áƒ áƒ˜áƒ¡ მáƒáƒœáƒ˜áƒ¨áƒ•ნრ+pdfjs-find-match-case-checkbox-label = მთáƒáƒ•რულით +pdfjs-find-match-diacritics-checkbox-label = ნიშნებით +pdfjs-find-entire-word-checkbox-label = მთლიáƒáƒœáƒ˜ სიტყვები +pdfjs-find-reached-top = მიღწეულირდáƒáƒ™áƒ£áƒ›áƒ”ნტის დáƒáƒ¡áƒáƒ¬áƒ§áƒ˜áƒ¡áƒ˜, გრძელდებრბáƒáƒšáƒáƒ“áƒáƒœ +pdfjs-find-reached-bottom = მიღწეულირდáƒáƒ™áƒ£áƒ›áƒ”ნტის ბáƒáƒšáƒ, გრძელდებრდáƒáƒ¡áƒáƒ¬áƒ§áƒ˜áƒ¡áƒ˜áƒ“áƒáƒœ +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] თáƒáƒœáƒ®áƒ•ედრრ{ $current }, სულ { $total } + *[other] თáƒáƒœáƒ®áƒ•ედრრ{ $current }, სულ { $total } + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] áƒáƒ áƒáƒœáƒáƒ™áƒšáƒ”ბ { $limit } თáƒáƒœáƒ®áƒ•ედრრ+ *[other] áƒáƒ áƒáƒœáƒáƒ™áƒšáƒ”ბ { $limit } თáƒáƒœáƒ®áƒ•ედრრ+ } +pdfjs-find-not-found = ფრáƒáƒ–რვერ მáƒáƒ˜áƒ«áƒ”ბნრ+ +## Predefined zoom values + +pdfjs-page-scale-width = გვერდის სიგáƒáƒœáƒ”ზე +pdfjs-page-scale-fit = მთლიáƒáƒœáƒ˜ გვერდი +pdfjs-page-scale-auto = áƒáƒ•ტáƒáƒ›áƒáƒ¢áƒ£áƒ áƒ˜ +pdfjs-page-scale-actual = სáƒáƒ¬áƒ§áƒ˜áƒ¡áƒ˜ ზáƒáƒ›áƒ +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = გვერდი { $page } + +## Loading indicator messages + +pdfjs-loading-error = შეცდáƒáƒ›áƒ, PDF-ფáƒáƒ˜áƒšáƒ˜áƒ¡ ჩáƒáƒ¢áƒ•ირთვისáƒáƒ¡. +pdfjs-invalid-file-error = áƒáƒ áƒáƒ›áƒáƒ áƒ—ებული áƒáƒœ დáƒáƒ–იáƒáƒœáƒ”ბული PDF-ფáƒáƒ˜áƒšáƒ˜. +pdfjs-missing-file-error = ნáƒáƒ™áƒšáƒ£áƒšáƒ˜ PDF-ფáƒáƒ˜áƒšáƒ˜. +pdfjs-unexpected-response-error = სერვერის მáƒáƒ£áƒšáƒáƒ“ნელი პáƒáƒ¡áƒ£áƒ®áƒ˜. +pdfjs-rendering-error = შეცდáƒáƒ›áƒ, გვერდის ჩვენებისáƒáƒ¡. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } შენიშვნáƒ] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = შეიყვáƒáƒœáƒ”თ პáƒáƒ áƒáƒšáƒ˜ PDF-ფáƒáƒ˜áƒšáƒ˜áƒ¡ გáƒáƒ¡áƒáƒ®áƒ¡áƒœáƒ”ლáƒáƒ“. +pdfjs-password-invalid = áƒáƒ áƒáƒ¡áƒ¬áƒáƒ áƒ˜ პáƒáƒ áƒáƒšáƒ˜. გთხáƒáƒ•თ, სცáƒáƒ“áƒáƒ— ხელáƒáƒ®áƒšáƒ. +pdfjs-password-ok-button = კáƒáƒ áƒ’ი +pdfjs-password-cancel-button = გáƒáƒ£áƒ¥áƒ›áƒ”ბრ+pdfjs-web-fonts-disabled = ვებშრიფტები გáƒáƒ›áƒáƒ áƒ—ულიáƒ: ჩáƒáƒ¨áƒ”ნებული PDF-შრიფტების გáƒáƒ›áƒáƒ§áƒ”ნებრვერ ხერხდებáƒ. + +## Editing + +pdfjs-editor-free-text-button = + .title = წáƒáƒ áƒ¬áƒ”რრ+pdfjs-editor-free-text-button-label = წáƒáƒ áƒ¬áƒ”რრ+pdfjs-editor-ink-button = + .title = ხáƒáƒ–ვრ+pdfjs-editor-ink-button-label = ხáƒáƒ–ვრ+pdfjs-editor-stamp-button = + .title = სურáƒáƒ—ების დáƒáƒ áƒ—ვრáƒáƒœ ჩáƒáƒ¡áƒ¬áƒáƒ áƒ”ბრ+pdfjs-editor-stamp-button-label = სურáƒáƒ—ების დáƒáƒ áƒ—ვრáƒáƒœ ჩáƒáƒ¡áƒ¬áƒáƒ áƒ”ბრ+pdfjs-editor-highlight-button = + .title = მáƒáƒœáƒ˜áƒ¨áƒ•ნრ+pdfjs-editor-highlight-button-label = მáƒáƒœáƒ˜áƒ¨áƒ•ნრ+pdfjs-highlight-floating-button1 = + .title = მáƒáƒœáƒ˜áƒ¨áƒ•ნრ+ .aria-label = მáƒáƒœáƒ˜áƒ¨áƒ•ნრ+pdfjs-highlight-floating-button-label = მáƒáƒœáƒ˜áƒ¨áƒ•ნრ+ +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = დáƒáƒ®áƒáƒ–ულის მáƒáƒªáƒ˜áƒšáƒ”ბრ+pdfjs-editor-remove-freetext-button = + .title = წáƒáƒ áƒ¬áƒ”რის მáƒáƒªáƒ˜áƒšáƒ”ბრ+pdfjs-editor-remove-stamp-button = + .title = სურáƒáƒ—ის მáƒáƒªáƒ˜áƒšáƒ”ბრ+pdfjs-editor-remove-highlight-button = + .title = მáƒáƒœáƒ˜áƒ¨áƒ•ნის მáƒáƒªáƒ˜áƒšáƒ”ბრ+ +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = ფერი +pdfjs-editor-free-text-size-input = ზáƒáƒ›áƒ +pdfjs-editor-ink-color-input = ფერი +pdfjs-editor-ink-thickness-input = სისქე +pdfjs-editor-ink-opacity-input = გáƒáƒ£áƒ›áƒ­áƒ•ირვáƒáƒšáƒáƒ‘რ+pdfjs-editor-stamp-add-image-button = + .title = სურáƒáƒ—ის დáƒáƒ›áƒáƒ¢áƒ”ბრ+pdfjs-editor-stamp-add-image-button-label = სურáƒáƒ—ის დáƒáƒ›áƒáƒ¢áƒ”ბრ+# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = სისქე +pdfjs-editor-free-highlight-thickness-title = + .title = სისქის შეცვლრწáƒáƒ áƒ¬áƒ”რის გáƒáƒ áƒ“რსხვრნáƒáƒ¬áƒ˜áƒšáƒ”ბის მáƒáƒœáƒ˜áƒ¨áƒ•ნისáƒáƒ¡ +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = ნáƒáƒ¬áƒ”რის ჩáƒáƒ¡áƒ¬áƒáƒ áƒ”ბრ+ .default-content = დáƒáƒ˜áƒ¬áƒ§áƒ”თ áƒáƒ™áƒ áƒ”ფáƒâ€¦ +pdfjs-free-text = + .aria-label = ნáƒáƒ¬áƒ”რის ჩáƒáƒ¡áƒ¬áƒáƒ áƒ”ბრ+pdfjs-free-text-default-content = áƒáƒ™áƒ áƒ˜áƒ¤áƒ”თ… +pdfjs-ink = + .aria-label = დáƒáƒ®áƒáƒ–ულის შესწáƒáƒ áƒ”ბრ+pdfjs-ink-canvas = + .aria-label = მáƒáƒ›áƒ®áƒ›áƒáƒ áƒ”ბლის შექმნილი სურáƒáƒ—ი + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = თáƒáƒœáƒ“áƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რრ+pdfjs-editor-alt-text-edit-button = + .aria-label = დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის ჩáƒáƒ¡áƒ¬áƒáƒ áƒ”ბრ+pdfjs-editor-alt-text-edit-button-label = თáƒáƒœáƒ“áƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის ჩáƒáƒ¡áƒ¬áƒáƒ áƒ”ბრ+pdfjs-editor-alt-text-dialog-label = áƒáƒ áƒ©áƒ”ვრ+pdfjs-editor-alt-text-dialog-description = თáƒáƒœáƒ“áƒáƒ áƒ—ული (შემნáƒáƒªáƒ•ლებელი) წáƒáƒ áƒ¬áƒ”რრგáƒáƒ›áƒáƒ¡áƒáƒ“ეგირმáƒáƒ—თვის, ვინც ვერ ხედáƒáƒ•ს სურáƒáƒ—ებს áƒáƒœ გáƒáƒ›áƒáƒ˜áƒ¡áƒáƒ®áƒ”ბრმáƒáƒ¨áƒ˜áƒœ, რáƒáƒªáƒ სურáƒáƒ—ი ვერ ჩáƒáƒ˜áƒ¢áƒ•ირთებáƒ. +pdfjs-editor-alt-text-add-description-label = áƒáƒ¦áƒ¬áƒ”რილáƒáƒ‘ის მითითებრ+pdfjs-editor-alt-text-add-description-description = გáƒáƒœáƒ™áƒ£áƒ—ვნილირ1-2 წინáƒáƒ“áƒáƒ“ებით სáƒáƒ’ნის, მáƒáƒ®áƒáƒ¡áƒ˜áƒáƒ—ებლის áƒáƒœ მáƒáƒ¥áƒ›áƒ”დების áƒáƒ¦áƒ¡áƒáƒ¬áƒ”რáƒáƒ“. +pdfjs-editor-alt-text-mark-decorative-label = მáƒáƒ˜áƒœáƒ˜áƒ¨áƒœáƒáƒ¡ მáƒáƒ áƒ—ულáƒáƒ‘áƒáƒ“ +pdfjs-editor-alt-text-mark-decorative-description = გáƒáƒœáƒ™áƒ£áƒ—ვნილირშესáƒáƒ›áƒ™áƒáƒ‘ი სურáƒáƒ—ებისთვის, გáƒáƒ áƒ¡áƒ¨áƒ”მáƒáƒ¡áƒáƒ•ლები ჩáƒáƒ áƒ©áƒáƒ”ბისრდრჭვირნიშნებისთვის. +pdfjs-editor-alt-text-cancel-button = გáƒáƒ£áƒ¥áƒ›áƒ”ბრ+pdfjs-editor-alt-text-save-button = შენáƒáƒ®áƒ•რ+pdfjs-editor-alt-text-decorative-tooltip = მáƒáƒ˜áƒœáƒ˜áƒ¨áƒœáƒáƒ¡ მáƒáƒ áƒ—ულáƒáƒ‘áƒáƒ“ +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = მáƒáƒ’áƒáƒšáƒ˜áƒ—áƒáƒ“, „áƒáƒ®áƒáƒšáƒ’áƒáƒ–რდრმáƒáƒ›áƒáƒ™áƒáƒªáƒ˜ მáƒáƒ’იდáƒáƒ¡áƒ—áƒáƒœ ზის დრსáƒáƒ“ილáƒáƒ‘ს“ +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რრ+ +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = ზევით მáƒáƒ áƒªáƒ®áƒœáƒ˜áƒ• — ზáƒáƒ›áƒáƒªáƒ•ლრ+pdfjs-editor-resizer-label-top-middle = ზევით შუáƒáƒ¨áƒ˜ — ზáƒáƒ›áƒáƒªáƒ•ლრ+pdfjs-editor-resizer-label-top-right = ზევით მáƒáƒ áƒ¯áƒ•ნივ — ზáƒáƒ›áƒáƒªáƒ•ლრ+pdfjs-editor-resizer-label-middle-right = შუáƒáƒ¨áƒ˜ მáƒáƒ áƒ¯áƒ•ნივ — ზáƒáƒ›áƒáƒªáƒ•ლრ+pdfjs-editor-resizer-label-bottom-right = ქვევით მáƒáƒ áƒ¯áƒ•ნივ — ზáƒáƒ›áƒáƒªáƒ•ლრ+pdfjs-editor-resizer-label-bottom-middle = ქვევით შუáƒáƒ¨áƒ˜ — ზáƒáƒ›áƒáƒªáƒ•ლრ+pdfjs-editor-resizer-label-bottom-left = ზვევით მáƒáƒ áƒªáƒ®áƒœáƒ˜áƒ• — ზáƒáƒ›áƒáƒªáƒ•ლრ+pdfjs-editor-resizer-label-middle-left = შუáƒáƒ¨áƒ˜ მáƒáƒ áƒªáƒ®áƒœáƒ˜áƒ• — ზáƒáƒ›áƒáƒªáƒ•ლრ+pdfjs-editor-resizer-top-left = + .aria-label = ზევით მáƒáƒ áƒªáƒ®áƒœáƒ˜áƒ• — ზáƒáƒ›áƒáƒªáƒ•ლრ+pdfjs-editor-resizer-top-middle = + .aria-label = ზევით შუáƒáƒ¨áƒ˜ — ზáƒáƒ›áƒáƒªáƒ•ლრ+pdfjs-editor-resizer-top-right = + .aria-label = ზევით მáƒáƒ áƒ¯áƒ•ნივ — ზáƒáƒ›áƒáƒªáƒ•ლრ+pdfjs-editor-resizer-middle-right = + .aria-label = შუáƒáƒ¨áƒ˜ მáƒáƒ áƒ¯áƒ•ნივ — ზáƒáƒ›áƒáƒªáƒ•ლრ+pdfjs-editor-resizer-bottom-right = + .aria-label = ქვევით მáƒáƒ áƒ¯áƒ•ნივ — ზáƒáƒ›áƒáƒªáƒ•ლრ+pdfjs-editor-resizer-bottom-middle = + .aria-label = ქვევით შუáƒáƒ¨áƒ˜ — ზáƒáƒ›áƒáƒªáƒ•ლრ+pdfjs-editor-resizer-bottom-left = + .aria-label = ზვევით მáƒáƒ áƒªáƒ®áƒœáƒ˜áƒ• — ზáƒáƒ›áƒáƒªáƒ•ლრ+pdfjs-editor-resizer-middle-left = + .aria-label = შუáƒáƒ¨áƒ˜ მáƒáƒ áƒªáƒ®áƒœáƒ˜áƒ• — ზáƒáƒ›áƒáƒªáƒ•ლრ+ +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = მáƒáƒ¡áƒáƒœáƒ˜áƒ¨áƒœáƒ˜ ფერი +pdfjs-editor-colorpicker-button = + .title = ფერის შეცვლრ+pdfjs-editor-colorpicker-dropdown = + .aria-label = ფერის áƒáƒ áƒ©áƒ”ვრ+pdfjs-editor-colorpicker-yellow = + .title = ყვითელი +pdfjs-editor-colorpicker-green = + .title = მწვáƒáƒœáƒ” +pdfjs-editor-colorpicker-blue = + .title = ლურჯი +pdfjs-editor-colorpicker-pink = + .title = ვáƒáƒ áƒ“ისფერი +pdfjs-editor-colorpicker-red = + .title = წითელი + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = ყველáƒáƒ¡ ჩვენებრ+pdfjs-editor-highlight-show-all-button = + .title = ყველáƒáƒ¡ ჩვენებრ+ +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის ჩáƒáƒ¡áƒ¬áƒáƒ áƒ”ბრ(სურáƒáƒ—ის áƒáƒ¦áƒ¬áƒ”რის) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის დáƒáƒ›áƒáƒ¢áƒ”ბრ(სურáƒáƒ—ის áƒáƒ¦áƒ¬áƒ”რის) +pdfjs-editor-new-alt-text-textarea = + .placeholder = დáƒáƒ¬áƒ”რეთ თქვენი áƒáƒ¦áƒ¬áƒ”რრáƒáƒ¥â€¦ +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = მáƒáƒ™áƒšáƒ” áƒáƒ¦áƒ¬áƒ”რრმáƒáƒ—თვის, ვინც ვერ ხედáƒáƒ•ს სურáƒáƒ—ს áƒáƒœ ვისთáƒáƒœáƒáƒª ვერ ჩáƒáƒ˜áƒ¢áƒ•ირთებრსურáƒáƒ—ი. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = ეს დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რრáƒáƒ•ტáƒáƒ›áƒáƒ¢áƒ£áƒ áƒáƒ“áƒáƒ შედგენილი დრშესáƒáƒ«áƒšáƒáƒ, უმáƒáƒ áƒ—ებულრიყáƒáƒ¡. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = ვრცლáƒáƒ“ +pdfjs-editor-new-alt-text-create-automatically-button-label = დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის áƒáƒ•ტáƒáƒ›áƒáƒ¢áƒ£áƒ áƒ˜ შედგენრ+pdfjs-editor-new-alt-text-not-now-button = áƒáƒ®áƒšáƒ áƒáƒ áƒ +pdfjs-editor-new-alt-text-error-title = დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის შედგენრვერ მáƒáƒ®áƒ”რხდრ+pdfjs-editor-new-alt-text-error-description = გთხáƒáƒ•თ დáƒáƒ¬áƒ”რáƒáƒ— სáƒáƒ™áƒ£áƒ—áƒáƒ áƒ˜ დáƒáƒœáƒáƒ áƒ—ი დრკვლáƒáƒ• სცáƒáƒ“áƒáƒ— მáƒáƒ’ვიáƒáƒœáƒ”ბით. +pdfjs-editor-new-alt-text-error-close-button = დáƒáƒ®áƒ£áƒ áƒ•რ+# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = ჩáƒáƒ›áƒáƒ˜áƒ¢áƒ•ირთებრდáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის შესáƒáƒ“ეგი AI-მáƒáƒ“ელი ({ $downloadedSize } ზáƒáƒ›áƒ˜áƒ— { $totalSize } მბáƒáƒ˜áƒ¢áƒ˜) + .aria-valuetext = ჩáƒáƒ›áƒáƒ˜áƒ¢áƒ•ირთებრდáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის შესáƒáƒ“ეგი AI-მáƒáƒ“ელი ({ $downloadedSize } ზáƒáƒ›áƒ˜áƒ— { $totalSize } მბáƒáƒ˜áƒ¢áƒ˜) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რრდáƒáƒ›áƒáƒ¢áƒ”ბულირ+pdfjs-editor-new-alt-text-added-button-label = დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რრდáƒáƒ›áƒáƒ¢áƒ”ბულირ+# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = áƒáƒ™áƒšáƒ˜áƒ დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რრ+pdfjs-editor-new-alt-text-missing-button-label = áƒáƒ™áƒšáƒ˜áƒ დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რრ+# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის გáƒáƒ“áƒáƒ®áƒ”დვრ+pdfjs-editor-new-alt-text-to-review-button-label = დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის გáƒáƒ“áƒáƒ®áƒ”დვრ+# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = შედგენილირáƒáƒ•ტáƒáƒ›áƒáƒ¢áƒ£áƒ áƒáƒ“: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = სურáƒáƒ—ის დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის პáƒáƒ áƒáƒ›áƒ”ტრები +pdfjs-image-alt-text-settings-button-label = სურáƒáƒ—ის დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის პáƒáƒ áƒáƒ›áƒ”ტრები +pdfjs-editor-alt-text-settings-dialog-label = სურáƒáƒ—ის დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის პáƒáƒ áƒáƒ›áƒ”ტრები +pdfjs-editor-alt-text-settings-automatic-title = áƒáƒ•ტáƒáƒ›áƒáƒ¢áƒ£áƒ áƒáƒ“ დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რრ+pdfjs-editor-alt-text-settings-create-model-button-label = დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის áƒáƒ•ტáƒáƒ›áƒáƒ¢áƒ£áƒ áƒ˜ შედგენრ+pdfjs-editor-alt-text-settings-create-model-description = áƒáƒ¦áƒ¬áƒ”რს სურáƒáƒ—ს მáƒáƒ—თვის, ვინც ვერ ხედáƒáƒ•ს áƒáƒœ ვისთáƒáƒœáƒáƒª ვერ ჩáƒáƒ˜áƒ¢áƒ•ირთებáƒ. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის შესáƒáƒ“გენი AI-მáƒáƒ“ელი ({ $totalSize } მბáƒáƒ˜áƒ¢áƒ˜) +pdfjs-editor-alt-text-settings-ai-model-description = ეშვებრáƒáƒ“გილáƒáƒ‘რივáƒáƒ“ თქვენს მáƒáƒ¬áƒ§áƒáƒ‘ილáƒáƒ‘áƒáƒ¡áƒ, áƒáƒ¡áƒ” რáƒáƒ› მáƒáƒœáƒáƒªáƒ”მები დáƒáƒ áƒ©áƒ”ბრპირáƒáƒ“ი. სáƒáƒ­áƒ˜áƒ áƒáƒ წáƒáƒ áƒ¬áƒ”რის áƒáƒ•ტáƒáƒ›áƒáƒ¢áƒ£áƒ áƒáƒ“ დáƒáƒ áƒ—ვისთვის. +pdfjs-editor-alt-text-settings-delete-model-button = წáƒáƒ¨áƒšáƒ +pdfjs-editor-alt-text-settings-download-model-button = ჩáƒáƒ›áƒáƒ¢áƒ•ირთვრ+pdfjs-editor-alt-text-settings-downloading-model-button = ჩáƒáƒ›áƒáƒ˜áƒ¢áƒ•რითებáƒ... +pdfjs-editor-alt-text-settings-editor-title = დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის ჩáƒáƒ›áƒ¡áƒ¬áƒáƒ áƒ”ბელი +pdfjs-editor-alt-text-settings-show-dialog-button-label = გáƒáƒ›áƒáƒ©áƒœáƒ“ეს დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რის ჩáƒáƒ›áƒ¡áƒ¬áƒáƒ áƒ”ბელი სურáƒáƒ—ის დáƒáƒ›áƒáƒ¢áƒ”ბისთáƒáƒœáƒáƒ•ე +pdfjs-editor-alt-text-settings-show-dialog-description = უზრუნველყáƒáƒ¤áƒ¡, რáƒáƒ› თქვენს ყველრსურáƒáƒ—ს áƒáƒ®áƒšáƒ“ეს დáƒáƒ áƒ—ული წáƒáƒ áƒ¬áƒ”რáƒ. +pdfjs-editor-alt-text-settings-close-button = დáƒáƒ®áƒ£áƒ áƒ•რ+ +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = მáƒáƒœáƒ˜áƒ¨áƒ•ნრმáƒáƒªáƒ˜áƒšáƒ”ბულირ+pdfjs-editor-undo-bar-message-freetext = წáƒáƒ áƒ¬áƒ”რრმáƒáƒªáƒ˜áƒšáƒ”ბულირ+pdfjs-editor-undo-bar-message-ink = ნáƒáƒ®áƒáƒ¢áƒ˜ მáƒáƒªáƒ˜áƒšáƒ”ბულირ+pdfjs-editor-undo-bar-message-stamp = სურáƒáƒ—ი მáƒáƒªáƒ˜áƒšáƒ”ბულირ+# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } შენიშვნრმáƒáƒªáƒ˜áƒšáƒ”ბულირ+ *[other] { $count } შენიშვნრმáƒáƒªáƒ˜áƒšáƒ”ბულირ+ } +pdfjs-editor-undo-bar-undo-button = + .title = დáƒáƒ‘რუნებრ+pdfjs-editor-undo-bar-undo-button-label = დáƒáƒ‘რუნებრ+pdfjs-editor-undo-bar-close-button = + .title = დáƒáƒ®áƒ£áƒ áƒ•რ+pdfjs-editor-undo-bar-close-button-label = დáƒáƒ®áƒ£áƒ áƒ•რdiff --git a/public/assets/pdfjs/locale/kab/viewer.ftl b/public/assets/pdfjs/locale/kab/viewer.ftl new file mode 100755 index 0000000..dda88c1 --- /dev/null +++ b/public/assets/pdfjs/locale/kab/viewer.ftl @@ -0,0 +1,438 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Asebter azewwar +pdfjs-previous-button-label = Azewwar +pdfjs-next-button = + .title = Asebter d-iteddun +pdfjs-next-button-label = Ddu É£er zdat +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Asebter +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = É£ef { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } n { $pagesCount }) +pdfjs-zoom-out-button = + .title = Semẓi +pdfjs-zoom-out-button-label = Semẓi +pdfjs-zoom-in-button = + .title = SemÉ£eá¹› +pdfjs-zoom-in-button-label = SemÉ£eá¹› +pdfjs-zoom-select = + .title = SemÉ£eá¹›/Semẓi +pdfjs-presentation-mode-button = + .title = UÉ£al É£er Uskar Tihawt +pdfjs-presentation-mode-button-label = Askar Tihawt +pdfjs-open-file-button = + .title = Ldi Afaylu +pdfjs-open-file-button-label = Ldi +pdfjs-print-button = + .title = Siggez +pdfjs-print-button-label = Siggez +pdfjs-save-button = + .title = Sekles +pdfjs-save-button-label = Sekles +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Sader +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Sader +pdfjs-bookmark-button = + .title = Asebter amiran (Sken-d tansa URL seg usebter amiran) +pdfjs-bookmark-button-label = Asebter amiran + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Ifecka +pdfjs-tools-button-label = Ifecka +pdfjs-first-page-button = + .title = Ddu É£er usebter amezwaru +pdfjs-first-page-button-label = Ddu É£er usebter amezwaru +pdfjs-last-page-button = + .title = Ddu É£er usebter aneggaru +pdfjs-last-page-button-label = Ddu É£er usebter aneggaru +pdfjs-page-rotate-cw-button = + .title = Tuzzya tusrigt +pdfjs-page-rotate-cw-button-label = Tuzzya tusrigt +pdfjs-page-rotate-ccw-button = + .title = Tuzzya amgal-usrig +pdfjs-page-rotate-ccw-button-label = Tuzzya amgal-usrig +pdfjs-cursor-text-select-tool-button = + .title = Rmed afecku n tefrant n uá¸ris +pdfjs-cursor-text-select-tool-button-label = Afecku n tefrant n uá¸ris +pdfjs-cursor-hand-tool-button = + .title = Rmed afecku afus +pdfjs-cursor-hand-tool-button-label = Afecku afus +pdfjs-scroll-page-button = + .title = Seqdec adrurem n usebter +pdfjs-scroll-page-button-label = Adrurem n usebter +pdfjs-scroll-vertical-button = + .title = Seqdec adrurem ubdid +pdfjs-scroll-vertical-button-label = Adrurem ubdid +pdfjs-scroll-horizontal-button = + .title = Seqdec adrurem aglawan +pdfjs-scroll-horizontal-button-label = Adrurem aglawan +pdfjs-scroll-wrapped-button = + .title = Seqdec adrurem yuẓen +pdfjs-scroll-wrapped-button-label = Adrurem yuẓen +pdfjs-spread-none-button = + .title = Ur sedday ara isiÉ£zaf n usebter +pdfjs-spread-none-button-label = Ulac isiÉ£zaf +pdfjs-spread-odd-button = + .title = Seddu isiÉ£zaf n usebter ibeddun s yisebtar irayuganen +pdfjs-spread-odd-button-label = IsiÉ£zaf irayuganen +pdfjs-spread-even-button = + .title = Seddu isiÉ£zaf n usebter ibeddun s yisebtar iyuganen +pdfjs-spread-even-button-label = IsiÉ£zaf iyuganen + +## Document properties dialog + +pdfjs-document-properties-button = + .title = TaÉ£aá¹›a n isemli… +pdfjs-document-properties-button-label = TaÉ£aá¹›a n isemli… +pdfjs-document-properties-file-name = Isem n ufaylu: +pdfjs-document-properties-file-size = TeÉ£zi n ufaylu: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } yibiten) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } yibiten) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KAṬ ({ $size_b } ibiten) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MAṬ ({ $size_b } iá¹­amá¸anen) +pdfjs-document-properties-title = Azwel: +pdfjs-document-properties-author = Ameskar: +pdfjs-document-properties-subject = Amgay: +pdfjs-document-properties-keywords = Awalen n tsaruÅ£ +pdfjs-document-properties-creation-date = Azemz n tmerna: +pdfjs-document-properties-modification-date = Azemz n usnifel: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Yerna-t: +pdfjs-document-properties-producer = Afecku n uselket PDF: +pdfjs-document-properties-version = Lqem PDF: +pdfjs-document-properties-page-count = Amá¸an n yisebtar: +pdfjs-document-properties-page-size = Tuγzi n usebter: +pdfjs-document-properties-page-size-unit-inches = deg +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = s teÉ£zi +pdfjs-document-properties-page-size-orientation-landscape = s tehri +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Asekkil +pdfjs-document-properties-page-size-name-legal = Usá¸if + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Taskant Web taruradt: +pdfjs-document-properties-linearized-yes = Ih +pdfjs-document-properties-linearized-no = Ala +pdfjs-document-properties-close-button = Mdel + +## Print + +pdfjs-print-progress-message = Aheggi i usiggez n isemli… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Sefsex +pdfjs-printing-not-supported = Æ”uá¹›-k: Asiggez ur ittusefrak ara yakan imaṛṛa deg iminig-a. +pdfjs-printing-not-ready = Æ”uá¹›-k: Afaylu PDF ur d-yuli ara imeṛṛa akken ad ittusiggez. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Sken/Fer agalis adisan +pdfjs-toggle-sidebar-notification-button = + .title = Ffer/Sekn agalis adisan (isemli yegber aÉ£awas/ticeqqufin yeddan/tissiwin) +pdfjs-toggle-sidebar-button-label = Sken/Fer agalis adisan +pdfjs-document-outline-button = + .title = Sken isemli (Senned snat tikal i wesemÉ£er/Afneẓ n iferdisen meṛṛa) +pdfjs-document-outline-button-label = IsÉ£alen n isebtar +pdfjs-attachments-button = + .title = Sken ticeqqufin yeddan +pdfjs-attachments-button-label = Ticeqqufin yeddan +pdfjs-layers-button = + .title = Skeen tissiwin (sit sin yiberdan i uwennez n meṛṛa tissiwin É£er waddad amezwer) +pdfjs-layers-button-label = Tissiwin +pdfjs-thumbs-button = + .title = Sken tanfult. +pdfjs-thumbs-button-label = Tinfulin +pdfjs-current-outline-item-button = + .title = Af-d aferdis n uÉ£awas amiran +pdfjs-current-outline-item-button-label = Aferdis n uÉ£awas amiran +pdfjs-findbar-button = + .title = Nadi deg isemli +pdfjs-findbar-button-label = Nadi +pdfjs-additional-layers = Tissiwin-nniá¸en + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Asebter { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Tanfult n usebter { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Nadi + .placeholder = Nadi deg isemli… +pdfjs-find-previous-button = + .title = Aff-d tamseá¸riwt n twinest n deffir +pdfjs-find-previous-button-label = Azewwar +pdfjs-find-next-button = + .title = Aff-d timseá¸riwt n twinest d-iteddun +pdfjs-find-next-button-label = Ddu É£er zdat +pdfjs-find-highlight-checkbox = Err izirig imaṛṛa +pdfjs-find-match-case-checkbox-label = Qadeá¹› amasal n isekkilen +pdfjs-find-match-diacritics-checkbox-label = Qadeá¹› ifeskilen +pdfjs-find-entire-word-checkbox-label = Awalen iÄÄuranen +pdfjs-find-reached-top = YabbeḠs afella n usebter, tuÉ£alin s wadda +pdfjs-find-reached-bottom = Tebá¸eḠs adda n usebter, tuÉ£alin s afella +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] Timeá¸riwt { $current } É£ef { $total } + *[other] Timeá¸riwin { $current } É£ef { $total } + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Ugar n { $limit } umá¹£ada + *[other] Ugar n { $limit } yimá¹£adayen + } +pdfjs-find-not-found = Ulac tawinest + +## Predefined zoom values + +pdfjs-page-scale-width = Tehri n usebter +pdfjs-page-scale-fit = Asebter imaṛṛa +pdfjs-page-scale-auto = AsemÉ£eá¹›/Asemẓi awurman +pdfjs-page-scale-actual = TeÉ£zi tilawt +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Asebter { $page } + +## Loading indicator messages + +pdfjs-loading-error = Teá¸ra-d tuccá¸a deg alluy n PDF: +pdfjs-invalid-file-error = Afaylu PDF arameÉ£tu neÉ£ yexá¹£eá¹›. +pdfjs-missing-file-error = Ulac afaylu PDF. +pdfjs-unexpected-response-error = Aqeddac yerra-d yir tiririt ur nettwaṛǧi ara. +pdfjs-rendering-error = Teá¸ra-d tuccá¸a deg uskan n usebter. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Tabzimt { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Sekcem awal uffir akken ad ldiḠafaylu-yagi PDF +pdfjs-password-invalid = Awal uffir maÄÄi d ameÉ£tu, ÆreḠtikelt-nniá¸en. +pdfjs-password-ok-button = IH +pdfjs-password-cancel-button = Sefsex +pdfjs-web-fonts-disabled = Tisefsiyin web ttwassensent; D awezÉ£i useqdec n tsefsiyin yettwarnan É£er PDF. + +## Editing + +pdfjs-editor-free-text-button = + .title = Aá¸ris +pdfjs-editor-free-text-button-label = Aá¸ris +pdfjs-editor-ink-button = + .title = SuneÉ£ +pdfjs-editor-ink-button-label = SuneÉ£ +pdfjs-editor-stamp-button = + .title = Rnu neÉ£ ẓreg tugniwin +pdfjs-editor-stamp-button-label = Rnu neÉ£ ẓreg tugniwin +pdfjs-editor-highlight-button = + .title = Derrer +pdfjs-editor-highlight-button-label = Derrer +pdfjs-highlight-floating-button1 = + .title = Derrer + .aria-label = Derrer +pdfjs-highlight-floating-button-label = Derrer + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Kkes asuneÉ£ +pdfjs-editor-remove-freetext-button = + .title = Kkes aá¸ris +pdfjs-editor-remove-stamp-button = + .title = Kkes tugna +pdfjs-editor-remove-highlight-button = + .title = Kkes aderrer + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Initen +pdfjs-editor-free-text-size-input = TeÉ£zi +pdfjs-editor-ink-color-input = Ini +pdfjs-editor-ink-thickness-input = Tuzert +pdfjs-editor-ink-opacity-input = Tebrek +pdfjs-editor-stamp-add-image-button = + .title = Rnu tawlaft +pdfjs-editor-stamp-add-image-button-label = Rnu tawlaft +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Tuzert +pdfjs-editor-free-highlight-thickness-title = + .title = Beddel tuzert mi ara d-tesbeggneḠiferdisen niá¸en ur nelli d aá¸ris +pdfjs-free-text = + .aria-label = Amaẓrag n uá¸ris +pdfjs-free-text-default-content = Bdu tira... +pdfjs-ink = + .aria-label = Amaẓrag n usuneÉ£ +pdfjs-ink-canvas = + .aria-label = Tugna yettwarnan sÉ£ur useqdac + +## Alt-text dialog + +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button-label = Aá¸ris amaskal +pdfjs-editor-alt-text-edit-button-label = Ẓreg aá¸ris amaskal +pdfjs-editor-alt-text-dialog-label = Fren taxtirt +pdfjs-editor-alt-text-add-description-label = Rnu aglam +pdfjs-editor-alt-text-mark-decorative-label = CreḠd adlag +pdfjs-editor-alt-text-cancel-button = Sefsex +pdfjs-editor-alt-text-save-button = Sekles +pdfjs-editor-alt-text-decorative-tooltip = YettwacreḠd adlag + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = TiÉ£mert n ufella n zelmeḠ— semsawi teÉ£zi +pdfjs-editor-resizer-label-top-middle = Talemmat n ufella — semsawi teÉ£zi +pdfjs-editor-resizer-label-top-right = TiÉ£mert n ufella n yeffus — semsawi teÉ£zi +pdfjs-editor-resizer-label-middle-right = Talemmast tayeffust — semsawi teÉ£zi +pdfjs-editor-resizer-label-bottom-right = TiÉ£mert n wadda n yeffus — semsawi teÉ£zi +pdfjs-editor-resizer-label-bottom-middle = Talemmat n wadda — semsawi teÉ£zi +pdfjs-editor-resizer-label-bottom-left = TiÉ£mert n wadda n zelmeḠ— semsawi teÉ£zi +pdfjs-editor-resizer-label-middle-left = Talemmast tazelmdaá¸t — semsawi teÉ£zi +pdfjs-editor-resizer-top-left = + .aria-label = TiÉ£mert n ufella n zelmeḠ— semsawi teÉ£zi +pdfjs-editor-resizer-top-middle = + .aria-label = Talemmat n ufella — semsawi teÉ£zi +pdfjs-editor-resizer-top-right = + .aria-label = TiÉ£mert n ufella n yeffus — semsawi teÉ£zi +pdfjs-editor-resizer-middle-right = + .aria-label = Talemmast tayeffust — semsawi teÉ£zi +pdfjs-editor-resizer-bottom-right = + .aria-label = TiÉ£mert n wadda n yeffus — semsawi teÉ£zi +pdfjs-editor-resizer-bottom-middle = + .aria-label = Talemmat n wadda — semsawi teÉ£zi +pdfjs-editor-resizer-bottom-left = + .aria-label = TiÉ£mert n wadda n zelmeḠ— semsawi teÉ£zi +pdfjs-editor-resizer-middle-left = + .aria-label = Talemmast tazelmdaá¸t — semsawi teÉ£zi + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Ini n uderrer +pdfjs-editor-colorpicker-button = + .title = Senfel ini +pdfjs-editor-colorpicker-dropdown = + .aria-label = Afran n yiniten +pdfjs-editor-colorpicker-yellow = + .title = AwraÉ£ +pdfjs-editor-colorpicker-green = + .title = Azegzaw +pdfjs-editor-colorpicker-blue = + .title = Amidadi +pdfjs-editor-colorpicker-pink = + .title = Axuxi +pdfjs-editor-colorpicker-red = + .title = AzggaÉ£ + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Sken akk +pdfjs-editor-highlight-show-all-button = + .title = Sken akk + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Rnu aá¸ris niá¸en (aglam n tugna) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Aru aglam-ik dagi… +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Issin ugar +pdfjs-editor-new-alt-text-create-automatically-button-label = Rnu aá¸ris niá¸en s wudem awurman +pdfjs-editor-new-alt-text-not-now-button = MaÄÄi tura +pdfjs-editor-new-alt-text-error-title = D awezÉ£i timerna n uá¸ris niá¸en s wudem awurman +pdfjs-editor-new-alt-text-error-close-button = Mdel + +## Image alt-text settings + +pdfjs-editor-alt-text-settings-delete-model-button = Kkes +pdfjs-editor-alt-text-settings-download-model-button = Sader +pdfjs-editor-alt-text-settings-downloading-model-button = Asader… +pdfjs-editor-alt-text-settings-close-button = Mdel diff --git a/public/assets/pdfjs/locale/kk/viewer.ftl b/public/assets/pdfjs/locale/kk/viewer.ftl new file mode 100755 index 0000000..fb14226 --- /dev/null +++ b/public/assets/pdfjs/locale/kk/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Ðлдыңғы парақ +pdfjs-previous-button-label = ÐлдыңғыÑÑ‹ +pdfjs-next-button = + .title = КелеÑÑ– парақ +pdfjs-next-button-label = КелеÑÑ– +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Парақ +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = { $pagesCount } ішінен +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = (парақ { $pageNumber }, { $pagesCount } ішінен) +pdfjs-zoom-out-button = + .title = Кішірейту +pdfjs-zoom-out-button-label = Кішірейту +pdfjs-zoom-in-button = + .title = Үлкейту +pdfjs-zoom-in-button-label = Үлкейту +pdfjs-zoom-select = + .title = МаÑштаб +pdfjs-presentation-mode-button = + .title = ÐŸÑ€ÐµÐ·ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ Ñ€ÐµÐ¶Ð¸Ð¼Ñ–Ð½Ðµ ауыÑу +pdfjs-presentation-mode-button-label = ÐŸÑ€ÐµÐ·ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ Ñ€ÐµÐ¶Ð¸Ð¼Ñ– +pdfjs-open-file-button = + .title = Файлды ашу +pdfjs-open-file-button-label = Ðшу +pdfjs-print-button = + .title = БаÑпаға шығару +pdfjs-print-button-label = БаÑпаға шығару +pdfjs-save-button = + .title = Сақтау +pdfjs-save-button-label = Сақтау +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Жүктеп алу +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Жүктеп алу +pdfjs-bookmark-button = + .title = Ðғымдағы бет (Ðғымдағы беттен URL адреÑін көру) +pdfjs-bookmark-button-label = Ðғымдағы бет + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Құралдар +pdfjs-tools-button-label = Құралдар +pdfjs-first-page-button = + .title = Ðлғашқы параққа өту +pdfjs-first-page-button-label = Ðлғашқы параққа өту +pdfjs-last-page-button = + .title = Соңғы параққа өту +pdfjs-last-page-button-label = Соңғы параққа өту +pdfjs-page-rotate-cw-button = + .title = Сағат тілі бағытымен айналдыру +pdfjs-page-rotate-cw-button-label = Сағат тілі бағытымен бұру +pdfjs-page-rotate-ccw-button = + .title = Сағат тілі бағытына қарÑÑ‹ бұру +pdfjs-page-rotate-ccw-button-label = Сағат тілі бағытына қарÑÑ‹ бұру +pdfjs-cursor-text-select-tool-button = + .title = Мәтінді таңдау құралын Ñ–Ñке қоÑу +pdfjs-cursor-text-select-tool-button-label = Мәтінді таңдау құралы +pdfjs-cursor-hand-tool-button = + .title = Қол құралын Ñ–Ñке қоÑу +pdfjs-cursor-hand-tool-button-label = Қол құралы +pdfjs-scroll-page-button = + .title = Беттерді айналдыруды пайдалану +pdfjs-scroll-page-button-label = Беттерді айналдыру +pdfjs-scroll-vertical-button = + .title = Вертикалды айналдыруды қолдану +pdfjs-scroll-vertical-button-label = Вертикалды айналдыру +pdfjs-scroll-horizontal-button = + .title = Горизонталды айналдыруды қолдану +pdfjs-scroll-horizontal-button-label = Горизонталды айналдыру +pdfjs-scroll-wrapped-button = + .title = МаÑштабталатын айналдыруды қолдану +pdfjs-scroll-wrapped-button-label = МаÑштабталатын айналдыру +pdfjs-spread-none-button = + .title = Жазық беттер режимін қолданбау +pdfjs-spread-none-button-label = Жазық беттер режимÑіз +pdfjs-spread-odd-button = + .title = Жазық беттер тақ нөмірлі беттерден баÑталады +pdfjs-spread-odd-button-label = Тақ нөмірлі беттер Ñол жақтан +pdfjs-spread-even-button = + .title = Жазық беттер жұп нөмірлі беттерден баÑталады +pdfjs-spread-even-button-label = Жұп нөмірлі беттер Ñол жақтан + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Құжат қаÑиеттері… +pdfjs-document-properties-button-label = Құжат қаÑиеттері… +pdfjs-document-properties-file-name = Файл аты: +pdfjs-document-properties-file-size = Файл өлшемі: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } КБ ({ $b } байт) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } МБ ({ $b } байт) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } КБ ({ $size_b } байт) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } МБ ({ $size_b } байт) +pdfjs-document-properties-title = Тақырыбы: +pdfjs-document-properties-author = Ðвторы: +pdfjs-document-properties-subject = Тақырыбы: +pdfjs-document-properties-keywords = Кілт Ñөздер: +pdfjs-document-properties-creation-date = ЖаÑалған күні: +pdfjs-document-properties-modification-date = Түзету күні: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = ЖаÑаған: +pdfjs-document-properties-producer = PDF өндірген: +pdfjs-document-properties-version = PDF нұÑқаÑÑ‹: +pdfjs-document-properties-page-count = Беттер Ñаны: +pdfjs-document-properties-page-size = Бет өлшемі: +pdfjs-document-properties-page-size-unit-inches = дюйм +pdfjs-document-properties-page-size-unit-millimeters = мм +pdfjs-document-properties-page-size-orientation-portrait = тік +pdfjs-document-properties-page-size-orientation-landscape = жатық +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Жылдам Web көрініÑÑ–: +pdfjs-document-properties-linearized-yes = Иә +pdfjs-document-properties-linearized-no = Жоқ +pdfjs-document-properties-close-button = Жабу + +## Print + +pdfjs-print-progress-message = Құжатты баÑпаға шығару үшін дайындау… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Ð‘Ð°Ñ Ñ‚Ð°Ñ€Ñ‚Ñƒ +pdfjs-printing-not-supported = ЕÑкерту: БаÑпаға шығаруды бұл браузер толығымен қолдамайды. +pdfjs-printing-not-ready = ЕÑкерту: БаÑпаға шығару үшін, бұл PDF толығымен жүктеліп алынбады. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Бүйір панелін көрÑету/жаÑыру +pdfjs-toggle-sidebar-notification-button = + .title = Бүйір панелін көрÑету/жаÑыру (құжатта құрылымы/Ñалынымдар/қабаттар бар) +pdfjs-toggle-sidebar-button-label = Бүйір панелін көрÑету/жаÑыру +pdfjs-document-outline-button = + .title = Құжат құрылымын көрÑету (барлық нәрÑелерді жазық қылу/жинау үшін Ò›Ð¾Ñ ÑˆÐµÑ€Ñ‚Ñƒ керек) +pdfjs-document-outline-button-label = Құжат құрамаÑÑ‹ +pdfjs-attachments-button = + .title = Салынымдарды көрÑету +pdfjs-attachments-button-label = Салынымдар +pdfjs-layers-button = + .title = Қабаттарды көрÑету (барлық қабаттарды баÑтапқы күйге келтіру үшін екі рет шертіңіз) +pdfjs-layers-button-label = Қабаттар +pdfjs-thumbs-button = + .title = Кіші көрініÑтерді көрÑету +pdfjs-thumbs-button-label = Кіші көрініÑтер +pdfjs-current-outline-item-button = + .title = Құрылымның ағымдағы Ñлементін табу +pdfjs-current-outline-item-button-label = Құрылымның ағымдағы Ñлементі +pdfjs-findbar-button = + .title = Құжаттан табу +pdfjs-findbar-button-label = Табу +pdfjs-additional-layers = ҚоÑымша қабаттар + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = { $page } парағы +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = { $page } парағы үшін кіші көрініÑÑ– + +## Find panel button title and messages + +pdfjs-find-input = + .title = Табу + .placeholder = Құжаттан табу… +pdfjs-find-previous-button = + .title = ОÑÑ‹ Ñөздердің мәтіннен алдыңғы кездеÑуін табу +pdfjs-find-previous-button-label = ÐлдыңғыÑÑ‹ +pdfjs-find-next-button = + .title = ОÑÑ‹ Ñөздердің мәтіннен келеÑÑ– кездеÑуін табу +pdfjs-find-next-button-label = КелеÑÑ– +pdfjs-find-highlight-checkbox = Барлығын түÑпен ерекшелеу +pdfjs-find-match-case-checkbox-label = РегиÑтрді еÑкеру +pdfjs-find-match-diacritics-checkbox-label = Диакритиканы еÑкеру +pdfjs-find-entire-word-checkbox-label = Сөздер толығымен +pdfjs-find-reached-top = Құжаттың баÑына жеттік, Ñоңынан баÑтап жалғаÑтырамыз +pdfjs-find-reached-bottom = Құжаттың Ñоңына жеттік, баÑынан баÑтап жалғаÑтырамыз +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } ÑәйкеÑтік, барлығы { $total } + *[other] { $current } ÑәйкеÑтік, барлығы { $total } + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] { $limit } ÑәйкеÑтіктен көп + *[other] { $limit } ÑәйкеÑтіктен көп + } +pdfjs-find-not-found = Сөз(дер) табылмады + +## Predefined zoom values + +pdfjs-page-scale-width = Парақ ені +pdfjs-page-scale-fit = Парақты Ñыйдыру +pdfjs-page-scale-auto = ÐвтомаÑштабтау +pdfjs-page-scale-actual = Ðақты өлшемі +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Бет { $page } + +## Loading indicator messages + +pdfjs-loading-error = PDF жүктеу кезінде қате кетті. +pdfjs-invalid-file-error = Зақымдалған немеÑе қате PDF файл. +pdfjs-missing-file-error = PDF файлы жоқ. +pdfjs-unexpected-response-error = Сервердің күтпеген жауабы. +pdfjs-rendering-error = Парақты өңдеу кезінде қате кетті. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } аңдатпаÑÑ‹] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Бұл PDF файлын ашу үшін парольді енгізіңіз. +pdfjs-password-invalid = Пароль Ð´Ò±Ñ€Ñ‹Ñ ÐµÐ¼ÐµÑ. Қайталап көріңіз. +pdfjs-password-ok-button = ОК +pdfjs-password-cancel-button = Ð‘Ð°Ñ Ñ‚Ð°Ñ€Ñ‚Ñƒ +pdfjs-web-fonts-disabled = Веб қаріптері Ñөндірілген: құрамына енгізілген PDF қаріптерін қолдану мүмкін емеÑ. + +## Editing + +pdfjs-editor-free-text-button = + .title = Мәтін +pdfjs-editor-free-text-button-label = Мәтін +pdfjs-editor-ink-button = + .title = Сурет Ñалу +pdfjs-editor-ink-button-label = Сурет Ñалу +pdfjs-editor-stamp-button = + .title = Суреттерді қоÑу немеÑе түзету +pdfjs-editor-stamp-button-label = Суреттерді қоÑу немеÑе түзету +pdfjs-editor-highlight-button = + .title = Ерекшелеу +pdfjs-editor-highlight-button-label = Ерекшелеу +pdfjs-highlight-floating-button1 = + .title = Ерекшелеу + .aria-label = Ерекшелеу +pdfjs-highlight-floating-button-label = Ерекшелеу + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Сызбаны өшіру +pdfjs-editor-remove-freetext-button = + .title = Мәтінді өшіру +pdfjs-editor-remove-stamp-button = + .title = Суретті өшіру +pdfjs-editor-remove-highlight-button = + .title = ТүÑпен ерекшелеуді өшіру + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Ð¢Ò¯Ñ +pdfjs-editor-free-text-size-input = Өлшемі +pdfjs-editor-ink-color-input = Ð¢Ò¯Ñ +pdfjs-editor-ink-thickness-input = Қалыңдығы +pdfjs-editor-ink-opacity-input = МөлдірÑіздігі +pdfjs-editor-stamp-add-image-button = + .title = Суретті қоÑу +pdfjs-editor-stamp-add-image-button-label = Суретті қоÑу +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Қалыңдығы +pdfjs-editor-free-highlight-thickness-title = + .title = Мәтіннен баÑқа Ñлементтерді ерекшелеу кезінде қалыңдықты өзгерту +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Мәтін түзеткіші + .default-content = Теріп баÑтаңыз… +pdfjs-free-text = + .aria-label = Мәтін түзеткіші +pdfjs-free-text-default-content = Теруді баÑтау… +pdfjs-ink = + .aria-label = Сурет түзеткіші +pdfjs-ink-canvas = + .aria-label = Пайдаланушы жаÑаған Ñурет + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Балама мәтін +pdfjs-editor-alt-text-edit-button = + .aria-label = Балама мәтінді өңдеу +pdfjs-editor-alt-text-edit-button-label = Балама мәтінді өңдеу +pdfjs-editor-alt-text-dialog-label = ОпциÑны таңдау +pdfjs-editor-alt-text-dialog-description = Балама мәтін адамдар Ñуретті көре алмағанда немеÑе ол жүктелмегенде көмектеÑеді. +pdfjs-editor-alt-text-add-description-label = Сипаттаманы қоÑу +pdfjs-editor-alt-text-add-description-description = Тақырыпты, баптауды немеÑе әрекетті Ñипаттайтын 1-2 Ñөйлемді қолдануға тырыÑыңыз. +pdfjs-editor-alt-text-mark-decorative-label = Декоративті деп белгілеу +pdfjs-editor-alt-text-mark-decorative-description = Бұл жиектер немеÑе Ñу белгілері ÑиÑқты оюлық Ñуреттер үшін пайдаланылады. +pdfjs-editor-alt-text-cancel-button = Ð‘Ð°Ñ Ñ‚Ð°Ñ€Ñ‚Ñƒ +pdfjs-editor-alt-text-save-button = Сақтау +pdfjs-editor-alt-text-decorative-tooltip = Декоративті деп белгіленген +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = МыÑалы, "Ð–Ð°Ñ Ð¶Ñ–Ð³Ñ–Ñ‚ тамақ ішу үшін Ò¯Ñтел баÑына отырады" +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Балама мәтін + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Жоғарғы Ñол жақ бұрыш — өлшемін өзгерту +pdfjs-editor-resizer-label-top-middle = Жоғарғы ортаÑÑ‹ — өлшемін өзгерту +pdfjs-editor-resizer-label-top-right = Жоғарғы оң жақ бұрыш — өлшемін өзгерту +pdfjs-editor-resizer-label-middle-right = Ортаңғы оң жақ — өлшемін өзгерту +pdfjs-editor-resizer-label-bottom-right = Төменгі оң жақ бұрыш — өлшемін өзгерту +pdfjs-editor-resizer-label-bottom-middle = Төменгі ортаÑÑ‹ — өлшемін өзгерту +pdfjs-editor-resizer-label-bottom-left = Төменгі Ñол жақ бұрыш — өлшемін өзгерту +pdfjs-editor-resizer-label-middle-left = Ортаңғы Ñол жақ — өлшемін өзгерту +pdfjs-editor-resizer-top-left = + .aria-label = Жоғарғы Ñол жақ бұрыш — өлшемін өзгерту +pdfjs-editor-resizer-top-middle = + .aria-label = Жоғарғы ортаÑÑ‹ — өлшемін өзгерту +pdfjs-editor-resizer-top-right = + .aria-label = Жоғарғы оң жақ бұрыш — өлшемін өзгерту +pdfjs-editor-resizer-middle-right = + .aria-label = Ортаңғы оң жақ — өлшемін өзгерту +pdfjs-editor-resizer-bottom-right = + .aria-label = Төменгі оң жақ бұрыш — өлшемін өзгерту +pdfjs-editor-resizer-bottom-middle = + .aria-label = Төменгі ортаÑÑ‹ — өлшемін өзгерту +pdfjs-editor-resizer-bottom-left = + .aria-label = Төменгі Ñол жақ бұрыш — өлшемін өзгерту +pdfjs-editor-resizer-middle-left = + .aria-label = Ортаңғы Ñол жақ — өлшемін өзгерту + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Ерекшелеу түÑÑ– +pdfjs-editor-colorpicker-button = + .title = ТүÑті өзгерту +pdfjs-editor-colorpicker-dropdown = + .aria-label = Ð¢Ò¯Ñ Ñ‚Ð°Ò£Ð´Ð°ÑƒÐ»Ð°Ñ€Ñ‹ +pdfjs-editor-colorpicker-yellow = + .title = Сары +pdfjs-editor-colorpicker-green = + .title = ЖаÑыл +pdfjs-editor-colorpicker-blue = + .title = Көк +pdfjs-editor-colorpicker-pink = + .title = Қызғылт +pdfjs-editor-colorpicker-red = + .title = Қызыл + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Барлығын көрÑету +pdfjs-editor-highlight-show-all-button = + .title = Барлығын көрÑету + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Балама мәтінді өңдеу (Ñурет ÑипаттамаÑÑ‹) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Балама мәтінді қоÑу (Ñурет ÑипаттамаÑÑ‹) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Сипаттамаңызды оÑында жазыңыз… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Суретті көре алмайтын адамдар үшін немеÑе Ñурет жүктелмеген кезіне арналған қыÑқаша Ñипаттама. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Бұл балама мәтін автоматты түрде жаÑалды және дәлÑіз болуы мүмкін. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Көбірек білу +pdfjs-editor-new-alt-text-create-automatically-button-label = Балама мәтінді автоматты түрде жаÑау +pdfjs-editor-new-alt-text-not-now-button = Қазір ÐµÐ¼ÐµÑ +pdfjs-editor-new-alt-text-error-title = Балама мәтінді автоматты түрде жаÑау мүмкін болмады +pdfjs-editor-new-alt-text-error-description = Өзіңіздің балама мәтініңізді жазыңыз немеÑе кейінірек қайталап көріңіз. +pdfjs-editor-new-alt-text-error-close-button = Жабу +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Балама мәтін үшін ЖИ моделі жүктеп алынуда ({ $downloadedSize }/{ $totalSize } МБ) + .aria-valuetext = Балама мәтін үшін ЖИ моделі жүктеп алынуда ({ $downloadedSize }/{ $totalSize } МБ) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Балама мәтін қоÑылды +pdfjs-editor-new-alt-text-added-button-label = Балама мәтін қоÑылды +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Балама мәтін жоқ +pdfjs-editor-new-alt-text-missing-button-label = Балама мәтін жоқ +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Балама мәтінге пікір қалдыру +pdfjs-editor-new-alt-text-to-review-button-label = Балама мәтінге пікір қалдыру +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Ðвтоматты түрде жаÑалды: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Суреттің балама мәтінінің баптаулары +pdfjs-image-alt-text-settings-button-label = Суреттің балама мәтінінің баптаулары +pdfjs-editor-alt-text-settings-dialog-label = Суреттің балама мәтінінің баптаулары +pdfjs-editor-alt-text-settings-automatic-title = Ðвтоматты балама мәтін +pdfjs-editor-alt-text-settings-create-model-button-label = Балама мәтінді автоматты түрде жаÑау +pdfjs-editor-alt-text-settings-create-model-description = Суретті көре алмайтын адамдар үшін немеÑе Ñурет жүктелмеген кезіне арналған Ñипаттамаларды Ò±Ñынады. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Баламалы мәтіннің ЖИ моделі ({ $totalSize } МБ) +pdfjs-editor-alt-text-settings-ai-model-description = Деректеріңіз жеке болып қалуы үшін құрылғыңызда жергілікті түрде Ð¶Ò±Ð¼Ñ‹Ñ Ñ–Ñтейді. Ðвтоматты балама мәтін үшін қажет. +pdfjs-editor-alt-text-settings-delete-model-button = Өшіру +pdfjs-editor-alt-text-settings-download-model-button = Жүктеп алу +pdfjs-editor-alt-text-settings-downloading-model-button = Жүктеліп алынуда… +pdfjs-editor-alt-text-settings-editor-title = Баламалы мәтін редакторы +pdfjs-editor-alt-text-settings-show-dialog-button-label = Суретті қоÑқанда балама мәтін редакторын бірден көрÑету +pdfjs-editor-alt-text-settings-show-dialog-description = Барлық Ñуреттерде балама мәтін бар екеніне көз жеткізуге көмектеÑеді. +pdfjs-editor-alt-text-settings-close-button = Жабу + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Ерекшелеу өшірілді +pdfjs-editor-undo-bar-message-freetext = Мәтін өшірілді +pdfjs-editor-undo-bar-message-ink = Сызба өшірілді +pdfjs-editor-undo-bar-message-stamp = Сурет өшірілді +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } Ð°Ð½Ð¸Ð¼Ð°Ñ†Ð¸Ñ Ó©ÑˆÑ–Ñ€Ñ–Ð»Ð´Ñ– + *[other] { $count } Ð°Ð½Ð¸Ð¼Ð°Ñ†Ð¸Ñ Ó©ÑˆÑ–Ñ€Ñ–Ð»Ð´Ñ– + } +pdfjs-editor-undo-bar-undo-button = + .title = Болдырмау +pdfjs-editor-undo-bar-undo-button-label = Болдырмау +pdfjs-editor-undo-bar-close-button = + .title = Жабу +pdfjs-editor-undo-bar-close-button-label = Жабу diff --git a/public/assets/pdfjs/locale/km/viewer.ftl b/public/assets/pdfjs/locale/km/viewer.ftl new file mode 100755 index 0000000..6efd105 --- /dev/null +++ b/public/assets/pdfjs/locale/km/viewer.ftl @@ -0,0 +1,223 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = ទំពáŸážšâ€‹áž˜áž»áž“ +pdfjs-previous-button-label = មុន +pdfjs-next-button = + .title = ទំពáŸážšâ€‹áž”ន្ទាប់ +pdfjs-next-button-label = បន្ទាប់ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = ទំពáŸážš +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = នៃ { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } នៃ { $pagesCount }) +pdfjs-zoom-out-button = + .title = ​បង្រួម +pdfjs-zoom-out-button-label = ​បង្រួម +pdfjs-zoom-in-button = + .title = ​ពង្រីក +pdfjs-zoom-in-button-label = ​ពង្រីក +pdfjs-zoom-select = + .title = ពង្រីក +pdfjs-presentation-mode-button = + .title = ប្ដូរ​ទៅ​របៀប​បទ​បង្ហាញ +pdfjs-presentation-mode-button-label = របៀប​បទ​បង្ហាញ +pdfjs-open-file-button = + .title = បើក​ឯកសារ +pdfjs-open-file-button-label = បើក +pdfjs-print-button = + .title = បោះពុម្ព +pdfjs-print-button-label = បោះពុម្ព + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = ឧបករណ០+pdfjs-tools-button-label = ឧបករណ០+pdfjs-first-page-button = + .title = ទៅកាន់​ទំពáŸážšâ€‹ážŠáŸ†áž”ូង​ +pdfjs-first-page-button-label = ទៅកាន់​ទំពáŸážšâ€‹ážŠáŸ†áž”ូង​ +pdfjs-last-page-button = + .title = ទៅកាន់​ទំពáŸážšâ€‹áž…ុងក្រោយ​ +pdfjs-last-page-button-label = ទៅកាន់​ទំពáŸážšâ€‹áž…ុងក្រោយ +pdfjs-page-rotate-cw-button = + .title = បង្វិល​ស្រប​ទ្រនិច​នាឡិកា +pdfjs-page-rotate-cw-button-label = បង្វិល​ស្រប​ទ្រនិច​នាឡិកា +pdfjs-page-rotate-ccw-button = + .title = បង្វិល​ច្រាស​ទ្រនិច​នាឡិកា​​ +pdfjs-page-rotate-ccw-button-label = បង្វិល​ច្រាស​ទ្រនិច​នាឡិកា​​ +pdfjs-cursor-text-select-tool-button = + .title = បើក​ឧបករណáŸâ€‹áž‡áŸ’រើស​អážáŸ’ážáž”áž‘ +pdfjs-cursor-text-select-tool-button-label = ឧបករណáŸâ€‹áž‡áŸ’រើស​អážáŸ’ážáž”áž‘ +pdfjs-cursor-hand-tool-button = + .title = បើក​ឧបករណáŸâ€‹ážŠáŸƒ +pdfjs-cursor-hand-tool-button-label = ឧបករណáŸâ€‹ážŠáŸƒ + +## Document properties dialog + +pdfjs-document-properties-button = + .title = លក្ážážŽâ€‹ážŸáž˜áŸ’áž”ážáŸ’ážáž·â€‹áž¯áž€ážŸáž¶ážšâ€¦ +pdfjs-document-properties-button-label = លក្ážážŽâ€‹ážŸáž˜áŸ’áž”ážáŸ’ážáž·â€‹áž¯áž€ážŸáž¶ážšâ€¦ +pdfjs-document-properties-file-name = ឈ្មោះ​ឯកសារ៖ +pdfjs-document-properties-file-size = ទំហំ​ឯកសារ៖ +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } បៃ) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } បៃ) +pdfjs-document-properties-title = ចំណងជើង៖ +pdfjs-document-properties-author = អ្នក​និពន្ធ៖ +pdfjs-document-properties-subject = ប្រធានបទ៖ +pdfjs-document-properties-keywords = ពាក្យ​គន្លឹះ៖ +pdfjs-document-properties-creation-date = កាលបរិច្ឆáŸáž‘​បង្កើážáŸ– +pdfjs-document-properties-modification-date = កាលបរិច្ឆáŸáž‘​កែប្រែ៖ +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = អ្នក​បង្កើážáŸ– +pdfjs-document-properties-producer = កម្មវិធី​បង្កើហPDF ៖ +pdfjs-document-properties-version = កំណែ PDF ៖ +pdfjs-document-properties-page-count = ចំនួន​ទំពáŸážšáŸ– +pdfjs-document-properties-page-size-unit-inches = អ៊ីញ +pdfjs-document-properties-page-size-unit-millimeters = មម +pdfjs-document-properties-page-size-orientation-portrait = បញ្ឈរ +pdfjs-document-properties-page-size-orientation-landscape = ផ្ážáŸáž€ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = សំបុážáŸ’ážš + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +pdfjs-document-properties-linearized-yes = បាទ/ចាស +pdfjs-document-properties-linearized-no = ទ០+pdfjs-document-properties-close-button = បិទ + +## Print + +pdfjs-print-progress-message = កំពុង​រៀបចំ​ឯកសារ​សម្រាប់​បោះពុម្ព… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = បោះបង់ +pdfjs-printing-not-supported = ការ​ព្រមាន ៖ កា​រ​បោះពុម្ព​មិន​ážáŸ’រូវ​បាន​គាំទ្រ​ពáŸáž‰áž›áŸáž‰â€‹ážŠáŸ„យ​កម្មវិធី​រុករក​នáŸáŸ‡â€‹áž‘áŸÂ áŸ” +pdfjs-printing-not-ready = ព្រមាន៖ PDF មិន​ážáŸ’រូវ​បាន​ផ្ទុក​ទាំងស្រុង​ដើម្បី​បោះពុម្ព​ទáŸáŸ” + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = បិទ/បើក​គ្រាប់​រំកិល +pdfjs-toggle-sidebar-button-label = បិទ/បើក​គ្រាប់​រំកិល +pdfjs-document-outline-button = + .title = បង្ហាញ​គ្រោង​ឯកសារ (ចុច​ទ្វáŸâ€‹ážŠáž„​ដើម្បី​ពង្រីក/បង្រួម​ធាážáž»â€‹áž‘ាំងអស់) +pdfjs-document-outline-button-label = គ្រោង​ឯកសារ +pdfjs-attachments-button = + .title = បង្ហាញ​ឯកសារ​ភ្ជាប់ +pdfjs-attachments-button-label = ឯកសារ​ភ្ជាប់ +pdfjs-thumbs-button = + .title = បង្ហាញ​រូបភាព​ážáž¼áž…ៗ +pdfjs-thumbs-button-label = រួបភាព​ážáž¼áž…ៗ +pdfjs-findbar-button = + .title = រក​នៅ​ក្នុង​ឯកសារ +pdfjs-findbar-button-label = រក + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = ទំពáŸážš { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = រូបភាព​ážáž¼áž…​របស់​ទំពáŸážš { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = រក + .placeholder = រក​នៅ​ក្នុង​ឯកសារ... +pdfjs-find-previous-button = + .title = រក​ពាក្យ ឬ​ឃ្លា​ដែល​បាន​ជួប​មុន +pdfjs-find-previous-button-label = មុន +pdfjs-find-next-button = + .title = រក​ពាក្យ ឬ​ឃ្លា​ដែល​បាន​ជួប​បន្ទាប់ +pdfjs-find-next-button-label = បន្ទាប់ +pdfjs-find-highlight-checkbox = បន្លិច​ទាំងអស់ +pdfjs-find-match-case-checkbox-label = ករណី​ដំណូច +pdfjs-find-reached-top = បាន​បន្ážâ€‹áž–ី​ážáž¶áž„​ក្រោម ទៅ​ដល់​ážáž¶áž„​​លើ​នៃ​ឯកសារ +pdfjs-find-reached-bottom = បាន​បន្ážâ€‹áž–ី​ážáž¶áž„លើ ទៅដល់​ចុង​​នៃ​ឯកសារ +pdfjs-find-not-found = រក​មិន​ឃើញ​ពាក្យ ឬ​ឃ្លា + +## Predefined zoom values + +pdfjs-page-scale-width = ទទឹង​ទំពáŸážš +pdfjs-page-scale-fit = សម​ទំពáŸážš +pdfjs-page-scale-auto = ពង្រីក​ស្វáŸáž™áž”្រវážáŸ’ážáž· +pdfjs-page-scale-actual = ទំហំ​ជាក់ស្ដែង +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = មាន​កំហុស​បាន​កើážáž¡áž¾áž„​ពáŸáž›â€‹áž€áŸ†áž–ុង​ផ្ទុក PDF ។ +pdfjs-invalid-file-error = ឯកសារ PDF ážáž¼áž… ឬ​មិន​ážáŸ’រឹមážáŸ’រូវ ។ +pdfjs-missing-file-error = បាážáŸ‹â€‹áž¯áž€ážŸáž¶ážš PDF +pdfjs-unexpected-response-error = ការ​ឆ្លើយ​ážáž˜â€‹áž˜áŸ‰áž¶ážŸáŸŠáž¸áž“​មáŸâ€‹ážŠáŸ‚ល​មិន​បាន​រំពឹង។ +pdfjs-rendering-error = មាន​កំហុស​បាន​កើážáž¡áž¾áž„​ពáŸáž›â€‹áž”ង្ហាញ​ទំពáŸážšÂ áŸ” + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } ចំណារ​ពន្យល់] + +## Password + +pdfjs-password-label = បញ្ចូល​ពាក្យសម្ងាážáŸ‹â€‹ážŠáž¾áž˜áŸ’បី​បើក​ឯកសារ PDF áž“áŸáŸ‡áŸ” +pdfjs-password-invalid = ពាក្យសម្ងាážáŸ‹â€‹áž˜áž·áž“​ážáŸ’រឹមážáŸ’រូវ។ សូម​ព្យាយាម​ម្ដងទៀážáŸ” +pdfjs-password-ok-button = យល់​ព្រម +pdfjs-password-cancel-button = បោះបង់ +pdfjs-web-fonts-disabled = បាន​បិទ​ពុម្ពអក្សរ​បណ្ដាញ ៖ មិន​អាច​ប្រើ​ពុម្ពអក្សរ PDF ដែល​បាន​បង្កប់​បាន​ទáŸÂ áŸ” + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/kn/viewer.ftl b/public/assets/pdfjs/locale/kn/viewer.ftl new file mode 100755 index 0000000..0332255 --- /dev/null +++ b/public/assets/pdfjs/locale/kn/viewer.ftl @@ -0,0 +1,213 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = ಹಿಂದಿನ ಪà³à²Ÿ +pdfjs-previous-button-label = ಹಿಂದಿನ +pdfjs-next-button = + .title = ಮà³à²‚ದಿನ ಪà³à²Ÿ +pdfjs-next-button-label = ಮà³à²‚ದಿನ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = ಪà³à²Ÿ +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = { $pagesCount } ರಲà³à²²à²¿ +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pagesCount } ರಲà³à²²à²¿ { $pageNumber }) +pdfjs-zoom-out-button = + .title = ಕಿರಿದಾಗಿಸೠ+pdfjs-zoom-out-button-label = ಕಿರಿದಾಗಿಸಿ +pdfjs-zoom-in-button = + .title = ಹಿರಿದಾಗಿಸೠ+pdfjs-zoom-in-button-label = ಹಿರಿದಾಗಿಸಿ +pdfjs-zoom-select = + .title = ಗಾತà³à²°à²¬à²¦à²²à²¿à²¸à³ +pdfjs-presentation-mode-button = + .title = ಪà³à²°à²¸à³à²¤à³à²¤à²¿ (ಪà³à²°à²¸à³†à²‚ಟೇಶನà³) ಕà³à²°à²®à²•à³à²•ೆ ಬದಲಾಯಿಸೠ+pdfjs-presentation-mode-button-label = ಪà³à²°à²¸à³à²¤à³à²¤à²¿ (ಪà³à²°à²¸à³†à²‚ಟೇಶನà³) ಕà³à²°à²® +pdfjs-open-file-button = + .title = ಕಡತವನà³à²¨à³ ತೆರೆ +pdfjs-open-file-button-label = ತೆರೆಯಿರಿ +pdfjs-print-button = + .title = ಮà³à²¦à³à²°à²¿à²¸à³ +pdfjs-print-button-label = ಮà³à²¦à³à²°à²¿à²¸à²¿ + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = ಉಪಕರಣಗಳೠ+pdfjs-tools-button-label = ಉಪಕರಣಗಳೠ+pdfjs-first-page-button = + .title = ಮೊದಲ ಪà³à²Ÿà²•à³à²•ೆ ತೆರಳೠ+pdfjs-first-page-button-label = ಮೊದಲ ಪà³à²Ÿà²•à³à²•ೆ ತೆರಳೠ+pdfjs-last-page-button = + .title = ಕೊನೆಯ ಪà³à²Ÿà²•à³à²•ೆ ತೆರಳೠ+pdfjs-last-page-button-label = ಕೊನೆಯ ಪà³à²Ÿà²•à³à²•ೆ ತೆರಳೠ+pdfjs-page-rotate-cw-button = + .title = ಪà³à²°à²¦à²•à³à²·à²¿à²£à³†à²¯à²²à³à²²à²¿ ತಿರà³à²—ಿಸೠ+pdfjs-page-rotate-cw-button-label = ಪà³à²°à²¦à²•à³à²·à²¿à²£à³†à²¯à²²à³à²²à²¿ ತಿರà³à²—ಿಸೠ+pdfjs-page-rotate-ccw-button = + .title = ಅಪà³à²°à²¦à²•à³à²·à²¿à²£à³†à²¯à²²à³à²²à²¿ ತಿರà³à²—ಿಸೠ+pdfjs-page-rotate-ccw-button-label = ಅಪà³à²°à²¦à²•à³à²·à²¿à²£à³†à²¯à²²à³à²²à²¿ ತಿರà³à²—ಿಸೠ+pdfjs-cursor-text-select-tool-button = + .title = ಪಠà³à²¯ ಆಯà³à²•ೆ ಉಪಕರಣವನà³à²¨à³ ಸಕà³à²°à²¿à²¯à²—ೊಳಿಸಿ +pdfjs-cursor-text-select-tool-button-label = ಪಠà³à²¯ ಆಯà³à²•ೆಯ ಉಪಕರಣ +pdfjs-cursor-hand-tool-button = + .title = ಕೈ ಉಪಕರಣವನà³à²¨à³ ಸಕà³à²°à²¿à²¯à²—ೊಳಿಸಿ +pdfjs-cursor-hand-tool-button-label = ಕೈ ಉಪಕರಣ + +## Document properties dialog + +pdfjs-document-properties-button = + .title = ಡಾಕà³à²¯à³à²®à³†à²‚ಟà³â€Œ ಗà³à²£à²—ಳà³... +pdfjs-document-properties-button-label = ಡಾಕà³à²¯à³à²®à³†à²‚ಟà³â€Œ ಗà³à²£à²—ಳà³... +pdfjs-document-properties-file-name = ಕಡತದ ಹೆಸರà³: +pdfjs-document-properties-file-size = ಕಡತದ ಗಾತà³à²°: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } ಬೈಟà³â€à²—ಳà³) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } ಬೈಟà³â€à²—ಳà³) +pdfjs-document-properties-title = ಶೀರà³à²·à²¿à²•ೆ: +pdfjs-document-properties-author = ಕರà³à²¤à³ƒ: +pdfjs-document-properties-subject = ವಿಷಯ: +pdfjs-document-properties-keywords = ಮà³à²–à³à²¯à²ªà²¦à²—ಳà³: +pdfjs-document-properties-creation-date = ರಚಿಸಿದ ದಿನಾಂಕ: +pdfjs-document-properties-modification-date = ಮಾರà³à²ªà²¡à²¿à²¸à²²à²¾à²¦ ದಿನಾಂಕ: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = ರಚಿಸಿದವರà³: +pdfjs-document-properties-producer = PDF ಉತà³à²ªà²¾à²¦à²•: +pdfjs-document-properties-version = PDF ಆವೃತà³à²¤à²¿: +pdfjs-document-properties-page-count = ಪà³à²Ÿà²¦ ಎಣಿಕೆ: +pdfjs-document-properties-page-size-unit-inches = ಇದರಲà³à²²à²¿ +pdfjs-document-properties-page-size-orientation-portrait = ಭಾವಚಿತà³à²° +pdfjs-document-properties-page-size-orientation-landscape = ಪà³à²°à²•ೃತಿ ಚಿತà³à²° + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + + +## + +pdfjs-document-properties-close-button = ಮà³à²šà³à²šà³ + +## Print + +pdfjs-print-progress-message = ಮà³à²¦à³à²°à²¿à²¸à³à²µà³à²¦à²•à³à²•ಾಗಿ ದಸà³à²¤à²¾à²µà³‡à²œà²¨à³à²¨à³ ಸಿದà³à²§à²—ೊಳಿಸಲಾಗà³à²¤à³à²¤à²¿à²¦à³†â€¦ +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = ರದà³à²¦à³ ಮಾಡೠ+pdfjs-printing-not-supported = ಎಚà³à²šà²°à²¿à²•ೆ: ಈ ಜಾಲವೀಕà³à²·à²•ದಲà³à²²à²¿ ಮà³à²¦à³à²°à²£à²•à³à²•ೆ ಸಂಪೂರà³à²£ ಬೆಂಬಲವಿಲà³à²². +pdfjs-printing-not-ready = ಎಚà³à²šà²°à²¿à²•ೆ: PDF ಕಡತವೠಮà³à²¦à³à²°à²¿à²¸à²²à³ ಸಂಪೂರà³à²£à²µà²¾à²—ಿ ಲೋಡೠಆಗಿಲà³à²². + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = ಬದಿಪಟà³à²Ÿà²¿à²¯à²¨à³à²¨à³ ಹೊರಳಿಸೠ+pdfjs-toggle-sidebar-button-label = ಬದಿಪಟà³à²Ÿà²¿à²¯à²¨à³à²¨à³ ಹೊರಳಿಸೠ+pdfjs-document-outline-button-label = ದಸà³à²¤à²¾à²µà³‡à²œà²¿à²¨ ಹೊರರೇಖೆ +pdfjs-attachments-button = + .title = ಲಗತà³à²¤à³à²—ಳನà³à²¨à³ ತೋರಿಸೠ+pdfjs-attachments-button-label = ಲಗತà³à²¤à³à²—ಳೠ+pdfjs-thumbs-button = + .title = ಚಿಕà³à²•ಚಿತà³à²°à²¦à²‚ತೆ ತೋರಿಸೠ+pdfjs-thumbs-button-label = ಚಿಕà³à²•ಚಿತà³à²°à²—ಳೠ+pdfjs-findbar-button = + .title = ದಸà³à²¤à²¾à²µà³‡à²œà²¿à²¨à²²à³à²²à²¿ ಹà³à²¡à³à²•à³ +pdfjs-findbar-button-label = ಹà³à²¡à³à²•à³ + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = ಪà³à²Ÿ { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = ಪà³à²Ÿà²µà²¨à³à²¨à³ ಚಿಕà³à²•ಚಿತà³à²°à²¦à²‚ತೆ ತೋರಿಸೠ{ $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = ಹà³à²¡à³à²•à³ + .placeholder = ದಸà³à²¤à²¾à²µà³‡à²œà²¿à²¨à²²à³à²²à²¿ ಹà³à²¡à³à²•à³â€¦ +pdfjs-find-previous-button = + .title = ವಾಕà³à²¯à²¦ ಹಿಂದಿನ ಇರà³à²µà²¿à²•ೆಯನà³à²¨à³ ಹà³à²¡à³à²•à³ +pdfjs-find-previous-button-label = ಹಿಂದಿನ +pdfjs-find-next-button = + .title = ವಾಕà³à²¯à²¦ ಮà³à²‚ದಿನ ಇರà³à²µà²¿à²•ೆಯನà³à²¨à³ ಹà³à²¡à³à²•à³ +pdfjs-find-next-button-label = ಮà³à²‚ದಿನ +pdfjs-find-highlight-checkbox = ಎಲà³à²²à²µà²¨à³à²¨à³ ಹೈಲೈಟೠಮಾಡೠ+pdfjs-find-match-case-checkbox-label = ಕೇಸನà³à²¨à³ ಹೊಂದಿಸೠ+pdfjs-find-reached-top = ದಸà³à²¤à²¾à²µà³‡à²œà²¿à²¨ ಮೇಲà³à²­à²¾à²—ವನà³à²¨à³ ತಲà³à²ªà²¿à²¦à³†, ಕೆಳಗಿನಿಂದ ಆರಂಭಿಸೠ+pdfjs-find-reached-bottom = ದಸà³à²¤à²¾à²µà³‡à²œà²¿à²¨ ಕೊನೆಯನà³à²¨à³ ತಲà³à²ªà²¿à²¦à³†, ಮೇಲಿನಿಂದ ಆರಂಭಿಸೠ+pdfjs-find-not-found = ವಾಕà³à²¯à²µà³ ಕಂಡೠಬಂದಿಲà³à²² + +## Predefined zoom values + +pdfjs-page-scale-width = ಪà³à²Ÿà²¦ ಅಗಲ +pdfjs-page-scale-fit = ಪà³à²Ÿà²¦ ಸರಿಹೊಂದಿಕೆ +pdfjs-page-scale-auto = ಸà³à²µà²¯à²‚ಚಾಲಿತ ಗಾತà³à²°à²¬à²¦à²²à²¾à²µà²£à³† +pdfjs-page-scale-actual = ನಿಜವಾದ ಗಾತà³à²° +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = PDF ಅನà³à²¨à³ ಲೋಡೠಮಾಡà³à²µà²¾à²— ಒಂದೠದೋಷ ಎದà³à²°à²¾à²—ಿದೆ. +pdfjs-invalid-file-error = ಅಮಾನà³à²¯à²µà²¾à²¦ ಅಥವ ಹಾಳಾದ PDF ಕಡತ. +pdfjs-missing-file-error = PDF ಕಡತ ಇಲà³à²². +pdfjs-unexpected-response-error = ಅನಿರೀಕà³à²·à²¿à²¤à²µà²¾à²¦ ಪೂರೈಕೆಗಣಕದ ಪà³à²°à²¤à²¿à²•à³à²°à²¿à²¯à³†. +pdfjs-rendering-error = ಪà³à²Ÿà²µà²¨à³à²¨à³ ನಿರೂಪಿಸà³à²µà²¾à²— ಒಂದೠದೋಷ ಎದà³à²°à²¾à²—ಿದೆ. + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } ಟಿಪà³à²ªà²£à²¿] + +## Password + +pdfjs-password-label = PDF ಅನà³à²¨à³ ತೆರೆಯಲೠಗà³à²ªà³à²¤à²ªà²¦à²µà²¨à³à²¨à³ ನಮೂದಿಸಿ. +pdfjs-password-invalid = ಅಮಾನà³à²¯à²µà²¾à²¦ ಗà³à²ªà³à²¤à²ªà²¦, ದಯವಿಟà³à²Ÿà³ ಇನà³à²¨à³Šà²®à³à²®à³† ಪà³à²°à²¯à²¤à³à²¨à²¿à²¸à²¿. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = ರದà³à²¦à³ ಮಾಡೠ+pdfjs-web-fonts-disabled = ಜಾಲ ಅಕà³à²·à²°à²¶à³ˆà²²à²¿à²¯à²¨à³à²¨à³ ನಿಷà³à²•à³à²°à²¿à²¯à²—ೊಳಿಸಲಾಗಿದೆ: ಅಡಕಗೊಳಿಸಿದ PDF ಅಕà³à²·à²°à²¶à³ˆà²²à²¿à²—ಳನà³à²¨à³ ಬಳಸಲೠಸಾಧà³à²¯à²µà²¾à²—ಿಲà³à²². + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/ko/viewer.ftl b/public/assets/pdfjs/locale/ko/viewer.ftl new file mode 100755 index 0000000..a321a11 --- /dev/null +++ b/public/assets/pdfjs/locale/ko/viewer.ftl @@ -0,0 +1,503 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = ì´ì „ 페ì´ì§€ +pdfjs-previous-button-label = ì´ì „ +pdfjs-next-button = + .title = ë‹¤ìŒ íŽ˜ì´ì§€ +pdfjs-next-button-label = ë‹¤ìŒ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = 페ì´ì§€ +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = / { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } / { $pagesCount }) +pdfjs-zoom-out-button = + .title = 축소 +pdfjs-zoom-out-button-label = 축소 +pdfjs-zoom-in-button = + .title = 확대 +pdfjs-zoom-in-button-label = 확대 +pdfjs-zoom-select = + .title = 확대/축소 +pdfjs-presentation-mode-button = + .title = 프레젠테ì´ì…˜ 모드로 전환 +pdfjs-presentation-mode-button-label = 프레젠테ì´ì…˜ 모드 +pdfjs-open-file-button = + .title = íŒŒì¼ ì—´ê¸° +pdfjs-open-file-button-label = 열기 +pdfjs-print-button = + .title = ì¸ì‡„ +pdfjs-print-button-label = ì¸ì‡„ +pdfjs-save-button = + .title = 저장 +pdfjs-save-button-label = 저장 +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = 다운로드 +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = 다운로드 +pdfjs-bookmark-button = + .title = 현재 페ì´ì§€ (현재 페ì´ì§€ì—서 URL 보기) +pdfjs-bookmark-button-label = 현재 페ì´ì§€ + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = ë„구 +pdfjs-tools-button-label = ë„구 +pdfjs-first-page-button = + .title = 첫 페ì´ì§€ë¡œ ì´ë™ +pdfjs-first-page-button-label = 첫 페ì´ì§€ë¡œ ì´ë™ +pdfjs-last-page-button = + .title = 마지막 페ì´ì§€ë¡œ ì´ë™ +pdfjs-last-page-button-label = 마지막 페ì´ì§€ë¡œ ì´ë™ +pdfjs-page-rotate-cw-button = + .title = 시계방향으로 회전 +pdfjs-page-rotate-cw-button-label = 시계방향으로 회전 +pdfjs-page-rotate-ccw-button = + .title = 시계 반대방향으로 회전 +pdfjs-page-rotate-ccw-button-label = 시계 반대방향으로 회전 +pdfjs-cursor-text-select-tool-button = + .title = í…스트 ì„ íƒ ë„구 활성화 +pdfjs-cursor-text-select-tool-button-label = í…스트 ì„ íƒ ë„구 +pdfjs-cursor-hand-tool-button = + .title = ì† ë„구 활성화 +pdfjs-cursor-hand-tool-button-label = ì† ë„구 +pdfjs-scroll-page-button = + .title = 페ì´ì§€ 스í¬ë¡¤ 사용 +pdfjs-scroll-page-button-label = 페ì´ì§€ 스í¬ë¡¤ +pdfjs-scroll-vertical-button = + .title = 세로 스í¬ë¡¤ 사용 +pdfjs-scroll-vertical-button-label = 세로 스í¬ë¡¤ +pdfjs-scroll-horizontal-button = + .title = 가로 스í¬ë¡¤ 사용 +pdfjs-scroll-horizontal-button-label = 가로 스í¬ë¡¤ +pdfjs-scroll-wrapped-button = + .title = 래핑(ìžë™ 줄 바꿈) 스í¬ë¡¤ 사용 +pdfjs-scroll-wrapped-button-label = 래핑 스í¬ë¡¤ +pdfjs-spread-none-button = + .title = 한 페ì´ì§€ 보기 +pdfjs-spread-none-button-label = 펼침 ì—†ìŒ +pdfjs-spread-odd-button = + .title = 홀수 페ì´ì§€ë¡œ 시작하는 ë‘ íŽ˜ì´ì§€ 보기 +pdfjs-spread-odd-button-label = 홀수 펼침 +pdfjs-spread-even-button = + .title = ì§ìˆ˜ 페ì´ì§€ë¡œ 시작하는 ë‘ íŽ˜ì´ì§€ 보기 +pdfjs-spread-even-button-label = ì§ìˆ˜ 펼침 + +## Document properties dialog + +pdfjs-document-properties-button = + .title = 문서 ì†ì„±â€¦ +pdfjs-document-properties-button-label = 문서 ì†ì„±â€¦ +pdfjs-document-properties-file-name = íŒŒì¼ ì´ë¦„: +pdfjs-document-properties-file-size = íŒŒì¼ í¬ê¸°: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } ë°”ì´íЏ) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } ë°”ì´íЏ) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b }ë°”ì´íЏ) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b }ë°”ì´íЏ) +pdfjs-document-properties-title = 제목: +pdfjs-document-properties-author = 작성ìž: +pdfjs-document-properties-subject = 주제: +pdfjs-document-properties-keywords = 키워드: +pdfjs-document-properties-creation-date = 작성 ë‚ ì§œ: +pdfjs-document-properties-modification-date = 수정 ë‚ ì§œ: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = 작성 프로그램: +pdfjs-document-properties-producer = PDF 변환 소프트웨어: +pdfjs-document-properties-version = PDF 버전: +pdfjs-document-properties-page-count = 페ì´ì§€ 수: +pdfjs-document-properties-page-size = 페ì´ì§€ í¬ê¸°: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = 세로 ë°©í–¥ +pdfjs-document-properties-page-size-orientation-landscape = 가로 ë°©í–¥ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = 레터 +pdfjs-document-properties-page-size-name-legal = 리걸 + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = 빠른 웹 보기: +pdfjs-document-properties-linearized-yes = 예 +pdfjs-document-properties-linearized-no = 아니요 +pdfjs-document-properties-close-button = 닫기 + +## Print + +pdfjs-print-progress-message = ì¸ì‡„ 문서 준비 중… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = 취소 +pdfjs-printing-not-supported = 경고: ì´ ë¸Œë¼ìš°ì €ëŠ” ì¸ì‡„를 완전히 ì§€ì›í•˜ì§€ 않습니다. +pdfjs-printing-not-ready = 경고: ì´ PDF를 ì¸ì‡„를 í•  수 ìžˆì„ ì •ë„로 ì½ì–´ë“¤ì´ì§€ 못했습니다. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = 사ì´ë“œë°” 표시/숨기기 +pdfjs-toggle-sidebar-notification-button = + .title = 사ì´ë“œë°” 표시/숨기기 (ë¬¸ì„œì— ì•„ì›ƒë¼ì¸/첨부파ì¼/ë ˆì´ì–´ í¬í•¨ë¨) +pdfjs-toggle-sidebar-button-label = 사ì´ë“œë°” 표시/숨기기 +pdfjs-document-outline-button = + .title = 문서 아웃ë¼ì¸ 보기 (ë”블 í´ë¦­í•´ì„œ 모든 항목 펼치기/접기) +pdfjs-document-outline-button-label = 문서 아웃ë¼ì¸ +pdfjs-attachments-button = + .title = ì²¨ë¶€íŒŒì¼ ë³´ê¸° +pdfjs-attachments-button-label = ì²¨ë¶€íŒŒì¼ +pdfjs-layers-button = + .title = ë ˆì´ì–´ 보기 (ë”블 í´ë¦­í•´ì„œ 모든 ë ˆì´ì–´ë¥¼ 기본 ìƒíƒœë¡œ 재설정) +pdfjs-layers-button-label = ë ˆì´ì–´ +pdfjs-thumbs-button = + .title = 미리보기 +pdfjs-thumbs-button-label = 미리보기 +pdfjs-current-outline-item-button = + .title = 현재 아웃ë¼ì¸ 항목 찾기 +pdfjs-current-outline-item-button-label = 현재 아웃ë¼ì¸ 항목 +pdfjs-findbar-button = + .title = 검색 +pdfjs-findbar-button-label = 검색 +pdfjs-additional-layers = 추가 ë ˆì´ì–´ + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = { $page } 페ì´ì§€ +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = { $page } 페ì´ì§€ 미리보기 + +## Find panel button title and messages + +pdfjs-find-input = + .title = 찾기 + .placeholder = 문서ì—서 찾기… +pdfjs-find-previous-button = + .title = 지정 문ìžì—´ì— ì¼ì¹˜í•˜ëŠ” 1ê°œ ë¶€ë¶„ì„ ê²€ìƒ‰ +pdfjs-find-previous-button-label = ì´ì „ +pdfjs-find-next-button = + .title = 지정 문ìžì—´ì— ì¼ì¹˜í•˜ëŠ” ë‹¤ìŒ ë¶€ë¶„ì„ ê²€ìƒ‰ +pdfjs-find-next-button-label = ë‹¤ìŒ +pdfjs-find-highlight-checkbox = ëª¨ë‘ ê°•ì¡° 표시 +pdfjs-find-match-case-checkbox-label = 대/ì†Œë¬¸ìž êµ¬ë¶„ +pdfjs-find-match-diacritics-checkbox-label = ë¶„ìŒ ë¶€í˜¸ ì¼ì¹˜ +pdfjs-find-entire-word-checkbox-label = 단어 단위로 +pdfjs-find-reached-top = 문서 처ìŒê¹Œì§€ 검색하고 ë으로 ëŒì•„와 검색했습니다. +pdfjs-find-reached-bottom = 문서 ë까지 검색하고 앞으로 ëŒì•„와 검색했습니다. +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = { $current } / { $total } ì¼ì¹˜ +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = { $limit }ê°œ ì´ìƒ ì¼ì¹˜ +pdfjs-find-not-found = 검색 ê²°ê³¼ ì—†ìŒ + +## Predefined zoom values + +pdfjs-page-scale-width = 페ì´ì§€ ë„ˆë¹„ì— ë§žì¶”ê¸° +pdfjs-page-scale-fit = 페ì´ì§€ì— 맞추기 +pdfjs-page-scale-auto = ìžë™ +pdfjs-page-scale-actual = 실제 í¬ê¸° +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = { $page } 페ì´ì§€ + +## Loading indicator messages + +pdfjs-loading-error = PDF를 로드하는 ë™ì•ˆ 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. +pdfjs-invalid-file-error = 잘못ë˜ì—ˆê±°ë‚˜ ì†ìƒëœ PDF 파ì¼. +pdfjs-missing-file-error = PDF íŒŒì¼ ì—†ìŒ. +pdfjs-unexpected-response-error = 예기치 ì•Šì€ ì„œë²„ ì‘답입니다. +pdfjs-rendering-error = 페ì´ì§€ë¥¼ ë Œë”ë§í•˜ëŠ” ë™ì•ˆ 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date } { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } 주ì„] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = ì´ PDF 파ì¼ì„ ì—´ 수 있는 비밀번호를 입력하세요. +pdfjs-password-invalid = ìž˜ëª»ëœ ë¹„ë°€ë²ˆí˜¸ìž…ë‹ˆë‹¤. 다시 시ë„하세요. +pdfjs-password-ok-button = í™•ì¸ +pdfjs-password-cancel-button = 취소 +pdfjs-web-fonts-disabled = 웹 í°íŠ¸ê°€ 비활성화ë¨: ë‚´ìž¥ëœ PDF ê¸€ê¼´ì„ ì‚¬ìš©í•  수 없습니다. + +## Editing + +pdfjs-editor-free-text-button = + .title = í…스트 +pdfjs-editor-free-text-button-label = í…스트 +pdfjs-editor-ink-button = + .title = 그리기 +pdfjs-editor-ink-button-label = 그리기 +pdfjs-editor-stamp-button = + .title = ì´ë¯¸ì§€ 추가 ë˜ëŠ” 편집 +pdfjs-editor-stamp-button-label = ì´ë¯¸ì§€ 추가 ë˜ëŠ” 편집 +pdfjs-editor-highlight-button = + .title = ê°•ì¡° 표시 +pdfjs-editor-highlight-button-label = ê°•ì¡° 표시 +pdfjs-highlight-floating-button1 = + .title = ê°•ì¡° 표시 + .aria-label = ê°•ì¡° 표시 +pdfjs-highlight-floating-button-label = ê°•ì¡° 표시 + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = 그리기 제거 +pdfjs-editor-remove-freetext-button = + .title = í…스트 제거 +pdfjs-editor-remove-stamp-button = + .title = ì´ë¯¸ì§€ 제거 +pdfjs-editor-remove-highlight-button = + .title = ê°•ì¡° 표시 제거 + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = ìƒ‰ìƒ +pdfjs-editor-free-text-size-input = í¬ê¸° +pdfjs-editor-ink-color-input = ìƒ‰ìƒ +pdfjs-editor-ink-thickness-input = ë‘께 +pdfjs-editor-ink-opacity-input = ë¶ˆíˆ¬ëª…ë„ +pdfjs-editor-stamp-add-image-button = + .title = ì´ë¯¸ì§€ 추가 +pdfjs-editor-stamp-add-image-button-label = ì´ë¯¸ì§€ 추가 +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = ë‘께 +pdfjs-editor-free-highlight-thickness-title = + .title = í…스트 ì´ì™¸ì˜ í•­ëª©ì„ ê°•ì¡° 표시할 때 ë‘께 변경 +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = í…스트 편집기 + .default-content = ìž…ë ¥ì„ ì‹œìž‘í•˜ì„¸ìš”â€¦ +pdfjs-free-text = + .aria-label = í…스트 편집기 +pdfjs-free-text-default-content = 입력하세요… +pdfjs-ink = + .aria-label = 그리기 편집기 +pdfjs-ink-canvas = + .aria-label = ì‚¬ìš©ìž ìƒì„± ì´ë¯¸ì§€ + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = 대체 í…스트 +pdfjs-editor-alt-text-edit-button = + .aria-label = 대체 í…스트 편집 +pdfjs-editor-alt-text-edit-button-label = 대체 í…스트 편집 +pdfjs-editor-alt-text-dialog-label = ì˜µì…˜ì„ ì„ íƒí•˜ì„¸ìš” +pdfjs-editor-alt-text-dialog-description = 대체 í…스트는 ì‚¬ëžŒë“¤ì´ ì´ë¯¸ì§€ë¥¼ ë³¼ 수 없거나 ì´ë¯¸ì§€ê°€ 로드ë˜ì§€ ì•Šì„ ë•Œ ë„ì›€ì´ ë©ë‹ˆë‹¤. +pdfjs-editor-alt-text-add-description-label = 설명 추가 +pdfjs-editor-alt-text-add-description-description = 주제, 설정, ë™ìž‘ì„ ì„¤ëª…í•˜ëŠ” 1~2ê°œì˜ ë¬¸ìž¥ì„ ëª©í‘œë¡œ 하세요. +pdfjs-editor-alt-text-mark-decorative-label = 장ì‹ìš©ìœ¼ë¡œ 표시 +pdfjs-editor-alt-text-mark-decorative-description = í…Œë‘리나 워터마í¬ì™€ ê°™ì€ ìž¥ì‹ì ì¸ ì´ë¯¸ì§€ì— 사용ë©ë‹ˆë‹¤. +pdfjs-editor-alt-text-cancel-button = 취소 +pdfjs-editor-alt-text-save-button = 저장 +pdfjs-editor-alt-text-decorative-tooltip = 장ì‹ìš©ìœ¼ë¡œ í‘œì‹œë¨ +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = 예를 들어, “한 ì²­ë…„ì´ ì‹íƒì— 앉아 ì‹ì‚¬ë¥¼ 하고 있습니다.†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = 대체 í…스트 + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = 왼쪽 위 — í¬ê¸° ì¡°ì • +pdfjs-editor-resizer-label-top-middle = ê°€ìš´ë° ìœ„ - í¬ê¸° ì¡°ì • +pdfjs-editor-resizer-label-top-right = 오른쪽 위 — í¬ê¸° ì¡°ì • +pdfjs-editor-resizer-label-middle-right = 오른쪽 ê°€ìš´ë° â€” í¬ê¸° ì¡°ì • +pdfjs-editor-resizer-label-bottom-right = 오른쪽 아래 - í¬ê¸° ì¡°ì • +pdfjs-editor-resizer-label-bottom-middle = ê°€ìš´ë° ì•„ëž˜ — í¬ê¸° ì¡°ì • +pdfjs-editor-resizer-label-bottom-left = 왼쪽 아래 - í¬ê¸° ì¡°ì • +pdfjs-editor-resizer-label-middle-left = 왼쪽 ê°€ìš´ë° â€” í¬ê¸° ì¡°ì • +pdfjs-editor-resizer-top-left = + .aria-label = 왼쪽 위 — í¬ê¸° ì¡°ì • +pdfjs-editor-resizer-top-middle = + .aria-label = ê°€ìš´ë° ìœ„ - í¬ê¸° ì¡°ì • +pdfjs-editor-resizer-top-right = + .aria-label = 오른쪽 위 — í¬ê¸° ì¡°ì • +pdfjs-editor-resizer-middle-right = + .aria-label = 오른쪽 ê°€ìš´ë° â€” í¬ê¸° ì¡°ì • +pdfjs-editor-resizer-bottom-right = + .aria-label = 오른쪽 아래 - í¬ê¸° ì¡°ì • +pdfjs-editor-resizer-bottom-middle = + .aria-label = ê°€ìš´ë° ì•„ëž˜ — í¬ê¸° ì¡°ì • +pdfjs-editor-resizer-bottom-left = + .aria-label = 왼쪽 아래 - í¬ê¸° ì¡°ì • +pdfjs-editor-resizer-middle-left = + .aria-label = 왼쪽 ê°€ìš´ë° â€” í¬ê¸° ì¡°ì • + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = ìƒ‰ìƒ +pdfjs-editor-colorpicker-button = + .title = ìƒ‰ìƒ ë³€ê²½ +pdfjs-editor-colorpicker-dropdown = + .aria-label = ìƒ‰ìƒ ì„ íƒ +pdfjs-editor-colorpicker-yellow = + .title = 노란색 +pdfjs-editor-colorpicker-green = + .title = 녹색 +pdfjs-editor-colorpicker-blue = + .title = 파란색 +pdfjs-editor-colorpicker-pink = + .title = ë¶„í™ìƒ‰ +pdfjs-editor-colorpicker-red = + .title = 빨간색 + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = ëª¨ë‘ ë³´ê¸° +pdfjs-editor-highlight-show-all-button = + .title = ëª¨ë‘ ë³´ê¸° + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = 대체 í…스트 (ì´ë¯¸ì§€ 설명) 편집 +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = 대체 í…스트 (ì´ë¯¸ì§€ 설명) 추가 +pdfjs-editor-new-alt-text-textarea = + .placeholder = ì—¬ê¸°ì— ì„¤ëª…ì„ ìž‘ì„±í•˜ì„¸ìš”â€¦ +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = ì´ë¯¸ì§€ê°€ ë³´ì´ì§€ 않거나 ì´ë¯¸ì§€ê°€ 로딩ë˜ì§€ 않는 경우를 위한 간단한 설명입니다. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = ì´ ëŒ€ì²´ í…스트는 ìžë™ìœ¼ë¡œ ìƒì„±ë˜ì—ˆìœ¼ë¯€ë¡œ 정확하지 ì•Šì„ ìˆ˜ 있습니다. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = ë” ì•Œì•„ë³´ê¸° +pdfjs-editor-new-alt-text-create-automatically-button-label = ìžë™ìœ¼ë¡œ 대체 í…스트 ìƒì„± +pdfjs-editor-new-alt-text-not-now-button = ë‚˜ì¤‘ì— +pdfjs-editor-new-alt-text-error-title = 대체 í…스트를 ìžë™ìœ¼ë¡œ ìƒì„±í•  수 없습니다. +pdfjs-editor-new-alt-text-error-description = 대체 í…스트를 ì§ì ‘ 작성하거나 ë‚˜ì¤‘ì— ë‹¤ì‹œ 시ë„하세요. +pdfjs-editor-new-alt-text-error-close-button = 닫기 +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = 대체 í…스트 AI ëª¨ë¸ ë‹¤ìš´ë¡œë“œ 중 ({ $downloadedSize } / { $totalSize } MB) + .aria-valuetext = 대체 í…스트 AI ëª¨ë¸ ë‹¤ìš´ë¡œë“œ 중 ({ $downloadedSize } / { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = 대체 í…스트 ì¶”ê°€ë¨ +pdfjs-editor-new-alt-text-added-button-label = 대체 í…스트 ì¶”ê°€ë¨ +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = 대체 í…스트 ëˆ„ë½ +pdfjs-editor-new-alt-text-missing-button-label = 대체 í…스트 ëˆ„ë½ +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = 대체 í…스트 검토 +pdfjs-editor-new-alt-text-to-review-button-label = 대체 í…스트 검토 +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = ìžë™ìœ¼ë¡œ ìƒì„±ë¨: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = ì´ë¯¸ì§€ 대체 í…스트 설정 +pdfjs-image-alt-text-settings-button-label = ì´ë¯¸ì§€ 대체 í…스트 설정 +pdfjs-editor-alt-text-settings-dialog-label = ì´ë¯¸ì§€ 대체 í…스트 설정 +pdfjs-editor-alt-text-settings-automatic-title = ìžë™ 대체 í…스트 +pdfjs-editor-alt-text-settings-create-model-button-label = ìžë™ìœ¼ë¡œ 대체 í…스트 ìƒì„± +pdfjs-editor-alt-text-settings-create-model-description = ì´ë¯¸ì§€ê°€ ë³´ì´ì§€ 않거나 ì´ë¯¸ì§€ê°€ 로딩ë˜ì§€ ì•Šì„ ë•Œ ë„ì›€ì´ ë˜ëŠ” ì„¤ëª…ì„ ì œì•ˆí•©ë‹ˆë‹¤. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = 대체 í…스트 AI ëª¨ë¸ ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = 사용ìžì˜ 장치ì—서 로컬로 실행ë˜ë¯€ë¡œ ë°ì´í„°ê°€ 비공개로 유지ë©ë‹ˆë‹¤. ìžë™ 대체 í…ìŠ¤íŠ¸ì— í•„ìš”í•©ë‹ˆë‹¤. +pdfjs-editor-alt-text-settings-delete-model-button = ì‚­ì œ +pdfjs-editor-alt-text-settings-download-model-button = 다운로드 +pdfjs-editor-alt-text-settings-downloading-model-button = 다운로드 중… +pdfjs-editor-alt-text-settings-editor-title = 대체 í…스트 편집기 +pdfjs-editor-alt-text-settings-show-dialog-button-label = ì´ë¯¸ì§€ 추가 시 바로 대체 í…스트 편집기 표시 +pdfjs-editor-alt-text-settings-show-dialog-description = 모든 ì´ë¯¸ì§€ì— 대체 í…스트가 있는지 확ì¸í•˜ëŠ” ë° ë„ì›€ì´ ë©ë‹ˆë‹¤. +pdfjs-editor-alt-text-settings-close-button = 닫기 + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = ê°•ì¡° 표시 ì œê±°ë¨ +pdfjs-editor-undo-bar-message-freetext = í…스트 ì œê±°ë¨ +pdfjs-editor-undo-bar-message-ink = 그리기 ì œê±°ë¨ +pdfjs-editor-undo-bar-message-stamp = ì´ë¯¸ì§€ ì œê±°ë¨ +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = ì£¼ì„ { $count }ê°œ ì œê±°ë¨ +pdfjs-editor-undo-bar-undo-button = + .title = 실행 취소 +pdfjs-editor-undo-bar-undo-button-label = 실행 취소 +pdfjs-editor-undo-bar-close-button = + .title = 닫기 +pdfjs-editor-undo-bar-close-button-label = 닫기 diff --git a/public/assets/pdfjs/locale/lij/viewer.ftl b/public/assets/pdfjs/locale/lij/viewer.ftl new file mode 100755 index 0000000..b2941f9 --- /dev/null +++ b/public/assets/pdfjs/locale/lij/viewer.ftl @@ -0,0 +1,247 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Pagina primma +pdfjs-previous-button-label = Precedente +pdfjs-next-button = + .title = Pagina dòppo +pdfjs-next-button-label = Pròscima +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Pagina +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = de { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount }) +pdfjs-zoom-out-button = + .title = Diminoisci zoom +pdfjs-zoom-out-button-label = Diminoisci zoom +pdfjs-zoom-in-button = + .title = Aomenta zoom +pdfjs-zoom-in-button-label = Aomenta zoom +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Vanni into mòddo de prezentaçion +pdfjs-presentation-mode-button-label = Mòddo de prezentaçion +pdfjs-open-file-button = + .title = Arvi file +pdfjs-open-file-button-label = Arvi +pdfjs-print-button = + .title = Stanpa +pdfjs-print-button-label = Stanpa + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Atressi +pdfjs-tools-button-label = Atressi +pdfjs-first-page-button = + .title = Vanni a-a primma pagina +pdfjs-first-page-button-label = Vanni a-a primma pagina +pdfjs-last-page-button = + .title = Vanni a l'urtima pagina +pdfjs-last-page-button-label = Vanni a l'urtima pagina +pdfjs-page-rotate-cw-button = + .title = Gia into verso oraio +pdfjs-page-rotate-cw-button-label = Gia into verso oraio +pdfjs-page-rotate-ccw-button = + .title = Gia into verso antioraio +pdfjs-page-rotate-ccw-button-label = Gia into verso antioraio +pdfjs-cursor-text-select-tool-button = + .title = Abilita strumento de seleçion do testo +pdfjs-cursor-text-select-tool-button-label = Strumento de seleçion do testo +pdfjs-cursor-hand-tool-button = + .title = Abilita strumento man +pdfjs-cursor-hand-tool-button-label = Strumento man +pdfjs-scroll-vertical-button = + .title = Deuvia rebelamento verticale +pdfjs-scroll-vertical-button-label = Rebelamento verticale +pdfjs-scroll-horizontal-button = + .title = Deuvia rebelamento orizontâ +pdfjs-scroll-horizontal-button-label = Rebelamento orizontâ +pdfjs-scroll-wrapped-button = + .title = Deuvia rebelamento incapsolou +pdfjs-scroll-wrapped-button-label = Rebelamento incapsolou +pdfjs-spread-none-button = + .title = No unite a-a difuxon de pagina +pdfjs-spread-none-button-label = No difuxon +pdfjs-spread-odd-button = + .title = Uniscite a-a difuxon de pagina co-o numero dèspa +pdfjs-spread-odd-button-label = Difuxon dèspa +pdfjs-spread-even-button = + .title = Uniscite a-a difuxon de pagina co-o numero pari +pdfjs-spread-even-button-label = Difuxon pari + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Propietæ do documento… +pdfjs-document-properties-button-label = Propietæ do documento… +pdfjs-document-properties-file-name = Nomme schedaio: +pdfjs-document-properties-file-size = Dimenscion schedaio: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } kB ({ $size_b } byte) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } byte) +pdfjs-document-properties-title = Titolo: +pdfjs-document-properties-author = Aoto: +pdfjs-document-properties-subject = Ogetto: +pdfjs-document-properties-keywords = Paròlle ciave: +pdfjs-document-properties-creation-date = Dæta creaçion: +pdfjs-document-properties-modification-date = Dæta cangiamento: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Aotô originale: +pdfjs-document-properties-producer = Produtô PDF: +pdfjs-document-properties-version = Verscion PDF: +pdfjs-document-properties-page-count = Contezzo pagine: +pdfjs-document-properties-page-size = Dimenscion da pagina: +pdfjs-document-properties-page-size-unit-inches = dii gròsci +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = drito +pdfjs-document-properties-page-size-orientation-landscape = desteizo +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letia +pdfjs-document-properties-page-size-name-legal = Lezze + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Vista veloce do Web: +pdfjs-document-properties-linearized-yes = Sci +pdfjs-document-properties-linearized-no = No +pdfjs-document-properties-close-button = Særa + +## Print + +pdfjs-print-progress-message = Praparo o documento pe-a stanpa… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Anulla +pdfjs-printing-not-supported = Atençion: a stanpa a no l'é conpletamente soportâ da sto navegatô. +pdfjs-printing-not-ready = Atençion: o PDF o no l'é ancon caregou conpletamente pe-a stanpa. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Ativa/dizativa bara de scianco +pdfjs-toggle-sidebar-button-label = Ativa/dizativa bara de scianco +pdfjs-document-outline-button = + .title = Fanni vedde o contorno do documento (scicca doggio pe espande/ridue tutti i elementi) +pdfjs-document-outline-button-label = Contorno do documento +pdfjs-attachments-button = + .title = Fanni vedde alegæ +pdfjs-attachments-button-label = Alegæ +pdfjs-thumbs-button = + .title = Mostra miniatue +pdfjs-thumbs-button-label = Miniatue +pdfjs-findbar-button = + .title = Treuva into documento +pdfjs-findbar-button-label = Treuva + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Pagina { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatua da pagina { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Treuva + .placeholder = Treuva into documento… +pdfjs-find-previous-button = + .title = Treuva a ripetiçion precedente do testo da çercâ +pdfjs-find-previous-button-label = Precedente +pdfjs-find-next-button = + .title = Treuva a ripetiçion dòppo do testo da çercâ +pdfjs-find-next-button-label = Segoente +pdfjs-find-highlight-checkbox = Evidençia +pdfjs-find-match-case-checkbox-label = Maioscole/minoscole +pdfjs-find-entire-word-checkbox-label = Poula intrega +pdfjs-find-reached-top = Razonto a fin da pagina, continoa da l'iniçio +pdfjs-find-reached-bottom = Razonto l'iniçio da pagina, continoa da-a fin +pdfjs-find-not-found = Testo no trovou + +## Predefined zoom values + +pdfjs-page-scale-width = Larghessa pagina +pdfjs-page-scale-fit = Adatta a una pagina +pdfjs-page-scale-auto = Zoom aotomatico +pdfjs-page-scale-actual = Dimenscioin efetive +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = S'é verificou 'n'erô itno caregamento do PDF. +pdfjs-invalid-file-error = O schedaio PDF o l'é no valido ò aroinou. +pdfjs-missing-file-error = O schedaio PDF o no gh'é. +pdfjs-unexpected-response-error = Risposta inprevista do-u server +pdfjs-rendering-error = Gh'é stæto 'n'erô itno rendering da pagina. + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Anotaçion: { $type }] + +## Password + +pdfjs-password-label = Dimme a paròlla segreta pe arvî sto schedaio PDF. +pdfjs-password-invalid = Paròlla segreta sbalia. Preuva torna. +pdfjs-password-ok-button = Va ben +pdfjs-password-cancel-button = Anulla +pdfjs-web-fonts-disabled = I font do web en dizativæ: inposcibile adeuviâ i carateri do PDF. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/lo/viewer.ftl b/public/assets/pdfjs/locale/lo/viewer.ftl new file mode 100755 index 0000000..557e201 --- /dev/null +++ b/public/assets/pdfjs/locale/lo/viewer.ftl @@ -0,0 +1,313 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = ຫນ້າàºà»ˆàº­àº™àº«àº™à»‰àº² +pdfjs-previous-button-label = àºà»ˆàº­àº™àº«àº™à»‰àº² +pdfjs-next-button = + .title = ຫນ້າຖັດໄປ +pdfjs-next-button-label = ຖັດໄປ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = ຫນ້າ +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = ຈາຠ{ $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } ຈາຠ{ $pagesCount }) +pdfjs-zoom-out-button = + .title = ຂະຫàºàº²àºàº­àº­àº +pdfjs-zoom-out-button-label = ຂະຫàºàº²àºàº­àº­àº +pdfjs-zoom-in-button = + .title = ຂະຫàºàº²àºà»€àº‚ົ້າ +pdfjs-zoom-in-button-label = ຂະຫàºàº²àºà»€àº‚ົ້າ +pdfjs-zoom-select = + .title = ຂະຫàºàº²àº +pdfjs-presentation-mode-button = + .title = ສັບປ່ຽນເປັນໂຫມດàºàº²àº™àº™àº³àºªàº°à»€àº«àº™àºµ +pdfjs-presentation-mode-button-label = ໂຫມດàºàº²àº™àº™àº³àºªàº°à»€àº«àº™àºµ +pdfjs-open-file-button = + .title = ເປີດໄຟລ໌ +pdfjs-open-file-button-label = ເປີດ +pdfjs-print-button = + .title = ພິມ +pdfjs-print-button-label = ພິມ +pdfjs-save-button = + .title = ບັນທຶຠ+pdfjs-save-button-label = ບັນທຶຠ+pdfjs-bookmark-button = + .title = ໜ້າປັດຈຸບັນ (ເບິ່ງ URL ຈາàºà»œà»‰àº²àº›àº±àº”ຈຸບັນ) +pdfjs-bookmark-button-label = ຫນ້າ​ປັດ​ຈຸ​ບັນ + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = ເຄື່ອງມື +pdfjs-tools-button-label = ເຄື່ອງມື +pdfjs-first-page-button = + .title = ໄປທີ່ຫນ້າທຳອິດ +pdfjs-first-page-button-label = ໄປທີ່ຫນ້າທຳອິດ +pdfjs-last-page-button = + .title = ໄປທີ່ຫນ້າສຸດທ້າຠ+pdfjs-last-page-button-label = ໄປທີ່ຫນ້າສຸດທ້າຠ+pdfjs-page-rotate-cw-button = + .title = ຫມູນຕາມເຂັມໂມງ +pdfjs-page-rotate-cw-button-label = ຫມູນຕາມເຂັມໂມງ +pdfjs-page-rotate-ccw-button = + .title = ຫມູນທວນເຂັມໂມງ +pdfjs-page-rotate-ccw-button-label = ຫມູນທວນເຂັມໂມງ +pdfjs-cursor-text-select-tool-button = + .title = ເປີດໃຊ້ເຄື່ອງມືàºàº²àº™à»€àº¥àº·àº­àºàº‚à»à»‰àº„ວາມ +pdfjs-cursor-text-select-tool-button-label = ເຄື່ອງມືເລືອàºàº‚à»à»‰àº„ວາມ +pdfjs-cursor-hand-tool-button = + .title = ເປີດໃຊ້ເຄື່ອງມືມື +pdfjs-cursor-hand-tool-button-label = ເຄື່ອງມືມື +pdfjs-scroll-page-button = + .title = ໃຊ້àºàº²àº™à»€àº¥àº·à»ˆàº­àº™à»œà»‰àº² +pdfjs-scroll-page-button-label = ເລື່ອນໜ້າ +pdfjs-scroll-vertical-button = + .title = ໃຊ້àºàº²àº™à»€àº¥àº·à»ˆàº­àº™à»àº™àº§àº•ັ້ງ +pdfjs-scroll-vertical-button-label = ເລື່ອນà»àº™àº§àº•ັ້ງ +pdfjs-scroll-horizontal-button = + .title = ໃຊ້àºàº²àº™à»€àº¥àº·à»ˆàº­àº™à»àº™àº§àº™àº­àº™ +pdfjs-scroll-horizontal-button-label = ເລື່ອນà»àº™àº§àº™àº­àº™ +pdfjs-scroll-wrapped-button = + .title = ໃຊ້ Wrapped Scrolling +pdfjs-scroll-wrapped-button-label = Wrapped Scrolling +pdfjs-spread-none-button = + .title = ບà»à»ˆàº•້ອງຮ່ວມàºàº²àº™à»àºœà»ˆàºàº°àºˆàº²àºàº«àº™à»‰àº² +pdfjs-spread-none-button-label = ບà»à»ˆàº¡àºµàºàº²àº™à»àºœà»ˆàºàº°àºˆàº²àº +pdfjs-spread-odd-button = + .title = ເຂົ້າຮ່ວມàºàº²àº™à»àºœà»ˆàºàº°àºˆàº²àºàº«àº™à»‰àº²à»€àº¥àºµà»ˆàº¡àº•ົ້ນດ້ວàºàº«àº™à»‰àº²à»€àº¥àºàº„ີຠ+pdfjs-spread-odd-button-label = àºàº²àº™à»àºœà»ˆàºàº°àºˆàº²àºàº„ີຠ+pdfjs-spread-even-button = + .title = ເຂົ້າຮ່ວມàºàº²àº™à»àºœà»ˆàºàº°àºˆàº²àºàº‚ອງຫນ້າເລີ່ມຕົ້ນດ້ວàºàº«àº™à»‰àº²à»€àº¥àºàº„ູ່ +pdfjs-spread-even-button-label = àºàº²àº™à»àºœà»ˆàºàº°àºˆàº²àºàº„ູ່ + +## Document properties dialog + +pdfjs-document-properties-button = + .title = ຄຸນສົມບັດເອàºàº°àºªàº²àº™... +pdfjs-document-properties-button-label = ຄຸນສົມບັດເອàºàº°àºªàº²àº™... +pdfjs-document-properties-file-name = ຊື່ໄຟລ໌: +pdfjs-document-properties-file-size = ຂະຫນາດໄຟລ໌: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } ໄບຕ໌) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } ໄບຕ໌) +pdfjs-document-properties-title = ຫົວຂà»à»‰: +pdfjs-document-properties-author = ຜູ້ຂຽນ: +pdfjs-document-properties-subject = ຫົວຂà»à»‰: +pdfjs-document-properties-keywords = ຄà»àº²àº—ີ່ຕ້ອງàºàº²àº™àº„ົ້ນຫາ: +pdfjs-document-properties-creation-date = ວັນທີສ້າງ: +pdfjs-document-properties-modification-date = ວັນທີà»àºà»‰à»„ຂ: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = ຜູ້ສ້າງ: +pdfjs-document-properties-producer = ຜູ້ຜະລິດ PDF: +pdfjs-document-properties-version = ເວີຊັ່ນ PDF: +pdfjs-document-properties-page-count = ຈຳນວນໜ້າ: +pdfjs-document-properties-page-size = ຂະໜາດໜ້າ: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = ລວງຕັ້ງ +pdfjs-document-properties-page-size-orientation-landscape = ລວງນອນ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = ຈົດà»àº²àº +pdfjs-document-properties-page-size-name-legal = ຂà»à»‰àºàº»àº”ຫມາຠ+ +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = ມຸມມອງເວັບທີ່ໄວ: +pdfjs-document-properties-linearized-yes = à»àº¡à»ˆàº™ +pdfjs-document-properties-linearized-no = ບà»à»ˆ +pdfjs-document-properties-close-button = ປິດ + +## Print + +pdfjs-print-progress-message = àºàº³àº¥àº±àº‡àºàº°àºàº½àº¡à»€àº­àºàº°àºªàº²àº™àºªàº³àº¥àº±àºšàºàº²àº™àºžàº´àº¡... +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = àºàº»àºà»€àº¥àºµàº +pdfjs-printing-not-supported = ຄຳເຕືອນ: ບຼາວເຊີນີ້ບà»à»ˆàº®àº­àº‡àº®àº±àºšàºàº²àº™àºžàº´àº¡àº¢à»ˆàº²àº‡à»€àº•ັມທີ່. +pdfjs-printing-not-ready = ຄà»àº²â€‹à»€àº•ືອນ​: PDF ບà»à»ˆâ€‹à»„ດ້​ຖືàºâ€‹à»‚ຫຼດ​ຢ່າງ​ເຕັມ​ທີ່​ສà»àº²â€‹àº¥àº±àºšâ€‹àºàº²àº™â€‹àºžàº´àº¡â€‹. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = ເປີດ/ປິດà»àº–ບຂ້າງ +pdfjs-toggle-sidebar-notification-button = + .title = ສະຫຼັບà»àº–ບດ້ານຂ້າງ (ເອàºàº°àºªàº²àº™àº›àº°àºàº­àºšàº¡àºµà»‚ຄງຮ່າງ/ໄຟລ໌à»àº™àºš/ຊັ້ນຂà»à»‰àº¡àº¹àº™) +pdfjs-toggle-sidebar-button-label = ເປີດ/ປິດà»àº–ບຂ້າງ +pdfjs-document-outline-button = + .title = ສະ​à»àº”ງ​ໂຄງ​ຮ່າງ​ເອ​àºàº°â€‹àºªàº²àº™ (àºàº»àº”​ສອງ​ຄັ້ງ​ເພື່ອ​ຂະ​ຫàºàº²àº / ຫàºà»à»‰â€‹àº¥àº²àºâ€‹àºàº²àº™â€‹àº—ັງ​ຫມົດ​) +pdfjs-document-outline-button-label = ເຄົ້າຮ່າງເອàºàº°àºªàº²àº™ +pdfjs-attachments-button = + .title = ສະà»àº”ງໄຟລ໌à»àº™àºš +pdfjs-attachments-button-label = ໄຟລ໌à»àº™àºš +pdfjs-layers-button = + .title = ສະà»àº”ງຊັ້ນຂà»à»‰àº¡àº¹àº™ (ຄລິàºàºªàº­àº‡à»€àº—ື່ອເພື່ອຣີເຊັດຊັ້ນຂà»à»‰àº¡àº¹àº™àº—ັງà»àº»àº”ໃຫ້ເປັນສະຖານະເລີ່ມຕົ້ນ) +pdfjs-layers-button-label = ຊັ້ນ +pdfjs-thumbs-button = + .title = ສະà»àº”ງຮູບຫàºà»à»‰ +pdfjs-thumbs-button-label = ຮູບຕົວຢ່າງ +pdfjs-current-outline-item-button = + .title = ຊອàºàº«àº²àº¥àº²àºàºàº²àº™à»‚ຄງຮ່າງປະຈຸບັນ +pdfjs-current-outline-item-button-label = ລາàºàºàº²àº™à»‚ຄງຮ່າງປະຈຸບັນ +pdfjs-findbar-button = + .title = ຊອàºàº«àº²à»ƒàº™à»€àº­àºàº°àºªàº²àº™ +pdfjs-findbar-button-label = ຄົ້ນຫາ +pdfjs-additional-layers = ຊັ້ນຂà»à»‰àº¡àº¹àº™à»€àºžàºµà»ˆàº¡à»€àº•ີມ + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = ໜ້າ { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = ຮູບຕົວຢ່າງຂອງໜ້າ { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = ຄົ້ນຫາ + .placeholder = ຊອàºàº«àº²à»ƒàº™à»€àº­àºàº°àºªàº²àº™... +pdfjs-find-previous-button = + .title = ຊອàºàº«àº²àºàº²àº™àº›àº°àºàº»àº”ຕົວທີ່ຜ່ານມາຂອງປະໂຫàºàº +pdfjs-find-previous-button-label = àºà»ˆàº­àº™àº«àº™à»‰àº²àº™àºµà»‰ +pdfjs-find-next-button = + .title = ຊອàºàº«àº²àº•ຳà»àº«àº™à»ˆàº‡àº–ັດໄປຂອງວະລີ +pdfjs-find-next-button-label = ຕà»à»ˆà»„ປ +pdfjs-find-highlight-checkbox = ໄຮໄລທ໌ທັງຫມົດ +pdfjs-find-match-case-checkbox-label = àºà»àº¥àº°àº™àºµàº—ີ່àºàº»àº‡àºàº±àº™ +pdfjs-find-match-diacritics-checkbox-label = ເຄື່ອງà»àº²àºàºàº³àºàº±àºšàºàº²àº™àº­àº­àºàºªàº½àº‡àºàº»àº‡àºàº±àº™ +pdfjs-find-entire-word-checkbox-label = àºàº»àº‡àºàº±àº™àº—ຸàºàº„ຳ +pdfjs-find-reached-top = ມາຮອດເທິງຂອງເອàºàº°àºªàº²àº™, ສືບຕà»à»ˆàºˆàº²àºàº¥àº¸à»ˆàº¡ +pdfjs-find-reached-bottom = ຮອດຕອນທ້າàºàº‚ອງເອàºàº°àºªàº²àº™, ສືບຕà»à»ˆàºˆàº²àºà»€àº—ິງ +pdfjs-find-not-found = ບà»à»ˆàºžàº»àºšàº§àº°àº¥àºµàº—ີ່ຕ້ອງàºàº²àº™ + +## Predefined zoom values + +pdfjs-page-scale-width = ຄວາມàºàº§à»‰àº²àº‡à»œà»‰àº² +pdfjs-page-scale-fit = ໜ້າພà»àº”ີ +pdfjs-page-scale-auto = ຊູມອັດຕະໂນມັດ +pdfjs-page-scale-actual = ຂະໜາດຕົວຈິງ +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = ໜ້າ { $page } + +## Loading indicator messages + +pdfjs-loading-error = ມີຂà»à»‰àºœàº´àº”ພາດເàºàºµàº”ຂື້ນຂະນະທີ່àºàº³àº¥àº±àº‡à»‚ຫລດ PDF. +pdfjs-invalid-file-error = ໄຟລ໌ PDF ບà»à»ˆàº–ືàºàº•້ອງຫລືເສàºàº«àº²àº. +pdfjs-missing-file-error = ບà»à»ˆàº¡àºµà»„ຟລ໌ PDF. +pdfjs-unexpected-response-error = àºàº²àº™àº•ອບສະໜອງຂອງເຊີບເວີທີ່ບà»à»ˆàº„າດຄິດ. +pdfjs-rendering-error = ມີຂà»à»‰àºœàº´àº”ພາດເàºàºµàº”ຂື້ນຂະນະທີ່àºàº³àº¥àº±àº‡à»€àº£àº±àº™à»€àº”ີຫນ້າ. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } ຄຳບັນàºàº²àº] + +## Password + +pdfjs-password-label = ໃສ່ລະຫັດຜ່ານເພື່ອເປີດໄຟລ໌ PDF ນີ້. +pdfjs-password-invalid = ລະຫັດຜ່ານບà»à»ˆàº–ືàºàº•້ອງ. àºàº°àº¥àº¸àº™àº²àº¥àº­àº‡àº­àºµàºàº„ັ້ງ. +pdfjs-password-ok-button = ຕົàºàº¥àº»àº‡ +pdfjs-password-cancel-button = àºàº»àºà»€àº¥àºµàº +pdfjs-web-fonts-disabled = ຟອນເວັບຖືàºàº›àº´àº”ໃຊ້ງານ: ບà»à»ˆàºªàº²àº¡àº²àº”ໃຊ້ຟອນ PDF ທີ່àºàº±àº‡à»„ວ້ໄດ້. + +## Editing + +pdfjs-editor-free-text-button = + .title = ຂà»à»‰àº„ວາມ +pdfjs-editor-free-text-button-label = ຂà»à»‰àº„ວາມ +pdfjs-editor-ink-button = + .title = à»àº•້ມ +pdfjs-editor-ink-button-label = à»àº•້ມ + +## Remove button for the various kind of editor. + + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = ສີ +pdfjs-editor-free-text-size-input = ຂະຫນາດ +pdfjs-editor-ink-color-input = ສີ +pdfjs-editor-ink-thickness-input = ຄວາມຫນາ +pdfjs-editor-ink-opacity-input = ຄວາມໂປ່ງໃສ +pdfjs-free-text = + .aria-label = ຕົວà»àºà»‰à»„ຂຂà»à»‰àº„ວາມ +pdfjs-free-text-default-content = ເລີ່ມພິມ... +pdfjs-ink = + .aria-label = ຕົວà»àºà»‰à»„ຂຮູບà»àº•້ມ +pdfjs-ink-canvas = + .aria-label = ຮູບພາບທີ່ຜູ້ໃຊ້ສ້າງ + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + + +## Color picker + + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + + +## Image alt-text settings + diff --git a/public/assets/pdfjs/locale/locale.json b/public/assets/pdfjs/locale/locale.json new file mode 100755 index 0000000..2012211 --- /dev/null +++ b/public/assets/pdfjs/locale/locale.json @@ -0,0 +1 @@ +{"ach":"ach/viewer.ftl","af":"af/viewer.ftl","an":"an/viewer.ftl","ar":"ar/viewer.ftl","ast":"ast/viewer.ftl","az":"az/viewer.ftl","be":"be/viewer.ftl","bg":"bg/viewer.ftl","bn":"bn/viewer.ftl","bo":"bo/viewer.ftl","br":"br/viewer.ftl","brx":"brx/viewer.ftl","bs":"bs/viewer.ftl","ca":"ca/viewer.ftl","cak":"cak/viewer.ftl","ckb":"ckb/viewer.ftl","cs":"cs/viewer.ftl","cy":"cy/viewer.ftl","da":"da/viewer.ftl","de":"de/viewer.ftl","dsb":"dsb/viewer.ftl","el":"el/viewer.ftl","en-ca":"en-CA/viewer.ftl","en-gb":"en-GB/viewer.ftl","en-us":"en-US/viewer.ftl","eo":"eo/viewer.ftl","es-ar":"es-AR/viewer.ftl","es-cl":"es-CL/viewer.ftl","es-es":"es-ES/viewer.ftl","es-mx":"es-MX/viewer.ftl","et":"et/viewer.ftl","eu":"eu/viewer.ftl","fa":"fa/viewer.ftl","ff":"ff/viewer.ftl","fi":"fi/viewer.ftl","fr":"fr/viewer.ftl","fur":"fur/viewer.ftl","fy-nl":"fy-NL/viewer.ftl","ga-ie":"ga-IE/viewer.ftl","gd":"gd/viewer.ftl","gl":"gl/viewer.ftl","gn":"gn/viewer.ftl","gu-in":"gu-IN/viewer.ftl","he":"he/viewer.ftl","hi-in":"hi-IN/viewer.ftl","hr":"hr/viewer.ftl","hsb":"hsb/viewer.ftl","hu":"hu/viewer.ftl","hy-am":"hy-AM/viewer.ftl","hye":"hye/viewer.ftl","ia":"ia/viewer.ftl","id":"id/viewer.ftl","is":"is/viewer.ftl","it":"it/viewer.ftl","ja":"ja/viewer.ftl","ka":"ka/viewer.ftl","kab":"kab/viewer.ftl","kk":"kk/viewer.ftl","km":"km/viewer.ftl","kn":"kn/viewer.ftl","ko":"ko/viewer.ftl","lij":"lij/viewer.ftl","lo":"lo/viewer.ftl","lt":"lt/viewer.ftl","ltg":"ltg/viewer.ftl","lv":"lv/viewer.ftl","meh":"meh/viewer.ftl","mk":"mk/viewer.ftl","mr":"mr/viewer.ftl","ms":"ms/viewer.ftl","my":"my/viewer.ftl","nb-no":"nb-NO/viewer.ftl","ne-np":"ne-NP/viewer.ftl","nl":"nl/viewer.ftl","nn-no":"nn-NO/viewer.ftl","oc":"oc/viewer.ftl","pa-in":"pa-IN/viewer.ftl","pl":"pl/viewer.ftl","pt-br":"pt-BR/viewer.ftl","pt-pt":"pt-PT/viewer.ftl","rm":"rm/viewer.ftl","ro":"ro/viewer.ftl","ru":"ru/viewer.ftl","sat":"sat/viewer.ftl","sc":"sc/viewer.ftl","scn":"scn/viewer.ftl","sco":"sco/viewer.ftl","si":"si/viewer.ftl","sk":"sk/viewer.ftl","skr":"skr/viewer.ftl","sl":"sl/viewer.ftl","son":"son/viewer.ftl","sq":"sq/viewer.ftl","sr":"sr/viewer.ftl","sv-se":"sv-SE/viewer.ftl","szl":"szl/viewer.ftl","ta":"ta/viewer.ftl","te":"te/viewer.ftl","tg":"tg/viewer.ftl","th":"th/viewer.ftl","tl":"tl/viewer.ftl","tr":"tr/viewer.ftl","trs":"trs/viewer.ftl","uk":"uk/viewer.ftl","ur":"ur/viewer.ftl","uz":"uz/viewer.ftl","vi":"vi/viewer.ftl","wo":"wo/viewer.ftl","xh":"xh/viewer.ftl","zh-cn":"zh-CN/viewer.ftl","zh-tw":"zh-TW/viewer.ftl"} \ No newline at end of file diff --git a/public/assets/pdfjs/locale/lt/viewer.ftl b/public/assets/pdfjs/locale/lt/viewer.ftl new file mode 100755 index 0000000..a8ee7a0 --- /dev/null +++ b/public/assets/pdfjs/locale/lt/viewer.ftl @@ -0,0 +1,268 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Ankstesnis puslapis +pdfjs-previous-button-label = Ankstesnis +pdfjs-next-button = + .title = Kitas puslapis +pdfjs-next-button-label = Kitas +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Puslapis +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = iÅ¡ { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } iÅ¡ { $pagesCount }) +pdfjs-zoom-out-button = + .title = Sumažinti +pdfjs-zoom-out-button-label = Sumažinti +pdfjs-zoom-in-button = + .title = Padidinti +pdfjs-zoom-in-button-label = Padidinti +pdfjs-zoom-select = + .title = Mastelis +pdfjs-presentation-mode-button = + .title = Pereiti į pateikties veiksenÄ… +pdfjs-presentation-mode-button-label = Pateikties veiksena +pdfjs-open-file-button = + .title = Atverti failÄ… +pdfjs-open-file-button-label = Atverti +pdfjs-print-button = + .title = Spausdinti +pdfjs-print-button-label = Spausdinti + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = PriemonÄ—s +pdfjs-tools-button-label = PriemonÄ—s +pdfjs-first-page-button = + .title = Eiti į pirmÄ… puslapį +pdfjs-first-page-button-label = Eiti į pirmÄ… puslapį +pdfjs-last-page-button = + .title = Eiti į paskutinį puslapį +pdfjs-last-page-button-label = Eiti į paskutinį puslapį +pdfjs-page-rotate-cw-button = + .title = Pasukti pagal laikrodžio rodyklÄ™ +pdfjs-page-rotate-cw-button-label = Pasukti pagal laikrodžio rodyklÄ™ +pdfjs-page-rotate-ccw-button = + .title = Pasukti prieÅ¡ laikrodžio rodyklÄ™ +pdfjs-page-rotate-ccw-button-label = Pasukti prieÅ¡ laikrodžio rodyklÄ™ +pdfjs-cursor-text-select-tool-button = + .title = Ä®jungti teksto žymÄ—jimo įrankį +pdfjs-cursor-text-select-tool-button-label = Teksto žymÄ—jimo įrankis +pdfjs-cursor-hand-tool-button = + .title = Ä®jungti vilkimo įrankį +pdfjs-cursor-hand-tool-button-label = Vilkimo įrankis +pdfjs-scroll-page-button = + .title = Naudoti puslapio slinkimÄ… +pdfjs-scroll-page-button-label = Puslapio slinkimas +pdfjs-scroll-vertical-button = + .title = Naudoti vertikalų slinkimÄ… +pdfjs-scroll-vertical-button-label = Vertikalus slinkimas +pdfjs-scroll-horizontal-button = + .title = Naudoti horizontalų slinkimÄ… +pdfjs-scroll-horizontal-button-label = Horizontalus slinkimas +pdfjs-scroll-wrapped-button = + .title = Naudoti iÅ¡klotÄ… slinkimÄ… +pdfjs-scroll-wrapped-button-label = IÅ¡klotas slinkimas +pdfjs-spread-none-button = + .title = Nejungti puslapių į dvilapius +pdfjs-spread-none-button-label = Be dvilapių +pdfjs-spread-odd-button = + .title = Sujungti į dvilapius pradedant nelyginiais puslapiais +pdfjs-spread-odd-button-label = Nelyginiai dvilapiai +pdfjs-spread-even-button = + .title = Sujungti į dvilapius pradedant lyginiais puslapiais +pdfjs-spread-even-button-label = Lyginiai dvilapiai + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Dokumento savybÄ—s… +pdfjs-document-properties-button-label = Dokumento savybÄ—s… +pdfjs-document-properties-file-name = Failo vardas: +pdfjs-document-properties-file-size = Failo dydis: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } B) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } B) +pdfjs-document-properties-title = AntraÅ¡tÄ—: +pdfjs-document-properties-author = Autorius: +pdfjs-document-properties-subject = Tema: +pdfjs-document-properties-keywords = ReikÅ¡miniai žodžiai: +pdfjs-document-properties-creation-date = SukÅ«rimo data: +pdfjs-document-properties-modification-date = Modifikavimo data: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = KÅ«rÄ—jas: +pdfjs-document-properties-producer = PDF generatorius: +pdfjs-document-properties-version = PDF versija: +pdfjs-document-properties-page-count = Puslapių skaiÄius: +pdfjs-document-properties-page-size = Puslapio dydis: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = staÄias +pdfjs-document-properties-page-size-orientation-landscape = gulsÄias +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = LaiÅ¡kas +pdfjs-document-properties-page-size-name-legal = Dokumentas + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Spartus žiniatinklio rodinys: +pdfjs-document-properties-linearized-yes = Taip +pdfjs-document-properties-linearized-no = Ne +pdfjs-document-properties-close-button = Užverti + +## Print + +pdfjs-print-progress-message = Dokumentas ruoÅ¡iamas spausdinimui… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Atsisakyti +pdfjs-printing-not-supported = DÄ—mesio! Spausdinimas Å¡ioje narÅ¡yklÄ—je nÄ—ra pilnai realizuotas. +pdfjs-printing-not-ready = DÄ—mesio! PDF failas dar nÄ—ra pilnai įkeltas spausdinimui. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Rodyti / slÄ—pti Å¡oninį polangį +pdfjs-toggle-sidebar-notification-button = + .title = ParankinÄ— (dokumentas turi struktÅ«rÄ… / priedų / sluoksnių) +pdfjs-toggle-sidebar-button-label = Å oninis polangis +pdfjs-document-outline-button = + .title = Rodyti dokumento struktÅ«rÄ… (spustelÄ—kite dukart norÄ—dami iÅ¡plÄ—sti/suskleisti visus elementus) +pdfjs-document-outline-button-label = Dokumento struktÅ«ra +pdfjs-attachments-button = + .title = Rodyti priedus +pdfjs-attachments-button-label = Priedai +pdfjs-layers-button = + .title = Rodyti sluoksnius (spustelÄ—kite dukart, norÄ—dami atstatyti visus sluoksnius į numatytÄ…jÄ… bÅ«senÄ…) +pdfjs-layers-button-label = Sluoksniai +pdfjs-thumbs-button = + .title = Rodyti puslapių miniatiÅ«ras +pdfjs-thumbs-button-label = MiniatiÅ«ros +pdfjs-current-outline-item-button = + .title = Rasti dabartinį struktÅ«ros elementÄ… +pdfjs-current-outline-item-button-label = Dabartinis struktÅ«ros elementas +pdfjs-findbar-button = + .title = IeÅ¡koti dokumente +pdfjs-findbar-button-label = Rasti +pdfjs-additional-layers = Papildomi sluoksniai + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = { $page } puslapis +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = { $page } puslapio miniatiÅ«ra + +## Find panel button title and messages + +pdfjs-find-input = + .title = Rasti + .placeholder = Rasti dokumente… +pdfjs-find-previous-button = + .title = IeÅ¡koti ankstesnio frazÄ—s egzemplioriaus +pdfjs-find-previous-button-label = Ankstesnis +pdfjs-find-next-button = + .title = IeÅ¡koti tolesnio frazÄ—s egzemplioriaus +pdfjs-find-next-button-label = Tolesnis +pdfjs-find-highlight-checkbox = ViskÄ… paryÅ¡kinti +pdfjs-find-match-case-checkbox-label = Skirti didžiÄ…sias ir mažąsias raides +pdfjs-find-match-diacritics-checkbox-label = Skirti diakritinius ženklus +pdfjs-find-entire-word-checkbox-label = IÅ¡tisi žodžiai +pdfjs-find-reached-top = Pasiekus dokumento pradžiÄ…, paieÅ¡ka pratÄ™sta nuo pabaigos +pdfjs-find-reached-bottom = Pasiekus dokumento pabaigÄ…, paieÅ¡ka pratÄ™sta nuo pradžios +pdfjs-find-not-found = IeÅ¡koma frazÄ— nerasta + +## Predefined zoom values + +pdfjs-page-scale-width = Priderinti prie lapo ploÄio +pdfjs-page-scale-fit = Pritaikyti prie lapo dydžio +pdfjs-page-scale-auto = Automatinis mastelis +pdfjs-page-scale-actual = Tikras dydis +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = { $page } puslapis + +## Loading indicator messages + +pdfjs-loading-error = Ä®keliant PDF failÄ… įvyko klaida. +pdfjs-invalid-file-error = Tai nÄ—ra PDF failas arba jis yra sugadintas. +pdfjs-missing-file-error = PDF failas nerastas. +pdfjs-unexpected-response-error = NetikÄ—tas serverio atsakas. +pdfjs-rendering-error = Atvaizduojant puslapį įvyko klaida. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [„{ $type }“ tipo anotacija] + +## Password + +pdfjs-password-label = Ä®veskite slaptažodį Å¡iam PDF failui atverti. +pdfjs-password-invalid = Slaptažodis neteisingas. Bandykite dar kartÄ…. +pdfjs-password-ok-button = Gerai +pdfjs-password-cancel-button = Atsisakyti +pdfjs-web-fonts-disabled = Saityno Å¡riftai iÅ¡jungti – PDF faile esanÄių Å¡riftų naudoti negalima. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/ltg/viewer.ftl b/public/assets/pdfjs/locale/ltg/viewer.ftl new file mode 100755 index 0000000..d262165 --- /dev/null +++ b/public/assets/pdfjs/locale/ltg/viewer.ftl @@ -0,0 +1,246 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = ĪprÄ«kÅ¡ejÄ lopa +pdfjs-previous-button-label = ĪprÄ«kÅ¡ejÄ +pdfjs-next-button = + .title = Nuokomuo lopa +pdfjs-next-button-label = Nuokomuo +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Lopa +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = nu { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } nu { $pagesCount }) +pdfjs-zoom-out-button = + .title = Attuolynuot +pdfjs-zoom-out-button-label = Attuolynuot +pdfjs-zoom-in-button = + .title = PÄ«tuvynuot +pdfjs-zoom-in-button-label = PÄ«tuvynuot +pdfjs-zoom-select = + .title = Palelynuojums +pdfjs-presentation-mode-button = + .title = PuorslÄ“gtÄ«s iz Prezentacejis režymu +pdfjs-presentation-mode-button-label = Prezentacejis režyms +pdfjs-open-file-button = + .title = Attaiseit failu +pdfjs-open-file-button-label = Attaiseit +pdfjs-print-button = + .title = DrukuoÅ¡ona +pdfjs-print-button-label = DrukÅt + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Reiki +pdfjs-tools-button-label = Reiki +pdfjs-first-page-button = + .title = Īt iz pyrmÅ« lopu +pdfjs-first-page-button-label = Īt iz pyrmÅ« lopu +pdfjs-last-page-button = + .title = Īt iz piedejÅ« lopu +pdfjs-last-page-button-label = Īt iz piedejÅ« lopu +pdfjs-page-rotate-cw-button = + .title = PagrÄ«zt pa pulksteni +pdfjs-page-rotate-cw-button-label = PagrÄ«zt pa pulksteni +pdfjs-page-rotate-ccw-button = + .title = PagrÄ«zt pret pulksteni +pdfjs-page-rotate-ccw-button-label = PagrÄ«zt pret pulksteni +pdfjs-cursor-text-select-tool-button = + .title = AktivizÄ“t teksta izvieles reiku +pdfjs-cursor-text-select-tool-button-label = Teksta izvieles reiks +pdfjs-cursor-hand-tool-button = + .title = AktivÄ“t rÅ«kys reiku +pdfjs-cursor-hand-tool-button-label = RÅ«kys reiks +pdfjs-scroll-vertical-button = + .title = IzmontÅt vertikalÅ« ritinÅÅ¡onu +pdfjs-scroll-vertical-button-label = VertikalÅ ritinÅÅ¡ona +pdfjs-scroll-horizontal-button = + .title = IzmontÅt horizontalÅ« ritinÅÅ¡onu +pdfjs-scroll-horizontal-button-label = HorizontalÅ ritinÅÅ¡ona +pdfjs-scroll-wrapped-button = + .title = IzmontÅt mÄrÅ«gojamÅ« ritinÅÅ¡onu +pdfjs-scroll-wrapped-button-label = MÄrÅ«gojamÅ ritinÅÅ¡ona +pdfjs-spread-none-button = + .title = NaizmontÅt lopu atvÄruma režimu +pdfjs-spread-none-button-label = Bez atvÄrumim +pdfjs-spread-odd-button = + .title = IzmontÅt lopu atvÄrumus sÅkut nu napÅra numeru lopom +pdfjs-spread-odd-button-label = NapÅra lopys pa kreisi +pdfjs-spread-even-button = + .title = IzmontÅt lopu atvÄrumus sÅkut nu pÅra numeru lopom +pdfjs-spread-even-button-label = PÅra lopys pa kreisi + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Dokumenta Ä«statiejumi… +pdfjs-document-properties-button-label = Dokumenta Ä«statiejumi… +pdfjs-document-properties-file-name = Faila nÅ«saukums: +pdfjs-document-properties-file-size = Faila izmÄrs: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } biti) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } biti) +pdfjs-document-properties-title = NÅ«saukums: +pdfjs-document-properties-author = Autors: +pdfjs-document-properties-subject = Tema: +pdfjs-document-properties-keywords = AtslÄgi vuordi: +pdfjs-document-properties-creation-date = Izveides datums: +pdfjs-document-properties-modification-date = lobuoÅ¡onys datums: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Radeituojs: +pdfjs-document-properties-producer = PDF producents: +pdfjs-document-properties-version = PDF verseja: +pdfjs-document-properties-page-count = Lopu skaits: +pdfjs-document-properties-page-size = Lopas izmÄrs: +pdfjs-document-properties-page-size-unit-inches = collas +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = portreta orientaceja +pdfjs-document-properties-page-size-orientation-landscape = ainovys orientaceja +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Fast Web View: +pdfjs-document-properties-linearized-yes = JÄ +pdfjs-document-properties-linearized-no = NÄ +pdfjs-document-properties-close-button = Aiztaiseit + +## Print + +pdfjs-print-progress-message = Preparing document for printing… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Atceļt +pdfjs-printing-not-supported = Uzmaneibu: DrukuoÅ¡ona nu itei puorlÅ«ka dorbojÄs tikai daleji. +pdfjs-printing-not-ready = Uzmaneibu: PDF nav pilneibÄ Ä«luodeits drukuoÅ¡onai. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = PuorslÄ“gt suonu jÅ«slu +pdfjs-toggle-sidebar-button-label = PuorslÄ“gt suonu jÅ«slu +pdfjs-document-outline-button = + .title = Show Document Outline (double-click to expand/collapse all items) +pdfjs-document-outline-button-label = Dokumenta saturs +pdfjs-attachments-button = + .title = Show Attachments +pdfjs-attachments-button-label = Attachments +pdfjs-thumbs-button = + .title = Paruodeit seiktÄlus +pdfjs-thumbs-button-label = SeiktÄli +pdfjs-findbar-button = + .title = Mekleit dokumentÄ +pdfjs-findbar-button-label = Mekleit + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Lopa { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Lopys { $page } seiktÄls + +## Find panel button title and messages + +pdfjs-find-input = + .title = Mekleit + .placeholder = Mekleit dokumentÄ… +pdfjs-find-previous-button = + .title = Atrast Ä«prÄ«kÅ¡ejÅ« +pdfjs-find-previous-button-label = ĪprÄ«kÅ¡ejÄ +pdfjs-find-next-button = + .title = Atrast nuokamÅ« +pdfjs-find-next-button-label = Nuokomuo +pdfjs-find-highlight-checkbox = Īkruosuot vysys +pdfjs-find-match-case-checkbox-label = LelÅ«, mozÅ« burtu jiuteigs +pdfjs-find-reached-top = SasnÄ«gts dokumenta suokums, turpynojom nu beigom +pdfjs-find-reached-bottom = SasnÄ«gtys dokumenta beigys, turpynojom nu suokuma +pdfjs-find-not-found = FrÄze nav atrosta + +## Predefined zoom values + +pdfjs-page-scale-width = Lopys plotumÄ +pdfjs-page-scale-fit = ĪtylpynÅ«t lopu +pdfjs-page-scale-auto = Automatiskais izmÄrs +pdfjs-page-scale-actual = PatÄ«sais izmÄrs +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = ĪluodejÅ«t PDF nÅ«tyka klaida. +pdfjs-invalid-file-error = Nadereigs voi bÅ«juots PDF fails. +pdfjs-missing-file-error = PDF fails nav atrosts. +pdfjs-unexpected-response-error = Unexpected server response. +pdfjs-rendering-error = AttÄlojÅ«t lopu rodÄs klaida + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Annotation] + +## Password + +pdfjs-password-label = Īvodit paroli, kab attaiseitu PDF failu. +pdfjs-password-invalid = Napareiza parole, raugit vēļreiz. +pdfjs-password-ok-button = Labi +pdfjs-password-cancel-button = Atceļt +pdfjs-web-fonts-disabled = Å Ä·Ärsteikla fonti nav aktivizÄti: Navar Ä«gult PDF fontus. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/lv/viewer.ftl b/public/assets/pdfjs/locale/lv/viewer.ftl new file mode 100755 index 0000000..067dc10 --- /dev/null +++ b/public/assets/pdfjs/locale/lv/viewer.ftl @@ -0,0 +1,247 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = IepriekšējÄ lapa +pdfjs-previous-button-label = IepriekšējÄ +pdfjs-next-button = + .title = NÄkamÄ lapa +pdfjs-next-button-label = NÄkamÄ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Lapa +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = no { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } no { $pagesCount }) +pdfjs-zoom-out-button = + .title = AttÄlinÄt +pdfjs-zoom-out-button-label = AttÄlinÄt +pdfjs-zoom-in-button = + .title = PietuvinÄt +pdfjs-zoom-in-button-label = PietuvinÄt +pdfjs-zoom-select = + .title = PalielinÄjums +pdfjs-presentation-mode-button = + .title = PÄrslÄ“gties uz PrezentÄcijas režīmu +pdfjs-presentation-mode-button-label = PrezentÄcijas režīms +pdfjs-open-file-button = + .title = AtvÄ“rt failu +pdfjs-open-file-button-label = AtvÄ“rt +pdfjs-print-button = + .title = DrukÄÅ¡ana +pdfjs-print-button-label = DrukÄt + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = RÄ«ki +pdfjs-tools-button-label = RÄ«ki +pdfjs-first-page-button = + .title = Iet uz pirmo lapu +pdfjs-first-page-button-label = Iet uz pirmo lapu +pdfjs-last-page-button = + .title = Iet uz pÄ“dÄ“jo lapu +pdfjs-last-page-button-label = Iet uz pÄ“dÄ“jo lapu +pdfjs-page-rotate-cw-button = + .title = Pagriezt pa pulksteni +pdfjs-page-rotate-cw-button-label = Pagriezt pa pulksteni +pdfjs-page-rotate-ccw-button = + .title = Pagriezt pret pulksteni +pdfjs-page-rotate-ccw-button-label = Pagriezt pret pulksteni +pdfjs-cursor-text-select-tool-button = + .title = AktivizÄ“t teksta izvÄ“les rÄ«ku +pdfjs-cursor-text-select-tool-button-label = Teksta izvÄ“les rÄ«ks +pdfjs-cursor-hand-tool-button = + .title = AktivÄ“t rokas rÄ«ku +pdfjs-cursor-hand-tool-button-label = Rokas rÄ«ks +pdfjs-scroll-vertical-button = + .title = Izmantot vertikÄlo ritinÄÅ¡anu +pdfjs-scroll-vertical-button-label = VertikÄlÄ ritinÄÅ¡ana +pdfjs-scroll-horizontal-button = + .title = Izmantot horizontÄlo ritinÄÅ¡anu +pdfjs-scroll-horizontal-button-label = HorizontÄlÄ ritinÄÅ¡ana +pdfjs-scroll-wrapped-button = + .title = Izmantot apkļauto ritinÄÅ¡anu +pdfjs-scroll-wrapped-button-label = ApkļautÄ ritinÄÅ¡ana +pdfjs-spread-none-button = + .title = Nepievienoties lapu izpletumiem +pdfjs-spread-none-button-label = Neizmantot izpletumus +pdfjs-spread-odd-button = + .title = Izmantot lapu izpletumus sÄkot ar nepÄra numuru lapÄm +pdfjs-spread-odd-button-label = NepÄra izpletumi +pdfjs-spread-even-button = + .title = Izmantot lapu izpletumus sÄkot ar pÄra numuru lapÄm +pdfjs-spread-even-button-label = PÄra izpletumi + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Dokumenta iestatÄ«jumi… +pdfjs-document-properties-button-label = Dokumenta iestatÄ«jumi… +pdfjs-document-properties-file-name = Faila nosaukums: +pdfjs-document-properties-file-size = Faila izmÄ“rs: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } biti) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } biti) +pdfjs-document-properties-title = Nosaukums: +pdfjs-document-properties-author = Autors: +pdfjs-document-properties-subject = TÄ“ma: +pdfjs-document-properties-keywords = AtslÄ“gas vÄrdi: +pdfjs-document-properties-creation-date = Izveides datums: +pdfjs-document-properties-modification-date = LAboÅ¡anas datums: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = RadÄ«tÄjs: +pdfjs-document-properties-producer = PDF producents: +pdfjs-document-properties-version = PDF versija: +pdfjs-document-properties-page-count = Lapu skaits: +pdfjs-document-properties-page-size = PapÄ«ra izmÄ“rs: +pdfjs-document-properties-page-size-unit-inches = collas +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = portretorientÄcija +pdfjs-document-properties-page-size-orientation-landscape = ainavorientÄcija +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = VÄ“stule +pdfjs-document-properties-page-size-name-legal = Juridiskie teksti + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Ä€trÄ tÄ«mekļa skats: +pdfjs-document-properties-linearized-yes = JÄ +pdfjs-document-properties-linearized-no = NÄ“ +pdfjs-document-properties-close-button = AizvÄ“rt + +## Print + +pdfjs-print-progress-message = Gatavo dokumentu drukÄÅ¡anai... +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Atcelt +pdfjs-printing-not-supported = UzmanÄ«bu: DrukÄÅ¡ana no šī pÄrlÅ«ka darbojas tikai daļēji. +pdfjs-printing-not-ready = UzmanÄ«bu: PDF nav pilnÄ«bÄ ielÄdÄ“ts drukÄÅ¡anai. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = PÄrslÄ“gt sÄnu joslu +pdfjs-toggle-sidebar-button-label = PÄrslÄ“gt sÄnu joslu +pdfjs-document-outline-button = + .title = RÄdÄ«t dokumenta struktÅ«ru (veiciet dubultklikšķi lai izvÄ“rstu/sakļautu visus vienumus) +pdfjs-document-outline-button-label = Dokumenta saturs +pdfjs-attachments-button = + .title = RÄdÄ«t pielikumus +pdfjs-attachments-button-label = Pielikumi +pdfjs-thumbs-button = + .title = ParÄdÄ«t sÄ«ktÄ“lus +pdfjs-thumbs-button-label = SÄ«ktÄ“li +pdfjs-findbar-button = + .title = MeklÄ“t dokumentÄ +pdfjs-findbar-button-label = MeklÄ“t + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Lapa { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Lapas { $page } sÄ«ktÄ“ls + +## Find panel button title and messages + +pdfjs-find-input = + .title = MeklÄ“t + .placeholder = MeklÄ“t dokumentÄ… +pdfjs-find-previous-button = + .title = Atrast iepriekšējo +pdfjs-find-previous-button-label = IepriekšējÄ +pdfjs-find-next-button = + .title = Atrast nÄkamo +pdfjs-find-next-button-label = NÄkamÄ +pdfjs-find-highlight-checkbox = IekrÄsot visas +pdfjs-find-match-case-checkbox-label = Lielo, mazo burtu jutÄ«gs +pdfjs-find-entire-word-checkbox-label = Veselus vÄrdus +pdfjs-find-reached-top = Sasniegts dokumenta sÄkums, turpinÄm no beigÄm +pdfjs-find-reached-bottom = Sasniegtas dokumenta beigas, turpinÄm no sÄkuma +pdfjs-find-not-found = FrÄze nav atrasta + +## Predefined zoom values + +pdfjs-page-scale-width = Lapas platumÄ +pdfjs-page-scale-fit = Ietilpinot lapu +pdfjs-page-scale-auto = AutomÄtiskais izmÄ“rs +pdfjs-page-scale-actual = Patiesais izmÄ“rs +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = IelÄdÄ“jot PDF notika kļūda. +pdfjs-invalid-file-error = NederÄ«gs vai bojÄts PDF fails. +pdfjs-missing-file-error = PDF fails nav atrasts. +pdfjs-unexpected-response-error = NegaidÄ«a servera atbilde. +pdfjs-rendering-error = AttÄ“lojot lapu radÄs kļūda + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } anotÄcija] + +## Password + +pdfjs-password-label = Ievadiet paroli, lai atvÄ“rtu PDF failu. +pdfjs-password-invalid = Nepareiza parole, mēģiniet vÄ“lreiz. +pdfjs-password-ok-button = Labi +pdfjs-password-cancel-button = Atcelt +pdfjs-web-fonts-disabled = TÄ«mekļa fonti nav aktivizÄ“ti: Nevar iegult PDF fontus. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/meh/viewer.ftl b/public/assets/pdfjs/locale/meh/viewer.ftl new file mode 100755 index 0000000..d8bddc9 --- /dev/null +++ b/public/assets/pdfjs/locale/meh/viewer.ftl @@ -0,0 +1,87 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Página yata +pdfjs-zoom-select = + .title = Nasa´a ka´nu/Nasa´a luli +pdfjs-open-file-button-label = Síne + +## Secondary toolbar and context menu + + +## Document properties dialog + +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +pdfjs-document-properties-linearized-yes = Kuvi +pdfjs-document-properties-close-button = Nakasɨ + +## Print + +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Nkuvi-ka + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-findbar-button-label = Nánuku + +## Thumbnails panel item (tooltip and alt text for images) + + +## Find panel button title and messages + + +## Predefined zoom values + +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } + +## Password + +pdfjs-password-cancel-button = Nkuvi-ka + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/mk/viewer.ftl b/public/assets/pdfjs/locale/mk/viewer.ftl new file mode 100755 index 0000000..47d24b2 --- /dev/null +++ b/public/assets/pdfjs/locale/mk/viewer.ftl @@ -0,0 +1,215 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Претходна Ñтраница +pdfjs-previous-button-label = Претходна +pdfjs-next-button = + .title = Следна Ñтраница +pdfjs-next-button-label = Следна +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Страница +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = од { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } од { $pagesCount }) +pdfjs-zoom-out-button = + .title = Ðамалување +pdfjs-zoom-out-button-label = Ðамали +pdfjs-zoom-in-button = + .title = Зголемување +pdfjs-zoom-in-button-label = Зголеми +pdfjs-zoom-select = + .title = Променување на големина +pdfjs-presentation-mode-button = + .title = Премини во презентациÑки режим +pdfjs-presentation-mode-button-label = ПрезентациÑки режим +pdfjs-open-file-button = + .title = Отворање датотека +pdfjs-open-file-button-label = Отвори +pdfjs-print-button = + .title = Печатење +pdfjs-print-button-label = Печати + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Ðлатки +pdfjs-tools-button-label = Ðлатки +pdfjs-first-page-button = + .title = Оди до првата Ñтраница +pdfjs-first-page-button-label = Оди до првата Ñтраница +pdfjs-last-page-button = + .title = Оди до поÑледната Ñтраница +pdfjs-last-page-button-label = Оди до поÑледната Ñтраница +pdfjs-page-rotate-cw-button = + .title = Ротирај по Ñтрелките на чаÑовникот +pdfjs-page-rotate-cw-button-label = Ротирај по Ñтрелките на чаÑовникот +pdfjs-page-rotate-ccw-button = + .title = Ротирај Ñпротивно од Ñтрелките на чаÑовникот +pdfjs-page-rotate-ccw-button-label = Ротирај Ñпротивно од Ñтрелките на чаÑовникот +pdfjs-cursor-text-select-tool-button = + .title = Овозможи алатка за избор на текÑÑ‚ +pdfjs-cursor-text-select-tool-button-label = Ðлатка за избор на текÑÑ‚ + +## Document properties dialog + +pdfjs-document-properties-button = + .title = СвојÑтва на документот… +pdfjs-document-properties-button-label = СвојÑтва на документот… +pdfjs-document-properties-file-name = Име на датотека: +pdfjs-document-properties-file-size = Големина на датотеката: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } КБ ({ $size_b } бајти) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } МБ ({ $size_b } бајти) +pdfjs-document-properties-title = ÐаÑлов: +pdfjs-document-properties-author = Ðвтор: +pdfjs-document-properties-subject = Тема: +pdfjs-document-properties-keywords = Клучни зборови: +pdfjs-document-properties-creation-date = Датум на Ñоздавање: +pdfjs-document-properties-modification-date = Датум на промена: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Креатор: +pdfjs-document-properties-version = Верзија на PDF: +pdfjs-document-properties-page-count = Број на Ñтраници: +pdfjs-document-properties-page-size = Големина на Ñтраница: +pdfjs-document-properties-page-size-unit-inches = инч +pdfjs-document-properties-page-size-unit-millimeters = мм +pdfjs-document-properties-page-size-orientation-portrait = портрет +pdfjs-document-properties-page-size-orientation-landscape = пејзаж +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = ПиÑмо + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +pdfjs-document-properties-linearized-yes = Да +pdfjs-document-properties-linearized-no = Ðе +pdfjs-document-properties-close-button = Затвори + +## Print + +pdfjs-print-progress-message = Документ Ñе подготвува за печатење… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Откажи +pdfjs-printing-not-supported = Предупредување: Печатењето не е целоÑно поддржано во овој прелиÑтувач. +pdfjs-printing-not-ready = Предупредување: PDF документот не е целоÑно вчитан за печатење. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Вклучи Ñтранична лента +pdfjs-toggle-sidebar-button-label = Вклучи Ñтранична лента +pdfjs-document-outline-button-label = Содржина на документот +pdfjs-attachments-button = + .title = Прикажи додатоци +pdfjs-thumbs-button = + .title = Прикажување на икони +pdfjs-thumbs-button-label = Икони +pdfjs-findbar-button = + .title = Ðајди во документот +pdfjs-findbar-button-label = Ðајди + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Страница { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Икона од Ñтраница { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Пронајди + .placeholder = Пронајди во документот… +pdfjs-find-previous-button = + .title = Ðајди ја предходната појава на фразата +pdfjs-find-previous-button-label = Претходно +pdfjs-find-next-button = + .title = Ðајди ја Ñледната појава на фразата +pdfjs-find-next-button-label = Следно +pdfjs-find-highlight-checkbox = Означи ÑÑ +pdfjs-find-match-case-checkbox-label = Токму така +pdfjs-find-entire-word-checkbox-label = Цели зборови +pdfjs-find-reached-top = Барањето Ñтигна до почетокот на документот и почнува од крајот +pdfjs-find-reached-bottom = Барањето Ñтигна до крајот на документот и почнува од почеток +pdfjs-find-not-found = Фразата не е пронајдена + +## Predefined zoom values + +pdfjs-page-scale-width = Ширина на Ñтраница +pdfjs-page-scale-fit = Цела Ñтраница +pdfjs-page-scale-auto = ÐвтоматÑка големина +pdfjs-page-scale-actual = ВиÑтинÑка големина +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = ÐаÑтана грешка при вчитувањето на PDF-от. +pdfjs-invalid-file-error = Ðевалидна или корумпирана PDF датотека. +pdfjs-missing-file-error = ÐедоÑтаÑува PDF документ. +pdfjs-unexpected-response-error = Ðеочекуван одговор од Ñерверот. +pdfjs-rendering-error = ÐаÑтана грешка при прикажувањето на Ñтраницата. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } + +## Password + +pdfjs-password-label = ВнеÑете ја лозинката за да ја отворите оваа датотека. +pdfjs-password-invalid = Ðевалидна лозинка. Обидете Ñе повторно. +pdfjs-password-ok-button = Во ред +pdfjs-password-cancel-button = Откажи +pdfjs-web-fonts-disabled = Интернет фонтовите Ñе оневозможени: не може да Ñе кориÑтат вградените PDF фонтови. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/mr/viewer.ftl b/public/assets/pdfjs/locale/mr/viewer.ftl new file mode 100755 index 0000000..49948b1 --- /dev/null +++ b/public/assets/pdfjs/locale/mr/viewer.ftl @@ -0,0 +1,239 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = मागील पृषà¥à¤  +pdfjs-previous-button-label = मागील +pdfjs-next-button = + .title = पà¥à¤¢à¥€à¤² पृषà¥à¤  +pdfjs-next-button-label = पà¥à¤¢à¥€à¤² +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = पृषà¥à¤  +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = { $pagesCount }पैकी +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pagesCount } पैकी { $pageNumber }) +pdfjs-zoom-out-button = + .title = छोटे करा +pdfjs-zoom-out-button-label = छोटे करा +pdfjs-zoom-in-button = + .title = मोठे करा +pdfjs-zoom-in-button-label = मोठे करा +pdfjs-zoom-select = + .title = लहान किंवा मोठे करा +pdfjs-presentation-mode-button = + .title = पà¥à¤°à¤¸à¥à¤¤à¥à¤¤à¤¿à¤•रण मोडचा वापर करा +pdfjs-presentation-mode-button-label = पà¥à¤°à¤¸à¥à¤¤à¥à¤¤à¤¿à¤•रण मोड +pdfjs-open-file-button = + .title = फाइल उघडा +pdfjs-open-file-button-label = उघडा +pdfjs-print-button = + .title = छपाई करा +pdfjs-print-button-label = छपाई करा + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = साधने +pdfjs-tools-button-label = साधने +pdfjs-first-page-button = + .title = पहिलà¥à¤¯à¤¾ पृषà¥à¤ à¤¾à¤µà¤° जा +pdfjs-first-page-button-label = पहिलà¥à¤¯à¤¾ पृषà¥à¤ à¤¾à¤µà¤° जा +pdfjs-last-page-button = + .title = शेवटचà¥à¤¯à¤¾ पृषà¥à¤ à¤¾à¤µà¤° जा +pdfjs-last-page-button-label = शेवटचà¥à¤¯à¤¾ पृषà¥à¤ à¤¾à¤µà¤° जा +pdfjs-page-rotate-cw-button = + .title = घडà¥à¤¯à¤¾à¤³à¤¾à¤šà¥à¤¯à¤¾ काटà¥à¤¯à¤¾à¤šà¥à¤¯à¤¾ दिशेने फिरवा +pdfjs-page-rotate-cw-button-label = घडà¥à¤¯à¤¾à¤³à¤¾à¤šà¥à¤¯à¤¾ काटà¥à¤¯à¤¾à¤šà¥à¤¯à¤¾ दिशेने फिरवा +pdfjs-page-rotate-ccw-button = + .title = घडà¥à¤¯à¤¾à¤³à¤¾à¤šà¥à¤¯à¤¾ काटà¥à¤¯à¤¾à¤šà¥à¤¯à¤¾ उलट दिशेने फिरवा +pdfjs-page-rotate-ccw-button-label = घडà¥à¤¯à¤¾à¤³à¤¾à¤šà¥à¤¯à¤¾ काटà¥à¤¯à¤¾à¤šà¥à¤¯à¤¾ उलट दिशेने फिरवा +pdfjs-cursor-text-select-tool-button = + .title = मजकूर निवड साधन कारà¥à¤¯à¤¾à¤¨à¥à¤µà¤¯à¥€à¤¤ करा +pdfjs-cursor-text-select-tool-button-label = मजकूर निवड साधन +pdfjs-cursor-hand-tool-button = + .title = हात साधन कारà¥à¤¯à¤¾à¤¨à¥à¤µà¤¿à¤¤ करा +pdfjs-cursor-hand-tool-button-label = हसà¥à¤¤ साधन +pdfjs-scroll-vertical-button = + .title = अनà¥à¤²à¤‚ब सà¥à¤•à¥à¤°à¥‹à¤²à¤¿à¤‚ग वापरा +pdfjs-scroll-vertical-button-label = अनà¥à¤²à¤‚ब सà¥à¤•à¥à¤°à¥‹à¤²à¤¿à¤‚ग +pdfjs-scroll-horizontal-button = + .title = कà¥à¤·à¥ˆà¤¤à¤¿à¤œ सà¥à¤•à¥à¤°à¥‹à¤²à¤¿à¤‚ग वापरा +pdfjs-scroll-horizontal-button-label = कà¥à¤·à¥ˆà¤¤à¤¿à¤œ सà¥à¤•à¥à¤°à¥‹à¤²à¤¿à¤‚ग + +## Document properties dialog + +pdfjs-document-properties-button = + .title = दसà¥à¤¤à¤à¤µà¤œ गà¥à¤£à¤§à¤°à¥à¤®â€¦ +pdfjs-document-properties-button-label = दसà¥à¤¤à¤à¤µà¤œ गà¥à¤£à¤§à¤°à¥à¤®â€¦ +pdfjs-document-properties-file-name = फाइलचे नाव: +pdfjs-document-properties-file-size = फाइल आकार: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } बाइटà¥à¤¸) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } बाइटà¥à¤¸) +pdfjs-document-properties-title = शिरà¥à¤·à¤•: +pdfjs-document-properties-author = लेखक: +pdfjs-document-properties-subject = विषय: +pdfjs-document-properties-keywords = मà¥à¤–à¥à¤¯à¤¶à¤¬à¥à¤¦: +pdfjs-document-properties-creation-date = निरà¥à¤®à¤¾à¤£ दिनांक: +pdfjs-document-properties-modification-date = दà¥à¤°à¥‚सà¥à¤¤à¥€ दिनांक: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = निरà¥à¤®à¤¾à¤¤à¤¾: +pdfjs-document-properties-producer = PDF निरà¥à¤®à¤¾à¤¤à¤¾: +pdfjs-document-properties-version = PDF आवृतà¥à¤¤à¥€: +pdfjs-document-properties-page-count = पृषà¥à¤  संखà¥à¤¯à¤¾: +pdfjs-document-properties-page-size = पृषà¥à¤  आकार: +pdfjs-document-properties-page-size-unit-inches = इंच +pdfjs-document-properties-page-size-unit-millimeters = मीमी +pdfjs-document-properties-page-size-orientation-portrait = उभी मांडणी +pdfjs-document-properties-page-size-orientation-landscape = आडवे +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = जलद वेब दृषà¥à¤¯: +pdfjs-document-properties-linearized-yes = हो +pdfjs-document-properties-linearized-no = नाही +pdfjs-document-properties-close-button = बंद करा + +## Print + +pdfjs-print-progress-message = छपाई करीता पृषà¥à¤  तयार करीत आहे… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = रदà¥à¤¦ करा +pdfjs-printing-not-supported = सावधानता: या बà¥à¤°à¤¾à¤‰à¤à¤°à¤¤à¤°à¥à¤«à¥‡ छपाइ पूरà¥à¤£à¤ªà¤£à¥‡ समरà¥à¤¥à¥€à¤¤ नाही. +pdfjs-printing-not-ready = सावधानता: छपाईकरिता PDF पूरà¥à¤£à¤¤à¤¯à¤¾ लोड à¤à¤¾à¤²à¥‡ नाही. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = बाजूचीपटà¥à¤Ÿà¥€ टॉगल करा +pdfjs-toggle-sidebar-button-label = बाजूचीपटà¥à¤Ÿà¥€ टॉगल करा +pdfjs-document-outline-button = + .title = दसà¥à¤¤à¤à¤µà¤œ बाहà¥à¤¯à¤°à¥‡à¤–ा दरà¥à¤¶à¤µà¤¾ (विसà¥à¤¤à¥ƒà¤¤ करणà¥à¤¯à¤¾à¤¸à¤¾à¤ à¥€ दोनवेळा कà¥à¤²à¤¿à¤• करा /सरà¥à¤µ घटक दाखवा) +pdfjs-document-outline-button-label = दसà¥à¤¤à¤à¤µà¤œ रूपरेषा +pdfjs-attachments-button = + .title = जोडपतà¥à¤° दाखवा +pdfjs-attachments-button-label = जोडपतà¥à¤° +pdfjs-thumbs-button = + .title = थंबनेलà¥à¤¸à¥ दाखवा +pdfjs-thumbs-button-label = थंबनेलà¥à¤¸à¥ +pdfjs-findbar-button = + .title = दसà¥à¤¤à¤à¤µà¤œà¤¾à¤¤ शोधा +pdfjs-findbar-button-label = शोधा + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = पृषà¥à¤  { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = पृषà¥à¤ à¤¾à¤šà¥‡ थंबनेल { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = शोधा + .placeholder = दसà¥à¤¤à¤à¤µà¤œà¤¾à¤¤ शोधा… +pdfjs-find-previous-button = + .title = वाकपà¥à¤°à¤¯à¥‹à¤—ची मागील घटना शोधा +pdfjs-find-previous-button-label = मागील +pdfjs-find-next-button = + .title = वाकपà¥à¤°à¤¯à¥‹à¤—ची पà¥à¤¢à¥€à¤² घटना शोधा +pdfjs-find-next-button-label = पà¥à¤¢à¥€à¤² +pdfjs-find-highlight-checkbox = सरà¥à¤µ ठळक करा +pdfjs-find-match-case-checkbox-label = आकार जà¥à¤³à¤µà¤¾ +pdfjs-find-entire-word-checkbox-label = संपूरà¥à¤£ शबà¥à¤¦ +pdfjs-find-reached-top = दसà¥à¤¤à¤à¤µà¤œà¤¾à¤šà¥à¤¯à¤¾ शीरà¥à¤·à¤•ास पोहचले, तळपासून पà¥à¤¢à¥‡ +pdfjs-find-reached-bottom = दसà¥à¤¤à¤à¤µà¤œà¤¾à¤šà¥à¤¯à¤¾ तळाला पोहचले, शीरà¥à¤·à¤•ापासून पà¥à¤¢à¥‡ +pdfjs-find-not-found = वाकपà¥à¤°à¤¯à¥‹à¤— आढळले नाही + +## Predefined zoom values + +pdfjs-page-scale-width = पृषà¥à¤ à¤¾à¤šà¥€ रूंदी +pdfjs-page-scale-fit = पृषà¥à¤  बसवा +pdfjs-page-scale-auto = सà¥à¤µà¤¯à¤‚ लाहन किंवा मोठे करणे +pdfjs-page-scale-actual = पà¥à¤°à¤¤à¥à¤¯à¤•à¥à¤· आकार +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = PDF लोड करतेवेळी तà¥à¤°à¥à¤Ÿà¥€ आढळली. +pdfjs-invalid-file-error = अवैध किंवा दोषीत PDF फाइल. +pdfjs-missing-file-error = न आढळणारी PDF फाइल. +pdfjs-unexpected-response-error = अनपेकà¥à¤·à¤¿à¤¤ सरà¥à¤µà¥à¤¹à¤° पà¥à¤°à¤¤à¤¿à¤¸à¤¾à¤¦. +pdfjs-rendering-error = पृषà¥à¤  दाखवतेवेळी तà¥à¤°à¥à¤Ÿà¥€ आढळली. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } टिपणà¥à¤£à¥€] + +## Password + +pdfjs-password-label = ही PDF फाइल उघडणà¥à¤¯à¤¾à¤•रिता पासवरà¥à¤¡ दà¥à¤¯à¤¾. +pdfjs-password-invalid = अवैध पासवरà¥à¤¡. कृपया पà¥à¤¨à¥à¤¹à¤¾ पà¥à¤°à¤¯à¤¤à¥à¤¨ करा. +pdfjs-password-ok-button = ठीक आहे +pdfjs-password-cancel-button = रदà¥à¤¦ करा +pdfjs-web-fonts-disabled = वेब टंक असमरà¥à¤¥à¥€à¤¤ आहेत: à¤à¤®à¥à¤¬à¥‡à¤¡à¥‡à¤¡ PDF टंक वापर अशकà¥à¤¯. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/ms/viewer.ftl b/public/assets/pdfjs/locale/ms/viewer.ftl new file mode 100755 index 0000000..11b8665 --- /dev/null +++ b/public/assets/pdfjs/locale/ms/viewer.ftl @@ -0,0 +1,247 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Halaman Dahulu +pdfjs-previous-button-label = Dahulu +pdfjs-next-button = + .title = Halaman Berikut +pdfjs-next-button-label = Berikut +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Halaman +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = daripada { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } daripada { $pagesCount }) +pdfjs-zoom-out-button = + .title = Zum Keluar +pdfjs-zoom-out-button-label = Zum Keluar +pdfjs-zoom-in-button = + .title = Zum Masuk +pdfjs-zoom-in-button-label = Zum Masuk +pdfjs-zoom-select = + .title = Zum +pdfjs-presentation-mode-button = + .title = Tukar ke Mod Persembahan +pdfjs-presentation-mode-button-label = Mod Persembahan +pdfjs-open-file-button = + .title = Buka Fail +pdfjs-open-file-button-label = Buka +pdfjs-print-button = + .title = Cetak +pdfjs-print-button-label = Cetak + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Alatan +pdfjs-tools-button-label = Alatan +pdfjs-first-page-button = + .title = Pergi ke Halaman Pertama +pdfjs-first-page-button-label = Pergi ke Halaman Pertama +pdfjs-last-page-button = + .title = Pergi ke Halaman Terakhir +pdfjs-last-page-button-label = Pergi ke Halaman Terakhir +pdfjs-page-rotate-cw-button = + .title = Berputar ikut arah Jam +pdfjs-page-rotate-cw-button-label = Berputar ikut arah Jam +pdfjs-page-rotate-ccw-button = + .title = Pusing berlawan arah jam +pdfjs-page-rotate-ccw-button-label = Pusing berlawan arah jam +pdfjs-cursor-text-select-tool-button = + .title = Dayakan Alatan Pilihan Teks +pdfjs-cursor-text-select-tool-button-label = Alatan Pilihan Teks +pdfjs-cursor-hand-tool-button = + .title = Dayakan Alatan Tangan +pdfjs-cursor-hand-tool-button-label = Alatan Tangan +pdfjs-scroll-vertical-button = + .title = Guna Skrol Menegak +pdfjs-scroll-vertical-button-label = Skrol Menegak +pdfjs-scroll-horizontal-button = + .title = Guna Skrol Mengufuk +pdfjs-scroll-horizontal-button-label = Skrol Mengufuk +pdfjs-scroll-wrapped-button = + .title = Guna Skrol Berbalut +pdfjs-scroll-wrapped-button-label = Skrol Berbalut +pdfjs-spread-none-button = + .title = Jangan hubungkan hamparan halaman +pdfjs-spread-none-button-label = Tanpa Hamparan +pdfjs-spread-odd-button = + .title = Hubungkan hamparan halaman dengan halaman nombor ganjil +pdfjs-spread-odd-button-label = Hamparan Ganjil +pdfjs-spread-even-button = + .title = Hubungkan hamparan halaman dengan halaman nombor genap +pdfjs-spread-even-button-label = Hamparan Seimbang + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Sifat Dokumen… +pdfjs-document-properties-button-label = Sifat Dokumen… +pdfjs-document-properties-file-name = Nama fail: +pdfjs-document-properties-file-size = Saiz fail: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bait) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bait) +pdfjs-document-properties-title = Tajuk: +pdfjs-document-properties-author = Pengarang: +pdfjs-document-properties-subject = Subjek: +pdfjs-document-properties-keywords = Kata kunci: +pdfjs-document-properties-creation-date = Masa Dicipta: +pdfjs-document-properties-modification-date = Tarikh Ubahsuai: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Pencipta: +pdfjs-document-properties-producer = Pengeluar PDF: +pdfjs-document-properties-version = Versi PDF: +pdfjs-document-properties-page-count = Kiraan Laman: +pdfjs-document-properties-page-size = Saiz Halaman: +pdfjs-document-properties-page-size-unit-inches = dalam +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = potret +pdfjs-document-properties-page-size-orientation-landscape = landskap +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Paparan Web Pantas: +pdfjs-document-properties-linearized-yes = Ya +pdfjs-document-properties-linearized-no = Tidak +pdfjs-document-properties-close-button = Tutup + +## Print + +pdfjs-print-progress-message = Menyediakan dokumen untuk dicetak… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Batal +pdfjs-printing-not-supported = Amaran: Cetakan ini tidak sepenuhnya disokong oleh pelayar ini. +pdfjs-printing-not-ready = Amaran: PDF tidak sepenuhnya dimuatkan untuk dicetak. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Togol Bar Sisi +pdfjs-toggle-sidebar-button-label = Togol Bar Sisi +pdfjs-document-outline-button = + .title = Papar Rangka Dokumen (klik-dua-kali untuk kembangkan/kolaps semua item) +pdfjs-document-outline-button-label = Rangka Dokumen +pdfjs-attachments-button = + .title = Papar Lampiran +pdfjs-attachments-button-label = Lampiran +pdfjs-thumbs-button = + .title = Papar Thumbnails +pdfjs-thumbs-button-label = Imej kecil +pdfjs-findbar-button = + .title = Cari didalam Dokumen +pdfjs-findbar-button-label = Cari + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Halaman { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Halaman Imej kecil { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Cari + .placeholder = Cari dalam dokumen… +pdfjs-find-previous-button = + .title = Cari teks frasa berkenaan yang terdahulu +pdfjs-find-previous-button-label = Dahulu +pdfjs-find-next-button = + .title = Cari teks frasa berkenaan yang berikut +pdfjs-find-next-button-label = Berikut +pdfjs-find-highlight-checkbox = Serlahkan semua +pdfjs-find-match-case-checkbox-label = Huruf sepadan +pdfjs-find-entire-word-checkbox-label = Seluruh perkataan +pdfjs-find-reached-top = Mencapai teratas daripada dokumen, sambungan daripada bawah +pdfjs-find-reached-bottom = Mencapai terakhir daripada dokumen, sambungan daripada atas +pdfjs-find-not-found = Frasa tidak ditemui + +## Predefined zoom values + +pdfjs-page-scale-width = Lebar Halaman +pdfjs-page-scale-fit = Muat Halaman +pdfjs-page-scale-auto = Zoom Automatik +pdfjs-page-scale-actual = Saiz Sebenar +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = Masalah berlaku semasa menuatkan sebuah PDF. +pdfjs-invalid-file-error = Tidak sah atau fail PDF rosak. +pdfjs-missing-file-error = Fail PDF Hilang. +pdfjs-unexpected-response-error = Respon pelayan yang tidak dijangka. +pdfjs-rendering-error = Ralat berlaku ketika memberikan halaman. + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Anotasi] + +## Password + +pdfjs-password-label = Masukan kata kunci untuk membuka fail PDF ini. +pdfjs-password-invalid = Kata laluan salah. Cuba lagi. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Batal +pdfjs-web-fonts-disabled = Fon web dinyahdayakan: tidak dapat menggunakan fon terbenam PDF. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/my/viewer.ftl b/public/assets/pdfjs/locale/my/viewer.ftl new file mode 100755 index 0000000..d3b973d --- /dev/null +++ b/public/assets/pdfjs/locale/my/viewer.ftl @@ -0,0 +1,206 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = အရင် စာမျက်နှာ +pdfjs-previous-button-label = အရင်နေရာ +pdfjs-next-button = + .title = ရှေ့ စာမျက်နှာ +pdfjs-next-button-label = နောက်á€á€á€¯ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = စာမျက်နှာ +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = { $pagesCount } á +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pagesCount } á { $pageNumber }) +pdfjs-zoom-out-button = + .title = á€á€»á€¯á€¶á€·á€•ါ +pdfjs-zoom-out-button-label = á€á€»á€¯á€¶á€·á€•ါ +pdfjs-zoom-in-button = + .title = á€á€»á€²á€·á€•ါ +pdfjs-zoom-in-button-label = á€á€»á€²á€·á€•ါ +pdfjs-zoom-select = + .title = á€á€»á€¯á€¶á€·/á€á€»á€²á€·á€•ါ +pdfjs-presentation-mode-button = + .title = ဆွေးနွေးá€á€„်ပြစနစ်သို့ ကူးပြောင်းပါ +pdfjs-presentation-mode-button-label = ဆွေးနွေးá€á€„်ပြစနစ် +pdfjs-open-file-button = + .title = ဖိုင်အားဖွင့်ပါዠ+pdfjs-open-file-button-label = ဖွင့်ပါ +pdfjs-print-button = + .title = ပုံနှိုပ်ပါ +pdfjs-print-button-label = ပုံနှိုပ်ပါ + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = ကိရိယာများ +pdfjs-tools-button-label = ကိရိယာများ +pdfjs-first-page-button = + .title = ပထမ စာမျက်နှာသို့ +pdfjs-first-page-button-label = ပထမ စာမျက်နှာသို့ +pdfjs-last-page-button = + .title = နောက်ဆုံး စာမျက်နှာသို့ +pdfjs-last-page-button-label = နောက်ဆုံး စာမျက်နှာသို့ +pdfjs-page-rotate-cw-button = + .title = နာရီလက်á€á€¶ အá€á€­á€¯á€„်း +pdfjs-page-rotate-cw-button-label = နာရီလက်á€á€¶ အá€á€­á€¯á€„်း +pdfjs-page-rotate-ccw-button = + .title = နာရီလက်á€á€¶ ပြောင်းပြန် +pdfjs-page-rotate-ccw-button-label = နာရီလက်á€á€¶ ပြောင်းပြန် + +## Document properties dialog + +pdfjs-document-properties-button = + .title = မှá€á€ºá€á€™á€ºá€¸á€™á€¾á€á€ºá€›á€¬ ဂုá€á€ºá€žá€á€¹á€á€­á€™á€»á€¬á€¸ +pdfjs-document-properties-button-label = မှá€á€ºá€á€™á€ºá€¸á€™á€¾á€á€ºá€›á€¬ ဂုá€á€ºá€žá€á€¹á€á€­á€™á€»á€¬á€¸ +pdfjs-document-properties-file-name = ဖိုင် : +pdfjs-document-properties-file-size = ဖိုင်ဆိုဒ် : +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } ကီလိုဘိုá€á€º ({ $size_b }ဘိုá€á€º) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = á€á€±á€«á€„်းစဉ်‌ - +pdfjs-document-properties-author = ရေးသားသူ: +pdfjs-document-properties-subject = အကြောင်းအရာ: +pdfjs-document-properties-keywords = သော့á€á€»á€€á€º စာလုံး: +pdfjs-document-properties-creation-date = ထုá€á€ºá€œá€¯á€•်ရက်စွဲ: +pdfjs-document-properties-modification-date = ပြင်ဆင်ရက်စွဲ: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = ဖန်á€á€®á€¸á€žá€°: +pdfjs-document-properties-producer = PDF ထုá€á€ºá€œá€¯á€•်သူ: +pdfjs-document-properties-version = PDF ဗားရှင်း: +pdfjs-document-properties-page-count = စာမျက်နှာအရေအá€á€½á€€á€º: + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + + +## + +pdfjs-document-properties-close-button = ပိá€á€º + +## Print + +pdfjs-print-progress-message = Preparing document for printing… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = ပယ်​ဖျက်ပါ +pdfjs-printing-not-supported = သá€á€­á€•ေးá€á€»á€€á€ºáŠá€•ရင့်ထုá€á€ºá€á€¼á€„်းကိုဤဘယောက်ဆာသည် ပြည့်á€á€…ွာထောက်ပံ့မထားပါ á‹ +pdfjs-printing-not-ready = သá€á€­á€•ေးá€á€»á€€á€º: ယá€á€¯ PDF ဖိုင်သည် ပုံနှိပ်ရန် မပြည့်စုံပါ + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = ဘေးá€á€”်းဖွင့်ပိá€á€º +pdfjs-toggle-sidebar-button-label = ဖွင့်ပိá€á€º ဆလိုက်ဒါ +pdfjs-document-outline-button = + .title = စာá€á€™á€ºá€¸á€¡á€€á€»á€‰á€ºá€¸á€á€»á€¯á€•်ကို ပြပါ (စာရင်းအားလုံးကို á€á€»á€¯á€¶á€·/á€á€»á€²á€·á€›á€”် ကလစ်နှစ်á€á€»á€€á€ºá€”ှိပ်ပါ) +pdfjs-document-outline-button-label = စာá€á€™á€ºá€¸á€¡á€€á€»á€‰á€ºá€¸á€á€»á€¯á€•် +pdfjs-attachments-button = + .title = á€á€½á€²á€á€»á€€á€ºá€™á€»á€¬á€¸ ပြပါ +pdfjs-attachments-button-label = á€á€½á€²á€‘ားá€á€»á€€á€ºá€™á€»á€¬á€¸ +pdfjs-thumbs-button = + .title = ပုံရိပ်ငယ်များကို ပြပါ +pdfjs-thumbs-button-label = ပုံရိပ်ငယ်များ +pdfjs-findbar-button = + .title = Find in Document +pdfjs-findbar-button-label = ရှာဖွေပါ + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = စာမျက်နှာ { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = စာမျက်နှာရဲ့ ပုံရိပ်ငယ် { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = ရှာဖွေပါ + .placeholder = စာá€á€™á€ºá€¸á€‘ဲá€á€½á€„် ရှာဖွေရန်… +pdfjs-find-previous-button = + .title = စကားစုရဲ့ အရင် ​ဖြစ်ပွားမှုကို ရှာဖွေပါ +pdfjs-find-previous-button-label = နောက်သို့ +pdfjs-find-next-button = + .title = စကားစုရဲ့ နောက်ထပ် ​ဖြစ်ပွားမှုကို ရှာဖွေပါ +pdfjs-find-next-button-label = ရှေ့သို့ +pdfjs-find-highlight-checkbox = အားလုံးကို မျဉ်းသားပါ +pdfjs-find-match-case-checkbox-label = စာလုံး á€á€­á€¯á€€á€ºá€†á€­á€¯á€„်ပါ +pdfjs-find-reached-top = စာမျက်နှာထိပ် ရောက်နေပြီአအဆုံးကနေ ပြန်စပါ +pdfjs-find-reached-bottom = စာမျက်နှာအဆုံး ရောက်နေပြီአထိပ်ကနေ ပြန်စပါ +pdfjs-find-not-found = စကားစု မá€á€½á€±á€·á€›á€˜á€°á€¸ + +## Predefined zoom values + +pdfjs-page-scale-width = စာမျက်နှာ အကျယ် +pdfjs-page-scale-fit = စာမျက်နှာ ကွက်á€á€­ +pdfjs-page-scale-auto = အလိုအလျောက် á€á€»á€¯á€¶á€·á€á€»á€²á€· +pdfjs-page-scale-actual = အမှန်á€á€€á€šá€ºá€›á€¾á€­á€á€²á€· အရွယ် +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = PDF ဖိုင် ကိုဆွဲá€á€„်နေá€á€»á€­á€”်မှာ အမှားá€á€…်á€á€¯á€á€½á€±á€·á€›á€•ါá€á€šá€ºá‹ +pdfjs-invalid-file-error = မရသော သို့ ပျက်နေသော PDF ဖိုင် +pdfjs-missing-file-error = PDF ပျောက်ဆုံး +pdfjs-unexpected-response-error = မမျှော်လင့်ထားသော ဆာဗာမှ ပြန်ကြားá€á€»á€€á€º +pdfjs-rendering-error = စာမျက်နှာကို ပုံဖော်နေá€á€»á€­á€”်မှာ အမှားá€á€…်á€á€¯á€á€½á€±á€·á€›á€•ါá€á€šá€ºá‹ + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } အဓိပ္ပာယ်ဖွင့်ဆိုá€á€»á€€á€º] + +## Password + +pdfjs-password-label = ယá€á€¯ PDF ကို ဖွင့်ရန် စကားá€á€¾á€€á€ºá€€á€­á€¯ ရိုက်ပါዠ+pdfjs-password-invalid = စာá€á€¾á€€á€º မှားသည်ዠထပ်ကြိုးစားကြည့်ပါዠ+pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = ပယ်​ဖျက်ပါ +pdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fonts. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/nb-NO/viewer.ftl b/public/assets/pdfjs/locale/nb-NO/viewer.ftl new file mode 100755 index 0000000..a644157 --- /dev/null +++ b/public/assets/pdfjs/locale/nb-NO/viewer.ftl @@ -0,0 +1,495 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Forrige side +pdfjs-previous-button-label = Forrige +pdfjs-next-button = + .title = Neste side +pdfjs-next-button-label = Neste +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Side +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = av { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } av { $pagesCount }) +pdfjs-zoom-out-button = + .title = Zoom ut +pdfjs-zoom-out-button-label = Zoom ut +pdfjs-zoom-in-button = + .title = Zoom inn +pdfjs-zoom-in-button-label = Zoom inn +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Bytt til presentasjonsmodus +pdfjs-presentation-mode-button-label = Presentasjonsmodus +pdfjs-open-file-button = + .title = Ã…pne fil +pdfjs-open-file-button-label = Ã…pne +pdfjs-print-button = + .title = Skriv ut +pdfjs-print-button-label = Skriv ut +pdfjs-save-button = + .title = Lagre +pdfjs-save-button-label = Lagre +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Last ned +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Last ned +pdfjs-bookmark-button = + .title = Gjeldende side (se URL fra gjeldende side) +pdfjs-bookmark-button-label = Gjeldende side + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Verktøy +pdfjs-tools-button-label = Verktøy +pdfjs-first-page-button = + .title = GÃ¥ til første side +pdfjs-first-page-button-label = GÃ¥ til første side +pdfjs-last-page-button = + .title = GÃ¥ til siste side +pdfjs-last-page-button-label = GÃ¥ til siste side +pdfjs-page-rotate-cw-button = + .title = Roter med klokken +pdfjs-page-rotate-cw-button-label = Roter med klokken +pdfjs-page-rotate-ccw-button = + .title = Roter mot klokken +pdfjs-page-rotate-ccw-button-label = Roter mot klokken +pdfjs-cursor-text-select-tool-button = + .title = Aktiver tekstmarkeringsverktøy +pdfjs-cursor-text-select-tool-button-label = Tekstmarkeringsverktøy +pdfjs-cursor-hand-tool-button = + .title = Aktiver handverktøy +pdfjs-cursor-hand-tool-button-label = Handverktøy +pdfjs-scroll-page-button = + .title = Bruk siderulling +pdfjs-scroll-page-button-label = Siderulling +pdfjs-scroll-vertical-button = + .title = Bruk vertikal rulling +pdfjs-scroll-vertical-button-label = Vertikal rulling +pdfjs-scroll-horizontal-button = + .title = Bruk horisontal rulling +pdfjs-scroll-horizontal-button-label = Horisontal rulling +pdfjs-scroll-wrapped-button = + .title = Bruk flersiderulling +pdfjs-scroll-wrapped-button-label = Flersiderulling +pdfjs-spread-none-button = + .title = Vis enkeltsider +pdfjs-spread-none-button-label = Enkeltsider +pdfjs-spread-odd-button = + .title = Vis oppslag med ulike sidenumre til venstre +pdfjs-spread-odd-button-label = Oppslag med forside +pdfjs-spread-even-button = + .title = Vis oppslag med like sidenumre til venstre +pdfjs-spread-even-button-label = Oppslag uten forside + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Dokumentegenskaper … +pdfjs-document-properties-button-label = Dokumentegenskaper … +pdfjs-document-properties-file-name = Filnavn: +pdfjs-document-properties-file-size = Filstørrelse: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } byte) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } byte) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Dokumentegenskaper … +pdfjs-document-properties-author = Forfatter: +pdfjs-document-properties-subject = Emne: +pdfjs-document-properties-keywords = Nøkkelord: +pdfjs-document-properties-creation-date = Opprettet dato: +pdfjs-document-properties-modification-date = Endret dato: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Opprettet av: +pdfjs-document-properties-producer = PDF-verktøy: +pdfjs-document-properties-version = PDF-versjon: +pdfjs-document-properties-page-count = Sideantall: +pdfjs-document-properties-page-size = Sidestørrelse: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = stÃ¥ende +pdfjs-document-properties-page-size-orientation-landscape = liggende +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Hurtig nettvisning: +pdfjs-document-properties-linearized-yes = Ja +pdfjs-document-properties-linearized-no = Nei +pdfjs-document-properties-close-button = Lukk + +## Print + +pdfjs-print-progress-message = Forbereder dokument for utskrift … +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Avbryt +pdfjs-printing-not-supported = Advarsel: Utskrift er ikke fullstendig støttet av denne nettleseren. +pdfjs-printing-not-ready = Advarsel: PDF er ikke fullstendig innlastet for utskrift. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = SlÃ¥ av/pÃ¥ sidestolpe +pdfjs-toggle-sidebar-notification-button = + .title = Vis/gjem sidestolpe (dokumentet inneholder oversikt/vedlegg/lag) +pdfjs-toggle-sidebar-button-label = SlÃ¥ av/pÃ¥ sidestolpe +pdfjs-document-outline-button = + .title = Vis dokumentdisposisjonen (dobbeltklikk for Ã¥ utvide/skjule alle elementer) +pdfjs-document-outline-button-label = Dokumentdisposisjon +pdfjs-attachments-button = + .title = Vis vedlegg +pdfjs-attachments-button-label = Vedlegg +pdfjs-layers-button = + .title = Vis lag (dobbeltklikk for Ã¥ tilbakestille alle lag til standardtilstand) +pdfjs-layers-button-label = Lag +pdfjs-thumbs-button = + .title = Vis miniatyrbilde +pdfjs-thumbs-button-label = Miniatyrbilde +pdfjs-current-outline-item-button = + .title = Finn gjeldende disposisjonselement +pdfjs-current-outline-item-button-label = Gjeldende disposisjonselement +pdfjs-findbar-button = + .title = Finn i dokumentet +pdfjs-findbar-button-label = Finn +pdfjs-additional-layers = Ytterligere lag + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Side { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatyrbilde av side { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Søk + .placeholder = Søk i dokument… +pdfjs-find-previous-button = + .title = Finn forrige forekomst av frasen +pdfjs-find-previous-button-label = Forrige +pdfjs-find-next-button = + .title = Finn neste forekomst av frasen +pdfjs-find-next-button-label = Neste +pdfjs-find-highlight-checkbox = Uthev alle +pdfjs-find-match-case-checkbox-label = Skill store/smÃ¥ bokstaver +pdfjs-find-match-diacritics-checkbox-label = Samsvar diakritiske tegn +pdfjs-find-entire-word-checkbox-label = Hele ord +pdfjs-find-reached-top = NÃ¥dde toppen av dokumentet, fortsetter fra bunnen +pdfjs-find-reached-bottom = NÃ¥dde bunnen av dokumentet, fortsetter fra toppen +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } av { $total } treff + *[other] { $current } av { $total } treff + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Mer enn { $limit } treff + *[other] Mer enn { $limit } treff + } +pdfjs-find-not-found = Fant ikke teksten + +## Predefined zoom values + +pdfjs-page-scale-width = Sidebredde +pdfjs-page-scale-fit = Tilpass til siden +pdfjs-page-scale-auto = Automatisk zoom +pdfjs-page-scale-actual = Virkelig størrelse +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale } % + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Side { $page } + +## Loading indicator messages + +pdfjs-loading-error = En feil oppstod ved lasting av PDF. +pdfjs-invalid-file-error = Ugyldig eller skadet PDF-fil. +pdfjs-missing-file-error = Manglende PDF-fil. +pdfjs-unexpected-response-error = Uventet serverrespons. +pdfjs-rendering-error = En feil oppstod ved opptegning av siden. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } annotasjon] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Skriv inn passordet for Ã¥ Ã¥pne denne PDF-filen. +pdfjs-password-invalid = Ugyldig passord. Prøv igjen. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Avbryt +pdfjs-web-fonts-disabled = Web-fonter er avslÃ¥tt: Kan ikke bruke innbundne PDF-fonter. + +## Editing + +pdfjs-editor-free-text-button = + .title = Tekst +pdfjs-editor-free-text-button-label = Tekst +pdfjs-editor-ink-button = + .title = Tegn +pdfjs-editor-ink-button-label = Tegn +pdfjs-editor-stamp-button = + .title = Legg til eller rediger bilder +pdfjs-editor-stamp-button-label = Legg til eller rediger bilder +pdfjs-editor-highlight-button = + .title = Markere +pdfjs-editor-highlight-button-label = Markere +pdfjs-highlight-floating-button1 = + .title = Markere + .aria-label = Markere +pdfjs-highlight-floating-button-label = Markere + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Fjern tegningen +pdfjs-editor-remove-freetext-button = + .title = Fjern tekst +pdfjs-editor-remove-stamp-button = + .title = Fjern bildet +pdfjs-editor-remove-highlight-button = + .title = Fjern utheving + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Farge +pdfjs-editor-free-text-size-input = Størrelse +pdfjs-editor-ink-color-input = Farge +pdfjs-editor-ink-thickness-input = Tykkelse +pdfjs-editor-ink-opacity-input = Ugjennomsiktighet +pdfjs-editor-stamp-add-image-button = + .title = Legg til bilde +pdfjs-editor-stamp-add-image-button-label = Legg til bilde +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Tykkelse +pdfjs-editor-free-highlight-thickness-title = + .title = Endre tykkelse nÃ¥r du markerer andre elementer enn tekst +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Tekstredigering + .default-content = Begynn Ã¥ skrive… +pdfjs-free-text = + .aria-label = Tekstredigering +pdfjs-free-text-default-content = Begynn Ã¥ skrive… +pdfjs-ink = + .aria-label = Tegneredigering +pdfjs-ink-canvas = + .aria-label = Brukerskapt bilde + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alt-tekst +pdfjs-editor-alt-text-edit-button = + .aria-label = Rediger alt-tekst +pdfjs-editor-alt-text-edit-button-label = Rediger alt-tekst tekst +pdfjs-editor-alt-text-dialog-label = Velg et alternativ +pdfjs-editor-alt-text-dialog-description = Alt-tekst (alternativ tekst) hjelper nÃ¥r folk ikke kan se bildet eller nÃ¥r det ikke lastes inn. +pdfjs-editor-alt-text-add-description-label = Legg til en beskrivelse +pdfjs-editor-alt-text-add-description-description = GÃ¥ etter 1-2 setninger som beskriver emnet, settingen eller handlingene. +pdfjs-editor-alt-text-mark-decorative-label = Merk som dekorativt +pdfjs-editor-alt-text-mark-decorative-description = Dette brukes til dekorative bilder, som kantlinjer eller vannmerker. +pdfjs-editor-alt-text-cancel-button = Avbryt +pdfjs-editor-alt-text-save-button = Lagre +pdfjs-editor-alt-text-decorative-tooltip = Merket som dekorativ +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = For eksempel, «En ung mann setter seg ved et bord for Ã¥ spise et mÃ¥ltid» +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alt-tekst + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Øverste venstre hjørne – endre størrelse +pdfjs-editor-resizer-label-top-middle = Øverst i midten — endre størrelse +pdfjs-editor-resizer-label-top-right = Øverste høyre hjørne – endre størrelse +pdfjs-editor-resizer-label-middle-right = Midt til høyre – endre størrelse +pdfjs-editor-resizer-label-bottom-right = Nederste høyre hjørne – endre størrelse +pdfjs-editor-resizer-label-bottom-middle = Nederst i midten — endre størrelse +pdfjs-editor-resizer-label-bottom-left = Nederste venstre hjørne – endre størrelse +pdfjs-editor-resizer-label-middle-left = Midt til venstre — endre størrelse +pdfjs-editor-resizer-top-left = + .aria-label = Øverste venstre hjørne – endre størrelse +pdfjs-editor-resizer-top-middle = + .aria-label = Øverst i midten — endre størrelse +pdfjs-editor-resizer-top-right = + .aria-label = Øverste høyre hjørne – endre størrelse +pdfjs-editor-resizer-middle-right = + .aria-label = Midt til høyre – endre størrelse +pdfjs-editor-resizer-bottom-right = + .aria-label = Nederste høyre hjørne – endre størrelse +pdfjs-editor-resizer-bottom-middle = + .aria-label = Nederst i midten — endre størrelse +pdfjs-editor-resizer-bottom-left = + .aria-label = Nederste venstre hjørne – endre størrelse +pdfjs-editor-resizer-middle-left = + .aria-label = Midt til venstre — endre størrelse + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Uthevingsfarge +pdfjs-editor-colorpicker-button = + .title = Endre farge +pdfjs-editor-colorpicker-dropdown = + .aria-label = Fargevalg +pdfjs-editor-colorpicker-yellow = + .title = Gul +pdfjs-editor-colorpicker-green = + .title = Grønn +pdfjs-editor-colorpicker-blue = + .title = BlÃ¥ +pdfjs-editor-colorpicker-pink = + .title = Rosa +pdfjs-editor-colorpicker-red = + .title = Rød + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Vis alle +pdfjs-editor-highlight-show-all-button = + .title = Vis alle + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Rediger alternativ tekst (bildebeskrivelse) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Legg til alternativ tekst (bildebeskrivelse) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Skriv din beskrivelse her… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Kort beskrivelse for folk som ikke kan se bildet eller nÃ¥r bildet ikke lastes inn. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Denne alternative teksten ble opprettet automatisk og kan være unøyaktig. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Les mer +pdfjs-editor-new-alt-text-create-automatically-button-label = Lag alternativ tekst automatisk +pdfjs-editor-new-alt-text-not-now-button = Ikke nÃ¥ +pdfjs-editor-new-alt-text-error-title = Kunne ikke opprette alternativ tekst automatisk +pdfjs-editor-new-alt-text-error-description = Skriv din egen alternativ-tekst eller prøv igjen senere. +pdfjs-editor-new-alt-text-error-close-button = Lukk +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Laster ned alternativ tekst AI-modell ({ $downloadedSize } av { $totalSize } MB) + .aria-valuetext = Laster ned alternativ tekst AI-modell ({ $downloadedSize } av { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alt-tekst lagt til +pdfjs-editor-new-alt-text-added-button-label = Alternativ tekst lagt til +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Mangler alt-tekst +pdfjs-editor-new-alt-text-missing-button-label = Mangler alternativ tekst +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = GjennomgÃ¥ alt-tekst +pdfjs-editor-new-alt-text-to-review-button-label = GjennomgÃ¥ alternativ tekst +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Opprettet automatisk: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Innstillinger for alternativ tekst for bilde +pdfjs-image-alt-text-settings-button-label = Innstillinger for alternativ tekst for bilde +pdfjs-editor-alt-text-settings-dialog-label = Innstillinger for alternativ tekst for bilde +pdfjs-editor-alt-text-settings-automatic-title = Automatisk alternativ tekst +pdfjs-editor-alt-text-settings-create-model-button-label = Opprett alternativ tekst automatisk +pdfjs-editor-alt-text-settings-create-model-description = ForeslÃ¥r beskrivelser for Ã¥ hjelpe folk som ikke kan se bildet eller nÃ¥r bildet ikke lastes inn. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Alternativ tekst AI-modell ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Kjører lokalt pÃ¥ enheten din slik at dataene dine forblir private. Nødvendig for automatisk alternativ tekst. +pdfjs-editor-alt-text-settings-delete-model-button = Slett +pdfjs-editor-alt-text-settings-download-model-button = Last ned +pdfjs-editor-alt-text-settings-downloading-model-button = Laster ned… +pdfjs-editor-alt-text-settings-editor-title = Alternativ tekst-redigerer +pdfjs-editor-alt-text-settings-show-dialog-button-label = Vis alternativ tekst-redigerer direkte nÃ¥r du legger til et bilde +pdfjs-editor-alt-text-settings-show-dialog-description = Hjelper deg Ã¥ sørge for at alle bildene dine har alternativ tekst. +pdfjs-editor-alt-text-settings-close-button = Lukk diff --git a/public/assets/pdfjs/locale/ne-NP/viewer.ftl b/public/assets/pdfjs/locale/ne-NP/viewer.ftl new file mode 100755 index 0000000..65193b6 --- /dev/null +++ b/public/assets/pdfjs/locale/ne-NP/viewer.ftl @@ -0,0 +1,234 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = अघिलà¥à¤²à¥‹ पृषà¥à¤  +pdfjs-previous-button-label = अघिलà¥à¤²à¥‹ +pdfjs-next-button = + .title = पछिलà¥à¤²à¥‹ पृषà¥à¤  +pdfjs-next-button-label = पछिलà¥à¤²à¥‹ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = पृषà¥à¤  +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = { $pagesCount } मधà¥à¤¯à¥‡ +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pagesCount } को { $pageNumber }) +pdfjs-zoom-out-button = + .title = जà¥à¤® घटाउनà¥à¤¹à¥‹à¤¸à¥ +pdfjs-zoom-out-button-label = जà¥à¤® घटाउनà¥à¤¹à¥‹à¤¸à¥ +pdfjs-zoom-in-button = + .title = जà¥à¤® बढाउनà¥à¤¹à¥‹à¤¸à¥ +pdfjs-zoom-in-button-label = जà¥à¤® बढाउनà¥à¤¹à¥‹à¤¸à¥ +pdfjs-zoom-select = + .title = जà¥à¤® गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-presentation-mode-button = + .title = पà¥à¤°à¤¸à¥à¤¤à¥à¤¤à¤¿ मोडमा जानà¥à¤¹à¥‹à¤¸à¥ +pdfjs-presentation-mode-button-label = पà¥à¤°à¤¸à¥à¤¤à¥à¤¤à¤¿ मोड +pdfjs-open-file-button = + .title = फाइल खोलà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-open-file-button-label = खोलà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-print-button = + .title = मà¥à¤¦à¥à¤°à¤£ गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-print-button-label = मà¥à¤¦à¥à¤°à¤£ गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = औजारहरू +pdfjs-tools-button-label = औजारहरू +pdfjs-first-page-button = + .title = पहिलो पृषà¥à¤ à¤®à¤¾ जानà¥à¤¹à¥‹à¤¸à¥ +pdfjs-first-page-button-label = पहिलो पृषà¥à¤ à¤®à¤¾ जानà¥à¤¹à¥‹à¤¸à¥ +pdfjs-last-page-button = + .title = पछिलà¥à¤²à¥‹ पृषà¥à¤ à¤®à¤¾ जानà¥à¤¹à¥‹à¤¸à¥ +pdfjs-last-page-button-label = पछिलà¥à¤²à¥‹ पृषà¥à¤ à¤®à¤¾ जानà¥à¤¹à¥‹à¤¸à¥ +pdfjs-page-rotate-cw-button = + .title = घडीको दिशामा घà¥à¤®à¤¾à¤‰à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-page-rotate-cw-button-label = घडीको दिशामा घà¥à¤®à¤¾à¤‰à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-page-rotate-ccw-button = + .title = घडीको विपरित दिशामा घà¥à¤®à¤¾à¤‰à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-page-rotate-ccw-button-label = घडीको विपरित दिशामा घà¥à¤®à¤¾à¤‰à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-cursor-text-select-tool-button = + .title = पाठ चयन उपकरण सकà¥à¤·à¤® गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-cursor-text-select-tool-button-label = पाठ चयन उपकरण +pdfjs-cursor-hand-tool-button = + .title = हाते उपकरण सकà¥à¤·à¤® गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-cursor-hand-tool-button-label = हाते उपकरण +pdfjs-scroll-vertical-button = + .title = ठाडो सà¥à¤•à¥à¤°à¥‹à¤²à¤¿à¤™à¥à¤— पà¥à¤°à¤¯à¥‹à¤— गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-scroll-vertical-button-label = ठाडो सà¥à¤•à¥à¤°à¥à¤°à¥‹à¤²à¤¿à¤™à¥à¤— +pdfjs-scroll-horizontal-button = + .title = तेरà¥à¤¸à¥‹ सà¥à¤•à¥à¤°à¥‹à¤²à¤¿à¤™à¥à¤— पà¥à¤°à¤¯à¥‹à¤— गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-scroll-horizontal-button-label = तेरà¥à¤¸à¥‹ सà¥à¤•à¥à¤°à¥‹à¤²à¤¿à¤™à¥à¤— +pdfjs-scroll-wrapped-button = + .title = लिपि सà¥à¤•à¥à¤°à¥‹à¤²à¤¿à¤™à¥à¤— पà¥à¤°à¤¯à¥‹à¤— गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-scroll-wrapped-button-label = लिपि सà¥à¤•à¥à¤°à¥‹à¤²à¤¿à¤™à¥à¤— +pdfjs-spread-none-button = + .title = पृषà¥à¤  सà¥à¤ªà¥à¤°à¥‡à¤¡à¤®à¤¾ सामेल हà¥à¤¨à¥à¤¹à¥à¤¨à¥à¤¨ +pdfjs-spread-none-button-label = सà¥à¤ªà¥à¤°à¥‡à¤¡ छैन + +## Document properties dialog + +pdfjs-document-properties-button = + .title = कागजात विशेषताहरू... +pdfjs-document-properties-button-label = कागजात विशेषताहरू... +pdfjs-document-properties-file-name = फाइल नाम: +pdfjs-document-properties-file-size = फाइल आकार: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = शीरà¥à¤·à¤•: +pdfjs-document-properties-author = लेखक: +pdfjs-document-properties-subject = विषयः +pdfjs-document-properties-keywords = शबà¥à¤¦à¤•à¥à¤žà¥à¤œà¥€à¤ƒ +pdfjs-document-properties-creation-date = सिरà¥à¤œà¤¨à¤¾ गरिà¤à¤•ो मिति: +pdfjs-document-properties-modification-date = परिमारà¥à¤œà¤¿à¤¤ मिति: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = सरà¥à¤œà¤•: +pdfjs-document-properties-producer = PDF निरà¥à¤®à¤¾à¤¤à¤¾: +pdfjs-document-properties-version = PDF संसà¥à¤•रण +pdfjs-document-properties-page-count = पृषà¥à¤  गणना: +pdfjs-document-properties-page-size = पृषà¥à¤  आकार: +pdfjs-document-properties-page-size-unit-inches = इनà¥à¤š +pdfjs-document-properties-page-size-unit-millimeters = मि.मि. +pdfjs-document-properties-page-size-orientation-portrait = पोटà¥à¤°à¥‡à¤Ÿ +pdfjs-document-properties-page-size-orientation-landscape = परिदृशà¥à¤¯ +pdfjs-document-properties-page-size-name-letter = अकà¥à¤·à¤° +pdfjs-document-properties-page-size-name-legal = कानूनी + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + + +## + +pdfjs-document-properties-linearized-yes = हो +pdfjs-document-properties-linearized-no = होइन +pdfjs-document-properties-close-button = बनà¥à¤¦ गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ + +## Print + +pdfjs-print-progress-message = मà¥à¤¦à¥à¤°à¤£à¤•ा लागि कागजात तयारी गरिदै… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = रदà¥à¤¦ गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-printing-not-supported = चेतावनी: यो बà¥à¤°à¤¾à¤‰à¤œà¤°à¤®à¤¾ मà¥à¤¦à¥à¤°à¤£ पूरà¥à¤£à¤¤à¤¯à¤¾ समरà¥à¤¥à¤¿à¤¤ छैन। +pdfjs-printing-not-ready = चेतावनी: PDF मà¥à¤¦à¥à¤°à¤£à¤•ा लागि पूरà¥à¤£à¤¤à¤¯à¤¾ लोड भà¤à¤•ो छैन। + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = टगल साइडबार +pdfjs-toggle-sidebar-button-label = टगल साइडबार +pdfjs-document-outline-button = + .title = कागजातको रूपरेखा देखाउनà¥à¤¹à¥‹à¤¸à¥ (सबै वसà¥à¤¤à¥à¤¹à¤°à¥‚ विसà¥à¤¤à¤¾à¤°/पतन गरà¥à¤¨ डबल-कà¥à¤²à¤¿à¤• गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥) +pdfjs-document-outline-button-label = दसà¥à¤¤à¤¾à¤µà¥‡à¤œà¤•ो रूपरेखा +pdfjs-attachments-button = + .title = संलगà¥à¤¨à¤¹à¤°à¥‚ देखाउनà¥à¤¹à¥‹à¤¸à¥ +pdfjs-attachments-button-label = संलगà¥à¤¨à¤•हरू +pdfjs-thumbs-button = + .title = थमà¥à¤¬à¤¨à¥‡à¤²à¤¹à¤°à¥‚ देखाउनà¥à¤¹à¥‹à¤¸à¥ +pdfjs-thumbs-button-label = थमà¥à¤¬à¤¨à¥‡à¤²à¤¹à¤°à¥‚ +pdfjs-findbar-button = + .title = कागजातमा फेला पारà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-findbar-button-label = फेला पारà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = पृषà¥à¤  { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = { $page } पृषà¥à¤ à¤•ो थमà¥à¤¬à¤¨à¥‡à¤² + +## Find panel button title and messages + +pdfjs-find-input = + .title = फेला पारà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ + .placeholder = कागजातमा फेला पारà¥à¤¨à¥à¤¹à¥‹à¤¸à¥â€¦ +pdfjs-find-previous-button = + .title = यस वाकà¥à¤¯à¤¾à¤‚शको अघिलà¥à¤²à¥‹ घटना फेला पारà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-find-previous-button-label = अघिलà¥à¤²à¥‹ +pdfjs-find-next-button = + .title = यस वाकà¥à¤¯à¤¾à¤‚शको पछिलà¥à¤²à¥‹ घटना फेला पारà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-find-next-button-label = अरà¥à¤•ो +pdfjs-find-highlight-checkbox = सबै हाइलाइट गरà¥à¤¨à¥‡ +pdfjs-find-match-case-checkbox-label = केस जोडा मिलाउनà¥à¤¹à¥‹à¤¸à¥ +pdfjs-find-entire-word-checkbox-label = पà¥à¤°à¤¾ शबà¥à¤¦à¤¹à¤°à¥ +pdfjs-find-reached-top = पृषà¥à¤ à¤•ो शिरà¥à¤·à¤®à¤¾ पà¥à¤—ीयो, तलबाट जारी गरिà¤à¤•ो थियो +pdfjs-find-reached-bottom = पृषà¥à¤ à¤•ो अनà¥à¤¤à¥à¤¯à¤®à¤¾ पà¥à¤—ीयो, शिरà¥à¤·à¤¬à¤¾à¤Ÿ जारी गरिà¤à¤•ो थियो +pdfjs-find-not-found = वाकà¥à¤¯à¤¾à¤‚श फेला परेन + +## Predefined zoom values + +pdfjs-page-scale-width = पृषà¥à¤  चौडाइ +pdfjs-page-scale-fit = पृषà¥à¤  ठिकà¥à¤• मिलà¥à¤¨à¥‡ +pdfjs-page-scale-auto = सà¥à¤µà¤šà¤¾à¤²à¤¿à¤¤ जà¥à¤® +pdfjs-page-scale-actual = वासà¥à¤¤à¤µà¤¿à¤• आकार +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = यो PDF लोड गरà¥à¤¦à¤¾ à¤à¤‰à¤Ÿà¤¾ तà¥à¤°à¥à¤Ÿà¤¿ देखापरà¥â€à¤¯à¥‹à¥¤ +pdfjs-invalid-file-error = अवैध वा दà¥à¤·à¤¿à¤¤ PDF फाइल। +pdfjs-missing-file-error = हराईरहेको PDF फाइल। +pdfjs-unexpected-response-error = अपà¥à¤°à¤¤à¥à¤¯à¤¾à¤¶à¤¿à¤¤ सरà¥à¤­à¤° पà¥à¤°à¤¤à¤¿à¤•à¥à¤°à¤¿à¤¯à¤¾à¥¤ +pdfjs-rendering-error = पृषà¥à¤  पà¥à¤°à¤¤à¤¿à¤ªà¤¾à¤¦à¤¨ गरà¥à¤¦à¤¾ à¤à¤‰à¤Ÿà¤¾ तà¥à¤°à¥à¤Ÿà¤¿ देखापरà¥â€à¤¯à¥‹à¥¤ + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Annotation] + +## Password + +pdfjs-password-label = यस PDF फाइललाई खोलà¥à¤¨ गोपà¥à¤¯à¤¶à¤¬à¥à¤¦ पà¥à¤°à¤µà¤¿à¤·à¥à¤Ÿ गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥à¥¤ +pdfjs-password-invalid = अवैध गोपà¥à¤¯à¤¶à¤¬à¥à¤¦à¥¤ पà¥à¤¨à¤ƒ पà¥à¤°à¤¯à¤¾à¤¸ गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥à¥¤ +pdfjs-password-ok-button = ठिक छ +pdfjs-password-cancel-button = रदà¥à¤¦ गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +pdfjs-web-fonts-disabled = वेब फनà¥à¤Ÿ असकà¥à¤·à¤® छनà¥: à¤à¤®à¥à¤¬à¥‡à¤¡à¥‡à¤¡ PDF फनà¥à¤Ÿ पà¥à¤°à¤¯à¥‹à¤— गरà¥à¤¨ असमरà¥à¤¥à¥¤ + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/nl/viewer.ftl b/public/assets/pdfjs/locale/nl/viewer.ftl new file mode 100755 index 0000000..fe24ce7 --- /dev/null +++ b/public/assets/pdfjs/locale/nl/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Vorige pagina +pdfjs-previous-button-label = Vorige +pdfjs-next-button = + .title = Volgende pagina +pdfjs-next-button-label = Volgende +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Pagina +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = van { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } van { $pagesCount }) +pdfjs-zoom-out-button = + .title = Uitzoomen +pdfjs-zoom-out-button-label = Uitzoomen +pdfjs-zoom-in-button = + .title = Inzoomen +pdfjs-zoom-in-button-label = Inzoomen +pdfjs-zoom-select = + .title = Zoomen +pdfjs-presentation-mode-button = + .title = Wisselen naar presentatiemodus +pdfjs-presentation-mode-button-label = Presentatiemodus +pdfjs-open-file-button = + .title = Bestand openen +pdfjs-open-file-button-label = Openen +pdfjs-print-button = + .title = Afdrukken +pdfjs-print-button-label = Afdrukken +pdfjs-save-button = + .title = Opslaan +pdfjs-save-button-label = Opslaan +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Downloaden +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Downloaden +pdfjs-bookmark-button = + .title = Huidige pagina (URL van huidige pagina bekijken) +pdfjs-bookmark-button-label = Huidige pagina + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Hulpmiddelen +pdfjs-tools-button-label = Hulpmiddelen +pdfjs-first-page-button = + .title = Naar eerste pagina gaan +pdfjs-first-page-button-label = Naar eerste pagina gaan +pdfjs-last-page-button = + .title = Naar laatste pagina gaan +pdfjs-last-page-button-label = Naar laatste pagina gaan +pdfjs-page-rotate-cw-button = + .title = Rechtsom draaien +pdfjs-page-rotate-cw-button-label = Rechtsom draaien +pdfjs-page-rotate-ccw-button = + .title = Linksom draaien +pdfjs-page-rotate-ccw-button-label = Linksom draaien +pdfjs-cursor-text-select-tool-button = + .title = Tekstselectiehulpmiddel inschakelen +pdfjs-cursor-text-select-tool-button-label = Tekstselectiehulpmiddel +pdfjs-cursor-hand-tool-button = + .title = Handhulpmiddel inschakelen +pdfjs-cursor-hand-tool-button-label = Handhulpmiddel +pdfjs-scroll-page-button = + .title = Paginascrollen gebruiken +pdfjs-scroll-page-button-label = Paginascrollen +pdfjs-scroll-vertical-button = + .title = Verticaal scrollen gebruiken +pdfjs-scroll-vertical-button-label = Verticaal scrollen +pdfjs-scroll-horizontal-button = + .title = Horizontaal scrollen gebruiken +pdfjs-scroll-horizontal-button-label = Horizontaal scrollen +pdfjs-scroll-wrapped-button = + .title = Scrollen met terugloop gebruiken +pdfjs-scroll-wrapped-button-label = Scrollen met terugloop +pdfjs-spread-none-button = + .title = Dubbele pagina’s niet samenvoegen +pdfjs-spread-none-button-label = Geen dubbele pagina’s +pdfjs-spread-odd-button = + .title = Dubbele pagina’s samenvoegen vanaf oneven pagina’s +pdfjs-spread-odd-button-label = Oneven dubbele pagina’s +pdfjs-spread-even-button = + .title = Dubbele pagina’s samenvoegen vanaf even pagina’s +pdfjs-spread-even-button-label = Even dubbele pagina’s + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Documenteigenschappen… +pdfjs-document-properties-button-label = Documenteigenschappen… +pdfjs-document-properties-file-name = Bestandsnaam: +pdfjs-document-properties-file-size = Bestandsgrootte: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Titel: +pdfjs-document-properties-author = Auteur: +pdfjs-document-properties-subject = Onderwerp: +pdfjs-document-properties-keywords = Sleutelwoorden: +pdfjs-document-properties-creation-date = Aanmaakdatum: +pdfjs-document-properties-modification-date = Wijzigingsdatum: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Maker: +pdfjs-document-properties-producer = PDF-producent: +pdfjs-document-properties-version = PDF-versie: +pdfjs-document-properties-page-count = Aantal pagina’s: +pdfjs-document-properties-page-size = Paginagrootte: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = staand +pdfjs-document-properties-page-size-orientation-landscape = liggend +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Snelle webweergave: +pdfjs-document-properties-linearized-yes = Ja +pdfjs-document-properties-linearized-no = Nee +pdfjs-document-properties-close-button = Sluiten + +## Print + +pdfjs-print-progress-message = Document voorbereiden voor afdrukken… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Annuleren +pdfjs-printing-not-supported = Waarschuwing: afdrukken wordt niet volledig ondersteund door deze browser. +pdfjs-printing-not-ready = Waarschuwing: de PDF is niet volledig geladen voor afdrukken. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Zijbalk in-/uitschakelen +pdfjs-toggle-sidebar-notification-button = + .title = Zijbalk in-/uitschakelen (document bevat overzicht/bijlagen/lagen) +pdfjs-toggle-sidebar-button-label = Zijbalk in-/uitschakelen +pdfjs-document-outline-button = + .title = Documentoverzicht tonen (dubbelklik om alle items uit/samen te vouwen) +pdfjs-document-outline-button-label = Documentoverzicht +pdfjs-attachments-button = + .title = Bijlagen tonen +pdfjs-attachments-button-label = Bijlagen +pdfjs-layers-button = + .title = Lagen tonen (dubbelklik om alle lagen naar de standaardstatus terug te zetten) +pdfjs-layers-button-label = Lagen +pdfjs-thumbs-button = + .title = Miniaturen tonen +pdfjs-thumbs-button-label = Miniaturen +pdfjs-current-outline-item-button = + .title = Huidig item in inhoudsopgave zoeken +pdfjs-current-outline-item-button-label = Huidig item in inhoudsopgave +pdfjs-findbar-button = + .title = Zoeken in document +pdfjs-findbar-button-label = Zoeken +pdfjs-additional-layers = Aanvullende lagen + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Pagina { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatuur van pagina { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Zoeken + .placeholder = Zoeken in document… +pdfjs-find-previous-button = + .title = De vorige overeenkomst van de tekst zoeken +pdfjs-find-previous-button-label = Vorige +pdfjs-find-next-button = + .title = De volgende overeenkomst van de tekst zoeken +pdfjs-find-next-button-label = Volgende +pdfjs-find-highlight-checkbox = Alles markeren +pdfjs-find-match-case-checkbox-label = Hoofdlettergevoelig +pdfjs-find-match-diacritics-checkbox-label = Diakritische tekens gebruiken +pdfjs-find-entire-word-checkbox-label = Hele woorden +pdfjs-find-reached-top = Bovenkant van document bereikt, doorgegaan vanaf onderkant +pdfjs-find-reached-bottom = Onderkant van document bereikt, doorgegaan vanaf bovenkant +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } van { $total } overeenkomst + *[other] { $current } van { $total } overeenkomsten + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Meer dan { $limit } overeenkomst + *[other] Meer dan { $limit } overeenkomsten + } +pdfjs-find-not-found = Tekst niet gevonden + +## Predefined zoom values + +pdfjs-page-scale-width = Paginabreedte +pdfjs-page-scale-fit = Hele pagina +pdfjs-page-scale-auto = Automatisch zoomen +pdfjs-page-scale-actual = Werkelijke grootte +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Pagina { $page } + +## Loading indicator messages + +pdfjs-loading-error = Er is een fout opgetreden bij het laden van de PDF. +pdfjs-invalid-file-error = Ongeldig of beschadigd PDF-bestand. +pdfjs-missing-file-error = PDF-bestand ontbreekt. +pdfjs-unexpected-response-error = Onverwacht serverantwoord. +pdfjs-rendering-error = Er is een fout opgetreden bij het weergeven van de pagina. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type }-aantekening] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Voer het wachtwoord in om dit PDF-bestand te openen. +pdfjs-password-invalid = Ongeldig wachtwoord. Probeer het opnieuw. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Annuleren +pdfjs-web-fonts-disabled = Weblettertypen zijn uitgeschakeld: gebruik van ingebedde PDF-lettertypen is niet mogelijk. + +## Editing + +pdfjs-editor-free-text-button = + .title = Tekst +pdfjs-editor-free-text-button-label = Tekst +pdfjs-editor-ink-button = + .title = Tekenen +pdfjs-editor-ink-button-label = Tekenen +pdfjs-editor-stamp-button = + .title = Afbeeldingen toevoegen of bewerken +pdfjs-editor-stamp-button-label = Afbeeldingen toevoegen of bewerken +pdfjs-editor-highlight-button = + .title = Markeren +pdfjs-editor-highlight-button-label = Markeren +pdfjs-highlight-floating-button1 = + .title = Markeren + .aria-label = Markeren +pdfjs-highlight-floating-button-label = Markeren + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Tekening verwijderen +pdfjs-editor-remove-freetext-button = + .title = Tekst verwijderen +pdfjs-editor-remove-stamp-button = + .title = Afbeelding verwijderen +pdfjs-editor-remove-highlight-button = + .title = Markering verwijderen + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Kleur +pdfjs-editor-free-text-size-input = Grootte +pdfjs-editor-ink-color-input = Kleur +pdfjs-editor-ink-thickness-input = Dikte +pdfjs-editor-ink-opacity-input = Opaciteit +pdfjs-editor-stamp-add-image-button = + .title = Afbeelding toevoegen +pdfjs-editor-stamp-add-image-button-label = Afbeelding toevoegen +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Dikte +pdfjs-editor-free-highlight-thickness-title = + .title = Dikte wijzigen bij accentuering van andere items dan tekst +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Tekstbewerker + .default-content = Start met typen… +pdfjs-free-text = + .aria-label = Tekstbewerker +pdfjs-free-text-default-content = Begin met typen… +pdfjs-ink = + .aria-label = Tekeningbewerker +pdfjs-ink-canvas = + .aria-label = Door gebruiker gemaakte afbeelding + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alternatieve tekst +pdfjs-editor-alt-text-edit-button = + .aria-label = Alternatieve tekst bewerken +pdfjs-editor-alt-text-edit-button-label = Alternatieve tekst bewerken +pdfjs-editor-alt-text-dialog-label = Kies een optie +pdfjs-editor-alt-text-dialog-description = Alternatieve tekst helpt wanneer mensen de afbeelding niet kunnen zien of wanneer deze niet wordt geladen. +pdfjs-editor-alt-text-add-description-label = Voeg een beschrijving toe +pdfjs-editor-alt-text-add-description-description = Streef naar 1-2 zinnen die het onderwerp, de omgeving of de acties beschrijven. +pdfjs-editor-alt-text-mark-decorative-label = Als decoratief markeren +pdfjs-editor-alt-text-mark-decorative-description = Dit wordt gebruikt voor sierafbeeldingen, zoals randen of watermerken. +pdfjs-editor-alt-text-cancel-button = Annuleren +pdfjs-editor-alt-text-save-button = Opslaan +pdfjs-editor-alt-text-decorative-tooltip = Als decoratief gemarkeerd +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Bijvoorbeeld: ‘Een jonge man gaat aan een tafel zitten om te eten’ +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alternatieve tekst + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Linkerbovenhoek – formaat wijzigen +pdfjs-editor-resizer-label-top-middle = Midden boven – formaat wijzigen +pdfjs-editor-resizer-label-top-right = Rechterbovenhoek – formaat wijzigen +pdfjs-editor-resizer-label-middle-right = Midden rechts – formaat wijzigen +pdfjs-editor-resizer-label-bottom-right = Rechterbenedenhoek – formaat wijzigen +pdfjs-editor-resizer-label-bottom-middle = Midden onder – formaat wijzigen +pdfjs-editor-resizer-label-bottom-left = Linkerbenedenhoek – formaat wijzigen +pdfjs-editor-resizer-label-middle-left = Links midden – formaat wijzigen +pdfjs-editor-resizer-top-left = + .aria-label = Linkerbovenhoek – formaat wijzigen +pdfjs-editor-resizer-top-middle = + .aria-label = Midden boven – formaat wijzigen +pdfjs-editor-resizer-top-right = + .aria-label = Rechterbovenhoek – formaat wijzigen +pdfjs-editor-resizer-middle-right = + .aria-label = Midden rechts – formaat wijzigen +pdfjs-editor-resizer-bottom-right = + .aria-label = Rechterbenedenhoek – formaat wijzigen +pdfjs-editor-resizer-bottom-middle = + .aria-label = Midden onder – formaat wijzigen +pdfjs-editor-resizer-bottom-left = + .aria-label = Linkerbenedenhoek – formaat wijzigen +pdfjs-editor-resizer-middle-left = + .aria-label = Links midden – formaat wijzigen + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Markeringskleur +pdfjs-editor-colorpicker-button = + .title = Kleur wijzigen +pdfjs-editor-colorpicker-dropdown = + .aria-label = Kleurkeuzes +pdfjs-editor-colorpicker-yellow = + .title = Geel +pdfjs-editor-colorpicker-green = + .title = Groen +pdfjs-editor-colorpicker-blue = + .title = Blauw +pdfjs-editor-colorpicker-pink = + .title = Roze +pdfjs-editor-colorpicker-red = + .title = Rood + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Alles tonen +pdfjs-editor-highlight-show-all-button = + .title = Alles tonen + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Alternatieve tekst (afbeeldingsbeschrijving) bewerken +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Alternatieve tekst (afbeeldingsbeschrijving) toevoegen +pdfjs-editor-new-alt-text-textarea = + .placeholder = Schrijf hier uw beschrijving… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Korte beschrijving voor mensen die de afbeelding niet kunnen zien of wanneer de afbeelding niet wordt geladen. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Deze alternatieve tekst is automatisch gemaakt en is mogelijk onjuist. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Meer info +pdfjs-editor-new-alt-text-create-automatically-button-label = Alternatieve tekst automatisch aanmaken +pdfjs-editor-new-alt-text-not-now-button = Niet nu +pdfjs-editor-new-alt-text-error-title = Kan alternatieve tekst niet automatisch aanmaken +pdfjs-editor-new-alt-text-error-description = Schrijf uw eigen alternatieve tekst of probeer het later nog eens. +pdfjs-editor-new-alt-text-error-close-button = Sluiten +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = AI-model voor alternatieve tekst downloaden ({ $downloadedSize } van { $totalSize } MB) + .aria-valuetext = AI-model voor alternatieve tekst downloaden ({ $downloadedSize } van { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alternatieve tekst toegevoegd +pdfjs-editor-new-alt-text-added-button-label = Alternatieve tekst toegevoegd +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Alternatieve tekst ontbreekt +pdfjs-editor-new-alt-text-missing-button-label = Alternatieve tekst ontbreekt +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Alternatieve tekst beoordelen +pdfjs-editor-new-alt-text-to-review-button-label = Alternatieve tekst beoordelen +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automatisch aangemaakt: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Instellingen voor alternatieve tekst van afbeeldingen +pdfjs-image-alt-text-settings-button-label = Instellingen voor alternatieve tekst van afbeeldingen +pdfjs-editor-alt-text-settings-dialog-label = Instellingen voor alternatieve tekst van afbeeldingen +pdfjs-editor-alt-text-settings-automatic-title = Automatische alternatieve tekst +pdfjs-editor-alt-text-settings-create-model-button-label = Alternatieve tekst automatisch aanmaken +pdfjs-editor-alt-text-settings-create-model-description = Stelt beschrijvingen voor om mensen te helpen die de afbeelding niet kunnen zien of voor wie de afbeelding niet wordt geladen. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = AI-model voor alternatieve tekst ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Wordt lokaal op uw apparaat uitgevoerd, zodat uw gegevens privé blijven. Vereist voor automatische alternatieve tekst. +pdfjs-editor-alt-text-settings-delete-model-button = Verwijderen +pdfjs-editor-alt-text-settings-download-model-button = Downloaden +pdfjs-editor-alt-text-settings-downloading-model-button = Downloaden… +pdfjs-editor-alt-text-settings-editor-title = Alternatieve-tekstbewerker +pdfjs-editor-alt-text-settings-show-dialog-button-label = Alternatieve-tekstbewerker meteen tonen bij toevoegen van een afbeelding +pdfjs-editor-alt-text-settings-show-dialog-description = Helpt u ervoor te zorgen dat al uw afbeeldingen alternatieve tekst hebben. +pdfjs-editor-alt-text-settings-close-button = Sluiten + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Markering verwijderd +pdfjs-editor-undo-bar-message-freetext = Tekst verwijderd +pdfjs-editor-undo-bar-message-ink = Tekening verwijderd +pdfjs-editor-undo-bar-message-stamp = Afbeelding verwijderd +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } annotatie verwijderd + *[other] { $count } annotaties verwijderd + } +pdfjs-editor-undo-bar-undo-button = + .title = Ongedaan maken +pdfjs-editor-undo-bar-undo-button-label = Ongedaan maken +pdfjs-editor-undo-bar-close-button = + .title = Sluiten +pdfjs-editor-undo-bar-close-button-label = Sluiten diff --git a/public/assets/pdfjs/locale/nn-NO/viewer.ftl b/public/assets/pdfjs/locale/nn-NO/viewer.ftl new file mode 100755 index 0000000..d617e16 --- /dev/null +++ b/public/assets/pdfjs/locale/nn-NO/viewer.ftl @@ -0,0 +1,498 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = FøregÃ¥ande side +pdfjs-previous-button-label = FøregÃ¥ande +pdfjs-next-button = + .title = Neste side +pdfjs-next-button-label = Neste +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Side +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = av { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } av { $pagesCount }) +pdfjs-zoom-out-button = + .title = Zoom ut +pdfjs-zoom-out-button-label = Zoom ut +pdfjs-zoom-in-button = + .title = Zoom inn +pdfjs-zoom-in-button-label = Zoom inn +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Byt til presentasjonsmodus +pdfjs-presentation-mode-button-label = Presentasjonsmodus +pdfjs-open-file-button = + .title = Opne fil +pdfjs-open-file-button-label = Opne +pdfjs-print-button = + .title = Skriv ut +pdfjs-print-button-label = Skriv ut +pdfjs-save-button = + .title = Lagre +pdfjs-save-button-label = Lagre +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Last ned +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Last ned +pdfjs-bookmark-button = + .title = Gjeldande side (sjÃ¥ URL frÃ¥ gjeldande side) +pdfjs-bookmark-button-label = Gjeldande side + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Verktøy +pdfjs-tools-button-label = Verktøy +pdfjs-first-page-button = + .title = GÃ¥ til første side +pdfjs-first-page-button-label = GÃ¥ til første side +pdfjs-last-page-button = + .title = GÃ¥ til siste side +pdfjs-last-page-button-label = GÃ¥ til siste side +pdfjs-page-rotate-cw-button = + .title = Roter med klokka +pdfjs-page-rotate-cw-button-label = Roter med klokka +pdfjs-page-rotate-ccw-button = + .title = Roter mot klokka +pdfjs-page-rotate-ccw-button-label = Roter mot klokka +pdfjs-cursor-text-select-tool-button = + .title = Aktiver tekstmarkeringsverktøy +pdfjs-cursor-text-select-tool-button-label = Tekstmarkeringsverktøy +pdfjs-cursor-hand-tool-button = + .title = Aktiver handverktøy +pdfjs-cursor-hand-tool-button-label = Handverktøy +pdfjs-scroll-page-button = + .title = Bruk siderulling +pdfjs-scroll-page-button-label = Siderulling +pdfjs-scroll-vertical-button = + .title = Bruk vertikal rulling +pdfjs-scroll-vertical-button-label = Vertikal rulling +pdfjs-scroll-horizontal-button = + .title = Bruk horisontal rulling +pdfjs-scroll-horizontal-button-label = Horisontal rulling +pdfjs-scroll-wrapped-button = + .title = Bruk fleirsiderulling +pdfjs-scroll-wrapped-button-label = Fleirsiderulling +pdfjs-spread-none-button = + .title = Vis enkeltsider +pdfjs-spread-none-button-label = Enkeltside +pdfjs-spread-odd-button = + .title = Vis oppslag med ulike sidenummer til venstre +pdfjs-spread-odd-button-label = Oppslag med framside +pdfjs-spread-even-button = + .title = Vis oppslag med like sidenummmer til venstre +pdfjs-spread-even-button-label = Oppslag utan framside + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Dokumenteigenskapar… +pdfjs-document-properties-button-label = Dokumenteigenskapar… +pdfjs-document-properties-file-name = Filnamn: +pdfjs-document-properties-file-size = Filstorleik: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kB ({ $b } byte) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } byte) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Tittel: +pdfjs-document-properties-author = Forfattar: +pdfjs-document-properties-subject = Emne: +pdfjs-document-properties-keywords = Stikkord: +pdfjs-document-properties-creation-date = Dato oppretta: +pdfjs-document-properties-modification-date = Dato endra: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Oppretta av: +pdfjs-document-properties-producer = PDF-verktøy: +pdfjs-document-properties-version = PDF-versjon: +pdfjs-document-properties-page-count = Sidetal: +pdfjs-document-properties-page-size = Sidestørrelse: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = stÃ¥ande (portrait) +pdfjs-document-properties-page-size-orientation-landscape = liggande (landscape) +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Brev +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Rask nettvising: +pdfjs-document-properties-linearized-yes = Ja +pdfjs-document-properties-linearized-no = Nei +pdfjs-document-properties-close-button = Lat att + +## Print + +pdfjs-print-progress-message = Førebur dokumentet for utskrift… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Avbryt +pdfjs-printing-not-supported = Ã…tvaring: Utskrift er ikkje fullstendig støtta av denne nettlesaren. +pdfjs-printing-not-ready = Ã…tvaring: PDF ikkje fullstendig innlasta for utskrift. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = SlÃ¥ av/pÃ¥ sidestolpe +pdfjs-toggle-sidebar-notification-button = + .title = Vis/gøym sidestolpe (dokumentet inneheld oversikt/vedlegg/lag) +pdfjs-toggle-sidebar-button-label = SlÃ¥ av/pÃ¥ sidestolpe +pdfjs-document-outline-button = + .title = Vis dokumentdisposisjonen (dobbelklikk for Ã¥ utvide/gøyme alle elementa) +pdfjs-document-outline-button-label = Dokumentdisposisjon +pdfjs-attachments-button = + .title = Vis vedlegg +pdfjs-attachments-button-label = Vedlegg +pdfjs-layers-button = + .title = Vis lag (dobbeltklikk for Ã¥ tilbakestille alle lag til standardtilstand) +pdfjs-layers-button-label = Lag +pdfjs-thumbs-button = + .title = Vis miniatyrbilde +pdfjs-thumbs-button-label = Miniatyrbilde +pdfjs-current-outline-item-button = + .title = Finn gjeldande disposisjonselement +pdfjs-current-outline-item-button-label = Gjeldande disposisjonselement +pdfjs-findbar-button = + .title = Finn i dokumentet +pdfjs-findbar-button-label = Finn +pdfjs-additional-layers = Ytterlegare lag + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Side { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatyrbilde av side { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Søk + .placeholder = Søk i dokument… +pdfjs-find-previous-button = + .title = Finn førre førekomst av frasen +pdfjs-find-previous-button-label = Førre +pdfjs-find-next-button = + .title = Finn neste førekomst av frasen +pdfjs-find-next-button-label = Neste +pdfjs-find-highlight-checkbox = Uthev alle +pdfjs-find-match-case-checkbox-label = Skil store/smÃ¥ bokstavar +pdfjs-find-match-diacritics-checkbox-label = Samsvar diakritiske teikn +pdfjs-find-entire-word-checkbox-label = Heile ord +pdfjs-find-reached-top = NÃ¥dde toppen av dokumentet, fortset frÃ¥ botnen +pdfjs-find-reached-bottom = NÃ¥dde botnen av dokumentet, fortset frÃ¥ toppen +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } av { $total } treff + *[other] { $current } av { $total } treff + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Meir enn { $limit } treff + *[other] Meir enn { $limit } treff + } +pdfjs-find-not-found = Fann ikkje teksten + +## Predefined zoom values + +pdfjs-page-scale-width = Sidebreidde +pdfjs-page-scale-fit = Tilpass til sida +pdfjs-page-scale-auto = Automatisk skalering +pdfjs-page-scale-actual = Verkeleg storleik +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Side { $page } + +## Loading indicator messages + +pdfjs-loading-error = Ein feil oppstod ved lasting av PDF. +pdfjs-invalid-file-error = Ugyldig eller korrupt PDF-fil. +pdfjs-missing-file-error = Manglande PDF-fil. +pdfjs-unexpected-response-error = Uventa tenarrespons. +pdfjs-rendering-error = Ein feil oppstod under vising av sida. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date } { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } annotasjon] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Skriv inn passordet for Ã¥ opne denne PDF-fila. +pdfjs-password-invalid = Ugyldig passord. Prøv pÃ¥ nytt. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Avbryt +pdfjs-web-fonts-disabled = Web-skrifter er slÃ¥tt av: Kan ikkje bruke innbundne PDF-skrifter. + +## Editing + +pdfjs-editor-free-text-button = + .title = Tekst +pdfjs-editor-free-text-button-label = Tekst +pdfjs-editor-ink-button = + .title = Teikne +pdfjs-editor-ink-button-label = Teikne +pdfjs-editor-stamp-button = + .title = Legg til eller rediger bilde +pdfjs-editor-stamp-button-label = Legg til eller rediger bilde +pdfjs-editor-highlight-button = + .title = Markere +pdfjs-editor-highlight-button-label = Markere +pdfjs-highlight-floating-button1 = + .title = Markere + .aria-label = Markere +pdfjs-highlight-floating-button-label = Markere + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Fjern teikninga +pdfjs-editor-remove-freetext-button = + .title = Fjern tekst +pdfjs-editor-remove-stamp-button = + .title = Fjern bildet +pdfjs-editor-remove-highlight-button = + .title = Fjern utheving + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Farge +pdfjs-editor-free-text-size-input = Storleik +pdfjs-editor-ink-color-input = Farge +pdfjs-editor-ink-thickness-input = Tjukn +pdfjs-editor-ink-opacity-input = Ugjennomskinleg +pdfjs-editor-stamp-add-image-button = + .title = Legg til bilde +pdfjs-editor-stamp-add-image-button-label = Legg til bilde +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Tjukn +pdfjs-editor-free-highlight-thickness-title = + .title = Endre tjukn nÃ¥r du markerer andre element enn tekst +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Tekstredigering + .default-content = Begynn Ã¥ skrive… +pdfjs-free-text = + .aria-label = Tekstredigering +pdfjs-free-text-default-content = Byrje Ã¥ skrive… +pdfjs-ink = + .aria-label = Teikneredigering +pdfjs-ink-canvas = + .aria-label = Brukarskapt bilde + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alt-tekst +pdfjs-editor-alt-text-edit-button = + .aria-label = Rediger alt-tekst tekst +pdfjs-editor-alt-text-edit-button-label = Rediger alt-tekst tekst +pdfjs-editor-alt-text-dialog-label = Vel eit alternativ +pdfjs-editor-alt-text-dialog-description = Alt-tekst (alternativ tekst) hjelper nÃ¥r folk ikkje kan sjÃ¥ bildet eller nÃ¥r det ikkje vert lasta inn. +pdfjs-editor-alt-text-add-description-label = Legg til ei skildring +pdfjs-editor-alt-text-add-description-description = GÃ¥ etter 1-2 setninger som skildrar emnet, settinga eller handlingane. +pdfjs-editor-alt-text-mark-decorative-label = Merk som dekorativt +pdfjs-editor-alt-text-mark-decorative-description = Dette vert brukt til dekorative bilde, som kantlinjer eller vassmerke. +pdfjs-editor-alt-text-cancel-button = Avbryt +pdfjs-editor-alt-text-save-button = Lagre +pdfjs-editor-alt-text-decorative-tooltip = Merkt som dekorativ +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Til dømes, «Ein ung mann set seg ved eit bord for Ã¥ ete eit mÃ¥ltid» +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alt-tekst + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Øvste venstre hjørne – endre størrelse +pdfjs-editor-resizer-label-top-middle = Øvst i midten — endre størrelse +pdfjs-editor-resizer-label-top-right = Øvste høgre hjørne – endre størrelse +pdfjs-editor-resizer-label-middle-right = Midt til høgre – endre størrelse +pdfjs-editor-resizer-label-bottom-right = Nedste høgre hjørne – endre størrelse +pdfjs-editor-resizer-label-bottom-middle = Nedst i midten — endre størrelse +pdfjs-editor-resizer-label-bottom-left = Nedste venstre hjørne – endre størrelse +pdfjs-editor-resizer-label-middle-left = Midt til venstre — endre størrelse +pdfjs-editor-resizer-top-left = + .aria-label = Øvste venstre hjørne – endre størrelse +pdfjs-editor-resizer-top-middle = + .aria-label = Øvst i midten — endre størrelse +pdfjs-editor-resizer-top-right = + .aria-label = Øvste høgre hjørne – endre størrelse +pdfjs-editor-resizer-middle-right = + .aria-label = Midt til høgre – endre størrelse +pdfjs-editor-resizer-bottom-right = + .aria-label = Nedste høgre hjørne – endre størrelse +pdfjs-editor-resizer-bottom-middle = + .aria-label = Nedst i midten — endre størrelse +pdfjs-editor-resizer-bottom-left = + .aria-label = Nedste venstre hjørne – endre størrelse +pdfjs-editor-resizer-middle-left = + .aria-label = Midt til venstre — endre størrelse + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Uthevingsfarge +pdfjs-editor-colorpicker-button = + .title = Endre farge +pdfjs-editor-colorpicker-dropdown = + .aria-label = Fargeval +pdfjs-editor-colorpicker-yellow = + .title = Gul +pdfjs-editor-colorpicker-green = + .title = Grøn +pdfjs-editor-colorpicker-blue = + .title = BlÃ¥ +pdfjs-editor-colorpicker-pink = + .title = Rosa +pdfjs-editor-colorpicker-red = + .title = Raud + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Vis alle +pdfjs-editor-highlight-show-all-button = + .title = Vis alle + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Rediger alternativ tekst (bildeskildring) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Legg til alternativ tekst (bildeskildring) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Skriv skildringa di her… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Kort skildring for personar som ikkje kan sjÃ¥ bildet, eller nÃ¥r bildet ikkje lastar inn. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Denne alternative teksten vart oppretta automatisk, og kan vere unøyaktig. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Les meir +pdfjs-editor-new-alt-text-create-automatically-button-label = Opprett alternativ tekt automatisk +pdfjs-editor-new-alt-text-not-now-button = Ikkje no +pdfjs-editor-new-alt-text-error-title = Klarte ikkje Ã¥ opprette alternativ tekst automatisk +pdfjs-editor-new-alt-text-error-description = Skriv din eigen alternative tekst eller prøv igjen seinare. +pdfjs-editor-new-alt-text-error-close-button = Lat att +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Lastar ned AI-modell med alternativ tekst ({ $downloadedSize } av { $totalSize } MB) + .aria-valuetext = Lastar ned AI-modell med alternativ tekst ({ $downloadedSize } av { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alternativ tekst lagt til +pdfjs-editor-new-alt-text-added-button-label = Alternativ tekst lagt til +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Manglande alternativ tekst +pdfjs-editor-new-alt-text-missing-button-label = Manglande alternativ tekst +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Vurder alternativ tekst +pdfjs-editor-new-alt-text-to-review-button-label = Vurder alternativ tekst +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Oppretta automatisk: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Alternative tekst-innstillingar for bilde +pdfjs-image-alt-text-settings-button-label = Alternative tekst-innstillingar for bilde +pdfjs-editor-alt-text-settings-dialog-label = Alternative tekst-innstillingar for bilde +pdfjs-editor-alt-text-settings-automatic-title = Automatisk alternativ tekst +pdfjs-editor-alt-text-settings-create-model-button-label = Opprett alternativ tekt automatisk +pdfjs-editor-alt-text-settings-create-model-description = ForeslÃ¥r skildringar for Ã¥ hjelpe folk som ikkje kan sjÃ¥ bildet eller nÃ¥r bildet ikkje blir lasta inn. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = AI-modell for alternativ tekst ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Køyrer lokalt pÃ¥ eininga di slik at dataa dine blir verande private. PÃ¥kravd for automatisk alternativ tekst. +pdfjs-editor-alt-text-settings-delete-model-button = Slett +pdfjs-editor-alt-text-settings-download-model-button = Last ned +pdfjs-editor-alt-text-settings-downloading-model-button = Lastar ned… +pdfjs-editor-alt-text-settings-editor-title = Alternativ tekst-redigerar +pdfjs-editor-alt-text-settings-show-dialog-button-label = Vis alternativ tekst-redigerar direkte nÃ¥r du legg til eit bilde +pdfjs-editor-alt-text-settings-show-dialog-description = Hjelper deg med Ã¥ sørgje for at alle bilda dine har alternativ tekst. +pdfjs-editor-alt-text-settings-close-button = Lat att + +## "Annotations removed" bar + diff --git a/public/assets/pdfjs/locale/oc/viewer.ftl b/public/assets/pdfjs/locale/oc/viewer.ftl new file mode 100755 index 0000000..b347aef --- /dev/null +++ b/public/assets/pdfjs/locale/oc/viewer.ftl @@ -0,0 +1,409 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Pagina precedenta +pdfjs-previous-button-label = Precedent +pdfjs-next-button = + .title = Pagina seguenta +pdfjs-next-button-label = Seguent +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Pagina +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = sus { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount }) +pdfjs-zoom-out-button = + .title = Zoom arrièr +pdfjs-zoom-out-button-label = Zoom arrièr +pdfjs-zoom-in-button = + .title = Zoom avant +pdfjs-zoom-in-button-label = Zoom avant +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Bascular en mòde presentacion +pdfjs-presentation-mode-button-label = Mòde Presentacion +pdfjs-open-file-button = + .title = Dobrir lo fichièr +pdfjs-open-file-button-label = Dobrir +pdfjs-print-button = + .title = Imprimir +pdfjs-print-button-label = Imprimir +pdfjs-save-button = + .title = Enregistrar +pdfjs-save-button-label = Enregistrar +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Telecargar +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Telecargar +pdfjs-bookmark-button = + .title = Pagina actuala (mostrar l’adreça de la pagina actuala) +pdfjs-bookmark-button-label = Pagina actuala + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Aisinas +pdfjs-tools-button-label = Aisinas +pdfjs-first-page-button = + .title = Anar a la primièra pagina +pdfjs-first-page-button-label = Anar a la primièra pagina +pdfjs-last-page-button = + .title = Anar a la darrièra pagina +pdfjs-last-page-button-label = Anar a la darrièra pagina +pdfjs-page-rotate-cw-button = + .title = Rotacion orària +pdfjs-page-rotate-cw-button-label = Rotacion orària +pdfjs-page-rotate-ccw-button = + .title = Rotacion antiorària +pdfjs-page-rotate-ccw-button-label = Rotacion antiorària +pdfjs-cursor-text-select-tool-button = + .title = Activar l'aisina de seleccion de tèxte +pdfjs-cursor-text-select-tool-button-label = Aisina de seleccion de tèxte +pdfjs-cursor-hand-tool-button = + .title = Activar l’aisina man +pdfjs-cursor-hand-tool-button-label = Aisina man +pdfjs-scroll-page-button = + .title = Activar lo defilament per pagina +pdfjs-scroll-page-button-label = Defilament per pagina +pdfjs-scroll-vertical-button = + .title = Utilizar lo defilament vertical +pdfjs-scroll-vertical-button-label = Defilament vertical +pdfjs-scroll-horizontal-button = + .title = Utilizar lo defilament orizontal +pdfjs-scroll-horizontal-button-label = Defilament orizontal +pdfjs-scroll-wrapped-button = + .title = Activar lo defilament continú +pdfjs-scroll-wrapped-button-label = Defilament continú +pdfjs-spread-none-button = + .title = Agropar pas las paginas doas a doas +pdfjs-spread-none-button-label = Una sola pagina +pdfjs-spread-odd-button = + .title = Mostrar doas paginas en començant per las paginas imparas a esquèrra +pdfjs-spread-odd-button-label = Dobla pagina, impara a drecha +pdfjs-spread-even-button = + .title = Mostrar doas paginas en començant per las paginas paras a esquèrra +pdfjs-spread-even-button-label = Dobla pagina, para a drecha + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Proprietats del document… +pdfjs-document-properties-button-label = Proprietats del document… +pdfjs-document-properties-file-name = Nom del fichièr : +pdfjs-document-properties-file-size = Talha del fichièr : +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } Ko ({ $size_b } octets) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } Mo ({ $size_b } octets) +pdfjs-document-properties-title = Títol : +pdfjs-document-properties-author = Autor : +pdfjs-document-properties-subject = Subjècte : +pdfjs-document-properties-keywords = Mots claus : +pdfjs-document-properties-creation-date = Data de creacion : +pdfjs-document-properties-modification-date = Data de modificacion : +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, a { $time } +pdfjs-document-properties-creator = Creator : +pdfjs-document-properties-producer = Aisina de conversion PDF : +pdfjs-document-properties-version = Version PDF : +pdfjs-document-properties-page-count = Nombre de paginas : +pdfjs-document-properties-page-size = Talha de la pagina : +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = retrach +pdfjs-document-properties-page-size-orientation-landscape = païsatge +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letra +pdfjs-document-properties-page-size-name-legal = Document juridic + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Vista web rapida : +pdfjs-document-properties-linearized-yes = Ã’c +pdfjs-document-properties-linearized-no = Non +pdfjs-document-properties-close-button = Tampar + +## Print + +pdfjs-print-progress-message = Preparacion del document per l’impression… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Anullar +pdfjs-printing-not-supported = Atencion : l'impression es pas complètament gerida per aqueste navegador. +pdfjs-printing-not-ready = Atencion : lo PDF es pas entièrament cargat per lo poder imprimir. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Afichar/amagar lo panèl lateral +pdfjs-toggle-sidebar-notification-button = + .title = Afichar/amagar lo panèl lateral (lo document conten esquèmas/pèças juntas/calques) +pdfjs-toggle-sidebar-button-label = Afichar/amagar lo panèl lateral +pdfjs-document-outline-button = + .title = Mostrar los esquèmas del document (dobleclicar per espandre/reduire totes los elements) +pdfjs-document-outline-button-label = Marcapaginas del document +pdfjs-attachments-button = + .title = Visualizar las pèças juntas +pdfjs-attachments-button-label = Pèças juntas +pdfjs-layers-button = + .title = Afichar los calques (doble-clicar per reïnicializar totes los calques a l’estat per defaut) +pdfjs-layers-button-label = Calques +pdfjs-thumbs-button = + .title = Afichar las vinhetas +pdfjs-thumbs-button-label = Vinhetas +pdfjs-current-outline-item-button = + .title = Trobar l’element de plan actual +pdfjs-current-outline-item-button-label = Element de plan actual +pdfjs-findbar-button = + .title = Cercar dins lo document +pdfjs-findbar-button-label = Recercar +pdfjs-additional-layers = Calques suplementaris + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Pagina { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Vinheta de la pagina { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Recercar + .placeholder = Cercar dins lo document… +pdfjs-find-previous-button = + .title = Tròba l'ocurréncia precedenta de la frasa +pdfjs-find-previous-button-label = Precedent +pdfjs-find-next-button = + .title = Tròba l'ocurréncia venenta de la frasa +pdfjs-find-next-button-label = Seguent +pdfjs-find-highlight-checkbox = Suslinhar tot +pdfjs-find-match-case-checkbox-label = Respectar la cassa +pdfjs-find-match-diacritics-checkbox-label = Respectar los diacritics +pdfjs-find-entire-word-checkbox-label = Mots entièrs +pdfjs-find-reached-top = Naut de la pagina atenh, perseguida del bas +pdfjs-find-reached-bottom = Bas de la pagina atench, perseguida al començament +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] Ocurréncia { $current } de { $total } + *[other] Ocurréncia { $current } de { $total } + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Mai de { $limit } ocurréncia + *[other] Mai de { $limit } ocurréncias + } +pdfjs-find-not-found = Frasa pas trobada + +## Predefined zoom values + +pdfjs-page-scale-width = Largor plena +pdfjs-page-scale-fit = Pagina entièra +pdfjs-page-scale-auto = Zoom automatic +pdfjs-page-scale-actual = Talha vertadièra +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Pagina { $page } + +## Loading indicator messages + +pdfjs-loading-error = Una error s'es producha pendent lo cargament del fichièr PDF. +pdfjs-invalid-file-error = Fichièr PDF invalid o corromput. +pdfjs-missing-file-error = Fichièr PDF mancant. +pdfjs-unexpected-response-error = Responsa de servidor imprevista. +pdfjs-rendering-error = Una error s'es producha pendent l'afichatge de la pagina. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date } a { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Anotacion { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Picatz lo senhal per dobrir aqueste fichièr PDF. +pdfjs-password-invalid = Senhal incorrècte. Tornatz ensajar. +pdfjs-password-ok-button = D'acòrdi +pdfjs-password-cancel-button = Anullar +pdfjs-web-fonts-disabled = Las polissas web son desactivadas : impossible d'utilizar las polissas integradas al PDF. + +## Editing + +pdfjs-editor-free-text-button = + .title = Tèxte +pdfjs-editor-free-text-button-label = Tèxte +pdfjs-editor-ink-button = + .title = Dessenhar +pdfjs-editor-ink-button-label = Dessenhar +pdfjs-editor-stamp-button = + .title = Apondre o modificar d’imatges +pdfjs-editor-stamp-button-label = Apondre o modificar d’imatges +pdfjs-editor-highlight-button = + .title = Subrelinhar +pdfjs-editor-highlight-button-label = Subrelinhar +pdfjs-highlight-floating-button1 = + .title = Subrelinhar + .aria-label = Subrelinhar +pdfjs-highlight-floating-button-label = Subrelinhar + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Levar lo dessenh +pdfjs-editor-remove-freetext-button = + .title = Suprimir lo tèxte +pdfjs-editor-remove-stamp-button = + .title = Suprimir l’imatge +pdfjs-editor-remove-highlight-button = + .title = Levar lo suslinhatge + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Color +pdfjs-editor-free-text-size-input = Talha +pdfjs-editor-ink-color-input = Color +pdfjs-editor-ink-thickness-input = Espessor +pdfjs-editor-ink-opacity-input = Opacitat +pdfjs-editor-stamp-add-image-button = + .title = Apondre imatge +pdfjs-editor-stamp-add-image-button-label = Apondre imatge +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Espessor +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Editor de tèxte + .default-content = Començatz de picar… +pdfjs-free-text = + .aria-label = Editor de tèxte +pdfjs-free-text-default-content = Començatz d’escriure… +pdfjs-ink = + .aria-label = Editor de dessenh +pdfjs-ink-canvas = + .aria-label = Imatge creat per l’utilizaire + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Tèxt alternatiu +pdfjs-editor-alt-text-edit-button-label = Modificar lo tèxt alternatiu +pdfjs-editor-alt-text-dialog-label = Causir una opcion +pdfjs-editor-alt-text-add-description-label = Apondre una descripcion +pdfjs-editor-alt-text-cancel-button = Anullar +pdfjs-editor-alt-text-save-button = Enregistrar + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Color de suslinhatge +pdfjs-editor-colorpicker-button = + .title = Cambiar de color +pdfjs-editor-colorpicker-dropdown = + .aria-label = Causida de colors +pdfjs-editor-colorpicker-yellow = + .title = Jaune +pdfjs-editor-colorpicker-green = + .title = Verd +pdfjs-editor-colorpicker-blue = + .title = Blau +pdfjs-editor-colorpicker-pink = + .title = Ròse +pdfjs-editor-colorpicker-red = + .title = Roge + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = O afichar tot +pdfjs-editor-highlight-show-all-button = + .title = O afichar tot + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +pdfjs-editor-new-alt-text-error-close-button = Tampar + +## Image alt-text settings + +pdfjs-editor-alt-text-settings-automatic-title = Tèxte alternatiu automatic +pdfjs-editor-alt-text-settings-create-model-button-label = Crear un tèxte alternatiu automaticament +pdfjs-editor-alt-text-settings-delete-model-button = Suprimir +pdfjs-editor-alt-text-settings-download-model-button = Telecargar +pdfjs-editor-alt-text-settings-downloading-model-button = Telecargament… +pdfjs-editor-alt-text-settings-editor-title = Editor de tèxte alternatiu +pdfjs-editor-alt-text-settings-close-button = Tampar + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-freetext = Tèxte suprimit +pdfjs-editor-undo-bar-message-ink = Dessenh suprimit +pdfjs-editor-undo-bar-message-stamp = Imatge suprimit +pdfjs-editor-undo-bar-undo-button = + .title = Anullar +pdfjs-editor-undo-bar-undo-button-label = Anullar +pdfjs-editor-undo-bar-close-button = + .title = Tampar +pdfjs-editor-undo-bar-close-button-label = Tampar diff --git a/public/assets/pdfjs/locale/pa-IN/viewer.ftl b/public/assets/pdfjs/locale/pa-IN/viewer.ftl new file mode 100755 index 0000000..10a6112 --- /dev/null +++ b/public/assets/pdfjs/locale/pa-IN/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = ਪਿਛਲਾ ਸਫ਼ਾ +pdfjs-previous-button-label = ਪਿੱਛੇ +pdfjs-next-button = + .title = ਅਗਲਾ ਸਫ਼ਾ +pdfjs-next-button-label = ਅੱਗੇ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = ਸਫ਼ਾ +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = { $pagesCount } ਵਿੱਚੋਂ +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = { $pagesCount }) ਵਿੱਚੋਂ ({ $pageNumber } +pdfjs-zoom-out-button = + .title = ਜ਼ੂਮ ਆਉਟ +pdfjs-zoom-out-button-label = ਜ਼ੂਮ ਆਉਟ +pdfjs-zoom-in-button = + .title = ਜ਼ੂਮ ਇਨ +pdfjs-zoom-in-button-label = ਜ਼ੂਮ ਇਨ +pdfjs-zoom-select = + .title = ਜ਼ੂਨ +pdfjs-presentation-mode-button = + .title = ਪਰਿਜੈਂਟੇਸ਼ਨ ਮੋਡ ਵਿੱਚ ਜਾਓ +pdfjs-presentation-mode-button-label = ਪਰਿਜੈਂਟੇਸ਼ਨ ਮੋਡ +pdfjs-open-file-button = + .title = ਫਾਈਲ ਨੂੰ ਖੋਲà©à¨¹à©‹ +pdfjs-open-file-button-label = ਖੋਲà©à¨¹à©‹ +pdfjs-print-button = + .title = ਪਰਿੰਟ +pdfjs-print-button-label = ਪਰਿੰਟ +pdfjs-save-button = + .title = ਸੰਭਾਲੋ +pdfjs-save-button-label = ਸੰਭਾਲੋ +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = ਡਾਊਨਲੋਡ +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = ਡਾਊਨਲੋਡ +pdfjs-bookmark-button = + .title = ਮੌਜੂਦਾ ਸਫ਼਼ਾ (ਮੌਜੂਦਾ ਸਫ਼ੇ ਤੋਂ URL ਵੇਖੋ) +pdfjs-bookmark-button-label = ਮੌਜੂਦਾ ਸਫ਼਼ਾ + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = ਟੂਲ +pdfjs-tools-button-label = ਟੂਲ +pdfjs-first-page-button = + .title = ਪਹਿਲੇ ਸਫ਼ੇ ਉੱਤੇ ਜਾਓ +pdfjs-first-page-button-label = ਪਹਿਲੇ ਸਫ਼ੇ ਉੱਤੇ ਜਾਓ +pdfjs-last-page-button = + .title = ਆਖਰੀ ਸਫ਼ੇ ਉੱਤੇ ਜਾਓ +pdfjs-last-page-button-label = ਆਖਰੀ ਸਫ਼ੇ ਉੱਤੇ ਜਾਓ +pdfjs-page-rotate-cw-button = + .title = ਸੱਜੇ ਦਾਅ ਘà©à©°à¨®à¨¾à¨“ +pdfjs-page-rotate-cw-button-label = ਸੱਜੇ ਦਾਅ ਘà©à©°à¨®à¨¾à¨“ +pdfjs-page-rotate-ccw-button = + .title = ਖੱਬੇ ਦਾਅ ਘà©à©°à¨®à¨¾à¨“ +pdfjs-page-rotate-ccw-button-label = ਖੱਬੇ ਦਾਅ ਘà©à©°à¨®à¨¾à¨“ +pdfjs-cursor-text-select-tool-button = + .title = ਲਿਖਤ ਚੋਣ ਟੂਲ ਸਮਰੱਥ ਕਰੋ +pdfjs-cursor-text-select-tool-button-label = ਲਿਖਤ ਚੋਣ ਟੂਲ +pdfjs-cursor-hand-tool-button = + .title = ਹੱਥ ਟੂਲ ਸਮਰੱਥ ਕਰੋ +pdfjs-cursor-hand-tool-button-label = ਹੱਥ ਟੂਲ +pdfjs-scroll-page-button = + .title = ਸਫ਼ਾ ਖਿਸਕਾਉਣ ਨੂੰ ਵਰਤੋਂ +pdfjs-scroll-page-button-label = ਸਫ਼ਾ ਖਿਸਕਾਉਣਾ +pdfjs-scroll-vertical-button = + .title = ਖੜà©à¨¹à¨µà©‡à¨‚ ਸਕਰਾਉਣ ਨੂੰ ਵਰਤੋਂ +pdfjs-scroll-vertical-button-label = ਖੜà©à¨¹à¨µà¨¾à¨‚ ਸਰਕਾਉਣਾ +pdfjs-scroll-horizontal-button = + .title = ਲੇਟਵੇਂ ਸਰਕਾਉਣ ਨੂੰ ਵਰਤੋਂ +pdfjs-scroll-horizontal-button-label = ਲੇਟਵਾਂ ਸਰਕਾਉਣਾ +pdfjs-scroll-wrapped-button = + .title = ਸਮੇਟੇ ਸਰਕਾਉਣ ਨੂੰ ਵਰਤੋਂ +pdfjs-scroll-wrapped-button-label = ਸਮੇਟਿਆ ਸਰਕਾਉਣਾ +pdfjs-spread-none-button = + .title = ਸਫ਼ਾ ਫੈਲਾਅ ਵਿੱਚ ਸ਼ਾਮਲ ਨਾ ਹੋਵੋ +pdfjs-spread-none-button-label = ਕੋਈ ਫੈਲਾਅ ਨਹੀਂ +pdfjs-spread-odd-button = + .title = ਟਾਂਕ ਅੰਕ ਵਾਲੇ ਸਫ਼ਿਆਂ ਨਾਲ ਸ਼à©à¨°à©‚ ਹੋਣ ਵਾਲੇ ਸਫਿਆਂ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਵੋ +pdfjs-spread-odd-button-label = ਟਾਂਕ ਫੈਲਾਅ +pdfjs-spread-even-button = + .title = ਜਿਸਤ ਅੰਕ ਵਾਲੇ ਸਫ਼ਿਆਂ ਨਾਲ ਸ਼à©à¨°à©‚ ਹੋਣ ਵਾਲੇ ਸਫਿਆਂ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਵੋ +pdfjs-spread-even-button-label = ਜਿਸਤ ਫੈਲਾਅ + +## Document properties dialog + +pdfjs-document-properties-button = + .title = …ਦਸਤਾਵੇਜ਼ ਦੀ ਵਿਸ਼ੇਸ਼ਤਾ +pdfjs-document-properties-button-label = …ਦਸਤਾਵੇਜ਼ ਦੀ ਵਿਸ਼ੇਸ਼ਤਾ +pdfjs-document-properties-file-name = ਫਾਈਲ ਦਾ ਨਾਂ: +pdfjs-document-properties-file-size = ਫਾਈਲ ਦਾ ਆਕਾਰ: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } ਬਾਈਟ) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } ਬਾਈਟ) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } ਬਾਈਟ) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } ਬਾਈਟ) +pdfjs-document-properties-title = ਟਾਈਟਲ: +pdfjs-document-properties-author = ਲੇਖਕ: +pdfjs-document-properties-subject = ਵਿਸ਼ਾ: +pdfjs-document-properties-keywords = ਸ਼ਬਦ: +pdfjs-document-properties-creation-date = ਬਣਾਉਣ ਦੀ ਮਿਤੀ: +pdfjs-document-properties-modification-date = ਸੋਧ ਦੀ ਮਿਤੀ: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = ਨਿਰਮਾਤਾ: +pdfjs-document-properties-producer = PDF ਪà©à¨°à©‹à¨¡à¨¿à¨Šà¨¸à¨°: +pdfjs-document-properties-version = PDF ਵਰਜਨ: +pdfjs-document-properties-page-count = ਸਫ਼ੇ ਦੀ ਗਿਣਤੀ: +pdfjs-document-properties-page-size = ਸਫ਼ਾ ਆਕਾਰ: +pdfjs-document-properties-page-size-unit-inches = ਇੰਚ +pdfjs-document-properties-page-size-unit-millimeters = ਮਿਮੀ +pdfjs-document-properties-page-size-orientation-portrait = ਪੋਰਟਰੇਟ +pdfjs-document-properties-page-size-orientation-landscape = ਲੈਂਡਸਕੇਪ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = ਲੈਟਰ +pdfjs-document-properties-page-size-name-legal = ਕਨੂੰਨੀ + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = ਤੇਜ਼ ਵੈੱਬ à¨à¨²à¨•: +pdfjs-document-properties-linearized-yes = ਹਾਂ +pdfjs-document-properties-linearized-no = ਨਹੀਂ +pdfjs-document-properties-close-button = ਬੰਦ ਕਰੋ + +## Print + +pdfjs-print-progress-message = …ਪਰਿੰਟ ਕਰਨ ਲਈ ਦਸਤਾਵੇਜ਼ ਨੂੰ ਤਿਆਰ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = ਰੱਦ ਕਰੋ +pdfjs-printing-not-supported = ਸਾਵਧਾਨ: ਇਹ ਬਰਾਊਜ਼ਰ ਪਰਿੰਟ ਕਰਨ ਲਈ ਪੂਰੀ ਤਰà©à¨¹à¨¾à¨‚ ਸਹਾਇਕ ਨਹੀਂ ਹੈ। +pdfjs-printing-not-ready = ਸਾਵਧਾਨ: PDF ਨੂੰ ਪਰਿੰਟ ਕਰਨ ਲਈ ਪੂਰੀ ਤਰà©à¨¹à¨¾à¨‚ ਲੋਡ ਨਹੀਂ ਹੈ। + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = ਬਾਹੀ ਬਦਲੋ +pdfjs-toggle-sidebar-notification-button = + .title = ਬਾਹੀ ਨੂੰ ਬਦਲੋ (ਦਸਤਾਵੇਜ਼ ਖਾਕਾ/ਅਟੈਚਮੈਂਟ/ਪਰਤਾਂ ਰੱਖਦਾ ਹੈ) +pdfjs-toggle-sidebar-button-label = ਬਾਹੀ ਬਦਲੋ +pdfjs-document-outline-button = + .title = ਦਸਤਾਵੇਜ਼ ਖਾਕਾ ਦਿਖਾਓ (ਸਾਰੀਆਂ ਆਈਟਮਾਂ ਨੂੰ ਫੈਲਾਉਣ/ਸਮੇਟਣ ਲਈ ਦੋ ਵਾਰ ਕਲਿੱਕ ਕਰੋ) +pdfjs-document-outline-button-label = ਦਸਤਾਵੇਜ਼ ਖਾਕਾ +pdfjs-attachments-button = + .title = ਅਟੈਚਮੈਂਟ ਵੇਖਾਓ +pdfjs-attachments-button-label = ਅਟੈਚਮੈਂਟਾਂ +pdfjs-layers-button = + .title = ਪਰਤਾਂ ਵੇਖਾਓ (ਸਾਰੀਆਂ ਪਰਤਾਂ ਨੂੰ ਮੂਲ ਹਾਲਤ ਉੱਤੇ ਮà©à©œ-ਸੈੱਟ ਕਰਨ ਲਈ ਦੋ ਵਾਰ ਕਲਿੱਕ ਕਰੋ) +pdfjs-layers-button-label = ਪਰਤਾਂ +pdfjs-thumbs-button = + .title = ਥੰਮਨੇਲ ਨੂੰ ਵੇਖਾਓ +pdfjs-thumbs-button-label = ਥੰਮਨੇਲ +pdfjs-current-outline-item-button = + .title = ਮੌੌਜੂਦਾ ਖਾਕਾ ਚੀਜ਼ ਲੱਭੋ +pdfjs-current-outline-item-button-label = ਮੌਜੂਦਾ ਖਾਕਾ ਚੀਜ਼ +pdfjs-findbar-button = + .title = ਦਸਤਾਵੇਜ਼ ਵਿੱਚ ਲੱਭੋ +pdfjs-findbar-button-label = ਲੱਭੋ +pdfjs-additional-layers = ਵਾਧੂ ਪਰਤਾਂ + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = ਸਫ਼ਾ { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = { $page } ਸਫ਼ੇ ਦਾ ਥੰਮਨੇਲ + +## Find panel button title and messages + +pdfjs-find-input = + .title = ਲੱਭੋ + .placeholder = …ਦਸਤਾਵੇਜ਼ 'ਚ ਲੱਭੋ +pdfjs-find-previous-button = + .title = ਵਾਕ ਦੀ ਪਿਛਲੀ ਮੌਜੂਦਗੀ ਲੱਭੋ +pdfjs-find-previous-button-label = ਪਿੱਛੇ +pdfjs-find-next-button = + .title = ਵਾਕ ਦੀ ਅਗਲੀ ਮੌਜੂਦਗੀ ਲੱਭੋ +pdfjs-find-next-button-label = ਅੱਗੇ +pdfjs-find-highlight-checkbox = ਸਭ ਉਭਾਰੋ +pdfjs-find-match-case-checkbox-label = ਅੱਖਰ ਆਕਾਰ ਨੂੰ ਮਿਲਾਉ +pdfjs-find-match-diacritics-checkbox-label = ਭੇਦਸੂਚਕ ਮੇਲ +pdfjs-find-entire-word-checkbox-label = ਪੂਰੇ ਸ਼ਬਦ +pdfjs-find-reached-top = ਦਸਤਾਵੇਜ਼ ਦੇ ਉੱਤੇ ਆ ਗਠਹਾਂ, ਥੱਲੇ ਤੋਂ ਜਾਰੀ ਰੱਖਿਆ ਹੈ +pdfjs-find-reached-bottom = ਦਸਤਾਵੇਜ਼ ਦੇ ਅੰਤ ਉੱਤੇ ਆ ਗਠਹਾਂ, ਉੱਤੇ ਤੋਂ ਜਾਰੀ ਰੱਖਿਆ ਹੈ +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $total } ਵਿੱਚੋਂ { $current } ਮੇਲ + *[other] { $total } ਵਿੱਚੋਂ { $current } ਮੇਲ + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] { $limit } ਤੋਂ ਵੱਧ ਮੇਲ + *[other] { $limit } ਤੋਂ ਵੱਧ ਮੇਲ + } +pdfjs-find-not-found = ਵਾਕ ਨਹੀਂ ਲੱਭਿਆ + +## Predefined zoom values + +pdfjs-page-scale-width = ਸਫ਼ੇ ਦੀ ਚੌੜਾਈ +pdfjs-page-scale-fit = ਸਫ਼ਾ ਫਿੱਟ +pdfjs-page-scale-auto = ਆਟੋਮੈਟਿਕ ਜ਼ੂਮ ਕਰੋ +pdfjs-page-scale-actual = ਆਟੋਮੈਟਿਕ ਆਕਾਰ +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = ਸਫ਼ਾ { $page } + +## Loading indicator messages + +pdfjs-loading-error = PDF ਲੋਡ ਕਰਨ ਦੇ ਦੌਰਾਨ ਗਲਤੀ ਆਈ ਹੈ। +pdfjs-invalid-file-error = ਗਲਤ ਜਾਂ ਨਿਕਾਰਾ PDF ਫਾਈਲ ਹੈ। +pdfjs-missing-file-error = ਨਾ-ਮੌਜੂਦ PDF ਫਾਈਲ। +pdfjs-unexpected-response-error = ਅਣਜਾਣ ਸਰਵਰ ਜਵਾਬ। +pdfjs-rendering-error = ਸਫ਼ਾ ਰੈਡਰ ਕਰਨ ਦੇ ਦੌਰਾਨ ਗਲਤੀ ਆਈ ਹੈ। + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } ਵਿਆਖਿਆ] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = ਇਹ PDF ਫਾਈਲ ਨੂੰ ਖੋਲà©à¨¹à¨£ ਲਈ ਪਾਸਵਰਡ ਦਿਉ। +pdfjs-password-invalid = ਗਲਤ ਪਾਸਵਰਡ। ਫੇਰ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਜੀ। +pdfjs-password-ok-button = ਠੀਕ ਹੈ +pdfjs-password-cancel-button = ਰੱਦ ਕਰੋ +pdfjs-web-fonts-disabled = ਵੈਬ ਫੋਂਟ ਬੰਦ ਹਨ: ਇੰਬੈਡ PDF ਫੋਂਟ ਨੂੰ ਵਰਤਣ ਲਈ ਅਸਮਰੱਥ ਹੈ। + +## Editing + +pdfjs-editor-free-text-button = + .title = ਲਿਖਤ +pdfjs-editor-free-text-button-label = ਲਿਖਤ +pdfjs-editor-ink-button = + .title = ਵਾਹੋ +pdfjs-editor-ink-button-label = ਵਾਹੋ +pdfjs-editor-stamp-button = + .title = ਚਿੱਤਰ ਜੋੜੋ ਜਾਂ ਸੋਧੋ +pdfjs-editor-stamp-button-label = ਚਿੱਤਰ ਜੋੜੋ ਜਾਂ ਸੋਧੋ +pdfjs-editor-highlight-button = + .title = ਹਾਈਲਾਈਟ +pdfjs-editor-highlight-button-label = ਹਾਈਲਾਈਟ +pdfjs-highlight-floating-button1 = + .title = ਹਾਈਲਾਈਟ + .aria-label = ਹਾਈਲਾਈਟ +pdfjs-highlight-floating-button-label = ਹਾਈਲਾਈਟ + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = ਡਰਾਇੰਗ ਨੂੰ ਹਟਾਓ +pdfjs-editor-remove-freetext-button = + .title = ਲਿਖਤ ਨੂੰ ਹਟਾਓ +pdfjs-editor-remove-stamp-button = + .title = ਚਿੱਤਰ ਨੂੰ ਹਟਾਓ +pdfjs-editor-remove-highlight-button = + .title = ਹਾਈਲਾਈਟ ਨੂੰ ਹਟਾਓ + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = ਰੰਗ +pdfjs-editor-free-text-size-input = ਆਕਾਰ +pdfjs-editor-ink-color-input = ਰੰਗ +pdfjs-editor-ink-thickness-input = ਮੋਟਾਈ +pdfjs-editor-ink-opacity-input = ਧà©à©°à¨¦à¨²à¨¾à¨ªà¨¨ +pdfjs-editor-stamp-add-image-button = + .title = ਚਿੱਤਰ ਜੋੜੋ +pdfjs-editor-stamp-add-image-button-label = ਚਿੱਤਰ ਜੋੜੋ +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = ਮੋਟਾਈ +pdfjs-editor-free-highlight-thickness-title = + .title = ਚੀਜ਼ਾਂ ਨੂੰ ਹੋਰ ਲਿਖਤਾਂ ਤੋਂ ਉਘਾੜਨ ਸਮੇਂ ਮੋਟਾਈ ਨੂੰ ਬਦਲੋ +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = ਲਿਖਤ à¨à¨¡à©€à¨Ÿà¨° + .default-content = …ਲਿਖਣਾ ਸ਼à©à¨°à©‚ ਕਰੋ +pdfjs-free-text = + .aria-label = ਲਿਖਤ à¨à¨¡à©€à¨Ÿà¨° +pdfjs-free-text-default-content = …ਲਿਖਣਾ ਸ਼à©à¨°à©‚ ਕਰੋ +pdfjs-ink = + .aria-label = ਵਹਾਉਣ à¨à¨¡à©€à¨Ÿà¨° +pdfjs-ink-canvas = + .aria-label = ਵਰਤੋਂਕਾਰ ਵਲੋਂ ਬਣਾਇਆ ਚਿੱਤਰ + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = ਬਦਲਵੀਂ ਲਿਖਤ +pdfjs-editor-alt-text-edit-button = + .aria-label = ਬਦਲਵੀ ਲਿਖਤ ਨੂੰ ਸੋਧੋ +pdfjs-editor-alt-text-edit-button-label = ਬਦਲਵੀ ਲਿਖਤ ਨੂੰ ਸੋਧੋ +pdfjs-editor-alt-text-dialog-label = ਚੋਣ ਕਰੋ +pdfjs-editor-alt-text-dialog-description = ਚਿੱਤਰ ਨਾ ਦਿੱਸਣ ਜਾਂ ਲੋਡ ਨਾ ਹੋਣ ਦੀ ਹਾਲਤ ਵਿੱਚ Alt ਲਿਖਤ (ਬਦਲਵੀਂ ਲਿਖਤ) ਲੋਕਾਂ ਲਈ ਮਦਦਗਾਰ ਹà©à©°à¨¦à©€ ਹੈ। +pdfjs-editor-alt-text-add-description-label = ਵਰਣਨ ਜੋੜੋ +pdfjs-editor-alt-text-add-description-description = 1-2 ਵਾਕ ਰੱਖੋ, ਜੋ ਕਿ ਵਿਸ਼ੇ, ਸੈਟਿੰਗ ਜਾਂ ਕਾਰਵਾਈਆਂ ਬਾਰੇ ਦਰਸਾਉਂਦੇ ਹੋਣ। +pdfjs-editor-alt-text-mark-decorative-label = ਸਜਾਵਟ ਵਜੋਂ ਨਿਸ਼ਾਨ ਲਾਇਆ +pdfjs-editor-alt-text-mark-decorative-description = ਇਸ ਨੂੰ ਸਜਾਵਟੀ ਚਿੱਤਰਾਂ ਲਈ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ ਜਿਵੇਂ ਕਿ ਹਾਸ਼ੀਆ ਜਾਂ ਵਾਟਰਮਾਰਕ ਆਦਿ। +pdfjs-editor-alt-text-cancel-button = ਰੱਦ ਕਰੋ +pdfjs-editor-alt-text-save-button = ਸੰਭਾਲੋ +pdfjs-editor-alt-text-decorative-tooltip = ਸਜਾਵਟ ਵਜੋਂ ਨਿਸ਼ਾਨ ਲਾਓ +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = ਮਿਸਾਲ ਵਜੋਂ, “ਗੱਭਰੂ ਭੋਜਨ ਲੈ ਕੇ ਮੇਜ਼ ਉੱਤੇ ਬੈਠਾ ਹੈ†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = ਬਦਲਵੀਂ ਲਿਖਤ + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = ਉੱਤੇ ਖੱਬਾ ਕੋਨਾ — ਮà©à©œ-ਆਕਾਰ ਕਰੋ +pdfjs-editor-resizer-label-top-middle = ਉੱਤੇ ਮੱਧ — ਮà©à©œ-ਆਕਾਰ ਕਰੋ +pdfjs-editor-resizer-label-top-right = ਉੱਤੇ ਸੱਜਾ ਕੋਨਾ — ਮà©à©œ-ਆਕਾਰ ਕਰੋ +pdfjs-editor-resizer-label-middle-right = ਮੱਧ ਸੱਜਾ — ਮà©à©œ-ਆਕਾਰ ਕਰੋ +pdfjs-editor-resizer-label-bottom-right = ਹੇਠਾਂ ਸੱਜਾ ਕੋਨਾ — ਮà©à©œ-ਆਕਾਰ ਕਰੋ +pdfjs-editor-resizer-label-bottom-middle = ਹੇਠਾਂ ਮੱਧ — ਮà©à©œ-ਆਕਾਰ ਕਰੋ +pdfjs-editor-resizer-label-bottom-left = ਹੇਠਾਂ ਖੱਬਾ ਕੋਨਾ — ਮà©à©œ-ਆਕਾਰ ਕਰੋ +pdfjs-editor-resizer-label-middle-left = ਮੱਧ ਖੱਬਾ — ਮà©à©œ-ਆਕਾਰ ਕਰੋ +pdfjs-editor-resizer-top-left = + .aria-label = ਉੱਤੇ ਖੱਬਾ ਕੋਨਾ — ਮà©à©œ-ਆਕਾਰ ਕਰੋ +pdfjs-editor-resizer-top-middle = + .aria-label = ਉੱਤੇ ਮੱਧ — ਮà©à©œ-ਆਕਾਰ ਕਰੋ +pdfjs-editor-resizer-top-right = + .aria-label = ਉੱਤੇ ਸੱਜਾ ਕੋਨਾ — ਮà©à©œ-ਆਕਾਰ ਕਰੋ +pdfjs-editor-resizer-middle-right = + .aria-label = ਮੱਧ ਸੱਜਾ — ਮà©à©œ-ਆਕਾਰ ਕਰੋ +pdfjs-editor-resizer-bottom-right = + .aria-label = ਹੇਠਾਂ ਸੱਜਾ ਕੋਨਾ — ਮà©à©œ-ਆਕਾਰ ਕਰੋ +pdfjs-editor-resizer-bottom-middle = + .aria-label = ਹੇਠਾਂ ਮੱਧ — ਮà©à©œ-ਆਕਾਰ ਕਰੋ +pdfjs-editor-resizer-bottom-left = + .aria-label = ਹੇਠਾਂ ਖੱਬਾ ਕੋਨਾ — ਮà©à©œ-ਆਕਾਰ ਕਰੋ +pdfjs-editor-resizer-middle-left = + .aria-label = ਮੱਧ ਖੱਬਾ — ਮà©à©œ-ਆਕਾਰ ਕਰੋ + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = ਹਾਈਟਲਾਈਟ ਦਾ ਰੰਗ +pdfjs-editor-colorpicker-button = + .title = ਰੰਗ ਨੂੰ ਬਦਲੋ +pdfjs-editor-colorpicker-dropdown = + .aria-label = ਰੰਗ ਚੋਣਾਂ +pdfjs-editor-colorpicker-yellow = + .title = ਪੀਲਾ +pdfjs-editor-colorpicker-green = + .title = ਹਰਾ +pdfjs-editor-colorpicker-blue = + .title = ਨੀਲਾ +pdfjs-editor-colorpicker-pink = + .title = ਗà©à¨²à¨¾à¨¬à©€ +pdfjs-editor-colorpicker-red = + .title = ਲਾਲ + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = ਸਭ ਵੇਖੋ +pdfjs-editor-highlight-show-all-button = + .title = ਸਭ ਵੇਖੋ + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = ਬਦਲਵੀਂ ਲਿਖਤ (ਚਿੱਤਰ ਦਾ ਵਰਣਨ) ਨੂੰ ਸੋਧੋ +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = ਬਦਲਵੀਂ ਲਿਖਤ (ਚਿੱਤਰ ਦਾ ਵਰਣਨ) ਨੂੰ ਜੋੜੋ +pdfjs-editor-new-alt-text-textarea = + .placeholder = …ਆਪਣਾ ਵਰਣਨਾ ਇੱਥੇ ਲਿਖੋ +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = ਲੋਕ, ਜੋ ਕਿ ਚਿੱਤਰ ਨਹੀਂ ਵੇਖ ਸਕਦੇ ਜਾਂ ਜਦ ਵੀ ਚਿੱਤਰਾਂ ਨੂੰ ਲੋਡ ਨਹੀਂ ਜਾ ਸਕਦਾ, ਉਸ ਲਈ ਛੋਟਾ ਵੇਰਵਾ ਦਿਓ। +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = ਇਹ ਬਦਲਵੀਂ ਲਿਖਤ ਆਪਣੇ-ਆਪ ਤਿਆਰ ਕੀਤੀ ਗਈ ਸੀ ਅਤੇ ਗਲਤ ਵੀ ਹੋ ਸਕਦੀ ਹੈ। +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = ਹੋਰ ਜਾਣੋ +pdfjs-editor-new-alt-text-create-automatically-button-label = ਬਲਦਵੀਂ ਲਿਖਤ ਆਪਣੇ-ਆਪ ਬਣਾਓ +pdfjs-editor-new-alt-text-not-now-button = ਹà©à¨£à©‡ ਨਹੀਂ +pdfjs-editor-new-alt-text-error-title = ਬਦਲਵੀਂ ਲਿਖਤ ਆਪਣੇ-ਆਪ ਬਣਾਈ ਨਹੀਂ ਜਾ ਸਕੀ +pdfjs-editor-new-alt-text-error-description = ਆਪਣਾ ਖà©à¨¦ ਦੀ ਬਦਲਵੀਂ ਲਿਖਤ ਲਿਖੋ ਜਾਂ ਫੇਰ ਕੋਸ਼ਿਸ਼ ਕਰੋ। +pdfjs-editor-new-alt-text-error-close-button = ਬੰਦ ਕਰੋ +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = ਬਦਲਵਾਂ ਲਿਖਤ AI ਮਾਡਲ ਡਾਊਨਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ ({ $totalSize } MB ਵਿੱਚੋਂ { $downloadedSize }) + .aria-valuetext = ਬਦਲਵਾਂ ਲਿਖਤ AI ਮਾਡਲ ਡਾਊਨਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ ({ $totalSize } MB ਵਿੱਚੋਂ { $downloadedSize }) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = ਬਦਲਵੀਂ ਲਿਖਤ ਜੋੜੀ +pdfjs-editor-new-alt-text-added-button-label = ਬਦਲਵੀਂ ਲਿਖਤ ਜੋੜੀ +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = ਬਦਲਵਾਂ ਲਿਖਤ ਗà©à©°à¨® ਹੈ +pdfjs-editor-new-alt-text-missing-button-label = ਬਦਲਵਾਂ ਲਿਖਤ ਗà©à©°à¨® ਹੈ +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = ਬਦਲਵੀਂ ਲਿਖਤ ਦਾ ਰੀਵਿਊ ਕਰੋ +pdfjs-editor-new-alt-text-to-review-button-label = ਬਦਲਵੀਂ ਲਿਖਤ ਦਾ ਰੀਵਿਊ ਕਰੋ +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = ਆਪਣੇ-ਆਪ ਬਣਾਇਆ: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = ਚਿੱਤਰ ਬਦਲਵੀਂ ਲਿਖਤ ਦੀਆਂ ਸੈਟਿੰਗਾਂ +pdfjs-image-alt-text-settings-button-label = ਚਿੱਤਰ ਬਦਲਵੀਂ ਲਿਖਤ ਦੀਆਂ ਸੈਟਿੰਗਾਂ +pdfjs-editor-alt-text-settings-dialog-label = ਚਿੱਤਰ ਬਦਲਵੀਂ ਲਿਖਤ ਦੀਆਂ ਸੈਟਿੰਗਾਂ +pdfjs-editor-alt-text-settings-automatic-title = ਆਟੋਮਮੈਟਿਕ ਬਦਲਵੀਂ ਲਿਖਤ +pdfjs-editor-alt-text-settings-create-model-button-label = ਬਲਦਵੀਂ ਲਿਖਤ ਆਪਣੇ-ਆਪ ਬਣਾਓ +pdfjs-editor-alt-text-settings-create-model-description = ਚਿੱਤਰ ਨਾ ਵੇਖ ਸਕਣ ਵਾਲੇ ਲੋਕਾਂ ਦੀ ਮਦਦ ਜਾਂ ਜਦ ਵੀ ਚਿੱਤਰਾਂ ਨੂੰ ਲੋਡ ਨਹੀਂ ਜਾ ਸਕਦਾ, ਉਸ ਲਈ ਛੋਟਾ ਵੇਰਵਾ ਦਿਓ। +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = ਬਦਲਵੀ ਲਿਖਤ ਲਈ AI ਮਾਡਲ ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = ਤà©à¨¹à¨¾à¨¡à©‡ ਡਿਵਾਈਸ ਉੱਤੇ ਲੋਕਲ ਹੀ ਚੱਲਦਾ ਹੋਣ ਕਰਕੇ ਤà©à¨¹à¨¾à¨¡à¨¾ ਡਾਟਾ ਪà©à¨°à¨¾à¨ˆà¨µà©‡à¨Ÿ ਹੀ ਰਹਿੰਦਾ ਹੈ। ਆਟੋਮੈਟਿਕ ਬਦਲਵੀਂ ਲਿਖਤ ਲਈ ਚਾਹੀਦਾ ਹੈ। +pdfjs-editor-alt-text-settings-delete-model-button = ਹਟਾਓ +pdfjs-editor-alt-text-settings-download-model-button = ਡਾਊਨਲੋਡ +pdfjs-editor-alt-text-settings-downloading-model-button = …ਨੂੰ ਡਾਊਨਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ +pdfjs-editor-alt-text-settings-editor-title = ਬਦਲਵੀਂ ਲਿਖਤ à¨à¨¡à©€à¨Ÿà¨° +pdfjs-editor-alt-text-settings-show-dialog-button-label = ਜਦੋਂ ਵਿੱਚ ਚਿੱਤਰ ਜੋੜਿਆ ਜਾਵੇ ਤਾਂ ਫ਼ੌਰਨ ਬਦਲਵੀ ਲਿਖਤ ਸੰਪਾਦਕ ਵੇਖਾਓ +pdfjs-editor-alt-text-settings-show-dialog-description = ਤà©à¨¹à¨¾à¨¡à©€ ਮਦਦ ਕਰਦਾ ਹੈ ਕਿ ਤà©à¨¹à¨¾à¨¡à©‡ ਸਾਰੇ ਚਿੱਤਰਾਂ ਲਈ ਬਦਲਵੀਂ ਲਿਖਤ ਮੌਜੂਦ ਹੋਵੇ। +pdfjs-editor-alt-text-settings-close-button = ਬੰਦ ਕਰੋ + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = ਹਾਈਲਾਈਟ ਨੂੰ ਹਟਾਇਆ ਗਿਆ +pdfjs-editor-undo-bar-message-freetext = ਲਿਖਤ ਨੂੰ ਹਟਾਇਆ ਗਿਆ +pdfjs-editor-undo-bar-message-ink = ਡਰਾਇੰਗ ਨੂੰ ਹਟਾਇਆ ਗਿਆ +pdfjs-editor-undo-bar-message-stamp = ਚਿੱਤਰ ਨੂੰ ਹਟਾਇਆ ਗਿਆ +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } ਵਿਆਖਿਆ ਨੂੰ ਹਟਾਇਆ + *[other] { $count } ਵਿਆਖਿਆਵਾਂ ਨੂੰ ਹਟਾਇਆ + } +pdfjs-editor-undo-bar-undo-button = + .title = ਵਾਪਸ +pdfjs-editor-undo-bar-undo-button-label = ਵਾਪਸ +pdfjs-editor-undo-bar-close-button = + .title = ਬੰਦ ਕਰੋ +pdfjs-editor-undo-bar-close-button-label = ਬੰਦ ਕਰੋ diff --git a/public/assets/pdfjs/locale/pl/viewer.ftl b/public/assets/pdfjs/locale/pl/viewer.ftl new file mode 100755 index 0000000..07f9416 --- /dev/null +++ b/public/assets/pdfjs/locale/pl/viewer.ftl @@ -0,0 +1,518 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Poprzednia strona +pdfjs-previous-button-label = Poprzednia +pdfjs-next-button = + .title = NastÄ™pna strona +pdfjs-next-button-label = NastÄ™pna +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Strona +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = z { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } z { $pagesCount }) +pdfjs-zoom-out-button = + .title = Pomniejsz +pdfjs-zoom-out-button-label = Pomniejsz +pdfjs-zoom-in-button = + .title = PowiÄ™ksz +pdfjs-zoom-in-button-label = PowiÄ™ksz +pdfjs-zoom-select = + .title = Skala +pdfjs-presentation-mode-button = + .title = Przełącz na tryb prezentacji +pdfjs-presentation-mode-button-label = Tryb prezentacji +pdfjs-open-file-button = + .title = Otwórz plik +pdfjs-open-file-button-label = Otwórz +pdfjs-print-button = + .title = Drukuj +pdfjs-print-button-label = Drukuj +pdfjs-save-button = + .title = Zapisz +pdfjs-save-button-label = Zapisz +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Pobierz +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Pobierz +pdfjs-bookmark-button = + .title = Bieżąca strona (adres do otwarcia na bieżącej stronie) +pdfjs-bookmark-button-label = Bieżąca strona + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = NarzÄ™dzia +pdfjs-tools-button-label = NarzÄ™dzia +pdfjs-first-page-button = + .title = Przejdź do pierwszej strony +pdfjs-first-page-button-label = Przejdź do pierwszej strony +pdfjs-last-page-button = + .title = Przejdź do ostatniej strony +pdfjs-last-page-button-label = Przejdź do ostatniej strony +pdfjs-page-rotate-cw-button = + .title = Obróć zgodnie z ruchem wskazówek zegara +pdfjs-page-rotate-cw-button-label = Obróć zgodnie z ruchem wskazówek zegara +pdfjs-page-rotate-ccw-button = + .title = Obróć przeciwnie do ruchu wskazówek zegara +pdfjs-page-rotate-ccw-button-label = Obróć przeciwnie do ruchu wskazówek zegara +pdfjs-cursor-text-select-tool-button = + .title = Włącz narzÄ™dzie zaznaczania tekstu +pdfjs-cursor-text-select-tool-button-label = NarzÄ™dzie zaznaczania tekstu +pdfjs-cursor-hand-tool-button = + .title = Włącz narzÄ™dzie rÄ…czka +pdfjs-cursor-hand-tool-button-label = NarzÄ™dzie rÄ…czka +pdfjs-scroll-page-button = + .title = Przewijaj strony +pdfjs-scroll-page-button-label = Przewijanie stron +pdfjs-scroll-vertical-button = + .title = Przewijaj dokument w pionie +pdfjs-scroll-vertical-button-label = Przewijanie pionowe +pdfjs-scroll-horizontal-button = + .title = Przewijaj dokument w poziomie +pdfjs-scroll-horizontal-button-label = Przewijanie poziome +pdfjs-scroll-wrapped-button = + .title = Strony dokumentu wyÅ›wietlaj i przewijaj w kolumnach +pdfjs-scroll-wrapped-button-label = Widok dwóch stron +pdfjs-spread-none-button = + .title = Nie ustawiaj stron obok siebie +pdfjs-spread-none-button-label = Brak kolumn +pdfjs-spread-odd-button = + .title = Strony nieparzyste ustawiaj na lewo od parzystych +pdfjs-spread-odd-button-label = Nieparzyste po lewej +pdfjs-spread-even-button = + .title = Strony parzyste ustawiaj na lewo od nieparzystych +pdfjs-spread-even-button-label = Parzyste po lewej + +## Document properties dialog + +pdfjs-document-properties-button = + .title = WÅ‚aÅ›ciwoÅ›ci dokumentu… +pdfjs-document-properties-button-label = WÅ‚aÅ›ciwoÅ›ci dokumentu… +pdfjs-document-properties-file-name = Nazwa pliku: +pdfjs-document-properties-file-size = Rozmiar pliku: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } B) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } B) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } B) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } B) +pdfjs-document-properties-title = TytuÅ‚: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Temat: +pdfjs-document-properties-keywords = SÅ‚owa kluczowe: +pdfjs-document-properties-creation-date = Data utworzenia: +pdfjs-document-properties-modification-date = Data modyfikacji: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Utworzony przez: +pdfjs-document-properties-producer = PDF wyprodukowany przez: +pdfjs-document-properties-version = Wersja PDF: +pdfjs-document-properties-page-count = Liczba stron: +pdfjs-document-properties-page-size = Wymiary strony: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = pionowa +pdfjs-document-properties-page-size-orientation-landscape = pozioma +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = US Letter +pdfjs-document-properties-page-size-name-legal = US Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width }×{ $height } { $unit } (orientacja { $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width }×{ $height } { $unit } ({ $name }, orientacja { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Szybki podglÄ…d w Internecie: +pdfjs-document-properties-linearized-yes = tak +pdfjs-document-properties-linearized-no = nie +pdfjs-document-properties-close-button = Zamknij + +## Print + +pdfjs-print-progress-message = Przygotowywanie dokumentu do druku… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Anuluj +pdfjs-printing-not-supported = Ostrzeżenie: drukowanie nie jest w peÅ‚ni obsÅ‚ugiwane przez tÄ™ przeglÄ…darkÄ™. +pdfjs-printing-not-ready = Ostrzeżenie: dokument PDF nie jest caÅ‚kowicie wczytany, wiÄ™c nie można go wydrukować. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Przełącz panel boczny +pdfjs-toggle-sidebar-notification-button = + .title = Przełącz panel boczny (dokument zawiera konspekt/załączniki/warstwy) +pdfjs-toggle-sidebar-button-label = Przełącz panel boczny +pdfjs-document-outline-button = + .title = Konspekt dokumentu (podwójne klikniÄ™cie rozwija lub zwija wszystkie pozycje) +pdfjs-document-outline-button-label = Konspekt dokumentu +pdfjs-attachments-button = + .title = Załączniki +pdfjs-attachments-button-label = Załączniki +pdfjs-layers-button = + .title = Warstwy (podwójne klikniÄ™cie przywraca wszystkie warstwy do stanu domyÅ›lnego) +pdfjs-layers-button-label = Warstwy +pdfjs-thumbs-button = + .title = Miniatury +pdfjs-thumbs-button-label = Miniatury +pdfjs-current-outline-item-button = + .title = Znajdź bieżący element konspektu +pdfjs-current-outline-item-button-label = Bieżący element konspektu +pdfjs-findbar-button = + .title = Znajdź w dokumencie +pdfjs-findbar-button-label = Znajdź +pdfjs-additional-layers = Dodatkowe warstwy + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = { $page }. strona +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura { $page }. strony + +## Find panel button title and messages + +pdfjs-find-input = + .title = Znajdź + .placeholder = Znajdź w dokumencie… +pdfjs-find-previous-button = + .title = Znajdź poprzednie wystÄ…pienie tekstu +pdfjs-find-previous-button-label = Poprzednie +pdfjs-find-next-button = + .title = Znajdź nastÄ™pne wystÄ…pienie tekstu +pdfjs-find-next-button-label = NastÄ™pne +pdfjs-find-highlight-checkbox = Wyróżnianie wszystkich +pdfjs-find-match-case-checkbox-label = Rozróżnianie wielkoÅ›ci liter +pdfjs-find-match-diacritics-checkbox-label = Rozróżnianie liter diakrytyzowanych +pdfjs-find-entire-word-checkbox-label = CaÅ‚e sÅ‚owa +pdfjs-find-reached-top = PoczÄ…tek dokumentu. Wyszukiwanie od koÅ„ca. +pdfjs-find-reached-bottom = Koniec dokumentu. Wyszukiwanie od poczÄ…tku. +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current }. z { $total } trafienia + [few] { $current }. z { $total } trafieÅ„ + *[many] { $current }. z { $total } trafieÅ„ + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] WiÄ™cej niż { $limit } trafienie + [few] WiÄ™cej niż { $limit } trafienia + *[many] WiÄ™cej niż { $limit } trafieÅ„ + } +pdfjs-find-not-found = Nie znaleziono tekstu + +## Predefined zoom values + +pdfjs-page-scale-width = Szerokość strony +pdfjs-page-scale-fit = Dopasowanie strony +pdfjs-page-scale-auto = Skala automatyczna +pdfjs-page-scale-actual = Rozmiar oryginalny +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = { $page }. strona + +## Loading indicator messages + +pdfjs-loading-error = Podczas wczytywania dokumentu PDF wystÄ…piÅ‚ błąd. +pdfjs-invalid-file-error = NieprawidÅ‚owy lub uszkodzony plik PDF. +pdfjs-missing-file-error = Brak pliku PDF. +pdfjs-unexpected-response-error = Nieoczekiwana odpowiedź serwera. +pdfjs-rendering-error = Podczas renderowania strony wystÄ…piÅ‚ błąd. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Przypis: { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Wprowadź hasÅ‚o, aby otworzyć ten dokument PDF. +pdfjs-password-invalid = NieprawidÅ‚owe hasÅ‚o. ProszÄ™ spróbować ponownie. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Anuluj +pdfjs-web-fonts-disabled = Czcionki sieciowe sÄ… wyłączone: nie można użyć osadzonych czcionek PDF. + +## Editing + +pdfjs-editor-free-text-button = + .title = Tekst +pdfjs-editor-free-text-button-label = Tekst +pdfjs-editor-ink-button = + .title = Rysunek +pdfjs-editor-ink-button-label = Rysunek +pdfjs-editor-stamp-button = + .title = Dodaj lub edytuj obrazy +pdfjs-editor-stamp-button-label = Dodaj lub edytuj obrazy +pdfjs-editor-highlight-button = + .title = Wyróżnij +pdfjs-editor-highlight-button-label = Wyróżnij +pdfjs-highlight-floating-button1 = + .title = Wyróżnij + .aria-label = Wyróżnij +pdfjs-highlight-floating-button-label = Wyróżnij + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = UsuÅ„ rysunek +pdfjs-editor-remove-freetext-button = + .title = UsuÅ„ tekst +pdfjs-editor-remove-stamp-button = + .title = UsuÅ„ obraz +pdfjs-editor-remove-highlight-button = + .title = UsuÅ„ wyróżnienie + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Kolor +pdfjs-editor-free-text-size-input = Rozmiar +pdfjs-editor-ink-color-input = Kolor +pdfjs-editor-ink-thickness-input = Grubość +pdfjs-editor-ink-opacity-input = Nieprzezroczystość +pdfjs-editor-stamp-add-image-button = + .title = Dodaj obraz +pdfjs-editor-stamp-add-image-button-label = Dodaj obraz +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Grubość +pdfjs-editor-free-highlight-thickness-title = + .title = ZmieÅ„ grubość podczas wyróżniania elementów innych niż tekst +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Edytor tekstu + .default-content = Zacznij pisać… +pdfjs-free-text = + .aria-label = Edytor tekstu +pdfjs-free-text-default-content = Zacznij pisać… +pdfjs-ink = + .aria-label = Edytor rysunku +pdfjs-ink-canvas = + .aria-label = Obraz utworzony przez użytkownika + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Tekst alternatywny +pdfjs-editor-alt-text-edit-button = + .aria-label = Edytuj tekst alternatywny +pdfjs-editor-alt-text-edit-button-label = Edytuj tekst alternatywny +pdfjs-editor-alt-text-dialog-label = Wybierz opcjÄ™ +pdfjs-editor-alt-text-dialog-description = Tekst alternatywny pomaga, kiedy ktoÅ› nie może zobaczyć obrazu lub gdy siÄ™ nie wczytuje. +pdfjs-editor-alt-text-add-description-label = Dodaj opis +pdfjs-editor-alt-text-add-description-description = Staraj siÄ™ napisać 1-2 zdania opisujÄ…ce temat, miejsce lub dziaÅ‚ania. +pdfjs-editor-alt-text-mark-decorative-label = Oznacz jako dekoracyjne +pdfjs-editor-alt-text-mark-decorative-description = Używane w przypadku obrazów ozdobnych, takich jak obramowania lub znaki wodne. +pdfjs-editor-alt-text-cancel-button = Anuluj +pdfjs-editor-alt-text-save-button = Zapisz +pdfjs-editor-alt-text-decorative-tooltip = Oznaczone jako dekoracyjne +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Na przykÅ‚ad: „MÅ‚ody czÅ‚owiek siada przy stole, aby zjeść posiÅ‚ek†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Tekst alternatywny + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Lewy górny róg — zmieÅ„ rozmiar +pdfjs-editor-resizer-label-top-middle = Górny Å›rodkowy — zmieÅ„ rozmiar +pdfjs-editor-resizer-label-top-right = Prawy górny róg — zmieÅ„ rozmiar +pdfjs-editor-resizer-label-middle-right = Prawy Å›rodkowy — zmieÅ„ rozmiar +pdfjs-editor-resizer-label-bottom-right = Prawy dolny róg — zmieÅ„ rozmiar +pdfjs-editor-resizer-label-bottom-middle = Dolny Å›rodkowy — zmieÅ„ rozmiar +pdfjs-editor-resizer-label-bottom-left = Lewy dolny róg — zmieÅ„ rozmiar +pdfjs-editor-resizer-label-middle-left = Lewy Å›rodkowy — zmieÅ„ rozmiar +pdfjs-editor-resizer-top-left = + .aria-label = Lewy górny róg — zmieÅ„ rozmiar +pdfjs-editor-resizer-top-middle = + .aria-label = Górny Å›rodkowy — zmieÅ„ rozmiar +pdfjs-editor-resizer-top-right = + .aria-label = Prawy górny róg — zmieÅ„ rozmiar +pdfjs-editor-resizer-middle-right = + .aria-label = Prawy Å›rodkowy — zmieÅ„ rozmiar +pdfjs-editor-resizer-bottom-right = + .aria-label = Prawy dolny róg — zmieÅ„ rozmiar +pdfjs-editor-resizer-bottom-middle = + .aria-label = Dolny Å›rodkowy — zmieÅ„ rozmiar +pdfjs-editor-resizer-bottom-left = + .aria-label = Lewy dolny róg — zmieÅ„ rozmiar +pdfjs-editor-resizer-middle-left = + .aria-label = Lewy Å›rodkowy — zmieÅ„ rozmiar + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Kolor wyróżnienia +pdfjs-editor-colorpicker-button = + .title = ZmieÅ„ kolor +pdfjs-editor-colorpicker-dropdown = + .aria-label = Wybór kolorów +pdfjs-editor-colorpicker-yellow = + .title = Żółty +pdfjs-editor-colorpicker-green = + .title = Zielony +pdfjs-editor-colorpicker-blue = + .title = Niebieski +pdfjs-editor-colorpicker-pink = + .title = Różowy +pdfjs-editor-colorpicker-red = + .title = Czerwony + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Pokaż wszystkie +pdfjs-editor-highlight-show-all-button = + .title = Pokaż wszystkie + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Edytuj tekst alternatywny (opis obrazu) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Dodaj tekst alternatywny (opis obrazu) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Napisz tutaj opis… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Krótki opis dla osób, które nie widzÄ… obrazu lub kiedy obraz siÄ™ nie wczytuje. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Ten tekst alternatywny zostaÅ‚ utworzony automatycznie i może być niepoprawny. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = WiÄ™cej informacji +pdfjs-editor-new-alt-text-create-automatically-button-label = Automatycznie utwórz tekst alternatywny +pdfjs-editor-new-alt-text-not-now-button = Nie teraz +pdfjs-editor-new-alt-text-error-title = Nie można automatycznie utworzyć tekstu alternatywnego +pdfjs-editor-new-alt-text-error-description = ProszÄ™ napisać wÅ‚asny tekst alternatywny lub spróbować ponownie później. +pdfjs-editor-new-alt-text-error-close-button = Zamknij +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Pobieranie modelu SI tekstu alternatywnego ({ $downloadedSize } z { $totalSize } MB) + .aria-valuetext = Pobieranie modelu SI tekstu alternatywnego ({ $downloadedSize } z { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Dodano tekst alternatywny +pdfjs-editor-new-alt-text-added-button-label = Dodano tekst alternatywny +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Brak tekstu alternatywnego +pdfjs-editor-new-alt-text-missing-button-label = Brak tekstu alternatywnego +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Przejrzyj tekst alternatywny +pdfjs-editor-new-alt-text-to-review-button-label = Przejrzyj tekst alternatywny +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Utworzono automatycznie: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Ustawienia tekstu alternatywnego obrazów +pdfjs-image-alt-text-settings-button-label = Ustawienia tekstu alternatywnego obrazów +pdfjs-editor-alt-text-settings-dialog-label = Ustawienia tekstu alternatywnego obrazów +pdfjs-editor-alt-text-settings-automatic-title = Automatyczny tekst alternatywny +pdfjs-editor-alt-text-settings-create-model-button-label = Automatyczne tworzenie tekstu alternatywnego +pdfjs-editor-alt-text-settings-create-model-description = Podpowiada opisy, które mogÄ… pomóc osobom, które nie widzÄ… obrazu lub kiedy obraz siÄ™ nie wczytuje. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model SI tekstu alternatywnego ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = DziaÅ‚a lokalnie na urzÄ…dzeniu użytkownika, wiÄ™c Twoje dane pozostajÄ… prywatne. Wymagane do funkcji automatycznego tekstu alternatywnego. +pdfjs-editor-alt-text-settings-delete-model-button = UsuÅ„ +pdfjs-editor-alt-text-settings-download-model-button = Pobierz +pdfjs-editor-alt-text-settings-downloading-model-button = Pobieranie… +pdfjs-editor-alt-text-settings-editor-title = Edytor tekstu alternatywnego +pdfjs-editor-alt-text-settings-show-dialog-button-label = WyÅ›wietlanie edytora tekstu alternatywnego od razu po dodaniu obrazu +pdfjs-editor-alt-text-settings-show-dialog-description = Pomaga upewnić siÄ™, że wszystkie obrazy majÄ… tekst alternatywny. +pdfjs-editor-alt-text-settings-close-button = Zamknij + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = UsuniÄ™to wyróżnienie +pdfjs-editor-undo-bar-message-freetext = UsuniÄ™to tekst +pdfjs-editor-undo-bar-message-ink = UsuniÄ™to rysunek +pdfjs-editor-undo-bar-message-stamp = UsuniÄ™to obraz +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] UsuniÄ™to przypis + [few] UsuniÄ™to { $count } przypisy + *[many] UsuniÄ™to { $count } przypisów + } +pdfjs-editor-undo-bar-undo-button = + .title = Cofnij +pdfjs-editor-undo-bar-undo-button-label = Cofnij +pdfjs-editor-undo-bar-close-button = + .title = Zamknij +pdfjs-editor-undo-bar-close-button-label = Zamknij diff --git a/public/assets/pdfjs/locale/pt-BR/viewer.ftl b/public/assets/pdfjs/locale/pt-BR/viewer.ftl new file mode 100755 index 0000000..7da5201 --- /dev/null +++ b/public/assets/pdfjs/locale/pt-BR/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Página anterior +pdfjs-previous-button-label = Anterior +pdfjs-next-button = + .title = Próxima página +pdfjs-next-button-label = Próxima +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Página +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = de { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount }) +pdfjs-zoom-out-button = + .title = Reduzir +pdfjs-zoom-out-button-label = Reduzir +pdfjs-zoom-in-button = + .title = Ampliar +pdfjs-zoom-in-button-label = Ampliar +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Mudar para o modo de apresentação +pdfjs-presentation-mode-button-label = Modo de apresentação +pdfjs-open-file-button = + .title = Abrir arquivo +pdfjs-open-file-button-label = Abrir +pdfjs-print-button = + .title = Imprimir +pdfjs-print-button-label = Imprimir +pdfjs-save-button = + .title = Salvar +pdfjs-save-button-label = Salvar +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Baixar +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Baixar +pdfjs-bookmark-button = + .title = Página atual (ver URL da página atual) +pdfjs-bookmark-button-label = Pagina atual + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Ferramentas +pdfjs-tools-button-label = Ferramentas +pdfjs-first-page-button = + .title = Ir para a primeira página +pdfjs-first-page-button-label = Ir para a primeira página +pdfjs-last-page-button = + .title = Ir para a última página +pdfjs-last-page-button-label = Ir para a última página +pdfjs-page-rotate-cw-button = + .title = Girar no sentido horário +pdfjs-page-rotate-cw-button-label = Girar no sentido horário +pdfjs-page-rotate-ccw-button = + .title = Girar no sentido anti-horário +pdfjs-page-rotate-ccw-button-label = Girar no sentido anti-horário +pdfjs-cursor-text-select-tool-button = + .title = Ativar a ferramenta de seleção de texto +pdfjs-cursor-text-select-tool-button-label = Ferramenta de seleção de texto +pdfjs-cursor-hand-tool-button = + .title = Ativar ferramenta de deslocamento +pdfjs-cursor-hand-tool-button-label = Ferramenta de deslocamento +pdfjs-scroll-page-button = + .title = Usar rolagem de página +pdfjs-scroll-page-button-label = Rolagem de página +pdfjs-scroll-vertical-button = + .title = Usar deslocamento vertical +pdfjs-scroll-vertical-button-label = Deslocamento vertical +pdfjs-scroll-horizontal-button = + .title = Usar deslocamento horizontal +pdfjs-scroll-horizontal-button-label = Deslocamento horizontal +pdfjs-scroll-wrapped-button = + .title = Usar deslocamento contido +pdfjs-scroll-wrapped-button-label = Deslocamento contido +pdfjs-spread-none-button = + .title = Não reagrupar páginas +pdfjs-spread-none-button-label = Não estender +pdfjs-spread-odd-button = + .title = Agrupar páginas começando em páginas com números ímpares +pdfjs-spread-odd-button-label = Estender ímpares +pdfjs-spread-even-button = + .title = Agrupar páginas começando em páginas com números pares +pdfjs-spread-even-button-label = Estender pares + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Propriedades do documento… +pdfjs-document-properties-button-label = Propriedades do documento… +pdfjs-document-properties-file-name = Nome do arquivo: +pdfjs-document-properties-file-size = Tamanho do arquivo: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Título: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Assunto: +pdfjs-document-properties-keywords = Palavras-chave: +pdfjs-document-properties-creation-date = Data da criação: +pdfjs-document-properties-modification-date = Data da modificação: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Criação: +pdfjs-document-properties-producer = Criador do PDF: +pdfjs-document-properties-version = Versão do PDF: +pdfjs-document-properties-page-count = Número de páginas: +pdfjs-document-properties-page-size = Tamanho da página: +pdfjs-document-properties-page-size-unit-inches = pol. +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = retrato +pdfjs-document-properties-page-size-orientation-landscape = paisagem +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Carta +pdfjs-document-properties-page-size-name-legal = Jurídico + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Exibição web rápida: +pdfjs-document-properties-linearized-yes = Sim +pdfjs-document-properties-linearized-no = Não +pdfjs-document-properties-close-button = Fechar + +## Print + +pdfjs-print-progress-message = Preparando documento para impressão… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress } % +pdfjs-print-progress-close-button = Cancelar +pdfjs-printing-not-supported = Aviso: a impressão não é totalmente suportada neste navegador. +pdfjs-printing-not-ready = Aviso: o PDF não está totalmente carregado para impressão. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Exibir/ocultar painel lateral +pdfjs-toggle-sidebar-notification-button = + .title = Exibir/ocultar painel lateral (documento contém estrutura/anexos/camadas) +pdfjs-toggle-sidebar-button-label = Exibir/ocultar painel lateral +pdfjs-document-outline-button = + .title = Mostrar estrutura do documento (duplo-clique expande/recolhe todos os itens) +pdfjs-document-outline-button-label = Estrutura do documento +pdfjs-attachments-button = + .title = Mostrar anexos +pdfjs-attachments-button-label = Anexos +pdfjs-layers-button = + .title = Mostrar camadas (duplo-clique redefine todas as camadas ao estado predefinido) +pdfjs-layers-button-label = Camadas +pdfjs-thumbs-button = + .title = Mostrar miniaturas +pdfjs-thumbs-button-label = Miniaturas +pdfjs-current-outline-item-button = + .title = Encontrar item atual da estrutura +pdfjs-current-outline-item-button-label = Item atual da estrutura +pdfjs-findbar-button = + .title = Procurar no documento +pdfjs-findbar-button-label = Procurar +pdfjs-additional-layers = Camadas adicionais + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Página { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura da página { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Procurar + .placeholder = Procurar no documento… +pdfjs-find-previous-button = + .title = Procurar a ocorrência anterior da frase +pdfjs-find-previous-button-label = Anterior +pdfjs-find-next-button = + .title = Procurar a próxima ocorrência da frase +pdfjs-find-next-button-label = Próxima +pdfjs-find-highlight-checkbox = Destacar tudo +pdfjs-find-match-case-checkbox-label = Diferenciar maiúsculas/minúsculas +pdfjs-find-match-diacritics-checkbox-label = Considerar acentuação +pdfjs-find-entire-word-checkbox-label = Palavras completas +pdfjs-find-reached-top = Início do documento alcançado, continuando do fim +pdfjs-find-reached-bottom = Fim do documento alcançado, continuando do início +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } de { $total } ocorrência + *[other] { $current } de { $total } ocorrências + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Mais de { $limit } ocorrência + *[other] Mais de { $limit } ocorrências + } +pdfjs-find-not-found = Não encontrado + +## Predefined zoom values + +pdfjs-page-scale-width = Largura da página +pdfjs-page-scale-fit = Ajustar à janela +pdfjs-page-scale-auto = Zoom automático +pdfjs-page-scale-actual = Tamanho real +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Página { $page } + +## Loading indicator messages + +pdfjs-loading-error = Ocorreu um erro ao carregar o PDF. +pdfjs-invalid-file-error = Arquivo PDF corrompido ou inválido. +pdfjs-missing-file-error = Arquivo PDF ausente. +pdfjs-unexpected-response-error = Resposta inesperada do servidor. +pdfjs-rendering-error = Ocorreu um erro ao renderizar a página. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Anotação { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Forneça a senha para abrir este arquivo PDF. +pdfjs-password-invalid = Senha inválida. Tente novamente. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Cancelar +pdfjs-web-fonts-disabled = As fontes web estão desativadas: não foi possível usar fontes incorporadas do PDF. + +## Editing + +pdfjs-editor-free-text-button = + .title = Texto +pdfjs-editor-free-text-button-label = Texto +pdfjs-editor-ink-button = + .title = Desenho +pdfjs-editor-ink-button-label = Desenho +pdfjs-editor-stamp-button = + .title = Adicionar ou editar imagens +pdfjs-editor-stamp-button-label = Adicionar ou editar imagens +pdfjs-editor-highlight-button = + .title = Destaque +pdfjs-editor-highlight-button-label = Destaque +pdfjs-highlight-floating-button1 = + .title = Destaque + .aria-label = Destaque +pdfjs-highlight-floating-button-label = Destaque + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Remover desenho +pdfjs-editor-remove-freetext-button = + .title = Remover texto +pdfjs-editor-remove-stamp-button = + .title = Remover imagem +pdfjs-editor-remove-highlight-button = + .title = Remover destaque + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Cor +pdfjs-editor-free-text-size-input = Tamanho +pdfjs-editor-ink-color-input = Cor +pdfjs-editor-ink-thickness-input = Espessura +pdfjs-editor-ink-opacity-input = Opacidade +pdfjs-editor-stamp-add-image-button = + .title = Adicionar imagem +pdfjs-editor-stamp-add-image-button-label = Adicionar imagem +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Espessura +pdfjs-editor-free-highlight-thickness-title = + .title = Mudar espessura ao destacar itens que não são texto +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Editor de texto + .default-content = Comece a digitar… +pdfjs-free-text = + .aria-label = Editor de texto +pdfjs-free-text-default-content = Comece digitando… +pdfjs-ink = + .aria-label = Editor de desenho +pdfjs-ink-canvas = + .aria-label = Imagem criada pelo usuário + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Texto alternativo +pdfjs-editor-alt-text-edit-button = + .aria-label = Editar texto alternativo +pdfjs-editor-alt-text-edit-button-label = Editar texto alternativo +pdfjs-editor-alt-text-dialog-label = Escolha uma opção +pdfjs-editor-alt-text-dialog-description = O texto alternativo ajuda quando uma imagem não aparece ou não é carregada. +pdfjs-editor-alt-text-add-description-label = Adicionar uma descrição +pdfjs-editor-alt-text-add-description-description = Procure usar uma ou duas frases que descrevam o assunto, cenário ou ação. +pdfjs-editor-alt-text-mark-decorative-label = Marcar como decorativa +pdfjs-editor-alt-text-mark-decorative-description = Isto é usado em imagens ornamentais, como bordas ou marcas d'água. +pdfjs-editor-alt-text-cancel-button = Cancelar +pdfjs-editor-alt-text-save-button = Salvar +pdfjs-editor-alt-text-decorative-tooltip = Marcado como decorativa +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Por exemplo, “Um jovem senta-se à mesa para comer uma refeição†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Texto alternativo + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Canto superior esquerdo — redimensionar +pdfjs-editor-resizer-label-top-middle = No centro do topo — redimensionar +pdfjs-editor-resizer-label-top-right = Canto superior direito — redimensionar +pdfjs-editor-resizer-label-middle-right = No meio à direita — redimensionar +pdfjs-editor-resizer-label-bottom-right = Canto inferior direito — redimensionar +pdfjs-editor-resizer-label-bottom-middle = No centro da base — redimensionar +pdfjs-editor-resizer-label-bottom-left = Canto inferior esquerdo — redimensionar +pdfjs-editor-resizer-label-middle-left = No meio à esquerda — redimensionar +pdfjs-editor-resizer-top-left = + .aria-label = Canto superior esquerdo — redimensionar +pdfjs-editor-resizer-top-middle = + .aria-label = No centro do topo — redimensionar +pdfjs-editor-resizer-top-right = + .aria-label = Canto superior direito — redimensionar +pdfjs-editor-resizer-middle-right = + .aria-label = No meio à direita — redimensionar +pdfjs-editor-resizer-bottom-right = + .aria-label = Canto inferior direito — redimensionar +pdfjs-editor-resizer-bottom-middle = + .aria-label = No centro da base — redimensionar +pdfjs-editor-resizer-bottom-left = + .aria-label = Canto inferior esquerdo — redimensionar +pdfjs-editor-resizer-middle-left = + .aria-label = No meio à esquerda — redimensionar + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Cor de destaque +pdfjs-editor-colorpicker-button = + .title = Mudar cor +pdfjs-editor-colorpicker-dropdown = + .aria-label = Opções de cores +pdfjs-editor-colorpicker-yellow = + .title = Amarelo +pdfjs-editor-colorpicker-green = + .title = Verde +pdfjs-editor-colorpicker-blue = + .title = Azul +pdfjs-editor-colorpicker-pink = + .title = Rosa +pdfjs-editor-colorpicker-red = + .title = Vermelho + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Mostrar todos +pdfjs-editor-highlight-show-all-button = + .title = Mostrar todos + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Editar texto alternativo (descrição da imagem) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Adicionar texto alternativo (descrição da imagem) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Escreva sua descrição aqui… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Descrição curta para pessoas que não conseguem ver a imagem ou quando a imagem não é carregada. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Este texto alternativo foi criado automaticamente, pode não estar correto. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Saiba mais +pdfjs-editor-new-alt-text-create-automatically-button-label = Criar texto alternativo automaticamente +pdfjs-editor-new-alt-text-not-now-button = Agora não +pdfjs-editor-new-alt-text-error-title = Não foi possível criar texto alternativo automaticamente +pdfjs-editor-new-alt-text-error-description = Escreva seu próprio texto alternativo ou tente novamente mais tarde. +pdfjs-editor-new-alt-text-error-close-button = Fechar +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Baixando modelo de inteligência artificial de texto alternativo ({ $downloadedSize } de { $totalSize } MB) + .aria-valuetext = Baixando modelo de inteligência artificial de texto alternativo ({ $downloadedSize } de { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Texto alternativo adicionado +pdfjs-editor-new-alt-text-added-button-label = Texto alternativo adicionado +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Falta texto alternativo +pdfjs-editor-new-alt-text-missing-button-label = Falta texto alternativo +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Revisar texto alternativo +pdfjs-editor-new-alt-text-to-review-button-label = Revisar texto alternativo +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Criado automaticamente: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Configurações de texto alternativo de imagens +pdfjs-image-alt-text-settings-button-label = Configurações de texto alternativo de imagens +pdfjs-editor-alt-text-settings-dialog-label = Configurações de texto alternativo de imagens +pdfjs-editor-alt-text-settings-automatic-title = Texto alternativo automático +pdfjs-editor-alt-text-settings-create-model-button-label = Criar texto alternativo automaticamente +pdfjs-editor-alt-text-settings-create-model-description = Sugere uma descrição para ajudar pessoas que não conseguem ver a imagem ou quando a imagem não é carregada. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modelo de inteligência artificial de texto alternativo ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Funciona localmente no seu dispositivo para que seus dados permaneçam privativos. Necessário para texto alternativo automático. +pdfjs-editor-alt-text-settings-delete-model-button = Excluir +pdfjs-editor-alt-text-settings-download-model-button = Baixar +pdfjs-editor-alt-text-settings-downloading-model-button = Baixando… +pdfjs-editor-alt-text-settings-editor-title = Editor de texto alternativo +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostrar o editor de texto alternativo imediatamente ao adicionar uma imagem +pdfjs-editor-alt-text-settings-show-dialog-description = Ajuda a assegurar que todas as suas imagens tenham texto alternativo. +pdfjs-editor-alt-text-settings-close-button = Fechar + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Destaque removido +pdfjs-editor-undo-bar-message-freetext = Texto removido +pdfjs-editor-undo-bar-message-ink = Desenho removido +pdfjs-editor-undo-bar-message-stamp = Imagem removida +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } anotação removida + *[other] { $count } anotações removidas + } +pdfjs-editor-undo-bar-undo-button = + .title = Desfazer +pdfjs-editor-undo-bar-undo-button-label = Desfazer +pdfjs-editor-undo-bar-close-button = + .title = Fechar +pdfjs-editor-undo-bar-close-button-label = Fechar diff --git a/public/assets/pdfjs/locale/pt-PT/viewer.ftl b/public/assets/pdfjs/locale/pt-PT/viewer.ftl new file mode 100755 index 0000000..1829417 --- /dev/null +++ b/public/assets/pdfjs/locale/pt-PT/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Página anterior +pdfjs-previous-button-label = Anterior +pdfjs-next-button = + .title = Página seguinte +pdfjs-next-button-label = Seguinte +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Página +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = de { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount }) +pdfjs-zoom-out-button = + .title = Reduzir +pdfjs-zoom-out-button-label = Reduzir +pdfjs-zoom-in-button = + .title = Ampliar +pdfjs-zoom-in-button-label = Ampliar +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Trocar para o modo de apresentação +pdfjs-presentation-mode-button-label = Modo de apresentação +pdfjs-open-file-button = + .title = Abrir ficheiro +pdfjs-open-file-button-label = Abrir +pdfjs-print-button = + .title = Imprimir +pdfjs-print-button-label = Imprimir +pdfjs-save-button = + .title = Guardar +pdfjs-save-button-label = Guardar +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Transferir +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Transferir +pdfjs-bookmark-button = + .title = Página atual (ver URL da página atual) +pdfjs-bookmark-button-label = Pagina atual + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Ferramentas +pdfjs-tools-button-label = Ferramentas +pdfjs-first-page-button = + .title = Ir para a primeira página +pdfjs-first-page-button-label = Ir para a primeira página +pdfjs-last-page-button = + .title = Ir para a última página +pdfjs-last-page-button-label = Ir para a última página +pdfjs-page-rotate-cw-button = + .title = Rodar à direita +pdfjs-page-rotate-cw-button-label = Rodar à direita +pdfjs-page-rotate-ccw-button = + .title = Rodar à esquerda +pdfjs-page-rotate-ccw-button-label = Rodar à esquerda +pdfjs-cursor-text-select-tool-button = + .title = Ativar ferramenta de seleção de texto +pdfjs-cursor-text-select-tool-button-label = Ferramenta de seleção de texto +pdfjs-cursor-hand-tool-button = + .title = Ativar ferramenta de mão +pdfjs-cursor-hand-tool-button-label = Ferramenta de mão +pdfjs-scroll-page-button = + .title = Utilizar deslocamento da página +pdfjs-scroll-page-button-label = Deslocamento da página +pdfjs-scroll-vertical-button = + .title = Utilizar deslocação vertical +pdfjs-scroll-vertical-button-label = Deslocação vertical +pdfjs-scroll-horizontal-button = + .title = Utilizar deslocação horizontal +pdfjs-scroll-horizontal-button-label = Deslocação horizontal +pdfjs-scroll-wrapped-button = + .title = Utilizar deslocação encapsulada +pdfjs-scroll-wrapped-button-label = Deslocação encapsulada +pdfjs-spread-none-button = + .title = Não juntar páginas dispersas +pdfjs-spread-none-button-label = Sem spreads +pdfjs-spread-odd-button = + .title = Juntar páginas dispersas a partir de páginas com números ímpares +pdfjs-spread-odd-button-label = Spreads ímpares +pdfjs-spread-even-button = + .title = Juntar páginas dispersas a partir de páginas com números pares +pdfjs-spread-even-button-label = Spreads pares + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Propriedades do documento… +pdfjs-document-properties-button-label = Propriedades do documento… +pdfjs-document-properties-file-name = Nome do ficheiro: +pdfjs-document-properties-file-size = Tamanho do ficheiro: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Título: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Assunto: +pdfjs-document-properties-keywords = Palavras-chave: +pdfjs-document-properties-creation-date = Data de criação: +pdfjs-document-properties-modification-date = Data de modificação: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Criador: +pdfjs-document-properties-producer = Produtor de PDF: +pdfjs-document-properties-version = Versão do PDF: +pdfjs-document-properties-page-count = N.º de páginas: +pdfjs-document-properties-page-size = Tamanho da página: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = retrato +pdfjs-document-properties-page-size-orientation-landscape = paisagem +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Carta +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Vista rápida web: +pdfjs-document-properties-linearized-yes = Sim +pdfjs-document-properties-linearized-no = Não +pdfjs-document-properties-close-button = Fechar + +## Print + +pdfjs-print-progress-message = A preparar o documento para impressão… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Cancelar +pdfjs-printing-not-supported = Aviso: a impressão não é totalmente suportada por este navegador. +pdfjs-printing-not-ready = Aviso: o PDF ainda não está totalmente carregado. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Alternar barra lateral +pdfjs-toggle-sidebar-notification-button = + .title = Alternar barra lateral (o documento contém contornos/anexos/camadas) +pdfjs-toggle-sidebar-button-label = Alternar barra lateral +pdfjs-document-outline-button = + .title = Mostrar esquema do documento (duplo clique para expandir/colapsar todos os itens) +pdfjs-document-outline-button-label = Esquema do documento +pdfjs-attachments-button = + .title = Mostrar anexos +pdfjs-attachments-button-label = Anexos +pdfjs-layers-button = + .title = Mostrar camadas (clique duas vezes para repor todas as camadas para o estado predefinido) +pdfjs-layers-button-label = Camadas +pdfjs-thumbs-button = + .title = Mostrar miniaturas +pdfjs-thumbs-button-label = Miniaturas +pdfjs-current-outline-item-button = + .title = Encontrar o item atualmente destacado +pdfjs-current-outline-item-button-label = Item atualmente destacado +pdfjs-findbar-button = + .title = Localizar em documento +pdfjs-findbar-button-label = Localizar +pdfjs-additional-layers = Camadas adicionais + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Página { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura da página { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Localizar + .placeholder = Localizar em documento… +pdfjs-find-previous-button = + .title = Localizar ocorrência anterior da frase +pdfjs-find-previous-button-label = Anterior +pdfjs-find-next-button = + .title = Localizar ocorrência seguinte da frase +pdfjs-find-next-button-label = Seguinte +pdfjs-find-highlight-checkbox = Destacar tudo +pdfjs-find-match-case-checkbox-label = Correspondência +pdfjs-find-match-diacritics-checkbox-label = Corresponder diacríticos +pdfjs-find-entire-word-checkbox-label = Palavras completas +pdfjs-find-reached-top = Topo do documento atingido, a continuar a partir do fundo +pdfjs-find-reached-bottom = Fim do documento atingido, a continuar a partir do topo +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } de { $total } correspondência + *[other] { $current } de { $total } correspondências + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Mais de { $limit } correspondência + *[other] Mais de { $limit } correspondências + } +pdfjs-find-not-found = Frase não encontrada + +## Predefined zoom values + +pdfjs-page-scale-width = Ajustar à largura +pdfjs-page-scale-fit = Ajustar à página +pdfjs-page-scale-auto = Zoom automático +pdfjs-page-scale-actual = Tamanho real +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Página { $page } + +## Loading indicator messages + +pdfjs-loading-error = Ocorreu um erro ao carregar o PDF. +pdfjs-invalid-file-error = Ficheiro PDF inválido ou danificado. +pdfjs-missing-file-error = Ficheiro PDF inexistente. +pdfjs-unexpected-response-error = Resposta inesperada do servidor. +pdfjs-rendering-error = Ocorreu um erro ao processar a página. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Anotação { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Introduza a palavra-passe para abrir este ficheiro PDF. +pdfjs-password-invalid = Palavra-passe inválida. Por favor, tente novamente. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Cancelar +pdfjs-web-fonts-disabled = Os tipos de letra web estão desativados: não é possível utilizar os tipos de letra PDF embutidos. + +## Editing + +pdfjs-editor-free-text-button = + .title = Texto +pdfjs-editor-free-text-button-label = Texto +pdfjs-editor-ink-button = + .title = Desenhar +pdfjs-editor-ink-button-label = Desenhar +pdfjs-editor-stamp-button = + .title = Adicionar ou editar imagens +pdfjs-editor-stamp-button-label = Adicionar ou editar imagens +pdfjs-editor-highlight-button = + .title = Destaque +pdfjs-editor-highlight-button-label = Destaque +pdfjs-highlight-floating-button1 = + .title = Realçar + .aria-label = Realçar +pdfjs-highlight-floating-button-label = Realçar + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Remover desenho +pdfjs-editor-remove-freetext-button = + .title = Remover texto +pdfjs-editor-remove-stamp-button = + .title = Remover imagem +pdfjs-editor-remove-highlight-button = + .title = Remover destaque + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Cor +pdfjs-editor-free-text-size-input = Tamanho +pdfjs-editor-ink-color-input = Cor +pdfjs-editor-ink-thickness-input = Espessura +pdfjs-editor-ink-opacity-input = Opacidade +pdfjs-editor-stamp-add-image-button = + .title = Adicionar imagem +pdfjs-editor-stamp-add-image-button-label = Adicionar imagem +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Espessura +pdfjs-editor-free-highlight-thickness-title = + .title = Alterar espessura quando destacar itens que não sejam texto +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Editor de texto + .default-content = Comece a escrever… +pdfjs-free-text = + .aria-label = Editor de texto +pdfjs-free-text-default-content = Começar a digitar… +pdfjs-ink = + .aria-label = Editor de desenho +pdfjs-ink-canvas = + .aria-label = Imagem criada pelo utilizador + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Texto alternativo +pdfjs-editor-alt-text-edit-button = + .aria-label = Editar texto alternativo +pdfjs-editor-alt-text-edit-button-label = Editar texto alternativo +pdfjs-editor-alt-text-dialog-label = Escolher uma opção +pdfjs-editor-alt-text-dialog-description = O texto alternativo (texto alternativo) ajuda quando as pessoas não conseguem ver a imagem ou quando a mesma não é carregada. +pdfjs-editor-alt-text-add-description-label = Adicionar uma descrição +pdfjs-editor-alt-text-add-description-description = Aponte para 1-2 frases que descrevam o assunto, definição ou ações. +pdfjs-editor-alt-text-mark-decorative-label = Marcar como decorativa +pdfjs-editor-alt-text-mark-decorative-description = Isto é utilizado para imagens decorativas, tais como limites ou marcas d'água. +pdfjs-editor-alt-text-cancel-button = Cancelar +pdfjs-editor-alt-text-save-button = Guardar +pdfjs-editor-alt-text-decorative-tooltip = Marcada como decorativa +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Por exemplo, “Um jovem senta-se à mesa para comer uma refeição†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Texto alternativo + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Canto superior esquerdo — redimensionar +pdfjs-editor-resizer-label-top-middle = Superior ao centro — redimensionar +pdfjs-editor-resizer-label-top-right = Canto superior direito — redimensionar +pdfjs-editor-resizer-label-middle-right = Centro à direita — redimensionar +pdfjs-editor-resizer-label-bottom-right = Canto inferior direito — redimensionar +pdfjs-editor-resizer-label-bottom-middle = Inferior ao centro — redimensionar +pdfjs-editor-resizer-label-bottom-left = Canto inferior esquerdo — redimensionar +pdfjs-editor-resizer-label-middle-left = Centro à esquerda — redimensionar +pdfjs-editor-resizer-top-left = + .aria-label = Canto superior esquerdo — redimensionar +pdfjs-editor-resizer-top-middle = + .aria-label = Superior ao centro — redimensionar +pdfjs-editor-resizer-top-right = + .aria-label = Canto superior direito — redimensionar +pdfjs-editor-resizer-middle-right = + .aria-label = Centro à direita — redimensionar +pdfjs-editor-resizer-bottom-right = + .aria-label = Canto inferior direito — redimensionar +pdfjs-editor-resizer-bottom-middle = + .aria-label = Inferior ao centro — redimensionar +pdfjs-editor-resizer-bottom-left = + .aria-label = Canto inferior esquerdo — redimensionar +pdfjs-editor-resizer-middle-left = + .aria-label = Centro à esquerda — redimensionar + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Cor de destaque +pdfjs-editor-colorpicker-button = + .title = Alterar cor +pdfjs-editor-colorpicker-dropdown = + .aria-label = Escolhas de cor +pdfjs-editor-colorpicker-yellow = + .title = Amarelo +pdfjs-editor-colorpicker-green = + .title = Verde +pdfjs-editor-colorpicker-blue = + .title = Azul +pdfjs-editor-colorpicker-pink = + .title = Rosa +pdfjs-editor-colorpicker-red = + .title = Vermelho + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Mostrar tudo +pdfjs-editor-highlight-show-all-button = + .title = Mostrar tudo + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Editar texto alternativo (descrição da imagem) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Adicionar texto alternativo (descrição da imagem) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Escreva a sua descrição aqui… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Descrição curta para as pessoas que não podem visualizar a imagem ou quando a imagem não carrega. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Este texto alternativo foi criado automaticamente e pode ser impreciso. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Saber mais +pdfjs-editor-new-alt-text-create-automatically-button-label = Criar texto alternativo automaticamente +pdfjs-editor-new-alt-text-not-now-button = Agora não +pdfjs-editor-new-alt-text-error-title = Não foi possível criar o texto alternativo automaticamente +pdfjs-editor-new-alt-text-error-description = Escreva o seu próprio texto alternativo ou tente novamente mais tarde. +pdfjs-editor-new-alt-text-error-close-button = Fechar +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = A transferir o modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB) + .aria-valuetext = A transferir o modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Texto alternativo adicionado +pdfjs-editor-new-alt-text-added-button-label = Texto alternativo adicionado +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Texto alternativo em falta +pdfjs-editor-new-alt-text-missing-button-label = Texto alternativo em falta +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Rever texto alternativo +pdfjs-editor-new-alt-text-to-review-button-label = Rever texto alternativo +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Criado automaticamente: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Definições de texto alternativo da imagem +pdfjs-image-alt-text-settings-button-label = Definições de texto alternativo da imagem +pdfjs-editor-alt-text-settings-dialog-label = Definições de texto alternativo das imagens +pdfjs-editor-alt-text-settings-automatic-title = Texto alternativo automático +pdfjs-editor-alt-text-settings-create-model-button-label = Criar texto alternativo automaticamente +pdfjs-editor-alt-text-settings-create-model-description = Sugere descrições para ajudar as pessoas que não podem visualizar a imagem ou quando a imagem não carrega. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modelo de IA de texto alternativo ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = É executado localmente no seu dispositivo para que os seus dados se mantenham privados. É necessário para o texto alternativo automático. +pdfjs-editor-alt-text-settings-delete-model-button = Eliminar +pdfjs-editor-alt-text-settings-download-model-button = Transferir +pdfjs-editor-alt-text-settings-downloading-model-button = A transferir… +pdfjs-editor-alt-text-settings-editor-title = Editor de texto alternativo +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostrar editor de texto alternativo imediatamente ao adicionar uma imagem +pdfjs-editor-alt-text-settings-show-dialog-description = Ajuda a garantir que todas as suas imagens tenham um texto alternativo. +pdfjs-editor-alt-text-settings-close-button = Fechar + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Destaque removido +pdfjs-editor-undo-bar-message-freetext = Texto removido +pdfjs-editor-undo-bar-message-ink = Desenho removido +pdfjs-editor-undo-bar-message-stamp = Imagem removida +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } anotação removida + *[other] { $count } anotações removidas + } +pdfjs-editor-undo-bar-undo-button = + .title = Anular +pdfjs-editor-undo-bar-undo-button-label = Anular +pdfjs-editor-undo-bar-close-button = + .title = Fechar +pdfjs-editor-undo-bar-close-button-label = Fechar diff --git a/public/assets/pdfjs/locale/rm/viewer.ftl b/public/assets/pdfjs/locale/rm/viewer.ftl new file mode 100755 index 0000000..76992da --- /dev/null +++ b/public/assets/pdfjs/locale/rm/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Pagina precedenta +pdfjs-previous-button-label = Enavos +pdfjs-next-button = + .title = Proxima pagina +pdfjs-next-button-label = Enavant +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Pagina +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = da { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } da { $pagesCount }) +pdfjs-zoom-out-button = + .title = Empitschnir +pdfjs-zoom-out-button-label = Empitschnir +pdfjs-zoom-in-button = + .title = Engrondir +pdfjs-zoom-in-button-label = Engrondir +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Midar en il modus da preschentaziun +pdfjs-presentation-mode-button-label = Modus da preschentaziun +pdfjs-open-file-button = + .title = Avrir datoteca +pdfjs-open-file-button-label = Avrir +pdfjs-print-button = + .title = Stampar +pdfjs-print-button-label = Stampar +pdfjs-save-button = + .title = Memorisar +pdfjs-save-button-label = Memorisar +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Telechargiar +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Telechargiar +pdfjs-bookmark-button = + .title = Pagina actuala (mussar l'URL da la pagina actuala) +pdfjs-bookmark-button-label = Pagina actuala + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Utensils +pdfjs-tools-button-label = Utensils +pdfjs-first-page-button = + .title = Siglir a l'emprima pagina +pdfjs-first-page-button-label = Siglir a l'emprima pagina +pdfjs-last-page-button = + .title = Siglir a la davosa pagina +pdfjs-last-page-button-label = Siglir a la davosa pagina +pdfjs-page-rotate-cw-button = + .title = Rotar en direcziun da l'ura +pdfjs-page-rotate-cw-button-label = Rotar en direcziun da l'ura +pdfjs-page-rotate-ccw-button = + .title = Rotar en direcziun cuntraria a l'ura +pdfjs-page-rotate-ccw-button-label = Rotar en direcziun cuntraria a l'ura +pdfjs-cursor-text-select-tool-button = + .title = Activar l'utensil per selecziunar text +pdfjs-cursor-text-select-tool-button-label = Utensil per selecziunar text +pdfjs-cursor-hand-tool-button = + .title = Activar l'utensil da maun +pdfjs-cursor-hand-tool-button-label = Utensil da maun +pdfjs-scroll-page-button = + .title = Utilisar la defilada per pagina +pdfjs-scroll-page-button-label = Defilada per pagina +pdfjs-scroll-vertical-button = + .title = Utilisar il defilar vertical +pdfjs-scroll-vertical-button-label = Defilar vertical +pdfjs-scroll-horizontal-button = + .title = Utilisar il defilar orizontal +pdfjs-scroll-horizontal-button-label = Defilar orizontal +pdfjs-scroll-wrapped-button = + .title = Utilisar il defilar en colonnas +pdfjs-scroll-wrapped-button-label = Defilar en colonnas +pdfjs-spread-none-button = + .title = Betg parallelisar las paginas +pdfjs-spread-none-button-label = Betg parallel +pdfjs-spread-odd-button = + .title = Parallelisar las paginas cun cumenzar cun paginas spèras +pdfjs-spread-odd-button-label = Parallel spèr +pdfjs-spread-even-button = + .title = Parallelisar las paginas cun cumenzar cun paginas pèras +pdfjs-spread-even-button-label = Parallel pèr + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Caracteristicas dal document… +pdfjs-document-properties-button-label = Caracteristicas dal document… +pdfjs-document-properties-file-name = Num da la datoteca: +pdfjs-document-properties-file-size = Grondezza da la datoteca: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Titel: +pdfjs-document-properties-author = Autur: +pdfjs-document-properties-subject = Tema: +pdfjs-document-properties-keywords = Chavazzins: +pdfjs-document-properties-creation-date = Data da creaziun: +pdfjs-document-properties-modification-date = Data da modificaziun: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date } { $time } +pdfjs-document-properties-creator = Creà da: +pdfjs-document-properties-producer = Creà il PDF cun: +pdfjs-document-properties-version = Versiun da PDF: +pdfjs-document-properties-page-count = Dumber da paginas: +pdfjs-document-properties-page-size = Grondezza da la pagina: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = vertical +pdfjs-document-properties-page-size-orientation-landscape = orizontal +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Fast Web View: +pdfjs-document-properties-linearized-yes = Gea +pdfjs-document-properties-linearized-no = Na +pdfjs-document-properties-close-button = Serrar + +## Print + +pdfjs-print-progress-message = Preparar il document per stampar… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Interrumper +pdfjs-printing-not-supported = Attenziun: Il stampar na funcziunescha anc betg dal tut en quest navigatur. +pdfjs-printing-not-ready = Attenziun: Il PDF n'è betg chargià cumplettamain per stampar. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Activar/deactivar la trav laterala +pdfjs-toggle-sidebar-notification-button = + .title = Activar/deactivar la trav laterala (il document cuntegna structura dal document/agiuntas/nivels) +pdfjs-toggle-sidebar-button-label = Activar/deactivar la trav laterala +pdfjs-document-outline-button = + .title = Mussar la structura dal document (cliccar duas giadas per extender/cumprimer tut ils elements) +pdfjs-document-outline-button-label = Structura dal document +pdfjs-attachments-button = + .title = Mussar agiuntas +pdfjs-attachments-button-label = Agiuntas +pdfjs-layers-button = + .title = Mussar ils nivels (cliccar dubel per restaurar il stadi da standard da tut ils nivels) +pdfjs-layers-button-label = Nivels +pdfjs-thumbs-button = + .title = Mussar las miniaturas +pdfjs-thumbs-button-label = Miniaturas +pdfjs-current-outline-item-button = + .title = Tschertgar l'element da structura actual +pdfjs-current-outline-item-button-label = Element da structura actual +pdfjs-findbar-button = + .title = Tschertgar en il document +pdfjs-findbar-button-label = Tschertgar +pdfjs-additional-layers = Nivels supplementars + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Pagina { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura da la pagina { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Tschertgar + .placeholder = Tschertgar en il document… +pdfjs-find-previous-button = + .title = Tschertgar la posiziun precedenta da l'expressiun +pdfjs-find-previous-button-label = Enavos +pdfjs-find-next-button = + .title = Tschertgar la proxima posiziun da l'expressiun +pdfjs-find-next-button-label = Enavant +pdfjs-find-highlight-checkbox = Relevar tuts +pdfjs-find-match-case-checkbox-label = Resguardar maiusclas/minusclas +pdfjs-find-match-diacritics-checkbox-label = Resguardar ils segns diacritics +pdfjs-find-entire-word-checkbox-label = Pleds entirs +pdfjs-find-reached-top = Il cumenzament dal document è cuntanschì, la tschertga cuntinuescha a la fin dal document +pdfjs-find-reached-bottom = La fin dal document è cuntanschì, la tschertga cuntinuescha al cumenzament dal document +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } dad { $total } correspundenza + *[other] { $current } da { $total } correspundenzas + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Dapli che { $limit } correspundenza + *[other] Dapli che { $limit } correspundenzas + } +pdfjs-find-not-found = Impussibel da chattar l'expressiun + +## Predefined zoom values + +pdfjs-page-scale-width = Ladezza da la pagina +pdfjs-page-scale-fit = Entira pagina +pdfjs-page-scale-auto = Zoom automatic +pdfjs-page-scale-actual = Grondezza actuala +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Pagina { $page } + +## Loading indicator messages + +pdfjs-loading-error = Ina errur è cumparida cun chargiar il PDF. +pdfjs-invalid-file-error = Datoteca PDF nunvalida u donnegiada. +pdfjs-missing-file-error = Datoteca PDF manconta. +pdfjs-unexpected-response-error = Resposta nunspetgada dal server. +pdfjs-rendering-error = Ina errur è cumparida cun visualisar questa pagina. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Annotaziun da { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Endatescha il pled-clav per avrir questa datoteca da PDF. +pdfjs-password-invalid = Pled-clav nunvalid. Emprova anc ina giada. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Interrumper +pdfjs-web-fonts-disabled = Scrittiras dal web èn deactivadas: impussibel dad utilisar las scrittiras integradas en il PDF. + +## Editing + +pdfjs-editor-free-text-button = + .title = Text +pdfjs-editor-free-text-button-label = Text +pdfjs-editor-ink-button = + .title = Dissegnar +pdfjs-editor-ink-button-label = Dissegnar +pdfjs-editor-stamp-button = + .title = Agiuntar u modifitgar maletgs +pdfjs-editor-stamp-button-label = Agiuntar u modifitgar maletgs +pdfjs-editor-highlight-button = + .title = Marcar +pdfjs-editor-highlight-button-label = Marcar +pdfjs-highlight-floating-button1 = + .title = Marcar + .aria-label = Marcar +pdfjs-highlight-floating-button-label = Marcar + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Allontanar il dissegn +pdfjs-editor-remove-freetext-button = + .title = Allontanar il text +pdfjs-editor-remove-stamp-button = + .title = Allontanar la grafica +pdfjs-editor-remove-highlight-button = + .title = Allontanar l'emfasa + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Colur +pdfjs-editor-free-text-size-input = Grondezza +pdfjs-editor-ink-color-input = Colur +pdfjs-editor-ink-thickness-input = Grossezza +pdfjs-editor-ink-opacity-input = Opacitad +pdfjs-editor-stamp-add-image-button = + .title = Agiuntar in maletg +pdfjs-editor-stamp-add-image-button-label = Agiuntar in maletg +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Grossezza +pdfjs-editor-free-highlight-thickness-title = + .title = Midar la grossezza cun relevar elements betg textuals +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Editur da text + .default-content = Cumenza a tippar… +pdfjs-free-text = + .aria-label = Editur da text +pdfjs-free-text-default-content = Cumenzar a tippar… +pdfjs-ink = + .aria-label = Editur dissegn +pdfjs-ink-canvas = + .aria-label = Maletg creà da l'utilisader + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Text alternativ +pdfjs-editor-alt-text-edit-button = + .aria-label = Modifitgar il text alternativ +pdfjs-editor-alt-text-edit-button-label = Modifitgar il text alternativ +pdfjs-editor-alt-text-dialog-label = Tscherner ina opziun +pdfjs-editor-alt-text-dialog-description = Il text alternativ (alt text) gida en cas che persunas na vesan betg il maletg u sch'i na reussescha betg d'al chargiar. +pdfjs-editor-alt-text-add-description-label = Agiuntar ina descripziun +pdfjs-editor-alt-text-add-description-description = Scriva idealmain 1-2 frasas che descrivan l'object, la situaziun u las acziuns. +pdfjs-editor-alt-text-mark-decorative-label = Marcar sco decorativ +pdfjs-editor-alt-text-mark-decorative-description = Quai vegn duvrà per maletgs ornamentals, sco urs u filigranas. +pdfjs-editor-alt-text-cancel-button = Interrumper +pdfjs-editor-alt-text-save-button = Memorisar +pdfjs-editor-alt-text-decorative-tooltip = Marcà sco decorativ +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Per exempel: «In um giuven sesa a maisa per mangiar in past» +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Text alternativ + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Chantun sura a sanestra — redimensiunar +pdfjs-editor-resizer-label-top-middle = Sura amez — redimensiunar +pdfjs-editor-resizer-label-top-right = Chantun sura a dretga — redimensiunar +pdfjs-editor-resizer-label-middle-right = Da vart dretga amez — redimensiunar +pdfjs-editor-resizer-label-bottom-right = Chantun sut a dretga — redimensiunar +pdfjs-editor-resizer-label-bottom-middle = Sutvart amez — redimensiunar +pdfjs-editor-resizer-label-bottom-left = Chantun sut a sanestra — redimensiunar +pdfjs-editor-resizer-label-middle-left = Vart sanestra amez — redimensiunar +pdfjs-editor-resizer-top-left = + .aria-label = Chantun sura a sanestra — redimensiunar +pdfjs-editor-resizer-top-middle = + .aria-label = Sura amez — redimensiunar +pdfjs-editor-resizer-top-right = + .aria-label = Chantun sura a dretga — redimensiunar +pdfjs-editor-resizer-middle-right = + .aria-label = Da vart dretga amez — redimensiunar +pdfjs-editor-resizer-bottom-right = + .aria-label = Chantun sut a dretga — redimensiunar +pdfjs-editor-resizer-bottom-middle = + .aria-label = Sutvart amez — redimensiunar +pdfjs-editor-resizer-bottom-left = + .aria-label = Chantun sut a sanestra — redimensiunar +pdfjs-editor-resizer-middle-left = + .aria-label = Vart sanestra amez — redimensiunar + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Colur per l'emfasa +pdfjs-editor-colorpicker-button = + .title = Midar la colur +pdfjs-editor-colorpicker-dropdown = + .aria-label = Colurs disponiblas +pdfjs-editor-colorpicker-yellow = + .title = Mellen +pdfjs-editor-colorpicker-green = + .title = Verd +pdfjs-editor-colorpicker-blue = + .title = Blau +pdfjs-editor-colorpicker-pink = + .title = Rosa +pdfjs-editor-colorpicker-red = + .title = Cotschen + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Mussar tut +pdfjs-editor-highlight-show-all-button = + .title = Mussar tut + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Modifitgar il text alternativ (descripziun dal maletg) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Agiuntar in text alternativ (descripziun dal maletg) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Scriva qua tia descripziun… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Curta descripziun per persunas che na vesan betg il maletg u per cass en ils quals il maletg na vegn betg chargià. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Quest text alternativ è vegnì creà automaticamain ed è eventualmain nunprecis. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Ulteriuras infurmaziuns +pdfjs-editor-new-alt-text-create-automatically-button-label = Crear automaticamain il text alternativ +pdfjs-editor-new-alt-text-not-now-button = Betg ussa +pdfjs-editor-new-alt-text-error-title = I n’è betg reussì da crear automaticamain il text alternativ +pdfjs-editor-new-alt-text-error-description = Scriva per plaschair tes agen text alternativ u emprova pli tard anc ina giada. +pdfjs-editor-new-alt-text-error-close-button = Serrar +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Telechargiar il model IA da text alternativ ({ $downloadedSize } da { $totalSize } MB) + .aria-valuetext = Telechargiar il model IA da text alternativ ({ $downloadedSize } da { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Agiuntà text alternativ +pdfjs-editor-new-alt-text-added-button-label = Text alternativ agiuntà +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Text alternativ manca +pdfjs-editor-new-alt-text-missing-button-label = Text alternativ manca +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Repassar il text alternativ +pdfjs-editor-new-alt-text-to-review-button-label = Repassar il text alternativ +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creà automaticamain: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Parameters dal text alternativ da maletgs +pdfjs-image-alt-text-settings-button-label = Parameters dal text alternativ da maletgs +pdfjs-editor-alt-text-settings-dialog-label = Parameters dal text alternativ da maletgs +pdfjs-editor-alt-text-settings-automatic-title = Text alternativ automatic +pdfjs-editor-alt-text-settings-create-model-button-label = Crear automaticamain text alternativ +pdfjs-editor-alt-text-settings-create-model-description = Propona descripziuns per gidar a persunas che na vesan betg il maletg u per cass en ils quals il maletg na vegn betg chargià. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model IA da text alternativ ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Vegn exequì localmain sin tes apparat per che tias datas restian privatas. Necessari per text alternativ automatic. +pdfjs-editor-alt-text-settings-delete-model-button = Stizzar +pdfjs-editor-alt-text-settings-download-model-button = Telechargiar +pdfjs-editor-alt-text-settings-downloading-model-button = Telechargiar… +pdfjs-editor-alt-text-settings-editor-title = Editur per text alternativ +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mussar l’editur per text alternativ directamain cun agiuntar in maletg +pdfjs-editor-alt-text-settings-show-dialog-description = Ta gida a garantir che tut tes maletgs hajan in text alternativ. +pdfjs-editor-alt-text-settings-close-button = Serrar + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Allontanà la marcaziun +pdfjs-editor-undo-bar-message-freetext = Allontanà il text +pdfjs-editor-undo-bar-message-ink = Allontanà il dissegn +pdfjs-editor-undo-bar-message-stamp = Allontanà il maletg +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } annotaziun allontanada + *[other] { $count } annotaziuns allontanadas + } +pdfjs-editor-undo-bar-undo-button = + .title = Revocar +pdfjs-editor-undo-bar-undo-button-label = Revocar +pdfjs-editor-undo-bar-close-button = + .title = Serrar +pdfjs-editor-undo-bar-close-button-label = Serrar diff --git a/public/assets/pdfjs/locale/ro/viewer.ftl b/public/assets/pdfjs/locale/ro/viewer.ftl new file mode 100755 index 0000000..7c6f0b6 --- /dev/null +++ b/public/assets/pdfjs/locale/ro/viewer.ftl @@ -0,0 +1,251 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Pagina precedentă +pdfjs-previous-button-label = ÃŽnapoi +pdfjs-next-button = + .title = Pagina următoare +pdfjs-next-button-label = ÃŽnainte +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Pagina +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = din { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } din { $pagesCount }) +pdfjs-zoom-out-button = + .title = MicÈ™orează +pdfjs-zoom-out-button-label = MicÈ™orează +pdfjs-zoom-in-button = + .title = MăreÈ™te +pdfjs-zoom-in-button-label = MăreÈ™te +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Comută la modul de prezentare +pdfjs-presentation-mode-button-label = Mod de prezentare +pdfjs-open-file-button = + .title = Deschide un fiÈ™ier +pdfjs-open-file-button-label = Deschide +pdfjs-print-button = + .title = TipăreÈ™te +pdfjs-print-button-label = TipăreÈ™te + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Instrumente +pdfjs-tools-button-label = Instrumente +pdfjs-first-page-button = + .title = Mergi la prima pagină +pdfjs-first-page-button-label = Mergi la prima pagină +pdfjs-last-page-button = + .title = Mergi la ultima pagină +pdfjs-last-page-button-label = Mergi la ultima pagină +pdfjs-page-rotate-cw-button = + .title = RoteÈ™te în sensul acelor de ceas +pdfjs-page-rotate-cw-button-label = RoteÈ™te în sensul acelor de ceas +pdfjs-page-rotate-ccw-button = + .title = RoteÈ™te în sens invers al acelor de ceas +pdfjs-page-rotate-ccw-button-label = RoteÈ™te în sens invers al acelor de ceas +pdfjs-cursor-text-select-tool-button = + .title = Activează instrumentul de selecÈ›ie a textului +pdfjs-cursor-text-select-tool-button-label = Instrumentul de selecÈ›ie a textului +pdfjs-cursor-hand-tool-button = + .title = Activează instrumentul mână +pdfjs-cursor-hand-tool-button-label = Unealta mână +pdfjs-scroll-vertical-button = + .title = FoloseÈ™te derularea verticală +pdfjs-scroll-vertical-button-label = Derulare verticală +pdfjs-scroll-horizontal-button = + .title = FoloseÈ™te derularea orizontală +pdfjs-scroll-horizontal-button-label = Derulare orizontală +pdfjs-scroll-wrapped-button = + .title = FoloseÈ™te derularea încadrată +pdfjs-scroll-wrapped-button-label = Derulare încadrată +pdfjs-spread-none-button = + .title = Nu uni paginile broÈ™ate +pdfjs-spread-none-button-label = Fără pagini broÈ™ate +pdfjs-spread-odd-button = + .title = UneÈ™te paginile broÈ™ate începând cu cele impare +pdfjs-spread-odd-button-label = BroÈ™are pagini impare +pdfjs-spread-even-button = + .title = UneÈ™te paginile broÈ™ate începând cu cele pare +pdfjs-spread-even-button-label = BroÈ™are pagini pare + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Proprietățile documentului… +pdfjs-document-properties-button-label = Proprietățile documentului… +pdfjs-document-properties-file-name = Numele fiÈ™ierului: +pdfjs-document-properties-file-size = Mărimea fiÈ™ierului: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } byÈ›i) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } byÈ›i) +pdfjs-document-properties-title = Titlu: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Subiect: +pdfjs-document-properties-keywords = Cuvinte cheie: +pdfjs-document-properties-creation-date = Data creării: +pdfjs-document-properties-modification-date = Data modificării: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Autor: +pdfjs-document-properties-producer = Producător PDF: +pdfjs-document-properties-version = Versiune PDF: +pdfjs-document-properties-page-count = Număr de pagini: +pdfjs-document-properties-page-size = Mărimea paginii: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = verticală +pdfjs-document-properties-page-size-orientation-landscape = orizontală +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Literă +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Vizualizare web rapidă: +pdfjs-document-properties-linearized-yes = Da +pdfjs-document-properties-linearized-no = Nu +pdfjs-document-properties-close-button = ÃŽnchide + +## Print + +pdfjs-print-progress-message = Se pregăteÈ™te documentul pentru tipărire… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Renunță +pdfjs-printing-not-supported = Avertisment: Tipărirea nu este suportată în totalitate de acest browser. +pdfjs-printing-not-ready = Avertisment: PDF-ul nu este încărcat complet pentru tipărire. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Comută bara laterală +pdfjs-toggle-sidebar-button-label = Comută bara laterală +pdfjs-document-outline-button = + .title = AfiÈ™ează schiÈ›a documentului (dublu-clic pentru a extinde/restrânge toate elementele) +pdfjs-document-outline-button-label = SchiÈ›a documentului +pdfjs-attachments-button = + .title = AfiÈ™ează ataÈ™amentele +pdfjs-attachments-button-label = AtaÈ™amente +pdfjs-thumbs-button = + .title = AfiÈ™ează miniaturi +pdfjs-thumbs-button-label = Miniaturi +pdfjs-findbar-button = + .title = Caută în document +pdfjs-findbar-button-label = Caută + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Pagina { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura paginii { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Caută + .placeholder = Caută în document… +pdfjs-find-previous-button = + .title = Mergi la apariÈ›ia anterioară a textului +pdfjs-find-previous-button-label = ÃŽnapoi +pdfjs-find-next-button = + .title = Mergi la apariÈ›ia următoare a textului +pdfjs-find-next-button-label = ÃŽnainte +pdfjs-find-highlight-checkbox = EvidenÈ›iază toate apariÈ›iile +pdfjs-find-match-case-checkbox-label = Èšine cont de majuscule È™i minuscule +pdfjs-find-entire-word-checkbox-label = Cuvinte întregi +pdfjs-find-reached-top = Am ajuns la începutul documentului, continuă de la sfârÈ™it +pdfjs-find-reached-bottom = Am ajuns la sfârÈ™itul documentului, continuă de la început +pdfjs-find-not-found = Nu s-a găsit textul + +## Predefined zoom values + +pdfjs-page-scale-width = Lățime pagină +pdfjs-page-scale-fit = Potrivire la pagină +pdfjs-page-scale-auto = Zoom automat +pdfjs-page-scale-actual = Mărime reală +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = A intervenit o eroare la încărcarea PDF-ului. +pdfjs-invalid-file-error = FiÈ™ier PDF nevalid sau corupt. +pdfjs-missing-file-error = FiÈ™ier PDF lipsă. +pdfjs-unexpected-response-error = Răspuns neaÈ™teptat de la server. +pdfjs-rendering-error = A intervenit o eroare la randarea paginii. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Adnotare { $type }] + +## Password + +pdfjs-password-label = Introdu parola pentru a deschide acest fiÈ™ier PDF. +pdfjs-password-invalid = Parolă nevalidă. Te rugăm să încerci din nou. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Renunță +pdfjs-web-fonts-disabled = Fonturile web sunt dezactivate: nu se pot folosi fonturile PDF încorporate. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/ru/viewer.ftl b/public/assets/pdfjs/locale/ru/viewer.ftl new file mode 100755 index 0000000..81c2f41 --- /dev/null +++ b/public/assets/pdfjs/locale/ru/viewer.ftl @@ -0,0 +1,518 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = ÐŸÑ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰Ð°Ñ Ñтраница +pdfjs-previous-button-label = ÐŸÑ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰Ð°Ñ +pdfjs-next-button = + .title = Ð¡Ð»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ñтраница +pdfjs-next-button-label = Ð¡Ð»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Страница +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = из { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } из { $pagesCount }) +pdfjs-zoom-out-button = + .title = Уменьшить +pdfjs-zoom-out-button-label = Уменьшить +pdfjs-zoom-in-button = + .title = Увеличить +pdfjs-zoom-in-button-label = Увеличить +pdfjs-zoom-select = + .title = МаÑштаб +pdfjs-presentation-mode-button = + .title = Перейти в режим презентации +pdfjs-presentation-mode-button-label = Режим презентации +pdfjs-open-file-button = + .title = Открыть файл +pdfjs-open-file-button-label = Открыть +pdfjs-print-button = + .title = Печать +pdfjs-print-button-label = Печать +pdfjs-save-button = + .title = Сохранить +pdfjs-save-button-label = Сохранить +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Загрузить +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Загрузить +pdfjs-bookmark-button = + .title = Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ñтраница (проÑмотр URL-адреÑа Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹ Ñтраницы) +pdfjs-bookmark-button-label = Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ñтраница + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = ИнÑтрументы +pdfjs-tools-button-label = ИнÑтрументы +pdfjs-first-page-button = + .title = Перейти на первую Ñтраницу +pdfjs-first-page-button-label = Перейти на первую Ñтраницу +pdfjs-last-page-button = + .title = Перейти на поÑледнюю Ñтраницу +pdfjs-last-page-button-label = Перейти на поÑледнюю Ñтраницу +pdfjs-page-rotate-cw-button = + .title = Повернуть по чаÑовой Ñтрелке +pdfjs-page-rotate-cw-button-label = Повернуть по чаÑовой Ñтрелке +pdfjs-page-rotate-ccw-button = + .title = Повернуть против чаÑовой Ñтрелки +pdfjs-page-rotate-ccw-button-label = Повернуть против чаÑовой Ñтрелки +pdfjs-cursor-text-select-tool-button = + .title = Включить ИнÑтрумент «Выделение текÑта» +pdfjs-cursor-text-select-tool-button-label = ИнÑтрумент «Выделение текÑта» +pdfjs-cursor-hand-tool-button = + .title = Включить ИнÑтрумент «Рука» +pdfjs-cursor-hand-tool-button-label = ИнÑтрумент «Рука» +pdfjs-scroll-page-button = + .title = ИÑпользовать прокрутку Ñтраниц +pdfjs-scroll-page-button-label = Прокрутка Ñтраниц +pdfjs-scroll-vertical-button = + .title = ИÑпользовать вертикальную прокрутку +pdfjs-scroll-vertical-button-label = Ð’ÐµÑ€Ñ‚Ð¸ÐºÐ°Ð»ÑŒÐ½Ð°Ñ Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‚ÐºÐ° +pdfjs-scroll-horizontal-button = + .title = ИÑпользовать горизонтальную прокрутку +pdfjs-scroll-horizontal-button-label = Ð“Ð¾Ñ€Ð¸Ð·Ð¾Ð½Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‚ÐºÐ° +pdfjs-scroll-wrapped-button = + .title = ИÑпользовать маÑштабируемую прокрутку +pdfjs-scroll-wrapped-button-label = МаÑÑˆÑ‚Ð°Ð±Ð¸Ñ€ÑƒÐµÐ¼Ð°Ñ Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‚ÐºÐ° +pdfjs-spread-none-button = + .title = Ðе иÑпользовать режим разворотов Ñтраниц +pdfjs-spread-none-button-label = Без разворотов Ñтраниц +pdfjs-spread-odd-button = + .title = Развороты начинаютÑÑ Ñ Ð½ÐµÑ‡Ñ‘Ñ‚Ð½Ñ‹Ñ… номеров Ñтраниц +pdfjs-spread-odd-button-label = Ðечётные Ñтраницы Ñлева +pdfjs-spread-even-button = + .title = Развороты начинаютÑÑ Ñ Ñ‡Ñ‘Ñ‚Ð½Ñ‹Ñ… номеров Ñтраниц +pdfjs-spread-even-button-label = Чётные Ñтраницы Ñлева + +## Document properties dialog + +pdfjs-document-properties-button = + .title = СвойÑтва документа… +pdfjs-document-properties-button-label = СвойÑтва документа… +pdfjs-document-properties-file-name = Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°: +pdfjs-document-properties-file-size = Размер файла: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } КБ ({ $b } байт) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } МБ ({ $b } байт) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } КБ ({ $size_b } байт) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } МБ ({ $size_b } байт) +pdfjs-document-properties-title = Заголовок: +pdfjs-document-properties-author = Ðвтор: +pdfjs-document-properties-subject = Тема: +pdfjs-document-properties-keywords = Ключевые Ñлова: +pdfjs-document-properties-creation-date = Дата ÑозданиÑ: +pdfjs-document-properties-modification-date = Дата изменениÑ: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Приложение: +pdfjs-document-properties-producer = Производитель PDF: +pdfjs-document-properties-version = ВерÑÐ¸Ñ PDF: +pdfjs-document-properties-page-count = ЧиÑло Ñтраниц: +pdfjs-document-properties-page-size = Размер Ñтраницы: +pdfjs-document-properties-page-size-unit-inches = дюймов +pdfjs-document-properties-page-size-unit-millimeters = мм +pdfjs-document-properties-page-size-orientation-portrait = ÐºÐ½Ð¸Ð¶Ð½Ð°Ñ +pdfjs-document-properties-page-size-orientation-landscape = Ð°Ð»ÑŒÐ±Ð¾Ð¼Ð½Ð°Ñ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = БыÑтрый проÑмотр в Web: +pdfjs-document-properties-linearized-yes = Да +pdfjs-document-properties-linearized-no = Ðет +pdfjs-document-properties-close-button = Закрыть + +## Print + +pdfjs-print-progress-message = Подготовка документа к печати… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Отмена +pdfjs-printing-not-supported = Предупреждение: Ð’ Ñтом браузере не полноÑтью поддерживаетÑÑ Ð¿ÐµÑ‡Ð°Ñ‚ÑŒ. +pdfjs-printing-not-ready = Предупреждение: PDF не полноÑтью загружен Ð´Ð»Ñ Ð¿ÐµÑ‡Ð°Ñ‚Ð¸. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Показать/Ñкрыть боковую панель +pdfjs-toggle-sidebar-notification-button = + .title = Показать/Ñкрыть боковую панель (документ имеет Ñодержание/вложениÑ/Ñлои) +pdfjs-toggle-sidebar-button-label = Показать/Ñкрыть боковую панель +pdfjs-document-outline-button = + .title = Показать Ñодержание документа (двойной щелчок, чтобы развернуть/Ñвернуть вÑе Ñлементы) +pdfjs-document-outline-button-label = Содержание документа +pdfjs-attachments-button = + .title = Показать Ð²Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ +pdfjs-attachments-button-label = Ð’Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ +pdfjs-layers-button = + .title = Показать Ñлои (дважды щёлкните, чтобы ÑброÑить вÑе Ñлои к ÑоÑтоÑнию по умолчанию) +pdfjs-layers-button-label = Слои +pdfjs-thumbs-button = + .title = Показать миниатюры +pdfjs-thumbs-button-label = Миниатюры +pdfjs-current-outline-item-button = + .title = Ðайти текущий Ñлемент Ñтруктуры +pdfjs-current-outline-item-button-label = Текущий Ñлемент Ñтруктуры +pdfjs-findbar-button = + .title = Ðайти в документе +pdfjs-findbar-button-label = Ðайти +pdfjs-additional-layers = Дополнительные Ñлои + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Страница { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Миниатюра Ñтраницы { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Ðайти + .placeholder = Ðайти в документе… +pdfjs-find-previous-button = + .title = Ðайти предыдущее вхождение фразы в текÑÑ‚ +pdfjs-find-previous-button-label = Ðазад +pdfjs-find-next-button = + .title = Ðайти Ñледующее вхождение фразы в текÑÑ‚ +pdfjs-find-next-button-label = Далее +pdfjs-find-highlight-checkbox = ПодÑветить вÑе +pdfjs-find-match-case-checkbox-label = С учётом региÑтра +pdfjs-find-match-diacritics-checkbox-label = С учётом диакритичеÑких знаков +pdfjs-find-entire-word-checkbox-label = Слова целиком +pdfjs-find-reached-top = ДоÑтигнут верх документа, продолжено Ñнизу +pdfjs-find-reached-bottom = ДоÑтигнут конец документа, продолжено Ñверху +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } из { $total } ÑÐ¾Ð²Ð¿Ð°Ð´ÐµÐ½Ð¸Ñ + [few] { $current } из { $total } Ñовпадений + *[many] { $current } из { $total } Ñовпадений + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Более { $limit } ÑÐ¾Ð²Ð¿Ð°Ð´ÐµÐ½Ð¸Ñ + [few] Более { $limit } Ñовпадений + *[many] Более { $limit } Ñовпадений + } +pdfjs-find-not-found = Фраза не найдена + +## Predefined zoom values + +pdfjs-page-scale-width = По ширине Ñтраницы +pdfjs-page-scale-fit = По размеру Ñтраницы +pdfjs-page-scale-auto = ÐвтоматичеÑки +pdfjs-page-scale-actual = Реальный размер +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Страница { $page } + +## Loading indicator messages + +pdfjs-loading-error = При загрузке PDF произошла ошибка. +pdfjs-invalid-file-error = Ðекорректный или повреждённый PDF-файл. +pdfjs-missing-file-error = PDF-файл отÑутÑтвует. +pdfjs-unexpected-response-error = Ðеожиданный ответ Ñервера. +pdfjs-rendering-error = При Ñоздании Ñтраницы произошла ошибка. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [ÐÐ½Ð½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Введите пароль, чтобы открыть Ñтот PDF-файл. +pdfjs-password-invalid = Ðеверный пароль. ПожалуйÑта, попробуйте Ñнова. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Отмена +pdfjs-web-fonts-disabled = Веб-шрифты отключены: не удалоÑÑŒ задейÑтвовать вÑтроенные PDF-шрифты. + +## Editing + +pdfjs-editor-free-text-button = + .title = ТекÑÑ‚ +pdfjs-editor-free-text-button-label = ТекÑÑ‚ +pdfjs-editor-ink-button = + .title = РиÑовать +pdfjs-editor-ink-button-label = РиÑовать +pdfjs-editor-stamp-button = + .title = Добавить или изменить Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ +pdfjs-editor-stamp-button-label = Добавить или изменить Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ +pdfjs-editor-highlight-button = + .title = Выделение +pdfjs-editor-highlight-button-label = Выделение +pdfjs-highlight-floating-button1 = + .title = Выделение + .aria-label = Выделение +pdfjs-highlight-floating-button-label = Выделение + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Удалить риÑунок +pdfjs-editor-remove-freetext-button = + .title = Удалить текÑÑ‚ +pdfjs-editor-remove-stamp-button = + .title = Удалить изображение +pdfjs-editor-remove-highlight-button = + .title = Удалить выделение + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Цвет +pdfjs-editor-free-text-size-input = Размер +pdfjs-editor-ink-color-input = Цвет +pdfjs-editor-ink-thickness-input = Толщина +pdfjs-editor-ink-opacity-input = ПрозрачноÑть +pdfjs-editor-stamp-add-image-button = + .title = Добавить изображение +pdfjs-editor-stamp-add-image-button-label = Добавить изображение +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Толщина +pdfjs-editor-free-highlight-thickness-title = + .title = Изменить толщину при выделении Ñлементов, кроме текÑта +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = ТекÑтовый редактор + .default-content = Ðачните ввод... +pdfjs-free-text = + .aria-label = ТекÑтовый редактор +pdfjs-free-text-default-content = Ðачните вводить… +pdfjs-ink = + .aria-label = Редактор риÑÐ¾Ð²Ð°Ð½Ð¸Ñ +pdfjs-ink-canvas = + .aria-label = Созданное пользователем изображение + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Ðльтернативный текÑÑ‚ +pdfjs-editor-alt-text-edit-button = + .aria-label = Изменить альтернативный текÑÑ‚ +pdfjs-editor-alt-text-edit-button-label = Изменить альтернативный текÑÑ‚ +pdfjs-editor-alt-text-dialog-label = Выберите вариант +pdfjs-editor-alt-text-dialog-description = Ðльтернативный текÑÑ‚ помогает, когда люди не видÑÑ‚ изображение или оно не загружаетÑÑ. +pdfjs-editor-alt-text-add-description-label = Добавить опиÑание +pdfjs-editor-alt-text-add-description-description = СтарайтеÑÑŒ ÑоÑтавлÑть 1–2 предложениÑ, опиÑывающих предмет, обÑтановку или дейÑтвиÑ. +pdfjs-editor-alt-text-mark-decorative-label = Отметить как декоративное +pdfjs-editor-alt-text-mark-decorative-description = ИÑпользуетÑÑ Ð´Ð»Ñ Ð´ÐµÐºÐ¾Ñ€Ð°Ñ‚Ð¸Ð²Ð½Ñ‹Ñ… изображений, таких как рамки или водÑные знаки. +pdfjs-editor-alt-text-cancel-button = Отменить +pdfjs-editor-alt-text-save-button = Сохранить +pdfjs-editor-alt-text-decorative-tooltip = Помечен как декоративный +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Ðапример: «Молодой человек ÑадитÑÑ Ð·Ð° Ñтол, чтобы поеÑть» +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Ðльтернативный текÑÑ‚ + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Левый верхний угол — изменить размер +pdfjs-editor-resizer-label-top-middle = Вверху поÑередине — изменить размер +pdfjs-editor-resizer-label-top-right = Верхний правый угол — изменить размер +pdfjs-editor-resizer-label-middle-right = Ð’ центре Ñправа — изменить размер +pdfjs-editor-resizer-label-bottom-right = Ðижний правый угол — изменить размер +pdfjs-editor-resizer-label-bottom-middle = Внизу поÑередине — изменить размер +pdfjs-editor-resizer-label-bottom-left = Ðижний левый угол — изменить размер +pdfjs-editor-resizer-label-middle-left = Ð’ центре Ñлева — изменить размер +pdfjs-editor-resizer-top-left = + .aria-label = Левый верхний угол — изменить размер +pdfjs-editor-resizer-top-middle = + .aria-label = Вверху поÑередине — изменить размер +pdfjs-editor-resizer-top-right = + .aria-label = Верхний правый угол — изменить размер +pdfjs-editor-resizer-middle-right = + .aria-label = Ð’ центре Ñправа — изменить размер +pdfjs-editor-resizer-bottom-right = + .aria-label = Ðижний правый угол — изменить размер +pdfjs-editor-resizer-bottom-middle = + .aria-label = Внизу поÑередине — изменить размер +pdfjs-editor-resizer-bottom-left = + .aria-label = Ðижний левый угол — изменить размер +pdfjs-editor-resizer-middle-left = + .aria-label = Ð’ центре Ñлева — изменить размер + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Цвет Ð²Ñ‹Ð´ÐµÐ»ÐµÐ½Ð¸Ñ +pdfjs-editor-colorpicker-button = + .title = Изменить цвет +pdfjs-editor-colorpicker-dropdown = + .aria-label = Выбор цвета +pdfjs-editor-colorpicker-yellow = + .title = Жёлтый +pdfjs-editor-colorpicker-green = + .title = Зелёный +pdfjs-editor-colorpicker-blue = + .title = Синий +pdfjs-editor-colorpicker-pink = + .title = Розовый +pdfjs-editor-colorpicker-red = + .title = КраÑный + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Показать вÑе +pdfjs-editor-highlight-show-all-button = + .title = Показать вÑе + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Изменить альтернативный текÑÑ‚ (опиÑание изображениÑ) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Добавить альтернативный текÑÑ‚ (опиÑание изображениÑ) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Ðапишите здеÑÑŒ Ñвоё опиÑание… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Короткое опиÑание Ð´Ð»Ñ Ð»ÑŽÐ´ÐµÐ¹, которые не видÑÑ‚ изображение, или еÑли изображение не загружаетÑÑ. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Этот альтернативный текÑÑ‚ был Ñоздан автоматичеÑки и может быть неточным. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Подробнее +pdfjs-editor-new-alt-text-create-automatically-button-label = ÐвтоматичеÑки Ñоздавать альтернативный текÑÑ‚ +pdfjs-editor-new-alt-text-not-now-button = Ðе ÑÐµÐ¹Ñ‡Ð°Ñ +pdfjs-editor-new-alt-text-error-title = Ðе удалоÑÑŒ автоматичеÑки Ñоздать альтернативный текÑÑ‚ +pdfjs-editor-new-alt-text-error-description = ПожалуйÑта, напишите Ñвой альтернативный текÑÑ‚ или попробуйте ещё раз позже. +pdfjs-editor-new-alt-text-error-close-button = Закрыть +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Загрузка модели ИИ Ð´Ð»Ñ Ð°Ð»ÑŒÑ‚ÐµÑ€Ð½Ð°Ñ‚Ð¸Ð²Ð½Ð¾Ð³Ð¾ текÑта ({ $downloadedSize } из { $totalSize } МБ) + .aria-valuetext = Загрузка модели ИИ Ð´Ð»Ñ Ð°Ð»ÑŒÑ‚ÐµÑ€Ð½Ð°Ñ‚Ð¸Ð²Ð½Ð¾Ð³Ð¾ текÑта ({ $downloadedSize } из { $totalSize } МБ) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Ðльтернативный текÑÑ‚ добавлен +pdfjs-editor-new-alt-text-added-button-label = Ðльтернативный текÑÑ‚ добавлен +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = ОтÑутÑтвует альтернативный текÑÑ‚ +pdfjs-editor-new-alt-text-missing-button-label = ОтÑутÑтвует альтернативный текÑÑ‚ +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Оценить альтернативный текÑÑ‚ +pdfjs-editor-new-alt-text-to-review-button-label = Оценить альтернативный текÑÑ‚ +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Создано автоматичеÑки: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = ÐаÑтройки альтернативного текÑта Ð´Ð»Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ +pdfjs-image-alt-text-settings-button-label = ÐаÑтройки альтернативного текÑта Ð´Ð»Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ +pdfjs-editor-alt-text-settings-dialog-label = ÐаÑтройки альтернативного текÑта Ð´Ð»Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ +pdfjs-editor-alt-text-settings-automatic-title = ÐвтоматичеÑкий альтернативный текÑÑ‚ +pdfjs-editor-alt-text-settings-create-model-button-label = ÐвтоматичеÑки Ñоздавать альтернативный текÑÑ‚ +pdfjs-editor-alt-text-settings-create-model-description = Предлагает опиÑаниÑ, чтобы помочь людÑм, которые не видÑÑ‚ изображение, или еÑли изображение не загружаетÑÑ. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = ИИ-модель альтернативного текÑта ({ $totalSize } МБ) +pdfjs-editor-alt-text-settings-ai-model-description = ЗапуÑкаетÑÑ Ð»Ð¾ÐºÐ°Ð»ÑŒÐ½Ð¾ на вашем уÑтройÑтве, поÑтому ваши данные оÑтаютÑÑ ÐºÐ¾Ð½Ñ„Ð¸Ð´ÐµÐ½Ñ†Ð¸Ð°Ð»ÑŒÐ½Ñ‹Ð¼Ð¸. ТребуетÑÑ Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкого альтернативного текÑта. +pdfjs-editor-alt-text-settings-delete-model-button = Удалить +pdfjs-editor-alt-text-settings-download-model-button = Загрузить +pdfjs-editor-alt-text-settings-downloading-model-button = Загрузка… +pdfjs-editor-alt-text-settings-editor-title = Редактор альтернативного текÑта +pdfjs-editor-alt-text-settings-show-dialog-button-label = Сразу показывать редактор альтернативного текÑта при добавлении Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ +pdfjs-editor-alt-text-settings-show-dialog-description = Помогает вам убедитьÑÑ, что вÑе ваши Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¸Ð¼ÐµÑŽÑ‚ альтернативный текÑÑ‚. +pdfjs-editor-alt-text-settings-close-button = Закрыть + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Выделение удалено +pdfjs-editor-undo-bar-message-freetext = ТекÑÑ‚ удалён +pdfjs-editor-undo-bar-message-ink = РиÑунок удалён +pdfjs-editor-undo-bar-message-stamp = Изображение удалено +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } Ð°Ð½Ð½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð° + [few] { $count } аннотации удалены + *[many] { $count } аннотаций удалены + } +pdfjs-editor-undo-bar-undo-button = + .title = Отменить +pdfjs-editor-undo-bar-undo-button-label = Отменить +pdfjs-editor-undo-bar-close-button = + .title = Закрыть +pdfjs-editor-undo-bar-close-button-label = Закрыть diff --git a/public/assets/pdfjs/locale/sat/viewer.ftl b/public/assets/pdfjs/locale/sat/viewer.ftl new file mode 100755 index 0000000..2fbbc12 --- /dev/null +++ b/public/assets/pdfjs/locale/sat/viewer.ftl @@ -0,0 +1,325 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = ᱢᱟᱲᱟᱠᱥᱟᱦᱴᱟ +pdfjs-previous-button-label = ᱢᱟᱲᱟá±á±Ÿá±œ +pdfjs-next-button = + .title = ᱤᱱᱟᱹ ᱛᱟᱭᱚᱢ ᱥᱟᱦᱴᱟ +pdfjs-next-button-label = ᱤᱱᱟᱹ ᱛᱟᱭᱚᱢ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = ᱥᱟᱦᱴᱟ +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = ᱨᱮᱭᱟᱜ { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } ᱠᱷᱚᱱ { $pagesCount }) +pdfjs-zoom-out-button = + .title = ᱦᱤᱲᱤᱧ ᱛᱮᱭᱟᱨ +pdfjs-zoom-out-button-label = ᱦᱤᱲᱤᱧ ᱛᱮᱭᱟᱨ +pdfjs-zoom-in-button = + .title = ᱢᱟᱨᱟᱠᱛᱮᱭᱟᱨ +pdfjs-zoom-in-button-label = ᱢᱟᱨᱟᱠᱛᱮᱭᱟᱨ +pdfjs-zoom-select = + .title = ᱡᱩᱢ +pdfjs-presentation-mode-button = + .title = ᱩᱫᱩᱜ ᱥᱚᱫᱚᱨ ᱚᱵᱚᱥᱛᱟ ᱨᱮ ᱚᱛᱟᱭ ᱢᱮ +pdfjs-presentation-mode-button-label = ᱩᱫᱩᱜ ᱥᱚᱫᱚᱨ ᱚᱵᱚᱥᱛᱟ ᱨᱮ +pdfjs-open-file-button = + .title = ᱨᱮᱫ ᱡᱷᱤᱡᱽ ᱢᱮ +pdfjs-open-file-button-label = ᱡᱷᱤᱡᱽ ᱢᱮ +pdfjs-print-button = + .title = ᱪᱷᱟᱯᱟ +pdfjs-print-button-label = ᱪᱷᱟᱯᱟ +pdfjs-save-button = + .title = ᱥᱟᱺᱪᱟᱣ ᱢᱮ +pdfjs-save-button-label = ᱥᱟᱺᱪᱟᱣ ᱢᱮ +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = ᱰᱟᱣᱩᱱᱞᱚᱰ +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = ᱰᱟᱣᱩᱱᱞᱚᱰ +pdfjs-bookmark-button = + .title = ᱱᱤᱛᱚᱜᱟᱜ ᱥᱟᱦᱴᱟ (ᱱᱤᱛᱚᱜᱟᱜ ᱥᱟᱦᱴᱟ ᱠᱷᱚᱱ URL ᱫᱮᱠᱷᱟᱣ ᱢᱮ) +pdfjs-bookmark-button-label = ᱱᱤᱛᱚᱜᱟᱜ ᱥᱟᱦᱴᱟ + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = ᱦᱟᱹᱛᱤᱭᱟᱹᱨ ᱠᱚ +pdfjs-tools-button-label = ᱦᱟᱹᱛᱤᱭᱟᱹᱨ ᱠᱚ +pdfjs-first-page-button = + .title = ᱯᱩᱭᱞᱩ ᱥᱟᱦᱴᱟ ᱥᱮᱫ ᱪᱟᱞᱟᱜ ᱢᱮ +pdfjs-first-page-button-label = ᱯᱩᱭᱞᱩ ᱥᱟᱦᱴᱟ ᱥᱮᱫ ᱪᱟᱞᱟᱜ ᱢᱮ +pdfjs-last-page-button = + .title = ᱢᱩᱪᱟᱹᱫ ᱥᱟᱦᱴᱟ ᱥᱮᱫ ᱪᱟᱞᱟᱜ ᱢᱮ +pdfjs-last-page-button-label = ᱢᱩᱪᱟᱹᱫ ᱥᱟᱦᱴᱟ ᱥᱮᱫ ᱪᱟᱞᱟᱜ ᱢᱮ +pdfjs-page-rotate-cw-button = + .title = ᱜᱷᱚᱰᱤ ᱦᱤᱥᱟᱹᱵ ᱛᱮ ᱟᱹᱪᱩᱨ +pdfjs-page-rotate-cw-button-label = ᱜᱷᱚᱰᱤ ᱦᱤᱥᱟᱹᱵ ᱛᱮ ᱟᱹᱪᱩᱨ +pdfjs-page-rotate-ccw-button = + .title = ᱜᱷᱚᱰᱤ ᱦᱤᱥᱟᱹᱵ ᱛᱮ ᱩᱞᱴᱟᱹ ᱟᱹᱪᱩᱨ +pdfjs-page-rotate-ccw-button-label = ᱜᱷᱚᱰᱤ ᱦᱤᱥᱟᱹᱵ ᱛᱮ ᱩᱞᱴᱟᱹ ᱟᱹᱪᱩᱨ +pdfjs-cursor-text-select-tool-button = + .title = ᱚᱞ ᱵᱟᱪᱷᱟᱣ ᱦᱟᱹᱛᱤᱭᱟᱨ ᱮᱢ ᱪᱷᱚᱭ ᱢᱮ +pdfjs-cursor-text-select-tool-button-label = ᱚᱞ ᱵᱟᱪᱷᱟᱣ ᱦᱟᱹᱛᱤᱭᱟᱨ +pdfjs-cursor-hand-tool-button = + .title = ᱛᱤ ᱦᱟᱹᱛᱤᱭᱟᱨ ᱮᱢ ᱪᱷᱚᱭ ᱢᱮ +pdfjs-cursor-hand-tool-button-label = ᱛᱤ ᱦᱟᱹᱛᱤᱭᱟᱨ +pdfjs-scroll-page-button = + .title = ᱥᱟᱦᱴᱟ ᱜᱩᱲᱟᱹᱣ ᱵᱮᱵᱷᱟᱨ ᱢᱮ +pdfjs-scroll-page-button-label = ᱥᱟᱦᱴᱟ ᱜᱩᱲᱟᱹᱣ +pdfjs-scroll-vertical-button = + .title = ᱥᱤᱫᱽ ᱜᱩᱲᱟᱹᱣ ᱵᱮᱵᱷᱟᱨ ᱢᱮ +pdfjs-scroll-vertical-button-label = ᱥᱤᱫᱽ ᱜᱩᱲᱟᱹᱣ +pdfjs-scroll-horizontal-button = + .title = ᱜᱤᱛᱤᱡ ᱛᱮ ᱜᱩᱲᱟᱹᱣ ᱵᱮᱵᱷᱟᱨ ᱢᱮ +pdfjs-scroll-horizontal-button-label = ᱜᱤᱛᱤᱡ ᱛᱮ ᱜᱩᱲᱟᱹᱣ +pdfjs-scroll-wrapped-button = + .title = ᱞᱤᱯᱴᱟᱹᱣ ᱜᱩᱰᱨᱟᱹᱣ ᱵᱮᱵᱷᱟᱨ ᱢᱮ +pdfjs-scroll-wrapped-button-label = ᱞᱤᱯᱴᱟᱣ ᱜᱩᱰᱨᱟᱹᱣ +pdfjs-spread-none-button = + .title = ᱟᱞᱚᱢ ᱡᱚᱲᱟᱣ ᱟ ᱥᱟᱦᱴᱟ ᱫᱚ ᱯᱟᱥᱱᱟᱣᱜᱼᱟ +pdfjs-spread-none-button-label = ᱯᱟᱥᱱᱟᱣ ᱵᱟᱹᱱᱩᱜᱼᱟ +pdfjs-spread-odd-button = + .title = ᱥᱟᱦᱴᱟ ᱯᱟᱥᱱᱟᱣ ᱡᱚᱲᱟᱣ ᱢᱮ ᱡᱟᱦᱟᱸ ᱫᱚ ᱚᱰᱼᱮᱞ ᱥᱟᱦᱴᱟᱠᱚ ᱥᱟᱞᱟᱜ ᱮᱛᱦᱚᱵᱚᱜ ᱠᱟᱱᱟ +pdfjs-spread-odd-button-label = ᱚᱰ ᱯᱟᱥᱱᱟᱣ +pdfjs-spread-even-button = + .title = ᱥᱟᱦᱴᱟ ᱯᱟᱥᱱᱟᱣ ᱡᱚᱲᱟᱣ ᱢᱮ ᱡᱟᱦᱟᱸ ᱫᱚ ᱤᱣᱮᱱᱼᱮᱞ ᱥᱟᱦᱴᱟᱠᱚ ᱥᱟᱞᱟᱜ ᱮᱛᱦᱚᱵᱚᱜ ᱠᱟᱱᱟ +pdfjs-spread-even-button-label = ᱯᱟᱥᱱᱟᱣ ᱤᱣᱮᱱ + +## Document properties dialog + +pdfjs-document-properties-button = + .title = ᱫᱚᱞᱤᱞ ᱜᱩᱱᱠᱚ … +pdfjs-document-properties-button-label = ᱫᱚᱞᱤᱞ ᱜᱩᱱᱠᱚ … +pdfjs-document-properties-file-name = ᱨᱮᱫᱽ ᱧᱩᱛᱩᱢ : +pdfjs-document-properties-file-size = ᱨᱮᱫᱽ ᱢᱟᱯ : +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } ᱵᱟᱭᱤᱴ ᱠᱚ) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } ᱵᱟᱭᱤᱴ ᱠᱚ) +pdfjs-document-properties-title = ᱧᱩᱛᱩᱢ : +pdfjs-document-properties-author = ᱚᱱᱚᱞᱤᱭᱟᱹ : +pdfjs-document-properties-subject = ᱵᱤᱥᱚᱭ : +pdfjs-document-properties-keywords = ᱠᱟᱹᱴᱷᱤ ᱥᱟᱵᱟᱫᱽ : +pdfjs-document-properties-creation-date = ᱛᱮᱭᱟᱨ ᱢᱟᱸᱦᱤᱛ : +pdfjs-document-properties-modification-date = ᱵᱚᱫᱚᱞ ᱦᱚᱪᱚ ᱢᱟᱹᱦᱤᱛ : +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = ᱵᱮᱱᱟᱣᱤᱡ : +pdfjs-document-properties-producer = PDF ᱛᱮᱭᱟᱨ ᱚᱰᱚᱠᱤᱡ : +pdfjs-document-properties-version = PDF ᱵᱷᱟᱹᱨᱥᱚᱱ : +pdfjs-document-properties-page-count = ᱥᱟᱦᱴᱟ ᱞᱮᱠᱷᱟ : +pdfjs-document-properties-page-size = ᱥᱟᱦᱴᱟ ᱢᱟᱯ : +pdfjs-document-properties-page-size-unit-inches = ᱤᱧᱪ +pdfjs-document-properties-page-size-unit-millimeters = ᱢᱤᱢᱤ +pdfjs-document-properties-page-size-orientation-portrait = ᱯᱚᱴᱨᱮᱴ +pdfjs-document-properties-page-size-orientation-landscape = ᱞᱮᱱᱰᱥᱠᱮᱯ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = ᱪᱤᱴᱷᱤ +pdfjs-document-properties-page-size-name-legal = ᱠᱟᱹᱱᱩᱱᱤ + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = ᱞᱚᱜᱚᱱ ᱣᱮᱵᱽ ᱧᱮᱞ : +pdfjs-document-properties-linearized-yes = ᱦᱚᱭ +pdfjs-document-properties-linearized-no = ᱵᱟᱠ+pdfjs-document-properties-close-button = ᱵᱚᱸᱫᱚᱭ ᱢᱮ + +## Print + +pdfjs-print-progress-message = ᱪᱷᱟᱯᱟ ᱞᱟᱹᱜᱤᱫ ᱫᱚᱞᱤᱞ ᱛᱮᱭᱟᱨᱚᱜ ᱠᱟᱱᱟ … +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = ᱵᱟᱹᱰᱨᱟᱹ +pdfjs-printing-not-supported = ᱦᱚᱥᱤᱭᱟᱨ : ᱪᱷᱟᱯᱟ ᱱᱚᱣᱟ ᱯᱟᱱᱛᱮᱭᱟᱜ ᱫᱟᱨᱟᱭ ᱛᱮ ᱯᱩᱨᱟᱹᱣ ᱵᱟᱭ ᱜᱚᱲᱚᱣᱟᱠᱟᱱᱟ á±¾ +pdfjs-printing-not-ready = ᱦᱩᱥᱤᱭᱟᱹᱨ : ᱪᱷᱟᱯᱟ ᱞᱟᱹᱜᱤᱫ PDF ᱯᱩᱨᱟᱹ ᱵᱟᱭ ᱞᱟᱫᱮ ᱟᱠᱟᱱᱟ á±¾ + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = ᱫᱷᱟᱨᱮᱵᱟᱨ ᱥᱮᱫ ᱩᱪᱟᱹᱲᱚᱜ ᱢᱮ +pdfjs-toggle-sidebar-notification-button = + .title = ᱫᱷᱟᱨᱮᱵᱟᱨ ᱥᱮᱫ ᱩᱪᱟᱹᱲᱚᱜ ᱢᱮ (ᱫᱚᱞᱤᱞ ᱨᱮ ᱟᱣᱴᱞᱟᱭᱤᱢ ᱢᱮᱱᱟᱜᱼᱟ/ᱞᱟᱪᱷᱟᱠᱚ/ᱯᱚᱨᱚᱛᱠᱚ) +pdfjs-toggle-sidebar-button-label = ᱫᱷᱟᱨᱮᱵᱟᱨ ᱥᱮᱫ ᱩᱪᱟᱹᱲᱚᱜ ᱢᱮ +pdfjs-document-outline-button = + .title = ᱫᱚᱞᱚᱞ ᱟᱣᱴᱞᱟᱭᱤᱱ ᱫᱮᱠᱷᱟᱣ ᱢᱮ (ᱡᱷᱚᱛᱚ ᱡᱤᱱᱤᱥᱠᱚ ᱵᱟᱨ ᱡᱮᱠᱷᱟ ᱚᱛᱟ ᱠᱮᱛᱮ ᱡᱷᱟᱹᱞ/ᱦᱩᱰᱤᱧ ᱪᱷᱚᱭ ᱢᱮ) +pdfjs-document-outline-button-label = ᱫᱚᱞᱤᱞ ᱛᱮᱭᱟᱨ ᱛᱮᱫ +pdfjs-attachments-button = + .title = ᱞᱟᱴᱷᱟ ᱥᱮᱞᱮᱫ ᱠᱚ ᱩᱫᱩᱜᱽ ᱢᱮ +pdfjs-attachments-button-label = ᱞᱟᱴᱷᱟ ᱥᱮᱞᱮᱫ ᱠᱚ +pdfjs-layers-button = + .title = ᱯᱚᱨᱚᱛ ᱫᱮᱠᱷᱟᱣ ᱢᱮ (ᱢᱩᱞ ᱡᱟᱭᱜᱟ ᱛᱮ ᱡᱷᱚᱛᱚ ᱯᱚᱨᱚᱛᱠᱚ ᱨᱤᱥᱮᱴ ᱞᱟᱹᱜᱤᱫ ᱵᱟᱨ ᱡᱮᱠᱷᱟ ᱚᱛᱚᱭ ᱢᱮ) +pdfjs-layers-button-label = ᱯᱚᱨᱚᱛᱠᱚ +pdfjs-thumbs-button = + .title = ᱪᱤᱛᱟᱹᱨ ᱟᱦᱞᱟ ᱠᱚ ᱩᱫᱩᱜᱽ ᱢᱮ +pdfjs-thumbs-button-label = ᱪᱤᱛᱟᱹᱨ ᱟᱦᱞᱟ ᱠᱚ +pdfjs-current-outline-item-button = + .title = ᱱᱤᱛᱚᱜᱟᱜ ᱟᱣᱴᱞᱟᱭᱤᱱ ᱡᱟᱱᱤᱥ ᱯᱟᱱᱛᱮ ᱢᱮ +pdfjs-current-outline-item-button-label = ᱱᱤᱛᱚᱜᱟᱜ ᱟᱣᱴᱞᱟᱭᱤᱱ ᱡᱟᱱᱤᱥ +pdfjs-findbar-button = + .title = ᱫᱚᱞᱤᱞ ᱨᱮ ᱯᱟᱱᱛᱮ +pdfjs-findbar-button-label = ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮ +pdfjs-additional-layers = ᱵᱟᱹᱲᱛᱤ ᱯᱚᱨᱚᱛᱠᱚ + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = { $page } ᱥᱟᱦᱴᱟ +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = { $page } ᱥᱟᱦᱴᱟ ᱨᱮᱭᱟᱜ ᱪᱤᱛᱟᱹᱨ ᱟᱦᱞᱟ + +## Find panel button title and messages + +pdfjs-find-input = + .title = ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮ + .placeholder = ᱫᱚᱞᱤᱞ ᱨᱮ ᱯᱟᱱᱛᱮ ᱢᱮ … +pdfjs-find-previous-button = + .title = ᱟᱭᱟᱛ ᱦᱤᱸᱥ ᱨᱮᱭᱟᱜ ᱯᱟᱹᱦᱤᱞ ᱥᱮᱫᱟᱜ ᱚᱰᱚᱠ ᱧᱟᱢ ᱢᱮ +pdfjs-find-previous-button-label = ᱢᱟᱲᱟá±á±Ÿá±œ +pdfjs-find-next-button = + .title = ᱟᱭᱟᱛ ᱦᱤᱸᱥ ᱨᱮᱭᱟᱜ ᱤᱱᱟᱹ ᱛᱟᱭᱚᱢ ᱚᱰᱚᱠ ᱧᱟᱢ ᱢᱮ +pdfjs-find-next-button-label = ᱤᱱᱟᱹ ᱛᱟᱭᱚᱢ +pdfjs-find-highlight-checkbox = ᱡᱷᱚᱛᱚ ᱩᱫᱩᱜ ᱨᱟᱠᱟᱵ +pdfjs-find-match-case-checkbox-label = ᱡᱚᱲ ᱠᱟᱛᱷᱟ +pdfjs-find-match-diacritics-checkbox-label = ᱵᱤᱥᱮᱥᱚᱠ ᱠᱚ ᱢᱮᱲᱟᱣ ᱢᱮ +pdfjs-find-entire-word-checkbox-label = ᱡᱷᱚᱛᱚ ᱟᱹᱲᱟᱹᱠᱚ +pdfjs-find-reached-top = ᱫᱚᱞᱤᱞ ᱨᱮᱭᱟᱜ ᱪᱤᱴ ᱨᱮ ᱥᱮᱴᱮᱨ, ᱞᱟᱛᱟᱨ ᱠᱷᱚᱱ ᱞᱮᱛᱟᱲ +pdfjs-find-reached-bottom = ᱫᱚᱞᱤᱞ ᱨᱮᱭᱟᱜ ᱢᱩᱪᱟᱹᱫ ᱨᱮ ᱥᱮᱴᱮᱨ, ᱪᱚᱴ ᱠᱷᱚᱱ ᱞᱮᱛᱟᱲ +pdfjs-find-not-found = ᱛᱚᱯᱚᱞ ᱫᱚᱱᱚᱲ ᱵᱟᱠᱧᱟᱢ ᱞᱮᱱᱟ + +## Predefined zoom values + +pdfjs-page-scale-width = ᱥᱟᱦᱴᱟ ᱚᱥᱟᱨ +pdfjs-page-scale-fit = ᱥᱟᱦᱴᱟ ᱠᱷᱟᱯ +pdfjs-page-scale-auto = ᱟᱡᱼᱟᱡ ᱛᱮ ᱦᱩᱰᱤᱧ ᱞᱟᱹᱴᱩ ᱛᱮᱭᱟᱨ +pdfjs-page-scale-actual = ᱴᱷᱤᱠ ᱢᱟᱨᱟᱠᱛᱮᱫ +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = { $page } ᱥᱟᱦᱴᱟ + +## Loading indicator messages + +pdfjs-loading-error = PDF ᱞᱟᱫᱮ ᱡᱚᱦᱚᱜ ᱢᱤᱫ ᱵᱷᱩᱞ ᱦᱩᱭ ᱮᱱᱟ á±¾ +pdfjs-invalid-file-error = ᱵᱟᱠᱵᱟᱛᱟᱣ ᱟᱨᱵᱟá±á± á±·á±Ÿá±± ᱰᱤᱜᱟᱹᱣ PDF ᱨᱮᱫᱽ á±¾ +pdfjs-missing-file-error = ᱟᱫᱟᱜ PDF ᱨᱮᱫᱽ á±¾ +pdfjs-unexpected-response-error = ᱵᱟá±á±µá±©á±¡á±· ᱥᱚᱨᱵᱷᱚᱨ ᱛᱮᱞᱟ á±¾ +pdfjs-rendering-error = ᱥᱟᱦᱴᱟ ᱮᱢ ᱡᱚᱦᱚᱠ ᱢᱤᱫ ᱵᱷᱩᱞ ᱦᱩᱭ ᱮᱱᱟ á±¾ + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } ᱢᱚᱱᱛᱚ ᱮᱢ] + +## Password + +pdfjs-password-label = ᱱᱚᱶᱟ PDF ᱨᱮᱫᱽ ᱡᱷᱤᱡᱽ ᱞᱟᱹᱜᱤᱫ ᱫᱟᱱᱟᱠᱥᱟᱵᱟᱫᱽ ᱟᱫᱮᱨ ᱢᱮ á±¾ +pdfjs-password-invalid = ᱵᱷᱩᱞ ᱫᱟᱱᱟᱠᱥᱟᱵᱟᱫᱽ á±¾ ᱫᱟᱭᱟᱠᱟᱛᱮ ᱫᱩᱦᱲᱟᱹ ᱪᱮᱥᱴᱟᱭ ᱢᱮ á±¾ +pdfjs-password-ok-button = ᱴᱷᱤᱠ +pdfjs-password-cancel-button = ᱵᱟᱹᱰᱨᱟᱹ +pdfjs-web-fonts-disabled = ᱣᱮᱵᱽ ᱪᱤᱠᱤ ᱵᱟᱠᱦᱩᱭ ᱦᱚᱪᱚ ᱠᱟᱱᱟ : ᱵᱷᱤᱛᱤᱨ ᱛᱷᱟᱯᱚᱱ PDF ᱪᱤᱠᱤ ᱵᱮᱵᱷᱟᱨ ᱵᱟᱠᱦᱩᱭ ᱠᱮᱭᱟ á±¾ + +## Editing + +pdfjs-editor-free-text-button = + .title = ᱚᱞ +pdfjs-editor-free-text-button-label = ᱚᱞ +pdfjs-editor-ink-button = + .title = ᱛᱮᱭᱟᱨ +pdfjs-editor-ink-button-label = ᱛᱮᱭᱟᱨ +pdfjs-editor-stamp-button = + .title = ᱪᱤᱛᱟᱹᱨᱠᱚ ᱥᱮᱞᱮᱫ ᱥᱮ ᱥᱟᱯᱲᱟᱣ ᱢᱮ +pdfjs-editor-stamp-button-label = ᱪᱤᱛᱟᱹᱨᱠᱚ ᱥᱮᱞᱮᱫ ᱥᱮ ᱥᱟᱯᱲᱟᱣ ᱢᱮ + +## Remove button for the various kind of editor. + + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = ᱨᱚᱠ+pdfjs-editor-free-text-size-input = ᱢᱟᱯ +pdfjs-editor-ink-color-input = ᱨᱚᱠ+pdfjs-editor-ink-thickness-input = ᱢᱚᱴᱟ +pdfjs-editor-ink-opacity-input = ᱟᱨᱯᱟᱨ +pdfjs-editor-stamp-add-image-button = + .title = ᱪᱤᱛᱟᱹᱨ ᱥᱮᱞᱮᱫ ᱢᱮ +pdfjs-editor-stamp-add-image-button-label = ᱪᱤᱛᱟᱹᱨ ᱥᱮᱞᱮᱫ ᱢᱮ +pdfjs-free-text = + .aria-label = ᱚᱞ ᱥᱟᱯᱲᱟᱣᱤᱭᱟᱹ +pdfjs-free-text-default-content = ᱚᱞ ᱮᱛᱦᱚᱵ ᱢᱮ … +pdfjs-ink = + .aria-label = ᱛᱮᱭᱟᱨ ᱥᱟᱯᱲᱟᱣᱤᱭᱟᱹ +pdfjs-ink-canvas = + .aria-label = ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ ᱛᱮᱭᱟᱨ ᱠᱟᱫ ᱪᱤᱛᱟᱹᱨ + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + + +## Color picker + + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + + +## Image alt-text settings + diff --git a/public/assets/pdfjs/locale/sc/viewer.ftl b/public/assets/pdfjs/locale/sc/viewer.ftl new file mode 100755 index 0000000..1137c2b --- /dev/null +++ b/public/assets/pdfjs/locale/sc/viewer.ftl @@ -0,0 +1,367 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Pàgina anteriore +pdfjs-previous-button-label = S'ischeda chi b'est primu +pdfjs-next-button = + .title = Pàgina imbeniente +pdfjs-next-button-label = Imbeniente +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Pàgina +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = de { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } de { $pagesCount }) +pdfjs-zoom-out-button = + .title = Impitica +pdfjs-zoom-out-button-label = Impitica +pdfjs-zoom-in-button = + .title = Ismànnia +pdfjs-zoom-in-button-label = Ismànnia +pdfjs-zoom-select = + .title = Ismànnia +pdfjs-presentation-mode-button = + .title = Cola a sa modalidade de presentatzione +pdfjs-presentation-mode-button-label = Modalidade de presentatzione +pdfjs-open-file-button = + .title = Aberi s'archìviu +pdfjs-open-file-button-label = Abertu +pdfjs-print-button = + .title = Imprenta +pdfjs-print-button-label = Imprenta +pdfjs-save-button = + .title = Sarva +pdfjs-save-button-label = Sarva +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Iscàrriga +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Iscàrriga +pdfjs-bookmark-button = + .title = Pàgina atuale (ammustra s’URL de sa pàgina atuale) +pdfjs-bookmark-button-label = Pàgina atuale + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Istrumentos +pdfjs-tools-button-label = Istrumentos +pdfjs-first-page-button = + .title = Bae a sa prima pàgina +pdfjs-first-page-button-label = Bae a sa prima pàgina +pdfjs-last-page-button = + .title = Bae a s'ùrtima pàgina +pdfjs-last-page-button-label = Bae a s'ùrtima pàgina +pdfjs-page-rotate-cw-button = + .title = Gira in sensu oràriu +pdfjs-page-rotate-cw-button-label = Gira in sensu oràriu +pdfjs-page-rotate-ccw-button = + .title = Gira in sensu anti-oràriu +pdfjs-page-rotate-ccw-button-label = Gira in sensu anti-oràriu +pdfjs-cursor-text-select-tool-button = + .title = Ativa s'aina de seletzione de testu +pdfjs-cursor-text-select-tool-button-label = Aina de seletzione de testu +pdfjs-cursor-hand-tool-button = + .title = Ativa s'aina de manu +pdfjs-cursor-hand-tool-button-label = Aina de manu +pdfjs-scroll-page-button = + .title = Imprea s'iscurrimentu de pàgina +pdfjs-scroll-page-button-label = Iscurrimentu de pàgina +pdfjs-scroll-vertical-button = + .title = Imprea s'iscurrimentu verticale +pdfjs-scroll-vertical-button-label = Iscurrimentu verticale +pdfjs-scroll-horizontal-button = + .title = Imprea s'iscurrimentu orizontale +pdfjs-scroll-horizontal-button-label = Iscurrimentu orizontale +pdfjs-scroll-wrapped-button = + .title = Imprea s'iscurrimentu continu +pdfjs-scroll-wrapped-button-label = Iscurrimentu continu + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Propiedades de su documentu… +pdfjs-document-properties-button-label = Propiedades de su documentu… +pdfjs-document-properties-file-name = Nòmine de s'archìviu: +pdfjs-document-properties-file-size = Mannària de s'archìviu: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Tìtulu: +pdfjs-document-properties-author = Autoria: +pdfjs-document-properties-subject = Ogetu: +pdfjs-document-properties-keywords = Faeddos crae: +pdfjs-document-properties-creation-date = Data de creatzione: +pdfjs-document-properties-modification-date = Data de modìfica: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Creatzione: +pdfjs-document-properties-producer = Produtore de PDF: +pdfjs-document-properties-version = Versione de PDF: +pdfjs-document-properties-page-count = Contu de pàginas: +pdfjs-document-properties-page-size = Mannària de sa pàgina: +pdfjs-document-properties-page-size-unit-inches = pòddighes +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = verticale +pdfjs-document-properties-page-size-orientation-landscape = orizontale +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Lìtera +pdfjs-document-properties-page-size-name-legal = Legale + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Visualizatzione web lestra: +pdfjs-document-properties-linearized-yes = Eja +pdfjs-document-properties-linearized-no = Nono +pdfjs-document-properties-close-button = Serra + +## Print + +pdfjs-print-progress-message = Aparitzende s'imprenta de su documentu… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Cantzella +pdfjs-printing-not-supported = Atentzione: s'imprenta no est funtzionende de su totu in custu navigadore. +pdfjs-printing-not-ready = Atentzione: su PDF no est istadu carrigadu de su totu pro s'imprenta. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Ativa/disativa sa barra laterale +pdfjs-toggle-sidebar-notification-button = + .title = Ativa/disativa sa barra laterale (su documentu cuntenet un'ischema, alligongiados o livellos) +pdfjs-toggle-sidebar-button-label = Ativa/disativa sa barra laterale +pdfjs-document-outline-button-label = Ischema de su documentu +pdfjs-attachments-button = + .title = Ammustra alligongiados +pdfjs-attachments-button-label = Alliongiados +pdfjs-layers-button = + .title = Ammustra livellos (clic dòpiu pro ripristinare totu is livellos a s'istadu predefinidu) +pdfjs-layers-button-label = Livellos +pdfjs-thumbs-button = + .title = Ammustra miniaturas +pdfjs-thumbs-button-label = Miniaturas +pdfjs-current-outline-item-button = + .title = Agata s'elementu atuale de s'ischema +pdfjs-current-outline-item-button-label = Elementu atuale de s'ischema +pdfjs-findbar-button = + .title = Agata in su documentu +pdfjs-findbar-button-label = Agata +pdfjs-additional-layers = Livellos additzionales + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Pàgina { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura de sa pàgina { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Agata + .placeholder = Agata in su documentu… +pdfjs-find-previous-button = + .title = Agata s'ocurrèntzia pretzedente de sa fràsia +pdfjs-find-previous-button-label = S'ischeda chi b'est primu +pdfjs-find-next-button = + .title = Agata s'ocurrèntzia imbeniente de sa fràsia +pdfjs-find-next-button-label = Imbeniente +pdfjs-find-highlight-checkbox = Evidèntzia totu +pdfjs-find-match-case-checkbox-label = Distinghe intre majùsculas e minùsculas +pdfjs-find-match-diacritics-checkbox-label = Respeta is diacrìticos +pdfjs-find-entire-word-checkbox-label = Faeddos intreos +pdfjs-find-reached-top = S'est lòmpidu a su cumintzu de su documentu, si sighit dae su bàsciu +pdfjs-find-reached-bottom = Acabbu de su documentu, si sighit dae s'artu +pdfjs-find-not-found = Testu no agatadu + +## Predefined zoom values + +pdfjs-page-scale-auto = Ingrandimentu automàticu +pdfjs-page-scale-actual = Mannària reale +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Pàgina { $page } + +## Loading indicator messages + +pdfjs-loading-error = Faddina in sa càrriga de su PDF. +pdfjs-invalid-file-error = Archìviu PDF non vàlidu o corrùmpidu. +pdfjs-missing-file-error = Ammancat s'archìviu PDF. +pdfjs-unexpected-response-error = Risposta imprevista de su serbidore. +pdfjs-rendering-error = Faddina in sa visualizatzione de sa pàgina. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } + +## Password + +pdfjs-password-label = Inserta sa crae pro abèrrere custu archìviu PDF. +pdfjs-password-invalid = Sa crae no est curreta. Torra a nche proare. +pdfjs-password-ok-button = Andat bene +pdfjs-password-cancel-button = Cantzella +pdfjs-web-fonts-disabled = Is tipografias web sunt disativadas: is tipografias incrustadas a su PDF non podent èssere impreadas. + +## Editing + +pdfjs-editor-free-text-button = + .title = Testu +pdfjs-editor-free-text-button-label = Testu +pdfjs-editor-ink-button = + .title = Disinnu +pdfjs-editor-ink-button-label = Disinnu +pdfjs-editor-stamp-button = + .title = Agiunghe o modìfica immàgines +pdfjs-editor-stamp-button-label = Agiunghe o modìfica immàgines +pdfjs-editor-highlight-button = + .title = Evidèntzia +pdfjs-editor-highlight-button-label = Evidèntzia +pdfjs-highlight-floating-button1 = + .title = Evidèntzia + .aria-label = Evidèntzia +pdfjs-highlight-floating-button-label = Evidèntzia + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Boga su disinnu +pdfjs-editor-remove-freetext-button = + .title = Boga su testu +pdfjs-editor-remove-stamp-button = + .title = Boga s’immàgine +pdfjs-editor-remove-highlight-button = + .title = Boga s’evidèntzia + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Colore +pdfjs-editor-free-text-size-input = Mannària +pdfjs-editor-ink-color-input = Colore +pdfjs-editor-ink-thickness-input = Grussària +pdfjs-editor-stamp-add-image-button = + .title = Agiunghe un’immàgine +pdfjs-editor-stamp-add-image-button-label = Agiunghe un’immàgine +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Grussària +pdfjs-free-text = + .aria-label = Editore de testu +pdfjs-free-text-default-content = Cumintza a iscrìere… +pdfjs-ink = + .aria-label = Editore de disinnos +pdfjs-ink-canvas = + .aria-label = Immàgine creada dae s’utente + +## Alt-text dialog + +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button-label = Testu alternativu +pdfjs-editor-alt-text-edit-button-label = Modifica su testu alternativu +pdfjs-editor-alt-text-dialog-label = Sèbera un’optzione +pdfjs-editor-alt-text-dialog-description = Su testu alternativu (“alt textâ€) est ùtile pro persones chi non podent bìdere s’immàgine o cando non benit carrigada. +pdfjs-editor-alt-text-add-description-label = Agiunghe una descritzione +pdfjs-editor-alt-text-cancel-button = Annulla +pdfjs-editor-alt-text-save-button = Sarva + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + + +## Color picker + +pdfjs-editor-colorpicker-button = + .title = Modifica su colore +pdfjs-editor-colorpicker-dropdown = + .aria-label = Colores a disponimentu +pdfjs-editor-colorpicker-yellow = + .title = Grogu +pdfjs-editor-colorpicker-green = + .title = Birde +pdfjs-editor-colorpicker-blue = + .title = Biaitu +pdfjs-editor-colorpicker-pink = + .title = Rosa + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Mancat su testu alternativu +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Revisiona su testu alternativu +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creadu in automàticu: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Cunfiguratzione de su testu alternativu de is immàgines +pdfjs-image-alt-text-settings-button-label = Cunfiguratzione de su testu alternativu de is immàgines +pdfjs-editor-alt-text-settings-dialog-label = Cunfiguratzione de su testu alternativu de is immàgines +pdfjs-editor-alt-text-settings-automatic-title = Testu alternativu automàticu +pdfjs-editor-alt-text-settings-create-model-button-label = Crea testu alternativu in automàticu +pdfjs-editor-alt-text-settings-create-model-description = Cussìgiat descritziones pro agiudare a gente chi non podet bìdere s’immàgine o cando non benit carrigada. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modellu de IA pro su testu alternativu ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Est esecutadu in locale in manera chi is datos tuos abarrent in privadu. Rechestu pro sa generatzione automàtica de testu alternativu. +pdfjs-editor-alt-text-settings-delete-model-button = Cantzella +pdfjs-editor-alt-text-settings-download-model-button = Iscàrriga +pdfjs-editor-alt-text-settings-downloading-model-button = Iscarrighende… +pdfjs-editor-alt-text-settings-editor-title = Editore de testu alternativu +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mustra deretu s’editore de testu alternativu cando siat agiunta un’immàgine +pdfjs-editor-alt-text-settings-show-dialog-description = T’agiudat a assegurare chi totu is immàgines tuas tèngiant unu testu alternativu. +pdfjs-editor-alt-text-settings-close-button = Serra diff --git a/public/assets/pdfjs/locale/scn/viewer.ftl b/public/assets/pdfjs/locale/scn/viewer.ftl new file mode 100755 index 0000000..a3c7c03 --- /dev/null +++ b/public/assets/pdfjs/locale/scn/viewer.ftl @@ -0,0 +1,74 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-zoom-out-button = + .title = Cchiù nicu +pdfjs-zoom-out-button-label = Cchiù nicu +pdfjs-zoom-in-button = + .title = Cchiù granni +pdfjs-zoom-in-button-label = Cchiù granni + +## Secondary toolbar and context menu + + +## Document properties dialog + + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Vista web lesta: +pdfjs-document-properties-linearized-yes = Se + +## Print + +pdfjs-print-progress-close-button = Sfai + +## Tooltips and alt text for side panel toolbar buttons + + +## Thumbnails panel item (tooltip and alt text for images) + + +## Find panel button title and messages + + +## Predefined zoom values + +pdfjs-page-scale-width = Larghizza dâ pàggina + +## PDF page + + +## Loading indicator messages + + +## Annotations + + +## Password + +pdfjs-password-cancel-button = Sfai + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/sco/viewer.ftl b/public/assets/pdfjs/locale/sco/viewer.ftl new file mode 100755 index 0000000..6f71c47 --- /dev/null +++ b/public/assets/pdfjs/locale/sco/viewer.ftl @@ -0,0 +1,264 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Page Afore +pdfjs-previous-button-label = Previous +pdfjs-next-button = + .title = Page Efter +pdfjs-next-button-label = Neist +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Page +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = o { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } o { $pagesCount }) +pdfjs-zoom-out-button = + .title = Zoom Oot +pdfjs-zoom-out-button-label = Zoom Oot +pdfjs-zoom-in-button = + .title = Zoom In +pdfjs-zoom-in-button-label = Zoom In +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Flit tae Presentation Mode +pdfjs-presentation-mode-button-label = Presentation Mode +pdfjs-open-file-button = + .title = Open File +pdfjs-open-file-button-label = Open +pdfjs-print-button = + .title = Prent +pdfjs-print-button-label = Prent + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Tools +pdfjs-tools-button-label = Tools +pdfjs-first-page-button = + .title = Gang tae First Page +pdfjs-first-page-button-label = Gang tae First Page +pdfjs-last-page-button = + .title = Gang tae Lest Page +pdfjs-last-page-button-label = Gang tae Lest Page +pdfjs-page-rotate-cw-button = + .title = Rotate Clockwise +pdfjs-page-rotate-cw-button-label = Rotate Clockwise +pdfjs-page-rotate-ccw-button = + .title = Rotate Coonterclockwise +pdfjs-page-rotate-ccw-button-label = Rotate Coonterclockwise +pdfjs-cursor-text-select-tool-button = + .title = Enable Text Walin Tool +pdfjs-cursor-text-select-tool-button-label = Text Walin Tool +pdfjs-cursor-hand-tool-button = + .title = Enable Haun Tool +pdfjs-cursor-hand-tool-button-label = Haun Tool +pdfjs-scroll-vertical-button = + .title = Yaise Vertical Scrollin +pdfjs-scroll-vertical-button-label = Vertical Scrollin +pdfjs-scroll-horizontal-button = + .title = Yaise Horizontal Scrollin +pdfjs-scroll-horizontal-button-label = Horizontal Scrollin +pdfjs-scroll-wrapped-button = + .title = Yaise Wrapped Scrollin +pdfjs-scroll-wrapped-button-label = Wrapped Scrollin +pdfjs-spread-none-button = + .title = Dinnae jyn page spreids +pdfjs-spread-none-button-label = Nae Spreids +pdfjs-spread-odd-button = + .title = Jyn page spreids stertin wi odd-numbered pages +pdfjs-spread-odd-button-label = Odd Spreids +pdfjs-spread-even-button = + .title = Jyn page spreids stertin wi even-numbered pages +pdfjs-spread-even-button-label = Even Spreids + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Document Properties… +pdfjs-document-properties-button-label = Document Properties… +pdfjs-document-properties-file-name = File nemme: +pdfjs-document-properties-file-size = File size: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Title: +pdfjs-document-properties-author = Author: +pdfjs-document-properties-subject = Subjeck: +pdfjs-document-properties-keywords = Keywirds: +pdfjs-document-properties-creation-date = Date o Makkin: +pdfjs-document-properties-modification-date = Date o Chynges: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Makker: +pdfjs-document-properties-producer = PDF Producer: +pdfjs-document-properties-version = PDF Version: +pdfjs-document-properties-page-count = Page Coont: +pdfjs-document-properties-page-size = Page Size: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = portrait +pdfjs-document-properties-page-size-orientation-landscape = landscape +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Fast Wab View: +pdfjs-document-properties-linearized-yes = Aye +pdfjs-document-properties-linearized-no = Naw +pdfjs-document-properties-close-button = Sneck + +## Print + +pdfjs-print-progress-message = Reddin document fur prentin… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Stap +pdfjs-printing-not-supported = Tak tent: Prentin isnae richt supportit by this stravaiger. +pdfjs-printing-not-ready = Tak tent: The PDF isnae richt loadit fur prentin. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Toggle Sidebaur +pdfjs-toggle-sidebar-notification-button = + .title = Toggle Sidebaur (document conteens ootline/attachments/layers) +pdfjs-toggle-sidebar-button-label = Toggle Sidebaur +pdfjs-document-outline-button = + .title = Kythe Document Ootline (double-click fur tae oot-fauld/in-fauld aw items) +pdfjs-document-outline-button-label = Document Ootline +pdfjs-attachments-button = + .title = Kythe Attachments +pdfjs-attachments-button-label = Attachments +pdfjs-layers-button = + .title = Kythe Layers (double-click fur tae reset aw layers tae the staunart state) +pdfjs-layers-button-label = Layers +pdfjs-thumbs-button = + .title = Kythe Thumbnails +pdfjs-thumbs-button-label = Thumbnails +pdfjs-current-outline-item-button = + .title = Find Current Ootline Item +pdfjs-current-outline-item-button-label = Current Ootline Item +pdfjs-findbar-button = + .title = Find in Document +pdfjs-findbar-button-label = Find +pdfjs-additional-layers = Mair Layers + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Page { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Thumbnail o Page { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Find + .placeholder = Find in document… +pdfjs-find-previous-button = + .title = Airt oot the last time this phrase occurred +pdfjs-find-previous-button-label = Previous +pdfjs-find-next-button = + .title = Airt oot the neist time this phrase occurs +pdfjs-find-next-button-label = Neist +pdfjs-find-highlight-checkbox = Highlicht aw +pdfjs-find-match-case-checkbox-label = Match case +pdfjs-find-entire-word-checkbox-label = Hale Wirds +pdfjs-find-reached-top = Raxed tap o document, went on fae the dowp end +pdfjs-find-reached-bottom = Raxed end o document, went on fae the tap +pdfjs-find-not-found = Phrase no fund + +## Predefined zoom values + +pdfjs-page-scale-width = Page Width +pdfjs-page-scale-fit = Page Fit +pdfjs-page-scale-auto = Automatic Zoom +pdfjs-page-scale-actual = Actual Size +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Page { $page } + +## Loading indicator messages + +pdfjs-loading-error = An mishanter tuik place while loadin the PDF. +pdfjs-invalid-file-error = No suithfest or camshauchlet PDF file. +pdfjs-missing-file-error = PDF file tint. +pdfjs-unexpected-response-error = Unexpectit server repone. +pdfjs-rendering-error = A mishanter tuik place while renderin the page. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Annotation] + +## Password + +pdfjs-password-label = Inpit the passwird fur tae open this PDF file. +pdfjs-password-invalid = Passwird no suithfest. Gonnae gie it anither shot. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Stap +pdfjs-web-fonts-disabled = Wab fonts are disabled: cannae yaise embeddit PDF fonts. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/si/viewer.ftl b/public/assets/pdfjs/locale/si/viewer.ftl new file mode 100755 index 0000000..0481116 --- /dev/null +++ b/public/assets/pdfjs/locale/si/viewer.ftl @@ -0,0 +1,271 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = කලින් පිටුව +pdfjs-previous-button-label = කලින් +pdfjs-next-button = + .title = à¶Šà·…à¶Ÿ පිටුව +pdfjs-next-button-label = à¶Šà·…à¶Ÿ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = පිටුව +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } / { $pagesCount }) +pdfjs-zoom-out-button = + .title = කුඩà·à¶½à¶±à¶º +pdfjs-zoom-out-button-label = කුඩà·à¶½à¶±à¶º +pdfjs-zoom-in-button = + .title = විà·à·à¶½à¶±à¶º +pdfjs-zoom-in-button-label = විà·à·à¶½à¶±à¶º +pdfjs-zoom-select = + .title = විà·à·à¶½ කරන්න +pdfjs-presentation-mode-button = + .title = සමර්පණ à¶´à·Šâ€à¶»à¶šà·à¶»à¶º වෙත මà·à¶»à·”වන්න +pdfjs-presentation-mode-button-label = සමර්පණ à¶´à·Šâ€à¶»à¶šà·à¶»à¶º +pdfjs-open-file-button = + .title = ගොනුව අරින්න +pdfjs-open-file-button-label = අරින්න +pdfjs-print-button = + .title = මුද්â€à¶»à¶«à¶º +pdfjs-print-button-label = මුද්â€à¶»à¶«à¶º +pdfjs-save-button = + .title = සුරකින්න +pdfjs-save-button-label = සුරකින්න +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = à¶¶à·à¶œà¶±à·Šà¶± +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = à¶¶à·à¶œà¶±à·Šà¶± +pdfjs-bookmark-button-label = පවතින පිටුව + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = මෙවලම් +pdfjs-tools-button-label = මෙවලම් +pdfjs-first-page-button = + .title = මුල් පිටුවට යන්න +pdfjs-first-page-button-label = මුල් පිටුවට යන්න +pdfjs-last-page-button = + .title = අවසන් පිටුවට යන්න +pdfjs-last-page-button-label = අවසන් පිටුවට යන්න +pdfjs-cursor-text-select-tool-button = + .title = පෙළ තේරීමේ මෙවලම සබල කරන්න +pdfjs-cursor-text-select-tool-button-label = පෙළ තේරීමේ මෙවලම +pdfjs-cursor-hand-tool-button = + .title = à¶…à¶­à·Š මෙවලම සබල කරන්න +pdfjs-cursor-hand-tool-button-label = à¶…à¶­à·Š මෙවලම +pdfjs-scroll-page-button = + .title = පිටුව අනුචලනය à¶·à·à·€à·’තය +pdfjs-scroll-page-button-label = පිටුව අනුචලනය +pdfjs-scroll-vertical-button = + .title = සිරස් අනුචලනය à¶·à·à·€à·’තය +pdfjs-scroll-vertical-button-label = සිරස් අනුචලනය +pdfjs-scroll-horizontal-button = + .title = තිරස් අනුචලනය à¶·à·à·€à·’තය +pdfjs-scroll-horizontal-button-label = තිරස් අනුචලනය + +## Document properties dialog + +pdfjs-document-properties-button = + .title = ලේඛනයේ ගුණà·à¶‚ග… +pdfjs-document-properties-button-label = ලේඛනයේ ගුණà·à¶‚ග… +pdfjs-document-properties-file-name = ගොනුවේ නම: +pdfjs-document-properties-file-size = ගොනුවේ à¶´à·Šâ€à¶»à¶¸à·à¶«à¶º: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = à¶šà·’.à¶¶. { $size_kb } (බයිට { $size_b }) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = මෙ.à¶¶. { $size_mb } (බයිට { $size_b }) +pdfjs-document-properties-title = සිරà·à·ƒà·’ය: +pdfjs-document-properties-author = à¶šà¶­à·˜: +pdfjs-document-properties-subject = මà·à¶­à·˜à¶šà·à·€: +pdfjs-document-properties-keywords = මූල පද: +pdfjs-document-properties-creation-date = සෑදූ දිනය: +pdfjs-document-properties-modification-date = සංà·à·à¶°à·’à¶­ දිනය: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = නිර්මà·à¶­à·˜: +pdfjs-document-properties-producer = පීඩීඑෆ් සම්පà·à¶¯à¶š: +pdfjs-document-properties-version = පීඩීඑෆ් අනුවà·à¶¯à¶º: +pdfjs-document-properties-page-count = à¶´à·’à¶§à·” ගණන: +pdfjs-document-properties-page-size = පිටුවේ තරම: +pdfjs-document-properties-page-size-unit-inches = අඟල් +pdfjs-document-properties-page-size-unit-millimeters = මි.මී. +pdfjs-document-properties-page-size-orientation-portrait = සිරස් +pdfjs-document-properties-page-size-orientation-landscape = තිරස් +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width }×{ $height }{ $unit }{ $name }{ $orientation } + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = වේගවත් වියමන දà·à¶šà·Šà¶¸: +pdfjs-document-properties-linearized-yes = ඔව් +pdfjs-document-properties-linearized-no = à¶±à·à·„à· +pdfjs-document-properties-close-button = වසන්න + +## Print + +pdfjs-print-progress-message = මුද්â€à¶»à¶«à¶º සඳහ෠ලේඛනය සූදà·à¶±à¶¸à·Š වෙමින්… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = අවලංගු කරන්න +pdfjs-printing-not-supported = අවවà·à¶¯à¶ºà¶ºà·’: මෙම අතිරික්සුව මුද්â€à¶»à¶«à¶º සඳහ෠හොඳින් සහà·à¶º නොදක්වයි. +pdfjs-printing-not-ready = අවවà·à¶¯à¶ºà¶ºà·’: මුද්â€à¶»à¶«à¶ºà¶§ පීඩීඑෆ් ගොනුව සම්පූර්ණයෙන් පූරණය වී à¶±à·à¶­. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-document-outline-button-label = ලේඛනයේ වටසන +pdfjs-attachments-button = + .title = ඇමුණුම් පෙන්වන්න +pdfjs-attachments-button-label = ඇමුණුම් +pdfjs-layers-button = + .title = ස්තර පෙන්වන්න (සියළු ස්තර පෙරනිමි à¶­à¶­à·Šâ€à·€à¶ºà¶§ යළි à·ƒà·à¶šà·ƒà·“මට දෙවරක් ඔබන්න) +pdfjs-layers-button-label = ස්තර +pdfjs-thumbs-button = + .title = සිඟිති රූ පෙන්වන්න +pdfjs-thumbs-button-label = සිඟිති රූ +pdfjs-findbar-button = + .title = ලේඛනයෙහි සොයන්න +pdfjs-findbar-button-label = සොයන්න +pdfjs-additional-layers = අතිරේක ස්තර + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = පිටුව { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = පිටුවේ සිඟිත රූව { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = සොයන්න + .placeholder = ලේඛනයේ සොයන්න… +pdfjs-find-previous-button = + .title = මෙම à·€à·à¶šà·’à¶šà¶© කලින් යෙදුණු ස්ථà·à¶±à¶º සොයන්න +pdfjs-find-previous-button-label = කලින් +pdfjs-find-next-button = + .title = මෙම à·€à·à¶šà·’à¶šà¶© à¶Šà·…à¶Ÿà¶§ යෙදෙන ස්ථà·à¶±à¶º සොයන්න +pdfjs-find-next-button-label = à¶Šà·…à¶Ÿ +pdfjs-find-highlight-checkbox = සියල්ල උද්දීපනය +pdfjs-find-entire-word-checkbox-label = සමස්ත වචන +pdfjs-find-reached-top = ලේඛනයේ මුදුනට ළඟ෠විය, à¶´à·„à·… සිට ඉහළට +pdfjs-find-reached-bottom = ලේඛනයේ අවසà·à¶±à¶ºà¶§ ළඟ෠විය, ඉහළ සිට à¶´à·„à·…à¶§ +pdfjs-find-not-found = à·€à·à¶šà·’à¶šà¶© හමු නොවුණි + +## Predefined zoom values + +pdfjs-page-scale-width = පිටුවේ à¶´à·…à¶½ +pdfjs-page-scale-auto = ස්වයංක්â€à¶»à·“ය විà·à·à¶½à¶±à¶º +pdfjs-page-scale-actual = à·ƒà·à¶¶à·‘ à¶´à·Šâ€à¶»à¶¸à·à¶«à¶º +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = පිටුව { $page } + +## Loading indicator messages + +pdfjs-loading-error = පීඩීඑෆ් පූරණය කිරීමේදී දà·à·‚යක් සිදු විය. +pdfjs-invalid-file-error = වලංගු නොවන à·„à· à·„à·à¶±à·’වූ පීඩීඑෆ් ගොනුවකි. +pdfjs-missing-file-error = මඟහà·à¶»à·”à¶«à·” පීඩීඑෆ් ගොනුවකි. +pdfjs-unexpected-response-error = අනපේක්â€à·‚à·’à¶­ සේවà·à¶¯à·à¶ºà¶š à¶´à·Šâ€à¶»à¶­à·’à¶ à·à¶»à¶ºà¶šà·’. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } + +## Password + +pdfjs-password-label = මෙම පීඩීඑෆ් ගොනුව විවෘත කිරීමට මුරපදය යොදන්න. +pdfjs-password-invalid = à·€à·à¶»à¶¯à·’ මුරපදයකි. à¶±à·à·€à¶­ à¶‹à¶­à·Šà·ƒà·à·„ කරන්න. +pdfjs-password-ok-button = හරි +pdfjs-password-cancel-button = අවලංගු +pdfjs-web-fonts-disabled = වියමන අකුරු අබලයි: පීඩීඑෆ් වෙත à¶šà·à·€à·à¶¯à·Šà¶¯à·– රුවකුරු à¶·à·à·€à·’à¶­à· à¶šà·… නොහà·à¶šà·’ය. + +## Editing + +pdfjs-editor-free-text-button = + .title = පෙළ +pdfjs-editor-free-text-button-label = පෙළ +pdfjs-editor-ink-button = + .title = අඳින්න +pdfjs-editor-ink-button-label = අඳින්න +pdfjs-editor-stamp-button = + .title = රූප සංස්කරණය à·„à· à¶‘à¶šà·Š කරන්න +pdfjs-editor-stamp-button-label = රූප සංස්කරණය à·„à· à¶‘à¶šà·Š කරන්න + +## Remove button for the various kind of editor. + + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = වර්ණය +pdfjs-editor-free-text-size-input = තරම +pdfjs-editor-ink-color-input = වර්ණය +pdfjs-editor-ink-thickness-input = à¶à¶«à¶šà¶¸ +pdfjs-free-text = + .aria-label = වදන් සකසනය +pdfjs-free-text-default-content = ලිවීීම අරඹන්න… + +## Alt-text dialog + +pdfjs-editor-alt-text-mark-decorative-description = මෙය දà·à¶» හ෠දිය සලකුණු à·€à·à¶±à·’ අලංකà·à¶» රූප සඳහ෠භà·à·€à·’ත෠වේ. + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + + +## Color picker + + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + + +## Image alt-text settings + diff --git a/public/assets/pdfjs/locale/sk/viewer.ftl b/public/assets/pdfjs/locale/sk/viewer.ftl new file mode 100755 index 0000000..5cbbb8d --- /dev/null +++ b/public/assets/pdfjs/locale/sk/viewer.ftl @@ -0,0 +1,521 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Predchádzajúca strana +pdfjs-previous-button-label = Predchádzajúca +pdfjs-next-button = + .title = Nasledujúca strana +pdfjs-next-button-label = Nasledujúca +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Strana +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = z { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } z { $pagesCount }) +pdfjs-zoom-out-button = + .title = ZmenÅ¡iÅ¥ veľkosÅ¥ +pdfjs-zoom-out-button-label = ZmenÅ¡iÅ¥ veľkosÅ¥ +pdfjs-zoom-in-button = + .title = ZväÄÅ¡iÅ¥ veľkosÅ¥ +pdfjs-zoom-in-button-label = ZväÄÅ¡iÅ¥ veľkosÅ¥ +pdfjs-zoom-select = + .title = Nastavenie veľkosti +pdfjs-presentation-mode-button = + .title = Prepnúť na režim prezentácie +pdfjs-presentation-mode-button-label = Režim prezentácie +pdfjs-open-file-button = + .title = OtvoriÅ¥ súbor +pdfjs-open-file-button-label = OtvoriÅ¥ +pdfjs-print-button = + .title = TlaÄiÅ¥ +pdfjs-print-button-label = TlaÄiÅ¥ +pdfjs-save-button = + .title = UložiÅ¥ +pdfjs-save-button-label = UložiÅ¥ +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = StiahnuÅ¥ +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = StiahnuÅ¥ +pdfjs-bookmark-button = + .title = Aktuálna stránka (zobraziÅ¥ adresu URL z aktuálnej stránky) +pdfjs-bookmark-button-label = Aktuálna stránka + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Nástroje +pdfjs-tools-button-label = Nástroje +pdfjs-first-page-button = + .title = PrejsÅ¥ na prvú stranu +pdfjs-first-page-button-label = PrejsÅ¥ na prvú stranu +pdfjs-last-page-button = + .title = PrejsÅ¥ na poslednú stranu +pdfjs-last-page-button-label = PrejsÅ¥ na poslednú stranu +pdfjs-page-rotate-cw-button = + .title = OtoÄiÅ¥ v smere hodinových ruÄiÄiek +pdfjs-page-rotate-cw-button-label = OtoÄiÅ¥ v smere hodinových ruÄiÄiek +pdfjs-page-rotate-ccw-button = + .title = OtoÄiÅ¥ proti smeru hodinových ruÄiÄiek +pdfjs-page-rotate-ccw-button-label = OtoÄiÅ¥ proti smeru hodinových ruÄiÄiek +pdfjs-cursor-text-select-tool-button = + .title = PovoliÅ¥ výber textu +pdfjs-cursor-text-select-tool-button-label = Výber textu +pdfjs-cursor-hand-tool-button = + .title = PovoliÅ¥ nástroj ruka +pdfjs-cursor-hand-tool-button-label = Nástroj ruka +pdfjs-scroll-page-button = + .title = PoužiÅ¥ rolovanie po stránkach +pdfjs-scroll-page-button-label = Rolovanie po stránkach +pdfjs-scroll-vertical-button = + .title = PoužívaÅ¥ zvislé posúvanie +pdfjs-scroll-vertical-button-label = Zvislé posúvanie +pdfjs-scroll-horizontal-button = + .title = PoužívaÅ¥ vodorovné posúvanie +pdfjs-scroll-horizontal-button-label = Vodorovné posúvanie +pdfjs-scroll-wrapped-button = + .title = PoužiÅ¥ postupné posúvanie +pdfjs-scroll-wrapped-button-label = Postupné posúvanie +pdfjs-spread-none-button = + .title = NezdružovaÅ¥ stránky +pdfjs-spread-none-button-label = Žiadne združovanie +pdfjs-spread-odd-button = + .title = Združí stránky a umiestni nepárne stránky vľavo +pdfjs-spread-odd-button-label = ZdružiÅ¥ stránky (nepárne vľavo) +pdfjs-spread-even-button = + .title = Združí stránky a umiestni párne stránky vľavo +pdfjs-spread-even-button-label = ZdružiÅ¥ stránky (párne vľavo) + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Vlastnosti dokumentu… +pdfjs-document-properties-button-label = Vlastnosti dokumentu… +pdfjs-document-properties-file-name = Názov súboru: +pdfjs-document-properties-file-size = VeľkosÅ¥ súboru: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kB ({ $b } bajtov) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bajtov) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } kB ({ $size_b } bajtov) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bajtov) +pdfjs-document-properties-title = Názov: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Predmet: +pdfjs-document-properties-keywords = KľúÄové slová: +pdfjs-document-properties-creation-date = Dátum vytvorenia: +pdfjs-document-properties-modification-date = Dátum úpravy: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Aplikácia: +pdfjs-document-properties-producer = Tvorca PDF: +pdfjs-document-properties-version = Verzia PDF: +pdfjs-document-properties-page-count = PoÄet strán: +pdfjs-document-properties-page-size = VeľkosÅ¥ stránky: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = na výšku +pdfjs-document-properties-page-size-orientation-landscape = na šírku +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = List +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Rýchle zobrazovanie z webu: +pdfjs-document-properties-linearized-yes = Ãno +pdfjs-document-properties-linearized-no = Nie +pdfjs-document-properties-close-button = ZavrieÅ¥ + +## Print + +pdfjs-print-progress-message = Príprava dokumentu na tlaÄ… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress } % +pdfjs-print-progress-close-button = ZruÅ¡iÅ¥ +pdfjs-printing-not-supported = Upozornenie: tlaÄ nie je v tomto prehliadaÄi plne podporovaná. +pdfjs-printing-not-ready = Upozornenie: súbor PDF nie je plne naÄítaný pre tlaÄ. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Prepnúť boÄný panel +pdfjs-toggle-sidebar-notification-button = + .title = Prepnúť boÄný panel (dokument obsahuje osnovu/prílohy/vrstvy) +pdfjs-toggle-sidebar-button-label = Prepnúť boÄný panel +pdfjs-document-outline-button = + .title = ZobraziÅ¥ osnovu dokumentu (dvojitým kliknutím rozbalíte/zbalíte vÅ¡etky položky) +pdfjs-document-outline-button-label = Osnova dokumentu +pdfjs-attachments-button = + .title = ZobraziÅ¥ prílohy +pdfjs-attachments-button-label = Prílohy +pdfjs-layers-button = + .title = ZobraziÅ¥ vrstvy (dvojitým kliknutím uvediete vÅ¡etky vrstvy do pôvodného stavu) +pdfjs-layers-button-label = Vrstvy +pdfjs-thumbs-button = + .title = ZobraziÅ¥ miniatúry +pdfjs-thumbs-button-label = Miniatúry +pdfjs-current-outline-item-button = + .title = NájsÅ¥ aktuálnu položku v osnove +pdfjs-current-outline-item-button-label = Aktuálna položka v osnove +pdfjs-findbar-button = + .title = HľadaÅ¥ v dokumente +pdfjs-findbar-button-label = HľadaÅ¥ +pdfjs-additional-layers = ÄŽalÅ¡ie vrstvy + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Strana { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatúra strany { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = HľadaÅ¥ + .placeholder = HľadaÅ¥ v dokumente… +pdfjs-find-previous-button = + .title = VyhľadaÅ¥ predchádzajúci výskyt reÅ¥azca +pdfjs-find-previous-button-label = Predchádzajúce +pdfjs-find-next-button = + .title = VyhľadaÅ¥ Äalší výskyt reÅ¥azca +pdfjs-find-next-button-label = ÄŽalÅ¡ie +pdfjs-find-highlight-checkbox = ZvýrazniÅ¥ vÅ¡etky +pdfjs-find-match-case-checkbox-label = RozliÅ¡ovaÅ¥ veľkosÅ¥ písmen +pdfjs-find-match-diacritics-checkbox-label = RozliÅ¡ovaÅ¥ diakritiku +pdfjs-find-entire-word-checkbox-label = Celé slová +pdfjs-find-reached-top = Bol dosiahnutý zaÄiatok stránky, pokraÄuje sa od konca +pdfjs-find-reached-bottom = Bol dosiahnutý koniec stránky, pokraÄuje sa od zaÄiatku +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] Výskyt { $current } z { $total } + [few] Výskyt { $current } z { $total } + [many] Výskyt { $current } z { $total } + *[other] Výskyt { $current } z { $total } + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Viac ako { $limit } výskyt + [few] Viac ako { $limit } výskyty + [many] Viac ako { $limit } výskytov + *[other] Viac ako { $limit } výskytov + } +pdfjs-find-not-found = Výraz nebol nájdený + +## Predefined zoom values + +pdfjs-page-scale-width = Na šírku strany +pdfjs-page-scale-fit = Na veľkosÅ¥ strany +pdfjs-page-scale-auto = Automatická veľkosÅ¥ +pdfjs-page-scale-actual = SkutoÄná veľkosÅ¥ +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale } % + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Strana { $page } + +## Loading indicator messages + +pdfjs-loading-error = PoÄas naÄítavania dokumentu PDF sa vyskytla chyba. +pdfjs-invalid-file-error = Neplatný alebo poÅ¡kodený súbor PDF. +pdfjs-missing-file-error = Chýbajúci súbor PDF. +pdfjs-unexpected-response-error = NeoÄakávaná odpoveÄ zo servera. +pdfjs-rendering-error = Pri vykresľovaní stránky sa vyskytla chyba. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Anotácia typu { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Ak chcete otvoriÅ¥ tento súbor PDF, zadajte jeho heslo. +pdfjs-password-invalid = Heslo nie je platné. Skúste to znova. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = ZruÅ¡iÅ¥ +pdfjs-web-fonts-disabled = Webové písma sú vypnuté: nie je možné použiÅ¥ písma vložené do súboru PDF. + +## Editing + +pdfjs-editor-free-text-button = + .title = Text +pdfjs-editor-free-text-button-label = Text +pdfjs-editor-ink-button = + .title = KresliÅ¥ +pdfjs-editor-ink-button-label = KresliÅ¥ +pdfjs-editor-stamp-button = + .title = PridaÅ¥ alebo upraviÅ¥ obrázky +pdfjs-editor-stamp-button-label = PridaÅ¥ alebo upraviÅ¥ obrázky +pdfjs-editor-highlight-button = + .title = ZvýrazniÅ¥ +pdfjs-editor-highlight-button-label = ZvýrazniÅ¥ +pdfjs-highlight-floating-button1 = + .title = ZvýrazniÅ¥ + .aria-label = ZvýrazniÅ¥ +pdfjs-highlight-floating-button-label = ZvýrazniÅ¥ + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = OdstrániÅ¥ kresbu +pdfjs-editor-remove-freetext-button = + .title = OdstrániÅ¥ text +pdfjs-editor-remove-stamp-button = + .title = OdstrániÅ¥ obrázok +pdfjs-editor-remove-highlight-button = + .title = OdstrániÅ¥ zvýraznenie + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Farba +pdfjs-editor-free-text-size-input = VeľkosÅ¥ +pdfjs-editor-ink-color-input = Farba +pdfjs-editor-ink-thickness-input = Hrúbka +pdfjs-editor-ink-opacity-input = PriehľadnosÅ¥ +pdfjs-editor-stamp-add-image-button = + .title = PridaÅ¥ obrázok +pdfjs-editor-stamp-add-image-button-label = PridaÅ¥ obrázok +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Hrúbka +pdfjs-editor-free-highlight-thickness-title = + .title = Zmeňte hrúbku pre zvýrazňovanie iných položiek ako textu +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Textový editor + .default-content = ZaÄnite písať… +pdfjs-free-text = + .aria-label = Textový editor +pdfjs-free-text-default-content = ZaÄnite písať… +pdfjs-ink = + .aria-label = Editor kreslenia +pdfjs-ink-canvas = + .aria-label = Obrázok vytvorený používateľom + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alternatívny text +pdfjs-editor-alt-text-edit-button = + .aria-label = UpraviÅ¥ alternatívny text +pdfjs-editor-alt-text-edit-button-label = UpraviÅ¥ alternatívny text +pdfjs-editor-alt-text-dialog-label = Vyberte možnosÅ¥ +pdfjs-editor-alt-text-dialog-description = Alternatívny text (alt text) pomáha, keÄ Ä¾udia obrázok nevidia alebo sa nenaÄítava. +pdfjs-editor-alt-text-add-description-label = PridaÅ¥ popis +pdfjs-editor-alt-text-add-description-description = Zamerajte sa na 1-2 vety, ktoré popisujú predmet, prostredie alebo akcie. +pdfjs-editor-alt-text-mark-decorative-label = OznaÄiÅ¥ ako dekoratívny +pdfjs-editor-alt-text-mark-decorative-description = Používa sa na ozdobné obrázky, ako sú okraje alebo vodoznaky. +pdfjs-editor-alt-text-cancel-button = ZruÅ¡iÅ¥ +pdfjs-editor-alt-text-save-button = UložiÅ¥ +pdfjs-editor-alt-text-decorative-tooltip = OznaÄený ako dekoratívny +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Napríklad: „Mladý muž si sadá za stôl, aby sa najedol“ +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alternatívny text + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Ľavý horný roh – zmena veľkosti +pdfjs-editor-resizer-label-top-middle = Horný stred – zmena veľkosti +pdfjs-editor-resizer-label-top-right = Pravý horný roh – zmena veľkosti +pdfjs-editor-resizer-label-middle-right = Vpravo uprostred – zmena veľkosti +pdfjs-editor-resizer-label-bottom-right = Pravý dolný roh – zmena veľkosti +pdfjs-editor-resizer-label-bottom-middle = Stred dole – zmena veľkosti +pdfjs-editor-resizer-label-bottom-left = Ľavý dolný roh – zmena veľkosti +pdfjs-editor-resizer-label-middle-left = Vľavo uprostred – zmena veľkosti +pdfjs-editor-resizer-top-left = + .aria-label = Ľavý horný roh – zmena veľkosti +pdfjs-editor-resizer-top-middle = + .aria-label = Horný stred – zmena veľkosti +pdfjs-editor-resizer-top-right = + .aria-label = Pravý horný roh – zmena veľkosti +pdfjs-editor-resizer-middle-right = + .aria-label = Vpravo uprostred – zmena veľkosti +pdfjs-editor-resizer-bottom-right = + .aria-label = Pravý dolný roh – zmena veľkosti +pdfjs-editor-resizer-bottom-middle = + .aria-label = Stred dole – zmena veľkosti +pdfjs-editor-resizer-bottom-left = + .aria-label = Ľavý dolný roh – zmena veľkosti +pdfjs-editor-resizer-middle-left = + .aria-label = Vľavo uprostred – zmena veľkosti + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Farba zvýraznenia +pdfjs-editor-colorpicker-button = + .title = ZmeniÅ¥ farbu +pdfjs-editor-colorpicker-dropdown = + .aria-label = Výber farieb +pdfjs-editor-colorpicker-yellow = + .title = Žltá +pdfjs-editor-colorpicker-green = + .title = Zelená +pdfjs-editor-colorpicker-blue = + .title = Modrá +pdfjs-editor-colorpicker-pink = + .title = Ružová +pdfjs-editor-colorpicker-red = + .title = ÄŒervená + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = ZobraziÅ¥ vÅ¡etko +pdfjs-editor-highlight-show-all-button = + .title = ZobraziÅ¥ vÅ¡etko + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = UpraviÅ¥ alternatívny text (popis obrázka) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = PridaÅ¥ alternatívny text (popis obrázka) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Sem napíšte svoj popis… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Krátky popis pre ľudí, ktorí nevidia obrázok alebo ak sa obrázok nenaÄíta. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Tento alternatívny text bol vytvorený automaticky a môže byÅ¥ nepresný. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = ÄŽalÅ¡ie informácie +pdfjs-editor-new-alt-text-create-automatically-button-label = Automaticky vytvoriÅ¥ alternatívny text +pdfjs-editor-new-alt-text-not-now-button = Teraz nie +pdfjs-editor-new-alt-text-error-title = Alternatívny text sa nepodarilo vytvoriÅ¥ automaticky +pdfjs-editor-new-alt-text-error-description = Napíšte svoj vlastný alternatívny text alebo to skúste znova neskôr. +pdfjs-editor-new-alt-text-error-close-button = ZavrieÅ¥ +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = SÅ¥ahuje sa model AI pre alternatívne texty ({ $downloadedSize } z { $totalSize } MB) + .aria-valuetext = SÅ¥ahuje sa model AI pre alternatívne texty ({ $downloadedSize } z { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alternatívny text bol pridaný +pdfjs-editor-new-alt-text-added-button-label = Alternatívny text bol pridaný +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Chýbajúci alternatívny text +pdfjs-editor-new-alt-text-missing-button-label = Chýbajúci alternatívny text +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = SkontrolovaÅ¥ alternatívny text +pdfjs-editor-new-alt-text-to-review-button-label = SkontrolovaÅ¥ alternatívny text +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Vytvorené automaticky: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Nastavenia alternatívneho textu obrázka +pdfjs-image-alt-text-settings-button-label = Nastavenia alternatívneho textu obrázka +pdfjs-editor-alt-text-settings-dialog-label = Nastavenia alternatívneho textu obrázka +pdfjs-editor-alt-text-settings-automatic-title = Automatický alternatívny text +pdfjs-editor-alt-text-settings-create-model-button-label = Automaticky vytvoriÅ¥ alternatívny text +pdfjs-editor-alt-text-settings-create-model-description = Navrhuje popisy, ktoré pomôžu ľuÄom, ktorým sa obrázok nezobrazuje alebo ak sa obrázok nenaÄíta. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model AI pre alternatívne texty ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Beží lokálne na vaÅ¡om zariadení, takže vaÅ¡e dáta zostanú súkromné. Vyžaduje sa pre automatický alternatívny text. +pdfjs-editor-alt-text-settings-delete-model-button = OdstrániÅ¥ +pdfjs-editor-alt-text-settings-download-model-button = StiahnuÅ¥ +pdfjs-editor-alt-text-settings-downloading-model-button = SÅ¥ahuje sa… +pdfjs-editor-alt-text-settings-editor-title = Editor alternatívneho textu +pdfjs-editor-alt-text-settings-show-dialog-button-label = Pri pridávaní obrázka ihneÄ zobraziÅ¥ editor alternatívneho textu +pdfjs-editor-alt-text-settings-show-dialog-description = Pomáha vám zabezpeÄiÅ¥, aby vÅ¡etky vaÅ¡e obrázky mali alternatívny text. +pdfjs-editor-alt-text-settings-close-button = ZavrieÅ¥ + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Zvýraznenie bolo odstránené +pdfjs-editor-undo-bar-message-freetext = Text bol odstránený +pdfjs-editor-undo-bar-message-ink = Kreslenie bolo odstránené +pdfjs-editor-undo-bar-message-stamp = Obrázok bol odstránený +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } anotácia odstránená + [few] { $count } anotácie odstránené + [many] { $count } anotácií odstránených + *[other] { $count } anotácií odstránených + } +pdfjs-editor-undo-bar-undo-button = + .title = Späť +pdfjs-editor-undo-bar-undo-button-label = Späť +pdfjs-editor-undo-bar-close-button = + .title = ZavrieÅ¥ +pdfjs-editor-undo-bar-close-button-label = ZavrieÅ¥ diff --git a/public/assets/pdfjs/locale/skr/viewer.ftl b/public/assets/pdfjs/locale/skr/viewer.ftl new file mode 100755 index 0000000..2d0e87f --- /dev/null +++ b/public/assets/pdfjs/locale/skr/viewer.ftl @@ -0,0 +1,498 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = پچھلا ÙˆØ±Ù‚Û +pdfjs-previous-button-label = پچھلا +pdfjs-next-button = + .title = اڳلا ÙˆØ±Ù‚Û +pdfjs-next-button-label = اڳلا +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = ÙˆØ±Ù‚Û +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = { $pagesCount } دا +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } دا { $pagesCount }) +pdfjs-zoom-out-button = + .title = زوم آؤٹ +pdfjs-zoom-out-button-label = زوم آؤٹ +pdfjs-zoom-in-button = + .title = زوم اÙÙ† +pdfjs-zoom-in-button-label = زوم اÙÙ† +pdfjs-zoom-select = + .title = زوم +pdfjs-presentation-mode-button = + .title = پریزنٹیشن موڈ تے سوئچ کرو +pdfjs-presentation-mode-button-label = پریزنٹیشن موڈ +pdfjs-open-file-button = + .title = ÙØ§Ø¦Ù„ کھولو +pdfjs-open-file-button-label = کھولو +pdfjs-print-button = + .title = چھاپو +pdfjs-print-button-label = چھاپو +pdfjs-save-button = + .title = ÛØªÚ¾ÛŒÚ©Ú‘ا کرو +pdfjs-save-button-label = ÛØªÚ¾ÛŒÚ©Ú‘ا کرو +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = ڈاؤن لوڈ +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = ڈاؤن لوڈ +pdfjs-bookmark-button = + .title = Ù…ÙˆØ¬ÙˆØ¯Û ÙˆØ±Ù‚Û (Ù…ÙˆØ¬ÙˆØ¯Û ÙˆØ±Ù‚Û’ کنوں یوآرایل ݙیکھو) +pdfjs-bookmark-button-label = Ù…ÙˆØ¬ÙˆØ¯Û ÙˆØ±Ù‚Û + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = اوزار +pdfjs-tools-button-label = اوزار +pdfjs-first-page-button = + .title = Ù¾ÛÙ„Û’ ورقے تے ونڄو +pdfjs-first-page-button-label = Ù¾ÛÙ„Û’ ورقے تے ونڄو +pdfjs-last-page-button = + .title = چھیکڑی ورقے تے ونڄو +pdfjs-last-page-button-label = چھیکڑی ورقے تے ونڄو +pdfjs-page-rotate-cw-button = + .title = Ú¯Ú¾Ú‘ÛŒ وانگوں گھماؤ +pdfjs-page-rotate-cw-button-label = Ú¯Ú¾Ú‘ÛŒ وانگوں گھماؤ +pdfjs-page-rotate-ccw-button = + .title = Ú¯Ú¾Ú‘ÛŒ تے اÙپٹھ گھماؤ +pdfjs-page-rotate-ccw-button-label = Ú¯Ú¾Ú‘ÛŒ تے اÙپٹھ گھماؤ +pdfjs-cursor-text-select-tool-button = + .title = متن منتخب کݨ والا Ø¢Ù„Û ÙØ¹Ø§Ù„ بݨاؤ +pdfjs-cursor-text-select-tool-button-label = متن منتخب کرݨ والا Ø¢Ù„Û +pdfjs-cursor-hand-tool-button = + .title = Ûینڈ ٹول ÙØ¹Ø§Ù„ بݨاؤ +pdfjs-cursor-hand-tool-button-label = Ûینڈ ٹول +pdfjs-scroll-page-button = + .title = پیج سکرولنگ استعمال کرو +pdfjs-scroll-page-button-label = پیج سکرولنگ +pdfjs-scroll-vertical-button = + .title = عمودی سکرولنگ استعمال کرو +pdfjs-scroll-vertical-button-label = عمودی سکرولنگ +pdfjs-scroll-horizontal-button = + .title = اÙÙ‚ÛŒ سکرولنگ استعمال کرو +pdfjs-scroll-horizontal-button-label = اÙÙ‚ÛŒ سکرولنگ +pdfjs-scroll-wrapped-button = + .title = ویڑھی Ûوئی سکرولنگ استعمال کرو +pdfjs-scroll-wrapped-button-label = ÙˆÛÚ‘Ú¾ÛŒ Ûوئی سکرولنگ +pdfjs-spread-none-button = + .title = پیج سپریڈز ÙˆÙÚ† شامل Ù†Û ØªÚ¾ÛŒÙˆÙˆÛ” +pdfjs-spread-none-button-label = کوئی پولھ کائنی +pdfjs-spread-odd-button = + .title = طاق نمبر والے ورقیاں دے نال شروع تھیوݨ والے پیج سپریڈز ÙˆÙÚ† شامل تھیوو۔ +pdfjs-spread-odd-button-label = تاک پھیلاؤ +pdfjs-spread-even-button = + .title = Ø¬ÙØª نمر والے ورقیاں نال شروع تھیوݨ والے پیج سپریڈز و٠شامل تھیوو۔ +pdfjs-spread-even-button-label = Ø¬ÙØª پھیلاؤ + +## Document properties dialog + +pdfjs-document-properties-button = + .title = دستاویز خواص… +pdfjs-document-properties-button-label = دستاویز خواص … +pdfjs-document-properties-file-name = ÙØ§Ø¦Ù„ دا ناں: +pdfjs-document-properties-file-size = ÙØ§Ø¦Ù„ دا سائز: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } بائٹاں) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } بائٹاں) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } Ú©Û’ بی ({ $size_b } بائٹس) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } ایم بی ({ $size_b } بائٹس) +pdfjs-document-properties-title = عنوان: +pdfjs-document-properties-author = تخلیق کار: +pdfjs-document-properties-subject = موضوع: +pdfjs-document-properties-keywords = کلیدی Ø§Ù„ÙØ§Ø¸: +pdfjs-document-properties-creation-date = تخلیق دی تاریخ: +pdfjs-document-properties-modification-date = ترمیم دی تاریخ: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = تخلیق کار: +pdfjs-document-properties-producer = PDF پیدا کار: +pdfjs-document-properties-version = PDF ورژن: +pdfjs-document-properties-page-count = ÙˆØ±Ù‚Û Ø´Ù…Ø§Ø±ÛŒ: +pdfjs-document-properties-page-size = ÙˆØ±Ù‚Û Ø¯ÛŒ سائز: +pdfjs-document-properties-page-size-unit-inches = ÙˆÙÚ† +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = عمودی انداز +pdfjs-document-properties-page-size-orientation-landscape = اÙقى انداز +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = لیٹر +pdfjs-document-properties-page-size-name-legal = قنونی + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = تکھا ویب نظارÛ: +pdfjs-document-properties-linearized-yes = جیا +pdfjs-document-properties-linearized-no = Ú©Ùˆ +pdfjs-document-properties-close-button = بند کرو + +## Print + +pdfjs-print-progress-message = چھاپݨ کیتے دستاویز تیار تھیندے پئے ÛÙ† … +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = منسوخ کرو +pdfjs-printing-not-supported = چتاوݨی: چھپائی ایں براؤزر تے پوری طراں معاونت Ø´Ø¯Û Ú©Ø§Ø¦Ù†ÛŒÛ” +pdfjs-printing-not-ready = چتاوݨی: PDF چھپائی کیتے پوری طراں لوڈ نئیں تھئی۔ + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = سائیڈ بار ٹوگل کرو +pdfjs-toggle-sidebar-notification-button = + .title = سائیڈ بار ٹوگل کرو (دستاویز ÙˆÙÚ† آؤٹ لائن/ منسلکات/ پرتاں شامل ÛÙ†) +pdfjs-toggle-sidebar-button-label = سائیڈ بار ٹوگل کرو +pdfjs-document-outline-button = + .title = دستاویز دا Ø®Ø§Ú©Û Ý™Ú©Ú¾Ø§Ø¤ (تمام آئٹمز Ú©ÙˆÚº پھیلاوݨ/سنگوڑݨ کیتے ڈبل Ú©Ù„Ú© کرو) +pdfjs-document-outline-button-label = دستاویز آؤٹ لائن +pdfjs-attachments-button = + .title = نتھیاں ݙکھاؤ +pdfjs-attachments-button-label = منسلکات +pdfjs-layers-button = + .title = پرتاں ݙکھاؤ (تمام پرتاں Ú©ÙˆÚº ÚˆÛŒÙØ§Ù„Ù¹ حالت ÙˆÙÚ† Ø¯ÙˆØ¨Ø§Ø±Û ØªØ±ØªÛŒØ¨ ݙیوݨ کیتے ڈبل Ú©Ù„Ú© کرو) +pdfjs-layers-button-label = پرتاں +pdfjs-thumbs-button = + .title = تھمبنیل ݙکھاؤ +pdfjs-thumbs-button-label = تھمبنیلز +pdfjs-current-outline-item-button = + .title = Ù…ÙˆØ¬ÙˆØ¯Û Ø¢Ø¤Ù¹ لائن آئٹم لبھو +pdfjs-current-outline-item-button-label = Ù…ÙˆØ¬ÙˆØ¯Û Ø¢Ø¤Ù¹ لائن آئٹم +pdfjs-findbar-button = + .title = دستاویز ÙˆÙÚ† لبھو +pdfjs-findbar-button-label = لبھو +pdfjs-additional-layers = اضاÙÛŒ پرتاں + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = ÙˆØ±Ù‚Û { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = ورقے دا تھمبنیل { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = لبھو + .placeholder = دستاویز ÙˆÙÚ† لبھو … +pdfjs-find-previous-button = + .title = Ùقرے دا پچھلا ÙˆØ§Ù‚Ø¹Û Ù„Ø¨Ú¾Ùˆ +pdfjs-find-previous-button-label = پچھلا +pdfjs-find-next-button = + .title = Ùقرے دا اڳلا ÙˆØ§Ù‚Ø¹Û Ù„Ø¨Ú¾Ùˆ +pdfjs-find-next-button-label = اڳلا +pdfjs-find-highlight-checkbox = تمام نشابر کرو +pdfjs-find-match-case-checkbox-label = Ø­Ø±ÙˆÙ Ù…Ø´Ø§Ø¨Û Ú©Ø±Ùˆ +pdfjs-find-match-diacritics-checkbox-label = ڈائیکرٹکس Ù…Ø´Ø§Ø¨Û Ú©Ø±Ùˆ +pdfjs-find-entire-word-checkbox-label = تمام Ø§Ù„ÙØ§Ø¸ +pdfjs-find-reached-top = ورقے دے شروع تے Ù¾ÙØ¬ ڳیا، تلوں جاری کیتا ڳیا +pdfjs-find-reached-bottom = ورقے دے پاند تے Ù¾ÙÚ„ ڳیا، Ø§ÙØªÙˆÚº شروع کیتا ڳیا +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $total } ÙˆÙÚ†ÙˆÚº { $current } Ù…Ø´Ø§Ø¨Û + *[other] { $total } ÙˆÙÚ†ÙˆÚº { $current } مشابے + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] { $limit } توں ودھ مماثلت۔ + *[other] { $limit } توں ودھ مماثلتاں۔ + } +pdfjs-find-not-found = ÙÙ‚Ø±Û Ù†Ø¦ÛŒÚº ملیا + +## Predefined zoom values + +pdfjs-page-scale-width = ورقے دی چوڑائی +pdfjs-page-scale-fit = ÙˆØ±Ù‚Û Ùٹنگ +pdfjs-page-scale-auto = آپوں آپ زوم +pdfjs-page-scale-actual = اصل میچا +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = ÙˆØ±Ù‚Û { $page } + +## Loading indicator messages + +pdfjs-loading-error = PDF لوڈ کریندے ویلھے نقص Ø¢ ڳیا۔ +pdfjs-invalid-file-error = غلط یا خراب Ø´Ø¯Û PDF ÙØ§Ø¦Ù„Û” +pdfjs-missing-file-error = PDF ÙØ§Ø¦Ù„ غائب ÛÛ’Û” +pdfjs-unexpected-response-error = سرور دا غیر متوقع جواب۔ +pdfjs-rendering-error = ÙˆØ±Ù‚Û Ø±ÛŒÙ†ÚˆØ± کریندے ویلھے ÛÚ© خرابی پیش آڳئی۔ + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } تشریح] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Ø§ÛŒÛ PDF ÙØ§Ø¦Ù„ کھولݨ کیتے پاس ورڈ درج کرو۔ +pdfjs-password-invalid = غلط پاس ورڈ: Ø¨Ø±Ø§Û Ù…ÛØ±Ø¨Ø§Ù†ÛŒ ولدا کوشش کرو۔ +pdfjs-password-ok-button = ٹھیک ÛÛ’ +pdfjs-password-cancel-button = منسوخ کرو +pdfjs-web-fonts-disabled = ویب Ùونٹس غیر ÙØ¹Ø§Ù„ ÛÙ†: ایمبیڈڈ PDF Ùونٹس استعمال کرݨ کنوں قاصر ÛÙ† + +## Editing + +pdfjs-editor-free-text-button = + .title = متن +pdfjs-editor-free-text-button-label = متن +pdfjs-editor-ink-button = + .title = Ú†Ú¾Ú©Ùˆ +pdfjs-editor-ink-button-label = Ú†Ú¾Ú©Ùˆ +pdfjs-editor-stamp-button = + .title = تصویراں Ú©ÙˆÚº شامل کرو یا ترمیم کرو +pdfjs-editor-stamp-button-label = تصویراں Ú©ÙˆÚº شامل کرو یا ترمیم کرو +pdfjs-editor-highlight-button = + .title = نمایاں کرو +pdfjs-editor-highlight-button-label = نمایاں کرو +pdfjs-highlight-floating-button1 = + .title = نمایاں کرو + .aria-label = نمایاں کرو +pdfjs-highlight-floating-button-label = نمایاں کرو + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = ڈرائینگ Ûٹاؤ +pdfjs-editor-remove-freetext-button = + .title = متن Ûٹاؤ +pdfjs-editor-remove-stamp-button = + .title = تصویر Ûٹاؤ +pdfjs-editor-remove-highlight-button = + .title = نمایاں Ûٹاؤ + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = رنگ +pdfjs-editor-free-text-size-input = سائز +pdfjs-editor-ink-color-input = رنگ +pdfjs-editor-ink-thickness-input = ٹھولھ +pdfjs-editor-ink-opacity-input = دھندلاپن +pdfjs-editor-stamp-add-image-button = + .title = تصویر شامل کرو +pdfjs-editor-stamp-add-image-button-label = تصویر شامل کرو +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Ù…Ùٹاݨ +pdfjs-editor-free-highlight-thickness-title = + .title = متن توں ان٘ج ٻئے شئیں Ú©ÙˆÚº نمایاں کرݨ ویلے Ù…Ùٹاݨ Ú©ÙˆÚº بدلو +pdfjs-free-text = + .aria-label = ٹیکسٹ ایڈیٹر +pdfjs-free-text-default-content = ٹائپنگ شروع کرو … +pdfjs-ink = + .aria-label = ڈرا ایڈیٹر +pdfjs-ink-canvas = + .aria-label = صار٠دی بݨائی Ûوئی تصویر + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alt متن +pdfjs-editor-alt-text-edit-button-label = alt متن ÙˆÙÚ† ترمیم کرو +pdfjs-editor-alt-text-dialog-label = ÛÙÚ© اختیار Ú†Ùݨو +pdfjs-editor-alt-text-dialog-description = Alt متن (متبادل متن) اÙیں ویلے مَدَت کرین٘دا ÛÙÛ’ جÛڑیلے لوک تصویر Ú©ÙˆÚº Ù†Ùھیں ݙیکھ سڳدے یا جÛڑیلے اÙÛŒÛ Ù„ÙˆÚˆ کائنی تÙھین٘دا۔ +pdfjs-editor-alt-text-add-description-label = ØªÙØµÛŒÙ„ شامل کرو +pdfjs-editor-alt-text-add-description-description = 1-2 جملیاں دا مقصد جÛÚ‘Û’ موضوع، ترتیب، یا اعمال Ú©ÙˆÚº بیان کرین٘دے ÛÙÙ†Û” +pdfjs-editor-alt-text-mark-decorative-label = آرائشی طور تے نشان زد کرو +pdfjs-editor-alt-text-mark-decorative-description = اÙÛŒÛ Ø¢Ø±Ø§Ø¦Ø´ÛŒ تصویراں Ú©Ùیتے استعمال تÙھین٘دا ÛÙÛ’ØŒ جیویں بارڈر یا واٹر مارکس۔ +pdfjs-editor-alt-text-cancel-button = منسوخ +pdfjs-editor-alt-text-save-button = محÙوظ +pdfjs-editor-alt-text-decorative-tooltip = آرائشی دے طور تے نشان زد تÙÚ¾ÛŒ Ú³Ùیا +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = مثال دے طور تے، "ÛÙÚ© جؤان کھاݨاں کھاوݨ Ú©Ùیتے میز Ø§ÙØªÙ‘Û’ ٻیٹھا ÛÙÛ’" +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alt متن + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Ø§ÙØªÙ„ÛŒ کَھٻّی Ù†ÙÚ©Ù‘Ú‘ — سائز بدلو +pdfjs-editor-resizer-label-top-middle = Ø§ÙØªÙ„ا ÙˆÙچلا — سائز بدلو +pdfjs-editor-resizer-label-top-right = Ø§ÙØªÙ„ÛŒ سَڄّی Ù†ÙÚ©Ù‘ÙŽÚ‘ — سائز بدلو +pdfjs-editor-resizer-label-middle-right = ÙˆÙچلا سڄّا — سائز بدلو +pdfjs-editor-resizer-label-bottom-right = تلوÙیں سَڄّی Ù†ÙÚ©Ù‘ÙŽÚ‘ — سائز بدلو +pdfjs-editor-resizer-label-bottom-middle = تلواں ÙˆÙچلا — سائز بدلو +pdfjs-editor-resizer-label-bottom-left = تلوÙیں کَھٻّی Ù†ÙÚ©Ù‘Ú‘ — سائز بدلو +pdfjs-editor-resizer-label-middle-left = ÙˆÙچلا کَھٻّا — سائز بدلو +pdfjs-editor-resizer-top-left = + .aria-label = Ø§ÙØªÙ„ÛŒ کَھٻّی Ù†ÙÚ©Ù‘Ú‘ — سائز بدلو +pdfjs-editor-resizer-top-middle = + .aria-label = Ø§ÙØªÙ„ا ÙˆÙچلا — سائز بدلو +pdfjs-editor-resizer-top-right = + .aria-label = Ø§ÙØªÙ„ÛŒ سَڄّی Ù†ÙÚ©Ù‘ÙŽÚ‘ — سائز بدلو +pdfjs-editor-resizer-middle-right = + .aria-label = ÙˆÙچلا سڄّا — سائز بدلو +pdfjs-editor-resizer-bottom-right = + .aria-label = تلوÙیں سَڄّی Ù†ÙÚ©Ù‘ÙŽÚ‘ — سائز بدلو +pdfjs-editor-resizer-bottom-middle = + .aria-label = تلواں ÙˆÙچلا — سائز بدلو +pdfjs-editor-resizer-bottom-left = + .aria-label = تلوÙیں کَھٻّی Ù†ÙÚ©Ù‘Ú‘ — سائز بدلو +pdfjs-editor-resizer-middle-left = + .aria-label = ÙˆÙچلا کَھٻّا — سائز بدلو + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = نشابر رنگ +pdfjs-editor-colorpicker-button = + .title = رنگ بدلو +pdfjs-editor-colorpicker-dropdown = + .aria-label = رنگ اختیارات +pdfjs-editor-colorpicker-yellow = + .title = پیلا +pdfjs-editor-colorpicker-green = + .title = ساوا +pdfjs-editor-colorpicker-blue = + .title = نیلا +pdfjs-editor-colorpicker-pink = + .title = گلابی +pdfjs-editor-colorpicker-red = + .title = لال + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = سارے ݙکھاؤ +pdfjs-editor-highlight-show-all-button = + .title = سارے ݙکھاؤ + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = آلٹ عبارت ÙˆÚ† تبدیلی کرو (تصویر ØªÙØµÛŒÙ„) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = آلٹ عبارت شامل کرو (تصویر ØªÙØµÛŒÙ„) +pdfjs-editor-new-alt-text-textarea = + .placeholder = اتھ آپݨی وضاحت Ù„Ú©Ú¾ÙˆÛ”Û”Û” +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = اÙÙ†ÛØ§Úº لوکاں کیتے مختصر ØªÙØµÛŒÙ„ جÛÚ‘Û’ تصویر کائنی ݙیکھ سڳدے یا ڄݙݨ تصویر لوڈ کائبی تھیندی۔ +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = آلٹ عبارت خودکار تخلیق تھئی ÛÛ’ تے غلط تھی سڳدی ÛÛ’Û” +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = ٻیا سÙÚ©Ú¾Ùˆ +pdfjs-editor-new-alt-text-create-automatically-button-label = آلٹ عبارت خودکار بݨاؤ +pdfjs-editor-new-alt-text-not-now-button = Ûݨ کائناں +pdfjs-editor-new-alt-text-error-title = آلٹ عبارت خودکار Ù†Û Ø¨Ý¨Ø§Ø¤ +pdfjs-editor-new-alt-text-error-description = سوÛݨا، آپݨی آلٹ عبارت Ù„Ú©Ú¾Ùˆ یا ولدا بعد ÙˆÚ† کوشش کرو۔ +pdfjs-editor-new-alt-text-error-close-button = بند کرو +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = آلٹ عبارت اے آئی ماڈل({ $totalSize }ایم بی دے { $downloadedSize }) ڈاؤن لوڈ تھیندا پئے + .aria-valuetext = آلٹ عبارت اے آئی ماڈل({ $totalSize }ایم بی دے { $downloadedSize }) ڈاؤن لوڈ تھیندا پئے +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = آلٹ عبارت شامل تھی ڳئی +pdfjs-editor-new-alt-text-added-button-label = آلٹ عبارت شامل تھی ڳئی +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = متبادل عبارت غائب ÛÛ’ +pdfjs-editor-new-alt-text-missing-button-label = متبادل عبارت غائب ÛÛ’ +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = alt متن تے نظرثانی کرو +pdfjs-editor-new-alt-text-to-review-button-label = alt متن تے نظرثانی کرو +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = خودکار تخلیق تھئی: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = تصویر آلٹ عبارت ترتیباں +pdfjs-image-alt-text-settings-button-label = تصویر آلٹ عبارت ترتیباں +pdfjs-editor-alt-text-settings-dialog-label = تصویر آلٹ عبارت ترتیباں +pdfjs-editor-alt-text-settings-automatic-title = خودکار آلٹ عبارت +pdfjs-editor-alt-text-settings-create-model-button-label = آلٹ عبارت خودکار بݨاؤ +pdfjs-editor-alt-text-settings-create-model-description = اÙÙ†ÛØ§Úº لوکاں دی مدد کیتے ØªÙØµÛŒÙ„ تجویز کرو جÛÚ‘Û’ تصویر کائنی ݙیکھ سڳدے یا ڄݙݨ تصویر لوڈ کائبی تھیندی۔ +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = آلٹ عبارت اے آئی ماڈل ({ $totalSize } ایم بی) +pdfjs-editor-alt-text-settings-ai-model-description = ØªÛØ§Ý™ÛŒ ڈیوائس تے مقامی طور تے چلدا ÛÛ’ تاں جو ØªÛØ§Ý™Ø§ ڈیٹا نجی رÛÙˆÛ’Û” خودکار آلٹ عبارت کیتے ضروری ÛÛ’Û” +pdfjs-editor-alt-text-settings-delete-model-button = مٹاؤ +pdfjs-editor-alt-text-settings-download-model-button = ڈاؤن لوڈ +pdfjs-editor-alt-text-settings-downloading-model-button = ڈاؤن لوڈ تھیندا پئے … +pdfjs-editor-alt-text-settings-editor-title = متبادل ٹیکسٹ ایڈیٹر +pdfjs-editor-alt-text-settings-show-dialog-button-label = تصویر شامل کرݨ ویلے Ùوری طور تے آلٹ ٹیکسٹ ایڈیٹر ݙکھاؤ +pdfjs-editor-alt-text-settings-show-dialog-description = Ø§ÛŒÛ ØªÛØ§Ú©ÙˆÚº یقینی بݨاوݨ ÙˆÚ† مدد کریندے جو ØªÛØ§Ý™ÛŒØ§Úº ساریاں تصویراں ÙˆÚ† آلٹ عبارت ÛÛ’Û” +pdfjs-editor-alt-text-settings-close-button = بند کرو + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-undo-button = + .title = کیتا اݨ کیتا +pdfjs-editor-undo-bar-undo-button-label = کیتا اݨ کیتا +pdfjs-editor-undo-bar-close-button = + .title = بند کرو +pdfjs-editor-undo-bar-close-button-label = بند کرو diff --git a/public/assets/pdfjs/locale/sl/viewer.ftl b/public/assets/pdfjs/locale/sl/viewer.ftl new file mode 100755 index 0000000..4e004bd --- /dev/null +++ b/public/assets/pdfjs/locale/sl/viewer.ftl @@ -0,0 +1,521 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = PrejÅ¡nja stran +pdfjs-previous-button-label = Nazaj +pdfjs-next-button = + .title = Naslednja stran +pdfjs-next-button-label = Naprej +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Stran +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = od { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } od { $pagesCount }) +pdfjs-zoom-out-button = + .title = PomanjÅ¡aj +pdfjs-zoom-out-button-label = PomanjÅ¡aj +pdfjs-zoom-in-button = + .title = PoveÄaj +pdfjs-zoom-in-button-label = PoveÄaj +pdfjs-zoom-select = + .title = PoveÄava +pdfjs-presentation-mode-button = + .title = Preklopi v naÄin predstavitve +pdfjs-presentation-mode-button-label = NaÄin predstavitve +pdfjs-open-file-button = + .title = Odpri datoteko +pdfjs-open-file-button-label = Odpri +pdfjs-print-button = + .title = Natisni +pdfjs-print-button-label = Natisni +pdfjs-save-button = + .title = Shrani +pdfjs-save-button-label = Shrani +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Prenesi +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Prenesi +pdfjs-bookmark-button = + .title = Trenutna stran (prikaži URL, ki vodi do trenutne strani) +pdfjs-bookmark-button-label = Na trenutno stran + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Orodja +pdfjs-tools-button-label = Orodja +pdfjs-first-page-button = + .title = Pojdi na prvo stran +pdfjs-first-page-button-label = Pojdi na prvo stran +pdfjs-last-page-button = + .title = Pojdi na zadnjo stran +pdfjs-last-page-button-label = Pojdi na zadnjo stran +pdfjs-page-rotate-cw-button = + .title = Zavrti v smeri urnega kazalca +pdfjs-page-rotate-cw-button-label = Zavrti v smeri urnega kazalca +pdfjs-page-rotate-ccw-button = + .title = Zavrti v nasprotni smeri urnega kazalca +pdfjs-page-rotate-ccw-button-label = Zavrti v nasprotni smeri urnega kazalca +pdfjs-cursor-text-select-tool-button = + .title = OmogoÄi orodje za izbor besedila +pdfjs-cursor-text-select-tool-button-label = Orodje za izbor besedila +pdfjs-cursor-hand-tool-button = + .title = OmogoÄi roko +pdfjs-cursor-hand-tool-button-label = Roka +pdfjs-scroll-page-button = + .title = Uporabi drsenje po strani +pdfjs-scroll-page-button-label = Drsenje po strani +pdfjs-scroll-vertical-button = + .title = Uporabi navpiÄno drsenje +pdfjs-scroll-vertical-button-label = NavpiÄno drsenje +pdfjs-scroll-horizontal-button = + .title = Uporabi vodoravno drsenje +pdfjs-scroll-horizontal-button-label = Vodoravno drsenje +pdfjs-scroll-wrapped-button = + .title = Uporabi ovito drsenje +pdfjs-scroll-wrapped-button-label = Ovito drsenje +pdfjs-spread-none-button = + .title = Ne združuj razponov strani +pdfjs-spread-none-button-label = Brez razponov +pdfjs-spread-odd-button = + .title = Združuj razpone strani z zaÄetkom pri lihih straneh +pdfjs-spread-odd-button-label = Lihi razponi +pdfjs-spread-even-button = + .title = Združuj razpone strani z zaÄetkom pri sodih straneh +pdfjs-spread-even-button-label = Sodi razponi + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Lastnosti dokumenta … +pdfjs-document-properties-button-label = Lastnosti dokumenta … +pdfjs-document-properties-file-name = Ime datoteke: +pdfjs-document-properties-file-size = Velikost datoteke: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bajtov) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bajtov) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bajtov) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bajtov) +pdfjs-document-properties-title = Ime: +pdfjs-document-properties-author = Avtor: +pdfjs-document-properties-subject = Tema: +pdfjs-document-properties-keywords = KljuÄne besede: +pdfjs-document-properties-creation-date = Datum nastanka: +pdfjs-document-properties-modification-date = Datum spremembe: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Ustvaril: +pdfjs-document-properties-producer = Izdelovalec PDF: +pdfjs-document-properties-version = RazliÄica PDF: +pdfjs-document-properties-page-count = Å tevilo strani: +pdfjs-document-properties-page-size = Velikost strani: +pdfjs-document-properties-page-size-unit-inches = palcev +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = pokonÄno +pdfjs-document-properties-page-size-orientation-landscape = ležeÄe +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Pismo +pdfjs-document-properties-page-size-name-legal = Pravno + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Hitri spletni ogled: +pdfjs-document-properties-linearized-yes = Da +pdfjs-document-properties-linearized-no = Ne +pdfjs-document-properties-close-button = Zapri + +## Print + +pdfjs-print-progress-message = Priprava dokumenta na tiskanje … +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress } % +pdfjs-print-progress-close-button = PrekliÄi +pdfjs-printing-not-supported = Opozorilo: ta brskalnik ne podpira vseh možnosti tiskanja. +pdfjs-printing-not-ready = Opozorilo: PDF ni v celoti naložen za tiskanje. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Preklopi stransko vrstico +pdfjs-toggle-sidebar-notification-button = + .title = Preklopi stransko vrstico (dokument vsebuje oris/priponke/plasti) +pdfjs-toggle-sidebar-button-label = Preklopi stransko vrstico +pdfjs-document-outline-button = + .title = Prikaži oris dokumenta (dvokliknite za razÅ¡iritev/strnitev vseh predmetov) +pdfjs-document-outline-button-label = Oris dokumenta +pdfjs-attachments-button = + .title = Prikaži priponke +pdfjs-attachments-button-label = Priponke +pdfjs-layers-button = + .title = Prikaži plasti (dvokliknite za ponastavitev vseh plasti na privzeto stanje) +pdfjs-layers-button-label = Plasti +pdfjs-thumbs-button = + .title = Prikaži sliÄice +pdfjs-thumbs-button-label = SliÄice +pdfjs-current-outline-item-button = + .title = Najdi trenutni predmet orisa +pdfjs-current-outline-item-button-label = Trenutni predmet orisa +pdfjs-findbar-button = + .title = Iskanje po dokumentu +pdfjs-findbar-button-label = Najdi +pdfjs-additional-layers = Dodatne plasti + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Stran { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = SliÄica strani { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Najdi + .placeholder = Najdi v dokumentu … +pdfjs-find-previous-button = + .title = Najdi prejÅ¡njo ponovitev iskanega +pdfjs-find-previous-button-label = Najdi nazaj +pdfjs-find-next-button = + .title = Najdi naslednjo ponovitev iskanega +pdfjs-find-next-button-label = Najdi naprej +pdfjs-find-highlight-checkbox = OznaÄi vse +pdfjs-find-match-case-checkbox-label = Razlikuj velike/male Ärke +pdfjs-find-match-diacritics-checkbox-label = Razlikuj diakritiÄne znake +pdfjs-find-entire-word-checkbox-label = Cele besede +pdfjs-find-reached-top = Dosežen zaÄetek dokumenta iz smeri konca +pdfjs-find-reached-bottom = Doseženo konec dokumenta iz smeri zaÄetka +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] Zadetek { $current } od { $total } + [two] Zadetek { $current } od { $total } + [few] Zadetek { $current } od { $total } + *[other] Zadetek { $current } od { $total } + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] VeÄ kot { $limit } zadetek + [two] VeÄ kot { $limit } zadetka + [few] VeÄ kot { $limit } zadetki + *[other] VeÄ kot { $limit } zadetkov + } +pdfjs-find-not-found = Iskanega ni mogoÄe najti + +## Predefined zoom values + +pdfjs-page-scale-width = Å irina strani +pdfjs-page-scale-fit = Prilagodi stran +pdfjs-page-scale-auto = Samodejno +pdfjs-page-scale-actual = Dejanska velikost +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale } % + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Stran { $page } + +## Loading indicator messages + +pdfjs-loading-error = Med nalaganjem datoteke PDF je priÅ¡lo do napake. +pdfjs-invalid-file-error = Neveljavna ali pokvarjena datoteka PDF. +pdfjs-missing-file-error = Ni datoteke PDF. +pdfjs-unexpected-response-error = NepriÄakovan odgovor strežnika. +pdfjs-rendering-error = Med pripravljanjem strani je priÅ¡lo do napake! + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Opomba vrste { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Vnesite geslo za odpiranje te datoteke PDF. +pdfjs-password-invalid = Neveljavno geslo. Poskusite znova. +pdfjs-password-ok-button = V redu +pdfjs-password-cancel-button = PrekliÄi +pdfjs-web-fonts-disabled = Spletne pisave so onemogoÄene: vgradnih pisav za PDF ni mogoÄe uporabiti. + +## Editing + +pdfjs-editor-free-text-button = + .title = Besedilo +pdfjs-editor-free-text-button-label = Besedilo +pdfjs-editor-ink-button = + .title = RiÅ¡i +pdfjs-editor-ink-button-label = RiÅ¡i +pdfjs-editor-stamp-button = + .title = Dodajanje ali urejanje slik +pdfjs-editor-stamp-button-label = Dodajanje ali urejanje slik +pdfjs-editor-highlight-button = + .title = OznaÄevalnik +pdfjs-editor-highlight-button-label = OznaÄevalnik +pdfjs-highlight-floating-button1 = + .title = OznaÄi + .aria-label = OznaÄi +pdfjs-highlight-floating-button-label = OznaÄi + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Odstrani risbo +pdfjs-editor-remove-freetext-button = + .title = Odstrani besedilo +pdfjs-editor-remove-stamp-button = + .title = Odstrani sliko +pdfjs-editor-remove-highlight-button = + .title = Odstrani oznaÄbo + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Barva +pdfjs-editor-free-text-size-input = Velikost +pdfjs-editor-ink-color-input = Barva +pdfjs-editor-ink-thickness-input = Debelina +pdfjs-editor-ink-opacity-input = Neprosojnost +pdfjs-editor-stamp-add-image-button = + .title = Dodaj sliko +pdfjs-editor-stamp-add-image-button-label = Dodaj sliko +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Debelina +pdfjs-editor-free-highlight-thickness-title = + .title = Spremeni debelino pri oznaÄevanju nebesedilnih elementov +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Urejevalnik besedila + .default-content = ZaÄnite tipkati … +pdfjs-free-text = + .aria-label = Urejevalnik besedila +pdfjs-free-text-default-content = ZaÄnite tipkati … +pdfjs-ink = + .aria-label = Urejevalnik risanja +pdfjs-ink-canvas = + .aria-label = Uporabnikova slika + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Nadomestno besedilo +pdfjs-editor-alt-text-edit-button = + .aria-label = Uredi nadomestno besedilo +pdfjs-editor-alt-text-edit-button-label = Uredi nadomestno besedilo +pdfjs-editor-alt-text-dialog-label = Izberite možnost +pdfjs-editor-alt-text-dialog-description = Nadomestno besedilo se prikaže tistim, ki ne vidijo slike, ali Äe se ta ne naloži. +pdfjs-editor-alt-text-add-description-label = Dodaj opis +pdfjs-editor-alt-text-add-description-description = PoskuÅ¡ajte v enem ali dveh stavkih opisati motiv, okolje ali dejanja. +pdfjs-editor-alt-text-mark-decorative-label = OznaÄi kot okrasno +pdfjs-editor-alt-text-mark-decorative-description = Uporablja se za slike, ki služijo samo okrasu, na primer obrobe ali vodne žige. +pdfjs-editor-alt-text-cancel-button = PrekliÄi +pdfjs-editor-alt-text-save-button = Shrani +pdfjs-editor-alt-text-decorative-tooltip = OznaÄeno kot okrasno +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Na primer: "MladeniÄ sedi za mizo pri jedi" +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Nadomestno besedilo + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Zgornji levi kot – spremeni velikost +pdfjs-editor-resizer-label-top-middle = Zgoraj na sredini – spremeni velikost +pdfjs-editor-resizer-label-top-right = Zgornji desni kot – spremeni velikost +pdfjs-editor-resizer-label-middle-right = Desno na sredini – spremeni velikost +pdfjs-editor-resizer-label-bottom-right = Spodnji desni kot – spremeni velikost +pdfjs-editor-resizer-label-bottom-middle = Spodaj na sredini – spremeni velikost +pdfjs-editor-resizer-label-bottom-left = Spodnji levi kot – spremeni velikost +pdfjs-editor-resizer-label-middle-left = Levo na sredini – spremeni velikost +pdfjs-editor-resizer-top-left = + .aria-label = Zgornji levi kot – spremeni velikost +pdfjs-editor-resizer-top-middle = + .aria-label = Zgoraj na sredini – spremeni velikost +pdfjs-editor-resizer-top-right = + .aria-label = Zgornji desni kot – spremeni velikost +pdfjs-editor-resizer-middle-right = + .aria-label = Desno na sredini – spremeni velikost +pdfjs-editor-resizer-bottom-right = + .aria-label = Spodnji desni kot – spremeni velikost +pdfjs-editor-resizer-bottom-middle = + .aria-label = Spodaj na sredini – spremeni velikost +pdfjs-editor-resizer-bottom-left = + .aria-label = Spodnji levi kot – spremeni velikost +pdfjs-editor-resizer-middle-left = + .aria-label = Levo na sredini – spremeni velikost + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Barva oznaÄbe +pdfjs-editor-colorpicker-button = + .title = Spremeni barvo +pdfjs-editor-colorpicker-dropdown = + .aria-label = Izbira barve +pdfjs-editor-colorpicker-yellow = + .title = Rumena +pdfjs-editor-colorpicker-green = + .title = Zelena +pdfjs-editor-colorpicker-blue = + .title = Modra +pdfjs-editor-colorpicker-pink = + .title = Roza +pdfjs-editor-colorpicker-red = + .title = RdeÄa + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Prikaži vse +pdfjs-editor-highlight-show-all-button = + .title = Prikaži vse + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Uredi nadomestno besedilo (opis slike) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Dodaj nadomestno besedilo (opis slike) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Tukaj napiÅ¡ite svoj opis … +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Kratek opis za ljudi, ki ne morejo videti slike, ali za primer, ko se slika ne naloži. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = To nadomestno besedilo je bilo ustvarjeno samodejno in je lahko netoÄno. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = VeÄ o tem +pdfjs-editor-new-alt-text-create-automatically-button-label = Samodejno ustvari nadomestno besedilo +pdfjs-editor-new-alt-text-not-now-button = Ne zdaj +pdfjs-editor-new-alt-text-error-title = Nadomestnega besedila ni bilo mogoÄe samodejno ustvariti +pdfjs-editor-new-alt-text-error-description = Sestavite svoje nadomestno besedilo ali poskusite znova pozneje. +pdfjs-editor-new-alt-text-error-close-button = Zapri +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = PrenaÅ¡anje modela UI za nadomestno besedilo ({ $downloadedSize } od { $totalSize } MB) + .aria-valuetext = PrenaÅ¡anje modela UI za nadomestno besedilo ({ $downloadedSize } od { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Nadomestno besedilo dodano +pdfjs-editor-new-alt-text-added-button-label = Nadomestno besedilo dodano +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Nadomestno besedilo manjka +pdfjs-editor-new-alt-text-missing-button-label = Nadomestno besedilo manjka +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Oceni nadomestno besedilo +pdfjs-editor-new-alt-text-to-review-button-label = Oceni nadomestno besedilo +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Samodejno ustvarjeno: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Nastavitve nadomestnega besedila slike +pdfjs-image-alt-text-settings-button-label = Nastavitve nadomestnega besedila slike +pdfjs-editor-alt-text-settings-dialog-label = Nastavitve nadomestnega besedila slike +pdfjs-editor-alt-text-settings-automatic-title = Samodejno nadomestno besedilo +pdfjs-editor-alt-text-settings-create-model-button-label = Samodejno ustvari nadomestno besedilo +pdfjs-editor-alt-text-settings-create-model-description = Predlaga opise za pomoÄ ljudem, ki ne morejo videti slike, ali za primer, ko se slika ne naloži. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model UI za nadomestno besedilo ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Izvaja se lokalno na vaÅ¡i napravi, tako da vaÅ¡i podatki ostajajo zasebni. Zahtevano za samodejno nadomestno besedilo. +pdfjs-editor-alt-text-settings-delete-model-button = IzbriÅ¡i +pdfjs-editor-alt-text-settings-download-model-button = Prenesi +pdfjs-editor-alt-text-settings-downloading-model-button = PrenaÅ¡anje ... +pdfjs-editor-alt-text-settings-editor-title = Urejevalnik nadomestnega besedila +pdfjs-editor-alt-text-settings-show-dialog-button-label = Ob dodajanju slike takoj prikaži urejevalnik nadomestnega besedila +pdfjs-editor-alt-text-settings-show-dialog-description = Pomaga vam zagotoviti, da imajo vse vaÅ¡e slike nadomestno besedilo. +pdfjs-editor-alt-text-settings-close-button = Zapri + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = OznaÄba odstranjena +pdfjs-editor-undo-bar-message-freetext = Besedilo odstranjeno +pdfjs-editor-undo-bar-message-ink = Risba odstranjena +pdfjs-editor-undo-bar-message-stamp = Slika odstranjena +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } oznaÄba odstranjena + [two] { $count } oznaÄbi odstranjeni + [few] { $count } oznaÄbe odstranjene + *[other] { $count } oznaÄb odstranjenih + } +pdfjs-editor-undo-bar-undo-button = + .title = Razveljavi +pdfjs-editor-undo-bar-undo-button-label = Razveljavi +pdfjs-editor-undo-bar-close-button = + .title = Zapri +pdfjs-editor-undo-bar-close-button-label = Zapri diff --git a/public/assets/pdfjs/locale/son/viewer.ftl b/public/assets/pdfjs/locale/son/viewer.ftl new file mode 100755 index 0000000..fa4f6b1 --- /dev/null +++ b/public/assets/pdfjs/locale/son/viewer.ftl @@ -0,0 +1,206 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Moo bisante +pdfjs-previous-button-label = Bisante +pdfjs-next-button = + .title = Jinehere moo +pdfjs-next-button-label = Jine +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Moo +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = { $pagesCount } ra +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } ka hun { $pagesCount }) ra +pdfjs-zoom-out-button = + .title = Nakasandi +pdfjs-zoom-out-button-label = Nakasandi +pdfjs-zoom-in-button = + .title = Bebbeerandi +pdfjs-zoom-in-button-label = Bebbeerandi +pdfjs-zoom-select = + .title = Bebbeerandi +pdfjs-presentation-mode-button = + .title = Bere cebeyan alhaali +pdfjs-presentation-mode-button-label = Cebeyan alhaali +pdfjs-open-file-button = + .title = Tuku feeri +pdfjs-open-file-button-label = Feeri +pdfjs-print-button = + .title = Kar +pdfjs-print-button-label = Kar + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Goyjinawey +pdfjs-tools-button-label = Goyjinawey +pdfjs-first-page-button = + .title = Koy moo jinaa ga +pdfjs-first-page-button-label = Koy moo jinaa ga +pdfjs-last-page-button = + .title = Koy moo koraa ga +pdfjs-last-page-button-label = Koy moo koraa ga +pdfjs-page-rotate-cw-button = + .title = Kuubi kanbe guma here +pdfjs-page-rotate-cw-button-label = Kuubi kanbe guma here +pdfjs-page-rotate-ccw-button = + .title = Kuubi kanbe wowa here +pdfjs-page-rotate-ccw-button-label = Kuubi kanbe wowa here + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Takadda mayrawey… +pdfjs-document-properties-button-label = Takadda mayrawey… +pdfjs-document-properties-file-name = Tuku maa: +pdfjs-document-properties-file-size = Tuku adadu: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = KB { $size_kb } (cebsu-ize { $size_b }) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = MB { $size_mb } (cebsu-ize { $size_b }) +pdfjs-document-properties-title = Tiiramaa: +pdfjs-document-properties-author = Hantumkaw: +pdfjs-document-properties-subject = Dalil: +pdfjs-document-properties-keywords = Kufalkalimawey: +pdfjs-document-properties-creation-date = Teeyan han: +pdfjs-document-properties-modification-date = Barmayan han: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Teekaw: +pdfjs-document-properties-producer = PDF berandikaw: +pdfjs-document-properties-version = PDF dumi: +pdfjs-document-properties-page-count = Moo hinna: + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + + +## + +pdfjs-document-properties-close-button = Daabu + +## Print + +pdfjs-print-progress-message = Goo ma takaddaa soolu k'a kar se… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = NaÅ‹ +pdfjs-printing-not-supported = Yaamar: Karyan Å¡i tee ka timme nda ceecikaa woo. +pdfjs-printing-not-ready = Yaamar: PDF Å¡i zunbu ka timme karyan Å¡e. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Kanjari ceraw zuu +pdfjs-toggle-sidebar-button-label = Kanjari ceraw zuu +pdfjs-document-outline-button = + .title = Takaddaa korfur alhaaloo cebe (naagu cee hinka ka haya-izey kul hayandi/kankamandi) +pdfjs-document-outline-button-label = Takadda filla-boÅ‹ +pdfjs-attachments-button = + .title = Hangarey cebe +pdfjs-attachments-button-label = Hangarey +pdfjs-thumbs-button = + .title = Kabeboy biyey cebe +pdfjs-thumbs-button-label = Kabeboy biyey +pdfjs-findbar-button = + .title = Ceeci takaddaa ra +pdfjs-findbar-button-label = Ceeci + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = { $page } moo +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Kabeboy bii { $page } moo Å¡e + +## Find panel button title and messages + +pdfjs-find-input = + .title = Ceeci + .placeholder = Ceeci takaddaa ra… +pdfjs-find-previous-button = + .title = KalimaɲaÅ‹oo bangayri bisantaa ceeci +pdfjs-find-previous-button-label = Bisante +pdfjs-find-next-button = + .title = KalimaɲaÅ‹oo hiino bangayroo ceeci +pdfjs-find-next-button-label = Jine +pdfjs-find-highlight-checkbox = Ikul Å¡ilbay +pdfjs-find-match-case-checkbox-label = Harfu-beeriyan hawgay +pdfjs-find-reached-top = A too moÅ‹oo boÅ‹oo, koy jine ka Å¡initin nda cewoo +pdfjs-find-reached-bottom = A too moɲoo cewoo, koy jine Å¡intioo ga +pdfjs-find-not-found = Kalimaɲaa mana duwandi + +## Predefined zoom values + +pdfjs-page-scale-width = Mooo hayyan +pdfjs-page-scale-fit = Moo sawayan +pdfjs-page-scale-auto = Boŋše azzaati barmayyan +pdfjs-page-scale-actual = Adadu cimi +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = Firka bangay kaÅ‹ PDF goo ma zumandi. +pdfjs-invalid-file-error = PDF tuku laala wala laybante. +pdfjs-missing-file-error = PDF tuku kumante. +pdfjs-unexpected-response-error = Manti ferÅ¡ikaw tuuruyan maatante. +pdfjs-rendering-error = Firka bangay kaÅ‹ moɲoo goo ma willandi. + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = { $type } maasa-caw] + +## Password + +pdfjs-password-label = Å ennikufal dam ka PDF tukoo woo feeri. +pdfjs-password-invalid = Å ennikufal laalo. Ceeci koyne taare. +pdfjs-password-ok-button = Ayyo +pdfjs-password-cancel-button = NaÅ‹ +pdfjs-web-fonts-disabled = Interneti Å¡igirawey kay: Å¡i hin ka goy nda PDF Å¡igira hurantey. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/sq/viewer.ftl b/public/assets/pdfjs/locale/sq/viewer.ftl new file mode 100755 index 0000000..2b1c91a --- /dev/null +++ b/public/assets/pdfjs/locale/sq/viewer.ftl @@ -0,0 +1,506 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Faqja e Mëparshme +pdfjs-previous-button-label = E mëparshmja +pdfjs-next-button = + .title = Faqja Pasuese +pdfjs-next-button-label = Pasuesja +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Faqe +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = nga { $pagesCount } gjithsej +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } nga { $pagesCount }) +pdfjs-zoom-out-button = + .title = Zvogëlojeni +pdfjs-zoom-out-button-label = Zvogëlojeni +pdfjs-zoom-in-button = + .title = Zmadhojeni +pdfjs-zoom-in-button-label = Zmadhojini +pdfjs-zoom-select = + .title = Zmadhim/Zvogëlim +pdfjs-presentation-mode-button = + .title = Kalo te Mënyra Paraqitje +pdfjs-presentation-mode-button-label = Mënyra Paraqitje +pdfjs-open-file-button = + .title = Hapni Kartelë +pdfjs-open-file-button-label = Hape +pdfjs-print-button = + .title = Shtypje +pdfjs-print-button-label = Shtype +pdfjs-save-button = + .title = Ruaje +pdfjs-save-button-label = Ruaje +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Shkarkojeni +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Shkarkoje +pdfjs-bookmark-button = + .title = Faqja e Tanishme (Shihni URL nga Faqja e Tanishme) +pdfjs-bookmark-button-label = Faqja e Tanishme + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Mjete +pdfjs-tools-button-label = Mjete +pdfjs-first-page-button = + .title = Kaloni te Faqja e Parë +pdfjs-first-page-button-label = Kaloni te Faqja e Parë +pdfjs-last-page-button = + .title = Kaloni te Faqja e Fundit +pdfjs-last-page-button-label = Kaloni te Faqja e Fundit +pdfjs-page-rotate-cw-button = + .title = Rrotullojeni Në Kahun Orar +pdfjs-page-rotate-cw-button-label = Rrotulloje Në Kahun Orar +pdfjs-page-rotate-ccw-button = + .title = Rrotullojeni Në Kahun Kundërorar +pdfjs-page-rotate-ccw-button-label = Rrotulloje Në Kahun Kundërorar +pdfjs-cursor-text-select-tool-button = + .title = Aktivizo Mjet Përzgjedhjeje Teksti +pdfjs-cursor-text-select-tool-button-label = Mjet Përzgjedhjeje Teksti +pdfjs-cursor-hand-tool-button = + .title = Aktivizo Mjetin Dorë +pdfjs-cursor-hand-tool-button-label = Mjeti Dorë +pdfjs-scroll-page-button = + .title = Përdor Rrëshqitje Në Faqe +pdfjs-scroll-page-button-label = Rrëshqitje Në Faqe +pdfjs-scroll-vertical-button = + .title = Përdor Rrëshqitje Vertikale +pdfjs-scroll-vertical-button-label = Rrëshqitje Vertikale +pdfjs-scroll-horizontal-button = + .title = Përdor Rrëshqitje Horizontale +pdfjs-scroll-horizontal-button-label = Rrëshqitje Horizontale +pdfjs-scroll-wrapped-button = + .title = Përdor Rrëshqitje Me Mbështjellje +pdfjs-scroll-wrapped-button-label = Rrëshqitje Me Mbështjellje + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Veti Dokumenti… +pdfjs-document-properties-button-label = Veti Dokumenti… +pdfjs-document-properties-file-name = Emër kartele: +pdfjs-document-properties-file-size = Madhësi kartele: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bajte) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bajte) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bajte) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bajte) +pdfjs-document-properties-title = Titull: +pdfjs-document-properties-author = Autor: +pdfjs-document-properties-subject = Subjekt: +pdfjs-document-properties-keywords = Fjalëkyçe: +pdfjs-document-properties-creation-date = Datë Krijimi: +pdfjs-document-properties-modification-date = Datë Ndryshimi: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Krijues: +pdfjs-document-properties-producer = Prodhues PDF-je: +pdfjs-document-properties-version = Version PDF-je: +pdfjs-document-properties-page-count = Numër Faqesh: +pdfjs-document-properties-page-size = Madhësi Faqeje: +pdfjs-document-properties-page-size-unit-inches = inç +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = portret +pdfjs-document-properties-page-size-orientation-landscape = së gjeri +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Parje e Shpjetë në Web: +pdfjs-document-properties-linearized-yes = Po +pdfjs-document-properties-linearized-no = Jo +pdfjs-document-properties-close-button = Mbylleni + +## Print + +pdfjs-print-progress-message = Po përgatitet dokumenti për shtypje… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Anuloje +pdfjs-printing-not-supported = Kujdes: Shtypja s’mbulohet plotësisht nga ky shfletues. +pdfjs-printing-not-ready = Kujdes: PDF-ja s’është ngarkuar plotësisht që ta shtypni. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Shfaqni/Fshihni Anështyllën +pdfjs-toggle-sidebar-notification-button = + .title = Hap/Mbyll Anështylë (dokumenti përmban përvijim/nashkëngjitje/shtresa) +pdfjs-toggle-sidebar-button-label = Shfaq/Fshih Anështyllën +pdfjs-document-outline-button = + .title = Shfaqni Përvijim Dokumenti (dyklikoni që të shfaqen/fshihen krejt elementët) +pdfjs-document-outline-button-label = Përvijim Dokumenti +pdfjs-attachments-button = + .title = Shfaqni Bashkëngjitje +pdfjs-attachments-button-label = Bashkëngjitje +pdfjs-layers-button = + .title = Shfaq Shtresa (dyklikoni që të rikthehen krejt shtresat në gjendjen e tyre parazgjedhje) +pdfjs-layers-button-label = Shtresa +pdfjs-thumbs-button = + .title = Shfaqni Miniatura +pdfjs-thumbs-button-label = Miniatura +pdfjs-current-outline-item-button = + .title = Gjej Objektin e Tanishëm të Përvijuar +pdfjs-current-outline-item-button-label = Objekt i Tanishëm i Përvijuar +pdfjs-findbar-button = + .title = Gjeni në Dokument +pdfjs-findbar-button-label = Gjej +pdfjs-additional-layers = Shtresa Shtesë + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Faqja { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniaturë e Faqes { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Gjej + .placeholder = Gjeni në dokument… +pdfjs-find-previous-button = + .title = Gjeni hasjen e mëparshme të togfjalëshit +pdfjs-find-previous-button-label = E mëparshmja +pdfjs-find-next-button = + .title = Gjeni hasjen pasuese të togfjalëshit +pdfjs-find-next-button-label = Pasuesja +pdfjs-find-highlight-checkbox = Theksoji të tëra +pdfjs-find-match-case-checkbox-label = Siç Është Shkruar +pdfjs-find-match-diacritics-checkbox-label = Me Përputhje Me Shenjat Diakritike +pdfjs-find-entire-word-checkbox-label = Fjalë të Plota +pdfjs-find-reached-top = U mbërrit në krye të dokumentit, vazhduar prej fundit +pdfjs-find-reached-bottom = U mbërrit në fund të dokumentit, vazhduar prej kreut +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } nga { $total } përputhje + *[other] { $current } nga { $total } përputhje + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Më tepër se { $limit } përputhje + *[other] Më tepër se { $limit } përputhje + } +pdfjs-find-not-found = Togfjalësh që s’gjendet + +## Predefined zoom values + +pdfjs-page-scale-width = Gjerësi Faqeje +pdfjs-page-scale-fit = Sa Nxë Faqja +pdfjs-page-scale-auto = Zoom i Vetvetishëm +pdfjs-page-scale-actual = Madhësia Faktike +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Faqja { $page } + +## Loading indicator messages + +pdfjs-loading-error = Ndodhi një gabim gjatë ngarkimit të PDF-së. +pdfjs-invalid-file-error = Kartelë PDF e pavlefshme ose e dëmtuar. +pdfjs-missing-file-error = Kartelë PDF që mungon. +pdfjs-unexpected-response-error = Përgjigje shërbyesi e papritur. +pdfjs-rendering-error = Ndodhi një gabim gjatë riprodhimit të faqes. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Nënvizim { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Jepni fjalëkalimin që të hapet kjo kartelë PDF. +pdfjs-password-invalid = Fjalëkalim i pavlefshëm. Ju lutemi, riprovoni. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Anuloje +pdfjs-web-fonts-disabled = Shkronjat Web janë të çaktivizuara: s’arrihet të përdoren shkronja të trupëzuara në PDF. + +## Editing + +pdfjs-editor-free-text-button = + .title = Tekst +pdfjs-editor-free-text-button-label = Tekst +pdfjs-editor-ink-button = + .title = Vizatoni +pdfjs-editor-ink-button-label = Vizatoni +pdfjs-editor-stamp-button = + .title = Shtoni ose përpunoni figura +pdfjs-editor-stamp-button-label = Shtoni ose përpunoni figura +pdfjs-editor-highlight-button = + .title = Theksim +pdfjs-editor-highlight-button-label = Theksoje +pdfjs-highlight-floating-button1 = + .title = Theksim + .aria-label = Theksim +pdfjs-highlight-floating-button-label = Theksim + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Hiq vizatim +pdfjs-editor-remove-freetext-button = + .title = Hiq tekst +pdfjs-editor-remove-stamp-button = + .title = Hiq figurë +pdfjs-editor-remove-highlight-button = + .title = Hiqe theksimin + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Ngjyrë +pdfjs-editor-free-text-size-input = Madhësi +pdfjs-editor-ink-color-input = Ngjyrë +pdfjs-editor-ink-thickness-input = Trashësi +pdfjs-editor-ink-opacity-input = Patejdukshmëri +pdfjs-editor-stamp-add-image-button = + .title = Shtoni figurë +pdfjs-editor-stamp-add-image-button-label = Shtoni figurë +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Trashësi +pdfjs-editor-free-highlight-thickness-title = + .title = Ndryshoni trashësinë kur theksoni objekte tjetër nga tekst +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Përpunues Tekstesh + .default-content = Filloni të shtypni… +pdfjs-free-text = + .aria-label = Përpunues Tekstesh +pdfjs-free-text-default-content = Filloni të shtypni… +pdfjs-ink = + .aria-label = Përpunues Vizatimesh +pdfjs-ink-canvas = + .aria-label = Figurë e krijuar nga përdoruesi + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Tekst alternativ +pdfjs-editor-alt-text-edit-button = + .aria-label = Përpunoni tekst alternativ +pdfjs-editor-alt-text-edit-button-label = Përpunoni tekst alternativ +pdfjs-editor-alt-text-dialog-label = Zgjidhni një mundësi +pdfjs-editor-alt-text-dialog-description = Teksti alt (tekst alternativ) vjen në ndihmë kur njerëzit s’mund të shohin figurën, ose kur ajo nuk ngarkohet. +pdfjs-editor-alt-text-add-description-label = Shtoni një përshkrim +pdfjs-editor-alt-text-add-description-description = Synoni për 1-2 togfjalësha që përshkruajnë subjektin, rrethanat apo veprimet. +pdfjs-editor-alt-text-mark-decorative-label = Vëri shenjë si dekorative +pdfjs-editor-alt-text-mark-decorative-description = Kjo përdoret për figura zbukuruese, fjala vjen, anë, ose watermark-e. +pdfjs-editor-alt-text-cancel-button = Anuloje +pdfjs-editor-alt-text-save-button = Ruaje +pdfjs-editor-alt-text-decorative-tooltip = Iu vu shenjë si dekorative +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Për shembull, “Një djalosh ulet në një tryezë të hajë†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Tekst alternativ + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Cepi i sipërm majtas — ripërmasojeni +pdfjs-editor-resizer-label-top-middle = Mesi i pjesës sipër — ripërmasojeni +pdfjs-editor-resizer-label-top-right = Cepi i sipërm djathtas — ripërmasojeni +pdfjs-editor-resizer-label-middle-right = Djathtas në mes — ripërmasojeni +pdfjs-editor-resizer-label-bottom-right = Cepi i poshtëm djathtas — ripërmasojeni +pdfjs-editor-resizer-label-bottom-middle = Mesi i pjesës poshtë — ripërmasojeni +pdfjs-editor-resizer-label-bottom-left = Cepi i poshtëm — ripërmasojeni +pdfjs-editor-resizer-label-middle-left = Majtas në mes — ripërmasojeni +pdfjs-editor-resizer-top-left = + .aria-label = Cepi i sipërm majtas — ripërmasojeni +pdfjs-editor-resizer-top-middle = + .aria-label = Mesi i pjesës sipër — ripërmasojeni +pdfjs-editor-resizer-top-right = + .aria-label = Cepi i sipërm djathtas — ripërmasojeni +pdfjs-editor-resizer-middle-right = + .aria-label = Djathtas në mes — ripërmasojeni +pdfjs-editor-resizer-bottom-right = + .aria-label = Cepi i poshtëm djathtas — ripërmasojeni +pdfjs-editor-resizer-bottom-middle = + .aria-label = Mesi i pjesës poshtë — ripërmasojeni +pdfjs-editor-resizer-bottom-left = + .aria-label = Cepi i poshtëm — ripërmasojeni +pdfjs-editor-resizer-middle-left = + .aria-label = Majtas në mes — ripërmasojeni + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Ngjyrë theksimi +pdfjs-editor-colorpicker-button = + .title = Ndryshoni ngjyrë +pdfjs-editor-colorpicker-dropdown = + .aria-label = Zgjedhje ngjyre +pdfjs-editor-colorpicker-yellow = + .title = E verdhë +pdfjs-editor-colorpicker-green = + .title = E gjelbër +pdfjs-editor-colorpicker-blue = + .title = Blu +pdfjs-editor-colorpicker-pink = + .title = Rozë +pdfjs-editor-colorpicker-red = + .title = E kuqe + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Shfaqi krejt +pdfjs-editor-highlight-show-all-button = + .title = Shfaqi krejt + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Përpunoni tekst alternativ (përshkrim figure) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Shtoni tekst alternativ (përshkrim figure) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Shkruani këtu përshkrimin tuaj… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Përshkrim i shkurtër për persona që s’munden të shohin figurën, ose për kur figura nuk ngarkohet dot. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Ky tekst alternativ qe krijuar automatikisht dhe mund të jetë i pasaktë. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Mësoni më tepër +pdfjs-editor-new-alt-text-create-automatically-button-label = Krijo automatikisht tekst alternativ +pdfjs-editor-new-alt-text-not-now-button = Jo tani +pdfjs-editor-new-alt-text-error-title = S’u krijua dot automatikisht tekst alternativ +pdfjs-editor-new-alt-text-error-description = Ju lutemi, shkruani tekstin tuaj alternativ, ose riprovoni më vonë. +pdfjs-editor-new-alt-text-error-close-button = Mbylle +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Po shkarkohet model IA teksti alternativ ({ $downloadedSize } nga { $totalSize } MB) + .aria-valuetext = Po shkarkohet model IA teksti alternativ ({ $downloadedSize } nga { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = U shtua tekst alternativ +pdfjs-editor-new-alt-text-added-button-label = U shtua tekst alternativ +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Mungon tekst alternativ +pdfjs-editor-new-alt-text-missing-button-label = Mungon tekst alternativ +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Shqyrtoni tekst alternativ +pdfjs-editor-new-alt-text-to-review-button-label = Shqyrtoni tekst alternativ +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Krijuar automatikisht: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Rregullime teksti alternativ figure +pdfjs-image-alt-text-settings-button-label = Rregullime teksti alternativ figure +pdfjs-editor-alt-text-settings-dialog-label = Rregullime teksti alternativ figure +pdfjs-editor-alt-text-settings-automatic-title = Tekst alternativ i automatizuar +pdfjs-editor-alt-text-settings-create-model-button-label = Krijo automatikisht tekst alternativ +pdfjs-editor-alt-text-settings-create-model-description = Sugjeron përshkrime, për të ndihmuar persona që s’munden të shohin figurën, ose për kur figura nuk ngarkohet dot. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model IA teksti alternativ ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Xhiron lokalisht në pajisjen tuaj, pra të dhënat tuaja mbeten private. E domosdoshme për tekst të automatizuar alternativ. +pdfjs-editor-alt-text-settings-delete-model-button = Fshije +pdfjs-editor-alt-text-settings-download-model-button = Shkarkoje +pdfjs-editor-alt-text-settings-downloading-model-button = Po shkarkohet… +pdfjs-editor-alt-text-settings-editor-title = Përpunues teksti alternativ +pdfjs-editor-alt-text-settings-show-dialog-button-label = Shfaq menjëherë përpunues teksti alternativ, kur shtohet një figurë +pdfjs-editor-alt-text-settings-show-dialog-description = Ju ndihmon të siguroheni se krejt figurat tuaja kanë tekst alternativ. +pdfjs-editor-alt-text-settings-close-button = Mbylle + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = U hoq theksimi +pdfjs-editor-undo-bar-message-freetext = U hoq tekst +pdfjs-editor-undo-bar-message-ink = U hoq vizatim +pdfjs-editor-undo-bar-message-stamp = U hoq figurë +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] U hoq { $count } shënim + *[other] U hoqën { $count } shënime + } +pdfjs-editor-undo-bar-undo-button = + .title = Zhbëje +pdfjs-editor-undo-bar-undo-button-label = Zhbëje +pdfjs-editor-undo-bar-close-button = + .title = Mbylle +pdfjs-editor-undo-bar-close-button-label = Mbylle diff --git a/public/assets/pdfjs/locale/sr/viewer.ftl b/public/assets/pdfjs/locale/sr/viewer.ftl new file mode 100755 index 0000000..e125dfb --- /dev/null +++ b/public/assets/pdfjs/locale/sr/viewer.ftl @@ -0,0 +1,421 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Претходна Ñтраница +pdfjs-previous-button-label = Претходна +pdfjs-next-button = + .title = Следећа Ñтраница +pdfjs-next-button-label = Следећа +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Страница +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = од { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } од { $pagesCount }) +pdfjs-zoom-out-button = + .title = Умањи +pdfjs-zoom-out-button-label = Умањи +pdfjs-zoom-in-button = + .title = Увеличај +pdfjs-zoom-in-button-label = Увеличај +pdfjs-zoom-select = + .title = Увеличавање +pdfjs-presentation-mode-button = + .title = Промени на приказ у режиму презентације +pdfjs-presentation-mode-button-label = Режим презентације +pdfjs-open-file-button = + .title = Отвори датотеку +pdfjs-open-file-button-label = Отвори +pdfjs-print-button = + .title = Штампај +pdfjs-print-button-label = Штампај +pdfjs-save-button = + .title = Сачувај +pdfjs-save-button-label = Сачувај +pdfjs-bookmark-button = + .title = Тренутна Ñтраница (погледајте URL Ñа тренутне Ñтранице) +pdfjs-bookmark-button-label = Тренутна Ñтраница + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Ðлатке +pdfjs-tools-button-label = Ðлатке +pdfjs-first-page-button = + .title = Иди на прву Ñтраницу +pdfjs-first-page-button-label = Иди на прву Ñтраницу +pdfjs-last-page-button = + .title = Иди на поÑледњу Ñтраницу +pdfjs-last-page-button-label = Иди на поÑледњу Ñтраницу +pdfjs-page-rotate-cw-button = + .title = Ротирај у Ñмеру казаљке на Ñату +pdfjs-page-rotate-cw-button-label = Ротирај у Ñмеру казаљке на Ñату +pdfjs-page-rotate-ccw-button = + .title = Ротирај у Ñмеру Ñупротном од казаљке на Ñату +pdfjs-page-rotate-ccw-button-label = Ротирај у Ñмеру Ñупротном од казаљке на Ñату +pdfjs-cursor-text-select-tool-button = + .title = Омогући алат за Ñелектовање текÑта +pdfjs-cursor-text-select-tool-button-label = Ðлат за Ñелектовање текÑта +pdfjs-cursor-hand-tool-button = + .title = Омогући алат за померање +pdfjs-cursor-hand-tool-button-label = Ðлат за померање +pdfjs-scroll-page-button = + .title = КориÑти Ñкроловање по омоту +pdfjs-scroll-page-button-label = Скроловање Ñтранице +pdfjs-scroll-vertical-button = + .title = КориÑти вертикално Ñкроловање +pdfjs-scroll-vertical-button-label = Вертикално Ñкроловање +pdfjs-scroll-horizontal-button = + .title = КориÑти хоризонтално Ñкроловање +pdfjs-scroll-horizontal-button-label = Хоризонтално Ñкроловање +pdfjs-scroll-wrapped-button = + .title = КориÑти Ñкроловање по омоту +pdfjs-scroll-wrapped-button-label = Скроловање по омоту +pdfjs-spread-none-button = + .title = Ðемој Ñпајати ширења Ñтраница +pdfjs-spread-none-button-label = Без раÑпроÑтирања +pdfjs-spread-odd-button = + .title = Споји ширења Ñтраница које почињу непарним бројем +pdfjs-spread-odd-button-label = Ðепарна раÑпроÑтирања +pdfjs-spread-even-button = + .title = Споји ширења Ñтраница које почињу парним бројем +pdfjs-spread-even-button-label = Парна раÑпроÑтирања + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Параметри документа… +pdfjs-document-properties-button-label = Параметри документа… +pdfjs-document-properties-file-name = Име датотеке: +pdfjs-document-properties-file-size = Величина датотеке: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } B) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } B) +pdfjs-document-properties-title = ÐаÑлов: +pdfjs-document-properties-author = Ðутор: +pdfjs-document-properties-subject = Тема: +pdfjs-document-properties-keywords = Кључне речи: +pdfjs-document-properties-creation-date = Датум креирања: +pdfjs-document-properties-modification-date = Датум модификације: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Стваралац: +pdfjs-document-properties-producer = PDF произвођач: +pdfjs-document-properties-version = PDF верзија: +pdfjs-document-properties-page-count = Број Ñтраница: +pdfjs-document-properties-page-size = Величина Ñтранице: +pdfjs-document-properties-page-size-unit-inches = ин +pdfjs-document-properties-page-size-unit-millimeters = мм +pdfjs-document-properties-page-size-orientation-portrait = уÑправно +pdfjs-document-properties-page-size-orientation-landscape = водоравно +pdfjs-document-properties-page-size-name-a-three = Ð3 +pdfjs-document-properties-page-size-name-a-four = Ð4 +pdfjs-document-properties-page-size-name-letter = Слово +pdfjs-document-properties-page-size-name-legal = Права + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Брз веб приказ: +pdfjs-document-properties-linearized-yes = Да +pdfjs-document-properties-linearized-no = Ðе +pdfjs-document-properties-close-button = Затвори + +## Print + +pdfjs-print-progress-message = Припремам документ за штампање… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Откажи +pdfjs-printing-not-supported = Упозорење: Штампање није у потпуноÑти подржано у овом прегледачу. +pdfjs-printing-not-ready = Упозорење: PDF није у потпуноÑти учитан за штампу. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Прикажи/Ñакриј бочни панел +pdfjs-toggle-sidebar-notification-button = + .title = Прикажи/Ñакриј бочни панел (документ Ñадржи контуру/прилоге/Ñлојеве) +pdfjs-toggle-sidebar-button-label = Прикажи/Ñакриј бочни панел +pdfjs-document-outline-button = + .title = Прикажи Ñтруктуру документа (двоÑтруким кликом проширујете/Ñкупљате Ñве Ñтавке) +pdfjs-document-outline-button-label = Контура документа +pdfjs-attachments-button = + .title = Прикажи прилоге +pdfjs-attachments-button-label = Прилози +pdfjs-layers-button = + .title = Прикажи Ñлојеве (дупли клик за враћање Ñвих Ñлојева у подразумевано Ñтање) +pdfjs-layers-button-label = Слојеви +pdfjs-thumbs-button = + .title = Прикажи Ñличице +pdfjs-thumbs-button-label = Сличице +pdfjs-current-outline-item-button = + .title = Пронађите тренутни елемент Ñтруктуре +pdfjs-current-outline-item-button-label = Тренутна контура +pdfjs-findbar-button = + .title = Пронађи у документу +pdfjs-findbar-button-label = Пронађи +pdfjs-additional-layers = Додатни Ñлојеви + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Страница { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Сличица од Ñтранице { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Пронађи + .placeholder = Пронађи у документу… +pdfjs-find-previous-button = + .title = Пронађи претходно појављивање фразе +pdfjs-find-previous-button-label = Претходна +pdfjs-find-next-button = + .title = Пронађи Ñледеће појављивање фразе +pdfjs-find-next-button-label = Следећа +pdfjs-find-highlight-checkbox = ИÑтакнути Ñве +pdfjs-find-match-case-checkbox-label = Подударања +pdfjs-find-match-diacritics-checkbox-label = Дијакритика +pdfjs-find-entire-word-checkbox-label = Целе речи +pdfjs-find-reached-top = ДоÑтигнут врх документа, наÑтавио Ñа дна +pdfjs-find-reached-bottom = ДоÑтигнуто дно документа, наÑтавио Ñа врха +pdfjs-find-not-found = Фраза није пронађена + +## Predefined zoom values + +pdfjs-page-scale-width = Ширина Ñтранице +pdfjs-page-scale-fit = Прилагоди Ñтраницу +pdfjs-page-scale-auto = ÐутоматÑко увеличавање +pdfjs-page-scale-actual = Стварна величина +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Страница { $page } + +## Loading indicator messages + +pdfjs-loading-error = Дошло је до грешке приликом учитавања PDF-а. +pdfjs-invalid-file-error = PDF датотека је неважећа или је оштећена. +pdfjs-missing-file-error = ÐедоÑтаје PDF датотека. +pdfjs-unexpected-response-error = Ðеочекиван одговор од Ñервера. +pdfjs-rendering-error = Дошло је до грешке приликом рендеровања ове Ñтранице. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } коментар] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = УнеÑите лозинку да биÑте отворили овај PDF докуменат. +pdfjs-password-invalid = ÐеиÑправна лозинка. Покушајте поново. +pdfjs-password-ok-button = У реду +pdfjs-password-cancel-button = Откажи +pdfjs-web-fonts-disabled = Веб фонтови Ñу онемогућени: не могу кориÑтити уграђене PDF фонтове. + +## Editing + +pdfjs-editor-free-text-button = + .title = ТекÑÑ‚ +pdfjs-editor-free-text-button-label = ТекÑÑ‚ +pdfjs-editor-ink-button = + .title = Цртај +pdfjs-editor-ink-button-label = Цртај +pdfjs-editor-stamp-button = + .title = Додај или уреди Ñлике +pdfjs-editor-stamp-button-label = Додај или уреди Ñлике +pdfjs-editor-highlight-button = + .title = Означи +pdfjs-editor-highlight-button-label = Означи +pdfjs-highlight-floating-button1 = + .title = Означи + .aria-label = Означи +pdfjs-highlight-floating-button-label = Означи + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Уклони цртеж +pdfjs-editor-remove-freetext-button = + .title = Уклони текÑÑ‚ +pdfjs-editor-remove-stamp-button = + .title = Уклони Ñлику +pdfjs-editor-remove-highlight-button = + .title = Уклони ознаку + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Боја +pdfjs-editor-free-text-size-input = Величина +pdfjs-editor-ink-color-input = Боја +pdfjs-editor-ink-thickness-input = Дебљина +pdfjs-editor-ink-opacity-input = Опацитет +pdfjs-editor-stamp-add-image-button = + .title = Додај Ñлику +pdfjs-editor-stamp-add-image-button-label = Додај Ñлику +pdfjs-editor-free-highlight-thickness-title = + .title = Промени дебљину при означавању других Ñтавки Ñем текÑта +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Уређивач текÑта + .default-content = Почни куцати… +pdfjs-free-text = + .aria-label = Уређивач текÑта +pdfjs-free-text-default-content = Почни куцање… +pdfjs-ink = + .aria-label = Уређивач цртежа +pdfjs-ink-canvas = + .aria-label = КориÑнички направљена Ñлика + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Ðлтернативни текÑÑ‚ +pdfjs-editor-alt-text-edit-button = + .aria-label = Уреди алтернативни текÑÑ‚ +pdfjs-editor-alt-text-edit-button-label = Уреди алтернативни текÑÑ‚ +pdfjs-editor-alt-text-dialog-label = Одабери опцију +pdfjs-editor-alt-text-dialog-description = Ðлтернативни текÑÑ‚ помаже Ñлепим и Ñлабовидим оÑобама или када Ñе Ñлика не учита. +pdfjs-editor-alt-text-add-description-label = Додај Ð¾Ð¿Ð¸Ñ +pdfjs-editor-alt-text-add-description-description = Сажмите у 1-2 реченице које опиÑују предмет, окружење или радње. +pdfjs-editor-alt-text-mark-decorative-label = Означи као украÑно +pdfjs-editor-alt-text-mark-decorative-description = Ово је за украÑне Ñлике, као што Ñу ивице или водени печати. +pdfjs-editor-alt-text-cancel-button = Откажи +pdfjs-editor-alt-text-save-button = Сачувај +pdfjs-editor-alt-text-decorative-tooltip = Означено као украÑно +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Ðа пример: „Младић Ñеда за Ñто да једе“ +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Ðлтернативни текÑÑ‚ + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Горњи леви угао — промени величину +pdfjs-editor-resizer-label-top-middle = Средина горе — промени величину +pdfjs-editor-resizer-label-top-right = Горњи деÑни угао — промени величину +pdfjs-editor-resizer-label-middle-right = Средина деÑно — промени величину +pdfjs-editor-resizer-label-bottom-right = Доњи деÑни угао — промени величину +pdfjs-editor-resizer-label-bottom-middle = Средина доле — промени величину +pdfjs-editor-resizer-label-bottom-left = Доњи леви угао — промени величину +pdfjs-editor-resizer-label-middle-left = Средина лево — промени величину +pdfjs-editor-resizer-top-left = + .aria-label = Горњи леви угао — промени величину +pdfjs-editor-resizer-top-middle = + .aria-label = Средина горе — промени величину +pdfjs-editor-resizer-top-right = + .aria-label = Горњи деÑни угао — промени величину +pdfjs-editor-resizer-middle-right = + .aria-label = Средина деÑно — промени величину +pdfjs-editor-resizer-bottom-right = + .aria-label = Доњи деÑни угао — промени величину +pdfjs-editor-resizer-bottom-middle = + .aria-label = Средина доле — промени величину +pdfjs-editor-resizer-bottom-left = + .aria-label = Доњи леви угао — промени величину +pdfjs-editor-resizer-middle-left = + .aria-label = Средина лево — промени величину + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Боја означавања +pdfjs-editor-colorpicker-button = + .title = Промени боју +pdfjs-editor-colorpicker-dropdown = + .aria-label = Избор боја +pdfjs-editor-colorpicker-yellow = + .title = Жута +pdfjs-editor-colorpicker-green = + .title = Зелена +pdfjs-editor-colorpicker-blue = + .title = Плава +pdfjs-editor-colorpicker-pink = + .title = Розе +pdfjs-editor-colorpicker-red = + .title = Црвена + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Прикажи Ñве +pdfjs-editor-highlight-show-all-button = + .title = Прикажи Ñве + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Уреди алтернативни текÑÑ‚ (Ð¾Ð¿Ð¸Ñ Ñлике) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Додај алтернативни текÑÑ‚ (Ð¾Ð¿Ð¸Ñ Ñлике) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Ðапиши Ð¾Ð¿Ð¸Ñ Ð¾Ð²Ð´Ðµâ€¦ +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Кратак Ð¾Ð¿Ð¸Ñ Ð·Ð° Ñлепе и Ñлабовиде људе или када Ñе Ñлика не уÑпе учитати. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Овај алтернативни текÑÑ‚ је направљен аутоматÑки и може бити нетачан. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Сазнајте више +pdfjs-editor-new-alt-text-create-automatically-button-label = Прави алтернативни текÑÑ‚ аутоматÑки +pdfjs-editor-new-alt-text-not-now-button = Ðе Ñада + +## Image alt-text settings + diff --git a/public/assets/pdfjs/locale/sv-SE/viewer.ftl b/public/assets/pdfjs/locale/sv-SE/viewer.ftl new file mode 100755 index 0000000..6c4c610 --- /dev/null +++ b/public/assets/pdfjs/locale/sv-SE/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = FöregÃ¥ende sida +pdfjs-previous-button-label = FöregÃ¥ende +pdfjs-next-button = + .title = Nästa sida +pdfjs-next-button-label = Nästa +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Sida +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = av { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } av { $pagesCount }) +pdfjs-zoom-out-button = + .title = Zooma ut +pdfjs-zoom-out-button-label = Zooma ut +pdfjs-zoom-in-button = + .title = Zooma in +pdfjs-zoom-in-button-label = Zooma in +pdfjs-zoom-select = + .title = Zoom +pdfjs-presentation-mode-button = + .title = Byt till presentationsläge +pdfjs-presentation-mode-button-label = Presentationsläge +pdfjs-open-file-button = + .title = Öppna fil +pdfjs-open-file-button-label = Öppna +pdfjs-print-button = + .title = Skriv ut +pdfjs-print-button-label = Skriv ut +pdfjs-save-button = + .title = Spara +pdfjs-save-button-label = Spara +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Hämta +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Hämta +pdfjs-bookmark-button = + .title = Aktuell sida (Visa URL frÃ¥n aktuell sida) +pdfjs-bookmark-button-label = Aktuell sida + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Verktyg +pdfjs-tools-button-label = Verktyg +pdfjs-first-page-button = + .title = GÃ¥ till första sidan +pdfjs-first-page-button-label = GÃ¥ till första sidan +pdfjs-last-page-button = + .title = GÃ¥ till sista sidan +pdfjs-last-page-button-label = GÃ¥ till sista sidan +pdfjs-page-rotate-cw-button = + .title = Rotera medurs +pdfjs-page-rotate-cw-button-label = Rotera medurs +pdfjs-page-rotate-ccw-button = + .title = Rotera moturs +pdfjs-page-rotate-ccw-button-label = Rotera moturs +pdfjs-cursor-text-select-tool-button = + .title = Aktivera textmarkeringsverktyg +pdfjs-cursor-text-select-tool-button-label = Textmarkeringsverktyg +pdfjs-cursor-hand-tool-button = + .title = Aktivera handverktyg +pdfjs-cursor-hand-tool-button-label = Handverktyg +pdfjs-scroll-page-button = + .title = Använd sidrullning +pdfjs-scroll-page-button-label = Sidrullning +pdfjs-scroll-vertical-button = + .title = Använd vertikal rullning +pdfjs-scroll-vertical-button-label = Vertikal rullning +pdfjs-scroll-horizontal-button = + .title = Använd horisontell rullning +pdfjs-scroll-horizontal-button-label = Horisontell rullning +pdfjs-scroll-wrapped-button = + .title = Använd överlappande rullning +pdfjs-scroll-wrapped-button-label = Överlappande rullning +pdfjs-spread-none-button = + .title = Visa enkelsidor +pdfjs-spread-none-button-label = Enkelsidor +pdfjs-spread-odd-button = + .title = Visa uppslag med olika sidnummer till vänster +pdfjs-spread-odd-button-label = Uppslag med framsida +pdfjs-spread-even-button = + .title = Visa uppslag med lika sidnummer till vänster +pdfjs-spread-even-button-label = Uppslag utan framsida + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Dokumentegenskaper… +pdfjs-document-properties-button-label = Dokumentegenskaper… +pdfjs-document-properties-file-name = Filnamn: +pdfjs-document-properties-file-size = Filstorlek: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kB ({ $b } byte) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } byte) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } kB ({ $size_b } byte) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } byte) +pdfjs-document-properties-title = Titel: +pdfjs-document-properties-author = Författare: +pdfjs-document-properties-subject = Ämne: +pdfjs-document-properties-keywords = Nyckelord: +pdfjs-document-properties-creation-date = Skapades: +pdfjs-document-properties-modification-date = Ändrades: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Skapare: +pdfjs-document-properties-producer = PDF-producent: +pdfjs-document-properties-version = PDF-version: +pdfjs-document-properties-page-count = Sidantal: +pdfjs-document-properties-page-size = Pappersstorlek: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = porträtt +pdfjs-document-properties-page-size-orientation-landscape = landskap +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Snabb webbvisning: +pdfjs-document-properties-linearized-yes = Ja +pdfjs-document-properties-linearized-no = Nej +pdfjs-document-properties-close-button = Stäng + +## Print + +pdfjs-print-progress-message = Förbereder sidor för utskrift… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Avbryt +pdfjs-printing-not-supported = Varning: Utskrifter stöds inte helt av den här webbläsaren. +pdfjs-printing-not-ready = Varning: PDF:en är inte klar för utskrift. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Visa/dölj sidofält +pdfjs-toggle-sidebar-notification-button = + .title = Växla sidofält (dokumentet innehÃ¥ller dokumentstruktur/bilagor/lager) +pdfjs-toggle-sidebar-button-label = Visa/dölj sidofält +pdfjs-document-outline-button = + .title = Visa dokumentdisposition (dubbelklicka för att expandera/komprimera alla objekt) +pdfjs-document-outline-button-label = Dokumentöversikt +pdfjs-attachments-button = + .title = Visa Bilagor +pdfjs-attachments-button-label = Bilagor +pdfjs-layers-button = + .title = Visa lager (dubbelklicka för att Ã¥terställa alla lager till standardläge) +pdfjs-layers-button-label = Lager +pdfjs-thumbs-button = + .title = Visa miniatyrer +pdfjs-thumbs-button-label = Miniatyrer +pdfjs-current-outline-item-button = + .title = Hitta aktuellt dispositionsobjekt +pdfjs-current-outline-item-button-label = Aktuellt dispositionsobjekt +pdfjs-findbar-button = + .title = Sök i dokument +pdfjs-findbar-button-label = Sök +pdfjs-additional-layers = Ytterligare lager + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Sida { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatyr av sida { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Sök + .placeholder = Sök i dokument… +pdfjs-find-previous-button = + .title = Hitta föregÃ¥ende förekomst av frasen +pdfjs-find-previous-button-label = FöregÃ¥ende +pdfjs-find-next-button = + .title = Hitta nästa förekomst av frasen +pdfjs-find-next-button-label = Nästa +pdfjs-find-highlight-checkbox = Markera alla +pdfjs-find-match-case-checkbox-label = Matcha versal/gemen +pdfjs-find-match-diacritics-checkbox-label = Matcha diakritiska tecken +pdfjs-find-entire-word-checkbox-label = Hela ord +pdfjs-find-reached-top = NÃ¥dde början av dokumentet, började frÃ¥n slutet +pdfjs-find-reached-bottom = NÃ¥dde slutet pÃ¥ dokumentet, började frÃ¥n början +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } av { $total } match + *[other] { $current } av { $total } matchningar + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Mer än { $limit } matchning + *[other] Fler än { $limit } matchningar + } +pdfjs-find-not-found = Frasen hittades inte + +## Predefined zoom values + +pdfjs-page-scale-width = Sidbredd +pdfjs-page-scale-fit = Anpassa sida +pdfjs-page-scale-auto = Automatisk zoom +pdfjs-page-scale-actual = Verklig storlek +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Sida { $page } + +## Loading indicator messages + +pdfjs-loading-error = Ett fel uppstod vid laddning av PDF-filen. +pdfjs-invalid-file-error = Ogiltig eller korrupt PDF-fil. +pdfjs-missing-file-error = Saknad PDF-fil. +pdfjs-unexpected-response-error = Oväntat svar frÃ¥n servern. +pdfjs-rendering-error = Ett fel uppstod vid visning av sidan. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date } { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type }-annotering] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Skriv in lösenordet för att öppna PDF-filen. +pdfjs-password-invalid = Ogiltigt lösenord. Försök igen. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Avbryt +pdfjs-web-fonts-disabled = Webbtypsnitt är inaktiverade: kan inte använda inbäddade PDF-typsnitt. + +## Editing + +pdfjs-editor-free-text-button = + .title = Text +pdfjs-editor-free-text-button-label = Text +pdfjs-editor-ink-button = + .title = Rita +pdfjs-editor-ink-button-label = Rita +pdfjs-editor-stamp-button = + .title = Lägg till eller redigera bilder +pdfjs-editor-stamp-button-label = Lägg till eller redigera bilder +pdfjs-editor-highlight-button = + .title = Markera +pdfjs-editor-highlight-button-label = Markera +pdfjs-highlight-floating-button1 = + .title = Markera + .aria-label = Markera +pdfjs-highlight-floating-button-label = Markera + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Ta bort ritning +pdfjs-editor-remove-freetext-button = + .title = Ta bort text +pdfjs-editor-remove-stamp-button = + .title = Ta bort bild +pdfjs-editor-remove-highlight-button = + .title = Ta bort markering + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Färg +pdfjs-editor-free-text-size-input = Storlek +pdfjs-editor-ink-color-input = Färg +pdfjs-editor-ink-thickness-input = Tjocklek +pdfjs-editor-ink-opacity-input = Opacitet +pdfjs-editor-stamp-add-image-button = + .title = Lägg till bild +pdfjs-editor-stamp-add-image-button-label = Lägg till bild +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Tjocklek +pdfjs-editor-free-highlight-thickness-title = + .title = Ändra tjocklek när du markerar andra objekt än text +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Textredigerare + .default-content = Börja skriva… +pdfjs-free-text = + .aria-label = Textredigerare +pdfjs-free-text-default-content = Börja skriva… +pdfjs-ink = + .aria-label = Ritredigerare +pdfjs-ink-canvas = + .aria-label = Användarskapad bild + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alternativ text +pdfjs-editor-alt-text-edit-button = + .aria-label = Redigera alternativ text +pdfjs-editor-alt-text-edit-button-label = Redigera alternativ text +pdfjs-editor-alt-text-dialog-label = Välj ett alternativ +pdfjs-editor-alt-text-dialog-description = Alt text (alternativ text) hjälper till när människor inte kan se bilden eller när den inte laddas. +pdfjs-editor-alt-text-add-description-label = Lägg till en beskrivning +pdfjs-editor-alt-text-add-description-description = Sikta pÃ¥ 1-2 meningar som beskriver ämnet, miljön eller handlingen. +pdfjs-editor-alt-text-mark-decorative-label = Markera som dekorativ +pdfjs-editor-alt-text-mark-decorative-description = Detta används för dekorativa bilder, som kanter eller vattenstämplar. +pdfjs-editor-alt-text-cancel-button = Avbryt +pdfjs-editor-alt-text-save-button = Spara +pdfjs-editor-alt-text-decorative-tooltip = Märkt som dekorativ +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Till exempel, "En ung man sätter sig vid ett bord för att äta en mÃ¥ltid" +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alternativ text + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Det övre vänstra hörnet — ändra storlek +pdfjs-editor-resizer-label-top-middle = Överst i mitten — ändra storlek +pdfjs-editor-resizer-label-top-right = Det övre högra hörnet — ändra storlek +pdfjs-editor-resizer-label-middle-right = Mitten höger — ändra storlek +pdfjs-editor-resizer-label-bottom-right = Nedre högra hörnet — ändra storlek +pdfjs-editor-resizer-label-bottom-middle = Nedre mitten — ändra storlek +pdfjs-editor-resizer-label-bottom-left = Nedre vänstra hörnet — ändra storlek +pdfjs-editor-resizer-label-middle-left = Mitten till vänster — ändra storlek +pdfjs-editor-resizer-top-left = + .aria-label = Det övre vänstra hörnet — ändra storlek +pdfjs-editor-resizer-top-middle = + .aria-label = Överst i mitten — ändra storlek +pdfjs-editor-resizer-top-right = + .aria-label = Det övre högra hörnet — ändra storlek +pdfjs-editor-resizer-middle-right = + .aria-label = Mitten höger — ändra storlek +pdfjs-editor-resizer-bottom-right = + .aria-label = Nedre högra hörnet — ändra storlek +pdfjs-editor-resizer-bottom-middle = + .aria-label = Nedre mitten — ändra storlek +pdfjs-editor-resizer-bottom-left = + .aria-label = Nedre vänstra hörnet — ändra storlek +pdfjs-editor-resizer-middle-left = + .aria-label = Mitten till vänster — ändra storlek + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Markeringsfärg +pdfjs-editor-colorpicker-button = + .title = Ändra färg +pdfjs-editor-colorpicker-dropdown = + .aria-label = Färgval +pdfjs-editor-colorpicker-yellow = + .title = Gul +pdfjs-editor-colorpicker-green = + .title = Grön +pdfjs-editor-colorpicker-blue = + .title = BlÃ¥ +pdfjs-editor-colorpicker-pink = + .title = Rosa +pdfjs-editor-colorpicker-red = + .title = Röd + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Visa alla +pdfjs-editor-highlight-show-all-button = + .title = Visa alla + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Redigera alternativ text (bildbeskrivning) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Lägg till alternativ text (bildbeskrivning) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Skriv din beskrivning här… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Kort beskrivning för personer som inte kan se bilden eller när bilden inte laddas. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Denna alternativa text skapades automatiskt och kan vara felaktig. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Läs mer +pdfjs-editor-new-alt-text-create-automatically-button-label = Skapa alternativ text automatiskt +pdfjs-editor-new-alt-text-not-now-button = Inte nu +pdfjs-editor-new-alt-text-error-title = Det gick inte att skapa alternativ text automatiskt +pdfjs-editor-new-alt-text-error-description = Skriv din egna alternativa text eller försök igen senare. +pdfjs-editor-new-alt-text-error-close-button = Stäng +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Hämtar AI-modell med alternativ text ({ $downloadedSize } av { $totalSize } MB) + .aria-valuetext = Hämtar AI-modell med alternativ text ({ $downloadedSize } av { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alternativ text tillagd +pdfjs-editor-new-alt-text-added-button-label = Alternativ text tillagd +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Saknar alternativ text +pdfjs-editor-new-alt-text-missing-button-label = Saknar alternativ text +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Granska alternativ text +pdfjs-editor-new-alt-text-to-review-button-label = Granska alternativ text +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Skapas automatiskt: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Alternativ textinställningar för bild +pdfjs-image-alt-text-settings-button-label = Alternativ textinställningar för bild +pdfjs-editor-alt-text-settings-dialog-label = Alternativ textinställningar för bild +pdfjs-editor-alt-text-settings-automatic-title = Automatisk alternativ text +pdfjs-editor-alt-text-settings-create-model-button-label = Skapa alternativ text automatiskt +pdfjs-editor-alt-text-settings-create-model-description = FöreslÃ¥r beskrivningar för att hjälpa personer som inte kan se bilden eller när bilden inte laddas. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = AI-modell för alternativ text ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Körs lokalt pÃ¥ din enhet sÃ¥ att din data förblir privat. Krävs för automatisk alternativ text. +pdfjs-editor-alt-text-settings-delete-model-button = Ta bort +pdfjs-editor-alt-text-settings-download-model-button = Hämta +pdfjs-editor-alt-text-settings-downloading-model-button = Hämtar… +pdfjs-editor-alt-text-settings-editor-title = Alternativ textredigerare +pdfjs-editor-alt-text-settings-show-dialog-button-label = Visa alternativ textredigerare direkt när du lägger till en bild +pdfjs-editor-alt-text-settings-show-dialog-description = Hjälper dig att se till att alla dina bilder har alternativ text. +pdfjs-editor-alt-text-settings-close-button = Stäng + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Markering borttagen +pdfjs-editor-undo-bar-message-freetext = Text borttagen +pdfjs-editor-undo-bar-message-ink = Ritning borttagen +pdfjs-editor-undo-bar-message-stamp = Bild borttagen +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } anteckning har tagits bort + *[other] { $count } anteckningar har tagits bort + } +pdfjs-editor-undo-bar-undo-button = + .title = Ã…ngra +pdfjs-editor-undo-bar-undo-button-label = Ã…ngra +pdfjs-editor-undo-bar-close-button = + .title = Stäng +pdfjs-editor-undo-bar-close-button-label = Stäng diff --git a/public/assets/pdfjs/locale/szl/viewer.ftl b/public/assets/pdfjs/locale/szl/viewer.ftl new file mode 100755 index 0000000..cbf166e --- /dev/null +++ b/public/assets/pdfjs/locale/szl/viewer.ftl @@ -0,0 +1,257 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Piyrwyjszo strÅna +pdfjs-previous-button-label = Piyrwyjszo +pdfjs-next-button = + .title = Nastympno strÅna +pdfjs-next-button-label = Dalij +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = StrÅna +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = ze { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } ze { $pagesCount }) +pdfjs-zoom-out-button = + .title = ZmyÅ„sz +pdfjs-zoom-out-button-label = ZmyÅ„sz +pdfjs-zoom-in-button = + .title = Zwiynksz +pdfjs-zoom-in-button-label = Zwiynksz +pdfjs-zoom-select = + .title = Srogość +pdfjs-presentation-mode-button = + .title = PrzeÅ‚Åncz na tryb prezyntacyje +pdfjs-presentation-mode-button-label = Tryb prezyntacyje +pdfjs-open-file-button = + .title = Ôdewrzij zbiÅr +pdfjs-open-file-button-label = Ôdewrzij +pdfjs-print-button = + .title = Durkuj +pdfjs-print-button-label = Durkuj + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Noczynia +pdfjs-tools-button-label = Noczynia +pdfjs-first-page-button = + .title = Idź ku piyrszyj strÅnie +pdfjs-first-page-button-label = Idź ku piyrszyj strÅnie +pdfjs-last-page-button = + .title = Idź ku ôstatnij strÅnie +pdfjs-last-page-button-label = Idź ku ôstatnij strÅnie +pdfjs-page-rotate-cw-button = + .title = Zwyrtnij w prawo +pdfjs-page-rotate-cw-button-label = Zwyrtnij w prawo +pdfjs-page-rotate-ccw-button = + .title = Zwyrtnij w lewo +pdfjs-page-rotate-ccw-button-label = Zwyrtnij w lewo +pdfjs-cursor-text-select-tool-button = + .title = ZaÅ‚Åncz noczynie ôbiyranio tekstu +pdfjs-cursor-text-select-tool-button-label = Noczynie ôbiyranio tekstu +pdfjs-cursor-hand-tool-button = + .title = ZaÅ‚Åncz noczynie rÅnczka +pdfjs-cursor-hand-tool-button-label = Noczynie rÅnczka +pdfjs-scroll-vertical-button = + .title = Używej piÅnowego przewijanio +pdfjs-scroll-vertical-button-label = PiÅnowe przewijanie +pdfjs-scroll-horizontal-button = + .title = Używej poziÅmego przewijanio +pdfjs-scroll-horizontal-button-label = PoziÅme przewijanie +pdfjs-scroll-wrapped-button = + .title = Używej szichtowego przewijanio +pdfjs-scroll-wrapped-button-label = Szichtowe przewijanie +pdfjs-spread-none-button = + .title = Niy dowej strÅn w widoku po dwie +pdfjs-spread-none-button-label = Po jednyj strÅnie +pdfjs-spread-odd-button = + .title = Pokoż strÅny po dwie; niyporziste po lewyj +pdfjs-spread-odd-button-label = Niyporziste po lewyj +pdfjs-spread-even-button = + .title = Pokoż strÅny po dwie; porziste po lewyj +pdfjs-spread-even-button-label = Porziste po lewyj + +## Document properties dialog + +pdfjs-document-properties-button = + .title = WÅ‚osnoÅ›ci dokumyntu… +pdfjs-document-properties-button-label = WÅ‚osnoÅ›ci dokumyntu… +pdfjs-document-properties-file-name = Miano zbioru: +pdfjs-document-properties-file-size = Srogość zbioru: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } B) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } B) +pdfjs-document-properties-title = TytuÅ‚: +pdfjs-document-properties-author = AutÅr: +pdfjs-document-properties-subject = Tymat: +pdfjs-document-properties-keywords = Kluczowe sÅ‚owa: +pdfjs-document-properties-creation-date = Data zrychtowanio: +pdfjs-document-properties-modification-date = Data zmiany: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Zrychtowane ôd: +pdfjs-document-properties-producer = PDF ôd: +pdfjs-document-properties-version = Wersyjo PDF: +pdfjs-document-properties-page-count = Wielość strÅn: +pdfjs-document-properties-page-size = Srogość strÅny: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = piÅnowo +pdfjs-document-properties-page-size-orientation-landscape = poziÅmo +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Gibki necowy podglÅnd: +pdfjs-document-properties-linearized-yes = Ja +pdfjs-document-properties-linearized-no = Niy +pdfjs-document-properties-close-button = Zawrzij + +## Print + +pdfjs-print-progress-message = Rychtowanie dokumyntu do durku… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Pociep +pdfjs-printing-not-supported = PozÅr: Ta przeglÅndarka niy coÅ‚kiym ôbsuguje durk. +pdfjs-printing-not-ready = PozÅr: Tyn PDF niy ma za tela zaladowany do durku. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = PrzeÅ‚Åncz posek na rancie +pdfjs-toggle-sidebar-notification-button = + .title = PrzeÅ‚Åncz posek na rancie (dokumynt mo struktura/przidowki/warstwy) +pdfjs-toggle-sidebar-button-label = PrzeÅ‚Åncz posek na rancie +pdfjs-document-outline-button = + .title = Pokoż struktura dokumyntu (tuplowane klikniyncie rozszyrzo/swijo wszyskie elymynta) +pdfjs-document-outline-button-label = Struktura dokumyntu +pdfjs-attachments-button = + .title = Pokoż przidowki +pdfjs-attachments-button-label = Przidowki +pdfjs-layers-button = + .title = Pokoż warstwy (tuplowane klikniyncie resetuje wszyskie warstwy do bazowego stanu) +pdfjs-layers-button-label = Warstwy +pdfjs-thumbs-button = + .title = Pokoż miniatury +pdfjs-thumbs-button-label = Miniatury +pdfjs-findbar-button = + .title = Znojdź w dokumyncie +pdfjs-findbar-button-label = Znojdź +pdfjs-additional-layers = Nadbytnie warstwy + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = StrÅna { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Miniatura strÅny { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Znojdź + .placeholder = Znojdź w dokumyncie… +pdfjs-find-previous-button = + .title = Znojdź piyrwyjsze pokozanie sie tyj frazy +pdfjs-find-previous-button-label = Piyrwyjszo +pdfjs-find-next-button = + .title = Znojdź nastympne pokozanie sie tyj frazy +pdfjs-find-next-button-label = Dalij +pdfjs-find-highlight-checkbox = Zaznacz wszysko +pdfjs-find-match-case-checkbox-label = Poznowej srogość liter +pdfjs-find-entire-word-checkbox-label = CoÅ‚ke sÅ‚owa +pdfjs-find-reached-top = DoszÅ‚o do samego wiyrchu strÅny, dalij ôd spodku +pdfjs-find-reached-bottom = DoszÅ‚o do samego spodku strÅny, dalij ôd wiyrchu +pdfjs-find-not-found = Fraza niy znaleziÅno + +## Predefined zoom values + +pdfjs-page-scale-width = Szyrzka strÅny +pdfjs-page-scale-fit = Napasowanie strÅny +pdfjs-page-scale-auto = AutÅmatyczno srogość +pdfjs-page-scale-actual = Aktualno srogość +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = Przi ladowaniu PDFa pokozoÅ‚ sie feler. +pdfjs-invalid-file-error = ZÅ‚y abo felerny zbiÅr PDF. +pdfjs-missing-file-error = Chybio zbioru PDF. +pdfjs-unexpected-response-error = Niyôczekowano ôdpowiydź serwera. +pdfjs-rendering-error = Przi renderowaniu strÅny pokozoÅ‚ sie feler. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Anotacyjo typu { $type }] + +## Password + +pdfjs-password-label = Wkludź hasÅ‚o, coby ôdewrzić tyn zbiÅr PDF. +pdfjs-password-invalid = HasÅ‚o je zÅ‚e. SprÅbuj jeszcze roz. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Pociep +pdfjs-web-fonts-disabled = Necowe fÅnty sÅm zastawiÅne: niy idzie użyć wkludzÅnych fÅntÅw PDF. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/ta/viewer.ftl b/public/assets/pdfjs/locale/ta/viewer.ftl new file mode 100755 index 0000000..82cf197 --- /dev/null +++ b/public/assets/pdfjs/locale/ta/viewer.ftl @@ -0,0 +1,223 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = à®®à¯à®¨à¯à®¤à¯ˆà®¯ பகà¯à®•ம௠+pdfjs-previous-button-label = à®®à¯à®¨à¯à®¤à¯ˆà®¯à®¤à¯ +pdfjs-next-button = + .title = அடà¯à®¤à¯à®¤ பகà¯à®•ம௠+pdfjs-next-button-label = அடà¯à®¤à¯à®¤à¯ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = பகà¯à®•ம௠+# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = { $pagesCount } இல௠+# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = { $pagesCount }) இல௠({ $pageNumber } +pdfjs-zoom-out-button = + .title = சிறிதாகà¯à®•௠+pdfjs-zoom-out-button-label = சிறிதாகà¯à®•௠+pdfjs-zoom-in-button = + .title = பெரிதாகà¯à®•௠+pdfjs-zoom-in-button-label = பெரிதாகà¯à®•௠+pdfjs-zoom-select = + .title = பெரிதாகà¯à®•௠+pdfjs-presentation-mode-button = + .title = விளகà¯à®•காடà¯à®šà®¿ பயனà¯à®®à¯à®±à¯ˆà®•à¯à®•௠மாற௠+pdfjs-presentation-mode-button-label = விளகà¯à®•காடà¯à®šà®¿ பயனà¯à®®à¯à®±à¯ˆ +pdfjs-open-file-button = + .title = கோபà¯à®ªà®¿à®©à¯ˆ திற +pdfjs-open-file-button-label = திற +pdfjs-print-button = + .title = அசà¯à®šà®¿à®Ÿà¯ +pdfjs-print-button-label = அசà¯à®šà®¿à®Ÿà¯ + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = கரà¯à®µà®¿à®•ள௠+pdfjs-tools-button-label = கரà¯à®µà®¿à®•ள௠+pdfjs-first-page-button = + .title = à®®à¯à®¤à®²à¯ பகà¯à®•தà¯à®¤à®¿à®±à¯à®•௠செலà¯à®²à®µà¯à®®à¯ +pdfjs-first-page-button-label = à®®à¯à®¤à®²à¯ பகà¯à®•தà¯à®¤à®¿à®±à¯à®•௠செலà¯à®²à®µà¯à®®à¯ +pdfjs-last-page-button = + .title = கடைசி பகà¯à®•தà¯à®¤à®¿à®±à¯à®•௠செலà¯à®²à®µà¯à®®à¯ +pdfjs-last-page-button-label = கடைசி பகà¯à®•தà¯à®¤à®¿à®±à¯à®•௠செலà¯à®²à®µà¯à®®à¯ +pdfjs-page-rotate-cw-button = + .title = வலஞà¯à®šà¯à®´à®¿à®¯à®¾à®• சà¯à®´à®±à¯à®±à¯ +pdfjs-page-rotate-cw-button-label = வலஞà¯à®šà¯à®´à®¿à®¯à®¾à®• சà¯à®´à®±à¯à®±à¯ +pdfjs-page-rotate-ccw-button = + .title = இடஞà¯à®šà¯à®´à®¿à®¯à®¾à®• சà¯à®´à®±à¯à®±à¯ +pdfjs-page-rotate-ccw-button-label = இடஞà¯à®šà¯à®´à®¿à®¯à®¾à®• சà¯à®´à®±à¯à®±à¯ +pdfjs-cursor-text-select-tool-button = + .title = உரைத௠தெரிவ௠கரà¯à®µà®¿à®¯à¯ˆà®šà¯ செயலà¯à®ªà®Ÿà¯à®¤à¯à®¤à¯ +pdfjs-cursor-text-select-tool-button-label = உரைத௠தெரிவ௠கரà¯à®µà®¿ +pdfjs-cursor-hand-tool-button = + .title = கைக௠கரà¯à®µà®¿à®•à¯à®šà¯ செயறà¯à®ªà®Ÿà¯à®¤à¯à®¤à¯ +pdfjs-cursor-hand-tool-button-label = கைகà¯à®•à¯à®°à¯à®µà®¿ + +## Document properties dialog + +pdfjs-document-properties-button = + .title = ஆவண பணà¯à®ªà¯à®•ளà¯... +pdfjs-document-properties-button-label = ஆவண பணà¯à®ªà¯à®•ளà¯... +pdfjs-document-properties-file-name = கோபà¯à®ªà¯ பெயரà¯: +pdfjs-document-properties-file-size = கோபà¯à®ªà®¿à®©à¯ அளவà¯: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } கிபை ({ $size_b } பைடà¯à®Ÿà¯à®•ளà¯) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } மெபை ({ $size_b } பைடà¯à®Ÿà¯à®•ளà¯) +pdfjs-document-properties-title = தலைபà¯à®ªà¯: +pdfjs-document-properties-author = எழà¯à®¤à®¿à®¯à®µà®°à¯ +pdfjs-document-properties-subject = பொரà¯à®³à¯: +pdfjs-document-properties-keywords = à®®à¯à®•à¯à®•ிய வாரà¯à®¤à¯à®¤à¯ˆà®•ளà¯: +pdfjs-document-properties-creation-date = படைதà¯à®¤ தேதி : +pdfjs-document-properties-modification-date = திரà¯à®¤à¯à®¤à®¿à®¯ தேதி: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = உரà¯à®µà®¾à®•à¯à®•à¯à®ªà®µà®°à¯: +pdfjs-document-properties-producer = பிடிஎஃப௠தயாரிபà¯à®ªà®¾à®³à®°à¯: +pdfjs-document-properties-version = PDF பதிபà¯à®ªà¯: +pdfjs-document-properties-page-count = பகà¯à®• எணà¯à®£à®¿à®•à¯à®•ை: +pdfjs-document-properties-page-size = பகà¯à®• அளவà¯: +pdfjs-document-properties-page-size-unit-inches = இதில௠+pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = நிலைபதிபà¯à®ªà¯ +pdfjs-document-properties-page-size-orientation-landscape = நிலைபரபà¯à®ªà¯ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = கடிதம௠+pdfjs-document-properties-page-size-name-legal = சடà¯à®Ÿà®ªà¯‚à®°à¯à®µ + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +pdfjs-document-properties-close-button = மூடà¯à®• + +## Print + +pdfjs-print-progress-message = அசà¯à®šà®¿à®Ÿà¯à®µà®¤à®±à¯à®•ான ஆவணம௠தயாராகிறதà¯... +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = ரதà¯à®¤à¯ +pdfjs-printing-not-supported = எசà¯à®šà®°à®¿à®•à¯à®•ை: இநà¯à®¤ உலாவி அசà¯à®šà®¿à®Ÿà¯à®¤à®²à¯ˆ à®®à¯à®´à¯à®®à¯ˆà®¯à®¾à®• ஆதரிகà¯à®•விலà¯à®²à¯ˆ. +pdfjs-printing-not-ready = எசà¯à®šà®°à®¿à®•à¯à®•ை: PDF அசà¯à®šà®¿à®Ÿ à®®à¯à®´à¯à®µà®¤à¯à®®à®¾à®• à®à®±à¯à®±à®ªà¯à®ªà®Ÿà®µà®¿à®²à¯à®²à¯ˆ. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = பகà¯à®•ப௠படà¯à®Ÿà®¿à®¯à¯ˆ நிலைமாறà¯à®±à¯ +pdfjs-toggle-sidebar-button-label = பகà¯à®•ப௠படà¯à®Ÿà®¿à®¯à¯ˆ நிலைமாறà¯à®±à¯ +pdfjs-document-outline-button = + .title = ஆவண அடகà¯à®•தà¯à®¤à¯ˆà®•௠காடà¯à®Ÿà¯ (இரà¯à®®à¯à®±à¯ˆà®šà¯ சொடà¯à®•à¯à®•ி அனைதà¯à®¤à¯ உறà¯à®ªà¯à®ªà®¿à®Ÿà®¿à®•ளையà¯à®®à¯ விரி/சேரà¯) +pdfjs-document-outline-button-label = ஆவண வெளிவரை +pdfjs-attachments-button = + .title = இணைபà¯à®ªà¯à®•ளை காணà¯à®ªà®¿ +pdfjs-attachments-button-label = இணைபà¯à®ªà¯à®•ள௠+pdfjs-thumbs-button = + .title = சிறà¯à®ªà®Ÿà®™à¯à®•ளைக௠காணà¯à®ªà®¿ +pdfjs-thumbs-button-label = சிறà¯à®ªà®Ÿà®™à¯à®•ள௠+pdfjs-findbar-button = + .title = ஆவணதà¯à®¤à®¿à®²à¯ கணà¯à®Ÿà®±à®¿ +pdfjs-findbar-button-label = தேட௠+ +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = பகà¯à®•ம௠{ $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = பகà¯à®•தà¯à®¤à®¿à®©à¯ சிறà¯à®ªà®Ÿà®®à¯ { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = கணà¯à®Ÿà¯à®ªà®¿à®Ÿà®¿ + .placeholder = ஆவணதà¯à®¤à®¿à®²à¯ கணà¯à®Ÿà®±à®¿â€¦ +pdfjs-find-previous-button = + .title = இநà¯à®¤ சொறà¯à®±à¯Šà®Ÿà®°à®¿à®©à¯ à®®à¯à®¨à¯à®¤à¯ˆà®¯ நிகழà¯à®µà¯ˆ தேட௠+pdfjs-find-previous-button-label = à®®à¯à®¨à¯à®¤à¯ˆà®¯à®¤à¯ +pdfjs-find-next-button = + .title = இநà¯à®¤ சொறà¯à®±à¯Šà®Ÿà®°à®¿à®©à¯ அடà¯à®¤à¯à®¤ நிகழà¯à®µà¯ˆ தேட௠+pdfjs-find-next-button-label = அடà¯à®¤à¯à®¤à¯ +pdfjs-find-highlight-checkbox = அனைதà¯à®¤à¯ˆà®¯à¯à®®à¯ தனிபà¯à®ªà®Ÿà¯à®¤à¯à®¤à¯ +pdfjs-find-match-case-checkbox-label = பேரெழà¯à®¤à¯à®¤à®¾à®•à¯à®•தà¯à®¤à¯ˆ உணர௠+pdfjs-find-reached-top = ஆவணதà¯à®¤à®¿à®©à¯ மேல௠பகà¯à®¤à®¿à®¯à¯ˆ அடைநà¯à®¤à®¤à¯, அடிபà¯à®ªà®•à¯à®•தà¯à®¤à®¿à®²à®¿à®°à¯à®¨à¯à®¤à¯ தொடரà¯à®¨à¯à®¤à®¤à¯ +pdfjs-find-reached-bottom = ஆவணதà¯à®¤à®¿à®©à¯ à®®à¯à®Ÿà®¿à®µà¯ˆ அடைநà¯à®¤à®¤à¯, மேலிரà¯à®¨à¯à®¤à¯ தொடரà¯à®¨à¯à®¤à®¤à¯ +pdfjs-find-not-found = சொறà¯à®±à¯Šà®Ÿà®°à¯ காணவிலà¯à®²à¯ˆ + +## Predefined zoom values + +pdfjs-page-scale-width = பகà¯à®• அகலம௠+pdfjs-page-scale-fit = பகà¯à®•ப௠பொரà¯à®¤à¯à®¤à®®à¯ +pdfjs-page-scale-auto = தானியகà¯à®• பெரிதாகà¯à®•ல௠+pdfjs-page-scale-actual = உணà¯à®®à¯ˆà®¯à®¾à®© அளவ௠+# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = PDF à® à®à®±à¯à®±à¯à®®à¯ போத௠ஒர௠பிழை à®à®±à¯à®ªà®Ÿà¯à®Ÿà®¤à¯. +pdfjs-invalid-file-error = செலà¯à®²à¯à®ªà®Ÿà®¿à®¯à®¾à®•ாத அலà¯à®²à®¤à¯ சிதைநà¯à®¤ PDF கோபà¯à®ªà¯. +pdfjs-missing-file-error = PDF கோபà¯à®ªà¯ காணவிலà¯à®²à¯ˆ. +pdfjs-unexpected-response-error = சேவகன௠பதில௠எதிரà¯à®ªà®¾à®°à®¤à®¤à¯. +pdfjs-rendering-error = இநà¯à®¤à®ªà¯ பகà¯à®•தà¯à®¤à¯ˆ காடà¯à®šà®¿à®ªà¯à®ªà®Ÿà¯à®¤à¯à®¤à¯à®®à¯ போத௠ஒர௠பிழை à®à®±à¯à®ªà®Ÿà¯à®Ÿà®¤à¯. + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } விளகà¯à®•à®®à¯] + +## Password + +pdfjs-password-label = இநà¯à®¤ PDF கோபà¯à®ªà¯ˆ திறகà¯à®• கடவà¯à®šà¯à®šà¯†à®¾à®²à¯à®²à¯ˆ உளà¯à®³à®¿à®Ÿà®µà¯à®®à¯. +pdfjs-password-invalid = செலà¯à®²à¯à®ªà®Ÿà®¿à®¯à®¾à®•ாத கடவà¯à®šà¯à®šà¯Šà®²à¯, தயை செயà¯à®¤à¯ மீணà¯à®Ÿà¯à®®à¯ à®®à¯à®¯à®±à¯à®šà®¿ செயà¯à®•. +pdfjs-password-ok-button = சரி +pdfjs-password-cancel-button = ரதà¯à®¤à¯ +pdfjs-web-fonts-disabled = வலை எழà¯à®¤à¯à®¤à¯à®°à¯à®•à¯à®•ள௠மà¯à®Ÿà®•à¯à®•பà¯à®ªà®Ÿà¯à®Ÿà¯à®³à¯à®³à®©: உடà¯à®ªà¯Šà®¤à®¿à®•à¯à®•பà¯à®ªà®Ÿà¯à®Ÿ PDF எழà¯à®¤à¯à®¤à¯à®°à¯à®•à¯à®•ளைப௠பயனà¯à®ªà®Ÿà¯à®¤à¯à®¤ à®®à¯à®Ÿà®¿à®¯à®µà®¿à®²à¯à®²à¯ˆ. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/te/viewer.ftl b/public/assets/pdfjs/locale/te/viewer.ftl new file mode 100755 index 0000000..94dc2b8 --- /dev/null +++ b/public/assets/pdfjs/locale/te/viewer.ftl @@ -0,0 +1,239 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = à°®à±à°¨à±à°ªà°Ÿà°¿ పేజీ +pdfjs-previous-button-label = à°•à±à°°à°¿à°¤à°‚ +pdfjs-next-button = + .title = తరà±à°µà°¾à°¤ పేజీ +pdfjs-next-button-label = తరà±à°µà°¾à°¤ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = పేజీ +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = మొతà±à°¤à°‚ { $pagesCount } లో +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = (మొతà±à°¤à°‚ { $pagesCount } లో { $pageNumber }వది) +pdfjs-zoom-out-button = + .title = జూమౠతగà±à°—à°¿à°‚à°šà± +pdfjs-zoom-out-button-label = జూమౠతగà±à°—à°¿à°‚à°šà± +pdfjs-zoom-in-button = + .title = జూమౠచేయి +pdfjs-zoom-in-button-label = జూమౠచేయి +pdfjs-zoom-select = + .title = జూమౠ+pdfjs-presentation-mode-button = + .title = à°ªà±à°°à°¦à°°à±à°¶à°¨à°¾ రీతికి మారౠ+pdfjs-presentation-mode-button-label = à°ªà±à°°à°¦à°°à±à°¶à°¨à°¾ రీతి +pdfjs-open-file-button = + .title = ఫైలౠతెరà±à°µà± +pdfjs-open-file-button-label = తెరà±à°µà± +pdfjs-print-button = + .title = à°®à±à°¦à±à°°à°¿à°‚à°šà± +pdfjs-print-button-label = à°®à±à°¦à±à°°à°¿à°‚à°šà± + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = పనిమà±à°Ÿà±à°²à± +pdfjs-tools-button-label = పనిమà±à°Ÿà±à°²à± +pdfjs-first-page-button = + .title = మొదటి పేజీకి వెళà±à°³à± +pdfjs-first-page-button-label = మొదటి పేజీకి వెళà±à°³à± +pdfjs-last-page-button = + .title = చివరి పేజీకి వెళà±à°³à± +pdfjs-last-page-button-label = చివరి పేజీకి వెళà±à°³à± +pdfjs-page-rotate-cw-button = + .title = సవà±à°¯à°¦à°¿à°¶à°²à±‹ తిపà±à°ªà± +pdfjs-page-rotate-cw-button-label = సవà±à°¯à°¦à°¿à°¶à°²à±‹ తిపà±à°ªà± +pdfjs-page-rotate-ccw-button = + .title = అపసవà±à°¯à°¦à°¿à°¶à°²à±‹ తిపà±à°ªà± +pdfjs-page-rotate-ccw-button-label = అపసవà±à°¯à°¦à°¿à°¶à°²à±‹ తిపà±à°ªà± +pdfjs-cursor-text-select-tool-button = + .title = టెకà±à°¸à±à°Ÿà± ఎంపిక సాధనానà±à°¨à°¿ à°ªà±à°°à°¾à°°à°‚à°­à°¿à°‚à°šà°‚à°¡à°¿ +pdfjs-cursor-text-select-tool-button-label = టెకà±à°¸à±à°Ÿà± ఎంపిక సాధనం +pdfjs-cursor-hand-tool-button = + .title = చేతి సాధనం చేతనించౠ+pdfjs-cursor-hand-tool-button-label = చేతి సాధనం +pdfjs-scroll-vertical-button-label = నిలà±à°µà± à°¸à±à°•à±à°°à±‹à°²à°¿à°‚à°—à± + +## Document properties dialog + +pdfjs-document-properties-button = + .title = పతà±à°°à°®à± లకà±à°·à°£à°¾à°²à±... +pdfjs-document-properties-button-label = పతà±à°°à°®à± లకà±à°·à°£à°¾à°²à±... +pdfjs-document-properties-file-name = దసà±à°¤à±à°°à°‚ పేరà±: +pdfjs-document-properties-file-size = దసà±à°¤à±à°°à°‚ పరిమాణం: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = శీరà±à°·à°¿à°•: +pdfjs-document-properties-author = మూలకరà±à°¤: +pdfjs-document-properties-subject = విషయం: +pdfjs-document-properties-keywords = à°•à±€ పదాలà±: +pdfjs-document-properties-creation-date = సృషà±à°Ÿà°¿à°‚à°šà°¿à°¨ తేదీ: +pdfjs-document-properties-modification-date = సవరించిన తేదీ: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = సృషà±à°Ÿà°¿à°•à°°à±à°¤: +pdfjs-document-properties-producer = PDF ఉతà±à°ªà°¾à°¦à°•à°¿: +pdfjs-document-properties-version = PDF వరà±à°·à°¨à±: +pdfjs-document-properties-page-count = పేజీల సంఖà±à°¯: +pdfjs-document-properties-page-size = కాగితం పరిమాణం: +pdfjs-document-properties-page-size-unit-inches = లో +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = నిలà±à°µà±à°šà°¿à°¤à±à°°à°‚ +pdfjs-document-properties-page-size-orientation-landscape = à°…à°¡à±à°¡à°šà°¿à°¤à±à°°à°‚ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = లేఖ +pdfjs-document-properties-page-size-name-legal = à°šà°Ÿà±à°Ÿà°ªà°°à°®à±†à±–à°¨ + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +pdfjs-document-properties-linearized-yes = à°…à°µà±à°¨à± +pdfjs-document-properties-linearized-no = కాదౠ+pdfjs-document-properties-close-button = మూసివేయి + +## Print + +pdfjs-print-progress-message = à°®à±à°¦à±à°°à°¿à°‚చడానికి పతà±à°°à°®à± సిదà±à°§à°®à°µà±à°¤à±à°¨à±à°¨à°¦à°¿â€¦ +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = à°°à°¦à±à°¦à±à°šà±‡à°¯à°¿ +pdfjs-printing-not-supported = హెచà±à°šà°°à°¿à°•: à°ˆ విహారిణి చేత à°®à±à°¦à±à°°à°£ పూరà±à°¤à°¿à°—à°¾ తోడà±à°ªà°¾à°Ÿà± లేదà±. +pdfjs-printing-not-ready = హెచà±à°šà°°à°¿à°•: à°®à±à°¦à±à°°à°£ కొరకౠఈ PDF పూరà±à°¤à°¿à°—à°¾ లోడవలేదà±. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = పకà±à°•పటà±à°Ÿà±€ మారà±à°šà± +pdfjs-toggle-sidebar-button-label = పకà±à°•పటà±à°Ÿà±€ మారà±à°šà± +pdfjs-document-outline-button = + .title = పతà±à°°à°®à± రూపమౠచూపించౠ(à°¡à°¬à±à°²à± à°•à±à°²à°¿à°•ౠచేసి à°…à°¨à±à°¨à°¿ అంశాలనౠవిసà±à°¤à°°à°¿à°‚à°šà±/కూలà±à°šà±) +pdfjs-document-outline-button-label = పతà±à°°à°®à± à°…à°µà±à°Ÿà±â€Œà°²à±ˆà°¨à± +pdfjs-attachments-button = + .title = à°…à°¨à±à°¬à°‚ధాలౠచూపౠ+pdfjs-attachments-button-label = à°…à°¨à±à°¬à°‚ధాలౠ+pdfjs-layers-button-label = పొరలౠ+pdfjs-thumbs-button = + .title = థంబà±â€Œà°¨à±ˆà°²à±à°¸à± చూపౠ+pdfjs-thumbs-button-label = థంబà±â€Œà°¨à±ˆà°²à±à°¸à± +pdfjs-findbar-button = + .title = పతà±à°°à°®à±à°²à±‹ à°•à°¨à±à°—ొనà±à°®à± +pdfjs-findbar-button-label = à°•à°¨à±à°—ొనౠ+pdfjs-additional-layers = అదనపౠపొరలౠ+ +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = పేజీ { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = { $page } పేజీ నఖచితà±à°°à°‚ + +## Find panel button title and messages + +pdfjs-find-input = + .title = à°•à°¨à±à°—ొనౠ+ .placeholder = పతà±à°°à°®à±à°²à±‹ à°•à°¨à±à°—ొనà±â€¦ +pdfjs-find-previous-button = + .title = పదం యొకà±à°• à°®à±à°‚దౠసంభవానà±à°¨à°¿ à°•à°¨à±à°—ొనౠ+pdfjs-find-previous-button-label = à°®à±à°¨à±à°ªà°Ÿà°¿ +pdfjs-find-next-button = + .title = పదం యొకà±à°• తరà±à°µà°¾à°¤à°¿ సంభవానà±à°¨à°¿ à°•à°¨à±à°—ొనౠ+pdfjs-find-next-button-label = తరà±à°µà°¾à°¤ +pdfjs-find-highlight-checkbox = à°…à°¨à±à°¨à°¿à°Ÿà°¿à°¨à°¿ ఉదà±à°¦à±€à°ªà°¨à°‚ చేయà±à°®à± +pdfjs-find-match-case-checkbox-label = à°…à°•à±à°·à°°à°®à±à°² తేడాతో పోలà±à°šà± +pdfjs-find-entire-word-checkbox-label = పూరà±à°¤à°¿ పదాలౠ+pdfjs-find-reached-top = పేజీ పైకి చేరà±à°•à±à°¨à±à°¨à°¦à°¿, à°•à±à°°à°¿à°‚ది à°¨à±à°‚à°¡à°¿ కొనసాగించండి +pdfjs-find-reached-bottom = పేజీ చివరకౠచేరà±à°•à±à°¨à±à°¨à°¦à°¿, పైనà±à°‚à°¡à°¿ కొనసాగించండి +pdfjs-find-not-found = పదబంధం కనబడలేదౠ+ +## Predefined zoom values + +pdfjs-page-scale-width = పేజీ వెడలà±à°ªà± +pdfjs-page-scale-fit = పేజీ అమరà±à°ªà± +pdfjs-page-scale-auto = à°¸à±à°µà°¯à°‚చాలక జూమౠ+pdfjs-page-scale-actual = యథారà±à°§ పరిమాణం +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = PDF లోడవà±à°šà±à°¨à±à°¨à°ªà±à°ªà±à°¡à± à°’à°• దోషం à°Žà°¦à±à°°à±ˆà°‚ది. +pdfjs-invalid-file-error = చెలà±à°²à°¨à°¿ లేదా పాడైన PDF ఫైలà±. +pdfjs-missing-file-error = దొరకని PDF ఫైలà±. +pdfjs-unexpected-response-error = à°…à°¨à±à°•ోని సరà±à°µà°°à± à°¸à±à°ªà°‚దన. +pdfjs-rendering-error = పేజీనౠరెండరౠచేయà±à°Ÿà°²à±‹ à°’à°• దోషం à°Žà°¦à±à°°à±ˆà°‚ది. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } టీకా] + +## Password + +pdfjs-password-label = à°ˆ PDF ఫైలౠతెరà±à°šà±à°Ÿà°•ౠసంకేతపదం à°ªà±à°°à°µà±‡à°¶à°ªà±†à°Ÿà±à°Ÿà±à°®à±. +pdfjs-password-invalid = సంకేతపదం చెలà±à°²à°¦à±. దయచేసి మళà±à°³à±€ à°ªà±à°°à°¯à°¤à±à°¨à°¿à°‚à°šà°‚à°¡à°¿. +pdfjs-password-ok-button = సరే +pdfjs-password-cancel-button = à°°à°¦à±à°¦à±à°šà±‡à°¯à°¿ +pdfjs-web-fonts-disabled = వెబౠఫాంటà±à°²à± అచేతనించబడెనà±: ఎంబెడెడౠPDF ఫాంటà±à°²à± ఉపయోగించలేక పోయింది. + +## Editing + +# Editor Parameters +pdfjs-editor-free-text-color-input = à°°à°‚à°—à± +pdfjs-editor-free-text-size-input = పరిమాణం +pdfjs-editor-ink-color-input = à°°à°‚à°—à± +pdfjs-editor-ink-thickness-input = మందం +pdfjs-editor-ink-opacity-input = à°…à°•à°¿à°°à°£à±à°¯à°¤ + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/tg/viewer.ftl b/public/assets/pdfjs/locale/tg/viewer.ftl new file mode 100755 index 0000000..b39fee5 --- /dev/null +++ b/public/assets/pdfjs/locale/tg/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Саҳифаи қаблӣ +pdfjs-previous-button-label = Қаблӣ +pdfjs-next-button = + .title = Саҳифаи навбатӣ +pdfjs-next-button-label = Ðавбатӣ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Саҳифа +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = аз { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } аз { $pagesCount }) +pdfjs-zoom-out-button = + .title = Хурд кардан +pdfjs-zoom-out-button-label = Хурд кардан +pdfjs-zoom-in-button = + .title = Калон кардан +pdfjs-zoom-in-button-label = Калон кардан +pdfjs-zoom-select = + .title = Танзими андоза +pdfjs-presentation-mode-button = + .title = Гузариш ба реҷаи тақдим +pdfjs-presentation-mode-button-label = Реҷаи тақдим +pdfjs-open-file-button = + .title = Кушодани файл +pdfjs-open-file-button-label = Кушодан +pdfjs-print-button = + .title = Чоп кардан +pdfjs-print-button-label = Чоп кардан +pdfjs-save-button = + .title = Ðигоҳ доштан +pdfjs-save-button-label = Ðигоҳ доштан +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Боргирӣ кардан +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Боргирӣ кардан +pdfjs-bookmark-button = + .title = Саҳифаи ҷорӣ (Дидани нишонии URL аз Ñаҳифаи ҷорӣ) +pdfjs-bookmark-button-label = Саҳифаи ҷорӣ + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Ðбзорҳо +pdfjs-tools-button-label = Ðбзорҳо +pdfjs-first-page-button = + .title = Ба Ñаҳифаи аввал гузаред +pdfjs-first-page-button-label = Ба Ñаҳифаи аввал гузаред +pdfjs-last-page-button = + .title = Ба Ñаҳифаи охирин гузаред +pdfjs-last-page-button-label = Ба Ñаҳифаи охирин гузаред +pdfjs-page-rotate-cw-button = + .title = Ба Ñамти ҳаракати ақрабаки Ñоат давр задан +pdfjs-page-rotate-cw-button-label = Ба Ñамти ҳаракати ақрабаки Ñоат давр задан +pdfjs-page-rotate-ccw-button = + .title = Ба муқобили Ñамти ҳаракати ақрабаки Ñоат давр задан +pdfjs-page-rotate-ccw-button-label = Ба муқобили Ñамти ҳаракати ақрабаки Ñоат давр задан +pdfjs-cursor-text-select-tool-button = + .title = Фаъол кардани «Ðбзори интихоби матн» +pdfjs-cursor-text-select-tool-button-label = Ðбзори интихоби матн +pdfjs-cursor-hand-tool-button = + .title = Фаъол кардани «Ðбзори даÑт» +pdfjs-cursor-hand-tool-button-label = Ðбзори даÑÑ‚ +pdfjs-scroll-page-button = + .title = ИÑтифодаи варақзанӣ +pdfjs-scroll-page-button-label = Варақзанӣ +pdfjs-scroll-vertical-button = + .title = ИÑтифодаи варақзании амудӣ +pdfjs-scroll-vertical-button-label = Варақзании амудӣ +pdfjs-scroll-horizontal-button = + .title = ИÑтифодаи варақзании уфуқӣ +pdfjs-scroll-horizontal-button-label = Варақзании уфуқӣ +pdfjs-scroll-wrapped-button = + .title = ИÑтифодаи варақзании миқёÑбандӣ +pdfjs-scroll-wrapped-button-label = Варақзании миқёÑбандӣ +pdfjs-spread-none-button = + .title = ГуÑтариши Ñаҳифаҳо иÑтифода бурда нашавад +pdfjs-spread-none-button-label = Бе гуÑтурдани Ñаҳифаҳо +pdfjs-spread-odd-button = + .title = ГуÑтариши Ñаҳифаҳо аз Ñаҳифаҳо бо рақамҳои тоқ оғоз карда мешавад +pdfjs-spread-odd-button-label = Саҳифаҳои тоқ аз тарафи чап +pdfjs-spread-even-button = + .title = ГуÑтариши Ñаҳифаҳо аз Ñаҳифаҳо бо рақамҳои ҷуфт оғоз карда мешавад +pdfjs-spread-even-button-label = Саҳифаҳои ҷуфт аз тарафи чап + +## Document properties dialog + +pdfjs-document-properties-button = + .title = ХуÑуÑиÑтҳои ҳуҷҷат… +pdfjs-document-properties-button-label = ХуÑуÑиÑтҳои ҳуҷҷат… +pdfjs-document-properties-file-name = Ðоми файл: +pdfjs-document-properties-file-size = Ðндозаи файл: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } КБ ({ $b } байт) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } МБ ({ $b } байт) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } КБ ({ $size_b } байт) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } МБ ({ $size_b } байт) +pdfjs-document-properties-title = Сарлавҳа: +pdfjs-document-properties-author = Муаллиф: +pdfjs-document-properties-subject = Мавзуъ: +pdfjs-document-properties-keywords = Калимаҳои калидӣ: +pdfjs-document-properties-creation-date = Санаи Ñҷод: +pdfjs-document-properties-modification-date = Санаи тағйирот: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Эҷодкунанда: +pdfjs-document-properties-producer = ТаҳиÑкунандаи «PDF»: +pdfjs-document-properties-version = ВерÑиÑи «PDF»: +pdfjs-document-properties-page-count = Шумораи Ñаҳифаҳо: +pdfjs-document-properties-page-size = Ðндозаи Ñаҳифа: +pdfjs-document-properties-page-size-unit-inches = дюйм +pdfjs-document-properties-page-size-unit-millimeters = мм +pdfjs-document-properties-page-size-orientation-portrait = амудӣ +pdfjs-document-properties-page-size-orientation-landscape = уфуқӣ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Мактуб +pdfjs-document-properties-page-size-name-legal = Ҳуқуқӣ + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Ðамоиши тез дар Интернет: +pdfjs-document-properties-linearized-yes = Ҳа +pdfjs-document-properties-linearized-no = Ðе +pdfjs-document-properties-close-button = Пӯшидан + +## Print + +pdfjs-print-progress-message = ОмодаÑозии ҳуҷҷат барои чоп… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Бекор кардан +pdfjs-printing-not-supported = Диққат: Чопкунӣ аз тарафи ин браузер ба таври пурра даÑтгирӣ намешавад. +pdfjs-printing-not-ready = Диққат: Файли «PDF» барои чопкунӣ пурра бор карда нашуд. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Фаъол кардани навори ҷонибӣ +pdfjs-toggle-sidebar-notification-button = + .title = Фаъол кардани навори ҷонибӣ (ҳуҷҷат дорои Ñохтор/замимаҳо/қабатҳо мебошад) +pdfjs-toggle-sidebar-button-label = Фаъол кардани навори ҷонибӣ +pdfjs-document-outline-button = + .title = Ðамоиш додани Ñохтори ҳуҷҷат (барои баркушодан/пеҷондани ҳамаи унÑурҳо дубора зер кунед) +pdfjs-document-outline-button-label = Сохтори ҳуҷҷат +pdfjs-attachments-button = + .title = Ðамоиш додани замимаҳо +pdfjs-attachments-button-label = Замимаҳо +pdfjs-layers-button = + .title = Ðамоиш додани қабатҳо (барои барқарор кардани ҳамаи қабатҳо ба вазъиÑти пешфарз дубора зер кунед) +pdfjs-layers-button-label = Қабатҳо +pdfjs-thumbs-button = + .title = Ðамоиш додани таÑвирчаҳо +pdfjs-thumbs-button-label = ТаÑвирчаҳо +pdfjs-current-outline-item-button = + .title = Ðфтани унÑури Ñохтори ҷорӣ +pdfjs-current-outline-item-button-label = УнÑури Ñохтори ҷорӣ +pdfjs-findbar-button = + .title = Ðфтан дар ҳуҷҷат +pdfjs-findbar-button-label = Ðфтан +pdfjs-additional-layers = Қабатҳои иловагӣ + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Саҳифаи { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = ТаÑвирчаи Ñаҳифаи { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Ðфтан + .placeholder = Ðфтан дар ҳуҷҷат… +pdfjs-find-previous-button = + .title = ҶуÑтуҷӯи мавриди қаблии ибораи пешниҳодшуда +pdfjs-find-previous-button-label = Қаблӣ +pdfjs-find-next-button = + .title = ҶуÑтуҷӯи мавриди навбатии ибораи пешниҳодшуда +pdfjs-find-next-button-label = Ðавбатӣ +pdfjs-find-highlight-checkbox = Ҳамаашро бо ранг ҷудо кардан +pdfjs-find-match-case-checkbox-label = Бо дарназардошти ҳарфҳои хурду калон +pdfjs-find-match-diacritics-checkbox-label = Бо дарназардошти аломатҳои диакритикӣ +pdfjs-find-entire-word-checkbox-label = Калимаҳои пурра +pdfjs-find-reached-top = Ба болои ҳуҷҷат раÑид, аз поён идома ёфт +pdfjs-find-reached-bottom = Ба поёни ҳуҷҷат раÑид, аз боло идома ёфт +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } аз { $total } мувофиқат + *[other] { $current } аз { $total } мувофиқат + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Зиёда аз { $limit } мувофиқат + *[other] Зиёда аз { $limit } мувофиқат + } +pdfjs-find-not-found = Ибора ёфт нашуд + +## Predefined zoom values + +pdfjs-page-scale-width = Ðз рӯи паҳнои Ñаҳифа +pdfjs-page-scale-fit = Ðз рӯи андозаи Ñаҳифа +pdfjs-page-scale-auto = Ðндозаи худкор +pdfjs-page-scale-actual = Ðндозаи воқеӣ +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Саҳифаи { $page } + +## Loading indicator messages + +pdfjs-loading-error = Ҳангоми боркунии «PDF» хато ба миён омад. +pdfjs-invalid-file-error = Файли «PDF» нодуруÑÑ‚ Ñ‘ вайроншуда мебошад. +pdfjs-missing-file-error = Файли «PDF» ғоиб аÑÑ‚. +pdfjs-unexpected-response-error = Ҷавоби ногаҳон аз Ñервер. +pdfjs-rendering-error = Ҳангоми шаклÑозии Ñаҳифа хато ба миён омад. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [ҲошиÑнавиÑÓ£ - { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Барои кушодани ин файли «PDF» ниҳонвожаро ворид кунед. +pdfjs-password-invalid = Ðиҳонвожаи нодуруÑÑ‚. Лутфан, аз нав кӯшиш кунед. +pdfjs-password-ok-button = ХУБ +pdfjs-password-cancel-button = Бекор кардан +pdfjs-web-fonts-disabled = Шрифтҳои интернетӣ ғайрифаъоланд: иÑтифодаи шрифтҳои дарунÑохти «PDF» ғайриимкон аÑÑ‚. + +## Editing + +pdfjs-editor-free-text-button = + .title = Матн +pdfjs-editor-free-text-button-label = Матн +pdfjs-editor-ink-button = + .title = РаÑмкашӣ +pdfjs-editor-ink-button-label = РаÑмкашӣ +pdfjs-editor-stamp-button = + .title = Илова Ñ‘ таҳрир кардани таÑвирҳо +pdfjs-editor-stamp-button-label = Илова Ñ‘ таҳрир кардани таÑвирҳо +pdfjs-editor-highlight-button = + .title = Ҷудокунӣ +pdfjs-editor-highlight-button-label = Ҷудокунӣ +pdfjs-highlight-floating-button1 = + .title = Ҷудокунӣ + .aria-label = Ҷудокунӣ +pdfjs-highlight-floating-button-label = Ҷудокунӣ + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Тоза кардани нақша +pdfjs-editor-remove-freetext-button = + .title = Тоза кардани матн +pdfjs-editor-remove-stamp-button = + .title = Тоза кардани таÑвир +pdfjs-editor-remove-highlight-button = + .title = Тоза кардани ҷудокунӣ + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Ранг +pdfjs-editor-free-text-size-input = Ðндоза +pdfjs-editor-ink-color-input = Ранг +pdfjs-editor-ink-thickness-input = ҒафÑÓ£ +pdfjs-editor-ink-opacity-input = Шаффофӣ +pdfjs-editor-stamp-add-image-button = + .title = Илова кардани таÑвир +pdfjs-editor-stamp-add-image-button-label = Илова кардани таÑвир +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = ҒафÑÓ£ +pdfjs-editor-free-highlight-thickness-title = + .title = Иваз кардани ғафÑÓ£ ҳангоми ҷудокунии унÑурҳо ба ғайр аз матн +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Муҳаррири матн + .default-content = Матнро ворид кунед… +pdfjs-free-text = + .aria-label = Муҳаррири матн +pdfjs-free-text-default-content = ÐавиÑед… +pdfjs-ink = + .aria-label = Муҳаррири раÑмкашӣ +pdfjs-ink-canvas = + .aria-label = ТаÑвири Ñҷодкардаи корбар + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Матни иловагӣ +pdfjs-editor-alt-text-edit-button = + .aria-label = Таҳрир кардани матни ивазкунанда +pdfjs-editor-alt-text-edit-button-label = Таҳрир кардани матни иловагӣ +pdfjs-editor-alt-text-dialog-label = Имконеро интихоб намоед +pdfjs-editor-alt-text-dialog-description = Вақте ки одамон таÑвирро дида наметавонанд Ñ‘ вақте ки таÑвир бор карда намешавад, матни иловагӣ (Alt text) кумак мераÑонад. +pdfjs-editor-alt-text-add-description-label = Илова кардани тавÑиф +pdfjs-editor-alt-text-add-description-description = Кӯшиш кунед, ки 1-2 ҷумлаеро навиÑед, ки ба мавзӯъ, танзим Ñ‘ амалҳо тавзеҳ медиҳад. +pdfjs-editor-alt-text-mark-decorative-label = Гузоштан ҳамчун матни ороишӣ +pdfjs-editor-alt-text-mark-decorative-description = Ин барои таÑвирҳои ороишӣ, ба монанди марзҳо Ñ‘ аломатҳои обӣ, иÑтифода мешавад. +pdfjs-editor-alt-text-cancel-button = Бекор кардан +pdfjs-editor-alt-text-save-button = Ðигоҳ доштан +pdfjs-editor-alt-text-decorative-tooltip = Ҳамчун матни ороишӣ гузошта шуд +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Барои миÑол, «Ман забони тоҷикиро дӯÑÑ‚ медорам» +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Матни ивазкунанда + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Кунҷи чапи боло — тағйир додани андоза +pdfjs-editor-resizer-label-top-middle = Канори миёнаи боло — тағйир додани андоза +pdfjs-editor-resizer-label-top-right = Кунҷи роÑти боло — тағйир додани андоза +pdfjs-editor-resizer-label-middle-right = Канори миёнаи роÑÑ‚ — тағйир додани андоза +pdfjs-editor-resizer-label-bottom-right = Кунҷи роÑти поён — тағйир додани андоза +pdfjs-editor-resizer-label-bottom-middle = Канори миёнаи поён — тағйир додани андоза +pdfjs-editor-resizer-label-bottom-left = Кунҷи чапи поён — тағйир додани андоза +pdfjs-editor-resizer-label-middle-left = Канори миёнаи чап — тағйир додани андоза +pdfjs-editor-resizer-top-left = + .aria-label = Кунҷи чапи боло — тағйир додани андоза +pdfjs-editor-resizer-top-middle = + .aria-label = Канори миёнаи боло — тағйир додани андоза +pdfjs-editor-resizer-top-right = + .aria-label = Кунҷи роÑти боло — тағйир додани андоза +pdfjs-editor-resizer-middle-right = + .aria-label = Канори миёнаи роÑÑ‚ — тағйир додани андоза +pdfjs-editor-resizer-bottom-right = + .aria-label = Кунҷи роÑти поён — тағйир додани андоза +pdfjs-editor-resizer-bottom-middle = + .aria-label = Канори миёнаи поён — тағйир додани андоза +pdfjs-editor-resizer-bottom-left = + .aria-label = Кунҷи чапи поён — тағйир додани андоза +pdfjs-editor-resizer-middle-left = + .aria-label = Канори миёнаи чап — тағйир додани андоза + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Ранги ҷудокунӣ +pdfjs-editor-colorpicker-button = + .title = Иваз кардани ранг +pdfjs-editor-colorpicker-dropdown = + .aria-label = Интихоби ранг +pdfjs-editor-colorpicker-yellow = + .title = Зард +pdfjs-editor-colorpicker-green = + .title = Сабз +pdfjs-editor-colorpicker-blue = + .title = Кабуд +pdfjs-editor-colorpicker-pink = + .title = Гулобӣ +pdfjs-editor-colorpicker-red = + .title = Сурх + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Ҳамаро намоиш додан +pdfjs-editor-highlight-show-all-button = + .title = Ҳамаро намоиш додан + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Таҳрир кардани матни иловагӣ (тафÑири таÑвир) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Илова кардани матни иловагӣ (тафÑири таÑвир) +pdfjs-editor-new-alt-text-textarea = + .placeholder = ТафÑири худро дар ин ҷо навиÑед… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = ТавÑифи мухтаÑар барои одамоне, ки акÑҳоро дида наметавонанд Ñ‘ вақте ки акÑҳо кушода намешаванд. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Ин матни ивазкунанда ба таври худкор Ñохта шудааÑÑ‚ ва шоÑд нодуруÑÑ‚ бошад. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Маълумоти бештар +pdfjs-editor-new-alt-text-create-automatically-button-label = Ба таври худкор Ñҷод кардани матни иловагӣ +pdfjs-editor-new-alt-text-not-now-button = Ҳоло не +pdfjs-editor-new-alt-text-error-title = Матни иловагӣ ба таври худкор Ñҷод карда нашуд +pdfjs-editor-new-alt-text-error-description = Лутфан, матни иловагии худро ворид кунед Ñ‘ баъдтар аз нав кӯшиш кунед. +pdfjs-editor-new-alt-text-error-close-button = Пӯшидан +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Боргирии модели зеҳни Ñунъӣ (AI) барои матни ивазкунанда ({ $downloadedSize } аз { $totalSize } МБ) + .aria-valuetext = Боргирии модели зеҳни Ñунъӣ (AI) барои матни ивазкунанда ({ $downloadedSize } аз { $totalSize } МБ) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Матни иловагӣ илова карда шуд +pdfjs-editor-new-alt-text-added-button-label = Матни иловагӣ илова карда шуд +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Матни иловагӣ вуҷуд надорад +pdfjs-editor-new-alt-text-missing-button-label = Матни иловагӣ вуҷуд надорад +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Бознигарӣ кардани матни иловагӣ +pdfjs-editor-new-alt-text-to-review-button-label = Бознигарӣ кардани матни иловагӣ +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Ба таври худкор Ñохта шудааÑÑ‚: «{ $generatedAltText }» + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Танзимоти матни иловагии таÑвир +pdfjs-image-alt-text-settings-button-label = Танзимоти матни иловагии таÑвир +pdfjs-editor-alt-text-settings-dialog-label = Танзимоти матни иловагии таÑвир +pdfjs-editor-alt-text-settings-automatic-title = Матни иловагии худкор +pdfjs-editor-alt-text-settings-create-model-button-label = Ба таври худкор Ñҷод кардани матни иловагӣ +pdfjs-editor-alt-text-settings-create-model-description = Ин имкон барои раÑонидани кумак ба одамоне, ки акÑҳоро дида наметавонанд Ñ‘ вақте ки акÑҳо кушода намешаванд, тавÑифи акÑҳоро пешниҳод мекунад. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Модели зеҳни Ñунъӣ «AI» барои матни ивазкунанда ({ $totalSize } МБ) +pdfjs-editor-alt-text-settings-ai-model-description = Дар даÑтгоҳи шумо ба таври маҳаллӣ кор мекунад, бинобар ин махфиÑти маълумоти шахÑии шумо нигоҳ дошта мешавад. Барои матни ивазкунандаи худкор лозим аÑÑ‚. +pdfjs-editor-alt-text-settings-delete-model-button = ÐеÑÑ‚ кардан +pdfjs-editor-alt-text-settings-download-model-button = Боргирӣ кардан +pdfjs-editor-alt-text-settings-downloading-model-button = Дар ҳоли боргирӣ… +pdfjs-editor-alt-text-settings-editor-title = Муҳаррири матни иловагӣ +pdfjs-editor-alt-text-settings-show-dialog-button-label = Дарҳол нишон додани муҳаррири матни ивазкунанда ҳангоми иловакунии таÑвир +pdfjs-editor-alt-text-settings-show-dialog-description = Ба шумо кумак мекунад, ки боварӣ ҳоÑил кунед, ки ҳамаи таÑвирҳои шумо дорои матни ивазкунанда мебошанд. +pdfjs-editor-alt-text-settings-close-button = Пӯшидан + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = ҶудоÑозӣ тоза карда шуд +pdfjs-editor-undo-bar-message-freetext = Матн тоза карда шуд +pdfjs-editor-undo-bar-message-ink = РаÑм тоза карда шуд +pdfjs-editor-undo-bar-message-stamp = ТаÑвир тоза карда шуд +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } ҳошиÑнавиÑÓ£ тоза карда шуд + *[other] { $count } ҳошиÑнавиÑÓ£ тоза карда шуданд + } +pdfjs-editor-undo-bar-undo-button = + .title = Бекор кардан +pdfjs-editor-undo-bar-undo-button-label = Бекор кардан +pdfjs-editor-undo-bar-close-button = + .title = Пӯшидан +pdfjs-editor-undo-bar-close-button-label = Пӯшидан diff --git a/public/assets/pdfjs/locale/th/viewer.ftl b/public/assets/pdfjs/locale/th/viewer.ftl new file mode 100755 index 0000000..cba15f9 --- /dev/null +++ b/public/assets/pdfjs/locale/th/viewer.ftl @@ -0,0 +1,503 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = หน้าà¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸² +pdfjs-previous-button-label = à¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸² +pdfjs-next-button = + .title = หน้าถัดไป +pdfjs-next-button-label = ถัดไป +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = หน้า +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = จาภ{ $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } จาภ{ $pagesCount }) +pdfjs-zoom-out-button = + .title = ซูมออภ+pdfjs-zoom-out-button-label = ซูมออภ+pdfjs-zoom-in-button = + .title = ซูมเข้า +pdfjs-zoom-in-button-label = ซูมเข้า +pdfjs-zoom-select = + .title = ซูม +pdfjs-presentation-mode-button = + .title = สลับเป็นโหมดà¸à¸²à¸£à¸™à¸³à¹€à¸ªà¸™à¸­ +pdfjs-presentation-mode-button-label = โหมดà¸à¸²à¸£à¸™à¸³à¹€à¸ªà¸™à¸­ +pdfjs-open-file-button = + .title = เปิดไฟล์ +pdfjs-open-file-button-label = เปิด +pdfjs-print-button = + .title = พิมพ์ +pdfjs-print-button-label = พิมพ์ +pdfjs-save-button = + .title = บันทึภ+pdfjs-save-button-label = บันทึภ+# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = ดาวน์โหลด +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = ดาวน์โหลด +pdfjs-bookmark-button = + .title = หน้าปัจจุบัน (ดู URL จาà¸à¸«à¸™à¹‰à¸²à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™) +pdfjs-bookmark-button-label = หน้าปัจจุบัน + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = เครื่องมือ +pdfjs-tools-button-label = เครื่องมือ +pdfjs-first-page-button = + .title = ไปยังหน้าà¹à¸£à¸ +pdfjs-first-page-button-label = ไปยังหน้าà¹à¸£à¸ +pdfjs-last-page-button = + .title = ไปยังหน้าสุดท้าย +pdfjs-last-page-button-label = ไปยังหน้าสุดท้าย +pdfjs-page-rotate-cw-button = + .title = หมุนตามเข็มนาฬิà¸à¸² +pdfjs-page-rotate-cw-button-label = หมุนตามเข็มนาฬิà¸à¸² +pdfjs-page-rotate-ccw-button = + .title = หมุนทวนเข็มนาฬิà¸à¸² +pdfjs-page-rotate-ccw-button-label = หมุนทวนเข็มนาฬิà¸à¸² +pdfjs-cursor-text-select-tool-button = + .title = เปิดใช้งานเครื่องมือà¸à¸²à¸£à¹€à¸¥à¸·à¸­à¸à¸‚้อความ +pdfjs-cursor-text-select-tool-button-label = เครื่องมือà¸à¸²à¸£à¹€à¸¥à¸·à¸­à¸à¸‚้อความ +pdfjs-cursor-hand-tool-button = + .title = เปิดใช้งานเครื่องมือมือ +pdfjs-cursor-hand-tool-button-label = เครื่องมือมือ +pdfjs-scroll-page-button = + .title = ใช้à¸à¸²à¸£à¹€à¸¥à¸·à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸² +pdfjs-scroll-page-button-label = à¸à¸²à¸£à¹€à¸¥à¸·à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸² +pdfjs-scroll-vertical-button = + .title = ใช้à¸à¸²à¸£à¹€à¸¥à¸·à¹ˆà¸­à¸™à¹à¸™à¸§à¸•ั้ง +pdfjs-scroll-vertical-button-label = à¸à¸²à¸£à¹€à¸¥à¸·à¹ˆà¸­à¸™à¹à¸™à¸§à¸•ั้ง +pdfjs-scroll-horizontal-button = + .title = ใช้à¸à¸²à¸£à¹€à¸¥à¸·à¹ˆà¸­à¸™à¹à¸™à¸§à¸™à¸­à¸™ +pdfjs-scroll-horizontal-button-label = à¸à¸²à¸£à¹€à¸¥à¸·à¹ˆà¸­à¸™à¹à¸™à¸§à¸™à¸­à¸™ +pdfjs-scroll-wrapped-button = + .title = ใช้à¸à¸²à¸£à¹€à¸¥à¸·à¹ˆà¸­à¸™à¹à¸šà¸šà¸„ลุม +pdfjs-scroll-wrapped-button-label = เลื่อนà¹à¸šà¸šà¸„ลุม +pdfjs-spread-none-button = + .title = ไม่ต้องรวมà¸à¸²à¸£à¸à¸£à¸°à¸ˆà¸²à¸¢à¸«à¸™à¹‰à¸² +pdfjs-spread-none-button-label = ไม่à¸à¸£à¸°à¸ˆà¸²à¸¢ +pdfjs-spread-odd-button = + .title = รวมà¸à¸²à¸£à¸à¸£à¸°à¸ˆà¸²à¸¢à¸«à¸™à¹‰à¸²à¹€à¸£à¸´à¹ˆà¸¡à¸ˆà¸²à¸à¸«à¸™à¹‰à¸²à¸„ี่ +pdfjs-spread-odd-button-label = à¸à¸£à¸°à¸ˆà¸²à¸¢à¸­à¸¢à¹ˆà¸²à¸‡à¹€à¸«à¸¥à¸·à¸­à¹€à¸¨à¸© +pdfjs-spread-even-button = + .title = รวมà¸à¸²à¸£à¸à¸£à¸°à¸ˆà¸²à¸¢à¸«à¸™à¹‰à¸²à¹€à¸£à¸´à¹ˆà¸¡à¸ˆà¸²à¸à¸«à¸™à¹‰à¸²à¸„ู่ +pdfjs-spread-even-button-label = à¸à¸£à¸°à¸ˆà¸²à¸¢à¸­à¸¢à¹ˆà¸²à¸‡à¹€à¸—่าเทียม + +## Document properties dialog + +pdfjs-document-properties-button = + .title = คุณสมบัติเอà¸à¸ªà¸²à¸£â€¦ +pdfjs-document-properties-button-label = คุณสมบัติเอà¸à¸ªà¸²à¸£â€¦ +pdfjs-document-properties-file-name = ชื่อไฟล์: +pdfjs-document-properties-file-size = ขนาดไฟล์: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } ไบต์) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } ไบต์) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } ไบต์) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } ไบต์) +pdfjs-document-properties-title = ชื่อเรื่อง: +pdfjs-document-properties-author = ผู้สร้าง: +pdfjs-document-properties-subject = ชื่อเรื่อง: +pdfjs-document-properties-keywords = คำสำคัà¸: +pdfjs-document-properties-creation-date = วันที่สร้าง: +pdfjs-document-properties-modification-date = วันที่à¹à¸à¹‰à¹„ข: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = ผู้สร้าง: +pdfjs-document-properties-producer = ผู้ผลิต PDF: +pdfjs-document-properties-version = รุ่น PDF: +pdfjs-document-properties-page-count = จำนวนหน้า: +pdfjs-document-properties-page-size = ขนาดหน้า: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = à¹à¸™à¸§à¸•ั้ง +pdfjs-document-properties-page-size-orientation-landscape = à¹à¸™à¸§à¸™à¸­à¸™ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = จดหมาย +pdfjs-document-properties-page-size-name-legal = ข้อà¸à¸Žà¸«à¸¡à¸²à¸¢ + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = มุมมองเว็บà¹à¸šà¸šà¸£à¸§à¸”เร็ว: +pdfjs-document-properties-linearized-yes = ใช่ +pdfjs-document-properties-linearized-no = ไม่ +pdfjs-document-properties-close-button = ปิด + +## Print + +pdfjs-print-progress-message = à¸à¸³à¸¥à¸±à¸‡à¹€à¸•รียมเอà¸à¸ªà¸²à¸£à¸ªà¸³à¸«à¸£à¸±à¸šà¸à¸²à¸£à¸žà¸´à¸¡à¸žà¹Œâ€¦ +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = ยà¸à¹€à¸¥à¸´à¸ +pdfjs-printing-not-supported = คำเตือน: เบราว์เซอร์นี้ไม่ได้สนับสนุนà¸à¸²à¸£à¸žà¸´à¸¡à¸žà¹Œà¸­à¸¢à¹ˆà¸²à¸‡à¹€à¸•็มที่ +pdfjs-printing-not-ready = คำเตือน: PDF ไม่ได้รับà¸à¸²à¸£à¹‚หลดอย่างเต็มที่สำหรับà¸à¸²à¸£à¸žà¸´à¸¡à¸žà¹Œ + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = เปิด/ปิดà¹à¸–บข้าง +pdfjs-toggle-sidebar-notification-button = + .title = เปิด/ปิดà¹à¸–บข้าง (เอà¸à¸ªà¸²à¸£à¸¡à¸µà¹€à¸„้าร่าง/ไฟล์à¹à¸™à¸š/เลเยอร์) +pdfjs-toggle-sidebar-button-label = เปิด/ปิดà¹à¸–บข้าง +pdfjs-document-outline-button = + .title = à¹à¸ªà¸”งเค้าร่างเอà¸à¸ªà¸²à¸£ (คลิà¸à¸ªà¸­à¸‡à¸„รั้งเพื่อขยาย/ยุบรายà¸à¸²à¸£à¸—ั้งหมด) +pdfjs-document-outline-button-label = เค้าร่างเอà¸à¸ªà¸²à¸£ +pdfjs-attachments-button = + .title = à¹à¸ªà¸”งไฟล์à¹à¸™à¸š +pdfjs-attachments-button-label = ไฟล์à¹à¸™à¸š +pdfjs-layers-button = + .title = à¹à¸ªà¸”งเลเยอร์ (คลิà¸à¸ªà¸­à¸‡à¸„รั้งเพื่อรีเซ็ตเลเยอร์ทั้งหมดเป็นสถานะเริ่มต้น) +pdfjs-layers-button-label = เลเยอร์ +pdfjs-thumbs-button = + .title = à¹à¸ªà¸”งภาพขนาดย่อ +pdfjs-thumbs-button-label = ภาพขนาดย่อ +pdfjs-current-outline-item-button = + .title = ค้นหารายà¸à¸²à¸£à¹€à¸„้าร่างปัจจุบัน +pdfjs-current-outline-item-button-label = รายà¸à¸²à¸£à¹€à¸„้าร่างปัจจุบัน +pdfjs-findbar-button = + .title = ค้นหาในเอà¸à¸ªà¸²à¸£ +pdfjs-findbar-button-label = ค้นหา +pdfjs-additional-layers = เลเยอร์เพิ่มเติม + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = หน้า { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = ภาพขนาดย่อของหน้า { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = ค้นหา + .placeholder = ค้นหาในเอà¸à¸ªà¸²à¸£â€¦ +pdfjs-find-previous-button = + .title = หาตำà¹à¸«à¸™à¹ˆà¸‡à¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸²à¸‚องวลี +pdfjs-find-previous-button-label = à¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸² +pdfjs-find-next-button = + .title = หาตำà¹à¸«à¸™à¹ˆà¸‡à¸–ัดไปของวลี +pdfjs-find-next-button-label = ถัดไป +pdfjs-find-highlight-checkbox = เน้นสีทั้งหมด +pdfjs-find-match-case-checkbox-label = ตัวพิมพ์ใหà¸à¹ˆà¹€à¸¥à¹‡à¸à¸•รงà¸à¸±à¸™ +pdfjs-find-match-diacritics-checkbox-label = เครื่องหมายà¸à¸³à¸à¸±à¸šà¸à¸²à¸£à¸­à¸­à¸à¹€à¸ªà¸µà¸¢à¸‡à¸•รงà¸à¸±à¸™ +pdfjs-find-entire-word-checkbox-label = ทั้งคำ +pdfjs-find-reached-top = ค้นหาถึงจุดเริ่มต้นของหน้า เริ่มค้นต่อจาà¸à¸”้านล่าง +pdfjs-find-reached-bottom = ค้นหาถึงจุดสิ้นสุดหน้า เริ่มค้นต่อจาà¸à¸”้านบน +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = { $current } จาภ{ $total } รายà¸à¸²à¸£à¸—ี่ตรงà¸à¸±à¸™ +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = มาà¸à¸à¸§à¹ˆà¸² { $limit } รายà¸à¸²à¸£à¸—ี่ตรงà¸à¸±à¸™ +pdfjs-find-not-found = ไม่พบวลี + +## Predefined zoom values + +pdfjs-page-scale-width = ความà¸à¸§à¹‰à¸²à¸‡à¸«à¸™à¹‰à¸² +pdfjs-page-scale-fit = พอดีหน้า +pdfjs-page-scale-auto = ซูมอัตโนมัติ +pdfjs-page-scale-actual = ขนาดจริง +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = หน้า { $page } + +## Loading indicator messages + +pdfjs-loading-error = เà¸à¸´à¸”ข้อผิดพลาดขณะโหลด PDF +pdfjs-invalid-file-error = ไฟล์ PDF ไม่ถูà¸à¸•้องหรือเสียหาย +pdfjs-missing-file-error = ไฟล์ PDF หายไป +pdfjs-unexpected-response-error = à¸à¸²à¸£à¸•อบสนองของเซิร์ฟเวอร์ที่ไม่คาดคิด +pdfjs-rendering-error = เà¸à¸´à¸”ข้อผิดพลาดขณะเรนเดอร์หน้า + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [คำอธิบายประà¸à¸­à¸š { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = ป้อนรหัสผ่านเพื่อเปิดไฟล์ PDF นี้ +pdfjs-password-invalid = รหัสผ่านไม่ถูà¸à¸•้อง โปรดลองอีà¸à¸„รั้ง +pdfjs-password-ok-button = ตà¸à¸¥à¸‡ +pdfjs-password-cancel-button = ยà¸à¹€à¸¥à¸´à¸ +pdfjs-web-fonts-disabled = à¹à¸šà¸šà¸­à¸±à¸à¸©à¸£à¹€à¸§à¹‡à¸šà¸–ูà¸à¸›à¸´à¸”ใช้งาน: ไม่สามารถใช้à¹à¸šà¸šà¸­à¸±à¸à¸©à¸£ PDF à¸à¸±à¸‡à¸•ัว + +## Editing + +pdfjs-editor-free-text-button = + .title = ข้อความ +pdfjs-editor-free-text-button-label = ข้อความ +pdfjs-editor-ink-button = + .title = รูปวาด +pdfjs-editor-ink-button-label = รูปวาด +pdfjs-editor-stamp-button = + .title = เพิ่มหรือà¹à¸à¹‰à¹„ขภาพ +pdfjs-editor-stamp-button-label = เพิ่มหรือà¹à¸à¹‰à¹„ขภาพ +pdfjs-editor-highlight-button = + .title = เน้น +pdfjs-editor-highlight-button-label = เน้น +pdfjs-highlight-floating-button1 = + .title = เน้นสี + .aria-label = เน้นสี +pdfjs-highlight-floating-button-label = เน้นสี + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = เอาภาพวาดออภ+pdfjs-editor-remove-freetext-button = + .title = เอาข้อความออภ+pdfjs-editor-remove-stamp-button = + .title = เอาภาพออภ+pdfjs-editor-remove-highlight-button = + .title = เอาà¸à¸²à¸£à¹€à¸™à¹‰à¸™à¸ªà¸µà¸­à¸­à¸ + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = สี +pdfjs-editor-free-text-size-input = ขนาด +pdfjs-editor-ink-color-input = สี +pdfjs-editor-ink-thickness-input = ความหนา +pdfjs-editor-ink-opacity-input = ความทึบ +pdfjs-editor-stamp-add-image-button = + .title = เพิ่มภาพ +pdfjs-editor-stamp-add-image-button-label = เพิ่มภาพ +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = ความหนา +pdfjs-editor-free-highlight-thickness-title = + .title = เปลี่ยนความหนาเมื่อเน้นรายà¸à¸²à¸£à¸­à¸·à¹ˆà¸™à¹† ที่ไม่ใช่ข้อความ +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = ตัวà¹à¸à¹‰à¹„ขข้อความ + .default-content = เริ่มพิมพ์ได้เลย… +pdfjs-free-text = + .aria-label = ตัวà¹à¸à¹‰à¹„ขข้อความ +pdfjs-free-text-default-content = เริ่มพิมพ์… +pdfjs-ink = + .aria-label = ตัวà¹à¸à¹‰à¹„ขรูปวาด +pdfjs-ink-canvas = + .aria-label = ภาพที่ผู้ใช้สร้างขึ้น + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = ข้อความทดà¹à¸—น +pdfjs-editor-alt-text-edit-button = + .aria-label = à¹à¸à¹‰à¹„ขข้อความทดà¹à¸—น +pdfjs-editor-alt-text-edit-button-label = à¹à¸à¹‰à¹„ขข้อความทดà¹à¸—น +pdfjs-editor-alt-text-dialog-label = เลือà¸à¸•ัวเลือภ+pdfjs-editor-alt-text-dialog-description = ข้อความทดà¹à¸—นสามารถช่วยเหลือได้เมื่อผู้ใช้มองไม่เห็นภาพ หรือภาพไม่โหลด +pdfjs-editor-alt-text-add-description-label = เพิ่มคำอธิบาย +pdfjs-editor-alt-text-add-description-description = à¹à¸™à¸°à¸™à¸³à¹ƒà¸«à¹‰à¹ƒà¸Šà¹‰ 1-2 ประโยคซึ่งอธิบายหัวเรื่อง ฉาภหรือà¸à¸²à¸£à¸à¸£à¸°à¸—ำ +pdfjs-editor-alt-text-mark-decorative-label = ทำเครื่องหมายเป็นสิ่งตà¸à¹à¸•่ง +pdfjs-editor-alt-text-mark-decorative-description = สิ่งนี้ใช้สำหรับภาพที่เป็นสิ่งประดับ เช่น ขอบ หรือลายน้ำ +pdfjs-editor-alt-text-cancel-button = ยà¸à¹€à¸¥à¸´à¸ +pdfjs-editor-alt-text-save-button = บันทึภ+pdfjs-editor-alt-text-decorative-tooltip = ทำเครื่องหมายเป็นสิ่งตà¸à¹à¸•่งà¹à¸¥à¹‰à¸§ +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = ตัวอย่างเช่น “ชายหนุ่มคนหนึ่งนั่งลงที่โต๊ะเพื่อรับประทานอาหารมื้อหนึ่ง†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = ข้อความทดà¹à¸—น + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = มุมซ้ายบน — ปรับขนาด +pdfjs-editor-resizer-label-top-middle = ตรงà¸à¸¥à¸²à¸‡à¸”้านบน — ปรับขนาด +pdfjs-editor-resizer-label-top-right = มุมขวาบน — ปรับขนาด +pdfjs-editor-resizer-label-middle-right = ตรงà¸à¸¥à¸²à¸‡à¸”้านขวา — ปรับขนาด +pdfjs-editor-resizer-label-bottom-right = มุมขวาล่าง — ปรับขนาด +pdfjs-editor-resizer-label-bottom-middle = ตรงà¸à¸¥à¸²à¸‡à¸”้านล่าง — ปรับขนาด +pdfjs-editor-resizer-label-bottom-left = มุมซ้ายล่าง — ปรับขนาด +pdfjs-editor-resizer-label-middle-left = ตรงà¸à¸¥à¸²à¸‡à¸”้านซ้าย — ปรับขนาด +pdfjs-editor-resizer-top-left = + .aria-label = มุมซ้ายบน — ปรับขนาด +pdfjs-editor-resizer-top-middle = + .aria-label = ตรงà¸à¸¥à¸²à¸‡à¸”้านบน — ปรับขนาด +pdfjs-editor-resizer-top-right = + .aria-label = มุมขวาบน — ปรับขนาด +pdfjs-editor-resizer-middle-right = + .aria-label = ตรงà¸à¸¥à¸²à¸‡à¸”้านขวา — ปรับขนาด +pdfjs-editor-resizer-bottom-right = + .aria-label = มุมขวาล่าง — ปรับขนาด +pdfjs-editor-resizer-bottom-middle = + .aria-label = ตรงà¸à¸¥à¸²à¸‡à¸”้านล่าง — ปรับขนาด +pdfjs-editor-resizer-bottom-left = + .aria-label = มุมซ้ายล่าง — ปรับขนาด +pdfjs-editor-resizer-middle-left = + .aria-label = ตรงà¸à¸¥à¸²à¸‡à¸”้านซ้าย — ปรับขนาด + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = สีเน้น +pdfjs-editor-colorpicker-button = + .title = เปลี่ยนสี +pdfjs-editor-colorpicker-dropdown = + .aria-label = ทางเลือà¸à¸ªà¸µ +pdfjs-editor-colorpicker-yellow = + .title = เหลือง +pdfjs-editor-colorpicker-green = + .title = เขียว +pdfjs-editor-colorpicker-blue = + .title = น้ำเงิน +pdfjs-editor-colorpicker-pink = + .title = ชมพู +pdfjs-editor-colorpicker-red = + .title = à¹à¸”ง + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = à¹à¸ªà¸”งทั้งหมด +pdfjs-editor-highlight-show-all-button = + .title = à¹à¸ªà¸”งทั้งหมด + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = à¹à¸à¹‰à¹„ขข้อความทดà¹à¸—น (คำอธิบายภาพ) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = เพิ่มข้อความทดà¹à¸—น (คำอธิบายภาพ) +pdfjs-editor-new-alt-text-textarea = + .placeholder = เขียนคำอธิบายของคุณที่นี่… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = คำอธิบายสั้นๆ สำหรับผู้ที่ไม่สามารถมองเห็นภาพหรือเมื่อภาพไม่โหลด +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = ข้อความทดà¹à¸—นนี้ถูà¸à¸ªà¸£à¹‰à¸²à¸‡à¸‚ึ้นโดยอัตโนมัติà¹à¸¥à¸°à¸­à¸²à¸ˆà¹„ม่ถูà¸à¸•้อง +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = เรียนรู้เพิ่มเติม +pdfjs-editor-new-alt-text-create-automatically-button-label = สร้างข้อความทดà¹à¸—นโดยอัตโนมัติ +pdfjs-editor-new-alt-text-not-now-button = ไม่ใช่ตอนนี้ +pdfjs-editor-new-alt-text-error-title = ไม่สามารถสร้างข้อความทดà¹à¸—นโดยอัตโนมัติได้ +pdfjs-editor-new-alt-text-error-description = à¸à¸£à¸¸à¸“าเขียนข้อความทดà¹à¸—นด้วยตัวเองหรือลองใหม่อีà¸à¸„รั้งในภายหลัง +pdfjs-editor-new-alt-text-error-close-button = ปิด +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = à¸à¸³à¸¥à¸±à¸‡à¸”าวน์โหลดโมเดล AI สำหรับข้อความทดà¹à¸—น ({ $downloadedSize } จาภ{ $totalSize } MB) + .aria-valuetext = à¸à¸³à¸¥à¸±à¸‡à¸”าวน์โหลดโมเดล AI สำหรับข้อความทดà¹à¸—น ({ $downloadedSize } จาภ{ $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = เพิ่มข้อความทดà¹à¸—นà¹à¸¥à¹‰à¸§ +pdfjs-editor-new-alt-text-added-button-label = เพิ่มข้อความทดà¹à¸—นà¹à¸¥à¹‰à¸§ +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = ขาดข้อความทดà¹à¸—น +pdfjs-editor-new-alt-text-missing-button-label = ขาดข้อความทดà¹à¸—น +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = ตรวจสอบข้อความทดà¹à¸—น +pdfjs-editor-new-alt-text-to-review-button-label = ตรวจสอบข้อความทดà¹à¸—น +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = สร้างขึ้นโดยอัตโนมัติ: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = ตั้งค่าข้อความทดà¹à¸—นภาพ +pdfjs-image-alt-text-settings-button-label = ตั้งค่าข้อความทดà¹à¸—นภาพ +pdfjs-editor-alt-text-settings-dialog-label = ตั้งค่าข้อความทดà¹à¸—นภาพ +pdfjs-editor-alt-text-settings-automatic-title = à¸à¸²à¸£à¸—ดà¹à¸—นด้วยข้อความอัตโนมัติ +pdfjs-editor-alt-text-settings-create-model-button-label = สร้างข้อความทดà¹à¸—นอัตโนมัติ +pdfjs-editor-alt-text-settings-create-model-description = à¹à¸™à¸°à¸™à¸³à¸„ำอธิบายเพื่อช่วยเหลือผู้ที่ไม่สามารถมองเห็นภาพหรือเมื่อภาพไม่โหลด +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = โมเดล AI สำหรับข้อความทดà¹à¸—น ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = ทำงานในเครื่องของคุณเพื่อให้ข้อมูลของคุณเป็นส่วนตัว จำเป็นสำหรับข้อความทดà¹à¸—นอัตโนมัติ +pdfjs-editor-alt-text-settings-delete-model-button = ลบ +pdfjs-editor-alt-text-settings-download-model-button = ดาวน์โหลด +pdfjs-editor-alt-text-settings-downloading-model-button = à¸à¸³à¸¥à¸±à¸‡à¸”าวน์โหลด… +pdfjs-editor-alt-text-settings-editor-title = ตัวà¹à¸à¹‰à¹„ขข้อความทดà¹à¸—น +pdfjs-editor-alt-text-settings-show-dialog-button-label = à¹à¸ªà¸”งตัวà¹à¸à¹‰à¹„ขข้อความทดà¹à¸—นทันทีเมื่อเพิ่มภาพ +pdfjs-editor-alt-text-settings-show-dialog-description = ช่วยให้คุณà¹à¸™à¹ˆà¹ƒà¸ˆà¸§à¹ˆà¸²à¸ à¸²à¸žà¸—ั้งหมดของคุณมีข้อความทดà¹à¸—น +pdfjs-editor-alt-text-settings-close-button = ปิด + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = เอาà¸à¸²à¸£à¹€à¸™à¹‰à¸™à¸ªà¸µà¸­à¸­à¸à¹à¸¥à¹‰à¸§ +pdfjs-editor-undo-bar-message-freetext = เอาข้อความออà¸à¹à¸¥à¹‰à¸§ +pdfjs-editor-undo-bar-message-ink = เอาภาพวาดออà¸à¹à¸¥à¹‰à¸§ +pdfjs-editor-undo-bar-message-stamp = เอาภาพออà¸à¹à¸¥à¹‰à¸§ +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = เอาคำอธิบายประà¸à¸­à¸š { $count } รายà¸à¸²à¸£à¸­à¸­à¸à¹à¸¥à¹‰à¸§ +pdfjs-editor-undo-bar-undo-button = + .title = เลิà¸à¸—ำ +pdfjs-editor-undo-bar-undo-button-label = เลิà¸à¸—ำ +pdfjs-editor-undo-bar-close-button = + .title = ปิด +pdfjs-editor-undo-bar-close-button-label = ปิด diff --git a/public/assets/pdfjs/locale/tl/viewer.ftl b/public/assets/pdfjs/locale/tl/viewer.ftl new file mode 100755 index 0000000..faa0009 --- /dev/null +++ b/public/assets/pdfjs/locale/tl/viewer.ftl @@ -0,0 +1,257 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Naunang Pahina +pdfjs-previous-button-label = Nakaraan +pdfjs-next-button = + .title = Sunod na Pahina +pdfjs-next-button-label = Sunod +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Pahina +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = ng { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } ng { $pagesCount }) +pdfjs-zoom-out-button = + .title = Paliitin +pdfjs-zoom-out-button-label = Paliitin +pdfjs-zoom-in-button = + .title = Palakihin +pdfjs-zoom-in-button-label = Palakihin +pdfjs-zoom-select = + .title = Mag-zoom +pdfjs-presentation-mode-button = + .title = Lumipat sa Presentation Mode +pdfjs-presentation-mode-button-label = Presentation Mode +pdfjs-open-file-button = + .title = Magbukas ng file +pdfjs-open-file-button-label = Buksan +pdfjs-print-button = + .title = i-Print +pdfjs-print-button-label = i-Print + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Mga Kagamitan +pdfjs-tools-button-label = Mga Kagamitan +pdfjs-first-page-button = + .title = Pumunta sa Unang Pahina +pdfjs-first-page-button-label = Pumunta sa Unang Pahina +pdfjs-last-page-button = + .title = Pumunta sa Huling Pahina +pdfjs-last-page-button-label = Pumunta sa Huling Pahina +pdfjs-page-rotate-cw-button = + .title = Paikutin Pakanan +pdfjs-page-rotate-cw-button-label = Paikutin Pakanan +pdfjs-page-rotate-ccw-button = + .title = Paikutin Pakaliwa +pdfjs-page-rotate-ccw-button-label = Paikutin Pakaliwa +pdfjs-cursor-text-select-tool-button = + .title = I-enable ang Text Selection Tool +pdfjs-cursor-text-select-tool-button-label = Text Selection Tool +pdfjs-cursor-hand-tool-button = + .title = I-enable ang Hand Tool +pdfjs-cursor-hand-tool-button-label = Hand Tool +pdfjs-scroll-vertical-button = + .title = Gumamit ng Vertical Scrolling +pdfjs-scroll-vertical-button-label = Vertical Scrolling +pdfjs-scroll-horizontal-button = + .title = Gumamit ng Horizontal Scrolling +pdfjs-scroll-horizontal-button-label = Horizontal Scrolling +pdfjs-scroll-wrapped-button = + .title = Gumamit ng Wrapped Scrolling +pdfjs-scroll-wrapped-button-label = Wrapped Scrolling +pdfjs-spread-none-button = + .title = Huwag pagsamahin ang mga page spread +pdfjs-spread-none-button-label = No Spreads +pdfjs-spread-odd-button = + .title = Join page spreads starting with odd-numbered pages +pdfjs-spread-odd-button-label = Mga Odd Spread +pdfjs-spread-even-button = + .title = Pagsamahin ang mga page spread na nagsisimula sa mga even-numbered na pahina +pdfjs-spread-even-button-label = Mga Even Spread + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Mga Katangian ng Dokumento… +pdfjs-document-properties-button-label = Mga Katangian ng Dokumento… +pdfjs-document-properties-file-name = File name: +pdfjs-document-properties-file-size = File size: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Pamagat: +pdfjs-document-properties-author = May-akda: +pdfjs-document-properties-subject = Paksa: +pdfjs-document-properties-keywords = Mga keyword: +pdfjs-document-properties-creation-date = Petsa ng Pagkakagawa: +pdfjs-document-properties-modification-date = Petsa ng Pagkakabago: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Tagalikha: +pdfjs-document-properties-producer = PDF Producer: +pdfjs-document-properties-version = PDF Version: +pdfjs-document-properties-page-count = Bilang ng Pahina: +pdfjs-document-properties-page-size = Laki ng Pahina: +pdfjs-document-properties-page-size-unit-inches = pulgada +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = patayo +pdfjs-document-properties-page-size-orientation-landscape = pahiga +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Fast Web View: +pdfjs-document-properties-linearized-yes = Oo +pdfjs-document-properties-linearized-no = Hindi +pdfjs-document-properties-close-button = Isara + +## Print + +pdfjs-print-progress-message = Inihahanda ang dokumento para sa pag-print… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Kanselahin +pdfjs-printing-not-supported = Babala: Hindi pa ganap na suportado ang pag-print sa browser na ito. +pdfjs-printing-not-ready = Babala: Hindi ganap na nabuksan ang PDF para sa pag-print. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Ipakita/Itago ang Sidebar +pdfjs-toggle-sidebar-notification-button = + .title = Ipakita/Itago ang Sidebar (nagtataglay ang dokumento ng balangkas/mga attachment/mga layer) +pdfjs-toggle-sidebar-button-label = Ipakita/Itago ang Sidebar +pdfjs-document-outline-button = + .title = Ipakita ang Document Outline (mag-double-click para i-expand/collapse ang laman) +pdfjs-document-outline-button-label = Balangkas ng Dokumento +pdfjs-attachments-button = + .title = Ipakita ang mga Attachment +pdfjs-attachments-button-label = Mga attachment +pdfjs-layers-button = + .title = Ipakita ang mga Layer (mag-double click para mareset ang lahat ng layer sa orihinal na estado) +pdfjs-layers-button-label = Mga layer +pdfjs-thumbs-button = + .title = Ipakita ang mga Thumbnail +pdfjs-thumbs-button-label = Mga thumbnail +pdfjs-findbar-button = + .title = Hanapin sa Dokumento +pdfjs-findbar-button-label = Hanapin +pdfjs-additional-layers = Mga Karagdagang Layer + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Pahina { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Thumbnail ng Pahina { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Hanapin + .placeholder = Hanapin sa dokumento… +pdfjs-find-previous-button = + .title = Hanapin ang nakaraang pangyayari ng parirala +pdfjs-find-previous-button-label = Nakaraan +pdfjs-find-next-button = + .title = Hanapin ang susunod na pangyayari ng parirala +pdfjs-find-next-button-label = Susunod +pdfjs-find-highlight-checkbox = I-highlight lahat +pdfjs-find-match-case-checkbox-label = Itugma ang case +pdfjs-find-entire-word-checkbox-label = Buong salita +pdfjs-find-reached-top = Naabot na ang tuktok ng dokumento, ipinagpatuloy mula sa ilalim +pdfjs-find-reached-bottom = Naabot na ang dulo ng dokumento, ipinagpatuloy mula sa tuktok +pdfjs-find-not-found = Hindi natagpuan ang parirala + +## Predefined zoom values + +pdfjs-page-scale-width = Lapad ng Pahina +pdfjs-page-scale-fit = Pagkasyahin ang Pahina +pdfjs-page-scale-auto = Automatic Zoom +pdfjs-page-scale-actual = Totoong sukat +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = Nagkaproblema habang niloload ang PDF. +pdfjs-invalid-file-error = Di-wasto o sira ang PDF file. +pdfjs-missing-file-error = Nawawalang PDF file. +pdfjs-unexpected-response-error = Hindi inaasahang tugon ng server. +pdfjs-rendering-error = Nagkaproblema habang nirerender ang pahina. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Annotation] + +## Password + +pdfjs-password-label = Ipasok ang password upang buksan ang PDF file na ito. +pdfjs-password-invalid = Maling password. Subukan uli. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Kanselahin +pdfjs-web-fonts-disabled = Naka-disable ang mga Web font: hindi kayang gamitin ang mga naka-embed na PDF font. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/tr/viewer.ftl b/public/assets/pdfjs/locale/tr/viewer.ftl new file mode 100755 index 0000000..b1b7cbf --- /dev/null +++ b/public/assets/pdfjs/locale/tr/viewer.ftl @@ -0,0 +1,515 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Önceki sayfa +pdfjs-previous-button-label = Önceki +pdfjs-next-button = + .title = Sonraki sayfa +pdfjs-next-button-label = Sonraki +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Sayfa +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = / { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } / { $pagesCount }) +pdfjs-zoom-out-button = + .title = UzaklaÅŸtır +pdfjs-zoom-out-button-label = UzaklaÅŸtır +pdfjs-zoom-in-button = + .title = YakınlaÅŸtır +pdfjs-zoom-in-button-label = YakınlaÅŸtır +pdfjs-zoom-select = + .title = YakınlaÅŸtırma +pdfjs-presentation-mode-button = + .title = Sunum moduna geç +pdfjs-presentation-mode-button-label = Sunum modu +pdfjs-open-file-button = + .title = Dosya aç +pdfjs-open-file-button-label = Aç +pdfjs-print-button = + .title = Yazdır +pdfjs-print-button-label = Yazdır +pdfjs-save-button = + .title = Kaydet +pdfjs-save-button-label = Kaydet +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = İndir +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = İndir +pdfjs-bookmark-button = + .title = Geçerli sayfa (geçerli sayfanın adresini görüntüle) +pdfjs-bookmark-button-label = Geçerli sayfa + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Araçlar +pdfjs-tools-button-label = Araçlar +pdfjs-first-page-button = + .title = İlk sayfaya git +pdfjs-first-page-button-label = İlk sayfaya git +pdfjs-last-page-button = + .title = Son sayfaya git +pdfjs-last-page-button-label = Son sayfaya git +pdfjs-page-rotate-cw-button = + .title = Saat yönünde döndür +pdfjs-page-rotate-cw-button-label = Saat yönünde döndür +pdfjs-page-rotate-ccw-button = + .title = Saat yönünün tersine döndür +pdfjs-page-rotate-ccw-button-label = Saat yönünün tersine döndür +pdfjs-cursor-text-select-tool-button = + .title = Metin seçme aracını etkinleÅŸtir +pdfjs-cursor-text-select-tool-button-label = Metin seçme aracı +pdfjs-cursor-hand-tool-button = + .title = El aracını etkinleÅŸtir +pdfjs-cursor-hand-tool-button-label = El aracı +pdfjs-scroll-page-button = + .title = Sayfa kaydırmayı kullan +pdfjs-scroll-page-button-label = Sayfa kaydırma +pdfjs-scroll-vertical-button = + .title = Dikey kaydırmayı kullan +pdfjs-scroll-vertical-button-label = Dikey kaydırma +pdfjs-scroll-horizontal-button = + .title = Yatay kaydırmayı kullan +pdfjs-scroll-horizontal-button-label = Yatay kaydırma +pdfjs-scroll-wrapped-button = + .title = Yan yana kaydırmayı kullan +pdfjs-scroll-wrapped-button-label = Yan yana kaydırma +pdfjs-spread-none-button = + .title = Yan yana sayfaları birleÅŸtirme +pdfjs-spread-none-button-label = BirleÅŸtirme +pdfjs-spread-odd-button = + .title = Yan yana sayfaları tek numaralı sayfalardan baÅŸlayarak birleÅŸtir +pdfjs-spread-odd-button-label = Tek numaralı +pdfjs-spread-even-button = + .title = Yan yana sayfaları çift numaralı sayfalardan baÅŸlayarak birleÅŸtir +pdfjs-spread-even-button-label = Çift numaralı + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Belge özellikleri… +pdfjs-document-properties-button-label = Belge özellikleri… +pdfjs-document-properties-file-name = Dosya adı: +pdfjs-document-properties-file-size = Dosya boyutu: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bayt) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bayt) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bayt) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bayt) +pdfjs-document-properties-title = BaÅŸlık: +pdfjs-document-properties-author = Yazar: +pdfjs-document-properties-subject = Konu: +pdfjs-document-properties-keywords = Anahtar kelimeler: +pdfjs-document-properties-creation-date = OluÅŸturma tarihi: +pdfjs-document-properties-modification-date = DeÄŸiÅŸtirme tarihi: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date } { $time } +pdfjs-document-properties-creator = OluÅŸturan: +pdfjs-document-properties-producer = PDF üreticisi: +pdfjs-document-properties-version = PDF sürümü: +pdfjs-document-properties-page-count = Sayfa sayısı: +pdfjs-document-properties-page-size = Sayfa boyutu: +pdfjs-document-properties-page-size-unit-inches = inç +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = dikey +pdfjs-document-properties-page-size-orientation-landscape = yatay +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Hızlı web görünümü: +pdfjs-document-properties-linearized-yes = Evet +pdfjs-document-properties-linearized-no = Hayır +pdfjs-document-properties-close-button = Kapat + +## Print + +pdfjs-print-progress-message = Belge yazdırılmaya hazırlanıyor… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = %{ $progress } +pdfjs-print-progress-close-button = İptal +pdfjs-printing-not-supported = Uyarı: Yazdırma bu tarayıcı tarafından tam olarak desteklenmemektedir. +pdfjs-printing-not-ready = Uyarı: PDF tamamen yüklenmedi ve yazdırmaya hazır deÄŸil. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Kenar çubuÄŸunu aç/kapat +pdfjs-toggle-sidebar-notification-button = + .title = Kenar çubuÄŸunu aç/kapat (Belge ana hat/ekler/katmanlar içeriyor) +pdfjs-toggle-sidebar-button-label = Kenar çubuÄŸunu aç/kapat +pdfjs-document-outline-button = + .title = Belge ana hatlarını göster (Tüm öğeleri geniÅŸletmek/daraltmak için çift tıklayın) +pdfjs-document-outline-button-label = Belge ana hatları +pdfjs-attachments-button = + .title = Ekleri göster +pdfjs-attachments-button-label = Ekler +pdfjs-layers-button = + .title = Katmanları göster (tüm katmanları varsayılan duruma sıfırlamak için çift tıklayın) +pdfjs-layers-button-label = Katmanlar +pdfjs-thumbs-button = + .title = Küçük resimleri göster +pdfjs-thumbs-button-label = Küçük resimler +pdfjs-current-outline-item-button = + .title = Mevcut ana hat öğesini bul +pdfjs-current-outline-item-button-label = Mevcut ana hat öğesi +pdfjs-findbar-button = + .title = Belgede bul +pdfjs-findbar-button-label = Bul +pdfjs-additional-layers = Ek katmanlar + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Sayfa { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = { $page }. sayfanın küçük hâli + +## Find panel button title and messages + +pdfjs-find-input = + .title = Bul + .placeholder = Belgede bul… +pdfjs-find-previous-button = + .title = Önceki eÅŸleÅŸmeyi bul +pdfjs-find-previous-button-label = Önceki +pdfjs-find-next-button = + .title = Sonraki eÅŸleÅŸmeyi bul +pdfjs-find-next-button-label = Sonraki +pdfjs-find-highlight-checkbox = Tümünü vurgula +pdfjs-find-match-case-checkbox-label = Büyük-küçük harfe duyarlı +pdfjs-find-match-diacritics-checkbox-label = Fonetik iÅŸaretleri bul +pdfjs-find-entire-word-checkbox-label = Tam sözcükler +pdfjs-find-reached-top = Belgenin başına ulaşıldı, sonundan devam edildi +pdfjs-find-reached-bottom = Belgenin sonuna ulaşıldı, başından devam edildi +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $total } eÅŸleÅŸmeden { $current }. eÅŸleÅŸme + *[other] { $total } eÅŸleÅŸmeden { $current }. eÅŸleÅŸme + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] { $limit } eÅŸleÅŸmeden fazla + *[other] { $limit } eÅŸleÅŸmeden fazla + } +pdfjs-find-not-found = EÅŸleÅŸme bulunamadı + +## Predefined zoom values + +pdfjs-page-scale-width = Sayfa geniÅŸliÄŸi +pdfjs-page-scale-fit = Sayfayı sığdır +pdfjs-page-scale-auto = Otomatik yakınlaÅŸtır +pdfjs-page-scale-actual = Gerçek boyut +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = %{ $scale } + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Sayfa { $page } + +## Loading indicator messages + +pdfjs-loading-error = PDF yüklenirken bir hata oluÅŸtu. +pdfjs-invalid-file-error = Geçersiz veya bozulmuÅŸ PDF dosyası. +pdfjs-missing-file-error = PDF dosyası eksik. +pdfjs-unexpected-response-error = Beklenmeyen sunucu yanıtı. +pdfjs-rendering-error = Sayfa yorumlanırken bir hata oluÅŸtu. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date } { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } iÅŸareti] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Bu PDF dosyasını açmak için parolasını yazın. +pdfjs-password-invalid = Geçersiz parola. Lütfen yeniden deneyin. +pdfjs-password-ok-button = Tamam +pdfjs-password-cancel-button = İptal +pdfjs-web-fonts-disabled = Web fontları devre dışı: Gömülü PDF fontları kullanılamıyor. + +## Editing + +pdfjs-editor-free-text-button = + .title = Metin +pdfjs-editor-free-text-button-label = Metin +pdfjs-editor-ink-button = + .title = Çiz +pdfjs-editor-ink-button-label = Çiz +pdfjs-editor-stamp-button = + .title = Resim ekle veya düzenle +pdfjs-editor-stamp-button-label = Resim ekle veya düzenle +pdfjs-editor-highlight-button = + .title = Vurgula +pdfjs-editor-highlight-button-label = Vurgula +pdfjs-highlight-floating-button1 = + .title = Vurgula + .aria-label = Vurgula +pdfjs-highlight-floating-button-label = Vurgula + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Çizimi kaldır +pdfjs-editor-remove-freetext-button = + .title = Metni kaldır +pdfjs-editor-remove-stamp-button = + .title = Resmi kaldır +pdfjs-editor-remove-highlight-button = + .title = Vurgulamayı kaldır + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Renk +pdfjs-editor-free-text-size-input = Boyut +pdfjs-editor-ink-color-input = Renk +pdfjs-editor-ink-thickness-input = Kalınlık +pdfjs-editor-ink-opacity-input = Saydamlık +pdfjs-editor-stamp-add-image-button = + .title = Resim ekle +pdfjs-editor-stamp-add-image-button-label = Resim ekle +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Kalınlık +pdfjs-editor-free-highlight-thickness-title = + .title = Metin dışındaki öğeleri vurgularken kalınlığı deÄŸiÅŸtir +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Metin düzenleyicisi + .default-content = Yazmaya baÅŸlayın… +pdfjs-free-text = + .aria-label = Metin düzenleyicisi +pdfjs-free-text-default-content = Yazmaya baÅŸlayın… +pdfjs-ink = + .aria-label = Çizim düzenleyicisi +pdfjs-ink-canvas = + .aria-label = Kullanıcı tarafından oluÅŸturulan resim + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Alternatif metin +pdfjs-editor-alt-text-edit-button = + .aria-label = Alternatif metni düzenle +pdfjs-editor-alt-text-edit-button-label = Alternatif metni düzenle +pdfjs-editor-alt-text-dialog-label = Bir seçenek seçin +pdfjs-editor-alt-text-dialog-description = Alternatif metin, insanlar resmi göremediÄŸinde veya resim yüklenmediÄŸinde iÅŸe yarar. +pdfjs-editor-alt-text-add-description-label = Açıklama ekle +pdfjs-editor-alt-text-add-description-description = Konuyu, ortamı veya eylemleri tanımlayan bir iki cümle yazmaya çalışın. +pdfjs-editor-alt-text-mark-decorative-label = Dekoratif olarak iÅŸaretle +pdfjs-editor-alt-text-mark-decorative-description = Kenarlıklar veya filigranlar gibi dekoratif resimler için kullanılır. +pdfjs-editor-alt-text-cancel-button = Vazgeç +pdfjs-editor-alt-text-save-button = Kaydet +pdfjs-editor-alt-text-decorative-tooltip = Dekoratif olarak iÅŸaretlendi +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = ÖrneÄŸin, “Genç bir adam yemek yemek için masaya oturuyor†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Alternatif metin + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Sol üst köşe — yeniden boyutlandır +pdfjs-editor-resizer-label-top-middle = Üst orta — yeniden boyutlandır +pdfjs-editor-resizer-label-top-right = SaÄŸ üst köşe — yeniden boyutlandır +pdfjs-editor-resizer-label-middle-right = Orta saÄŸ — yeniden boyutlandır +pdfjs-editor-resizer-label-bottom-right = SaÄŸ alt köşe — yeniden boyutlandır +pdfjs-editor-resizer-label-bottom-middle = Alt orta — yeniden boyutlandır +pdfjs-editor-resizer-label-bottom-left = Sol alt köşe — yeniden boyutlandır +pdfjs-editor-resizer-label-middle-left = Orta sol — yeniden boyutlandır +pdfjs-editor-resizer-top-left = + .aria-label = Sol üst köşe — yeniden boyutlandır +pdfjs-editor-resizer-top-middle = + .aria-label = Üst orta — yeniden boyutlandır +pdfjs-editor-resizer-top-right = + .aria-label = SaÄŸ üst köşe — yeniden boyutlandır +pdfjs-editor-resizer-middle-right = + .aria-label = Orta saÄŸ — yeniden boyutlandır +pdfjs-editor-resizer-bottom-right = + .aria-label = SaÄŸ alt köşe — yeniden boyutlandır +pdfjs-editor-resizer-bottom-middle = + .aria-label = Alt orta — yeniden boyutlandır +pdfjs-editor-resizer-bottom-left = + .aria-label = Sol alt köşe — yeniden boyutlandır +pdfjs-editor-resizer-middle-left = + .aria-label = Orta sol — yeniden boyutlandır + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Vurgu rengi +pdfjs-editor-colorpicker-button = + .title = Rengi deÄŸiÅŸtir +pdfjs-editor-colorpicker-dropdown = + .aria-label = Renk seçenekleri +pdfjs-editor-colorpicker-yellow = + .title = Sarı +pdfjs-editor-colorpicker-green = + .title = YeÅŸil +pdfjs-editor-colorpicker-blue = + .title = Mavi +pdfjs-editor-colorpicker-pink = + .title = Pembe +pdfjs-editor-colorpicker-red = + .title = Kırmızı + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Tümünü göster +pdfjs-editor-highlight-show-all-button = + .title = Tümünü göster + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Alt metni düzenle (resim açıklaması) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Alt metin ekle (resim açıklaması) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Açıklamanızı buraya yazın… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Görme engelli kiÅŸilere gösterilecek veya resmin yüklenemediÄŸi durumlarda gösterilecek kısa açıklama. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Bu alt metin otomatik olarak oluÅŸturulmuÅŸtur ve hatalı olabilir. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Daha fazla bilgi alın +pdfjs-editor-new-alt-text-create-automatically-button-label = Otomatik olarak alt metin oluÅŸtur +pdfjs-editor-new-alt-text-not-now-button = Åžimdi deÄŸil +pdfjs-editor-new-alt-text-error-title = Alt metin otomatik olarak oluÅŸturulamadı +pdfjs-editor-new-alt-text-error-description = Lütfen kendi alt metninizi yazın veya daha sonra yeniden deneyin. +pdfjs-editor-new-alt-text-error-close-button = Kapat +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Alt metin yapay zekâ modeli indiriliyor ({ $downloadedSize } / { $totalSize } MB) + .aria-valuetext = Alt metin yapay zekâ modeli indiriliyor ({ $downloadedSize } / { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Alternatif metin eklendi +pdfjs-editor-new-alt-text-added-button-label = Alt metin eklendi +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Alternatif metin eksik +pdfjs-editor-new-alt-text-missing-button-label = Alt metin eksik +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Alternatif metni incele +pdfjs-editor-new-alt-text-to-review-button-label = Alt metni incele +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Otomatik olarak oluÅŸturuldu: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Resim alt metni ayarları +pdfjs-image-alt-text-settings-button-label = Resim alt metni ayarları +pdfjs-editor-alt-text-settings-dialog-label = Resim alt metni ayarları +pdfjs-editor-alt-text-settings-automatic-title = Otomatik alt metin +pdfjs-editor-alt-text-settings-create-model-button-label = Otomatik olarak alt metin oluÅŸtur +pdfjs-editor-alt-text-settings-create-model-description = Görme engelli kiÅŸilere gösterilecek veya resmin yüklenemediÄŸi durumlarda gösterilecek açıklamalar önerir. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Alt metin yapay zekâ modeli ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Verilerinizin gizli kalması için cihazınızda yerel olarak çalışır. Otomatik alt metin için gereklidir. +pdfjs-editor-alt-text-settings-delete-model-button = Sil +pdfjs-editor-alt-text-settings-download-model-button = İndir +pdfjs-editor-alt-text-settings-downloading-model-button = İndiriliyor… +pdfjs-editor-alt-text-settings-editor-title = Alt metin düzenleyicisi +pdfjs-editor-alt-text-settings-show-dialog-button-label = Resim eklerken alt metin düzenleyicisini hemen göster +pdfjs-editor-alt-text-settings-show-dialog-description = Tüm resimlerinizin alt metne sahip olduÄŸundan emin olmanızı saÄŸlar. +pdfjs-editor-alt-text-settings-close-button = Kapat + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Vurgulama silindi +pdfjs-editor-undo-bar-message-freetext = Metin silindi +pdfjs-editor-undo-bar-message-ink = Çizim silindi +pdfjs-editor-undo-bar-message-stamp = Görsel silindi +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } ek açıklama silindi + *[other] { $count } ek açıklama silindi + } +pdfjs-editor-undo-bar-undo-button = + .title = Geri al +pdfjs-editor-undo-bar-undo-button-label = Geri al +pdfjs-editor-undo-bar-close-button = + .title = Kapat +pdfjs-editor-undo-bar-close-button-label = Kapat diff --git a/public/assets/pdfjs/locale/trs/viewer.ftl b/public/assets/pdfjs/locale/trs/viewer.ftl new file mode 100755 index 0000000..aba3c72 --- /dev/null +++ b/public/assets/pdfjs/locale/trs/viewer.ftl @@ -0,0 +1,197 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Pajinâ gunâj rukùu +pdfjs-previous-button-label = Sa gachin +pdfjs-next-button = + .title = Pajinâ 'na' ñaan +pdfjs-next-button-label = Ne' ñaan +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Ñanj +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = si'iaj { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount }) +pdfjs-zoom-out-button = + .title = Nagi'iaj li' +pdfjs-zoom-out-button-label = Nagi'iaj li' +pdfjs-zoom-in-button = + .title = Nagi'iaj niko' +pdfjs-zoom-in-button-label = Nagi'iaj niko' +pdfjs-zoom-select = + .title = dàj nìko ma'an +pdfjs-presentation-mode-button = + .title = Naduno' daj ga ma +pdfjs-presentation-mode-button-label = Daj gà ma +pdfjs-open-file-button = + .title = Na'nïn' chrû ñanj +pdfjs-open-file-button-label = Na'nïn +pdfjs-print-button = + .title = Nari' ña du'ua +pdfjs-print-button-label = Nari' ñadu'ua + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Rasun +pdfjs-tools-button-label = Nej rasùun +pdfjs-first-page-button = + .title = gun' riña pajina asiniin +pdfjs-first-page-button-label = Gun' riña pajina asiniin +pdfjs-last-page-button = + .title = Gun' riña pajina rukù ni'in +pdfjs-last-page-button-label = Gun' riña pajina rukù ni'inj +pdfjs-page-rotate-cw-button = + .title = Tanikaj ne' huat +pdfjs-page-rotate-cw-button-label = Tanikaj ne' huat +pdfjs-page-rotate-ccw-button = + .title = Tanikaj ne' chînt' +pdfjs-page-rotate-ccw-button-label = Tanikaj ne' chint +pdfjs-cursor-text-select-tool-button = + .title = Dugi'iaj sun' sa ganahui texto +pdfjs-cursor-text-select-tool-button-label = Nej rasun arajsun' da' nahui' texto +pdfjs-cursor-hand-tool-button = + .title = Nachrun' nej rasun +pdfjs-cursor-hand-tool-button-label = Sa rajsun ro'o' +pdfjs-scroll-vertical-button = + .title = Garasun' dukuán runÅ«u +pdfjs-scroll-vertical-button-label = Dukuán runÅ«u +pdfjs-scroll-horizontal-button = + .title = Garasun' dukuán nikin' nahui +pdfjs-scroll-horizontal-button-label = Dukuán nikin' nahui +pdfjs-scroll-wrapped-button = + .title = Garasun' sa nachree +pdfjs-scroll-wrapped-button-label = Sa nachree +pdfjs-spread-none-button = + .title = Si nagi'iaj nugun'un' nej pagina hua ninin +pdfjs-spread-none-button-label = Ni'io daj hua pagina +pdfjs-spread-odd-button = + .title = Nagi'iaj nugua'ant nej pajina +pdfjs-spread-odd-button-label = Ni'io' daj hua libro gurin +pdfjs-spread-even-button = + .title = NakÄj dugui' ngà nej pajinâ ayi'ì ngà da' hùi hùi +pdfjs-spread-even-button-label = Nahuin nìko nej + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Nej sa nikÄj ñanj… +pdfjs-document-properties-button-label = Nej sa nikÄj ñanj… +pdfjs-document-properties-file-name = Si yugui archîbo: +pdfjs-document-properties-file-size = Dàj yachìj archîbo: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Si yugui: +pdfjs-document-properties-author = Sí girirà: +pdfjs-document-properties-subject = Dugui': +pdfjs-document-properties-keywords = Nej nuguan' huìi: +pdfjs-document-properties-creation-date = Gui gurugui' man: +pdfjs-document-properties-modification-date = Nuguan' nahuin nakà: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Guiri ro' +pdfjs-document-properties-producer = Sa ri PDF: +pdfjs-document-properties-version = PDF Version: +pdfjs-document-properties-page-count = Si Guendâ Pâjina: +pdfjs-document-properties-page-size = Dàj yachìj pâjina: +pdfjs-document-properties-page-size-unit-inches = riña +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = nadu'ua +pdfjs-document-properties-page-size-orientation-landscape = dàj huaj +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Da'ngà'a +pdfjs-document-properties-page-size-name-legal = Nuguan' a'nï'ïn + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Nanèt chre ni'iajt riña Web: +pdfjs-document-properties-linearized-yes = Ga'ue +pdfjs-document-properties-linearized-no = Si ga'ue +pdfjs-document-properties-close-button = Narán + +## Print + +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Duyichin' + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = NadunÄ barrâ nù yi'nïn +pdfjs-toggle-sidebar-button-label = NadunÄ barrâ nù yi'nïn +pdfjs-findbar-button-label = Narì' + +## Thumbnails panel item (tooltip and alt text for images) + + +## Find panel button title and messages + +pdfjs-find-previous-button-label = Sa gachîn +pdfjs-find-next-button-label = Ne' ñaan +pdfjs-find-highlight-checkbox = Daran' sa ña'an +pdfjs-find-match-case-checkbox-label = Match case +pdfjs-find-not-found = Nu narì'ij nugua'anj + +## Predefined zoom values + +pdfjs-page-scale-actual = Dàj yàchi akuan' nín +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + + +## Annotations + + +## Password + +pdfjs-password-ok-button = Ga'ue +pdfjs-password-cancel-button = Duyichin' + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/uk/viewer.ftl b/public/assets/pdfjs/locale/uk/viewer.ftl new file mode 100755 index 0000000..dd54727 --- /dev/null +++ b/public/assets/pdfjs/locale/uk/viewer.ftl @@ -0,0 +1,518 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = ÐŸÐ¾Ð¿ÐµÑ€ÐµÐ´Ð½Ñ Ñторінка +pdfjs-previous-button-label = ÐŸÐ¾Ð¿ÐµÑ€ÐµÐ´Ð½Ñ +pdfjs-next-button = + .title = ÐаÑтупна Ñторінка +pdfjs-next-button-label = ÐаÑтупна +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Сторінка +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = із { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } із { $pagesCount }) +pdfjs-zoom-out-button = + .title = Зменшити +pdfjs-zoom-out-button-label = Зменшити +pdfjs-zoom-in-button = + .title = Збільшити +pdfjs-zoom-in-button-label = Збільшити +pdfjs-zoom-select = + .title = МаÑштаб +pdfjs-presentation-mode-button = + .title = Перейти в режим презентації +pdfjs-presentation-mode-button-label = Режим презентації +pdfjs-open-file-button = + .title = Відкрити файл +pdfjs-open-file-button-label = Відкрити +pdfjs-print-button = + .title = Друк +pdfjs-print-button-label = Друк +pdfjs-save-button = + .title = Зберегти +pdfjs-save-button-label = Зберегти +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Завантажити +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Завантажити +pdfjs-bookmark-button = + .title = Поточна Ñторінка (переглÑд URL-адреÑи з поточної Ñторінки) +pdfjs-bookmark-button-label = Поточна Ñторінка + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = ІнÑтрументи +pdfjs-tools-button-label = ІнÑтрументи +pdfjs-first-page-button = + .title = Ðа першу Ñторінку +pdfjs-first-page-button-label = Ðа першу Ñторінку +pdfjs-last-page-button = + .title = Ðа оÑтанню Ñторінку +pdfjs-last-page-button-label = Ðа оÑтанню Ñторінку +pdfjs-page-rotate-cw-button = + .title = Повернути за годинниковою Ñтрілкою +pdfjs-page-rotate-cw-button-label = Повернути за годинниковою Ñтрілкою +pdfjs-page-rotate-ccw-button = + .title = Повернути проти годинникової Ñтрілки +pdfjs-page-rotate-ccw-button-label = Повернути проти годинникової Ñтрілки +pdfjs-cursor-text-select-tool-button = + .title = Увімкнути інÑтрумент вибору текÑту +pdfjs-cursor-text-select-tool-button-label = ІнÑтрумент вибору текÑту +pdfjs-cursor-hand-tool-button = + .title = Увімкнути інÑтрумент "Рука" +pdfjs-cursor-hand-tool-button-label = ІнÑтрумент "Рука" +pdfjs-scroll-page-button = + .title = ВикориÑтовувати Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ñторінки +pdfjs-scroll-page-button-label = ÐŸÑ€Ð¾ÐºÑ€ÑƒÑ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ñторінки +pdfjs-scroll-vertical-button = + .title = ВикориÑтовувати вертикальне Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‡ÑƒÐ²Ð°Ð½Ð½Ñ +pdfjs-scroll-vertical-button-label = Вертикальне Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‡ÑƒÐ²Ð°Ð½Ð½Ñ +pdfjs-scroll-horizontal-button = + .title = ВикориÑтовувати горизонтальне Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‡ÑƒÐ²Ð°Ð½Ð½Ñ +pdfjs-scroll-horizontal-button-label = Горизонтальне Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‡ÑƒÐ²Ð°Ð½Ð½Ñ +pdfjs-scroll-wrapped-button = + .title = ВикориÑтовувати маÑштабоване Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‡ÑƒÐ²Ð°Ð½Ð½Ñ +pdfjs-scroll-wrapped-button-label = МаÑштабоване Ð¿Ñ€Ð¾ÐºÑ€ÑƒÑ‡ÑƒÐ²Ð°Ð½Ð½Ñ +pdfjs-spread-none-button = + .title = Ðе викориÑтовувати розгорнуті Ñторінки +pdfjs-spread-none-button-label = Без розгорнутих Ñторінок +pdfjs-spread-odd-button = + .title = Розгорнуті Ñторінки починаютьÑÑ Ð· непарних номерів +pdfjs-spread-odd-button-label = Ðепарні Ñторінки зліва +pdfjs-spread-even-button = + .title = Розгорнуті Ñторінки починаютьÑÑ Ð· парних номерів +pdfjs-spread-even-button-label = Парні Ñторінки зліва + +## Document properties dialog + +pdfjs-document-properties-button = + .title = ВлаÑтивоÑті документа… +pdfjs-document-properties-button-label = ВлаÑтивоÑті документа… +pdfjs-document-properties-file-name = Ðазва файлу: +pdfjs-document-properties-file-size = Розмір файлу: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } кБ ({ $b } байтів) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } МБ ({ $b } байтів) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } кБ ({ $size_b } байтів) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } МБ ({ $size_b } байтів) +pdfjs-document-properties-title = Заголовок: +pdfjs-document-properties-author = Ðвтор: +pdfjs-document-properties-subject = Тема: +pdfjs-document-properties-keywords = Ключові Ñлова: +pdfjs-document-properties-creation-date = Дата ÑтвореннÑ: +pdfjs-document-properties-modification-date = Дата зміни: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Створено: +pdfjs-document-properties-producer = Виробник PDF: +pdfjs-document-properties-version = ВерÑÑ–Ñ PDF: +pdfjs-document-properties-page-count = КількіÑть Ñторінок: +pdfjs-document-properties-page-size = Розмір Ñторінки: +pdfjs-document-properties-page-size-unit-inches = дюймів +pdfjs-document-properties-page-size-unit-millimeters = мм +pdfjs-document-properties-page-size-orientation-portrait = книжкова +pdfjs-document-properties-page-size-orientation-landscape = альбомна +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Швидкий переглÑд в Інтернеті: +pdfjs-document-properties-linearized-yes = Так +pdfjs-document-properties-linearized-no = ÐÑ– +pdfjs-document-properties-close-button = Закрити + +## Print + +pdfjs-print-progress-message = Підготовка документу до друку… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = СкаÑувати +pdfjs-printing-not-supported = ПопередженнÑ: Цей браузер не повніÑтю підтримує друк. +pdfjs-printing-not-ready = ПопередженнÑ: PDF не повніÑтю завантажений Ð´Ð»Ñ Ð´Ñ€ÑƒÐºÑƒ. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Бічна панель +pdfjs-toggle-sidebar-notification-button = + .title = Перемкнути бічну панель (документ міÑтить еÑкіз/вкладеннÑ/шари) +pdfjs-toggle-sidebar-button-label = Перемкнути бічну панель +pdfjs-document-outline-button = + .title = Показати Ñхему документу (подвійний клік Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ/Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ ÐµÐ»ÐµÐ¼ÐµÐ½Ñ‚Ñ–Ð²) +pdfjs-document-outline-button-label = Схема документа +pdfjs-attachments-button = + .title = Показати Ð²ÐºÐ»Ð°Ð´ÐµÐ½Ð½Ñ +pdfjs-attachments-button-label = Ð’ÐºÐ»Ð°Ð´ÐµÐ½Ð½Ñ +pdfjs-layers-button = + .title = Показати шари (двічі клацніть, щоб Ñкинути вÑÑ– шари до типового Ñтану) +pdfjs-layers-button-label = Шари +pdfjs-thumbs-button = + .title = Показати мініатюри +pdfjs-thumbs-button-label = Мініатюри +pdfjs-current-outline-item-button = + .title = Знайти поточний елемент зміÑту +pdfjs-current-outline-item-button-label = Поточний елемент зміÑту +pdfjs-findbar-button = + .title = Знайти в документі +pdfjs-findbar-button-label = Знайти +pdfjs-additional-layers = Додаткові шари + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Сторінка { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = ЕÑкіз Ñторінки { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Знайти + .placeholder = Знайти в документі… +pdfjs-find-previous-button = + .title = Знайти попереднє Ð²Ñ…Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ Ñ„Ñ€Ð°Ð·Ð¸ +pdfjs-find-previous-button-label = Попереднє +pdfjs-find-next-button = + .title = Знайти наÑтупне Ð²Ñ…Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ Ñ„Ñ€Ð°Ð·Ð¸ +pdfjs-find-next-button-label = ÐаÑтупне +pdfjs-find-highlight-checkbox = ПідÑвітити вÑе +pdfjs-find-match-case-checkbox-label = З урахуваннÑм регіÑтру +pdfjs-find-match-diacritics-checkbox-label = ВідповідніÑть діакритичних знаків +pdfjs-find-entire-word-checkbox-label = Цілі Ñлова +pdfjs-find-reached-top = ДоÑÑгнуто початку документу, продовжено з ÐºÑ–Ð½Ñ†Ñ +pdfjs-find-reached-bottom = ДоÑÑгнуто ÐºÑ–Ð½Ñ†Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ñƒ, продовжено з початку +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { $total -> + [one] { $current } збіг з { $total } + [few] { $current } збіги з { $total } + *[many] { $current } збігів з { $total } + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { $limit -> + [one] Понад { $limit } збіг + [few] Понад { $limit } збіги + *[many] Понад { $limit } збігів + } +pdfjs-find-not-found = Фразу не знайдено + +## Predefined zoom values + +pdfjs-page-scale-width = За шириною +pdfjs-page-scale-fit = ВміÑтити +pdfjs-page-scale-auto = ÐвтомаÑштаб +pdfjs-page-scale-actual = ДійÑний розмір +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Сторінка { $page } + +## Loading indicator messages + +pdfjs-loading-error = Під Ñ‡Ð°Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ PDF ÑталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°. +pdfjs-invalid-file-error = ÐедійÑний або пошкоджений PDF-файл. +pdfjs-missing-file-error = ВідÑутній PDF-файл. +pdfjs-unexpected-response-error = Ðеочікувана відповідь Ñервера. +pdfjs-rendering-error = Під Ñ‡Ð°Ñ Ð²Ð¸Ð²ÐµÐ´ÐµÐ½Ð½Ñ Ñторінки ÑталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type }-анотаціÑ] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Введіть пароль Ð´Ð»Ñ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñ†ÑŒÐ¾Ð³Ð¾ PDF-файлу. +pdfjs-password-invalid = Ðеправильний пароль. Спробуйте ще раз. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = СкаÑувати +pdfjs-web-fonts-disabled = Вебшрифти вимкнено: неможливо викориÑтати вбудовані у PDF шрифти. + +## Editing + +pdfjs-editor-free-text-button = + .title = ТекÑÑ‚ +pdfjs-editor-free-text-button-label = ТекÑÑ‚ +pdfjs-editor-ink-button = + .title = Малювати +pdfjs-editor-ink-button-label = Малювати +pdfjs-editor-stamp-button = + .title = Додати чи редагувати Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ +pdfjs-editor-stamp-button-label = Додати чи редагувати Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ +pdfjs-editor-highlight-button = + .title = ПідÑвітити +pdfjs-editor-highlight-button-label = ПідÑвітити +pdfjs-highlight-floating-button1 = + .title = ПідÑвітити + .aria-label = ПідÑвітити +pdfjs-highlight-floating-button-label = ПідÑвітити + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Вилучити малюнок +pdfjs-editor-remove-freetext-button = + .title = Вилучити текÑÑ‚ +pdfjs-editor-remove-stamp-button = + .title = Вилучити Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ +pdfjs-editor-remove-highlight-button = + .title = Вилучити підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Колір +pdfjs-editor-free-text-size-input = Розмір +pdfjs-editor-ink-color-input = Колір +pdfjs-editor-ink-thickness-input = Товщина +pdfjs-editor-ink-opacity-input = ПрозоріÑть +pdfjs-editor-stamp-add-image-button = + .title = Додати Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ +pdfjs-editor-stamp-add-image-button-label = Додати Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Товщина +pdfjs-editor-free-highlight-thickness-title = + .title = Змінюйте товщину під Ñ‡Ð°Ñ Ð¿Ñ–Ð´ÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ ÐµÐ»ÐµÐ¼ÐµÐ½Ñ‚Ñ–Ð², крім текÑту +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = ТекÑтовий редактор + .default-content = Ðапишіть щоÑь… +pdfjs-free-text = + .aria-label = ТекÑтовий редактор +pdfjs-free-text-default-content = Почніть вводити… +pdfjs-ink = + .aria-label = Графічний редактор +pdfjs-ink-canvas = + .aria-label = ЗображеннÑ, Ñтворене кориÑтувачем + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Ðльтернативний текÑÑ‚ +pdfjs-editor-alt-text-edit-button = + .aria-label = Редагувати альтернативний текÑÑ‚ +pdfjs-editor-alt-text-edit-button-label = Змінити альтернативний текÑÑ‚ +pdfjs-editor-alt-text-dialog-label = Вибрати варіант +pdfjs-editor-alt-text-dialog-description = Ðльтернативний текÑÑ‚ допомагає, коли Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð½Ðµ видно або коли воно не завантажуєтьÑÑ. +pdfjs-editor-alt-text-add-description-label = Додати Ð¾Ð¿Ð¸Ñ +pdfjs-editor-alt-text-add-description-description = ÐамагайтеÑÑ Ñтворити 1-2 реченнÑ, Ñкі опиÑують тему, обÑтавини або дії. +pdfjs-editor-alt-text-mark-decorative-label = Позначити декоративним +pdfjs-editor-alt-text-mark-decorative-description = ВикориÑтовуєтьÑÑ Ð´Ð»Ñ Ð´ÐµÐºÐ¾Ñ€Ð°Ñ‚Ð¸Ð²Ð½Ð¸Ñ… зображень, наприклад рамок або водÑних знаків. +pdfjs-editor-alt-text-cancel-button = СкаÑувати +pdfjs-editor-alt-text-save-button = Зберегти +pdfjs-editor-alt-text-decorative-tooltip = Позначено декоративним +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Ðаприклад, “Молодий чоловік Ñідає за Ñтіл Ñ—Ñти†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Ðльтернативний текÑÑ‚ + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Верхній лівий кут – зміна розміру +pdfjs-editor-resizer-label-top-middle = Вгорі поÑередині – зміна розміру +pdfjs-editor-resizer-label-top-right = Верхній правий кут – зміна розміру +pdfjs-editor-resizer-label-middle-right = Праворуч поÑередині – зміна розміру +pdfjs-editor-resizer-label-bottom-right = Ðижній правий кут – зміна розміру +pdfjs-editor-resizer-label-bottom-middle = Внизу поÑередині – зміна розміру +pdfjs-editor-resizer-label-bottom-left = Ðижній лівий кут – зміна розміру +pdfjs-editor-resizer-label-middle-left = Ліворуч поÑередині – зміна розміру +pdfjs-editor-resizer-top-left = + .aria-label = Верхній лівий кут – зміна розміру +pdfjs-editor-resizer-top-middle = + .aria-label = Вгорі поÑередині – зміна розміру +pdfjs-editor-resizer-top-right = + .aria-label = Верхній правий кут – зміна розміру +pdfjs-editor-resizer-middle-right = + .aria-label = Праворуч поÑередині – зміна розміру +pdfjs-editor-resizer-bottom-right = + .aria-label = Ðижній правий кут – зміна розміру +pdfjs-editor-resizer-bottom-middle = + .aria-label = Внизу поÑередині – зміна розміру +pdfjs-editor-resizer-bottom-left = + .aria-label = Ðижній лівий кут – зміна розміру +pdfjs-editor-resizer-middle-left = + .aria-label = Ліворуч поÑередині – зміна розміру + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Колір підÑÐ²Ñ–Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ +pdfjs-editor-colorpicker-button = + .title = Змінити колір +pdfjs-editor-colorpicker-dropdown = + .aria-label = Вибір кольору +pdfjs-editor-colorpicker-yellow = + .title = Жовтий +pdfjs-editor-colorpicker-green = + .title = Зелений +pdfjs-editor-colorpicker-blue = + .title = Блакитний +pdfjs-editor-colorpicker-pink = + .title = Рожевий +pdfjs-editor-colorpicker-red = + .title = Червоний + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Показати вÑе +pdfjs-editor-highlight-show-all-button = + .title = Показати вÑе + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Редагувати альтернативний текÑÑ‚ (Ð¾Ð¿Ð¸Ñ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Додати альтернативний текÑÑ‚ (Ð¾Ð¿Ð¸Ñ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Ðапишіть Ñвій Ð¾Ð¿Ð¸Ñ Ñ‚ÑƒÑ‚â€¦ +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Короткий Ð¾Ð¿Ð¸Ñ Ð´Ð»Ñ Ð»ÑŽÐ´ÐµÐ¹, Ñкі не бачать зображеннÑ, або Ñкщо Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð½Ðµ завантажуєтьÑÑ. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Цей альтернативний текÑÑ‚ Ñтворено автоматично, тому він може бути неточним. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Докладніше +pdfjs-editor-new-alt-text-create-automatically-button-label = Ðвтоматично Ñтворювати альтернативний текÑÑ‚ +pdfjs-editor-new-alt-text-not-now-button = Ðе зараз +pdfjs-editor-new-alt-text-error-title = Ðе вдалоÑÑ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ð¾ Ñтворити альтернативний текÑÑ‚ +pdfjs-editor-new-alt-text-error-description = Ðапишіть влаÑний альтернативний текÑÑ‚ або повторіть Ñпробу пізніше. +pdfjs-editor-new-alt-text-error-close-button = Закрити +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð¼Ð¾Ð´ÐµÐ»Ñ– ШІ Ð´Ð»Ñ Ð°Ð»ÑŒÑ‚ÐµÑ€Ð½Ð°Ñ‚Ð¸Ð²Ð½Ð¾Ð³Ð¾ текÑту ({ $downloadedSize } з { $totalSize } МБ) + .aria-valuetext = Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð¼Ð¾Ð´ÐµÐ»Ñ– ШІ Ð´Ð»Ñ Ð°Ð»ÑŒÑ‚ÐµÑ€Ð½Ð°Ñ‚Ð¸Ð²Ð½Ð¾Ð³Ð¾ текÑту ({ $downloadedSize } з { $totalSize } МБ) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Ðльтернативний текÑÑ‚ додано +pdfjs-editor-new-alt-text-added-button-label = Ðльтернативний текÑÑ‚ додано +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = ВідÑутній альтернативний текÑÑ‚ +pdfjs-editor-new-alt-text-missing-button-label = ВідÑутній альтернативний текÑÑ‚ +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = ПереглÑнути альтернативний текÑÑ‚ +pdfjs-editor-new-alt-text-to-review-button-label = ПереглÑнути альтернативний текÑÑ‚ +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Створено автоматично: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð»ÑŒÑ‚ÐµÑ€Ð½Ð°Ñ‚Ð¸Ð²Ð½Ð¾Ð³Ð¾ текÑту Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ +pdfjs-image-alt-text-settings-button-label = ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð»ÑŒÑ‚ÐµÑ€Ð½Ð°Ñ‚Ð¸Ð²Ð½Ð¾Ð³Ð¾ текÑту Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ +pdfjs-editor-alt-text-settings-dialog-label = ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð»ÑŒÑ‚ÐµÑ€Ð½Ð°Ñ‚Ð¸Ð²Ð½Ð¾Ð³Ð¾ текÑту Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ +pdfjs-editor-alt-text-settings-automatic-title = Ðвтоматичний альтернативний текÑÑ‚ +pdfjs-editor-alt-text-settings-create-model-button-label = Ðвтоматично Ñтворювати альтернативний текÑÑ‚ +pdfjs-editor-alt-text-settings-create-model-description = Пропонує опиÑи, щоб допомогти людÑм, Ñкі не бачать зображеннÑ, або Ñкщо Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð½Ðµ завантажуєтьÑÑ. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Модель ШІ Ð´Ð»Ñ Ð°Ð»ÑŒÑ‚ÐµÑ€Ð½Ð°Ñ‚Ð¸Ð²Ð½Ð¾Ð³Ð¾ текÑту ({ $totalSize } МБ) +pdfjs-editor-alt-text-settings-ai-model-description = Працює локально на вашому приÑтрої, тому приватніÑть ваших даних захищена. Призначена Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ð¾Ð³Ð¾ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð°Ð»ÑŒÑ‚ÐµÑ€Ð½Ð°Ñ‚Ð¸Ð²Ð½Ð¾Ð³Ð¾ текÑту. +pdfjs-editor-alt-text-settings-delete-model-button = Видалити +pdfjs-editor-alt-text-settings-download-model-button = Завантажити +pdfjs-editor-alt-text-settings-downloading-model-button = ЗавантаженнÑ… +pdfjs-editor-alt-text-settings-editor-title = Редактор альтернативного текÑту +pdfjs-editor-alt-text-settings-show-dialog-button-label = Показувати редактор альтернативного текÑту під Ñ‡Ð°Ñ Ð´Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ +pdfjs-editor-alt-text-settings-show-dialog-description = Допомагає переконатиÑÑ, що вÑÑ– ваші Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð¼Ð°ÑŽÑ‚ÑŒ альтернативний текÑÑ‚. +pdfjs-editor-alt-text-settings-close-button = Закрити + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = ПідÑÐ²Ñ–Ñ‡ÐµÐ½Ð½Ñ Ð²Ð¸Ð»ÑƒÑ‡ÐµÐ½Ð¾ +pdfjs-editor-undo-bar-message-freetext = ТекÑÑ‚ вилучено +pdfjs-editor-undo-bar-message-ink = Малюнок вилучено +pdfjs-editor-undo-bar-message-stamp = Ð—Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð²Ð¸Ð»ÑƒÑ‡ÐµÐ½Ð¾ +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = + { $count -> + [one] { $count } анотацію вилучено + [few] { $count } анотації вилучено + *[many] { $count } анотацій вилучено + } +pdfjs-editor-undo-bar-undo-button = + .title = Повернути +pdfjs-editor-undo-bar-undo-button-label = Повернути +pdfjs-editor-undo-bar-close-button = + .title = Закрити +pdfjs-editor-undo-bar-close-button-label = Закрити diff --git a/public/assets/pdfjs/locale/ur/viewer.ftl b/public/assets/pdfjs/locale/ur/viewer.ftl new file mode 100755 index 0000000..c15f157 --- /dev/null +++ b/public/assets/pdfjs/locale/ur/viewer.ftl @@ -0,0 +1,248 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = پچھلا ØµÙØ­Û +pdfjs-previous-button-label = پچھلا +pdfjs-next-button = + .title = اگلا ØµÙØ­Û +pdfjs-next-button-label = Ø¢Ú¯Û’ +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = ØµÙØ­Û +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = { $pagesCount } کا +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } کا { $pagesCount }) +pdfjs-zoom-out-button = + .title = Ø¨Ø§ÛØ± زوم کریں +pdfjs-zoom-out-button-label = Ø¨Ø§ÛØ± زوم کریں +pdfjs-zoom-in-button = + .title = اندر زوم کریں +pdfjs-zoom-in-button-label = اندر زوم کریں +pdfjs-zoom-select = + .title = زوم +pdfjs-presentation-mode-button = + .title = پیشکش موڈ میں Ú†Ù„Û’ جائیں +pdfjs-presentation-mode-button-label = پیشکش موڈ +pdfjs-open-file-button = + .title = مسل کھولیں +pdfjs-open-file-button-label = کھولیں +pdfjs-print-button = + .title = چھاپیں +pdfjs-print-button-label = چھاپیں + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = آلات +pdfjs-tools-button-label = آلات +pdfjs-first-page-button = + .title = Ù¾ÛÙ„Û’ ØµÙØ­Û پر جائیں +pdfjs-first-page-button-label = Ù¾ÛÙ„Û’ ØµÙØ­Û پر جائیں +pdfjs-last-page-button = + .title = آخری ØµÙØ­Û پر جائیں +pdfjs-last-page-button-label = آخری ØµÙØ­Û پر جائیں +pdfjs-page-rotate-cw-button = + .title = Ú¯Ú¾Ú‘ÛŒ وار گھمائیں +pdfjs-page-rotate-cw-button-label = Ú¯Ú¾Ú‘ÛŒ وار گھمائیں +pdfjs-page-rotate-ccw-button = + .title = ضد Ú¯Ú¾Ú‘ÛŒ وار گھمائیں +pdfjs-page-rotate-ccw-button-label = ضد Ú¯Ú¾Ú‘ÛŒ وار گھمائیں +pdfjs-cursor-text-select-tool-button = + .title = متن Ú©Û’ انتخاب Ú©Û’ ٹول Ú©Ùˆ ÙØ¹Ø§Ù„ بناے +pdfjs-cursor-text-select-tool-button-label = متن Ú©Û’ انتخاب کا Ø¢Ù„Û +pdfjs-cursor-hand-tool-button = + .title = Ûینڈ ٹول Ú©Ùˆ ÙØ¹Ø§Ù„ بناییں +pdfjs-cursor-hand-tool-button-label = ÛØ§ØªÚ¾ کا Ø¢Ù„Û +pdfjs-scroll-vertical-button = + .title = عمودی اسکرولنگ کا استعمال کریں +pdfjs-scroll-vertical-button-label = عمودی اسکرولنگ +pdfjs-scroll-horizontal-button = + .title = اÙÙ‚ÛŒ سکرولنگ کا استعمال کریں +pdfjs-scroll-horizontal-button-label = اÙÙ‚ÛŒ سکرولنگ +pdfjs-spread-none-button = + .title = ØµÙØ­Û پھیلانے میں شامل Ù†Û ÛÙˆÚº +pdfjs-spread-none-button-label = کوئی پھیلاؤ Ù†Ûیں +pdfjs-spread-odd-button-label = تاک پھیلاؤ +pdfjs-spread-even-button-label = Ø¬ÙØª پھیلاؤ + +## Document properties dialog + +pdfjs-document-properties-button = + .title = دستاویز خواص… +pdfjs-document-properties-button-label = دستاویز خواص… +pdfjs-document-properties-file-name = نام مسل: +pdfjs-document-properties-file-size = مسل سائز: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = عنوان: +pdfjs-document-properties-author = تخلیق کار: +pdfjs-document-properties-subject = موضوع: +pdfjs-document-properties-keywords = کلیدی Ø§Ù„ÙØ§Ø¸: +pdfjs-document-properties-creation-date = تخلیق Ú©ÛŒ تاریخ: +pdfjs-document-properties-modification-date = ترمیم Ú©ÛŒ تاریخ: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }ØŒ { $time } +pdfjs-document-properties-creator = تخلیق کار: +pdfjs-document-properties-producer = PDF پیدا کار: +pdfjs-document-properties-version = PDF ورژن: +pdfjs-document-properties-page-count = ØµÙØ­Û شمار: +pdfjs-document-properties-page-size = صÙÛ Ú©ÛŒ لمبائ: +pdfjs-document-properties-page-size-unit-inches = میں +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = عمودی انداز +pdfjs-document-properties-page-size-orientation-landscape = اÙقى انداز +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = خط +pdfjs-document-properties-page-size-name-legal = قانونی + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } { $name } { $orientation } + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = تیز ویب دیکھیں: +pdfjs-document-properties-linearized-yes = ÛØ§Úº +pdfjs-document-properties-linearized-no = Ù†Ûیں +pdfjs-document-properties-close-button = بند کریں + +## Print + +pdfjs-print-progress-message = چھاپنے کرنے Ú©Û’ لیے دستاویز تیار کیے جا رھے ھیں +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = *{ $progress }%* +pdfjs-print-progress-close-button = منسوخ کریں +pdfjs-printing-not-supported = تنبیÛ:چھاپنا اس براؤزر پر پوری طرح معاونت Ø´Ø¯Û Ù†Ûیں ÛÛ’Û” +pdfjs-printing-not-ready = تنبیÛ: PDF چھپائی Ú©Û’ لیے پوری طرح لوڈ Ù†Ûیں Ûوئی۔ + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = سلائیڈ ٹوگل کریں +pdfjs-toggle-sidebar-button-label = سلائیڈ ٹوگل کریں +pdfjs-document-outline-button = + .title = دستاویز Ú©ÛŒ سرخیاں دکھایں (تمام اشیاء وسیع / غائب کرنے Ú©Û’ لیے ڈبل Ú©Ù„Ú© کریں) +pdfjs-document-outline-button-label = دستاویز آؤٹ لائن +pdfjs-attachments-button = + .title = منسلکات دکھائیں +pdfjs-attachments-button-label = منسلکات +pdfjs-thumbs-button = + .title = تھمبنیل دکھائیں +pdfjs-thumbs-button-label = مجمل +pdfjs-findbar-button = + .title = دستاویز میں ڈھونڈیں +pdfjs-findbar-button-label = ڈھونڈیں + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = ØµÙØ­Û { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = ØµÙØ­Û’ کا مجمل { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = ڈھونڈیں + .placeholder = دستاویز… میں ڈھونڈیں +pdfjs-find-previous-button = + .title = Ùقرے کا پچھلا وقوع ڈھونڈیں +pdfjs-find-previous-button-label = پچھلا +pdfjs-find-next-button = + .title = Ùقرے کا Ø§Ú¯Ù„Û ÙˆÙ‚ÙˆØ¹ ڈھونڈیں +pdfjs-find-next-button-label = Ø¢Ú¯Û’ +pdfjs-find-highlight-checkbox = تمام نمایاں کریں +pdfjs-find-match-case-checkbox-label = Ø­Ø±ÙˆÙ Ù…Ø´Ø§Ø¨Û Ú©Ø±ÛŒÚº +pdfjs-find-entire-word-checkbox-label = تمام Ø§Ù„ÙØ§Ø¸ +pdfjs-find-reached-top = ØµÙØ­Û Ú©Û’ شروع پر Ù¾ÛÙ†Ú† گیا، نیچے سے جاری کیا +pdfjs-find-reached-bottom = ØµÙØ­Û Ú©Û’ اختتام پر Ù¾ÛÙ†Ú† گیا، اوپر سے جاری کیا +pdfjs-find-not-found = Ùقرا Ù†Ûیں ملا + +## Predefined zoom values + +pdfjs-page-scale-width = ØµÙØ­Û چوڑائی +pdfjs-page-scale-fit = ØµÙØ­Û Ùٹنگ +pdfjs-page-scale-auto = خودکار زوم +pdfjs-page-scale-actual = اصل سائز +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = ØµÙØ­Û { $page } + +## Loading indicator messages + +pdfjs-loading-error = PDF لوڈ کرتے وقت نقص Ø¢ گیا۔ +pdfjs-invalid-file-error = ناجائز یا خراب PDF مسل +pdfjs-missing-file-error = PDF مسل غائب ÛÛ’Û” +pdfjs-unexpected-response-error = غیرمتوقع پیش کار جواب +pdfjs-rendering-error = ØµÙØ­Û بناتے Ûوئے نقص Ø¢ گیا۔ + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }.{ $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } نوٹ] + +## Password + +pdfjs-password-label = PDF مسل کھولنے Ú©Û’ لیے پاس ورڈ داخل کریں. +pdfjs-password-invalid = ناجائز پاس ورڈ. براےؑ کرم Ø¯ÙˆØ¨Ø§Ø±Û Ú©ÙˆØ´Ø´ کریں. +pdfjs-password-ok-button = ٹھیک ÛÛ’ +pdfjs-password-cancel-button = منسوخ کریں +pdfjs-web-fonts-disabled = ویب ÙØ§Ù†Ù¹ نا اÛÙ„ Ûیں: شامل PDF ÙØ§Ù†Ù¹ استعمال کرنے میں ناکام۔ + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/uz/viewer.ftl b/public/assets/pdfjs/locale/uz/viewer.ftl new file mode 100755 index 0000000..fb82f22 --- /dev/null +++ b/public/assets/pdfjs/locale/uz/viewer.ftl @@ -0,0 +1,187 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Oldingi sahifa +pdfjs-previous-button-label = Oldingi +pdfjs-next-button = + .title = Keyingi sahifa +pdfjs-next-button-label = Keyingi +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = /{ $pagesCount } +pdfjs-zoom-out-button = + .title = Kichiklashtirish +pdfjs-zoom-out-button-label = Kichiklashtirish +pdfjs-zoom-in-button = + .title = Kattalashtirish +pdfjs-zoom-in-button-label = Kattalashtirish +pdfjs-zoom-select = + .title = Masshtab +pdfjs-presentation-mode-button = + .title = Namoyish usuliga oÊ»tish +pdfjs-presentation-mode-button-label = Namoyish usuli +pdfjs-open-file-button = + .title = Faylni ochish +pdfjs-open-file-button-label = Ochish +pdfjs-print-button = + .title = Chop qilish +pdfjs-print-button-label = Chop qilish + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Vositalar +pdfjs-tools-button-label = Vositalar +pdfjs-first-page-button = + .title = Birinchi sahifaga oÊ»tish +pdfjs-first-page-button-label = Birinchi sahifaga oÊ»tish +pdfjs-last-page-button = + .title = SoÊ»nggi sahifaga oÊ»tish +pdfjs-last-page-button-label = SoÊ»nggi sahifaga oÊ»tish +pdfjs-page-rotate-cw-button = + .title = Soat yoÊ»nalishi boÊ»yicha burish +pdfjs-page-rotate-cw-button-label = Soat yoÊ»nalishi boÊ»yicha burish +pdfjs-page-rotate-ccw-button = + .title = Soat yoÊ»nalishiga qarshi burish +pdfjs-page-rotate-ccw-button-label = Soat yoÊ»nalishiga qarshi burish + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Hujjat xossalari +pdfjs-document-properties-button-label = Hujjat xossalari +pdfjs-document-properties-file-name = Fayl nomi: +pdfjs-document-properties-file-size = Fayl hajmi: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +pdfjs-document-properties-title = Nomi: +pdfjs-document-properties-author = Muallifi: +pdfjs-document-properties-subject = Mavzusi: +pdfjs-document-properties-keywords = Kalit so‘zlar +pdfjs-document-properties-creation-date = Yaratilgan sanasi: +pdfjs-document-properties-modification-date = O‘zgartirilgan sanasi +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Yaratuvchi: +pdfjs-document-properties-producer = PDF ishlab chiqaruvchi: +pdfjs-document-properties-version = PDF versiyasi: +pdfjs-document-properties-page-count = Sahifa soni: + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + + +## + +pdfjs-document-properties-close-button = Yopish + +## Print + +pdfjs-printing-not-supported = Diqqat: chop qilish bruzer tomonidan toÊ»liq qoÊ»llab-quvvatlanmaydi. +pdfjs-printing-not-ready = Diqqat: PDF fayl chop qilish uchun toÊ»liq yuklanmadi. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Yon panelni yoqib/oÊ»chirib qoÊ»yish +pdfjs-toggle-sidebar-button-label = Yon panelni yoqib/oÊ»chirib qoÊ»yish +pdfjs-document-outline-button-label = Hujjat tuzilishi +pdfjs-attachments-button = + .title = Ilovalarni ko‘rsatish +pdfjs-attachments-button-label = Ilovalar +pdfjs-thumbs-button = + .title = Nishonchalarni koÊ»rsatish +pdfjs-thumbs-button-label = Nishoncha +pdfjs-findbar-button = + .title = Hujjat ichidan topish + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = { $page } sahifa +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = { $page } sahifa nishonchasi + +## Find panel button title and messages + +pdfjs-find-previous-button = + .title = SoÊ»zlardagi oldingi hodisani topish +pdfjs-find-previous-button-label = Oldingi +pdfjs-find-next-button = + .title = Iboradagi keyingi hodisani topish +pdfjs-find-next-button-label = Keyingi +pdfjs-find-highlight-checkbox = Barchasini ajratib koÊ»rsatish +pdfjs-find-match-case-checkbox-label = Katta-kichik harflarni farqlash +pdfjs-find-reached-top = Hujjatning boshigacha yetib keldik, pastdan davom ettiriladi +pdfjs-find-reached-bottom = Hujjatning oxiriga yetib kelindi, yuqoridan davom ettirladi +pdfjs-find-not-found = SoÊ»zlar topilmadi + +## Predefined zoom values + +pdfjs-page-scale-width = Sahifa eni +pdfjs-page-scale-fit = Sahifani moslashtirish +pdfjs-page-scale-auto = Avtomatik masshtab +pdfjs-page-scale-actual = Haqiqiy hajmi +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = PDF yuklanayotganda xato yuz berdi. +pdfjs-invalid-file-error = Xato yoki buzuq PDF fayli. +pdfjs-missing-file-error = PDF fayl kerak. +pdfjs-unexpected-response-error = Kutilmagan server javobi. +pdfjs-rendering-error = Sahifa renderlanayotganda xato yuz berdi. + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Annotation] + +## Password + +pdfjs-password-label = PDF faylni ochish uchun parolni kiriting. +pdfjs-password-invalid = Parol - notoÊ»gÊ»ri. Qaytadan urinib koÊ»ring. +pdfjs-password-ok-button = OK +pdfjs-web-fonts-disabled = Veb shriftlar oÊ»chirilgan: ichki PDF shriftlardan foydalanib boÊ»lmmaydi. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/vi/viewer.ftl b/public/assets/pdfjs/locale/vi/viewer.ftl new file mode 100755 index 0000000..af1291f --- /dev/null +++ b/public/assets/pdfjs/locale/vi/viewer.ftl @@ -0,0 +1,503 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Trang trước +pdfjs-previous-button-label = Trước +pdfjs-next-button = + .title = Trang Sau +pdfjs-next-button-label = Tiếp +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Trang +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = trên { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } trên { $pagesCount }) +pdfjs-zoom-out-button = + .title = Thu nhá» +pdfjs-zoom-out-button-label = Thu nhá» +pdfjs-zoom-in-button = + .title = Phóng to +pdfjs-zoom-in-button-label = Phóng to +pdfjs-zoom-select = + .title = Thu phóng +pdfjs-presentation-mode-button = + .title = Chuyển sang chế độ trình chiếu +pdfjs-presentation-mode-button-label = Chế độ trình chiếu +pdfjs-open-file-button = + .title = Mở tập tin +pdfjs-open-file-button-label = Mở tập tin +pdfjs-print-button = + .title = In +pdfjs-print-button-label = In +pdfjs-save-button = + .title = Lưu +pdfjs-save-button-label = Lưu +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = Tải xuống +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Tải xuống +pdfjs-bookmark-button = + .title = Trang hiện tại (xem URL từ trang hiện tại) +pdfjs-bookmark-button-label = Trang hiện tại + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Công cụ +pdfjs-tools-button-label = Công cụ +pdfjs-first-page-button = + .title = Vá» trang đầu +pdfjs-first-page-button-label = Vá» trang đầu +pdfjs-last-page-button = + .title = Äến trang cuối +pdfjs-last-page-button-label = Äến trang cuối +pdfjs-page-rotate-cw-button = + .title = Xoay theo chiá»u kim đồng hồ +pdfjs-page-rotate-cw-button-label = Xoay theo chiá»u kim đồng hồ +pdfjs-page-rotate-ccw-button = + .title = Xoay ngược chiá»u kim đồng hồ +pdfjs-page-rotate-ccw-button-label = Xoay ngược chiá»u kim đồng hồ +pdfjs-cursor-text-select-tool-button = + .title = Kích hoạt công cụ chá»n vùng văn bản +pdfjs-cursor-text-select-tool-button-label = Công cụ chá»n vùng văn bản +pdfjs-cursor-hand-tool-button = + .title = Kích hoạt công cụ con trá» +pdfjs-cursor-hand-tool-button-label = Công cụ con trá» +pdfjs-scroll-page-button = + .title = Sá»­ dụng cuá»™n trang hiện tại +pdfjs-scroll-page-button-label = Cuá»™n trang hiện tại +pdfjs-scroll-vertical-button = + .title = Sá»­ dụng cuá»™n dá»c +pdfjs-scroll-vertical-button-label = Cuá»™n dá»c +pdfjs-scroll-horizontal-button = + .title = Sá»­ dụng cuá»™n ngang +pdfjs-scroll-horizontal-button-label = Cuá»™n ngang +pdfjs-scroll-wrapped-button = + .title = Sá»­ dụng cuá»™n ngắt dòng +pdfjs-scroll-wrapped-button-label = Cuá»™n ngắt dòng +pdfjs-spread-none-button = + .title = Không nối rá»™ng trang +pdfjs-spread-none-button-label = Không có phân cách +pdfjs-spread-odd-button = + .title = Nối trang bài bắt đầu vá»›i các trang được đánh số lẻ +pdfjs-spread-odd-button-label = Phân cách theo số lẻ +pdfjs-spread-even-button = + .title = Nối trang bài bắt đầu vá»›i các trang được đánh số chẵn +pdfjs-spread-even-button-label = Phân cách theo số chẵn + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Thuá»™c tính cá»§a tài liệu… +pdfjs-document-properties-button-label = Thuá»™c tính cá»§a tài liệu… +pdfjs-document-properties-file-name = Tên tập tin: +pdfjs-document-properties-file-size = Kích thước: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } byte) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } byte) +pdfjs-document-properties-title = Tiêu Ä‘á»: +pdfjs-document-properties-author = Tác giả: +pdfjs-document-properties-subject = Chá»§ Ä‘á»: +pdfjs-document-properties-keywords = Từ khóa: +pdfjs-document-properties-creation-date = Ngày tạo: +pdfjs-document-properties-modification-date = Ngày sá»­a đổi: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Ngưá»i tạo: +pdfjs-document-properties-producer = Phần má»m tạo PDF: +pdfjs-document-properties-version = Phiên bản PDF: +pdfjs-document-properties-page-count = Tổng số trang: +pdfjs-document-properties-page-size = Kích thước trang: +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = khổ dá»c +pdfjs-document-properties-page-size-orientation-landscape = khổ ngang +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Thư +pdfjs-document-properties-page-size-name-legal = Pháp lý + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = Xem nhanh trên web: +pdfjs-document-properties-linearized-yes = Có +pdfjs-document-properties-linearized-no = Không +pdfjs-document-properties-close-button = Ãóng + +## Print + +pdfjs-print-progress-message = Chuẩn bị trang để in… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Há»§y bá» +pdfjs-printing-not-supported = Cảnh báo: In ấn không được há»— trợ đầy đủ ở trình duyệt này. +pdfjs-printing-not-ready = Cảnh báo: PDF chưa được tải hết để in. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Bật/Tắt thanh lá» +pdfjs-toggle-sidebar-notification-button = + .title = Bật tắt thanh lá» (tài liệu bao gồm bản phác thảo/tập tin đính kèm/lá»›p) +pdfjs-toggle-sidebar-button-label = Bật/Tắt thanh lá» +pdfjs-document-outline-button = + .title = Hiển thị tài liệu phác thảo (nhấp đúp vào để mở rá»™ng/thu gá»n tất cả các mục) +pdfjs-document-outline-button-label = Bản phác tài liệu +pdfjs-attachments-button = + .title = Hiện ná»™i dung đính kèm +pdfjs-attachments-button-label = Ná»™i dung đính kèm +pdfjs-layers-button = + .title = Hiển thị các lá»›p (nhấp đúp để đặt lại tất cả các lá»›p vá» trạng thái mặc định) +pdfjs-layers-button-label = Lá»›p +pdfjs-thumbs-button = + .title = Hiển thị ảnh thu nhá» +pdfjs-thumbs-button-label = Ảnh thu nhá» +pdfjs-current-outline-item-button = + .title = Tìm mục phác thảo hiện tại +pdfjs-current-outline-item-button-label = Mục phác thảo hiện tại +pdfjs-findbar-button = + .title = Tìm trong tài liệu +pdfjs-findbar-button-label = Tìm +pdfjs-additional-layers = Các lá»›p bổ sung + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Trang { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Ảnh thu nhá» cá»§a trang { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Tìm + .placeholder = Tìm trong tài liệu… +pdfjs-find-previous-button = + .title = Tìm cụm từ ở phần trước +pdfjs-find-previous-button-label = Trước +pdfjs-find-next-button = + .title = Tìm cụm từ ở phần sau +pdfjs-find-next-button-label = Tiếp +pdfjs-find-highlight-checkbox = Äánh dấu tất cả +pdfjs-find-match-case-checkbox-label = Phân biệt hoa, thưá»ng +pdfjs-find-match-diacritics-checkbox-label = Khá»›p dấu phụ +pdfjs-find-entire-word-checkbox-label = Toàn bá»™ từ +pdfjs-find-reached-top = Äã đến phần đầu tài liệu, quay trở lại từ cuối +pdfjs-find-reached-bottom = Äã đến phần cuối cá»§a tài liệu, quay trở lại từ đầu +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = { $current } trên { $total } kết quả +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = Tìm thấy hÆ¡n { $limit } kết quả +pdfjs-find-not-found = Không tìm thấy cụm từ này + +## Predefined zoom values + +pdfjs-page-scale-width = Vừa chiá»u rá»™ng +pdfjs-page-scale-fit = Vừa chiá»u cao +pdfjs-page-scale-auto = Tá»± động chá»n kích thước +pdfjs-page-scale-actual = Kích thước thá»±c +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = Trang { $page } + +## Loading indicator messages + +pdfjs-loading-error = Lá»—i khi tải tài liệu PDF. +pdfjs-invalid-file-error = Tập tin PDF há»ng hoặc không hợp lệ. +pdfjs-missing-file-error = Thiếu tập tin PDF. +pdfjs-unexpected-response-error = Máy chá»§ có phản hồi lạ. +pdfjs-rendering-error = Lá»—i khi hiển thị trang. + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date }, { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Chú thích] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = Nhập mật khẩu để mở tập tin PDF này. +pdfjs-password-invalid = Mật khẩu không đúng. Vui lòng thá»­ lại. +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Há»§y bá» +pdfjs-web-fonts-disabled = Phông chữ Web bị vô hiệu hóa: không thể sá»­ dụng các phông chữ PDF được nhúng. + +## Editing + +pdfjs-editor-free-text-button = + .title = Văn bản +pdfjs-editor-free-text-button-label = Văn bản +pdfjs-editor-ink-button = + .title = Vẽ +pdfjs-editor-ink-button-label = Vẽ +pdfjs-editor-stamp-button = + .title = Thêm hoặc chỉnh sá»­a hình ảnh +pdfjs-editor-stamp-button-label = Thêm hoặc chỉnh sá»­a hình ảnh +pdfjs-editor-highlight-button = + .title = Äánh dấu +pdfjs-editor-highlight-button-label = Äánh dấu +pdfjs-highlight-floating-button1 = + .title = Äánh dấu + .aria-label = Äánh dấu +pdfjs-highlight-floating-button-label = Äánh dấu + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Xóa bản vẽ +pdfjs-editor-remove-freetext-button = + .title = Xóa văn bản +pdfjs-editor-remove-stamp-button = + .title = Xóa ảnh +pdfjs-editor-remove-highlight-button = + .title = Xóa phần đánh dấu + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = Màu +pdfjs-editor-free-text-size-input = Kích cỡ +pdfjs-editor-ink-color-input = Màu +pdfjs-editor-ink-thickness-input = Äá»™ dày +pdfjs-editor-ink-opacity-input = Äộ mờ +pdfjs-editor-stamp-add-image-button = + .title = Thêm hình ảnh +pdfjs-editor-stamp-add-image-button-label = Thêm hình ảnh +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Äá»™ dày +pdfjs-editor-free-highlight-thickness-title = + .title = Thay đổi độ dày khi đánh dấu các mục không phải là văn bản +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = Trình chỉnh sá»­a văn bản + .default-content = Bắt đầu nhập… +pdfjs-free-text = + .aria-label = Trình sá»­a văn bản +pdfjs-free-text-default-content = Bắt đầu nhập… +pdfjs-ink = + .aria-label = Trình sá»­a nét vẽ +pdfjs-ink-canvas = + .aria-label = Hình ảnh do ngưá»i dùng tạo + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = Văn bản thay thế +pdfjs-editor-alt-text-edit-button = + .aria-label = Chỉnh sá»­a văn bản thay thế +pdfjs-editor-alt-text-edit-button-label = Chỉnh sá»­a văn bản thay thế +pdfjs-editor-alt-text-dialog-label = Chá»n má»™t lá»±a chá»n +pdfjs-editor-alt-text-dialog-description = Văn bản thay thế sẽ hữu ích khi má»i ngưá»i không thể thấy hình ảnh hoặc khi hình ảnh không tải. +pdfjs-editor-alt-text-add-description-label = Thêm má»™t mô tả +pdfjs-editor-alt-text-add-description-description = Hãy nhắm tá»›i 1-2 câu mô tả chá»§ Ä‘á», bối cảnh hoặc hành động. +pdfjs-editor-alt-text-mark-decorative-label = Äánh dấu là trang trí +pdfjs-editor-alt-text-mark-decorative-description = Äiá»u này được sá»­ dụng cho các hình ảnh trang trí, như đưá»ng viá»n hoặc watermark. +pdfjs-editor-alt-text-cancel-button = Há»§y bá» +pdfjs-editor-alt-text-save-button = Lưu +pdfjs-editor-alt-text-decorative-tooltip = Äã đánh dấu là trang trí +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Ví dụ: “Má»™t thanh niên ngồi xuống bàn để thưởng thức má»™t bữa ăn†+# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = Văn bản thay thế + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = Trên cùng bên trái — thay đổi kích thước +pdfjs-editor-resizer-label-top-middle = Trên cùng ở giữa — thay đổi kích thước +pdfjs-editor-resizer-label-top-right = Trên cùng bên phải — thay đổi kích thước +pdfjs-editor-resizer-label-middle-right = Ở giữa bên phải — thay đổi kích thước +pdfjs-editor-resizer-label-bottom-right = Dưới cùng bên phải — thay đổi kích thước +pdfjs-editor-resizer-label-bottom-middle = Ở giữa dưới cùng — thay đổi kích thước +pdfjs-editor-resizer-label-bottom-left = Góc dưới bên trái — thay đổi kích thước +pdfjs-editor-resizer-label-middle-left = Ở giữa bên trái — thay đổi kích thước +pdfjs-editor-resizer-top-left = + .aria-label = Trên cùng bên trái — thay đổi kích thước +pdfjs-editor-resizer-top-middle = + .aria-label = Trên cùng ở giữa — thay đổi kích thước +pdfjs-editor-resizer-top-right = + .aria-label = Trên cùng bên phải — thay đổi kích thước +pdfjs-editor-resizer-middle-right = + .aria-label = Ở giữa bên phải — thay đổi kích thước +pdfjs-editor-resizer-bottom-right = + .aria-label = Dưới cùng bên phải — thay đổi kích thước +pdfjs-editor-resizer-bottom-middle = + .aria-label = Ở giữa dưới cùng — thay đổi kích thước +pdfjs-editor-resizer-bottom-left = + .aria-label = Góc dưới bên trái — thay đổi kích thước +pdfjs-editor-resizer-middle-left = + .aria-label = Ở giữa bên trái — thay đổi kích thước + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Màu đánh dấu +pdfjs-editor-colorpicker-button = + .title = Thay đổi màu +pdfjs-editor-colorpicker-dropdown = + .aria-label = Lá»±a chá»n màu sắc +pdfjs-editor-colorpicker-yellow = + .title = Vàng +pdfjs-editor-colorpicker-green = + .title = Xanh lục +pdfjs-editor-colorpicker-blue = + .title = Xanh dương +pdfjs-editor-colorpicker-pink = + .title = Hồng +pdfjs-editor-colorpicker-red = + .title = Äá» + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Hiện tất cả +pdfjs-editor-highlight-show-all-button = + .title = Hiện tất cả + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Chỉnh sá»­a văn bản thay thế (mô tả hình ảnh) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Thêm văn bản thay thế (mô tả hình ảnh) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Viết mô tả cá»§a bạn ở đây… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Mô tả ngắn gá»n dành cho ngưá»i không xem được ảnh hoặc khi không thể tải ảnh. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Văn bản thay thế này được tạo tá»± động và có thể không chính xác. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Tìm hiểu thêm +pdfjs-editor-new-alt-text-create-automatically-button-label = Tạo văn bản thay thế tá»± động +pdfjs-editor-new-alt-text-not-now-button = Không phải bây giá» +pdfjs-editor-new-alt-text-error-title = Không thể tạo tá»± động văn bản thay thế +pdfjs-editor-new-alt-text-error-description = Vui lòng viết văn bản thay thế cá»§a riêng bạn hoặc thá»­ lại sau. +pdfjs-editor-new-alt-text-error-close-button = Äóng +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Äang tải xuống mô hình AI văn bản thay thế ({ $downloadedSize } trong số { $totalSize } MB) + .aria-valuetext = Äang tải xuống mô hình AI văn bản thay thế ({ $downloadedSize } trong số { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = Äã thêm văn bản thay thế +pdfjs-editor-new-alt-text-added-button-label = Äã thêm văn bản thay thế +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = Thiếu văn bản thay thế +pdfjs-editor-new-alt-text-missing-button-label = Thiếu văn bản thay thế +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = Xem lại văn bản thay thế +pdfjs-editor-new-alt-text-to-review-button-label = Xem lại văn bản thay thế +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = ÄÆ°á»£c tạo tá»± động: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Cài đặt văn bản thay thế cá»§a hình ảnh +pdfjs-image-alt-text-settings-button-label = Cài đặt văn bản thay thế cá»§a hình ảnh +pdfjs-editor-alt-text-settings-dialog-label = Cài đặt văn bản thay thế cá»§a hình ảnh +pdfjs-editor-alt-text-settings-automatic-title = Văn bản thay thế tá»± động +pdfjs-editor-alt-text-settings-create-model-button-label = Tạo văn bản thay thế tá»± động +pdfjs-editor-alt-text-settings-create-model-description = Äá» xuất mô tả giúp ích cho những ngưá»i không xem được ảnh hoặc khi không thể tải ảnh. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Mô hình AI văn bản khác ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Chạy cục bá»™ trên thiết bị cá»§a bạn để dữ liệu cá»§a bạn luôn ở chế độ riêng tư. Bắt buá»™c đối vá»›i văn bản thay thế tá»± động. +pdfjs-editor-alt-text-settings-delete-model-button = Xóa +pdfjs-editor-alt-text-settings-download-model-button = Tải xuống +pdfjs-editor-alt-text-settings-downloading-model-button = Äang tải xuống… +pdfjs-editor-alt-text-settings-editor-title = Trình soạn thảo văn bản thay thế +pdfjs-editor-alt-text-settings-show-dialog-button-label = Hiển thị ngay trình soạn thảo văn bản thay thế khi thêm hình ảnh +pdfjs-editor-alt-text-settings-show-dialog-description = Giúp bạn đảm bảo tất cả hình ảnh cá»§a bạn Ä‘á»u có văn bản thay thế. +pdfjs-editor-alt-text-settings-close-button = Äóng + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = Äã xóa đánh dấu +pdfjs-editor-undo-bar-message-freetext = Äã xóa văn bản +pdfjs-editor-undo-bar-message-ink = Äã xóa bản vẽ +pdfjs-editor-undo-bar-message-stamp = Äã xóa hình ảnh +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = { $count } chú thích đã bị xóa +pdfjs-editor-undo-bar-undo-button = + .title = Hoàn tác +pdfjs-editor-undo-bar-undo-button-label = Hoàn tác +pdfjs-editor-undo-bar-close-button = + .title = Äóng +pdfjs-editor-undo-bar-close-button-label = Äóng diff --git a/public/assets/pdfjs/locale/wo/viewer.ftl b/public/assets/pdfjs/locale/wo/viewer.ftl new file mode 100755 index 0000000..d66c459 --- /dev/null +++ b/public/assets/pdfjs/locale/wo/viewer.ftl @@ -0,0 +1,127 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Xët wi jiitu +pdfjs-previous-button-label = Bi jiitu +pdfjs-next-button = + .title = Xët wi ci topp +pdfjs-next-button-label = Bi ci topp +pdfjs-zoom-out-button = + .title = Wàññi +pdfjs-zoom-out-button-label = Wàññi +pdfjs-zoom-in-button = + .title = Yaatal +pdfjs-zoom-in-button-label = Yaatal +pdfjs-zoom-select = + .title = YambalaÅ‹ +pdfjs-presentation-mode-button = + .title = Wañarñil ci anamu wone +pdfjs-presentation-mode-button-label = Anamu Wone +pdfjs-open-file-button = + .title = Ubbi benn dencukaay +pdfjs-open-file-button-label = Ubbi +pdfjs-print-button = + .title = Móol +pdfjs-print-button-label = Móol + +## Secondary toolbar and context menu + + +## Document properties dialog + +pdfjs-document-properties-title = Bopp: + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + + +## + + +## Print + +pdfjs-printing-not-supported = Artu: Joowkat bii nanguwul lool mool. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-thumbs-button = + .title = Wone nataal yu ndaw yi +pdfjs-thumbs-button-label = Nataal yu ndaw yi +pdfjs-findbar-button = + .title = Gis ci biir jukki bi +pdfjs-findbar-button-label = Wut + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Xët { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Wiñet bu xët { $page } + +## Find panel button title and messages + +pdfjs-find-previous-button = + .title = Seet beneen kaddu bu ni mel te jiitu +pdfjs-find-previous-button-label = Bi jiitu +pdfjs-find-next-button = + .title = Seet beneen kaddu bu ni mel +pdfjs-find-next-button-label = Bi ci topp +pdfjs-find-highlight-checkbox = Melaxal lépp +pdfjs-find-match-case-checkbox-label = Sàmm jëmmalin wi +pdfjs-find-reached-top = Jot nañu ndorteel xët wi, kontine dale ko ci suuf +pdfjs-find-reached-bottom = Jot nañu jeexitalu xët wi, kontine ci ndorte +pdfjs-find-not-found = Gisiñu kaddu gi + +## Predefined zoom values + +pdfjs-page-scale-width = Yaatuwaay bu mët +pdfjs-page-scale-fit = Xët lëmm +pdfjs-page-scale-auto = YambalaÅ‹ ci saa si +pdfjs-page-scale-actual = Dayo bi am + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = Am na njumte ci yebum dencukaay PDF bi. +pdfjs-invalid-file-error = Dencukaay PDF bi baaxul walla mu sankar. +pdfjs-rendering-error = Am njumte bu am bi xët bi di wonewu. + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [Karmat { $type }] + +## Password + +pdfjs-password-ok-button = OK +pdfjs-password-cancel-button = Neenal + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/xh/viewer.ftl b/public/assets/pdfjs/locale/xh/viewer.ftl new file mode 100755 index 0000000..0798887 --- /dev/null +++ b/public/assets/pdfjs/locale/xh/viewer.ftl @@ -0,0 +1,212 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = Iphepha langaphambili +pdfjs-previous-button-label = Okwangaphambili +pdfjs-next-button = + .title = Iphepha elilandelayo +pdfjs-next-button-label = Okulandelayo +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = Iphepha +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = kwali- { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } kwali { $pagesCount }) +pdfjs-zoom-out-button = + .title = Bhekelisela Kudana +pdfjs-zoom-out-button-label = Bhekelisela Kudana +pdfjs-zoom-in-button = + .title = Sondeza Kufuphi +pdfjs-zoom-in-button-label = Sondeza Kufuphi +pdfjs-zoom-select = + .title = Yandisa / Nciphisa +pdfjs-presentation-mode-button = + .title = Tshintshela kwimo yonikezelo +pdfjs-presentation-mode-button-label = Imo yonikezelo +pdfjs-open-file-button = + .title = Vula Ifayile +pdfjs-open-file-button-label = Vula +pdfjs-print-button = + .title = Printa +pdfjs-print-button-label = Printa + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = Izixhobo zemiyalelo +pdfjs-tools-button-label = Izixhobo zemiyalelo +pdfjs-first-page-button = + .title = Yiya kwiphepha lokuqala +pdfjs-first-page-button-label = Yiya kwiphepha lokuqala +pdfjs-last-page-button = + .title = Yiya kwiphepha lokugqibela +pdfjs-last-page-button-label = Yiya kwiphepha lokugqibela +pdfjs-page-rotate-cw-button = + .title = Jikelisa ngasekunene +pdfjs-page-rotate-cw-button-label = Jikelisa ngasekunene +pdfjs-page-rotate-ccw-button = + .title = Jikelisa ngasekhohlo +pdfjs-page-rotate-ccw-button-label = Jikelisa ngasekhohlo +pdfjs-cursor-text-select-tool-button = + .title = Vumela iSixhobo sokuKhetha iTeksti +pdfjs-cursor-text-select-tool-button-label = ISixhobo sokuKhetha iTeksti +pdfjs-cursor-hand-tool-button = + .title = Yenza iSixhobo seSandla siSebenze +pdfjs-cursor-hand-tool-button-label = ISixhobo seSandla + +## Document properties dialog + +pdfjs-document-properties-button = + .title = Iipropati zoxwebhu… +pdfjs-document-properties-button-label = Iipropati zoxwebhu… +pdfjs-document-properties-file-name = Igama lefayile: +pdfjs-document-properties-file-size = Isayizi yefayile: +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB (iibhayiti{ $size_b }) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB (iibhayithi{ $size_b }) +pdfjs-document-properties-title = Umxholo: +pdfjs-document-properties-author = Umbhali: +pdfjs-document-properties-subject = Umbandela: +pdfjs-document-properties-keywords = Amagama aphambili: +pdfjs-document-properties-creation-date = Umhla wokwenziwa kwayo: +pdfjs-document-properties-modification-date = Umhla wokulungiswa kwayo: +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = Umntu oyenzileyo: +pdfjs-document-properties-producer = Umvelisi we-PDF: +pdfjs-document-properties-version = Uhlelo lwe-PDF: +pdfjs-document-properties-page-count = Inani lamaphepha: + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + + +## + +pdfjs-document-properties-close-button = Vala + +## Print + +pdfjs-print-progress-message = Ilungisa uxwebhu ukuze iprinte… +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = Rhoxisa +pdfjs-printing-not-supported = Isilumkiso: Ukuprinta akuxhaswa ngokupheleleyo yile bhrawuza. +pdfjs-printing-not-ready = Isilumkiso: IPDF ayihlohlwanga ngokupheleleyo ukwenzela ukuprinta. + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = Togola ngebha eseCaleni +pdfjs-toggle-sidebar-button-label = Togola ngebha eseCaleni +pdfjs-document-outline-button = + .title = Bonisa uLwandlalo loXwebhu (cofa kabini ukuze wandise/diliza zonke izinto) +pdfjs-document-outline-button-label = Isishwankathelo soxwebhu +pdfjs-attachments-button = + .title = Bonisa iziqhotyoshelwa +pdfjs-attachments-button-label = Iziqhoboshelo +pdfjs-thumbs-button = + .title = Bonisa ukrobiso kumfanekiso +pdfjs-thumbs-button-label = Ukrobiso kumfanekiso +pdfjs-findbar-button = + .title = Fumana kuXwebhu +pdfjs-findbar-button-label = Fumana + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = Iphepha { $page } +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = Ukrobiso kumfanekiso wephepha { $page } + +## Find panel button title and messages + +pdfjs-find-input = + .title = Fumana + .placeholder = Fumana kuXwebhu… +pdfjs-find-previous-button = + .title = Fumanisa isenzeko sangaphambili sebinzana lamagama +pdfjs-find-previous-button-label = Okwangaphambili +pdfjs-find-next-button = + .title = Fumanisa isenzeko esilandelayo sebinzana lamagama +pdfjs-find-next-button-label = Okulandelayo +pdfjs-find-highlight-checkbox = Qaqambisa konke +pdfjs-find-match-case-checkbox-label = Tshatisa ngobukhulu bukanobumba +pdfjs-find-reached-top = Ufike ngaphezulu ephepheni, kusukwa ngezantsi +pdfjs-find-reached-bottom = Ufike ekupheleni kwephepha, kusukwa ngaphezulu +pdfjs-find-not-found = Ibinzana alifunyenwanga + +## Predefined zoom values + +pdfjs-page-scale-width = Ububanzi bephepha +pdfjs-page-scale-fit = Ukulinganiswa kwephepha +pdfjs-page-scale-auto = Ukwandisa/Ukunciphisa Ngokwayo +pdfjs-page-scale-actual = Ubungakanani bokwenene +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + + +## Loading indicator messages + +pdfjs-loading-error = Imposiso yenzekile xa kulayishwa i-PDF. +pdfjs-invalid-file-error = Ifayile ye-PDF engeyiyo okanye eyonakalisiweyo. +pdfjs-missing-file-error = Ifayile ye-PDF edukileyo. +pdfjs-unexpected-response-error = Impendulo yeseva engalindelekanga. +pdfjs-rendering-error = Imposiso yenzekile xa bekunikezelwa iphepha. + +## Annotations + +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } Ubhalo-nqaku] + +## Password + +pdfjs-password-label = Faka ipasiwedi ukuze uvule le fayile yePDF. +pdfjs-password-invalid = Ipasiwedi ayisebenzi. Nceda uzame kwakhona. +pdfjs-password-ok-button = KULUNGILE +pdfjs-password-cancel-button = Rhoxisa +pdfjs-web-fonts-disabled = Iifonti zewebhu ziqhwalelisiwe: ayikwazi ukusebenzisa iifonti ze-PDF ezincanyathelisiweyo. + +## Editing + + +## Alt-text dialog + + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + diff --git a/public/assets/pdfjs/locale/zh-CN/viewer.ftl b/public/assets/pdfjs/locale/zh-CN/viewer.ftl new file mode 100755 index 0000000..8fe9a6a --- /dev/null +++ b/public/assets/pdfjs/locale/zh-CN/viewer.ftl @@ -0,0 +1,503 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = 上一页 +pdfjs-previous-button-label = 上一页 +pdfjs-next-button = + .title = 下一页 +pdfjs-next-button-label = 下一页 +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = é¡µé¢ +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = / { $pagesCount } +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = ({ $pageNumber } / { $pagesCount }) +pdfjs-zoom-out-button = + .title = ç¼©å° +pdfjs-zoom-out-button-label = ç¼©å° +pdfjs-zoom-in-button = + .title = 放大 +pdfjs-zoom-in-button-label = 放大 +pdfjs-zoom-select = + .title = 缩放 +pdfjs-presentation-mode-button = + .title = 切æ¢åˆ°æ¼”ç¤ºæ¨¡å¼ +pdfjs-presentation-mode-button-label = æ¼”ç¤ºæ¨¡å¼ +pdfjs-open-file-button = + .title = 打开文件 +pdfjs-open-file-button-label = 打开 +pdfjs-print-button = + .title = æ‰“å° +pdfjs-print-button-label = æ‰“å° +pdfjs-save-button = + .title = ä¿å­˜ +pdfjs-save-button-label = ä¿å­˜ +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = 下载 +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = 下载 +pdfjs-bookmark-button = + .title = 当å‰é¡µé¢ï¼ˆåœ¨å½“å‰é¡µé¢æŸ¥çœ‹ URL) +pdfjs-bookmark-button-label = 当å‰é¡µé¢ + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = 工具 +pdfjs-tools-button-label = 工具 +pdfjs-first-page-button = + .title = 转到第一页 +pdfjs-first-page-button-label = 转到第一页 +pdfjs-last-page-button = + .title = 转到最åŽä¸€é¡µ +pdfjs-last-page-button-label = 转到最åŽä¸€é¡µ +pdfjs-page-rotate-cw-button = + .title = 顺时针旋转 +pdfjs-page-rotate-cw-button-label = 顺时针旋转 +pdfjs-page-rotate-ccw-button = + .title = 逆时针旋转 +pdfjs-page-rotate-ccw-button-label = 逆时针旋转 +pdfjs-cursor-text-select-tool-button = + .title = å¯ç”¨æ–‡æœ¬é€‰æ‹©å·¥å…· +pdfjs-cursor-text-select-tool-button-label = 文本选择工具 +pdfjs-cursor-hand-tool-button = + .title = å¯ç”¨æ‰‹å½¢å·¥å…· +pdfjs-cursor-hand-tool-button-label = 手形工具 +pdfjs-scroll-page-button = + .title = ä½¿ç”¨é¡µé¢æ»šåЍ +pdfjs-scroll-page-button-label = 页颿»šåЍ +pdfjs-scroll-vertical-button = + .title = 使用垂直滚动 +pdfjs-scroll-vertical-button-label = 垂直滚动 +pdfjs-scroll-horizontal-button = + .title = 使用水平滚动 +pdfjs-scroll-horizontal-button-label = 水平滚动 +pdfjs-scroll-wrapped-button = + .title = 使用平铺滚动 +pdfjs-scroll-wrapped-button-label = 平铺滚动 +pdfjs-spread-none-button = + .title = ä¸åŠ å…¥è¡”æŽ¥é¡µ +pdfjs-spread-none-button-label = å•页视图 +pdfjs-spread-odd-button = + .title = 加入衔接页使奇数页作为起始页 +pdfjs-spread-odd-button-label = åŒé¡µè§†å›¾ +pdfjs-spread-even-button = + .title = åŠ å…¥è¡”æŽ¥é¡µä½¿å¶æ•°é¡µä½œä¸ºèµ·å§‹é¡µ +pdfjs-spread-even-button-label = 书ç±è§†å›¾ + +## Document properties dialog + +pdfjs-document-properties-button = + .title = 文档属性… +pdfjs-document-properties-button-label = 文档属性… +pdfjs-document-properties-file-name = 文件å: +pdfjs-document-properties-file-size = 文件大å°: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB({ $b } 字节) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB({ $b } 字节) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } 字节) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } 字节) +pdfjs-document-properties-title = 标题: +pdfjs-document-properties-author = 作者: +pdfjs-document-properties-subject = 主题: +pdfjs-document-properties-keywords = 关键è¯: +pdfjs-document-properties-creation-date = 创建日期: +pdfjs-document-properties-modification-date = 修改日期: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date }, { $time } +pdfjs-document-properties-creator = 创建者: +pdfjs-document-properties-producer = PDF 生æˆå™¨ï¼š +pdfjs-document-properties-version = PDF 版本: +pdfjs-document-properties-page-count = 页数: +pdfjs-document-properties-page-size = 页é¢å¤§å°ï¼š +pdfjs-document-properties-page-size-unit-inches = 英寸 +pdfjs-document-properties-page-size-unit-millimeters = 毫米 +pdfjs-document-properties-page-size-orientation-portrait = çºµå‘ +pdfjs-document-properties-page-size-orientation-landscape = æ¨ªå‘ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit }({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit }({ $name },{ $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = 快速 Web 视图: +pdfjs-document-properties-linearized-yes = 是 +pdfjs-document-properties-linearized-no = å¦ +pdfjs-document-properties-close-button = 关闭 + +## Print + +pdfjs-print-progress-message = æ­£åœ¨å‡†å¤‡æ‰“å°æ–‡æ¡£â€¦ +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = å–æ¶ˆ +pdfjs-printing-not-supported = 警告:此æµè§ˆå™¨å°šæœªå®Œæ•´æ”¯æŒæ‰“å°åŠŸèƒ½ã€‚ +pdfjs-printing-not-ready = 警告:此 PDF 未完æˆåŠ è½½ï¼Œæ— æ³•æ‰“å°ã€‚ + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = 切æ¢ä¾§æ  +pdfjs-toggle-sidebar-notification-button = + .title = 切æ¢ä¾§æ ï¼ˆæ–‡æ¡£æ‰€å«çš„大纲/附件/图层) +pdfjs-toggle-sidebar-button-label = 切æ¢ä¾§æ  +pdfjs-document-outline-button = + .title = 显示文档大纲(åŒå‡»å±•å¼€/æŠ˜å æ‰€æœ‰é¡¹ï¼‰ +pdfjs-document-outline-button-label = 文档大纲 +pdfjs-attachments-button = + .title = 显示附件 +pdfjs-attachments-button-label = 附件 +pdfjs-layers-button = + .title = 显示图层(åŒå‡»å³å¯å°†æ‰€æœ‰å›¾å±‚é‡ç½®ä¸ºé»˜è®¤çжæ€ï¼‰ +pdfjs-layers-button-label = 图层 +pdfjs-thumbs-button = + .title = 显示缩略图 +pdfjs-thumbs-button-label = 缩略图 +pdfjs-current-outline-item-button = + .title = 查找当å‰å¤§çº²é¡¹ç›® +pdfjs-current-outline-item-button-label = 当å‰å¤§çº²é¡¹ç›® +pdfjs-findbar-button = + .title = 在文档中查找 +pdfjs-findbar-button-label = 查找 +pdfjs-additional-layers = 其他图层 + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = 第 { $page } 页 +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = é¡µé¢ { $page } 的缩略图 + +## Find panel button title and messages + +pdfjs-find-input = + .title = 查找 + .placeholder = 在文档中查找… +pdfjs-find-previous-button = + .title = 查找è¯è¯­ä¸Šä¸€æ¬¡å‡ºçŽ°çš„ä½ç½® +pdfjs-find-previous-button-label = 上一页 +pdfjs-find-next-button = + .title = 查找è¯è¯­åŽä¸€æ¬¡å‡ºçŽ°çš„ä½ç½® +pdfjs-find-next-button-label = 下一页 +pdfjs-find-highlight-checkbox = 全部高亮显示 +pdfjs-find-match-case-checkbox-label = 区分大å°å†™ +pdfjs-find-match-diacritics-checkbox-label = 匹é…å˜éŸ³ç¬¦å· +pdfjs-find-entire-word-checkbox-label = å…¨è¯åŒ¹é… +pdfjs-find-reached-top = 到达文档开头,从末尾继续 +pdfjs-find-reached-bottom = 到达文档末尾,从开头继续 +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = 第 { $current } 项,共找到 { $total } 个匹é…项 +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = 匹é…超过 { $limit } 项 +pdfjs-find-not-found = 找ä¸åˆ°æŒ‡å®šè¯è¯­ + +## Predefined zoom values + +pdfjs-page-scale-width = 适åˆé¡µå®½ +pdfjs-page-scale-fit = 适åˆé¡µé¢ +pdfjs-page-scale-auto = 自动缩放 +pdfjs-page-scale-actual = å®žé™…å¤§å° +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = 第 { $page } 页 + +## Loading indicator messages + +pdfjs-loading-error = 加载 PDF æ—¶å‘生错误。 +pdfjs-invalid-file-error = 无效或æŸåçš„ PDF 文件。 +pdfjs-missing-file-error = 缺少 PDF 文件。 +pdfjs-unexpected-response-error = æ„外的æœåС噍å“应。 +pdfjs-rendering-error = æ¸²æŸ“é¡µé¢æ—¶å‘生错误。 + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date },{ $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } 注释] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = 输入密ç ä»¥æ‰“开此 PDF 文件。 +pdfjs-password-invalid = å¯†ç æ— æ•ˆã€‚请é‡è¯•。 +pdfjs-password-ok-button = 确定 +pdfjs-password-cancel-button = å–æ¶ˆ +pdfjs-web-fonts-disabled = Web 字体已被ç¦ç”¨ï¼šæ— æ³•使用嵌入的 PDF 字体。 + +## Editing + +pdfjs-editor-free-text-button = + .title = 文本 +pdfjs-editor-free-text-button-label = 文本 +pdfjs-editor-ink-button = + .title = 绘图 +pdfjs-editor-ink-button-label = 绘图 +pdfjs-editor-stamp-button = + .title = æ·»åŠ æˆ–ç¼–è¾‘å›¾åƒ +pdfjs-editor-stamp-button-label = æ·»åŠ æˆ–ç¼–è¾‘å›¾åƒ +pdfjs-editor-highlight-button = + .title = 高亮 +pdfjs-editor-highlight-button-label = 高亮 +pdfjs-highlight-floating-button1 = + .title = 高亮 + .aria-label = 高亮 +pdfjs-highlight-floating-button-label = 高亮 + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = 移除绘图 +pdfjs-editor-remove-freetext-button = + .title = 移除文本 +pdfjs-editor-remove-stamp-button = + .title = ç§»é™¤å›¾åƒ +pdfjs-editor-remove-highlight-button = + .title = 移除高亮 + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = 颜色 +pdfjs-editor-free-text-size-input = å­—å· +pdfjs-editor-ink-color-input = 颜色 +pdfjs-editor-ink-thickness-input = 粗细 +pdfjs-editor-ink-opacity-input = ä¸é€æ˜Žåº¦ +pdfjs-editor-stamp-add-image-button = + .title = æ·»åŠ å›¾åƒ +pdfjs-editor-stamp-add-image-button-label = æ·»åŠ å›¾åƒ +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = 粗细 +pdfjs-editor-free-highlight-thickness-title = + .title = 更改高亮粗细(用于文本以外项目) +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = 文本编辑器 + .default-content = 在此键入… +pdfjs-free-text = + .aria-label = 文本编辑器 +pdfjs-free-text-default-content = 开始输入… +pdfjs-ink = + .aria-label = 绘图编辑器 +pdfjs-ink-canvas = + .aria-label = ç”¨æˆ·åˆ›å»ºå›¾åƒ + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = æ›¿æ¢æ–‡å­— +pdfjs-editor-alt-text-edit-button = + .aria-label = ç¼–è¾‘æ›¿æ¢æ–‡å­— +pdfjs-editor-alt-text-edit-button-label = ç¼–è¾‘æ›¿æ¢æ–‡å­— +pdfjs-editor-alt-text-dialog-label = 选择一项 +pdfjs-editor-alt-text-dialog-description = æ›¿æ¢æ–‡å­—å¯åœ¨ç”¨æˆ·æ— æ³•çœ‹åˆ°æˆ–åŠ è½½å›¾åƒæ—¶ï¼Œæè¿°å…¶å†…容。 +pdfjs-editor-alt-text-add-description-label = 添加æè¿° +pdfjs-editor-alt-text-add-description-description = 用一两个å¥å­ï¼Œæè¿°ä¸»é¢˜ã€èƒŒæ™¯æˆ–动作。 +pdfjs-editor-alt-text-mark-decorative-label = 标记为装饰 +pdfjs-editor-alt-text-mark-decorative-description = 用于装饰的图åƒï¼Œä¾‹å¦‚边框和水å°ã€‚ +pdfjs-editor-alt-text-cancel-button = å–æ¶ˆ +pdfjs-editor-alt-text-save-button = ä¿å­˜ +pdfjs-editor-alt-text-decorative-tooltip = 已标记为装饰 +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = 例如:一个少年å到桌å‰ï¼Œå‡†å¤‡åƒé¥­ +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = æ›¿æ¢æ–‡å­— + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = 调整尺寸 - 左上角 +pdfjs-editor-resizer-label-top-middle = 调整尺寸 - 顶部中间 +pdfjs-editor-resizer-label-top-right = 调整尺寸 - å³ä¸Šè§’ +pdfjs-editor-resizer-label-middle-right = 调整尺寸 - å³ä¾§ä¸­é—´ +pdfjs-editor-resizer-label-bottom-right = 调整尺寸 - å³ä¸‹è§’ +pdfjs-editor-resizer-label-bottom-middle = è°ƒæ•´å¤§å° - 底部中间 +pdfjs-editor-resizer-label-bottom-left = 调整尺寸 - 左下角 +pdfjs-editor-resizer-label-middle-left = 调整尺寸 - 左侧中间 +pdfjs-editor-resizer-top-left = + .aria-label = 调整尺寸 - 左上角 +pdfjs-editor-resizer-top-middle = + .aria-label = 调整尺寸 - 顶部中间 +pdfjs-editor-resizer-top-right = + .aria-label = 调整尺寸 - å³ä¸Šè§’ +pdfjs-editor-resizer-middle-right = + .aria-label = 调整尺寸 - å³ä¾§ä¸­é—´ +pdfjs-editor-resizer-bottom-right = + .aria-label = 调整尺寸 - å³ä¸‹è§’ +pdfjs-editor-resizer-bottom-middle = + .aria-label = è°ƒæ•´å¤§å° - 底部中间 +pdfjs-editor-resizer-bottom-left = + .aria-label = 调整尺寸 - 左下角 +pdfjs-editor-resizer-middle-left = + .aria-label = 调整尺寸 - 左侧中间 + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = 高亮色 +pdfjs-editor-colorpicker-button = + .title = 更改颜色 +pdfjs-editor-colorpicker-dropdown = + .aria-label = 颜色选择 +pdfjs-editor-colorpicker-yellow = + .title = 黄色 +pdfjs-editor-colorpicker-green = + .title = 绿色 +pdfjs-editor-colorpicker-blue = + .title = è“色 +pdfjs-editor-colorpicker-pink = + .title = 粉色 +pdfjs-editor-colorpicker-red = + .title = 红色 + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = 显示全部 +pdfjs-editor-highlight-show-all-button = + .title = 显示全部 + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = ç¼–è¾‘æ›¿æ¢æ–‡å­—ï¼ˆå›¾åƒæè¿°ï¼‰ +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = æ·»åŠ æ›¿æ¢æ–‡å­—ï¼ˆå›¾åƒæè¿°ï¼‰ +pdfjs-editor-new-alt-text-textarea = + .placeholder = 请在此处撰写æè¿°â€¦ +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = 呿— æ³•看到或加载图åƒçš„用户æä¾›çš„简短æè¿°ã€‚ +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = æ­¤æ®µæ›¿æ¢æ–‡å­—为自动创建,有å¯èƒ½ä¸å‡†ç¡®ã€‚ +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = 详细了解 +pdfjs-editor-new-alt-text-create-automatically-button-label = è‡ªåŠ¨åˆ›å»ºæ›¿æ¢æ–‡å­— +pdfjs-editor-new-alt-text-not-now-button = 暂时ä¸è¦ +pdfjs-editor-new-alt-text-error-title = æ— æ³•è‡ªåŠ¨åˆ›å»ºæ›¿æ¢æ–‡å­— +pdfjs-editor-new-alt-text-error-description = è¯·è‡ªè¡Œæ’°å†™æ›¿æ¢æ–‡å­—,或ç¨åŽå†è¯•。 +pdfjs-editor-new-alt-text-error-close-button = 关闭 +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = 正在下载æä¾›æ›¿æ¢æ–‡å­—çš„ AI 模型({ $downloadedSize }/{ $totalSize } MB) + .aria-valuetext = 正在下载æä¾›æ›¿æ¢æ–‡å­—çš„ AI 模型({ $downloadedSize }/{ $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = å·²æ·»åŠ æ›¿æ¢æ–‡å­— +pdfjs-editor-new-alt-text-added-button-label = å·²æ·»åŠ æ›¿æ¢æ–‡å­— +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = ç¼ºå°‘æ›¿æ¢æ–‡å­— +pdfjs-editor-new-alt-text-missing-button-label = ç¼ºå°‘æ›¿æ¢æ–‡å­— +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = æ£€æŸ¥æ›¿æ¢æ–‡å­— +pdfjs-editor-new-alt-text-to-review-button-label = æ£€æŸ¥æ›¿æ¢æ–‡å­— +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = [自动创建] { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = å›¾åƒæ›¿æ¢æ–‡å­—设置 +pdfjs-image-alt-text-settings-button-label = å›¾åƒæ›¿æ¢æ–‡å­—设置 +pdfjs-editor-alt-text-settings-dialog-label = å›¾åƒæ›¿æ¢æ–‡å­—设置 +pdfjs-editor-alt-text-settings-automatic-title = è‡ªåŠ¨åˆ›å»ºæ›¿æ¢æ–‡å­— +pdfjs-editor-alt-text-settings-create-model-button-label = è‡ªåŠ¨åˆ›å»ºæ›¿æ¢æ–‡å­— +pdfjs-editor-alt-text-settings-create-model-description = 呿— æ³•看到或加载图åƒçš„用户æä¾›æè¿°ã€‚ +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = æä¾›æ›¿æ¢æ–‡å­—çš„ AI 模型({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = 在您的设备本地è¿è¡Œï¼Œå¯ä½¿æ•°æ®ä¿æŒç§å¯†ã€‚è‡ªåŠ¨åˆ›å»ºæ›¿æ¢æ–‡å­—需è¦ä½¿ç”¨æ­¤æ¨¡åž‹ã€‚ +pdfjs-editor-alt-text-settings-delete-model-button = 删除 +pdfjs-editor-alt-text-settings-download-model-button = 下载 +pdfjs-editor-alt-text-settings-downloading-model-button = 正在下载… +pdfjs-editor-alt-text-settings-editor-title = æ›¿æ¢æ–‡å­—编辑器 +pdfjs-editor-alt-text-settings-show-dialog-button-label = 添加图åƒåŽç«‹å³æ˜¾ç¤ºæ›¿æ¢æ–‡å­—编辑器 +pdfjs-editor-alt-text-settings-show-dialog-description = å¸®åŠ©ç¡®ä¿æ‰€æœ‰å›¾åƒå‡æ‹¥æœ‰æ›¿æ¢æ–‡å­—。 +pdfjs-editor-alt-text-settings-close-button = 关闭 + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = 已移除高亮 +pdfjs-editor-undo-bar-message-freetext = 已移除文本 +pdfjs-editor-undo-bar-message-ink = 已移除绘图 +pdfjs-editor-undo-bar-message-stamp = å·²ç§»é™¤å›¾åƒ +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = 已移除 { $count } æ¡æ³¨é‡Š +pdfjs-editor-undo-bar-undo-button = + .title = 撤销 +pdfjs-editor-undo-bar-undo-button-label = 撤销 +pdfjs-editor-undo-bar-close-button = + .title = 关闭 +pdfjs-editor-undo-bar-close-button-label = 关闭 diff --git a/public/assets/pdfjs/locale/zh-TW/viewer.ftl b/public/assets/pdfjs/locale/zh-TW/viewer.ftl new file mode 100755 index 0000000..bc4b7ef --- /dev/null +++ b/public/assets/pdfjs/locale/zh-TW/viewer.ftl @@ -0,0 +1,503 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +## Main toolbar buttons (tooltips and alt text for images) + +pdfjs-previous-button = + .title = ä¸Šä¸€é  +pdfjs-previous-button-label = ä¸Šä¸€é  +pdfjs-next-button = + .title = ä¸‹ä¸€é  +pdfjs-next-button-label = ä¸‹ä¸€é  +# .title: Tooltip for the pageNumber input. +pdfjs-page-input = + .title = 第 +# Variables: +# $pagesCount (Number) - the total number of pages in the document +# This string follows an input field with the number of the page currently displayed. +pdfjs-of-pages = é ï¼Œå…± { $pagesCount } é  +# Variables: +# $pageNumber (Number) - the currently visible page +# $pagesCount (Number) - the total number of pages in the document +pdfjs-page-of-pages = (第 { $pageNumber } é ï¼Œå…± { $pagesCount } é ï¼‰ +pdfjs-zoom-out-button = + .title = ç¸®å° +pdfjs-zoom-out-button-label = ç¸®å° +pdfjs-zoom-in-button = + .title = 放大 +pdfjs-zoom-in-button-label = 放大 +pdfjs-zoom-select = + .title = 縮放 +pdfjs-presentation-mode-button = + .title = 切æ›è‡³ç°¡å ±æ¨¡å¼ +pdfjs-presentation-mode-button-label = ç°¡å ±æ¨¡å¼ +pdfjs-open-file-button = + .title = 開啟檔案 +pdfjs-open-file-button-label = 開啟 +pdfjs-print-button = + .title = åˆ—å° +pdfjs-print-button-label = åˆ—å° +pdfjs-save-button = + .title = 儲存 +pdfjs-save-button-label = 儲存 +# Used in Firefox for Android as a tooltip for the download button (“download†is a verb). +pdfjs-download-button = + .title = 下載 +# Used in Firefox for Android as a label for the download button (“download†is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = 下載 +pdfjs-bookmark-button = + .title = ç›®å‰é é¢ï¼ˆå«ç›®å‰æª¢è¦–é é¢çš„ç¶²å€ï¼‰ +pdfjs-bookmark-button-label = ç›®å‰é é¢ + +## Secondary toolbar and context menu + +pdfjs-tools-button = + .title = 工具 +pdfjs-tools-button-label = 工具 +pdfjs-first-page-button = + .title = è·³åˆ°ç¬¬ä¸€é  +pdfjs-first-page-button-label = è·³åˆ°ç¬¬ä¸€é  +pdfjs-last-page-button = + .title = è·³åˆ°æœ€å¾Œä¸€é  +pdfjs-last-page-button-label = è·³åˆ°æœ€å¾Œä¸€é  +pdfjs-page-rotate-cw-button = + .title = é †æ™‚é‡æ—‹è½‰ +pdfjs-page-rotate-cw-button-label = é †æ™‚é‡æ—‹è½‰ +pdfjs-page-rotate-ccw-button = + .title = é€†æ™‚é‡æ—‹è½‰ +pdfjs-page-rotate-ccw-button-label = é€†æ™‚é‡æ—‹è½‰ +pdfjs-cursor-text-select-tool-button = + .title = é–‹å•Ÿæ–‡å­—é¸æ“‡å·¥å…· +pdfjs-cursor-text-select-tool-button-label = æ–‡å­—é¸æ“‡å·¥å…· +pdfjs-cursor-hand-tool-button = + .title = 開啟é é¢ç§»å‹•工具 +pdfjs-cursor-hand-tool-button-label = é é¢ç§»å‹•工具 +pdfjs-scroll-page-button = + .title = ä½¿ç”¨å–®é æ²å‹•ç‰ˆé¢ +pdfjs-scroll-page-button-label = 單頿²å‹• +pdfjs-scroll-vertical-button = + .title = 使用垂直æ²å‹•ç‰ˆé¢ +pdfjs-scroll-vertical-button-label = 垂直æ²å‹• +pdfjs-scroll-horizontal-button = + .title = 使用水平æ²å‹•ç‰ˆé¢ +pdfjs-scroll-horizontal-button-label = æ°´å¹³æ²å‹• +pdfjs-scroll-wrapped-button = + .title = ä½¿ç”¨å¤šé æ²å‹•ç‰ˆé¢ +pdfjs-scroll-wrapped-button-label = å¤šé æ²å‹• +pdfjs-spread-none-button = + .title = ä¸è¦é€²è¡Œè·¨é é¡¯ç¤º +pdfjs-spread-none-button-label = ä¸è·¨é  +pdfjs-spread-odd-button = + .title = 從奇數é é–‹å§‹è·¨é  +pdfjs-spread-odd-button-label = å¥‡æ•¸è·¨é  +pdfjs-spread-even-button = + .title = å¾žå¶æ•¸é é–‹å§‹è·¨é  +pdfjs-spread-even-button-label = å¶æ•¸è·¨é  + +## Document properties dialog + +pdfjs-document-properties-button = + .title = 文件內容… +pdfjs-document-properties-button-label = 文件內容… +pdfjs-document-properties-file-name = 檔案å稱: +pdfjs-document-properties-file-size = 檔案大å°ï¼š +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB({ $b } ä½å…ƒçµ„) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB({ $b } ä½å…ƒçµ„) +# Variables: +# $size_kb (Number) - the PDF file size in kilobytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-kb = { $size_kb } KB({ $size_b } ä½å…ƒçµ„) +# Variables: +# $size_mb (Number) - the PDF file size in megabytes +# $size_b (Number) - the PDF file size in bytes +pdfjs-document-properties-mb = { $size_mb } MB({ $size_b } ä½å…ƒçµ„) +pdfjs-document-properties-title = 標題: +pdfjs-document-properties-author = 作者: +pdfjs-document-properties-subject = 主旨: +pdfjs-document-properties-keywords = é—œéµå­—: +pdfjs-document-properties-creation-date = 建立日期: +pdfjs-document-properties-modification-date = 修改日期: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: +# $date (Date) - the creation/modification date of the PDF file +# $time (Time) - the creation/modification time of the PDF file +pdfjs-document-properties-date-string = { $date } { $time } +pdfjs-document-properties-creator = 建立者: +pdfjs-document-properties-producer = PDF 產生器: +pdfjs-document-properties-version = PDF 版本: +pdfjs-document-properties-page-count = é æ•¸ï¼š +pdfjs-document-properties-page-size = é é¢å¤§å°ï¼š +pdfjs-document-properties-page-size-unit-inches = in +pdfjs-document-properties-page-size-unit-millimeters = mm +pdfjs-document-properties-page-size-orientation-portrait = 垂直 +pdfjs-document-properties-page-size-orientation-landscape = æ°´å¹³ +pdfjs-document-properties-page-size-name-a-three = A3 +pdfjs-document-properties-page-size-name-a-four = A4 +pdfjs-document-properties-page-size-name-letter = Letter +pdfjs-document-properties-page-size-name-legal = Legal + +## Variables: +## $width (Number) - the width of the (current) page +## $height (Number) - the height of the (current) page +## $unit (String) - the unit of measurement of the (current) page +## $name (String) - the name of the (current) page +## $orientation (String) - the orientation of the (current) page + +pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit }({ $orientation }) +pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit }({ $name },{ $orientation }) + +## + +# The linearization status of the document; usually called "Fast Web View" in +# English locales of Adobe software. +pdfjs-document-properties-linearized = 快速 Web 檢視: +pdfjs-document-properties-linearized-yes = 是 +pdfjs-document-properties-linearized-no = å¦ +pdfjs-document-properties-close-button = 關閉 + +## Print + +pdfjs-print-progress-message = æ­£åœ¨æº–å‚™åˆ—å°æ–‡ä»¶â€¦ +# Variables: +# $progress (Number) - percent value +pdfjs-print-progress-percent = { $progress }% +pdfjs-print-progress-close-button = å–æ¶ˆ +pdfjs-printing-not-supported = 警告: æ­¤ç€è¦½å™¨æœªå®Œæ•´æ”¯æ´åˆ—å°åŠŸèƒ½ã€‚ +pdfjs-printing-not-ready = 警告: æ­¤ PDF 未完æˆä¸‹è¼‰ä»¥ä¾›åˆ—å°ã€‚ + +## Tooltips and alt text for side panel toolbar buttons + +pdfjs-toggle-sidebar-button = + .title = 切æ›å´é‚Šæ¬„ +pdfjs-toggle-sidebar-notification-button = + .title = 切æ›å´é‚Šæ¬„(包å«å¤§ç¶±ã€é™„ä»¶ã€åœ–層的文件) +pdfjs-toggle-sidebar-button-label = 切æ›å´é‚Šæ¬„ +pdfjs-document-outline-button = + .title = 顯示文件大綱(雙擊展開/摺疊所有項目) +pdfjs-document-outline-button-label = 文件大綱 +pdfjs-attachments-button = + .title = 顯示附件 +pdfjs-attachments-button-label = 附件 +pdfjs-layers-button = + .title = 顯示圖層(滑鼠雙擊å³å¯å°‡æ‰€æœ‰åœ–層é‡è¨­ç‚ºé è¨­ç‹€æ…‹ï¼‰ +pdfjs-layers-button-label = 圖層 +pdfjs-thumbs-button = + .title = 顯示縮圖 +pdfjs-thumbs-button-label = 縮圖 +pdfjs-current-outline-item-button = + .title = 尋找目å‰çš„大綱項目 +pdfjs-current-outline-item-button-label = ç›®å‰çš„大綱項目 +pdfjs-findbar-button = + .title = 在文件中尋找 +pdfjs-findbar-button-label = 尋找 +pdfjs-additional-layers = 其他圖層 + +## Thumbnails panel item (tooltip and alt text for images) + +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-title = + .title = 第 { $page } é  +# Variables: +# $page (Number) - the page number +pdfjs-thumb-page-canvas = + .aria-label = 第 { $page } é çš„縮圖 + +## Find panel button title and messages + +pdfjs-find-input = + .title = 尋找 + .placeholder = 在文件中æœå°‹â€¦ +pdfjs-find-previous-button = + .title = å°‹æ‰¾æ–‡å­—å‰æ¬¡å‡ºç¾çš„ä½ç½® +pdfjs-find-previous-button-label = 上一個 +pdfjs-find-next-button = + .title = 尋找文字下次出ç¾çš„ä½ç½® +pdfjs-find-next-button-label = 下一個 +pdfjs-find-highlight-checkbox = 強調全部 +pdfjs-find-match-case-checkbox-label = å€åˆ†å¤§å°å¯« +pdfjs-find-match-diacritics-checkbox-label = 符åˆè®ŠéŸ³ç¬¦è™Ÿ +pdfjs-find-entire-word-checkbox-label = ç¬¦åˆæ•´å€‹å­— +pdfjs-find-reached-top = å·²æœå°‹è‡³æ–‡ä»¶é ‚端,自底端繼續æœå°‹ +pdfjs-find-reached-bottom = å·²æœå°‹è‡³æ–‡ä»¶åº•端,自頂端繼續æœå°‹ +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = 第 { $current } 筆符åˆï¼Œå…±ç¬¦åˆ { $total } ç­† +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = 符åˆè¶…éŽ { $limit } é … +pdfjs-find-not-found = 找ä¸åˆ°æŒ‡å®šæ–‡å­— + +## Predefined zoom values + +pdfjs-page-scale-width = é é¢å¯¬åº¦ +pdfjs-page-scale-fit = 縮放至é é¢å¤§å° +pdfjs-page-scale-auto = 自動縮放 +pdfjs-page-scale-actual = å¯¦éš›å¤§å° +# Variables: +# $scale (Number) - percent value for page scale +pdfjs-page-scale-percent = { $scale }% + +## PDF page + +# Variables: +# $page (Number) - the page number +pdfjs-page-landmark = + .aria-label = 第 { $page } é  + +## Loading indicator messages + +pdfjs-loading-error = 載入 PDF 時發生錯誤。 +pdfjs-invalid-file-error = 無效或毀æçš„ PDF 檔案。 +pdfjs-missing-file-error = 找ä¸åˆ° PDF 檔案。 +pdfjs-unexpected-response-error = 伺æœå™¨å›žæ‡‰æœªé æœŸçš„內容。 +pdfjs-rendering-error = æç¹ªé é¢æ™‚發生錯誤。 + +## Annotations + +# Variables: +# $date (Date) - the modification date of the annotation +# $time (Time) - the modification time of the annotation +pdfjs-annotation-date-string = { $date } { $time } +# .alt: This is used as a tooltip. +# Variables: +# $type (String) - an annotation type from a list defined in the PDF spec +# (32000-1:2008 Table 169 – Annotation types). +# Some common types are e.g.: "Check", "Text", "Comment", "Note" +pdfjs-text-annotation-type = + .alt = [{ $type } 註解] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } + +## Password + +pdfjs-password-label = 請輸入用來開啟此 PDF 檔案的密碼。 +pdfjs-password-invalid = å¯†ç¢¼ä¸æ­£ç¢ºï¼Œè«‹å†è©¦ä¸€æ¬¡ã€‚ +pdfjs-password-ok-button = 確定 +pdfjs-password-cancel-button = å–æ¶ˆ +pdfjs-web-fonts-disabled = å·²åœç”¨ç¶²è·¯å­—åž‹ (Web fonts): 無法使用 PDF 內嵌字型。 + +## Editing + +pdfjs-editor-free-text-button = + .title = 文字 +pdfjs-editor-free-text-button-label = 文字 +pdfjs-editor-ink-button = + .title = 繪圖 +pdfjs-editor-ink-button-label = 繪圖 +pdfjs-editor-stamp-button = + .title = 新增或編輯圖片 +pdfjs-editor-stamp-button-label = 新增或編輯圖片 +pdfjs-editor-highlight-button = + .title = 強調 +pdfjs-editor-highlight-button-label = 強調 +pdfjs-highlight-floating-button1 = + .title = 強調 + .aria-label = 強調 +pdfjs-highlight-floating-button-label = 強調 + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = 移除繪圖 +pdfjs-editor-remove-freetext-button = + .title = 移除文字 +pdfjs-editor-remove-stamp-button = + .title = 移除圖片 +pdfjs-editor-remove-highlight-button = + .title = ç§»é™¤å¼·èª¿ç¯„åœ + +## + +# Editor Parameters +pdfjs-editor-free-text-color-input = 色彩 +pdfjs-editor-free-text-size-input = å¤§å° +pdfjs-editor-ink-color-input = 色彩 +pdfjs-editor-ink-thickness-input = ç·šæ¢ç²—ç´° +pdfjs-editor-ink-opacity-input = é€â€‹æ˜Žåº¦ +pdfjs-editor-stamp-add-image-button = + .title = 新增圖片 +pdfjs-editor-stamp-add-image-button-label = 新增圖片 +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = ç·šæ¢ç²—ç´° +pdfjs-editor-free-highlight-thickness-title = + .title = 更改強調文字以外的項目時的線æ¢ç²—ç´° +# .default-content is used as a placeholder in an empty text editor. +pdfjs-free-text2 = + .aria-label = 文字編輯器 + .default-content = 請打字… +pdfjs-free-text = + .aria-label = 文本編輯器 +pdfjs-free-text-default-content = 在此打字… +pdfjs-ink = + .aria-label = 圖形編輯器 +pdfjs-ink-canvas = + .aria-label = 使用者建立的圖片 + +## Alt-text dialog + +pdfjs-editor-alt-text-button-label = 替代文字 +pdfjs-editor-alt-text-edit-button = + .aria-label = 編輯替代文字 +pdfjs-editor-alt-text-edit-button-label = 編輯替代文字 +pdfjs-editor-alt-text-dialog-label = 挑é¸ä¸€ç¨® +pdfjs-editor-alt-text-dialog-description = 替代文字å¯å”助盲人,或於圖片無法載入時æä¾›èªªæ˜Žã€‚ +pdfjs-editor-alt-text-add-description-label = 新增æè¿° +pdfjs-editor-alt-text-add-description-description = 用 1-2 奿–‡å­—æè¿°ä¸»é¡Œã€èƒŒæ™¯æˆ–動作。 +pdfjs-editor-alt-text-mark-decorative-label = 標示為è£é£¾æ€§å…§å®¹ +pdfjs-editor-alt-text-mark-decorative-description = 這是è£é£¾æ€§åœ–片,例如邊框或浮水å°ã€‚ +pdfjs-editor-alt-text-cancel-button = å–æ¶ˆ +pdfjs-editor-alt-text-save-button = 儲存 +pdfjs-editor-alt-text-decorative-tooltip = 已標示為è£é£¾æ€§å…§å®¹ +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = 例如:「有一ä½å¹´è¼•男人å在桌å­å‰é¢åƒé£¯ã€ +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button = + .aria-label = 替代文字 + +## Editor resizers +## This is used in an aria label to help to understand the role of the resizer. + +pdfjs-editor-resizer-label-top-left = 左上角 — èª¿æ•´å¤§å° +pdfjs-editor-resizer-label-top-middle = 頂部中間 — èª¿æ•´å¤§å° +pdfjs-editor-resizer-label-top-right = å³ä¸Šè§’ — èª¿æ•´å¤§å° +pdfjs-editor-resizer-label-middle-right = 䏭間峿–¹ — èª¿æ•´å¤§å° +pdfjs-editor-resizer-label-bottom-right = å³ä¸‹è§’ — èª¿æ•´å¤§å° +pdfjs-editor-resizer-label-bottom-middle = 底部中間 — èª¿æ•´å¤§å° +pdfjs-editor-resizer-label-bottom-left = 左下角 — èª¿æ•´å¤§å° +pdfjs-editor-resizer-label-middle-left = 中間左方 — èª¿æ•´å¤§å° +pdfjs-editor-resizer-top-left = + .aria-label = 左上角 — èª¿æ•´å¤§å° +pdfjs-editor-resizer-top-middle = + .aria-label = 頂部中間 — èª¿æ•´å¤§å° +pdfjs-editor-resizer-top-right = + .aria-label = å³ä¸Šè§’ — èª¿æ•´å¤§å° +pdfjs-editor-resizer-middle-right = + .aria-label = 䏭間峿–¹ — èª¿æ•´å¤§å° +pdfjs-editor-resizer-bottom-right = + .aria-label = å³ä¸‹è§’ — èª¿æ•´å¤§å° +pdfjs-editor-resizer-bottom-middle = + .aria-label = 底部中間 — èª¿æ•´å¤§å° +pdfjs-editor-resizer-bottom-left = + .aria-label = 左下角 — èª¿æ•´å¤§å° +pdfjs-editor-resizer-middle-left = + .aria-label = 中間左方 — èª¿æ•´å¤§å° + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = 強調色彩 +pdfjs-editor-colorpicker-button = + .title = 更改色彩 +pdfjs-editor-colorpicker-dropdown = + .aria-label = 色彩é¸é … +pdfjs-editor-colorpicker-yellow = + .title = 黃色 +pdfjs-editor-colorpicker-green = + .title = 綠色 +pdfjs-editor-colorpicker-blue = + .title = è—色 +pdfjs-editor-colorpicker-pink = + .title = 粉紅色 +pdfjs-editor-colorpicker-red = + .title = 紅色 + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = 顯示全部 +pdfjs-editor-highlight-show-all-button = + .title = 顯示全部 + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = 編輯替代文字(圖片æè¿°ï¼‰ +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = 新增替代文字(圖片æè¿°ï¼‰ +pdfjs-editor-new-alt-text-textarea = + .placeholder = 在此寫下您的æè¿°æ–‡å­—… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = 為看ä¸åˆ°åœ–片的讀者,或圖片無法載入時顯示的簡短æè¿°ã€‚ +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = 此替代文字是自動產生的,å¯èƒ½ä¸å¤ ç²¾ç¢ºã€‚ +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = 更多資訊 +pdfjs-editor-new-alt-text-create-automatically-button-label = 自動產生替代文字 +pdfjs-editor-new-alt-text-not-now-button = 暫時ä¸è¦ +pdfjs-editor-new-alt-text-error-title = 無法自動產生替代文字 +pdfjs-editor-new-alt-text-error-description = 請自行填寫替代文字,或ç¨å¾Œå†è©¦ä¸€æ¬¡ã€‚ +pdfjs-editor-new-alt-text-error-close-button = 關閉 +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = 正在下載替代文字 AI 模型({ $downloadedSize } / { $totalSize } MB) + .aria-valuetext = 正在下載替代文字 AI 模型({ $downloadedSize } / { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button = + .aria-label = 已新增替代文字 +pdfjs-editor-new-alt-text-added-button-label = 已新增替代文字 +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button = + .aria-label = 缺少替代文字 +pdfjs-editor-new-alt-text-missing-button-label = 缺少替代文字 +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button = + .aria-label = ç¢ºèªæ›¿ä»£æ–‡å­— +pdfjs-editor-new-alt-text-to-review-button-label = ç¢ºèªæ›¿ä»£æ–‡å­— +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = 自動產生:{ $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = 圖片替代文字設定 +pdfjs-image-alt-text-settings-button-label = 圖片替代文字設定 +pdfjs-editor-alt-text-settings-dialog-label = 圖片替代文字設定 +pdfjs-editor-alt-text-settings-automatic-title = 自動化替代文字 +pdfjs-editor-alt-text-settings-create-model-button-label = 自動產生替代文字 +pdfjs-editor-alt-text-settings-create-model-description = 為您建議圖片æè¿°ï¼Œå¹«åŠ©çœ‹ä¸åˆ°åœ–片的讀者,或於圖片無法載入時顯示。 +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = 替代文字 AI 模型({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = 在您的本機è£ç½®ä¸Šé‹ä½œï¼Œä»¥ç¢ºä¿æ‚¨çš„資料隱ç§ã€‚必須下載此模型æ‰å¯ä»¥è‡ªå‹•產生替代文字。 +pdfjs-editor-alt-text-settings-delete-model-button = 刪除 +pdfjs-editor-alt-text-settings-download-model-button = 下載 +pdfjs-editor-alt-text-settings-downloading-model-button = 下載中… +pdfjs-editor-alt-text-settings-editor-title = 替代文字編輯器 +pdfjs-editor-alt-text-settings-show-dialog-button-label = 新增圖片後立å³é¡¯ç¤ºæ›¿ä»£æ–‡å­—編輯器 +pdfjs-editor-alt-text-settings-show-dialog-description = å¹«åŠ©æ‚¨ç¢ºä¿æ‰€æœ‰åœ–片都有替代文字。 +pdfjs-editor-alt-text-settings-close-button = 關閉 + +## "Annotations removed" bar + +pdfjs-editor-undo-bar-message-highlight = 已移除強調 +pdfjs-editor-undo-bar-message-freetext = 已移除文字 +pdfjs-editor-undo-bar-message-ink = 已移除繪圖 +pdfjs-editor-undo-bar-message-stamp = 已移除圖片 +# Variables: +# $count (Number) - the number of removed annotations. +pdfjs-editor-undo-bar-message-multiple = 已移除 { $count } 筆註解 +pdfjs-editor-undo-bar-undo-button = + .title = 還原 +pdfjs-editor-undo-bar-undo-button-label = 還原 +pdfjs-editor-undo-bar-close-button = + .title = 關閉 +pdfjs-editor-undo-bar-close-button-label = 關閉 diff --git a/public/assets/pdfjs/standard_fonts/FoxitDingbats.pfb b/public/assets/pdfjs/standard_fonts/FoxitDingbats.pfb new file mode 100755 index 0000000..30d5296 Binary files /dev/null and b/public/assets/pdfjs/standard_fonts/FoxitDingbats.pfb differ diff --git a/public/assets/pdfjs/standard_fonts/FoxitFixed.pfb b/public/assets/pdfjs/standard_fonts/FoxitFixed.pfb new file mode 100755 index 0000000..f12dcbc Binary files /dev/null and b/public/assets/pdfjs/standard_fonts/FoxitFixed.pfb differ diff --git a/public/assets/pdfjs/standard_fonts/FoxitFixedBold.pfb b/public/assets/pdfjs/standard_fonts/FoxitFixedBold.pfb new file mode 100755 index 0000000..cf8e24a Binary files /dev/null and b/public/assets/pdfjs/standard_fonts/FoxitFixedBold.pfb differ diff --git a/public/assets/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb b/public/assets/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb new file mode 100755 index 0000000..d288001 Binary files /dev/null and b/public/assets/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb differ diff --git a/public/assets/pdfjs/standard_fonts/FoxitFixedItalic.pfb b/public/assets/pdfjs/standard_fonts/FoxitFixedItalic.pfb new file mode 100755 index 0000000..d71697d Binary files /dev/null and b/public/assets/pdfjs/standard_fonts/FoxitFixedItalic.pfb differ diff --git a/public/assets/pdfjs/standard_fonts/FoxitSerif.pfb b/public/assets/pdfjs/standard_fonts/FoxitSerif.pfb new file mode 100755 index 0000000..3fa682e Binary files /dev/null and b/public/assets/pdfjs/standard_fonts/FoxitSerif.pfb differ diff --git a/public/assets/pdfjs/standard_fonts/FoxitSerifBold.pfb b/public/assets/pdfjs/standard_fonts/FoxitSerifBold.pfb new file mode 100755 index 0000000..ff7c6dd Binary files /dev/null and b/public/assets/pdfjs/standard_fonts/FoxitSerifBold.pfb differ diff --git a/public/assets/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb b/public/assets/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb new file mode 100755 index 0000000..460231f Binary files /dev/null and b/public/assets/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb differ diff --git a/public/assets/pdfjs/standard_fonts/FoxitSerifItalic.pfb b/public/assets/pdfjs/standard_fonts/FoxitSerifItalic.pfb new file mode 100755 index 0000000..d03a7c7 Binary files /dev/null and b/public/assets/pdfjs/standard_fonts/FoxitSerifItalic.pfb differ diff --git a/public/assets/pdfjs/standard_fonts/FoxitSymbol.pfb b/public/assets/pdfjs/standard_fonts/FoxitSymbol.pfb new file mode 100755 index 0000000..c8f9bca Binary files /dev/null and b/public/assets/pdfjs/standard_fonts/FoxitSymbol.pfb differ diff --git a/public/assets/pdfjs/standard_fonts/LICENSE_FOXIT b/public/assets/pdfjs/standard_fonts/LICENSE_FOXIT new file mode 100755 index 0000000..8b4ed6d --- /dev/null +++ b/public/assets/pdfjs/standard_fonts/LICENSE_FOXIT @@ -0,0 +1,27 @@ +// Copyright 2014 PDFium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/public/assets/pdfjs/standard_fonts/LICENSE_LIBERATION b/public/assets/pdfjs/standard_fonts/LICENSE_LIBERATION new file mode 100755 index 0000000..aba73e8 --- /dev/null +++ b/public/assets/pdfjs/standard_fonts/LICENSE_LIBERATION @@ -0,0 +1,102 @@ +Digitized data copyright (c) 2010 Google Corporation + with Reserved Font Arimo, Tinos and Cousine. +Copyright (c) 2012 Red Hat, Inc. + with Reserved Font Name Liberation. + +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + +PREAMBLE The goals of the Open Font License (OFL) are to stimulate +worldwide development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to provide +a free and open framework in which fonts may be shared and improved in +partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. +The fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + + + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. +This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components +as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting ? in part or in whole ? +any of the components of the Original Version, by changing formats or +by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer +or other person who contributed to the Font Software. + + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a +copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components,in + Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, + redistributed and/or sold with any software, provided that each copy + contains the above copyright notice and this license. These can be + included either as stand-alone text files, human-readable headers or + in the appropriate machine-readable metadata fields within text or + binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font + Name(s) unless explicit written permission is granted by the + corresponding Copyright Holder. This restriction only applies to the + primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font + Software shall not be used to promote, endorse or advertise any + Modified Version, except to acknowledge the contribution(s) of the + Copyright Holder(s) and the Author(s) or with their explicit written + permission. + +5) The Font Software, modified or unmodified, in part or in whole, must + be distributed entirely under this license, and must not be distributed + under any other license. The requirement for fonts to remain under + this license does not apply to any document created using the Font + Software. + + + +TERMINATION +This license becomes null and void if any of the above conditions are not met. + + + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER +DEALINGS IN THE FONT SOFTWARE. + diff --git a/public/assets/pdfjs/standard_fonts/LiberationSans-Bold.ttf b/public/assets/pdfjs/standard_fonts/LiberationSans-Bold.ttf new file mode 100755 index 0000000..ee23715 Binary files /dev/null and b/public/assets/pdfjs/standard_fonts/LiberationSans-Bold.ttf differ diff --git a/public/assets/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf b/public/assets/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf new file mode 100755 index 0000000..42b5717 Binary files /dev/null and b/public/assets/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf differ diff --git a/public/assets/pdfjs/standard_fonts/LiberationSans-Italic.ttf b/public/assets/pdfjs/standard_fonts/LiberationSans-Italic.ttf new file mode 100755 index 0000000..0cf6126 Binary files /dev/null and b/public/assets/pdfjs/standard_fonts/LiberationSans-Italic.ttf differ diff --git a/public/assets/pdfjs/standard_fonts/LiberationSans-Regular.ttf b/public/assets/pdfjs/standard_fonts/LiberationSans-Regular.ttf new file mode 100755 index 0000000..366d148 Binary files /dev/null and b/public/assets/pdfjs/standard_fonts/LiberationSans-Regular.ttf differ diff --git a/public/assets/pdfjs/viewer.css b/public/assets/pdfjs/viewer.css new file mode 100755 index 0000000..b6df80b --- /dev/null +++ b/public/assets/pdfjs/viewer.css @@ -0,0 +1,6068 @@ +/* Copyright 2014 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.messageBar{ + --closing-button-icon:url(images/messageBar_closingButton.svg); + --message-bar-close-button-color:var(--text-primary-color); + --message-bar-close-button-color-hover:var(--text-primary-color); + --message-bar-close-button-border-radius:4px; + --message-bar-close-button-border:none; + --message-bar-close-button-hover-bg-color:rgb(21 20 26 / 0.14); + --message-bar-close-button-active-bg-color:rgb(21 20 26 / 0.21); + --message-bar-close-button-focus-bg-color:rgb(21 20 26 / 0.07); +} + +@media (prefers-color-scheme: dark){ + + :where(html:not(.is-light)) .messageBar{ + --message-bar-close-button-hover-bg-color:rgb(251 251 254 / 0.14); + --message-bar-close-button-active-bg-color:rgb(251 251 254 / 0.21); + --message-bar-close-button-focus-bg-color:rgb(251 251 254 / 0.07); + } +} + +:where(html.is-dark) .messageBar{ + --message-bar-close-button-hover-bg-color:rgb(251 251 254 / 0.14); + --message-bar-close-button-active-bg-color:rgb(251 251 254 / 0.21); + --message-bar-close-button-focus-bg-color:rgb(251 251 254 / 0.07); +} + +@media screen and (forced-colors: active){ + + .messageBar{ + --message-bar-close-button-color:ButtonText; + --message-bar-close-button-border:1px solid ButtonText; + --message-bar-close-button-hover-bg-color:ButtonText; + --message-bar-close-button-active-bg-color:ButtonText; + --message-bar-close-button-focus-bg-color:ButtonText; + --message-bar-close-button-color-hover:HighlightText; + } +} + +.messageBar{ + + display:flex; + position:relative; + padding:8px 8px 8px 16px; + flex-direction:column; + justify-content:center; + align-items:center; + gap:8px; + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; + + border-radius:4px; + + border:1px solid var(--message-bar-border-color); + background:var(--message-bar-bg-color); + color:var(--message-bar-fg-color); +} + +.messageBar > div{ + display:flex; + align-items:flex-start; + gap:8px; + align-self:stretch; +} + +:is(.messageBar > div)::before{ + content:""; + display:inline-block; + width:16px; + height:16px; + -webkit-mask-image:var(--message-bar-icon); + mask-image:var(--message-bar-icon); + -webkit-mask-size:cover; + mask-size:cover; + background-color:var(--message-bar-icon-color); + flex-shrink:0; +} + +.messageBar button{ + cursor:pointer; +} + +:is(.messageBar button):focus-visible{ + outline:var(--focus-ring-outline); + outline-offset:2px; +} + +.messageBar .closeButton{ + width:32px; + height:32px; + background:none; + border-radius:var(--message-bar-close-button-border-radius); + border:var(--message-bar-close-button-border); + + display:flex; + align-items:center; + justify-content:center; +} + +:is(.messageBar .closeButton)::before{ + content:""; + display:inline-block; + width:16px; + height:16px; + -webkit-mask-image:var(--closing-button-icon); + mask-image:var(--closing-button-icon); + -webkit-mask-size:cover; + mask-size:cover; + background-color:var(--message-bar-close-button-color); +} + +:is(.messageBar .closeButton):is(:hover,:active,:focus)::before{ + background-color:var(--message-bar-close-button-color-hover); +} + +:is(.messageBar .closeButton):hover{ + background-color:var(--message-bar-close-button-hover-bg-color); +} + +:is(.messageBar .closeButton):active{ + background-color:var(--message-bar-close-button-active-bg-color); +} + +:is(.messageBar .closeButton):focus{ + background-color:var(--message-bar-close-button-focus-bg-color); +} + +:is(.messageBar .closeButton) > span{ + display:inline-block; + width:0; + height:0; + overflow:hidden; +} + +#editorUndoBar{ + --text-primary-color:#15141a; + + --message-bar-icon:url(images/secondaryToolbarButton-documentProperties.svg); + --message-bar-icon-color:#0060df; + --message-bar-bg-color:#deeafc; + --message-bar-fg-color:var(--text-primary-color); + --message-bar-border-color:rgb(0 0 0 / 0.08); + + --undo-button-bg-color:rgb(21 20 26 / 0.07); + --undo-button-bg-color-hover:rgb(21 20 26 / 0.14); + --undo-button-bg-color-active:rgb(21 20 26 / 0.21); + + --undo-button-fg-color:var(--message-bar-fg-color); + --undo-button-fg-color-hover:var(--undo-button-fg-color); + --undo-button-fg-color-active:var(--undo-button-fg-color); +} + +@media (prefers-color-scheme: dark){ + + :where(html:not(.is-light)) #editorUndoBar{ + --text-primary-color:#fbfbfe; + + --message-bar-icon-color:#73a7f3; + --message-bar-bg-color:#003070; + --message-bar-border-color:rgb(255 255 255 / 0.08); + + --undo-button-bg-color:rgb(255 255 255 / 0.08); + --undo-button-bg-color-hover:rgb(255 255 255 / 0.14); + --undo-button-bg-color-active:rgb(255 255 255 / 0.21); + } +} + +:where(html.is-dark) #editorUndoBar{ + --text-primary-color:#fbfbfe; + + --message-bar-icon-color:#73a7f3; + --message-bar-bg-color:#003070; + --message-bar-border-color:rgb(255 255 255 / 0.08); + + --undo-button-bg-color:rgb(255 255 255 / 0.08); + --undo-button-bg-color-hover:rgb(255 255 255 / 0.14); + --undo-button-bg-color-active:rgb(255 255 255 / 0.21); +} + +@media screen and (forced-colors: active){ + + #editorUndoBar{ + --text-primary-color:CanvasText; + + --message-bar-icon-color:CanvasText; + --message-bar-bg-color:Canvas; + --message-bar-border-color:CanvasText; + + --undo-button-bg-color:ButtonText; + --undo-button-bg-color-hover:SelectedItem; + --undo-button-bg-color-active:SelectedItem; + + --undo-button-fg-color:ButtonFace; + --undo-button-fg-color-hover:SelectedItemText; + --undo-button-fg-color-active:SelectedItemText; + } +} + +#editorUndoBar{ + + position:fixed; + top:50px; + left:50%; + transform:translateX(-50%); + z-index:10; + + padding-block:8px; + padding-inline:16px 8px; + + font:menu; + font-size:15px; + + cursor:default; +} + +#editorUndoBar button{ + cursor:pointer; +} + +#editorUndoBar #editorUndoBarUndoButton{ + border-radius:4px; + font-weight:590; + line-height:19.5px; + color:var(--undo-button-fg-color); + border:none; + padding:4px 16px; + margin-inline-start:8px; + height:32px; + + background-color:var(--undo-button-bg-color); +} + +:is(#editorUndoBar #editorUndoBarUndoButton):hover{ + background-color:var(--undo-button-bg-color-hover); + color:var(--undo-button-fg-color-hover); +} + +:is(#editorUndoBar #editorUndoBarUndoButton):active{ + background-color:var(--undo-button-bg-color-active); + color:var(--undo-button-fg-color-active); +} + +#editorUndoBar > div{ + align-items:center; +} + +.dialog{ + --dialog-bg-color:white; + --dialog-border-color:white; + --dialog-shadow:0 2px 14px 0 rgb(58 57 68 / 0.2); + --text-primary-color:#15141a; + --text-secondary-color:#5b5b66; + --hover-filter:brightness(0.9); + --link-fg-color:#0060df; + --link-hover-fg-color:#0250bb; + --separator-color:#f0f0f4; + + --textarea-border-color:#8f8f9d; + --textarea-bg-color:white; + --textarea-fg-color:var(--text-secondary-color); + + --radio-bg-color:#f0f0f4; + --radio-checked-bg-color:#fbfbfe; + --radio-border-color:#8f8f9d; + --radio-checked-border-color:#0060df; + + --button-secondary-bg-color:#f0f0f4; + --button-secondary-fg-color:var(--text-primary-color); + --button-secondary-border-color:var(--button-secondary-bg-color); + --button-secondary-hover-bg-color:var(--button-secondary-bg-color); + --button-secondary-hover-fg-color:var(--button-secondary-fg-color); + --button-secondary-hover-border-color:var(--button-secondary-hover-bg-color); + + --button-primary-bg-color:#0060df; + --button-primary-fg-color:#fbfbfe; + --button-primary-border-color:var(--button-primary-bg-color); + --button-primary-hover-bg-color:var(--button-primary-bg-color); + --button-primary-hover-fg-color:var(--button-primary-fg-color); + --button-primary-hover-border-color:var(--button-primary-hover-bg-color); + + --button-disabled-bg-color:color-mix(in srgb, currentcolor, transparent 60%); + --button-disabled-fg-color:var(--button-disabled-bg-color); +} + +@media (prefers-color-scheme: dark){ + + :where(html:not(.is-light)) .dialog{ + --dialog-bg-color:#1c1b22; + --dialog-border-color:#1c1b22; + --dialog-shadow:0 2px 14px 0 #15141a; + --text-primary-color:#fbfbfe; + --text-secondary-color:#cfcfd8; + --hover-filter:brightness(1.4); + --link-fg-color:#0df; + --link-hover-fg-color:#80ebff; + --separator-color:#52525e; + + --textarea-bg-color:#42414d; + + --radio-bg-color:#2b2a33; + --radio-checked-bg-color:#15141a; + --radio-checked-border-color:#0df; + + --button-secondary-bg-color:#2b2a33; + --button-primary-bg-color:#0df; + --button-primary-fg-color:#15141a; + } +} + +:where(html.is-dark) .dialog{ + --dialog-bg-color:#1c1b22; + --dialog-border-color:#1c1b22; + --dialog-shadow:0 2px 14px 0 #15141a; + --text-primary-color:#fbfbfe; + --text-secondary-color:#cfcfd8; + --hover-filter:brightness(1.4); + --link-fg-color:#0df; + --link-hover-fg-color:#80ebff; + --separator-color:#52525e; + + --textarea-bg-color:#42414d; + + --radio-bg-color:#2b2a33; + --radio-checked-bg-color:#15141a; + --radio-checked-border-color:#0df; + + --button-secondary-bg-color:#2b2a33; + --button-primary-bg-color:#0df; + --button-primary-fg-color:#15141a; +} + +@media screen and (forced-colors: active){ + + .dialog{ + --dialog-bg-color:Canvas; + --dialog-border-color:CanvasText; + --dialog-shadow:none; + --text-primary-color:CanvasText; + --text-secondary-color:CanvasText; + --hover-filter:none; + --link-fg-color:LinkText; + --link-hover-fg-color:LinkText; + --separator-color:CanvasText; + + --textarea-border-color:ButtonBorder; + --textarea-bg-color:Field; + --textarea-fg-color:ButtonText; + + --radio-bg-color:ButtonFace; + --radio-checked-bg-color:ButtonFace; + --radio-border-color:ButtonText; + --radio-checked-border-color:ButtonText; + + --button-secondary-bg-color:ButtonFace; + --button-secondary-fg-color:ButtonText; + --button-secondary-border-color:ButtonText; + --button-secondary-hover-bg-color:AccentColor; + --button-secondary-hover-fg-color:AccentColorText; + + --button-primary-bg-color:ButtonText; + --button-primary-fg-color:ButtonFace; + --button-primary-hover-bg-color:AccentColor; + --button-primary-hover-fg-color:AccentColorText; + + --button-disabled-bg-color:GrayText; + --button-disabled-fg-color:ButtonFace; + } +} + +.dialog{ + + font:message-box; + font-size:13px; + font-weight:400; + line-height:150%; + border-radius:4px; + padding:12px 16px; + border:1px solid var(--dialog-border-color); + background:var(--dialog-bg-color); + color:var(--text-primary-color); + box-shadow:var(--dialog-shadow); +} + +:is(.dialog .mainContainer) *:focus-visible{ + outline:var(--focus-ring-outline); + outline-offset:2px; +} + +:is(.dialog .mainContainer) .title{ + display:flex; + width:auto; + flex-direction:column; + justify-content:flex-end; + align-items:flex-start; + gap:12px; +} + +:is(:is(.dialog .mainContainer) .title) > span{ + font-size:13px; + font-style:normal; + font-weight:590; + line-height:150%; +} + +:is(.dialog .mainContainer) .dialogSeparator{ + width:100%; + height:0; + margin-block:4px; + border-top:1px solid var(--separator-color); + border-bottom:none; +} + +:is(.dialog .mainContainer) .dialogButtonsGroup{ + display:flex; + gap:12px; + align-self:flex-end; +} + +:is(.dialog .mainContainer) .radio{ + display:flex; + flex-direction:column; + align-items:flex-start; + gap:4px; +} + +:is(:is(.dialog .mainContainer) .radio) > .radioButton{ + display:flex; + gap:8px; + align-self:stretch; + align-items:center; +} + +:is(:is(:is(.dialog .mainContainer) .radio) > .radioButton) input{ + -webkit-appearance:none; + -moz-appearance:none; + appearance:none; + box-sizing:border-box; + width:16px; + height:16px; + border-radius:50%; + background-color:var(--radio-bg-color); + border:1px solid var(--radio-border-color); +} + +:is(:is(:is(:is(.dialog .mainContainer) .radio) > .radioButton) input):hover{ + filter:var(--hover-filter); +} + +:is(:is(:is(:is(.dialog .mainContainer) .radio) > .radioButton) input):checked{ + background-color:var(--radio-checked-bg-color); + border:4px solid var(--radio-checked-border-color); +} + +:is(:is(.dialog .mainContainer) .radio) > .radioLabel{ + display:flex; + padding-inline-start:24px; + align-items:flex-start; + gap:10px; + align-self:stretch; +} + +:is(:is(:is(.dialog .mainContainer) .radio) > .radioLabel) > span{ + flex:1 0 0; + font-size:11px; + color:var(--text-secondary-color); +} + +:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton)){ + border-radius:4px; + border:1px solid; + font:menu; + font-weight:600; + padding:4px 16px; + width:auto; + height:32px; +} + +:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton))):hover{ + cursor:pointer; + filter:var(--hover-filter); +} + +:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton))) > span{ + color:inherit; +} + +.secondaryButton:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton))){ + color:var(--button-secondary-fg-color); + background-color:var(--button-secondary-bg-color); + border-color:var(--button-secondary-border-color); +} + +.secondaryButton:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton))):hover{ + color:var(--button-secondary-hover-fg-color); + background-color:var(--button-secondary-hover-bg-color); + border-color:var(--button-secondary-hover-border-color); +} + +.primaryButton:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton))){ + color:var(--button-primary-fg-color); + background-color:var(--button-primary-bg-color); + border-color:var(--button-primary-border-color); + opacity:1; +} + +.primaryButton:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton))):hover{ + color:var(--button-primary-hover-fg-color); + background-color:var(--button-primary-hover-bg-color); + border-color:var(--button-primary-hover-border-color); +} + +:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton,.clearInputButton))):disabled{ + color:var(--button-disabled-fg-color) !important; + background-color:var(--button-disabled-bg-color); + border-color:var(--button-disabled-bg-color); + pointer-events:none; +} + +:is(.dialog .mainContainer) a{ + color:var(--link-fg-color); +} + +:is(:is(.dialog .mainContainer) a):hover{ + color:var(--link-hover-fg-color); +} + +:is(.dialog .mainContainer) textarea{ + font:inherit; + padding:8px; + resize:none; + margin:0; + box-sizing:border-box; + border-radius:4px; + border:1px solid var(--textarea-border-color); + background:var(--textarea-bg-color); + color:var(--textarea-fg-color); +} + +:is(:is(.dialog .mainContainer) textarea):focus{ + outline-offset:0; + border-color:transparent; +} + +:is(:is(.dialog .mainContainer) textarea):disabled{ + pointer-events:none; + opacity:0.4; +} + +:is(.dialog .mainContainer) .messageBar{ + --message-bar-bg-color:#ffebcd; + --message-bar-fg-color:#15141a; + --message-bar-border-color:rgb(0 0 0 / 0.08); + --message-bar-icon:url(images/messageBar_warning.svg); + --message-bar-icon-color:#cd411e; +} + +@media (prefers-color-scheme: dark){ + + :where(html:not(.is-light)) :is(.dialog .mainContainer) .messageBar{ + --message-bar-bg-color:#5a3100; + --message-bar-fg-color:#fbfbfe; + --message-bar-border-color:rgb(255 255 255 / 0.08); + --message-bar-icon-color:#e49c49; + } +} + +:where(html.is-dark) :is(.dialog .mainContainer) .messageBar{ + --message-bar-bg-color:#5a3100; + --message-bar-fg-color:#fbfbfe; + --message-bar-border-color:rgb(255 255 255 / 0.08); + --message-bar-icon-color:#e49c49; +} + +@media screen and (forced-colors: active){ + + :is(.dialog .mainContainer) .messageBar{ + --message-bar-bg-color:HighlightText; + --message-bar-fg-color:CanvasText; + --message-bar-border-color:CanvasText; + --message-bar-icon-color:CanvasText; + } +} + +:is(.dialog .mainContainer) .messageBar{ + + align-self:stretch; +} + +:is(:is(:is(.dialog .mainContainer) .messageBar) > div)::before,:is(:is(:is(.dialog .mainContainer) .messageBar) > div) > div{ + margin-block:4px; +} + +:is(:is(:is(.dialog .mainContainer) .messageBar) > div) > div{ + display:flex; + flex-direction:column; + align-items:flex-start; + gap:8px; + flex:1 0 0; +} + +:is(:is(:is(:is(.dialog .mainContainer) .messageBar) > div) > div) .title{ + font-size:13px; + font-weight:590; +} + +:is(:is(:is(:is(.dialog .mainContainer) .messageBar) > div) > div) .description{ + font-size:13px; +} + +:is(.dialog .mainContainer) .toggler{ + display:flex; + align-items:center; + gap:8px; + align-self:stretch; +} + +:is(:is(.dialog .mainContainer) .toggler) > .togglerLabel{ + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; +} + +.textLayer{ + position:absolute; + text-align:initial; + inset:0; + overflow:clip; + opacity:1; + line-height:1; + -webkit-text-size-adjust:none; + -moz-text-size-adjust:none; + text-size-adjust:none; + forced-color-adjust:none; + transform-origin:0 0; + caret-color:CanvasText; + z-index:0; +} + +.textLayer.highlighting{ + touch-action:none; +} + +.textLayer :is(span,br){ + color:transparent; + position:absolute; + white-space:pre; + cursor:text; + transform-origin:0% 0%; +} + +.textLayer > :not(.markedContent),.textLayer .markedContent span:not(.markedContent){ + z-index:1; +} + +.textLayer span.markedContent{ + top:0; + height:0; +} + +.textLayer span[role="img"]{ + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; + cursor:default; +} + +.textLayer .highlight{ + --highlight-bg-color:rgb(180 0 170 / 0.25); + --highlight-selected-bg-color:rgb(0 100 0 / 0.25); + --highlight-backdrop-filter:none; + --highlight-selected-backdrop-filter:none; +} + +@media screen and (forced-colors: active){ + + .textLayer .highlight{ + --highlight-bg-color:transparent; + --highlight-selected-bg-color:transparent; + --highlight-backdrop-filter:var(--hcm-highlight-filter); + --highlight-selected-backdrop-filter:var( + --hcm-highlight-selected-filter + ); + } +} + +.textLayer .highlight{ + + margin:-1px; + padding:1px; + background-color:var(--highlight-bg-color); + -webkit-backdrop-filter:var(--highlight-backdrop-filter); + backdrop-filter:var(--highlight-backdrop-filter); + border-radius:4px; +} + +.appended:is(.textLayer .highlight){ + position:initial; +} + +.begin:is(.textLayer .highlight){ + border-radius:4px 0 0 4px; +} + +.end:is(.textLayer .highlight){ + border-radius:0 4px 4px 0; +} + +.middle:is(.textLayer .highlight){ + border-radius:0; +} + +.selected:is(.textLayer .highlight){ + background-color:var(--highlight-selected-bg-color); + -webkit-backdrop-filter:var(--highlight-selected-backdrop-filter); + backdrop-filter:var(--highlight-selected-backdrop-filter); +} + +.textLayer ::-moz-selection{ + background:rgba(0 0 255 / 0.25); + background:color-mix(in srgb, AccentColor, transparent 75%); +} + +.textLayer ::selection{ + background:rgba(0 0 255 / 0.25); + background:color-mix(in srgb, AccentColor, transparent 75%); +} + +.textLayer br::-moz-selection{ + background:transparent; +} + +.textLayer br::selection{ + background:transparent; +} + +.textLayer .endOfContent{ + display:block; + position:absolute; + inset:100% 0 0; + z-index:0; + cursor:default; + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; +} + +.textLayer.selecting .endOfContent{ + top:0; +} + +.annotationLayer{ + --annotation-unfocused-field-background:url("data:image/svg+xml;charset=UTF-8,"); + --input-focus-border-color:Highlight; + --input-focus-outline:1px solid Canvas; + --input-unfocused-border-color:transparent; + --input-disabled-border-color:transparent; + --input-hover-border-color:black; + --link-outline:none; +} + +@media screen and (forced-colors: active){ + + .annotationLayer{ + --input-focus-border-color:CanvasText; + --input-unfocused-border-color:ActiveText; + --input-disabled-border-color:GrayText; + --input-hover-border-color:Highlight; + --link-outline:1.5px solid LinkText; + } + + .annotationLayer .textWidgetAnnotation :is(input,textarea):required,.annotationLayer .choiceWidgetAnnotation select:required,.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input:required{ + outline:1.5px solid selectedItem; + } + + .annotationLayer .linkAnnotation{ + outline:var(--link-outline); + } + + :is(.annotationLayer .linkAnnotation):hover{ + -webkit-backdrop-filter:var(--hcm-highlight-filter); + backdrop-filter:var(--hcm-highlight-filter); + } + + :is(.annotationLayer .linkAnnotation) > a:hover{ + opacity:0 !important; + background:none !important; + box-shadow:none; + } + + .annotationLayer .popupAnnotation .popup{ + outline:calc(1.5px * var(--total-scale-factor)) solid CanvasText !important; + background-color:ButtonFace !important; + color:ButtonText !important; + } + + .annotationLayer .highlightArea:hover::after{ + position:absolute; + top:0; + left:0; + width:100%; + height:100%; + -webkit-backdrop-filter:var(--hcm-highlight-filter); + backdrop-filter:var(--hcm-highlight-filter); + content:""; + pointer-events:none; + } + + .annotationLayer .popupAnnotation.focused .popup{ + outline:calc(3px * var(--total-scale-factor)) solid Highlight !important; + } +} + +.annotationLayer{ + + position:absolute; + top:0; + left:0; + pointer-events:none; + transform-origin:0 0; +} + +.annotationLayer[data-main-rotation="90"] .norotate{ + transform:rotate(270deg) translateX(-100%); +} + +.annotationLayer[data-main-rotation="180"] .norotate{ + transform:rotate(180deg) translate(-100%, -100%); +} + +.annotationLayer[data-main-rotation="270"] .norotate{ + transform:rotate(90deg) translateY(-100%); +} + +.annotationLayer.disabled section,.annotationLayer.disabled .popup{ + pointer-events:none; +} + +.annotationLayer .annotationContent{ + position:absolute; + width:100%; + height:100%; + pointer-events:none; +} + +.freetext:is(.annotationLayer .annotationContent){ + background:transparent; + border:none; + inset:0; + overflow:visible; + white-space:nowrap; + font:10px sans-serif; + line-height:1.35; +} + +.annotationLayer section{ + position:absolute; + text-align:initial; + pointer-events:auto; + box-sizing:border-box; + transform-origin:0 0; + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; +} + +:is(.annotationLayer section):has(div.annotationContent) canvas.annotationContent{ + display:none; +} + +.textLayer.selecting ~ .annotationLayer section{ + pointer-events:none; +} + +.annotationLayer :is(.linkAnnotation,.buttonWidgetAnnotation.pushButton) > a{ + position:absolute; + font-size:1em; + top:0; + left:0; + width:100%; + height:100%; +} + +.annotationLayer :is(.linkAnnotation,.buttonWidgetAnnotation.pushButton):not(.hasBorder) > a:hover{ + opacity:0.2; + background-color:rgb(255 255 0); + box-shadow:0 2px 10px rgb(255 255 0); +} + +.annotationLayer .linkAnnotation.hasBorder:hover{ + background-color:rgb(255 255 0 / 0.2); +} + +.annotationLayer .hasBorder{ + background-size:100% 100%; +} + +.annotationLayer .textAnnotation img{ + position:absolute; + cursor:pointer; + width:100%; + height:100%; + top:0; + left:0; +} + +.annotationLayer .textWidgetAnnotation :is(input,textarea),.annotationLayer .choiceWidgetAnnotation select,.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input{ + background-image:var(--annotation-unfocused-field-background); + border:2px solid var(--input-unfocused-border-color); + box-sizing:border-box; + font:calc(9px * var(--total-scale-factor)) sans-serif; + height:100%; + margin:0; + vertical-align:top; + width:100%; +} + +.annotationLayer .textWidgetAnnotation :is(input,textarea):required,.annotationLayer .choiceWidgetAnnotation select:required,.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input:required{ + outline:1.5px solid red; +} + +.annotationLayer .choiceWidgetAnnotation select option{ + padding:0; +} + +.annotationLayer .buttonWidgetAnnotation.radioButton input{ + border-radius:50%; +} + +.annotationLayer .textWidgetAnnotation textarea{ + resize:none; +} + +.annotationLayer .textWidgetAnnotation [disabled]:is(input,textarea),.annotationLayer .choiceWidgetAnnotation select[disabled],.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input[disabled]{ + background:none; + border:2px solid var(--input-disabled-border-color); + cursor:not-allowed; +} + +.annotationLayer .textWidgetAnnotation :is(input,textarea):hover,.annotationLayer .choiceWidgetAnnotation select:hover,.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input:hover{ + border:2px solid var(--input-hover-border-color); +} + +.annotationLayer .textWidgetAnnotation :is(input,textarea):hover,.annotationLayer .choiceWidgetAnnotation select:hover,.annotationLayer .buttonWidgetAnnotation.checkBox input:hover{ + border-radius:2px; +} + +.annotationLayer .textWidgetAnnotation :is(input,textarea):focus,.annotationLayer .choiceWidgetAnnotation select:focus{ + background:none; + border:2px solid var(--input-focus-border-color); + border-radius:2px; + outline:var(--input-focus-outline); +} + +.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) :focus{ + background-image:none; + background-color:transparent; +} + +.annotationLayer .buttonWidgetAnnotation.checkBox :focus{ + border:2px solid var(--input-focus-border-color); + border-radius:2px; + outline:var(--input-focus-outline); +} + +.annotationLayer .buttonWidgetAnnotation.radioButton :focus{ + border:2px solid var(--input-focus-border-color); + outline:var(--input-focus-outline); +} + +.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before,.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after,.annotationLayer .buttonWidgetAnnotation.radioButton input:checked::before{ + background-color:CanvasText; + content:""; + display:block; + position:absolute; +} + +.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before,.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after{ + height:80%; + left:45%; + width:1px; +} + +.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before{ + transform:rotate(45deg); +} + +.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after{ + transform:rotate(-45deg); +} + +.annotationLayer .buttonWidgetAnnotation.radioButton input:checked::before{ + border-radius:50%; + height:50%; + left:25%; + top:25%; + width:50%; +} + +.annotationLayer .textWidgetAnnotation input.comb{ + font-family:monospace; + padding-left:2px; + padding-right:0; +} + +.annotationLayer .textWidgetAnnotation input.comb:focus{ + width:103%; +} + +.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input{ + -webkit-appearance:none; + -moz-appearance:none; + appearance:none; +} + +.annotationLayer .fileAttachmentAnnotation .popupTriggerArea{ + height:100%; + width:100%; +} + +.annotationLayer .popupAnnotation{ + position:absolute; + font-size:calc(9px * var(--total-scale-factor)); + pointer-events:none; + width:-moz-max-content; + width:max-content; + max-width:45%; + height:auto; +} + +.annotationLayer .popup{ + background-color:rgb(255 255 153); + box-shadow:0 calc(2px * var(--total-scale-factor)) calc(5px * var(--total-scale-factor)) rgb(136 136 136); + border-radius:calc(2px * var(--total-scale-factor)); + outline:1.5px solid rgb(255 255 74); + padding:calc(6px * var(--total-scale-factor)); + cursor:pointer; + font:message-box; + white-space:normal; + word-wrap:break-word; + pointer-events:auto; + -webkit-user-select:text; + -moz-user-select:text; + user-select:text; +} + +.annotationLayer .popupAnnotation.focused .popup{ + outline-width:3px; +} + +.annotationLayer .popup *{ + font-size:calc(9px * var(--total-scale-factor)); +} + +.annotationLayer .popup > .header{ + display:inline-block; +} + +.annotationLayer .popup > .header h1{ + display:inline; +} + +.annotationLayer .popup > .header .popupDate{ + display:inline-block; + margin-left:calc(5px * var(--total-scale-factor)); + width:-moz-fit-content; + width:fit-content; +} + +.annotationLayer .popupContent{ + border-top:1px solid rgb(51 51 51); + margin-top:calc(2px * var(--total-scale-factor)); + padding-top:calc(2px * var(--total-scale-factor)); +} + +.annotationLayer .richText > *{ + white-space:pre-wrap; + font-size:calc(9px * var(--total-scale-factor)); +} + +.annotationLayer .popupTriggerArea{ + cursor:pointer; +} + +.annotationLayer section svg{ + position:absolute; + width:100%; + height:100%; + top:0; + left:0; +} + +.annotationLayer .annotationTextContent{ + position:absolute; + width:100%; + height:100%; + opacity:0; + color:transparent; + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; + pointer-events:none; +} + +:is(.annotationLayer .annotationTextContent) span{ + width:100%; + display:inline-block; +} + +.annotationLayer svg.quadrilateralsContainer{ + contain:strict; + width:0; + height:0; + position:absolute; + top:0; + left:0; + z-index:-1; +} + +:root{ + --xfa-unfocused-field-background:url("data:image/svg+xml;charset=UTF-8,"); + --xfa-focus-outline:auto; +} + +@media screen and (forced-colors: active){ + :root{ + --xfa-focus-outline:2px solid CanvasText; + } + .xfaLayer *:required{ + outline:1.5px solid selectedItem; + } +} + +.xfaLayer{ + background-color:transparent; +} + +.xfaLayer .highlight{ + margin:-1px; + padding:1px; + background-color:rgb(239 203 237); + border-radius:4px; +} + +.xfaLayer .highlight.appended{ + position:initial; +} + +.xfaLayer .highlight.begin{ + border-radius:4px 0 0 4px; +} + +.xfaLayer .highlight.end{ + border-radius:0 4px 4px 0; +} + +.xfaLayer .highlight.middle{ + border-radius:0; +} + +.xfaLayer .highlight.selected{ + background-color:rgb(203 223 203); +} + +.xfaPage{ + overflow:hidden; + position:relative; +} + +.xfaContentarea{ + position:absolute; +} + +.xfaPrintOnly{ + display:none; +} + +.xfaLayer{ + position:absolute; + text-align:initial; + top:0; + left:0; + transform-origin:0 0; + line-height:1.2; +} + +.xfaLayer *{ + color:inherit; + font:inherit; + font-style:inherit; + font-weight:inherit; + font-kerning:inherit; + letter-spacing:-0.01px; + text-align:inherit; + text-decoration:inherit; + box-sizing:border-box; + background-color:transparent; + padding:0; + margin:0; + pointer-events:auto; + line-height:inherit; +} + +.xfaLayer *:required{ + outline:1.5px solid red; +} + +.xfaLayer div, +.xfaLayer svg, +.xfaLayer svg *{ + pointer-events:none; +} + +.xfaLayer a{ + color:blue; +} + +.xfaRich li{ + margin-left:3em; +} + +.xfaFont{ + color:black; + font-weight:normal; + font-kerning:none; + font-size:10px; + font-style:normal; + letter-spacing:0; + text-decoration:none; + vertical-align:0; +} + +.xfaCaption{ + overflow:hidden; + flex:0 0 auto; +} + +.xfaCaptionForCheckButton{ + overflow:hidden; + flex:1 1 auto; +} + +.xfaLabel{ + height:100%; + width:100%; +} + +.xfaLeft{ + display:flex; + flex-direction:row; + align-items:center; +} + +.xfaRight{ + display:flex; + flex-direction:row-reverse; + align-items:center; +} + +:is(.xfaLeft, .xfaRight) > :is(.xfaCaption, .xfaCaptionForCheckButton){ + max-height:100%; +} + +.xfaTop{ + display:flex; + flex-direction:column; + align-items:flex-start; +} + +.xfaBottom{ + display:flex; + flex-direction:column-reverse; + align-items:flex-start; +} + +:is(.xfaTop, .xfaBottom) > :is(.xfaCaption, .xfaCaptionForCheckButton){ + width:100%; +} + +.xfaBorder{ + background-color:transparent; + position:absolute; + pointer-events:none; +} + +.xfaWrapped{ + width:100%; + height:100%; +} + +:is(.xfaTextfield, .xfaSelect):focus{ + background-image:none; + background-color:transparent; + outline:var(--xfa-focus-outline); + outline-offset:-1px; +} + +:is(.xfaCheckbox, .xfaRadio):focus{ + outline:var(--xfa-focus-outline); +} + +.xfaTextfield, +.xfaSelect{ + height:100%; + width:100%; + flex:1 1 auto; + border:none; + resize:none; + background-image:var(--xfa-unfocused-field-background); +} + +.xfaSelect{ + padding-inline:2px; +} + +:is(.xfaTop, .xfaBottom) > :is(.xfaTextfield, .xfaSelect){ + flex:0 1 auto; +} + +.xfaButton{ + cursor:pointer; + width:100%; + height:100%; + border:none; + text-align:center; +} + +.xfaLink{ + width:100%; + height:100%; + position:absolute; + top:0; + left:0; +} + +.xfaCheckbox, +.xfaRadio{ + width:100%; + height:100%; + flex:0 0 auto; + border:none; +} + +.xfaRich{ + white-space:pre-wrap; + width:100%; + height:100%; +} + +.xfaImage{ + -o-object-position:left top; + object-position:left top; + -o-object-fit:contain; + object-fit:contain; + width:100%; + height:100%; +} + +.xfaLrTb, +.xfaRlTb, +.xfaTb{ + display:flex; + flex-direction:column; + align-items:stretch; +} + +.xfaLr{ + display:flex; + flex-direction:row; + align-items:stretch; +} + +.xfaRl{ + display:flex; + flex-direction:row-reverse; + align-items:stretch; +} + +.xfaTb > div{ + justify-content:left; +} + +.xfaPosition{ + position:relative; +} + +.xfaArea{ + position:relative; +} + +.xfaValignMiddle{ + display:flex; + align-items:center; +} + +.xfaTable{ + display:flex; + flex-direction:column; + align-items:stretch; +} + +.xfaTable .xfaRow{ + display:flex; + flex-direction:row; + align-items:stretch; +} + +.xfaTable .xfaRlRow{ + display:flex; + flex-direction:row-reverse; + align-items:stretch; + flex:1; +} + +.xfaTable .xfaRlRow > div{ + flex:1; +} + +:is(.xfaNonInteractive, .xfaDisabled, .xfaReadOnly) :is(input, textarea){ + background:initial; +} + +@media print{ + .xfaTextfield, + .xfaSelect{ + background:transparent; + } + + .xfaSelect{ + -webkit-appearance:none; + -moz-appearance:none; + appearance:none; + text-indent:1px; + text-overflow:""; + } +} + +.canvasWrapper svg{ + transform:none; +} + +.moving:is(.canvasWrapper svg){ + z-index:100000; +} + +[data-main-rotation="90"]:is(.highlight:is(.canvasWrapper svg),.highlightOutline:is(.canvasWrapper svg)) mask,[data-main-rotation="90"]:is(.highlight:is(.canvasWrapper svg),.highlightOutline:is(.canvasWrapper svg)) use:not(.clip,.mask){ + transform:matrix(0, 1, -1, 0, 1, 0); +} + +[data-main-rotation="180"]:is(.highlight:is(.canvasWrapper svg),.highlightOutline:is(.canvasWrapper svg)) mask,[data-main-rotation="180"]:is(.highlight:is(.canvasWrapper svg),.highlightOutline:is(.canvasWrapper svg)) use:not(.clip,.mask){ + transform:matrix(-1, 0, 0, -1, 1, 1); +} + +[data-main-rotation="270"]:is(.highlight:is(.canvasWrapper svg),.highlightOutline:is(.canvasWrapper svg)) mask,[data-main-rotation="270"]:is(.highlight:is(.canvasWrapper svg),.highlightOutline:is(.canvasWrapper svg)) use:not(.clip,.mask){ + transform:matrix(0, -1, 1, 0, 0, 1); +} + +.draw:is(.canvasWrapper svg){ + position:absolute; + mix-blend-mode:normal; +} + +.draw[data-draw-rotation="90"]:is(.canvasWrapper svg){ + transform:rotate(90deg); +} + +.draw[data-draw-rotation="180"]:is(.canvasWrapper svg){ + transform:rotate(180deg); +} + +.draw[data-draw-rotation="270"]:is(.canvasWrapper svg){ + transform:rotate(270deg); +} + +.highlight:is(.canvasWrapper svg){ + --blend-mode:multiply; +} + +@media screen and (forced-colors: active){ + + .highlight:is(.canvasWrapper svg){ + --blend-mode:difference; + } +} + +.highlight:is(.canvasWrapper svg){ + + position:absolute; + mix-blend-mode:var(--blend-mode); +} + +.highlight:is(.canvasWrapper svg):not(.free){ + fill-rule:evenodd; +} + +.highlightOutline:is(.canvasWrapper svg){ + position:absolute; + mix-blend-mode:normal; + fill-rule:evenodd; + fill:none; +} + +.highlightOutline.hovered:is(.canvasWrapper svg):not(.free):not(.selected){ + stroke:var(--hover-outline-color); + stroke-width:var(--outline-width); +} + +.highlightOutline.selected:is(.canvasWrapper svg):not(.free) .mainOutline{ + stroke:var(--outline-around-color); + stroke-width:calc( + var(--outline-width) + 2 * var(--outline-around-width) + ); +} + +.highlightOutline.selected:is(.canvasWrapper svg):not(.free) .secondaryOutline{ + stroke:var(--outline-color); + stroke-width:var(--outline-width); +} + +.highlightOutline.free.hovered:is(.canvasWrapper svg):not(.selected){ + stroke:var(--hover-outline-color); + stroke-width:calc(2 * var(--outline-width)); +} + +.highlightOutline.free.selected:is(.canvasWrapper svg) .mainOutline{ + stroke:var(--outline-around-color); + stroke-width:calc( + 2 * (var(--outline-width) + var(--outline-around-width)) + ); +} + +.highlightOutline.free.selected:is(.canvasWrapper svg) .secondaryOutline{ + stroke:var(--outline-color); + stroke-width:calc(2 * var(--outline-width)); +} + +.toggle-button{ + --button-background-color:color-mix(in srgb, currentColor 7%, transparent); + --button-background-color-hover:color-mix( + in srgb, + currentColor 14%, + transparent + ); + --button-background-color-active:color-mix( + in srgb, + currentColor 21%, + transparent + ); + --color-accent-primary:#0060df; + --color-accent-primary-hover:#0250bb; + --color-accent-primary-active:#054096; + --border-radius-circle:9999px; + --border-width:1px; + --size-item-small:16px; + --size-item-large:32px; + --color-canvas:white; + --background-color-canvas:var(--color-canvas); + --border-color-interactive:#8f8f9d; + --border-color-interactive-hover:var(--border-color-interactive); + --border-color-interactive-active:var(--border-color-interactive); +} + +@media (prefers-color-scheme: dark){ + + :where(html:not(.is-light)) .toggle-button{ + --color-accent-primary:#0df; + --color-accent-primary-hover:#80ebff; + --color-accent-primary-active:#aaf2ff; + --color-canvas:#1c1b22; + --border-color-interactive:#f9f9fa; + } +} + +:where(html.is-dark) .toggle-button{ + --color-accent-primary:#0df; + --color-accent-primary-hover:#80ebff; + --color-accent-primary-active:#aaf2ff; + --color-canvas:#1c1b22; + --border-color-interactive:#f9f9fa; +} + +@media (forced-colors: active){ + + .toggle-button{ + --color-accent-primary:ButtonText; + --color-accent-primary-hover:SelectedItem; + --color-accent-primary-active:SelectedItem; + --button-background-color:ButtonFace; + --border-color-interactive:ButtonText; + --border-color-interactive-hover:SelectedItem; + --border-color-interactive-active:ButtonText; + --color-canvas:ButtonText; + --background-color-canvas:Canvas; + } +} + +.toggle-button{ + --toggle-background-color:var(--button-background-color); + --toggle-background-color-hover:var(--button-background-color-hover); + --toggle-background-color-active:var(--button-background-color-active); + --toggle-background-color-pressed:var(--color-accent-primary); + --toggle-background-color-pressed-hover:var(--color-accent-primary-hover); + --toggle-background-color-pressed-active:var(--color-accent-primary-active); + --toggle-border-color:var(--border-color-interactive); + --toggle-border-color-hover:var(--toggle-border-color); + --toggle-border-color-active:var(--toggle-border-color); + --toggle-border-radius:var(--border-radius-circle); + --toggle-border-width:var(--border-width); + --toggle-height:var(--size-item-small); + --toggle-width:var(--size-item-large); + --toggle-dot-background-color:var(--toggle-border-color); + --toggle-dot-background-color-hover:var(--toggle-dot-background-color); + --toggle-dot-background-color-active:var(--toggle-dot-background-color); + --toggle-dot-background-color-on-pressed:var(--background-color-canvas); + --toggle-dot-margin:1px; + --toggle-dot-height:calc( + var(--toggle-height) - 2 * var(--toggle-dot-margin) - 2 * + var(--toggle-border-width) + ); + --toggle-dot-width:var(--toggle-dot-height); + --toggle-dot-transform-x:calc( + var(--toggle-width) - 4 * var(--toggle-dot-margin) - var(--toggle-dot-width) + ); + --input-width:var(--toggle-width); + + -webkit-appearance:none; + + -moz-appearance:none; + + appearance:none; + padding:0; + border:var(--toggle-border-width) solid var(--toggle-border-color); + height:var(--toggle-height); + width:var(--toggle-width); + border-radius:var(--toggle-border-radius); + background-color:var(--toggle-background-color); + box-sizing:border-box; +} + +.toggle-button:focus-visible{ + outline:var(--focus-outline); + outline-offset:var(--focus-outline-offset); +} + +.toggle-button:enabled:hover{ + background-color:var(--toggle-background-color-hover); + border-color:var(--toggle-border-color); +} + +.toggle-button:enabled:hover:active{ + background-color:var(--toggle-background-color-active); + border-color:var(--toggle-border-color); +} + +.toggle-button::before{ + display:block; + content:""; + background-color:var(--toggle-dot-background-color); + height:var(--toggle-dot-height); + width:var(--toggle-dot-width); + margin:var(--toggle-dot-margin); + border-radius:var(--toggle-border-radius); + translate:0; +} + +.toggle-button[aria-pressed="true"]{ + background-color:var(--toggle-background-color-pressed); + border-color:transparent; +} + +.toggle-button[aria-pressed="true"]:enabled:hover{ + background-color:var(--toggle-background-color-pressed-hover); + border-color:transparent; +} + +.toggle-button[aria-pressed="true"]:enabled:hover:active{ + background-color:var(--toggle-background-color-pressed-active); + border-color:transparent; +} + +.toggle-button[aria-pressed="true"]::before{ + translate:var(--toggle-dot-transform-x); + background-color:var(--toggle-dot-background-color-on-pressed); +} + +.toggle-button[aria-pressed="true"]:enabled:hover::before,.toggle-button[aria-pressed="true"]:enabled:hover:active::before{ + background-color:var(--toggle-dot-background-color-on-pressed); +} + +.toggle-button[aria-pressed="true"]:-moz-locale-dir(rtl)::before,[dir="rtl"] .toggle-button[aria-pressed="true"]::before{ + translate:calc(-1 * var(--toggle-dot-transform-x)); +} + +@media (prefers-reduced-motion: no-preference){ + .toggle-button::before{ + transition:translate 100ms; + } +} + +@media (prefers-contrast){ + .toggle-button:enabled:hover{ + border-color:var(--toggle-border-color-hover); + } + + .toggle-button:enabled:hover:active{ + border-color:var(--toggle-border-color-active); + } + + .toggle-button[aria-pressed="true"]:enabled{ + border-color:var(--toggle-border-color); + position:relative; + } + + .toggle-button[aria-pressed="true"]:enabled:hover{ + border-color:var(--toggle-border-color-hover); + } + + .toggle-button[aria-pressed="true"]:enabled:hover:active{ + background-color:var(--toggle-dot-background-color-active); + border-color:var(--toggle-dot-background-color-hover); + } + + .toggle-button:enabled:hover::before, + .toggle-button:enabled:hover:active::before{ + background-color:var(--toggle-dot-background-color-hover); + } +} + +@media (forced-colors){ + .toggle-button{ + --toggle-dot-background-color:var(--color-accent-primary); + --toggle-dot-background-color-hover:var(--color-accent-primary-hover); + --toggle-dot-background-color-active:var(--color-accent-primary-active); + --toggle-dot-background-color-on-pressed:var(--button-background-color); + --toggle-border-color-hover:var(--border-color-interactive-hover); + --toggle-border-color-active:var(--border-color-interactive-active); + } + + .toggle-button[aria-pressed="true"]:enabled::after{ + border:1px solid var(--button-background-color); + content:""; + position:absolute; + height:var(--toggle-height); + width:var(--toggle-width); + display:block; + border-radius:var(--toggle-border-radius); + inset:-2px; + } + + .toggle-button[aria-pressed="true"]:enabled:hover:active::after{ + border-color:var(--toggle-border-color-active); + } +} + +:root{ + --clear-signature-button-icon:url(images/editor-toolbar-delete.svg); + --signature-bg:#f9f9fb; + --signature-hover-bg:#f0f0f4; + --button-signature-bg:transparent; + --button-signature-color:var(--main-color); + --button-signature-active-bg:#cfcfd8; + --button-signature-active-border:none; + --button-signature-active-color:var(--button-signature-color); + --button-signature-border:none; + --button-signature-hover-bg:#e0e0e6; + --button-signature-hover-color:var(--button-signature-color); +} + +@media (prefers-color-scheme: dark){ + + :root:where(:not(.is-light)){ + --signature-bg:#2b2a33; + --signature-hover-bg:var(--signature-bg); + --button-signature-active-bg:#5b5b66; + --button-signature-hover-bg:#52525e; + } +} + +:root:where(.is-dark){ + --signature-bg:#2b2a33; + --signature-hover-bg:var(--signature-bg); + --button-signature-active-bg:#5b5b66; + --button-signature-hover-bg:#52525e; +} + +@media screen and (forced-colors: active){ + + :root{ + --signature-bg:HighlightText; + --signature-hover-bg:var(--signature-bg); + --button-signature-bg:HighlightText; + --button-signature-color:ButtonText; + --button-signature-active-bg:ButtonText; + --button-signature-active-color:HighlightText; + --button-signature-border:1px solid ButtonText; + --button-signature-hover-bg:Highlight; + --button-signature-hover-color:HighlightText; + } +} + +.signatureDialog{ + --primary-color:var(--text-primary-color); + --description-input-color:var(--primary-color); + --border-color:#8f8f9d; +} + +@media screen and (forced-colors: active){ + + .signatureDialog{ + --primary-color:ButtonText; + --border-color:ButtonText; + } +} + +.signatureDialog{ + + width:570px; + max-width:100%; + min-width:300px; + padding:16px 0; +} + +.signatureDialog .mainContainer{ + width:100%; + display:flex; + flex-direction:column; + align-items:flex-start; + gap:12px; +} + +:is(.signatureDialog .mainContainer) span:not([role="sectionhead"]){ + font-size:13px; + font-style:normal; + font-weight:400; + line-height:normal; +} + +:is(.signatureDialog .mainContainer) .title{ + margin-inline-start:16px; +} + +.signatureDialog .inputWithClearButton{ + --button-dimension:24px; + + --closing-button-icon:url(images/messageBar_closingButton.svg); + --closing-button-color:var(--primary-color); + + width:100%; + position:relative; + display:flex; + align-items:center; + justify-content:center; +} + +:is(.signatureDialog .inputWithClearButton) > input{ + width:100%; + height:32px; + padding-inline:8px calc(4px + var(--button-dimension)); + box-sizing:border-box; + background-color:transparent; + border-radius:4px; + border:1px solid var(--border-color); + color:var(--description-input-color); +} + +:is(.signatureDialog .inputWithClearButton) .clearInputButton{ + position:absolute; + inset-block-start:4px; + inset-inline-end:4px; + display:inline-block; + width:var(--button-dimension); + height:var(--button-dimension); + background-color:var(--closing-button-color); + -webkit-mask-size:cover; + mask-size:cover; + -webkit-mask-image:var(--closing-button-icon); + mask-image:var(--closing-button-icon); + padding:0; + border:0; +} + +#addSignatureDialog{ + --secondary-color:var(--text-secondary-color); + --bg-hover:#e0e0e6; + --tab-top-line-active-color:#0060df; + --tab-top-line-active-hover-color:var(--tab-text-hover-color); + --tab-top-line-hover-color:#8f8f9d; + --tab-top-line-inactive-color:#cfcfd8; + --tab-bottom-line-active-color:var(--tab-top-line-inactive-color); + --tab-bottom-line-hover-color:var(--tab-top-line-inactive-color); + --tab-bottom-line-inactive-color:var(--tab-top-line-inactive-color); + --tab-bg:var(--dialog-bg-color); + --tab-bg-active-color:var(--tab-bg); + --tab-bg-active-hover-color:var(--bg-hover); + --tab-bg-hover:var(--bg-hover); + --tab-text-color:var(--primary-color); + --tab-text-active-color:var(--tab-top-line-active-color); + --tab-text-active-hover-color:var(--tab-text-hover-color); + --tab-text-hover-color:var(--tab-text-color); + --signature-placeholder-color:var(--secondary-color); + --signature-draw-placeholder-color:var(--primary-color); + --signature-color:var(--primary-color); + --clear-signature-button-border-width:0; + --clear-signature-button-border-style:solid; + --clear-signature-button-border-color:transparent; + --clear-signature-button-border-disabled-color:transparent; + --clear-signature-button-color:var(--primary-color); + --clear-signature-button-hover-color:var(--clear-signature-button-color); + --clear-signature-button-active-color:var(--clear-signature-button-color); + --clear-signature-button-disabled-color:var(--clear-signature-button-color); + --clear-signature-button-focus-color:var(--clear-signature-button-color); + --clear-signature-button-bg:var(--dialog-bg-color); + --clear-signature-button-bg-hover:var(--bg-hover); + --clear-signature-button-bg-active:#cfcfd8; + --clear-signature-button-bg-focus:#f0f0f4; + --clear-signature-button-bg-disabled:color-mix( + in srgb, + #f0f0f4, + transparent 40% + ); + --save-warning-color:var(--secondary-color); + --thickness-bg:var(--dialog-bg-color); + --thickness-label-color:var(--primary-color); + --thickness-slider-color:var(--primary-color); + --draw-cursor:url(images/cursor-editorInk.svg) 0 16, pointer; +} + +@media (prefers-color-scheme: dark){ + + :where(html:not(.is-light)) #addSignatureDialog{ + --dialog-bg-color:#42414d; + --bg-hover:#52525e; + --primary-color:#fbfbfe; + --secondary-color:#cfcfd8; + --tab-top-line-active-color:#0df; + --tab-top-line-inactive-color:#8f8f9d; + --clear-signature-button-bg-active:#5b5b66; + --clear-signature-button-bg-focus:#2b2a33; + --clear-signature-button-bg-disabled:color-mix( + in srgb, + #2b2a33, + transparent 40% + ); + } +} + +:where(html.is-dark) #addSignatureDialog{ + --dialog-bg-color:#42414d; + --bg-hover:#52525e; + --primary-color:#fbfbfe; + --secondary-color:#cfcfd8; + --tab-top-line-active-color:#0df; + --tab-top-line-inactive-color:#8f8f9d; + --clear-signature-button-bg-active:#5b5b66; + --clear-signature-button-bg-focus:#2b2a33; + --clear-signature-button-bg-disabled:color-mix( + in srgb, + #2b2a33, + transparent 40% + ); +} + +@media screen and (forced-colors: active){ + + #addSignatureDialog{ + --secondary-color:ButtonText; + --bg:HighlightText; + --bg-hover:var(--bg); + --tab-top-line-active-color:ButtonText; + --tab-top-line-active-hover-color:HighlightText; + --tab-top-line-hover-color:SelectedItem; + --tab-top-line-inactive-color:ButtonText; + --tab-bottom-line-active-color:var(--tab-top-line-active-color); + --tab-bottom-line-hover-color:var(--tab-top-line-hover-color); + --tab-bg:var(--bg); + --tab-bg-active-color:SelectedItem; + --tab-bg-active-hover-color:SelectedItem; + --tab-text-color:ButtonText; + --tab-text-active-color:HighlightText; + --tab-text-active-hover-color:HighlightText; + --tab-text-hover-color:SelectedItem; + --signature-color:ButtonText; + --clear-signature-button-border-width:1px; + --clear-signature-button-border-style:solid; + --clear-signature-button-border-color:ButtonText; + --clear-signature-button-border-disabled-color:GrayText; + --clear-signature-button-color:ButtonText; + --clear-signature-button-hover-color:HighlightText; + --clear-signature-button-active-color:SelectedItem; + --clear-signature-button-focus-color:CanvasText; + --clear-signature-button-disabled-color:GrayText; + --clear-signature-button-bg:var(--bg); + --clear-signature-button-bg-hover:SelectedItem; + --clear-signature-button-bg-active:var(--bg); + --clear-signature-button-bg-focus:var(--bg); + --clear-signature-button-bg-disabled:var(--bg); + --thickness-bg:Canvas; + --thickness-label-color:CanvasText; + --thickness-slider-color:ButtonText; + } +} + +#addSignatureDialog #addSignatureDialogLabel{ + overflow:hidden; + position:absolute; + inset:0; + width:0; + height:0; +} + +#addSignatureDialog.waiting::after{ + content:""; + cursor:wait; + position:absolute; + inset:0; + width:100%; + height:100%; +} + +:is(#addSignatureDialog .mainContainer) [role="tablist"]{ + width:100%; + display:flex; + align-items:flex-start; + gap:0; +} + +:is(:is(#addSignatureDialog .mainContainer) [role="tablist"]) > [role="tab"]{ + flex:1 0 0; + align-self:stretch; + background-color:var(--tab-bg); + padding-inline:0; + cursor:default; + + border-inline:0; + border-block-width:1px; + border-block-style:solid; + border-block-start-color:var(--tab-top-line-inactive-color); + border-block-end-color:var(--tab-bottom-line-inactive-color); + border-radius:0; + + font:menu; + font-size:13px; + font-style:normal; + line-height:normal; + font-weight:400; + color:var(--tab-text-color); +} + +:is(:is(:is(#addSignatureDialog .mainContainer) [role="tablist"]) > [role="tab"]):hover{ + border-block-start-width:2px; + border-block-start-color:var(--tab-top-line-hover-color); + border-block-end-color:var(--tab-bottom-line-hover-color); + background-color:var(--tab-bg-hover); + color:var(--tab-text-hover-color); +} + +:is(:is(:is(#addSignatureDialog .mainContainer) [role="tablist"]) > [role="tab"]):focus-visible{ + outline:2px solid var(--tab-top-line-active-color); + outline-offset:-2px; +} + +[aria-selected="true"]:is(:is(:is(#addSignatureDialog .mainContainer) [role="tablist"]) > [role="tab"]){ + border-block-start-width:2px; + border-block-start-color:var(--tab-top-line-active-color); + border-block-end-color:var(--tab-bottom-line-active-color); + background-color:var(--tab-bg-active-color); + font-weight:590; + color:var(--tab-text-active-color); +} + +[aria-selected="true"]:is(:is(:is(#addSignatureDialog .mainContainer) [role="tablist"]) > [role="tab"]):hover{ + border-block-start-color:var(--tab-top-line-active-hover-color); + background-color:var(--tab-bg-active-hover-color); + color:var(--tab-text-active-hover-color); +} + +:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer{ + width:100%; + height:auto; + display:flex; + flex-direction:column; + align-items:flex-end; + align-self:stretch; + gap:12px; + padding-inline:16px; + box-sizing:border-box; +} + +:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]{ + position:relative; + width:100%; + height:220px; + background-color:var(--signature-bg); + border-radius:4px; +} + +:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) > svg{ + position:absolute; + inset:0; + width:100%; + height:100%; + background-color:transparent; +} + +#addSignatureTypeContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]){ + display:none; +} + +#addSignatureTypeContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #addSignatureTypeInput{ + position:absolute; + inset:0; + width:100%; + height:100%; + border:0; + padding:0; + text-align:center; + color:var(--signature-color); + background-color:transparent; + + font-family:"Brush script", "Apple Chancery", "Segoe script", "Freestyle Script", "Palace Script MT", "Brush Script MT", TK, cursive, serif; + font-size:44px; + font-style:italic; + font-weight:400; +} + +:is(#addSignatureTypeContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #addSignatureTypeInput)::-moz-placeholder{ + color:var(--signature-placeholder-color); + text-align:center; + + font:menu; + font-style:normal; + font-weight:274; + font-size:44px; + line-height:normal; +} + +:is(#addSignatureTypeContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #addSignatureTypeInput)::placeholder{ + color:var(--signature-placeholder-color); + text-align:center; + + font:menu; + font-style:normal; + font-weight:274; + font-size:44px; + line-height:normal; +} + +#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]){ + display:none; +} + +#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) > span{ + position:absolute; + top:0; + left:0; + width:100%; + height:100%; + display:grid; + align-items:center; + justify-content:center; + + background-color:transparent; + color:var(--signature-placeholder-color); + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; +} + +#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) > svg{ + stroke:var(--primary-color); + fill:none; + stroke-opacity:1; + stroke-linecap:round; + stroke-linejoin:round; + stroke-miterlimit:10; +} + +:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) > svg):hover{ + cursor:var(--draw-cursor); +} + +#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness{ + position:absolute; + width:100%; + inset-block-end:0; + display:grid; + align-items:center; + justify-content:center; + pointer-events:none; +} + +:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > span{ + color:var(--signature-draw-placeholder-color); +} + +:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div{ + width:auto; + height:auto; + display:flex; + align-items:center; + justify-content:center; + gap:8px; + padding:6px 8px; + margin:0; + background-color:var(--thickness-bg); + border-radius:4px 4px 0 0; + pointer-events:auto; +} + +:is(:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div) > label{ + color:var(--thickness-label-color); +} + +:is(:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div) > input{ + width:100px; + height:14px; + background-color:transparent; +} + +:is(:is(:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div) > input)::-webkit-slider-runnable-track,:is(:is(:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div) > input)::-moz-range-track,:is(:is(:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div) > input)::-moz-range-progress{ + background-color:var(--thickness-slider-color); +} + +:is(:is(:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div) > input)::-webkit-slider-thumb,:is(:is(:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div) > input)::-moz-range-thumb{ + background-color:var(--thickness-bg); +} + +:is(:is(#addSignatureDrawContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #thickness) > div) > input{ + + border-radius:4.5px; + border:0; + color:var(--signature-color); +} + +#addSignatureImageContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]){ + display:none; +} + +#addSignatureImageContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) > svg{ + stroke:none; + stroke-width:0; + fill:var(--primary-color); + fill-opacity:1; +} + +#addSignatureImageContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #addSignatureImagePlaceholder{ + position:absolute; + top:0; + left:0; + width:100%; + height:100%; + background-color:transparent; + display:flex; + flex-direction:column; + align-items:center; + justify-content:center; +} + +:is(#addSignatureImageContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #addSignatureImagePlaceholder) a{ + text-decoration:underline; + cursor:pointer; +} + +#addSignatureImageContainer:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > [role="tabpanel"]) #addSignatureFilePicker{ + visibility:hidden; + position:relative; + width:0; + height:0; +} + +[data-selected="type"]:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > #addSignatureTypeContainer,[data-selected="draw"]:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > #addSignatureDrawContainer,[data-selected="image"]:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) > #addSignatureImageContainer{ + display:block; +} + +:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls{ + display:flex; + flex-direction:column; + justify-content:center; + align-items:flex-start; + gap:12px; + align-self:stretch; +} + +:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer{ + display:flex; + align-items:flex-end; + gap:16px; + align-self:stretch; +} + +:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #addSignatureDescriptionContainer{ + display:flex; + flex-direction:column; + align-items:flex-start; + gap:4px; + flex:1 0 0; +} + +:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #addSignatureDescriptionContainer) > label{ + width:auto; +} + +:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton{ + display:flex; + height:32px; + padding:4px 8px; + align-items:center; + background-color:var(--clear-signature-button-bg); + border-width:var(--clear-signature-button-border-width); + border-style:var(--clear-signature-button-border-style); + border-color:var(--clear-signature-button-border-color); + border-radius:4px; +} + +:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton) > span{ + display:flex; + height:24px; + align-items:center; + gap:4px; + flex-shrink:0; + + color:var(--clear-signature-button-color); +} + +:is(:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton) > span)::after{ + content:""; + display:inline-block; + width:16px; + height:16px; + -webkit-mask-image:var(--clear-signature-button-icon); + mask-image:var(--clear-signature-button-icon); + -webkit-mask-size:cover; + mask-size:cover; + background-color:var(--clear-signature-button-color); + flex-shrink:0; +} + +:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):hover{ + background-color:var(--clear-signature-button-bg-hover); +} + +:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):hover > span{ + color:var(--clear-signature-button-hover-color); +} + +:is(:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):hover > span)::after{ + background-color:var(--clear-signature-button-hover-color); +} + +:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):active{ + background-color:var(--clear-signature-button-bg-active); +} + +:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):active > span{ + color:var(--clear-signature-button-active-color); +} + +:is(:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):active > span)::after{ + background-color:var(--clear-signature-button-active-color); +} + +:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):focus-visible{ + background-color:var(--clear-signature-button-bg-focus); +} + +:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):focus-visible > span{ + color:var(--clear-signature-button-focus-color); +} + +:is(:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):focus-visible > span)::after{ + background-color:var(--clear-signature-button-focus-color); +} + +:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):disabled{ + background-color:var(--clear-signature-button-bg-disabled); + border-color:var(--clear-signature-button-border-disabled-color); +} + +:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):disabled > span{ + color:var(--clear-signature-button-disabled-color); +} + +:is(:is(:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #horizontalContainer) #clearSignatureButton):disabled > span)::after{ + background-color:var( + --clear-signature-button-disabled-color + ); +} + +:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #addSignatureSaveContainer{ + display:grid; + grid-template-columns:max-content max-content; + gap:4px; + width:100%; +} + +:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #addSignatureSaveContainer) > input{ + margin:0; +} + +:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #addSignatureSaveContainer) > label{ + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; +} + +:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #addSignatureSaveContainer):not(.fullStorage) #addSignatureSaveWarning{ + display:none; +} + +.fullStorage:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #addSignatureSaveContainer) #addSignatureSaveWarning{ + display:block; + opacity:1; + color:var(--save-warning-color); + font-size:11px; +} + +:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #addSignatureSaveContainer):is([disabled],.fullStorage){ + pointer-events:none; +} + +:is(:is(:is(:is(#addSignatureDialog .mainContainer) #addSignatureActionContainer) #addSignatureControls) #addSignatureSaveContainer):is([disabled],.fullStorage) > :not(#addSignatureSaveWarning){ + opacity:0.4; +} + +#editSignatureDescriptionDialog .mainContainer{ + padding-inline:16px; + box-sizing:border-box; +} + +:is(#editSignatureDescriptionDialog .mainContainer) .title{ + margin-inline-start:0; +} + +:is(#editSignatureDescriptionDialog .mainContainer) #editSignatureDescriptionAndView{ + width:auto; + display:flex; + justify-content:flex-end; + align-items:flex-start; + gap:12px; + align-self:stretch; +} + +:is(:is(#editSignatureDescriptionDialog .mainContainer) #editSignatureDescriptionAndView) #editSignatureDescriptionContainer{ + display:flex; + flex-direction:column; + align-items:flex-start; + gap:4px; + flex:1 1 auto; +} + +:is(:is(#editSignatureDescriptionDialog .mainContainer) #editSignatureDescriptionAndView) > svg{ + width:210px; + height:180px; + padding:8px; + background-color:var(--signature-bg); +} + +:is(:is(:is(#editSignatureDescriptionDialog .mainContainer) #editSignatureDescriptionAndView) > svg) > path{ + stroke:var(--button-signature-color); + stroke-width:1px; + stroke-linecap:round; + stroke-linejoin:round; + stroke-miterlimit:10; + vector-effect:non-scaling-stroke; + fill:none; +} + +.contours:is(:is(:is(:is(#editSignatureDescriptionDialog .mainContainer) #editSignatureDescriptionAndView) > svg) > path){ + fill:var(--button-signature-color); + stroke-width:0.5px; +} + +#editorSignatureParamsToolbar{ + padding:8px; +} + +#editorSignatureParamsToolbar #addSignatureDoorHanger{ + gap:8px; + padding:2px; +} + +:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer{ + height:32px; + display:flex; + justify-content:space-between; + align-items:center; + align-self:stretch; + gap:8px; +} + +:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) button{ + border:var(--button-signature-border); + border-radius:4px; + background-color:var(--button-signature-bg); + color:var(--button-signature-color); +} + +:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) button):hover{ + background-color:var(--button-signature-hover-bg); +} + +:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) button):active{ + border:var(--button-signature-active-border); + background-color:var(--button-signature-active-bg); + color:var(--button-signature-active-color); +} + +:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) button):active::before{ + background-color:var(--button-signature-active-color); +} + +:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) button):focus-visible{ + outline:var(--focus-ring-outline); +} + +:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) button):focus-visible::before{ + background-color:var(--button-signature-color); +} + +:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .deleteButton)::before{ + -webkit-mask-image:var(--clear-signature-button-icon); + mask-image:var(--clear-signature-button-icon); +} + +:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton{ + width:auto; + height:100%; + min-height:var(--menuitem-height); + aspect-ratio:unset; + display:flex; + align-items:center; + justify-content:flex-start; + outline:none; + border-radius:4px; + box-sizing:border-box; + font:message-box; + position:relative; + flex:1 1 auto; + padding:0; + gap:8px; + text-align:start; + white-space:normal; + cursor:default; + overflow:hidden; +} + +:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton) > svg{ + display:inline-block; + height:100%; + aspect-ratio:1; + background-color:var(--signature-bg); + flex:none; + padding:4px; + box-sizing:border-box; + border:none; + border-radius:4px; +} + +:is(:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton) > svg) > path{ + stroke:var(--button-signature-color); + stroke-width:1px; + stroke-linecap:round; + stroke-linejoin:round; + stroke-miterlimit:10; + vector-effect:non-scaling-stroke; + fill:none; +} + +.contours:is(:is(:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton) > svg) > path){ + fill:var(--button-signature-color); + stroke-width:0.5px; +} + +:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton):is(:hover,:active) > svg{ + border-radius:4px 0 0 4px; + background-color:var(--signature-hover-bg); +} + +:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton):hover > span{ + color:var(--button-signature-hover-color); +} + +:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton):active{ + background-color:var(--button-signature-active-bg); +} + +:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton):is([disabled="disabled"],[disabled]){ + opacity:0.5; + pointer-events:none; +} + +:is(:is(:is(#editorSignatureParamsToolbar #addSignatureDoorHanger) .toolbarAddSignatureButtonContainer) .toolbarAddSignatureButton) > span{ + height:auto; + text-overflow:ellipsis; + white-space:nowrap; + flex:1 1 auto; + font:menu; + font-size:13px; + font-style:normal; + font-weight:400; + line-height:normal; + overflow:hidden; +} + +.editDescription.altText{ + --alt-text-add-image:url(images/editor-toolbar-edit.svg) !important; +} + +.editDescription.altText::before{ + width:16px !important; + height:16px !important; +} + +:root{ + --outline-width:2px; + --outline-color:#0060df; + --outline-around-width:1px; + --outline-around-color:#f0f0f4; + --hover-outline-around-color:var(--outline-around-color); + --focus-outline:solid var(--outline-width) var(--outline-color); + --unfocus-outline:solid var(--outline-width) transparent; + --focus-outline-around:solid var(--outline-around-width) var(--outline-around-color); + --hover-outline-color:#8f8f9d; + --hover-outline:solid var(--outline-width) var(--hover-outline-color); + --hover-outline-around:solid var(--outline-around-width) var(--hover-outline-around-color); + --freetext-line-height:1.35; + --freetext-padding:2px; + --resizer-bg-color:var(--outline-color); + --resizer-size:6px; + --resizer-shift:calc( + 0px - (var(--outline-width) + var(--resizer-size)) / 2 - + var(--outline-around-width) + ); + --editorFreeText-editing-cursor:text; + --editorInk-editing-cursor:url(images/cursor-editorInk.svg) 0 16, pointer; + --editorHighlight-editing-cursor:url(images/cursor-editorTextHighlight.svg) 24 24, text; + --editorFreeHighlight-editing-cursor:url(images/cursor-editorFreeHighlight.svg) 1 18, pointer; + + --new-alt-text-warning-image:url(images/altText_warning.svg); +} +.visuallyHidden{ + position:absolute; + top:0; + left:0; + border:0; + margin:0; + padding:0; + width:0; + height:0; + overflow:hidden; + white-space:nowrap; + font-size:0; +} + +.textLayer.highlighting{ + cursor:var(--editorFreeHighlight-editing-cursor); +} + +.textLayer.highlighting:not(.free) span{ + cursor:var(--editorHighlight-editing-cursor); +} + +[role="img"]:is(.textLayer.highlighting:not(.free) span){ + cursor:var(--editorFreeHighlight-editing-cursor); +} + +.textLayer.highlighting.free span{ + cursor:var(--editorFreeHighlight-editing-cursor); +} + +:is(#viewerContainer.pdfPresentationMode:fullscreen,.annotationEditorLayer.disabled) .noAltTextBadge{ + display:none !important; +} + +@media (min-resolution: 1.1dppx){ + :root{ + --editorFreeText-editing-cursor:url(images/cursor-editorFreeText.svg) 0 16, text; + } +} + +@media screen and (forced-colors: active){ + :root{ + --outline-color:CanvasText; + --outline-around-color:ButtonFace; + --resizer-bg-color:ButtonText; + --hover-outline-color:Highlight; + --hover-outline-around-color:SelectedItemText; + } +} + +[data-editor-rotation="90"]{ + transform:rotate(90deg); +} + +[data-editor-rotation="180"]{ + transform:rotate(180deg); +} + +[data-editor-rotation="270"]{ + transform:rotate(270deg); +} + +.annotationEditorLayer{ + background:transparent; + position:absolute; + inset:0; + font-size:calc(100px * var(--total-scale-factor)); + transform-origin:0 0; + cursor:auto; +} + +.annotationEditorLayer .selectedEditor{ + z-index:100000 !important; +} + +.annotationEditorLayer.drawing *{ + pointer-events:none !important; +} + +.annotationEditorLayer.waiting{ + content:""; + cursor:wait; + position:absolute; + inset:0; + width:100%; + height:100%; +} + +.annotationEditorLayer.disabled{ + pointer-events:none; +} + +.annotationEditorLayer.freetextEditing{ + cursor:var(--editorFreeText-editing-cursor); +} + +.annotationEditorLayer.inkEditing{ + cursor:var(--editorInk-editing-cursor); +} + +.annotationEditorLayer .draw{ + box-sizing:border-box; +} + +.annotationEditorLayer +:is(.freeTextEditor, .inkEditor, .stampEditor, .signatureEditor){ + position:absolute; + background:transparent; + z-index:1; + transform-origin:0 0; + cursor:auto; + max-width:100%; + max-height:100%; + border:var(--unfocus-outline); +} + +.draggable.selectedEditor:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)){ + cursor:move; +} + +.selectedEditor:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)){ + border:var(--focus-outline); + outline:var(--focus-outline-around); +} + +.selectedEditor:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor))::before{ + content:""; + position:absolute; + inset:0; + border:var(--focus-outline-around); + pointer-events:none; +} + +:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)):hover:not(.selectedEditor){ + border:var(--hover-outline); + outline:var(--hover-outline-around); +} + +:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)):hover:not(.selectedEditor)::before{ + content:""; + position:absolute; + inset:0; + border:var(--focus-outline-around); +} + +:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar{ + --editor-toolbar-delete-image:url(images/editor-toolbar-delete.svg); + --editor-toolbar-bg-color:#f0f0f4; + --editor-toolbar-highlight-image:url(images/toolbarButton-editorHighlight.svg); + --editor-toolbar-fg-color:#2e2e56; + --editor-toolbar-border-color:#8f8f9d; + --editor-toolbar-hover-border-color:var(--editor-toolbar-border-color); + --editor-toolbar-hover-bg-color:#e0e0e6; + --editor-toolbar-hover-fg-color:var(--editor-toolbar-fg-color); + --editor-toolbar-hover-outline:none; + --editor-toolbar-focus-outline-color:#0060df; + --editor-toolbar-shadow:0 2px 6px 0 rgb(58 57 68 / 0.2); + --editor-toolbar-vert-offset:6px; + --editor-toolbar-height:28px; + --editor-toolbar-padding:2px; + --alt-text-done-color:#2ac3a2; + --alt-text-warning-color:#0090ed; + --alt-text-hover-done-color:var(--alt-text-done-color); + --alt-text-hover-warning-color:var(--alt-text-warning-color); +} + +@media (prefers-color-scheme: dark){ + + :where(html:not(.is-light)) :is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar{ + --editor-toolbar-bg-color:#2b2a33; + --editor-toolbar-fg-color:#fbfbfe; + --editor-toolbar-hover-bg-color:#52525e; + --editor-toolbar-focus-outline-color:#0df; + --alt-text-done-color:#54ffbd; + --alt-text-warning-color:#80ebff; + } +} + +:where(html.is-dark) :is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar{ + --editor-toolbar-bg-color:#2b2a33; + --editor-toolbar-fg-color:#fbfbfe; + --editor-toolbar-hover-bg-color:#52525e; + --editor-toolbar-focus-outline-color:#0df; + --alt-text-done-color:#54ffbd; + --alt-text-warning-color:#80ebff; +} + +@media screen and (forced-colors: active){ + + :is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar{ + --editor-toolbar-bg-color:ButtonFace; + --editor-toolbar-fg-color:ButtonText; + --editor-toolbar-border-color:ButtonText; + --editor-toolbar-hover-border-color:AccentColor; + --editor-toolbar-hover-bg-color:ButtonFace; + --editor-toolbar-hover-fg-color:AccentColor; + --editor-toolbar-hover-outline:2px solid var(--editor-toolbar-hover-border-color); + --editor-toolbar-focus-outline-color:ButtonBorder; + --editor-toolbar-shadow:none; + --alt-text-done-color:var(--editor-toolbar-fg-color); + --alt-text-warning-color:var(--editor-toolbar-fg-color); + --alt-text-hover-done-color:var(--editor-toolbar-hover-fg-color); + --alt-text-hover-warning-color:var(--editor-toolbar-hover-fg-color); + } +} + +:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar{ + + display:flex; + width:-moz-fit-content; + width:fit-content; + height:var(--editor-toolbar-height); + flex-direction:column; + justify-content:center; + align-items:center; + cursor:default; + pointer-events:auto; + box-sizing:content-box; + padding:var(--editor-toolbar-padding); + + position:absolute; + inset-inline-end:0; + inset-block-start:calc(100% + var(--editor-toolbar-vert-offset)); + + border-radius:6px; + background-color:var(--editor-toolbar-bg-color); + border:1px solid var(--editor-toolbar-border-color); + box-shadow:var(--editor-toolbar-shadow); +} + +.hidden:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar){ + display:none; +} + +:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar):has(:focus-visible){ + border-color:transparent; +} + +[dir="ltr"] :is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar){ + transform-origin:100% 0; +} + +[dir="rtl"] :is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar){ + transform-origin:0 0; +} + +:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons{ + display:flex; + justify-content:center; + align-items:center; + gap:0; + height:100%; +} + +:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) button{ + padding:0; +} + +:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .divider{ + width:0; + height:calc( + 2 * var(--editor-toolbar-padding) + var(--editor-toolbar-height) + ); + border-left:1px solid var(--editor-toolbar-border-color); + border-right:none; + display:inline-block; + margin-inline:2px; +} + +:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .highlightButton{ + width:var(--editor-toolbar-height); +} + +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .highlightButton)::before{ + content:""; + -webkit-mask-image:var(--editor-toolbar-highlight-image); + mask-image:var(--editor-toolbar-highlight-image); + -webkit-mask-repeat:no-repeat; + mask-repeat:no-repeat; + -webkit-mask-position:center; + mask-position:center; + display:inline-block; + background-color:var(--editor-toolbar-fg-color); + width:100%; + height:100%; +} + +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .highlightButton):hover::before{ + background-color:var(--editor-toolbar-hover-fg-color); +} + +:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .delete{ + width:var(--editor-toolbar-height); +} + +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .delete)::before{ + content:""; + -webkit-mask-image:var(--editor-toolbar-delete-image); + mask-image:var(--editor-toolbar-delete-image); + -webkit-mask-repeat:no-repeat; + mask-repeat:no-repeat; + -webkit-mask-position:center; + mask-position:center; + display:inline-block; + background-color:var(--editor-toolbar-fg-color); + width:100%; + height:100%; +} + +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .delete):hover::before{ + background-color:var(--editor-toolbar-hover-fg-color); +} + +:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) > *{ + height:var(--editor-toolbar-height); +} + +:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) > :not(.divider){ + border:none; + background-color:transparent; + cursor:pointer; +} + +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) > :not(.divider)):hover{ + border-radius:2px; + background-color:var(--editor-toolbar-hover-bg-color); + color:var(--editor-toolbar-hover-fg-color); + outline:var(--editor-toolbar-hover-outline); + outline-offset:1px; +} + +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) > :not(.divider)):hover:active{ + outline:none; +} + +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) > :not(.divider)):focus-visible{ + border-radius:2px; + outline:2px solid var(--editor-toolbar-focus-outline-color); +} + +:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText{ + --alt-text-add-image:url(images/altText_add.svg); + --alt-text-done-image:url(images/altText_done.svg); + + display:flex; + align-items:center; + justify-content:center; + width:-moz-max-content; + width:max-content; + padding-inline:8px; + pointer-events:all; + font:menu; + font-weight:590; + font-size:12px; + color:var(--editor-toolbar-fg-color); +} + +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText):disabled{ + pointer-events:none; +} + +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText)::before{ + content:""; + -webkit-mask-image:var(--alt-text-add-image); + mask-image:var(--alt-text-add-image); + -webkit-mask-repeat:no-repeat; + mask-repeat:no-repeat; + -webkit-mask-position:center; + mask-position:center; + display:inline-block; + width:12px; + height:13px; + background-color:var(--editor-toolbar-fg-color); + margin-inline-end:4px; +} + +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText):hover::before{ + background-color:var(--editor-toolbar-hover-fg-color); +} + +.done:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText)::before{ + -webkit-mask-image:var(--alt-text-done-image); + mask-image:var(--alt-text-done-image); +} + +.new:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText)::before{ + width:16px; + height:16px; + -webkit-mask-image:var(--new-alt-text-warning-image); + mask-image:var(--new-alt-text-warning-image); + background-color:var(--alt-text-warning-color); + -webkit-mask-size:cover; + mask-size:cover; +} + +.new:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText):hover::before{ + background-color:var(--alt-text-hover-warning-color); +} + +.new.done:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText)::before{ + -webkit-mask-image:var(--alt-text-done-image); + mask-image:var(--alt-text-done-image); + background-color:var(--alt-text-done-color); +} + +.new.done:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText):hover::before{ + background-color:var(--alt-text-hover-done-color); +} + +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip{ + display:none; + word-wrap:anywhere; +} + +.show:is(:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip){ + --alt-text-tooltip-bg:#f0f0f4; + --alt-text-tooltip-fg:#15141a; + --alt-text-tooltip-border:#8f8f9d; + --alt-text-tooltip-shadow:0px 2px 6px 0px rgb(58 57 68 / 0.2); +} + +@media (prefers-color-scheme: dark){ + + :where(html:not(.is-light)) .show:is(:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip){ + --alt-text-tooltip-bg:#1c1b22; + --alt-text-tooltip-fg:#fbfbfe; + --alt-text-tooltip-shadow:0px 2px 6px 0px #15141a; + } +} + +:where(html.is-dark) .show:is(:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip){ + --alt-text-tooltip-bg:#1c1b22; + --alt-text-tooltip-fg:#fbfbfe; + --alt-text-tooltip-shadow:0px 2px 6px 0px #15141a; +} + +@media screen and (forced-colors: active){ + + .show:is(:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip){ + --alt-text-tooltip-bg:Canvas; + --alt-text-tooltip-fg:CanvasText; + --alt-text-tooltip-border:CanvasText; + --alt-text-tooltip-shadow:none; + } +} + +.show:is(:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor,.signatureEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip){ + + display:inline-flex; + flex-direction:column; + align-items:center; + justify-content:center; + position:absolute; + top:calc(100% + 2px); + inset-inline-start:0; + padding-block:2px 3px; + padding-inline:3px; + max-width:300px; + width:-moz-max-content; + width:max-content; + height:auto; + font-size:12px; + + border:0.5px solid var(--alt-text-tooltip-border); + background:var(--alt-text-tooltip-bg); + box-shadow:var(--alt-text-tooltip-shadow); + color:var(--alt-text-tooltip-fg); + + pointer-events:none; +} + +.annotationEditorLayer .freeTextEditor{ + padding:calc(var(--freetext-padding) * var(--total-scale-factor)); + width:auto; + height:auto; + touch-action:none; +} + +.annotationEditorLayer .freeTextEditor .internal{ + background:transparent; + border:none; + inset:0; + overflow:visible; + white-space:nowrap; + font:10px sans-serif; + line-height:var(--freetext-line-height); + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; +} + +.annotationEditorLayer .freeTextEditor .overlay{ + position:absolute; + display:none; + background:transparent; + inset:0; + width:100%; + height:100%; +} + +.annotationEditorLayer freeTextEditor .overlay.enabled{ + display:block; +} + +.annotationEditorLayer .freeTextEditor .internal:empty::before{ + content:attr(default-content); + color:gray; +} + +.annotationEditorLayer .freeTextEditor .internal:focus{ + outline:none; + -webkit-user-select:auto; + -moz-user-select:auto; + user-select:auto; +} + +.annotationEditorLayer .inkEditor{ + width:100%; + height:100%; +} + +.annotationEditorLayer .inkEditor.editing{ + cursor:inherit; +} + +.annotationEditorLayer .inkEditor .inkEditorCanvas{ + position:absolute; + inset:0; + width:100%; + height:100%; + touch-action:none; +} + +.annotationEditorLayer .stampEditor{ + width:auto; + height:auto; +} + +:is(.annotationEditorLayer .stampEditor) canvas{ + position:absolute; + width:100%; + height:100%; + margin:0; + top:0; + left:0; +} + +:is(.annotationEditorLayer .stampEditor) .noAltTextBadge{ + --no-alt-text-badge-border-color:#f0f0f4; + --no-alt-text-badge-bg-color:#cfcfd8; + --no-alt-text-badge-fg-color:#5b5b66; +} + +@media (prefers-color-scheme: dark){ + + :where(html:not(.is-light)) :is(.annotationEditorLayer .stampEditor) .noAltTextBadge{ + --no-alt-text-badge-border-color:#52525e; + --no-alt-text-badge-bg-color:#fbfbfe; + --no-alt-text-badge-fg-color:#15141a; + } +} + +:where(html.is-dark) :is(.annotationEditorLayer .stampEditor) .noAltTextBadge{ + --no-alt-text-badge-border-color:#52525e; + --no-alt-text-badge-bg-color:#fbfbfe; + --no-alt-text-badge-fg-color:#15141a; +} + +@media screen and (forced-colors: active){ + + :is(.annotationEditorLayer .stampEditor) .noAltTextBadge{ + --no-alt-text-badge-border-color:ButtonText; + --no-alt-text-badge-bg-color:ButtonFace; + --no-alt-text-badge-fg-color:ButtonText; + } +} + +:is(.annotationEditorLayer .stampEditor) .noAltTextBadge{ + + position:absolute; + inset-inline-end:5px; + inset-block-end:5px; + display:inline-flex; + width:32px; + height:32px; + padding:3px; + justify-content:center; + align-items:center; + pointer-events:none; + z-index:1; + + border-radius:2px; + border:1px solid var(--no-alt-text-badge-border-color); + background:var(--no-alt-text-badge-bg-color); +} + +:is(:is(.annotationEditorLayer .stampEditor) .noAltTextBadge)::before{ + content:""; + display:inline-block; + width:16px; + height:16px; + -webkit-mask-image:var(--new-alt-text-warning-image); + mask-image:var(--new-alt-text-warning-image); + -webkit-mask-size:cover; + mask-size:cover; + background-color:var(--no-alt-text-badge-fg-color); +} + +:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers{ + position:absolute; + inset:0; +} + +.hidden:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers){ + display:none; +} + +:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer{ + width:var(--resizer-size); + height:var(--resizer-size); + background:content-box var(--resizer-bg-color); + border:var(--focus-outline-around); + border-radius:2px; + position:absolute; +} + +.topLeft:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer){ + top:var(--resizer-shift); + left:var(--resizer-shift); +} + +.topMiddle:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer){ + top:var(--resizer-shift); + left:calc(50% + var(--resizer-shift)); +} + +.topRight:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer){ + top:var(--resizer-shift); + right:var(--resizer-shift); +} + +.middleRight:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer){ + top:calc(50% + var(--resizer-shift)); + right:var(--resizer-shift); +} + +.bottomRight:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer){ + bottom:var(--resizer-shift); + right:var(--resizer-shift); +} + +.bottomMiddle:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer){ + bottom:var(--resizer-shift); + left:calc(50% + var(--resizer-shift)); +} + +.bottomLeft:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer){ + bottom:var(--resizer-shift); + left:var(--resizer-shift); +} + +.middleLeft:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.signatureEditor)) > .resizers) > .resizer){ + top:calc(50% + var(--resizer-shift)); + left:var(--resizer-shift); +} + +.topLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer),.bottomRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer){ + cursor:nwse-resize; +} + +.topMiddle:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer),.bottomMiddle:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer){ + cursor:ns-resize; +} + +.topRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer),.bottomLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer){ + cursor:nesw-resize; +} + +.middleRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer),.middleLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer){ + cursor:ew-resize; +} + +.topLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer),.bottomRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer){ + cursor:nesw-resize; +} + +.topMiddle:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer),.bottomMiddle:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer){ + cursor:ew-resize; +} + +.topRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer),.bottomLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer){ + cursor:nwse-resize; +} + +.middleRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer),.middleLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer){ + cursor:ns-resize; +} + +:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="90"],[data-main-rotation="90"] [data-editor-rotation="0"],[data-main-rotation="180"] [data-editor-rotation="270"],[data-main-rotation="270"] [data-editor-rotation="180"])) .editToolbar{ + rotate:270deg; +} + +[dir="ltr"] :is(:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="90"],[data-main-rotation="90"] [data-editor-rotation="0"],[data-main-rotation="180"] [data-editor-rotation="270"],[data-main-rotation="270"] [data-editor-rotation="180"])) .editToolbar){ + inset-inline-end:calc(0px - var(--editor-toolbar-vert-offset)); + inset-block-start:0; +} + +[dir="rtl"] :is(:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="90"],[data-main-rotation="90"] [data-editor-rotation="0"],[data-main-rotation="180"] [data-editor-rotation="270"],[data-main-rotation="270"] [data-editor-rotation="180"])) .editToolbar){ + inset-inline-end:calc(100% + var(--editor-toolbar-vert-offset)); + inset-block-start:0; +} + +:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="180"],[data-main-rotation="90"] [data-editor-rotation="90"],[data-main-rotation="180"] [data-editor-rotation="0"],[data-main-rotation="270"] [data-editor-rotation="270"])) .editToolbar{ + rotate:180deg; + inset-inline-end:100%; + inset-block-start:calc(0pc - var(--editor-toolbar-vert-offset)); +} + +:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="270"],[data-main-rotation="90"] [data-editor-rotation="180"],[data-main-rotation="180"] [data-editor-rotation="90"],[data-main-rotation="270"] [data-editor-rotation="0"])) .editToolbar{ + rotate:90deg; +} + +[dir="ltr"] :is(:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="270"],[data-main-rotation="90"] [data-editor-rotation="180"],[data-main-rotation="180"] [data-editor-rotation="90"],[data-main-rotation="270"] [data-editor-rotation="0"])) .editToolbar){ + inset-inline-end:calc(100% + var(--editor-toolbar-vert-offset)); + inset-block-start:100%; +} + +[dir="rtl"] :is(:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="270"],[data-main-rotation="90"] [data-editor-rotation="180"],[data-main-rotation="180"] [data-editor-rotation="90"],[data-main-rotation="270"] [data-editor-rotation="0"])) .editToolbar){ + inset-inline-start:calc(0px - var(--editor-toolbar-vert-offset)); + inset-block-start:0; +} + +.dialog.altText::backdrop{ + -webkit-mask:url(#alttext-manager-mask); + mask:url(#alttext-manager-mask); +} + +.dialog.altText.positioned{ + margin:0; +} + +.dialog.altText #altTextContainer{ + width:300px; + height:-moz-fit-content; + height:fit-content; + display:inline-flex; + flex-direction:column; + align-items:flex-start; + gap:16px; +} + +:is(.dialog.altText #altTextContainer) #overallDescription{ + display:flex; + flex-direction:column; + align-items:flex-start; + gap:4px; + align-self:stretch; +} + +:is(:is(.dialog.altText #altTextContainer) #overallDescription) span{ + align-self:stretch; +} + +:is(:is(.dialog.altText #altTextContainer) #overallDescription) .title{ + font-size:13px; + font-style:normal; + font-weight:590; +} + +:is(.dialog.altText #altTextContainer) #addDescription{ + display:flex; + flex-direction:column; + align-items:stretch; + gap:8px; +} + +:is(:is(.dialog.altText #altTextContainer) #addDescription) .descriptionArea{ + flex:1; + padding-inline:24px 10px; +} + +:is(:is(:is(.dialog.altText #altTextContainer) #addDescription) .descriptionArea) textarea{ + width:100%; + min-height:75px; +} + +:is(.dialog.altText #altTextContainer) #buttons{ + display:flex; + justify-content:flex-end; + align-items:flex-start; + gap:8px; + align-self:stretch; +} + +.dialog.newAltText{ + --new-alt-text-ai-disclaimer-icon:url(images/altText_disclaimer.svg); + --new-alt-text-spinner-icon:url(images/altText_spinner.svg); + --preview-image-bg-color:#f0f0f4; + --preview-image-border:none; +} + +@media (prefers-color-scheme: dark){ + + :where(html:not(.is-light)) .dialog.newAltText{ + --preview-image-bg-color:#2b2a33; + } +} + +:where(html.is-dark) .dialog.newAltText{ + --preview-image-bg-color:#2b2a33; +} + +@media screen and (forced-colors: active){ + + .dialog.newAltText{ + --preview-image-bg-color:ButtonFace; + --preview-image-border:1px solid ButtonText; + } +} + +.dialog.newAltText{ + + width:80%; + max-width:570px; + min-width:300px; + padding:0; +} + +.dialog.newAltText.noAi #newAltTextDisclaimer,.dialog.newAltText.noAi #newAltTextCreateAutomatically{ + display:none !important; +} + +.dialog.newAltText.aiInstalling #newAltTextCreateAutomatically{ + display:none !important; +} + +.dialog.newAltText.aiInstalling #newAltTextDownloadModel{ + display:flex !important; +} + +.dialog.newAltText.error #newAltTextNotNow{ + display:none !important; +} + +.dialog.newAltText.error #newAltTextCancel{ + display:inline-block !important; +} + +.dialog.newAltText:not(.error) #newAltTextError{ + display:none !important; +} + +.dialog.newAltText #newAltTextContainer{ + display:flex; + width:auto; + padding:16px; + flex-direction:column; + justify-content:flex-end; + align-items:flex-start; + gap:12px; + flex:0 1 auto; + line-height:normal; +} + +:is(.dialog.newAltText #newAltTextContainer) #mainContent{ + display:flex; + justify-content:flex-end; + align-items:flex-start; + gap:12px; + align-self:stretch; + flex:1 1 auto; +} + +:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionAndSettings{ + display:flex; + flex-direction:column; + align-items:flex-start; + gap:16px; + flex:1 0 0; + align-self:stretch; +} + +:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction{ + display:flex; + flex-direction:column; + align-items:flex-start; + gap:8px; + align-self:stretch; + flex:1 1 auto; +} + +:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer{ + width:100%; + height:70px; + position:relative; +} + +:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) textarea{ + width:100%; + height:100%; + padding:8px; +} + +:is(:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) textarea)::-moz-placeholder{ + color:var(--text-secondary-color); +} + +:is(:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) textarea)::placeholder{ + color:var(--text-secondary-color); +} + +:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) .altTextSpinner{ + display:none; + position:absolute; + width:16px; + height:16px; + inset-inline-start:8px; + inset-block-start:8px; + -webkit-mask-size:cover; + mask-size:cover; + background-color:var(--text-secondary-color); + pointer-events:none; +} + +.loading:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) textarea::-moz-placeholder{ + color:transparent; +} + +.loading:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) textarea::placeholder{ + color:transparent; +} + +.loading:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) .altTextSpinner{ + display:inline-block; + -webkit-mask-image:var(--new-alt-text-spinner-icon); + mask-image:var(--new-alt-text-spinner-icon); +} + +:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescription{ + font-size:11px; +} + +:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDisclaimer{ + display:flex; + flex-direction:row; + align-items:flex-start; + gap:4px; + font-size:11px; +} + +:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDisclaimer)::before{ + content:""; + display:inline-block; + width:17px; + height:16px; + -webkit-mask-image:var(--new-alt-text-ai-disclaimer-icon); + mask-image:var(--new-alt-text-ai-disclaimer-icon); + -webkit-mask-size:cover; + mask-size:cover; + background-color:var(--text-secondary-color); + flex:1 0 auto; +} + +:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #newAltTextDownloadModel{ + display:flex; + align-items:center; + gap:4px; + align-self:stretch; +} + +:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #newAltTextDownloadModel)::before{ + content:""; + display:inline-block; + width:16px; + height:16px; + -webkit-mask-image:var(--new-alt-text-spinner-icon); + mask-image:var(--new-alt-text-spinner-icon); + -webkit-mask-size:cover; + mask-size:cover; + background-color:var(--text-secondary-color); +} + +:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #newAltTextImagePreview{ + width:180px; + aspect-ratio:1; + display:flex; + justify-content:center; + align-items:center; + flex:0 0 auto; + background-color:var(--preview-image-bg-color); + border:var(--preview-image-border); +} + +:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #newAltTextImagePreview) > canvas{ + max-width:100%; + max-height:100%; +} + +.colorPicker{ + --hover-outline-color:#0250bb; + --selected-outline-color:#0060df; + --swatch-border-color:#cfcfd8; +} + +@media (prefers-color-scheme: dark){ + + :where(html:not(.is-light)) .colorPicker{ + --hover-outline-color:#80ebff; + --selected-outline-color:#aaf2ff; + --swatch-border-color:#52525e; + } +} + +:where(html.is-dark) .colorPicker{ + --hover-outline-color:#80ebff; + --selected-outline-color:#aaf2ff; + --swatch-border-color:#52525e; +} + +@media screen and (forced-colors: active){ + + .colorPicker{ + --hover-outline-color:Highlight; + --selected-outline-color:var(--hover-outline-color); + --swatch-border-color:ButtonText; + } +} + +.colorPicker .swatch{ + width:16px; + height:16px; + border:1px solid var(--swatch-border-color); + border-radius:100%; + outline-offset:2px; + box-sizing:border-box; + forced-color-adjust:none; +} + +.colorPicker button:is(:hover,.selected) > .swatch{ + border:none; +} + +.annotationEditorLayer[data-main-rotation="0"] .highlightEditor:not(.free) > .editToolbar{ + rotate:0deg; +} + +.annotationEditorLayer[data-main-rotation="90"] .highlightEditor:not(.free) > .editToolbar{ + rotate:270deg; +} + +.annotationEditorLayer[data-main-rotation="180"] .highlightEditor:not(.free) > .editToolbar{ + rotate:180deg; +} + +.annotationEditorLayer[data-main-rotation="270"] .highlightEditor:not(.free) > .editToolbar{ + rotate:90deg; +} + +.annotationEditorLayer .highlightEditor{ + position:absolute; + background:transparent; + z-index:1; + cursor:auto; + max-width:100%; + max-height:100%; + border:none; + outline:none; + pointer-events:none; + transform-origin:0 0; +} + +:is(.annotationEditorLayer .highlightEditor):not(.free){ + transform:none; +} + +:is(.annotationEditorLayer .highlightEditor) .internal{ + position:absolute; + top:0; + left:0; + width:100%; + height:100%; + pointer-events:auto; +} + +.disabled:is(.annotationEditorLayer .highlightEditor) .internal{ + pointer-events:none; +} + +.selectedEditor:is(.annotationEditorLayer .highlightEditor) .internal{ + cursor:pointer; +} + +:is(.annotationEditorLayer .highlightEditor) .editToolbar{ + --editor-toolbar-colorpicker-arrow-image:url(images/toolbarButton-menuArrow.svg); + + transform-origin:center !important; +} + +:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker{ + position:relative; + width:auto; + display:flex; + justify-content:center; + align-items:center; + gap:4px; + padding:4px; +} + +:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker)::after{ + content:""; + -webkit-mask-image:var(--editor-toolbar-colorpicker-arrow-image); + mask-image:var(--editor-toolbar-colorpicker-arrow-image); + -webkit-mask-repeat:no-repeat; + mask-repeat:no-repeat; + -webkit-mask-position:center; + mask-position:center; + display:inline-block; + background-color:var(--editor-toolbar-fg-color); + width:12px; + height:12px; +} + +:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker):hover::after{ + background-color:var(--editor-toolbar-hover-fg-color); +} + +:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker):has(.dropdown:not(.hidden)){ + background-color:var(--editor-toolbar-hover-bg-color); +} + +:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker):has(.dropdown:not(.hidden))::after{ + scale:-1; +} + +:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown{ + position:absolute; + display:flex; + justify-content:center; + align-items:center; + flex-direction:column; + gap:11px; + padding-block:8px; + border-radius:6px; + background-color:var(--editor-toolbar-bg-color); + border:1px solid var(--editor-toolbar-border-color); + box-shadow:var(--editor-toolbar-shadow); + inset-block-start:calc(100% + 4px); + width:calc(100% + 2 * var(--editor-toolbar-padding)); +} + +:is(:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown) button{ + width:100%; + height:auto; + border:none; + cursor:pointer; + display:flex; + justify-content:center; + align-items:center; + background:none; +} + +:is(:is(:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown) button):is(:active,:focus-visible){ + outline:none; +} + +:is(:is(:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown) button) > .swatch{ + outline-offset:2px; +} + +[aria-selected="true"]:is(:is(:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown) button) > .swatch{ + outline:2px solid var(--selected-outline-color); +} + +:is(:is(:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown) button):is(:hover,:active,:focus-visible) > .swatch{ + outline:2px solid var(--hover-outline-color); +} + +.editorParamsToolbar:has(#highlightParamsToolbarContainer){ + padding:unset; +} + +#highlightParamsToolbarContainer{ + gap:16px; + padding-inline:10px; + padding-block-end:12px; +} + +#highlightParamsToolbarContainer .colorPicker{ + display:flex; + flex-direction:column; + gap:8px; +} + +:is(#highlightParamsToolbarContainer .colorPicker) .dropdown{ + display:flex; + justify-content:space-between; + align-items:center; + flex-direction:row; + height:auto; +} + +:is(:is(#highlightParamsToolbarContainer .colorPicker) .dropdown) button{ + width:auto; + height:auto; + border:none; + cursor:pointer; + display:flex; + justify-content:center; + align-items:center; + background:none; + flex:0 0 auto; + padding:0; +} + +:is(:is(:is(#highlightParamsToolbarContainer .colorPicker) .dropdown) button) .swatch{ + width:24px; + height:24px; +} + +:is(:is(:is(#highlightParamsToolbarContainer .colorPicker) .dropdown) button):is(:active,:focus-visible){ + outline:none; +} + +[aria-selected="true"]:is(:is(:is(#highlightParamsToolbarContainer .colorPicker) .dropdown) button) > .swatch{ + outline:2px solid var(--selected-outline-color); +} + +:is(:is(:is(#highlightParamsToolbarContainer .colorPicker) .dropdown) button):is(:hover,:active,:focus-visible) > .swatch{ + outline:2px solid var(--hover-outline-color); +} + +#highlightParamsToolbarContainer #editorHighlightThickness{ + display:flex; + flex-direction:column; + align-items:center; + gap:4px; + align-self:stretch; +} + +:is(#highlightParamsToolbarContainer #editorHighlightThickness) .editorParamsLabel{ + height:auto; + align-self:stretch; +} + +:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker{ + display:flex; + justify-content:space-between; + align-items:center; + align-self:stretch; + + --example-color:#bfbfc9; +} + +@media (prefers-color-scheme: dark){ + + :where(html:not(.is-light)) :is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker{ + --example-color:#80808e; + } +} + +:where(html.is-dark) :is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker{ + --example-color:#80808e; +} + +@media screen and (forced-colors: active){ + + :is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker{ + --example-color:CanvasText; + } +} + +:is(:is(:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker) > .editorParamsSlider[disabled]){ + opacity:0.4; +} + +:is(:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker)::before,:is(:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker)::after{ + content:""; + width:8px; + aspect-ratio:1; + display:block; + border-radius:100%; + background-color:var(--example-color); +} + +:is(:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker)::after{ + width:24px; +} + +:is(:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker) .editorParamsSlider{ + width:unset; + height:14px; +} + +#highlightParamsToolbarContainer #editorHighlightVisibility{ + display:flex; + flex-direction:column; + align-items:flex-start; + gap:8px; + align-self:stretch; +} + +:is(#highlightParamsToolbarContainer #editorHighlightVisibility) .divider{ + --divider-color:#d7d7db; +} + +@media (prefers-color-scheme: dark){ + + :where(html:not(.is-light)) :is(#highlightParamsToolbarContainer #editorHighlightVisibility) .divider{ + --divider-color:#8f8f9d; + } +} + +:where(html.is-dark) :is(#highlightParamsToolbarContainer #editorHighlightVisibility) .divider{ + --divider-color:#8f8f9d; +} + +@media screen and (forced-colors: active){ + + :is(#highlightParamsToolbarContainer #editorHighlightVisibility) .divider{ + --divider-color:CanvasText; + } +} + +:is(#highlightParamsToolbarContainer #editorHighlightVisibility) .divider{ + + margin-block:4px; + width:100%; + height:1px; + background-color:var(--divider-color); +} + +:is(#highlightParamsToolbarContainer #editorHighlightVisibility) .toggler{ + display:flex; + justify-content:space-between; + align-items:center; + align-self:stretch; +} + +#altTextSettingsDialog{ + padding:16px; +} + +#altTextSettingsDialog #altTextSettingsContainer{ + display:flex; + width:573px; + flex-direction:column; + gap:16px; +} + +:is(#altTextSettingsDialog #altTextSettingsContainer) .mainContainer{ + gap:16px; +} + +:is(#altTextSettingsDialog #altTextSettingsContainer) .description{ + color:var(--text-secondary-color); +} + +:is(#altTextSettingsDialog #altTextSettingsContainer) #aiModelSettings{ + display:flex; + flex-direction:column; + gap:12px; +} + +:is(:is(#altTextSettingsDialog #altTextSettingsContainer) #aiModelSettings) button{ + width:-moz-fit-content; + width:fit-content; +} + +.download:is(:is(#altTextSettingsDialog #altTextSettingsContainer) #aiModelSettings) #deleteModelButton{ + display:none; +} + +:is(:is(#altTextSettingsDialog #altTextSettingsContainer) #aiModelSettings):not(.download) #downloadModelButton{ + display:none; +} + +:is(#altTextSettingsDialog #altTextSettingsContainer) #automaticAltText,:is(#altTextSettingsDialog #altTextSettingsContainer) #altTextEditor{ + display:flex; + flex-direction:column; + gap:8px; +} + +:is(#altTextSettingsDialog #altTextSettingsContainer) #createModelDescription,:is(#altTextSettingsDialog #altTextSettingsContainer) #aiModelSettings,:is(#altTextSettingsDialog #altTextSettingsContainer) #showAltTextDialogDescription{ + padding-inline-start:40px; +} + +:is(#altTextSettingsDialog #altTextSettingsContainer) #automaticSettings{ + display:flex; + flex-direction:column; + gap:16px; +} + +:root{ + --viewer-container-height:0; + --pdfViewer-padding-bottom:0; + --page-margin:1px auto -8px; + --page-border:9px solid transparent; + --spreadHorizontalWrapped-margin-LR:-3.5px; + --loading-icon-delay:400ms; + --focus-ring-color:#0060df; + --focus-ring-outline:2px solid var(--focus-ring-color); +} + +@media (prefers-color-scheme: dark){ + + :root:where(:not(.is-light)){ + --focus-ring-color:#0df; + } +} + +:root:where(.is-dark){ + --focus-ring-color:#0df; +} + +@media screen and (forced-colors: active){ + + :root{ + --pdfViewer-padding-bottom:9px; + --page-margin:8px auto -1px; + --page-border:1px solid CanvasText; + --spreadHorizontalWrapped-margin-LR:3.5px; + --focus-ring-color:CanvasText; + } +} + +[data-main-rotation="90"]{ + transform:rotate(90deg) translateY(-100%); +} +[data-main-rotation="180"]{ + transform:rotate(180deg) translate(-100%, -100%); +} +[data-main-rotation="270"]{ + transform:rotate(270deg) translateX(-100%); +} + +#hiddenCopyElement, +.hiddenCanvasElement{ + position:absolute; + top:0; + left:0; + width:0; + height:0; + display:none; +} + +.pdfViewer{ + --scale-factor:1; + --page-bg-color:unset; + + padding-bottom:var(--pdfViewer-padding-bottom); + + --hcm-highlight-filter:none; + --hcm-highlight-selected-filter:none; +} + +@media screen and (forced-colors: active){ + + .pdfViewer{ + --hcm-highlight-filter:invert(100%); + } +} + +.pdfViewer.copyAll{ + cursor:wait; +} + +.pdfViewer .canvasWrapper{ + overflow:hidden; + width:100%; + height:100%; +} + +:is(.pdfViewer .canvasWrapper) canvas{ + position:absolute; + top:0; + left:0; + margin:0; + display:block; + width:100%; + height:100%; + contain:content; +} + +:is(:is(.pdfViewer .canvasWrapper) canvas) .structTree{ + contain:strict; +} + +.pdfViewer .page{ + --user-unit:1; + --total-scale-factor:calc(var(--scale-factor) * var(--user-unit)); + --scale-round-x:1px; + --scale-round-y:1px; + + direction:ltr; + width:816px; + height:1056px; + margin:var(--page-margin); + position:relative; + overflow:visible; + border:var(--page-border); + background-clip:content-box; + background-color:var(--page-bg-color, rgb(255 255 255)); +} + +.pdfViewer .dummyPage{ + position:relative; + width:0; + height:var(--viewer-container-height); +} + +.pdfViewer.noUserSelect{ + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; +} + +.pdfViewer.removePageBorders .page{ + margin:0 auto 10px; + border:none; +} + +.pdfViewer:is(.scrollHorizontal, .scrollWrapped), +.spread{ + margin-inline:3.5px; + text-align:center; +} + +.pdfViewer.scrollHorizontal, +.spread{ + white-space:nowrap; +} + +.pdfViewer.removePageBorders, +.pdfViewer:is(.scrollHorizontal, .scrollWrapped) .spread{ + margin-inline:0; +} + +.spread :is(.page, .dummyPage), +.pdfViewer:is(.scrollHorizontal, .scrollWrapped) :is(.page, .spread){ + display:inline-block; + vertical-align:middle; +} + +.spread .page, +.pdfViewer:is(.scrollHorizontal, .scrollWrapped) .page{ + margin-inline:var(--spreadHorizontalWrapped-margin-LR); +} + +.pdfViewer.removePageBorders .spread .page, +.pdfViewer.removePageBorders:is(.scrollHorizontal, .scrollWrapped) .page{ + margin-inline:5px; +} + +.pdfViewer .page.loadingIcon::after{ + position:absolute; + top:0; + left:0; + content:""; + width:100%; + height:100%; + background:url("images/loading-icon.gif") center no-repeat; + display:none; + transition-property:display; + transition-delay:var(--loading-icon-delay); + z-index:5; + contain:strict; +} + +.pdfViewer .page.loading::after{ + display:block; +} + +.pdfViewer .page:not(.loading)::after{ + transition-property:none; + display:none; +} + +.pdfPresentationMode .pdfViewer{ + padding-bottom:0; +} + +.pdfPresentationMode .spread{ + margin:0; +} + +.pdfPresentationMode .pdfViewer .page{ + margin:0 auto; + border:2px solid transparent; +} + +:root{ + --dir-factor:1; + --inline-start:left; + --inline-end:right; + + --sidebar-width:200px; + --sidebar-transition-duration:200ms; + --sidebar-transition-timing-function:ease; + + --toolbar-height:32px; + --toolbar-horizontal-padding:1px; + --toolbar-vertical-padding:2px; + --icon-size:16px; + + --toolbar-icon-opacity:0.7; + --doorhanger-icon-opacity:0.9; + --doorhanger-height:8px; + + --main-color:rgb(12 12 13); + --body-bg-color:rgb(212 212 215); + --progressBar-color:rgb(10 132 255); + --progressBar-bg-color:rgb(221 221 222); + --progressBar-blend-color:rgb(116 177 239); + --scrollbar-color:auto; + --scrollbar-bg-color:auto; + --toolbar-icon-bg-color:rgb(0 0 0); + --toolbar-icon-hover-bg-color:rgb(0 0 0); + + --sidebar-narrow-bg-color:rgb(212 212 215 / 0.9); + --sidebar-toolbar-bg-color:rgb(245 246 247); + --toolbar-bg-color:rgb(249 249 250); + --toolbar-border-color:rgb(184 184 184); + --toolbar-box-shadow:0 1px 0 var(--toolbar-border-color); + --toolbar-border-bottom:none; + --toolbarSidebar-box-shadow:inset calc(-1px * var(--dir-factor)) 0 0 rgb(0 0 0 / 0.25), 0 1px 0 rgb(0 0 0 / 0.15), 0 0 1px rgb(0 0 0 / 0.1); + --toolbarSidebar-border-bottom:none; + --button-hover-color:color-mix(in srgb, currentColor 17%, transparent); + --toggled-btn-color:rgb(0 0 0); + --toggled-btn-bg-color:rgb(0 0 0 / 0.3); + --toggled-hover-active-btn-color:rgb(0 0 0 / 0.4); + --toggled-hover-btn-outline:none; + --dropdown-btn-bg-color:rgb(215 215 219); + --dropdown-btn-border:none; + --separator-color:rgb(0 0 0 / 0.3); + --field-color:rgb(6 6 6); + --field-bg-color:rgb(255 255 255); + --field-border-color:rgb(187 187 188); + --treeitem-color:rgb(0 0 0 / 0.8); + --treeitem-bg-color:rgb(0 0 0 / 0.15); + --treeitem-hover-color:rgb(0 0 0 / 0.9); + --treeitem-selected-color:rgb(0 0 0 / 0.9); + --treeitem-selected-bg-color:rgb(0 0 0 / 0.25); + --thumbnail-hover-color:rgb(0 0 0 / 0.1); + --thumbnail-selected-color:rgb(0 0 0 / 0.2); + --doorhanger-bg-color:rgb(255 255 255); + --doorhanger-border-color:rgb(12 12 13 / 0.2); + --doorhanger-hover-color:rgb(12 12 13); + --doorhanger-separator-color:rgb(222 222 222); + --dialog-button-border:none; + --dialog-button-bg-color:rgb(12 12 13 / 0.1); + --dialog-button-hover-bg-color:rgb(12 12 13 / 0.3); + + --loading-icon:url(images/loading.svg); + --treeitem-expanded-icon:url(images/treeitem-expanded.svg); + --treeitem-collapsed-icon:url(images/treeitem-collapsed.svg); + --toolbarButton-editorFreeText-icon:url(images/toolbarButton-editorFreeText.svg); + --toolbarButton-editorHighlight-icon:url(images/toolbarButton-editorHighlight.svg); + --toolbarButton-editorInk-icon:url(images/toolbarButton-editorInk.svg); + --toolbarButton-editorStamp-icon:url(images/toolbarButton-editorStamp.svg); + --toolbarButton-editorSignature-icon:url(images/toolbarButton-editorSignature.svg); + --toolbarButton-menuArrow-icon:url(images/toolbarButton-menuArrow.svg); + --toolbarButton-sidebarToggle-icon:url(images/toolbarButton-sidebarToggle.svg); + --toolbarButton-secondaryToolbarToggle-icon:url(images/toolbarButton-secondaryToolbarToggle.svg); + --toolbarButton-pageUp-icon:url(images/toolbarButton-pageUp.svg); + --toolbarButton-pageDown-icon:url(images/toolbarButton-pageDown.svg); + --toolbarButton-zoomOut-icon:url(images/toolbarButton-zoomOut.svg); + --toolbarButton-zoomIn-icon:url(images/toolbarButton-zoomIn.svg); + --toolbarButton-presentationMode-icon:url(images/toolbarButton-presentationMode.svg); + --toolbarButton-print-icon:url(images/toolbarButton-print.svg); + --toolbarButton-openFile-icon:url(images/toolbarButton-openFile.svg); + --toolbarButton-download-icon:url(images/toolbarButton-download.svg); + --toolbarButton-bookmark-icon:url(images/toolbarButton-bookmark.svg); + --toolbarButton-viewThumbnail-icon:url(images/toolbarButton-viewThumbnail.svg); + --toolbarButton-viewOutline-icon:url(images/toolbarButton-viewOutline.svg); + --toolbarButton-viewAttachments-icon:url(images/toolbarButton-viewAttachments.svg); + --toolbarButton-viewLayers-icon:url(images/toolbarButton-viewLayers.svg); + --toolbarButton-currentOutlineItem-icon:url(images/toolbarButton-currentOutlineItem.svg); + --toolbarButton-search-icon:url(images/toolbarButton-search.svg); + --findbarButton-previous-icon:url(images/findbarButton-previous.svg); + --findbarButton-next-icon:url(images/findbarButton-next.svg); + --secondaryToolbarButton-firstPage-icon:url(images/secondaryToolbarButton-firstPage.svg); + --secondaryToolbarButton-lastPage-icon:url(images/secondaryToolbarButton-lastPage.svg); + --secondaryToolbarButton-rotateCcw-icon:url(images/secondaryToolbarButton-rotateCcw.svg); + --secondaryToolbarButton-rotateCw-icon:url(images/secondaryToolbarButton-rotateCw.svg); + --secondaryToolbarButton-selectTool-icon:url(images/secondaryToolbarButton-selectTool.svg); + --secondaryToolbarButton-handTool-icon:url(images/secondaryToolbarButton-handTool.svg); + --secondaryToolbarButton-scrollPage-icon:url(images/secondaryToolbarButton-scrollPage.svg); + --secondaryToolbarButton-scrollVertical-icon:url(images/secondaryToolbarButton-scrollVertical.svg); + --secondaryToolbarButton-scrollHorizontal-icon:url(images/secondaryToolbarButton-scrollHorizontal.svg); + --secondaryToolbarButton-scrollWrapped-icon:url(images/secondaryToolbarButton-scrollWrapped.svg); + --secondaryToolbarButton-spreadNone-icon:url(images/secondaryToolbarButton-spreadNone.svg); + --secondaryToolbarButton-spreadOdd-icon:url(images/secondaryToolbarButton-spreadOdd.svg); + --secondaryToolbarButton-spreadEven-icon:url(images/secondaryToolbarButton-spreadEven.svg); + --secondaryToolbarButton-imageAltTextSettings-icon:var( + --toolbarButton-editorStamp-icon + ); + --secondaryToolbarButton-documentProperties-icon:url(images/secondaryToolbarButton-documentProperties.svg); + --editorParams-stampAddImage-icon:url(images/toolbarButton-zoomIn.svg); +} + +[dir="rtl"]:root{ + --dir-factor:-1; + --inline-start:right; + --inline-end:left; +} + +@media (prefers-color-scheme: dark){ + :root:where(:not(.is-light)){ + --main-color:rgb(249 249 250); + --body-bg-color:rgb(42 42 46); + --progressBar-color:rgb(0 96 223); + --progressBar-bg-color:rgb(40 40 43); + --progressBar-blend-color:rgb(20 68 133); + --scrollbar-color:rgb(121 121 123); + --scrollbar-bg-color:rgb(35 35 39); + --toolbar-icon-bg-color:rgb(255 255 255); + --toolbar-icon-hover-bg-color:rgb(255 255 255); + + --sidebar-narrow-bg-color:rgb(42 42 46 / 0.9); + --sidebar-toolbar-bg-color:rgb(50 50 52); + --toolbar-bg-color:rgb(56 56 61); + --toolbar-border-color:rgb(12 12 13); + --toggled-btn-color:rgb(255 255 255); + --toggled-btn-bg-color:rgb(0 0 0 / 0.3); + --toggled-hover-active-btn-color:rgb(0 0 0 / 0.4); + --dropdown-btn-bg-color:rgb(74 74 79); + --separator-color:rgb(0 0 0 / 0.3); + --field-color:rgb(250 250 250); + --field-bg-color:rgb(64 64 68); + --field-border-color:rgb(115 115 115); + --treeitem-color:rgb(255 255 255 / 0.8); + --treeitem-bg-color:rgb(255 255 255 / 0.15); + --treeitem-hover-color:rgb(255 255 255 / 0.9); + --treeitem-selected-color:rgb(255 255 255 / 0.9); + --treeitem-selected-bg-color:rgb(255 255 255 / 0.25); + --thumbnail-hover-color:rgb(255 255 255 / 0.1); + --thumbnail-selected-color:rgb(255 255 255 / 0.2); + --doorhanger-bg-color:#42414d; + --doorhanger-border-color:rgb(39 39 43); + --doorhanger-hover-color:rgb(249 249 250); + --doorhanger-separator-color:rgb(92 92 97); + --dialog-button-bg-color:rgb(92 92 97); + --dialog-button-hover-bg-color:rgb(115 115 115); + } +} + +:root:where(.is-dark){ + --main-color:rgb(249 249 250); + --body-bg-color:rgb(42 42 46); + --progressBar-color:rgb(0 96 223); + --progressBar-bg-color:rgb(40 40 43); + --progressBar-blend-color:rgb(20 68 133); + --scrollbar-color:rgb(121 121 123); + --scrollbar-bg-color:rgb(35 35 39); + --toolbar-icon-bg-color:rgb(255 255 255); + --toolbar-icon-hover-bg-color:rgb(255 255 255); + + --sidebar-narrow-bg-color:rgb(42 42 46 / 0.9); + --sidebar-toolbar-bg-color:rgb(50 50 52); + --toolbar-bg-color:rgb(56 56 61); + --toolbar-border-color:rgb(12 12 13); + --toggled-btn-color:rgb(255 255 255); + --toggled-btn-bg-color:rgb(0 0 0 / 0.3); + --toggled-hover-active-btn-color:rgb(0 0 0 / 0.4); + --dropdown-btn-bg-color:rgb(74 74 79); + --separator-color:rgb(0 0 0 / 0.3); + --field-color:rgb(250 250 250); + --field-bg-color:rgb(64 64 68); + --field-border-color:rgb(115 115 115); + --treeitem-color:rgb(255 255 255 / 0.8); + --treeitem-bg-color:rgb(255 255 255 / 0.15); + --treeitem-hover-color:rgb(255 255 255 / 0.9); + --treeitem-selected-color:rgb(255 255 255 / 0.9); + --treeitem-selected-bg-color:rgb(255 255 255 / 0.25); + --thumbnail-hover-color:rgb(255 255 255 / 0.1); + --thumbnail-selected-color:rgb(255 255 255 / 0.2); + --doorhanger-bg-color:#42414d; + --doorhanger-border-color:rgb(39 39 43); + --doorhanger-hover-color:rgb(249 249 250); + --doorhanger-separator-color:rgb(92 92 97); + --dialog-button-bg-color:rgb(92 92 97); + --dialog-button-hover-bg-color:rgb(115 115 115); +} + +@media screen and (forced-colors: active){ + :root{ + --button-hover-color:Highlight; + --toolbar-icon-opacity:1; + --toolbar-icon-bg-color:ButtonText; + --toolbar-icon-hover-bg-color:ButtonFace; + --toggled-hover-active-btn-color:ButtonText; + --toggled-hover-btn-outline:2px solid ButtonBorder; + --toolbar-border-color:CanvasText; + --toolbar-border-bottom:1px solid var(--toolbar-border-color); + --toolbar-box-shadow:none; + --toggled-btn-color:HighlightText; + --toggled-btn-bg-color:LinkText; + --doorhanger-hover-color:ButtonFace; + --doorhanger-border-color-whcm:1px solid ButtonText; + --doorhanger-triangle-opacity-whcm:0; + --dialog-button-border:1px solid Highlight; + --dialog-button-hover-bg-color:Highlight; + --dialog-button-hover-color:ButtonFace; + --dropdown-btn-border:1px solid ButtonText; + --field-border-color:ButtonText; + --main-color:CanvasText; + --separator-color:GrayText; + --doorhanger-separator-color:GrayText; + --toolbarSidebar-box-shadow:none; + --toolbarSidebar-border-bottom:1px solid var(--toolbar-border-color); + } +} + +@media screen and (prefers-reduced-motion: reduce){ + :root{ + --sidebar-transition-duration:0; + } +} + +@keyframes progressIndeterminate{ + 0%{ + transform:translateX(calc(-142px * var(--dir-factor))); + } + + 100%{ + transform:translateX(0); + } +} + +html[data-toolbar-density="compact"]{ + --toolbar-height:30px; +} + +html[data-toolbar-density="touch"]{ + --toolbar-height:44px; +} + +html, +body{ + height:100%; + width:100%; +} + +body{ + margin:0; + background-color:var(--body-bg-color); + scrollbar-color:var(--scrollbar-color) var(--scrollbar-bg-color); +} + +body.wait::before{ + content:""; + position:fixed; + width:100%; + height:100%; + z-index:100000; + cursor:wait; +} + +.hidden, +[hidden]{ + display:none !important; +} + +#viewerContainer.pdfPresentationMode:fullscreen{ + top:0; + background-color:rgb(0 0 0); + width:100%; + height:100%; + overflow:hidden; + cursor:none; + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; +} + +.pdfPresentationMode:fullscreen section:not([data-internal-link]){ + pointer-events:none; +} + +.pdfPresentationMode:fullscreen .textLayer span{ + cursor:none; +} + +.pdfPresentationMode.pdfPresentationModeControls > *, +.pdfPresentationMode.pdfPresentationModeControls .textLayer span{ + cursor:default; +} + +#outerContainer{ + width:100%; + height:100%; + position:relative; + margin:0; +} + +#sidebarContainer{ + position:absolute; + inset-block:var(--toolbar-height) 0; + inset-inline-start:calc(-1 * var(--sidebar-width)); + width:var(--sidebar-width); + visibility:hidden; + z-index:1; + font:message-box; + border-top:1px solid transparent; + border-inline-end:var(--doorhanger-border-color-whcm); + transition-property:inset-inline-start; + transition-duration:var(--sidebar-transition-duration); + transition-timing-function:var(--sidebar-transition-timing-function); +} + +#outerContainer:is(.sidebarMoving, .sidebarOpen) #sidebarContainer{ + visibility:visible; +} + +#outerContainer.sidebarOpen #sidebarContainer{ + inset-inline-start:0; +} + +#mainContainer{ + position:absolute; + inset:0; + min-width:350px; + margin:0; + display:flex; + flex-direction:column; +} + +#sidebarContent{ + inset-block:var(--toolbar-height) 0; + inset-inline-start:0; + overflow:auto; + position:absolute; + width:100%; + box-shadow:inset calc(-1px * var(--dir-factor)) 0 0 rgb(0 0 0 / 0.25); +} + +#viewerContainer{ + overflow:auto; + position:absolute; + inset:var(--toolbar-height) 0 0; + outline:none; + z-index:0; +} + +#viewerContainer:not(.pdfPresentationMode){ + transition-duration:var(--sidebar-transition-duration); + transition-timing-function:var(--sidebar-transition-timing-function); +} + +#outerContainer.sidebarOpen #viewerContainer:not(.pdfPresentationMode){ + inset-inline-start:var(--sidebar-width); + transition-property:inset-inline-start; +} + +#sidebarContainer :is(input, button, select){ + font:message-box; +} + +.toolbar{ + z-index:2; +} + +#toolbarSidebar{ + width:100%; + height:var(--toolbar-height); + background-color:var(--sidebar-toolbar-bg-color); + box-shadow:var(--toolbarSidebar-box-shadow); + border-bottom:var(--toolbarSidebar-border-bottom); + padding:var(--toolbar-vertical-padding) var(--toolbar-horizontal-padding); + justify-content:space-between; +} + +#toolbarSidebar #toolbarSidebarLeft{ + width:auto; + height:100%; +} + +:is(#toolbarSidebar #toolbarSidebarLeft) #viewThumbnail::before{ + -webkit-mask-image:var(--toolbarButton-viewThumbnail-icon); + mask-image:var(--toolbarButton-viewThumbnail-icon); +} + +:is(#toolbarSidebar #toolbarSidebarLeft) #viewOutline::before{ + -webkit-mask-image:var(--toolbarButton-viewOutline-icon); + mask-image:var(--toolbarButton-viewOutline-icon); + transform:scaleX(var(--dir-factor)); +} + +:is(#toolbarSidebar #toolbarSidebarLeft) #viewAttachments::before{ + -webkit-mask-image:var(--toolbarButton-viewAttachments-icon); + mask-image:var(--toolbarButton-viewAttachments-icon); +} + +:is(#toolbarSidebar #toolbarSidebarLeft) #viewLayers::before{ + -webkit-mask-image:var(--toolbarButton-viewLayers-icon); + mask-image:var(--toolbarButton-viewLayers-icon); +} + +#toolbarSidebar #toolbarSidebarRight{ + width:auto; + height:100%; + padding-inline-end:2px; +} + +#sidebarResizer{ + position:absolute; + inset-block:0; + inset-inline-end:-6px; + width:6px; + z-index:200; + cursor:ew-resize; +} + +#outerContainer.sidebarOpen #loadingBar{ + inset-inline-start:var(--sidebar-width); +} + +#outerContainer.sidebarResizing +:is(#sidebarContainer, #viewerContainer, #loadingBar){ + transition-duration:0s; +} + +.doorHanger, +.doorHangerRight{ + border-radius:2px; + box-shadow:0 1px 5px var(--doorhanger-border-color), 0 0 0 1px var(--doorhanger-border-color); + border:var(--doorhanger-border-color-whcm); + background-color:var(--doorhanger-bg-color); + inset-block-start:calc(100% + var(--doorhanger-height) - 2px); +} + +:is(.doorHanger,.doorHangerRight)::after,:is(.doorHanger,.doorHangerRight)::before{ + bottom:100%; + border-style:solid; + border-color:transparent; + content:""; + height:0; + width:0; + position:absolute; + pointer-events:none; + opacity:var(--doorhanger-triangle-opacity-whcm); +} + +:is(.doorHanger,.doorHangerRight)::before{ + border-width:calc(var(--doorhanger-height) + 2px); + border-bottom-color:var(--doorhanger-border-color); +} + +:is(.doorHanger,.doorHangerRight)::after{ + border-width:var(--doorhanger-height); +} + +.doorHangerRight{ + inset-inline-end:calc(50% - var(--doorhanger-height) - 1px); +} + +.doorHangerRight::before{ + inset-inline-end:-1px; +} + +.doorHangerRight::after{ + border-bottom-color:var(--doorhanger-bg-color); + inset-inline-end:1px; +} + +.doorHanger{ + inset-inline-start:calc(50% - var(--doorhanger-height) - 1px); +} + +.doorHanger::before{ + inset-inline-start:-1px; +} + +.doorHanger::after{ + border-bottom-color:var(--toolbar-bg-color); + inset-inline-start:1px; +} + +.dialogButton{ + border:none; + background:none; + width:28px; + height:28px; + outline:none; +} + +.dialogButton:is(:hover, :focus-visible){ + background-color:var(--dialog-button-hover-bg-color); +} + +.dialogButton:is(:hover, :focus-visible) > span{ + color:var(--dialog-button-hover-color); +} + +.splitToolbarButtonSeparator{ + float:var(--inline-start); + width:0; + height:62%; + border-left:1px solid var(--separator-color); + border-right:none; +} + +.dialogButton{ + min-width:16px; + margin:2px 1px; + padding:2px 6px 0; + border:none; + border-radius:2px; + color:var(--main-color); + font-size:12px; + line-height:14px; + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; + cursor:default; + box-sizing:border-box; +} + +.treeItemToggler::before{ + position:absolute; + display:inline-block; + width:16px; + height:16px; + + content:""; + background-color:var(--toolbar-icon-bg-color); + -webkit-mask-size:cover; + mask-size:cover; +} + +#sidebarToggleButton::before{ + -webkit-mask-image:var(--toolbarButton-sidebarToggle-icon); + mask-image:var(--toolbarButton-sidebarToggle-icon); + transform:scaleX(var(--dir-factor)); +} + +#secondaryToolbarToggleButton::before{ + -webkit-mask-image:var(--toolbarButton-secondaryToolbarToggle-icon); + mask-image:var(--toolbarButton-secondaryToolbarToggle-icon); + transform:scaleX(var(--dir-factor)); +} + +#previous::before{ + -webkit-mask-image:var(--toolbarButton-pageUp-icon); + mask-image:var(--toolbarButton-pageUp-icon); +} + +#next::before{ + -webkit-mask-image:var(--toolbarButton-pageDown-icon); + mask-image:var(--toolbarButton-pageDown-icon); +} + +#zoomOutButton::before{ + -webkit-mask-image:var(--toolbarButton-zoomOut-icon); + mask-image:var(--toolbarButton-zoomOut-icon); +} + +#zoomInButton::before{ + -webkit-mask-image:var(--toolbarButton-zoomIn-icon); + mask-image:var(--toolbarButton-zoomIn-icon); +} + +#presentationMode::before{ + -webkit-mask-image:var(--toolbarButton-presentationMode-icon); + mask-image:var(--toolbarButton-presentationMode-icon); +} + +#editorFreeTextButton::before{ + -webkit-mask-image:var(--toolbarButton-editorFreeText-icon); + mask-image:var(--toolbarButton-editorFreeText-icon); +} + +#editorHighlightButton::before{ + -webkit-mask-image:var(--toolbarButton-editorHighlight-icon); + mask-image:var(--toolbarButton-editorHighlight-icon); +} + +#editorInkButton::before{ + -webkit-mask-image:var(--toolbarButton-editorInk-icon); + mask-image:var(--toolbarButton-editorInk-icon); +} + +#editorStampButton::before{ + -webkit-mask-image:var(--toolbarButton-editorStamp-icon); + mask-image:var(--toolbarButton-editorStamp-icon); +} + +#editorSignatureButton::before{ + -webkit-mask-image:var(--toolbarButton-editorSignature-icon); + mask-image:var(--toolbarButton-editorSignature-icon); +} + +#printButton::before{ + -webkit-mask-image:var(--toolbarButton-print-icon); + mask-image:var(--toolbarButton-print-icon); +} + +#secondaryOpenFile::before{ + -webkit-mask-image:var(--toolbarButton-openFile-icon); + mask-image:var(--toolbarButton-openFile-icon); +} + +#downloadButton::before{ + -webkit-mask-image:var(--toolbarButton-download-icon); + mask-image:var(--toolbarButton-download-icon); +} + +#viewBookmark::before{ + -webkit-mask-image:var(--toolbarButton-bookmark-icon); + mask-image:var(--toolbarButton-bookmark-icon); +} + +#currentOutlineItem::before{ + -webkit-mask-image:var(--toolbarButton-currentOutlineItem-icon); + mask-image:var(--toolbarButton-currentOutlineItem-icon); + transform:scaleX(var(--dir-factor)); +} + +#viewFindButton::before{ + -webkit-mask-image:var(--toolbarButton-search-icon); + mask-image:var(--toolbarButton-search-icon); +} + +.pdfSidebarNotification::after{ + position:absolute; + display:inline-block; + top:2px; + inset-inline-end:2px; + content:""; + background-color:rgb(112 219 85); + height:9px; + width:9px; + border-radius:50%; +} + +.verticalToolbarSeparator{ + display:block; + margin-inline:2px; + width:0; + height:80%; + border-left:1px solid var(--separator-color); + border-right:none; + box-sizing:border-box; +} + +.horizontalToolbarSeparator{ + display:block; + margin:6px 0; + border-top:1px solid var(--doorhanger-separator-color); + border-bottom:none; + height:0; + width:100%; +} + +.toggleButton{ + display:inline; +} + +.toggleButton:has( > input:checked){ + color:var(--toggled-btn-color); + background-color:var(--toggled-btn-bg-color); +} + +.toggleButton:is(:hover,:has( > input:focus-visible)){ + color:var(--toggled-btn-color); + background-color:var(--button-hover-color); +} + +.toggleButton > input{ + position:absolute; + top:50%; + left:50%; + opacity:0; + width:0; + height:0; +} + +.toolbarField{ + padding:4px 7px; + margin:3px 0; + border-radius:2px; + background-color:var(--field-bg-color); + background-clip:padding-box; + border:1px solid var(--field-border-color); + box-shadow:none; + color:var(--field-color); + font-size:12px; + line-height:16px; + outline:none; +} + +.toolbarField:focus{ + border-color:#0a84ff; +} + +#pageNumber{ + -moz-appearance:textfield; + text-align:end; + width:40px; + background-size:0 0; + transition-property:none; +} + +#pageNumber::-webkit-inner-spin-button{ + -webkit-appearance:none; +} + +.loadingInput:has( > .loading:is(#pageNumber))::after{ + display:inline; + visibility:visible; + + transition-property:visibility; + transition-delay:var(--loading-icon-delay); +} + +.loadingInput{ + position:relative; +} + +.loadingInput::after{ + position:absolute; + visibility:hidden; + display:none; + width:var(--icon-size); + height:var(--icon-size); + + content:""; + background-color:var(--toolbar-icon-bg-color); + -webkit-mask-size:cover; + mask-size:cover; + -webkit-mask-image:var(--loading-icon); + mask-image:var(--loading-icon); +} + +.loadingInput.start::after{ + inset-inline-start:4px; +} + +.loadingInput.end::after{ + inset-inline-end:4px; +} + +#thumbnailView, +#outlineView, +#attachmentsView, +#layersView{ + position:absolute; + width:calc(100% - 8px); + inset-block:0; + padding:4px 4px 0; + overflow:auto; + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; +} + +#thumbnailView{ + width:calc(100% - 60px); + padding:10px 30px 0; +} + +#thumbnailView > a:is(:active, :focus){ + outline:0; +} + +.thumbnail{ + --thumbnail-width:0; + --thumbnail-height:0; + + float:var(--inline-start); + width:var(--thumbnail-width); + height:var(--thumbnail-height); + margin:0 10px 5px; + padding:1px; + border:7px solid transparent; + border-radius:2px; +} + +#thumbnailView > a:last-of-type > .thumbnail{ + margin-bottom:10px; +} + +a:focus > .thumbnail, +.thumbnail:hover{ + border-color:var(--thumbnail-hover-color); +} + +.thumbnail.selected{ + border-color:var(--thumbnail-selected-color) !important; +} + +.thumbnailImage{ + width:var(--thumbnail-width); + height:var(--thumbnail-height); + opacity:0.9; +} + +a:focus > .thumbnail > .thumbnailImage, +.thumbnail:hover > .thumbnailImage{ + opacity:0.95; +} + +.thumbnail.selected > .thumbnailImage{ + opacity:1 !important; +} + +.thumbnail:not([data-loaded]) > .thumbnailImage{ + width:calc(var(--thumbnail-width) - 2px); + height:calc(var(--thumbnail-height) - 2px); + border:1px dashed rgb(132 132 132); +} + +.treeWithDeepNesting > .treeItem, +.treeItem > .treeItems{ + margin-inline-start:20px; +} + +.treeItem > a{ + text-decoration:none; + display:inline-block; + min-width:calc(100% - 4px); + height:auto; + margin-bottom:1px; + padding:2px 0 5px; + padding-inline-start:4px; + border-radius:2px; + color:var(--treeitem-color); + font-size:13px; + line-height:15px; + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; + white-space:normal; + cursor:pointer; +} + +#layersView .treeItem > a *{ + cursor:pointer; +} + +#layersView .treeItem > a > label{ + padding-inline-start:4px; +} + +#layersView .treeItem > a > label > input{ + float:var(--inline-start); + margin-top:1px; +} + +.treeItemToggler{ + position:relative; + float:var(--inline-start); + height:0; + width:0; + color:rgb(255 255 255 / 0.5); +} + +.treeItemToggler::before{ + inset-inline-end:4px; + -webkit-mask-image:var(--treeitem-expanded-icon); + mask-image:var(--treeitem-expanded-icon); +} + +.treeItemToggler.treeItemsHidden::before{ + -webkit-mask-image:var(--treeitem-collapsed-icon); + mask-image:var(--treeitem-collapsed-icon); + transform:scaleX(var(--dir-factor)); +} + +.treeItemToggler.treeItemsHidden ~ .treeItems{ + display:none; +} + +.treeItem.selected > a{ + background-color:var(--treeitem-selected-bg-color); + color:var(--treeitem-selected-color); +} + +.treeItemToggler:hover, +.treeItemToggler:hover + a, +.treeItemToggler:hover ~ .treeItems, +.treeItem > a:hover{ + background-color:var(--treeitem-bg-color); + background-clip:padding-box; + border-radius:2px; + color:var(--treeitem-hover-color); +} + +#outlineOptionsContainer{ + display:none; +} + +#sidebarContainer:has(#outlineView:not(.hidden)) #outlineOptionsContainer{ + display:inline flex; +} + +.dialogButton{ + width:auto; + margin:3px 4px 2px !important; + padding:2px 11px; + color:var(--main-color); + background-color:var(--dialog-button-bg-color); + border:var(--dialog-button-border) !important; +} + +dialog{ + margin:auto; + padding:15px; + border-spacing:4px; + color:var(--main-color); + font:message-box; + font-size:12px; + line-height:14px; + background-color:var(--doorhanger-bg-color); + border:1px solid rgb(0 0 0 / 0.5); + border-radius:4px; + box-shadow:0 1px 4px rgb(0 0 0 / 0.3); +} + +dialog::backdrop{ + background-color:rgb(0 0 0 / 0.2); +} + +dialog > .row{ + display:table-row; +} + +dialog > .row > *{ + display:table-cell; +} + +dialog .toolbarField{ + margin:5px 0; +} + +dialog .separator{ + display:block; + margin:4px 0; + height:0; + width:100%; + border-top:1px solid var(--separator-color); + border-bottom:none; +} + +dialog .buttonRow{ + text-align:center; + vertical-align:middle; +} + +dialog :link{ + color:rgb(255 255 255); +} + +#passwordDialog{ + text-align:center; +} + +#passwordDialog .toolbarField{ + width:200px; +} + +#documentPropertiesDialog{ + text-align:left; +} + +#documentPropertiesDialog .row > *{ + min-width:100px; + text-align:start; +} + +#documentPropertiesDialog .row > span{ + width:125px; + word-wrap:break-word; +} + +#documentPropertiesDialog .row > p{ + max-width:225px; + word-wrap:break-word; +} + +#documentPropertiesDialog .buttonRow{ + margin-top:10px; +} + +.grab-to-pan-grab{ + cursor:grab !important; +} + +.grab-to-pan-grab +*:not(input):not(textarea):not(button):not(select):not(:link){ + cursor:inherit !important; +} + +.grab-to-pan-grab:active, +.grab-to-pan-grabbing{ + cursor:grabbing !important; +} + +.grab-to-pan-grabbing{ + position:fixed; + background:rgb(0 0 0 / 0); + display:block; + inset:0; + overflow:hidden; + z-index:50000; +} + +.toolbarButton{ + height:100%; + aspect-ratio:1; + display:flex; + align-items:center; + justify-content:center; + background:none; + border:none; + color:var(--main-color); + outline:none; + border-radius:2px; + box-sizing:border-box; + font:message-box; + flex:none; + position:relative; + padding:0; +} + +.toolbarButton > span{ + display:inline-block; + width:0; + height:0; + overflow:hidden; +} + +.toolbarButton::before{ + opacity:var(--toolbar-icon-opacity); + display:inline-block; + width:var(--icon-size); + height:var(--icon-size); + content:""; + background-color:var(--toolbar-icon-bg-color); + -webkit-mask-size:cover; + mask-size:cover; + -webkit-mask-position:center; + mask-position:center; +} + +.toolbarButton.toggled{ + background-color:var(--toggled-btn-bg-color); + color:var(--toggled-btn-color); +} + +.toolbarButton.toggled::before{ + background-color:var(--toggled-btn-color); +} + +.toolbarButton.toggled:hover{ + outline:var(--toggled-hover-btn-outline) !important; +} + +.toolbarButton.toggled:hover:active{ + background-color:var(--toggled-hover-active-btn-color); +} + +.toolbarButton:is(:hover,:focus-visible){ + background-color:var(--button-hover-color); +} + +.toolbarButton:is(:hover,:focus-visible)::before{ + background-color:var(--toolbar-icon-hover-bg-color); +} + +.toolbarButton:is([disabled="disabled"],[disabled]){ + opacity:0.5; + pointer-events:none; +} + +.toolbarButton.labeled{ + width:100%; + min-height:var(--menuitem-height); + justify-content:flex-start; + gap:8px; + padding-inline-start:12px; + aspect-ratio:unset; + text-align:start; + white-space:normal; + cursor:default; +} + +.toolbarButton.labeled:is(a){ + text-decoration:none; +} + +.toolbarButton.labeled[href="#"]:is(a){ + opacity:0.5; + pointer-events:none; +} + +.toolbarButton.labeled::before{ + opacity:var(--doorhanger-icon-opacity); +} + +.toolbarButton.labeled:is(:hover,:focus-visible){ + color:var(--doorhanger-hover-color); +} + +.toolbarButton.labeled > span{ + display:inline-block; + width:-moz-max-content; + width:max-content; + height:auto; +} + +.toolbarButtonWithContainer{ + height:100%; + aspect-ratio:1; + display:inline-block; + position:relative; + flex:none; +} + +.toolbarButtonWithContainer > .toolbarButton{ + width:100%; + height:100%; +} + +.toolbarButtonWithContainer .menu{ + padding-block:5px; +} + +.toolbarButtonWithContainer .menuContainer{ + width:100%; + height:auto; + max-height:calc( + var(--viewer-container-height) - var(--toolbar-height) - + var(--doorhanger-height) + ); + display:flex; + flex-direction:column; + box-sizing:border-box; + overflow-y:auto; +} + +.toolbarButtonWithContainer .editorParamsToolbar{ + height:auto; + width:220px; + position:absolute; + z-index:30000; + cursor:default; +} + +:is(.toolbarButtonWithContainer .editorParamsToolbar) :is(#editorStampAddImage,#editorSignatureAddSignature)::before{ + -webkit-mask-image:var(--editorParams-stampAddImage-icon); + mask-image:var(--editorParams-stampAddImage-icon); +} + +:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsLabel{ + flex:none; + font:menu; + font-size:13px; + font-style:normal; + font-weight:400; + line-height:150%; + width:-moz-fit-content; + width:fit-content; + inset-inline-start:0; + color:var(--main-color); +} + +:is(.toolbarButtonWithContainer .editorParamsToolbar) button:is(:hover,:focus-visible) .editorParamsLabel{ + color:var(--doorhanger-hover-color); +} + +:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer{ + width:100%; + height:auto; + display:flex; + flex-direction:column; + box-sizing:border-box; + padding-inline:10px; + padding-block:10px; +} + +:is(:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer) > .editorParamsSetter{ + min-height:26px; + display:flex; + align-items:center; + justify-content:space-between; +} + +:is(:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer) .editorParamsColor{ + width:32px; + height:32px; + flex:none; + padding:0; +} + +:is(:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer) .editorParamsSlider{ + background-color:transparent; + width:90px; + flex:0 1 0; + font:message-box; +} + +:is(:is(:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer) .editorParamsSlider)::-moz-range-progress{ + background-color:black; +} + +:is(:is(:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer) .editorParamsSlider)::-webkit-slider-runnable-track,:is(:is(:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer) .editorParamsSlider)::-moz-range-track{ + background-color:black; +} + +:is(:is(:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer) .editorParamsSlider)::-webkit-slider-thumb,:is(:is(:is(.toolbarButtonWithContainer .editorParamsToolbar) .editorParamsToolbarContainer) .editorParamsSlider)::-moz-range-thumb{ + background-color:white; +} + +#secondaryToolbar{ + height:auto; + width:220px; + position:absolute; + z-index:30000; + cursor:default; + min-height:26px; + max-height:calc(var(--viewer-container-height) - 40px); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #secondaryOpenFile::before{ + -webkit-mask-image:var(--toolbarButton-openFile-icon); + mask-image:var(--toolbarButton-openFile-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #secondaryPrint::before{ + -webkit-mask-image:var(--toolbarButton-print-icon); + mask-image:var(--toolbarButton-print-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #secondaryDownload::before{ + -webkit-mask-image:var(--toolbarButton-download-icon); + mask-image:var(--toolbarButton-download-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #presentationMode::before{ + -webkit-mask-image:var(--toolbarButton-presentationMode-icon); + mask-image:var(--toolbarButton-presentationMode-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #viewBookmark::before{ + -webkit-mask-image:var(--toolbarButton-bookmark-icon); + mask-image:var(--toolbarButton-bookmark-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #firstPage::before{ + -webkit-mask-image:var(--secondaryToolbarButton-firstPage-icon); + mask-image:var(--secondaryToolbarButton-firstPage-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #lastPage::before{ + -webkit-mask-image:var(--secondaryToolbarButton-lastPage-icon); + mask-image:var(--secondaryToolbarButton-lastPage-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #pageRotateCcw::before{ + -webkit-mask-image:var(--secondaryToolbarButton-rotateCcw-icon); + mask-image:var(--secondaryToolbarButton-rotateCcw-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #pageRotateCw::before{ + -webkit-mask-image:var(--secondaryToolbarButton-rotateCw-icon); + mask-image:var(--secondaryToolbarButton-rotateCw-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #cursorSelectTool::before{ + -webkit-mask-image:var(--secondaryToolbarButton-selectTool-icon); + mask-image:var(--secondaryToolbarButton-selectTool-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #cursorHandTool::before{ + -webkit-mask-image:var(--secondaryToolbarButton-handTool-icon); + mask-image:var(--secondaryToolbarButton-handTool-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #scrollPage::before{ + -webkit-mask-image:var(--secondaryToolbarButton-scrollPage-icon); + mask-image:var(--secondaryToolbarButton-scrollPage-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #scrollVertical::before{ + -webkit-mask-image:var(--secondaryToolbarButton-scrollVertical-icon); + mask-image:var(--secondaryToolbarButton-scrollVertical-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #scrollHorizontal::before{ + -webkit-mask-image:var(--secondaryToolbarButton-scrollHorizontal-icon); + mask-image:var(--secondaryToolbarButton-scrollHorizontal-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #scrollWrapped::before{ + -webkit-mask-image:var(--secondaryToolbarButton-scrollWrapped-icon); + mask-image:var(--secondaryToolbarButton-scrollWrapped-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #spreadNone::before{ + -webkit-mask-image:var(--secondaryToolbarButton-spreadNone-icon); + mask-image:var(--secondaryToolbarButton-spreadNone-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #spreadOdd::before{ + -webkit-mask-image:var(--secondaryToolbarButton-spreadOdd-icon); + mask-image:var(--secondaryToolbarButton-spreadOdd-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #spreadEven::before{ + -webkit-mask-image:var(--secondaryToolbarButton-spreadEven-icon); + mask-image:var(--secondaryToolbarButton-spreadEven-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #imageAltTextSettings::before{ + -webkit-mask-image:var(--secondaryToolbarButton-imageAltTextSettings-icon); + mask-image:var(--secondaryToolbarButton-imageAltTextSettings-icon); +} + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #documentProperties::before{ + -webkit-mask-image:var(--secondaryToolbarButton-documentProperties-icon); + mask-image:var(--secondaryToolbarButton-documentProperties-icon); +} + +#findbar{ + --input-horizontal-padding:4px; + --findbar-padding:2px; + + width:-moz-max-content; + + width:max-content; + max-width:90vw; + min-height:var(--toolbar-height); + height:auto; + position:absolute; + z-index:30000; + cursor:default; + padding:0; + min-width:300px; + background-color:var(--toolbar-bg-color); + box-sizing:border-box; + flex-wrap:wrap; + justify-content:flex-start; +} + +#findbar > *{ + height:var(--toolbar-height); + padding:var(--findbar-padding); +} + +#findbar #findInputContainer{ + margin-inline-start:2px; +} + +:is(#findbar #findInputContainer) #findPreviousButton::before{ + -webkit-mask-image:var(--findbarButton-previous-icon); + mask-image:var(--findbarButton-previous-icon); +} + +:is(#findbar #findInputContainer) #findNextButton::before{ + -webkit-mask-image:var(--findbarButton-next-icon); + mask-image:var(--findbarButton-next-icon); +} + +:is(#findbar #findInputContainer) #findInput{ + width:200px; + padding:5px var(--input-horizontal-padding); +} + +:is(:is(#findbar #findInputContainer) #findInput)::-moz-placeholder{ + font-style:normal; +} + +:is(:is(#findbar #findInputContainer) #findInput)::placeholder{ + font-style:normal; +} + +.loadingInput:has( > [data-status="pending"]:is(:is(#findbar #findInputContainer) #findInput))::after{ + display:inline; + visibility:visible; + inset-inline-end:calc(var(--input-horizontal-padding) + 1px); +} + +[data-status="notFound"]:is(:is(#findbar #findInputContainer) #findInput){ + background-color:rgb(255 102 102); +} + +#findbar #findbarMessageContainer{ + display:none; + gap:4px; +} + +:is(#findbar #findbarMessageContainer):has( > :is(#findResultsCount,#findMsg):not(:empty)){ + display:inline flex; +} + +:is(#findbar #findbarMessageContainer) #findResultsCount{ + background-color:rgb(217 217 217); + color:rgb(82 82 82); + padding-block:4px; +} + +:is(:is(#findbar #findbarMessageContainer) #findResultsCount):empty{ + display:none; +} + +[data-status="notFound"]:is(:is(#findbar #findbarMessageContainer) #findMsg){ + font-weight:bold; +} + +:is(:is(#findbar #findbarMessageContainer) #findMsg):empty{ + display:none; +} + +#findbar.wrapContainers{ + flex-direction:column; + align-items:flex-start; + height:-moz-max-content; + height:max-content; +} + +#findbar.wrapContainers .toolbarLabel{ + margin:0 4px; +} + +#findbar.wrapContainers #findbarMessageContainer{ + flex-wrap:wrap; + flex-flow:column nowrap; + align-items:flex-start; + height:-moz-max-content; + height:max-content; +} + +:is(#findbar.wrapContainers #findbarMessageContainer) #findResultsCount{ + height:calc(var(--toolbar-height) - 2 * var(--findbar-padding)); +} + +:is(#findbar.wrapContainers #findbarMessageContainer) #findMsg{ + min-height:var(--toolbar-height); +} + +@page{ + margin:0; +} + +#printContainer{ + display:none; +} + +@media print{ + body{ + background:rgb(0 0 0 / 0) none; + } + + body[data-pdfjsprinting] #outerContainer{ + display:none; + } + + body[data-pdfjsprinting] #printContainer{ + display:block; + } + + #printContainer{ + height:100%; + } + #printContainer > .printedPage{ + page-break-after:always; + page-break-inside:avoid; + height:100%; + width:100%; + + display:flex; + flex-direction:column; + justify-content:center; + align-items:center; + } + + #printContainer > .xfaPrintedPage .xfaPage{ + position:absolute; + } + + #printContainer > .xfaPrintedPage{ + page-break-after:always; + page-break-inside:avoid; + width:100%; + height:100%; + position:relative; + } + + #printContainer > .printedPage :is(canvas, img){ + max-width:100%; + max-height:100%; + + direction:ltr; + display:block; + } +} + +.visibleMediumView{ + display:none !important; +} + +.toolbarLabel{ + width:-moz-max-content; + width:max-content; + min-width:16px; + height:100%; + padding-inline:4px; + margin:2px; + border-radius:2px; + color:var(--main-color); + font-size:12px; + line-height:14px; + text-align:left; + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; + cursor:default; + box-sizing:border-box; + + display:inline flex; + flex-direction:column; + align-items:center; + justify-content:center; +} + +.toolbarLabel > label{ + width:100%; +} + +.toolbarHorizontalGroup{ + height:100%; + display:inline flex; + flex-direction:row; + align-items:center; + justify-content:space-between; + gap:1px; + box-sizing:border-box; +} + +.dropdownToolbarButton{ + display:inline flex; + flex-direction:row; + align-items:center; + justify-content:center; + position:relative; + + width:-moz-fit-content; + + width:fit-content; + min-width:140px; + padding:0; + background-color:var(--dropdown-btn-bg-color); + border:var(--dropdown-btn-border); + border-radius:2px; + color:var(--main-color); + font-size:12px; + line-height:14px; + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; + cursor:default; + box-sizing:border-box; + outline:none; +} + +.dropdownToolbarButton:hover{ + background-color:var(--button-hover-color); +} + +.dropdownToolbarButton > select{ + -webkit-appearance:none; + -moz-appearance:none; + appearance:none; + width:inherit; + min-width:inherit; + height:28px; + font:message-box; + font-size:12px; + color:var(--main-color); + margin:0; + padding-block:1px 2px; + padding-inline:6px 38px; + border:none; + outline:none; + background-color:var(--dropdown-btn-bg-color); +} + +:is(.dropdownToolbarButton > select) > option{ + background:var(--doorhanger-bg-color); + color:var(--main-color); +} + +:is(.dropdownToolbarButton > select):is(:hover,:focus-visible){ + background-color:var(--button-hover-color); + color:var(--toggled-btn-color); +} + +.dropdownToolbarButton::after{ + position:absolute; + display:inline; + width:var(--icon-size); + height:var(--icon-size); + + content:""; + background-color:var(--toolbar-icon-bg-color); + -webkit-mask-size:cover; + mask-size:cover; + + inset-inline-end:4px; + pointer-events:none; + -webkit-mask-image:var(--toolbarButton-menuArrow-icon); + mask-image:var(--toolbarButton-menuArrow-icon); +} + +.dropdownToolbarButton:is(:hover,:focus-visible,:active)::after{ + background-color:var(--toolbar-icon-hover-bg-color); +} + +#toolbarContainer{ + --menuitem-height:calc(var(--toolbar-height) - 6px); + + width:100%; + height:var(--toolbar-height); + padding:var(--toolbar-vertical-padding) var(--toolbar-horizontal-padding); + position:relative; + box-sizing:border-box; + font:message-box; + background-color:var(--toolbar-bg-color); + box-shadow:var(--toolbar-box-shadow); + border-bottom:var(--toolbar-border-bottom); +} + +#toolbarContainer #toolbarViewer{ + width:100%; + height:100%; + justify-content:space-between; +} + +:is(#toolbarContainer #toolbarViewer) > *{ + flex:none; +} + +:is(#toolbarContainer #toolbarViewer) input{ + font:message-box; +} + +:is(#toolbarContainer #toolbarViewer) .toolbarButtonSpacer{ + width:30px; + display:block; + height:1px; +} + +:is(#toolbarContainer #toolbarViewer) #toolbarViewerLeft #numPages.toolbarLabel{ + padding-inline-start:3px; + flex:none; +} + +#toolbarContainer #loadingBar{ + --progressBar-percent:0%; + --progressBar-end-offset:0; + + position:absolute; + top:var(--toolbar-height); + inset-inline:0 var(--progressBar-end-offset); + height:4px; + background-color:var(--progressBar-bg-color); + border-bottom:1px solid var(--toolbar-border-color); + transition-property:inset-inline-start; + transition-duration:var(--sidebar-transition-duration); + transition-timing-function:var(--sidebar-transition-timing-function); +} + +:is(#toolbarContainer #loadingBar) .progress{ + position:absolute; + top:0; + inset-inline-start:0; + width:100%; + transform:scaleX(var(--progressBar-percent)); + transform-origin:calc(50% - 50% * var(--dir-factor)) 0; + height:100%; + background-color:var(--progressBar-color); + overflow:hidden; + transition:transform 200ms; +} + +.indeterminate:is(#toolbarContainer #loadingBar) .progress{ + transform:none; + background-color:var(--progressBar-bg-color); + transition:none; +} + +:is(.indeterminate:is(#toolbarContainer #loadingBar) .progress) .glimmer{ + position:absolute; + top:0; + inset-inline-start:0; + height:100%; + width:calc(100% + 150px); + background:repeating-linear-gradient( + 135deg, + var(--progressBar-blend-color) 0, + var(--progressBar-bg-color) 5px, + var(--progressBar-bg-color) 45px, + var(--progressBar-color) 55px, + var(--progressBar-color) 95px, + var(--progressBar-blend-color) 100px + ); + animation:progressIndeterminate 1s linear infinite; +} + +#secondaryToolbar #firstPage::before{ + -webkit-mask-image:var(--secondaryToolbarButton-firstPage-icon); + mask-image:var(--secondaryToolbarButton-firstPage-icon); +} + +#secondaryToolbar #lastPage::before{ + -webkit-mask-image:var(--secondaryToolbarButton-lastPage-icon); + mask-image:var(--secondaryToolbarButton-lastPage-icon); +} + +#secondaryToolbar #pageRotateCcw::before{ + -webkit-mask-image:var(--secondaryToolbarButton-rotateCcw-icon); + mask-image:var(--secondaryToolbarButton-rotateCcw-icon); +} + +#secondaryToolbar #pageRotateCw::before{ + -webkit-mask-image:var(--secondaryToolbarButton-rotateCw-icon); + mask-image:var(--secondaryToolbarButton-rotateCw-icon); +} + +#secondaryToolbar #cursorSelectTool::before{ + -webkit-mask-image:var(--secondaryToolbarButton-selectTool-icon); + mask-image:var(--secondaryToolbarButton-selectTool-icon); +} + +#secondaryToolbar #cursorHandTool::before{ + -webkit-mask-image:var(--secondaryToolbarButton-handTool-icon); + mask-image:var(--secondaryToolbarButton-handTool-icon); +} + +#secondaryToolbar #scrollPage::before{ + -webkit-mask-image:var(--secondaryToolbarButton-scrollPage-icon); + mask-image:var(--secondaryToolbarButton-scrollPage-icon); +} + +#secondaryToolbar #scrollVertical::before{ + -webkit-mask-image:var(--secondaryToolbarButton-scrollVertical-icon); + mask-image:var(--secondaryToolbarButton-scrollVertical-icon); +} + +#secondaryToolbar #scrollHorizontal::before{ + -webkit-mask-image:var(--secondaryToolbarButton-scrollHorizontal-icon); + mask-image:var(--secondaryToolbarButton-scrollHorizontal-icon); +} + +#secondaryToolbar #scrollWrapped::before{ + -webkit-mask-image:var(--secondaryToolbarButton-scrollWrapped-icon); + mask-image:var(--secondaryToolbarButton-scrollWrapped-icon); +} + +#secondaryToolbar #spreadNone::before{ + -webkit-mask-image:var(--secondaryToolbarButton-spreadNone-icon); + mask-image:var(--secondaryToolbarButton-spreadNone-icon); +} + +#secondaryToolbar #spreadOdd::before{ + -webkit-mask-image:var(--secondaryToolbarButton-spreadOdd-icon); + mask-image:var(--secondaryToolbarButton-spreadOdd-icon); +} + +#secondaryToolbar #spreadEven::before{ + -webkit-mask-image:var(--secondaryToolbarButton-spreadEven-icon); + mask-image:var(--secondaryToolbarButton-spreadEven-icon); +} + +#secondaryToolbar #documentProperties::before{ + -webkit-mask-image:var(--secondaryToolbarButton-documentProperties-icon); + mask-image:var(--secondaryToolbarButton-documentProperties-icon); +} + +@media all and (max-width: 840px){ + #sidebarContainer{ + background-color:var(--sidebar-narrow-bg-color); + } + #outerContainer.sidebarOpen #viewerContainer{ + inset-inline-start:0 !important; + } +} + +@media all and (max-width: 750px){ + #outerContainer .hiddenMediumView{ + display:none !important; + } + #outerContainer .visibleMediumView:not(.hidden, [hidden]){ + display:inline-block !important; + } +} + +@media all and (max-width: 690px){ + .hiddenSmallView, + .hiddenSmallView *{ + display:none !important; + } + + #toolbarContainer #toolbarViewer .toolbarButtonSpacer{ + width:0; + } +} + +@media all and (max-width: 560px){ + #scaleSelectContainer{ + display:none; + } +} \ No newline at end of file diff --git a/public/assets/pdfjs/viewer.mjs b/public/assets/pdfjs/viewer.mjs new file mode 100755 index 0000000..03417e6 --- /dev/null +++ b/public/assets/pdfjs/viewer.mjs @@ -0,0 +1,16311 @@ +/** + * @licstart The following is the entire license notice for the + * JavaScript code in this page + * + * Copyright 2024 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @licend The above is the entire license notice for the + * JavaScript code in this page + */ + +/******/ // The require scope +/******/ var __webpack_require__ = {}; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ (() => { + /******/ // define getter functions for harmony exports + /******/ __webpack_require__.d = (exports, definition) => { + /******/ for (var key in definition) { + /******/ if ( + __webpack_require__.o(definition, key) && + !__webpack_require__.o(exports, key) + ) { + /******/ Object.defineProperty(exports, key, { + enumerable: true, + get: definition[key], + }); + /******/ + } + /******/ + } + /******/ + }; + /******/ +})(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ (() => { + /******/ __webpack_require__.o = (obj, prop) => + Object.prototype.hasOwnProperty.call(obj, prop); + /******/ +})(); +/******/ +/************************************************************************/ +var __webpack_exports__ = {}; + +// EXPORTS +__webpack_require__.d(__webpack_exports__, { + PDFViewerApplication: () => /* reexport */ PDFViewerApplication, + PDFViewerApplicationConstants: () => /* binding */ AppConstants, + PDFViewerApplicationOptions: () => /* reexport */ AppOptions, +}); // ./web/ui_utils.js +const pageParams = new URLSearchParams(window.location.search); +const DEFAULT_SCALE_VALUE = "auto"; +const DEFAULT_SCALE = 1.0; +const DEFAULT_SCALE_DELTA = 1.1; +const MIN_SCALE = 0.1; +const MAX_SCALE = 10.0; +const UNKNOWN_SCALE = 0; +const MAX_AUTO_SCALE = 1.25; +const SCROLLBAR_PADDING = 40; +const VERTICAL_PADDING = 5; +const RenderingStates = { + INITIAL: 0, + RUNNING: 1, + PAUSED: 2, + FINISHED: 3, +}; +const PresentationModeState = { + UNKNOWN: 0, + NORMAL: 1, + CHANGING: 2, + FULLSCREEN: 3, +}; +const SidebarView = { + UNKNOWN: -1, + NONE: 0, + THUMBS: 1, + OUTLINE: 2, + ATTACHMENTS: 3, + LAYERS: 4, +}; +const TextLayerMode = { + DISABLE: 0, + ENABLE: 1, + ENABLE_PERMISSIONS: 2, +}; +const ScrollMode = { + UNKNOWN: -1, + VERTICAL: 0, + HORIZONTAL: 1, + WRAPPED: 2, + PAGE: 3, +}; +const SpreadMode = { + UNKNOWN: -1, + NONE: 0, + ODD: 1, + EVEN: 2, +}; +const CursorTool = { + SELECT: 0, + HAND: 1, + ZOOM: 2, +}; +const AutoPrintRegExp = /\bprint\s*\(/; +function scrollIntoView(element, spot, scrollMatches = false) { + let parent = element.offsetParent; + if (!parent) { + console.error("offsetParent is not set -- cannot scroll"); + return; + } + let offsetY = element.offsetTop + element.clientTop; + let offsetX = element.offsetLeft + element.clientLeft; + while ( + (parent.clientHeight === parent.scrollHeight && + parent.clientWidth === parent.scrollWidth) || + (scrollMatches && + (parent.classList.contains("markedContent") || + getComputedStyle(parent).overflow === "hidden")) + ) { + offsetY += parent.offsetTop; + offsetX += parent.offsetLeft; + parent = parent.offsetParent; + if (!parent) { + return; + } + } + if (spot) { + if (spot.top !== undefined) { + offsetY += spot.top; + } + if (spot.left !== undefined) { + offsetX += spot.left; + parent.scrollLeft = offsetX; + } + } + parent.scrollTop = offsetY; +} +function watchScroll(viewAreaElement, callback, abortSignal = undefined) { + const debounceScroll = function (evt) { + if (rAF) { + return; + } + rAF = window.requestAnimationFrame(function viewAreaElementScrolled() { + rAF = null; + const currentX = viewAreaElement.scrollLeft; + const lastX = state.lastX; + if (currentX !== lastX) { + state.right = currentX > lastX; + } + state.lastX = currentX; + const currentY = viewAreaElement.scrollTop; + const lastY = state.lastY; + if (currentY !== lastY) { + state.down = currentY > lastY; + } + state.lastY = currentY; + callback(state); + }); + }; + const state = { + right: true, + down: true, + lastX: viewAreaElement.scrollLeft, + lastY: viewAreaElement.scrollTop, + _eventHandler: debounceScroll, + }; + let rAF = null; + viewAreaElement.addEventListener("scroll", debounceScroll, { + useCapture: true, + signal: abortSignal, + }); + abortSignal?.addEventListener( + "abort", + () => window.cancelAnimationFrame(rAF), + { + once: true, + }, + ); + return state; +} +function parseQueryString(query) { + const params = new Map(); + for (const [key, value] of new URLSearchParams(query)) { + params.set(key.toLowerCase(), value); + } + return params; +} +const InvisibleCharsRegExp = /[\x00-\x1F]/g; +function removeNullCharacters(str, replaceInvisible = false) { + if (!InvisibleCharsRegExp.test(str)) { + return str; + } + if (replaceInvisible) { + return str.replaceAll(InvisibleCharsRegExp, (m) => + m === "\x00" ? "" : " ", + ); + } + return str.replaceAll("\x00", ""); +} +function binarySearchFirstItem(items, condition, start = 0) { + let minIndex = start; + let maxIndex = items.length - 1; + if (maxIndex < 0 || !condition(items[maxIndex])) { + return items.length; + } + if (condition(items[minIndex])) { + return minIndex; + } + while (minIndex < maxIndex) { + const currentIndex = (minIndex + maxIndex) >> 1; + const currentItem = items[currentIndex]; + if (condition(currentItem)) { + maxIndex = currentIndex; + } else { + minIndex = currentIndex + 1; + } + } + return minIndex; +} +function approximateFraction(x) { + if (Math.floor(x) === x) { + return [x, 1]; + } + const xinv = 1 / x; + const limit = 8; + if (xinv > limit) { + return [1, limit]; + } else if (Math.floor(xinv) === xinv) { + return [1, xinv]; + } + const x_ = x > 1 ? xinv : x; + let a = 0, + b = 1, + c = 1, + d = 1; + while (true) { + const p = a + c, + q = b + d; + if (q > limit) { + break; + } + if (x_ <= p / q) { + c = p; + d = q; + } else { + a = p; + b = q; + } + } + let result; + if (x_ - a / b < c / d - x_) { + result = x_ === x ? [a, b] : [b, a]; + } else { + result = x_ === x ? [c, d] : [d, c]; + } + return result; +} +function floorToDivide(x, div) { + return x - (x % div); +} +function getPageSizeInches({ view, userUnit, rotate }) { + const [x1, y1, x2, y2] = view; + const changeOrientation = rotate % 180 !== 0; + const width = ((x2 - x1) / 72) * userUnit; + const height = ((y2 - y1) / 72) * userUnit; + return { + width: changeOrientation ? height : width, + height: changeOrientation ? width : height, + }; +} +function backtrackBeforeAllVisibleElements(index, views, top) { + if (index < 2) { + return index; + } + let elt = views[index].div; + let pageTop = elt.offsetTop + elt.clientTop; + if (pageTop >= top) { + elt = views[index - 1].div; + pageTop = elt.offsetTop + elt.clientTop; + } + for (let i = index - 2; i >= 0; --i) { + elt = views[i].div; + if (elt.offsetTop + elt.clientTop + elt.clientHeight <= pageTop) { + break; + } + index = i; + } + return index; +} +function getVisibleElements({ + scrollEl, + views, + sortByVisibility = false, + horizontal = false, + rtl = false, +}) { + const top = scrollEl.scrollTop, + bottom = top + scrollEl.clientHeight; + const left = scrollEl.scrollLeft, + right = left + scrollEl.clientWidth; + function isElementBottomAfterViewTop(view) { + const element = view.div; + const elementBottom = + element.offsetTop + element.clientTop + element.clientHeight; + return elementBottom > top; + } + function isElementNextAfterViewHorizontally(view) { + const element = view.div; + const elementLeft = element.offsetLeft + element.clientLeft; + const elementRight = elementLeft + element.clientWidth; + return rtl ? elementLeft < right : elementRight > left; + } + const visible = [], + ids = new Set(), + numViews = views.length; + let firstVisibleElementInd = binarySearchFirstItem( + views, + horizontal + ? isElementNextAfterViewHorizontally + : isElementBottomAfterViewTop, + ); + if ( + firstVisibleElementInd > 0 && + firstVisibleElementInd < numViews && + !horizontal + ) { + firstVisibleElementInd = backtrackBeforeAllVisibleElements( + firstVisibleElementInd, + views, + top, + ); + } + let lastEdge = horizontal ? right : -1; + for (let i = firstVisibleElementInd; i < numViews; i++) { + const view = views[i], + element = view.div; + const currentWidth = element.offsetLeft + element.clientLeft; + const currentHeight = element.offsetTop + element.clientTop; + const viewWidth = element.clientWidth, + viewHeight = element.clientHeight; + const viewRight = currentWidth + viewWidth; + const viewBottom = currentHeight + viewHeight; + if (lastEdge === -1) { + if (viewBottom >= bottom) { + lastEdge = viewBottom; + } + } else if ((horizontal ? currentWidth : currentHeight) > lastEdge) { + break; + } + if ( + viewBottom <= top || + currentHeight >= bottom || + viewRight <= left || + currentWidth >= right + ) { + continue; + } + const hiddenHeight = + Math.max(0, top - currentHeight) + Math.max(0, viewBottom - bottom); + const hiddenWidth = + Math.max(0, left - currentWidth) + Math.max(0, viewRight - right); + const fractionHeight = (viewHeight - hiddenHeight) / viewHeight, + fractionWidth = (viewWidth - hiddenWidth) / viewWidth; + const percent = (fractionHeight * fractionWidth * 100) | 0; + visible.push({ + id: view.id, + x: currentWidth, + y: currentHeight, + view, + percent, + widthPercent: (fractionWidth * 100) | 0, + }); + ids.add(view.id); + } + const first = visible[0], + last = visible.at(-1); + if (sortByVisibility) { + visible.sort(function (a, b) { + const pc = a.percent - b.percent; + if (Math.abs(pc) > 0.001) { + return -pc; + } + return a.id - b.id; + }); + } + return { + first, + last, + views: visible, + ids, + }; +} +function normalizeWheelEventDirection(evt) { + let delta = Math.hypot(evt.deltaX, evt.deltaY); + const angle = Math.atan2(evt.deltaY, evt.deltaX); + if (-0.25 * Math.PI < angle && angle < 0.75 * Math.PI) { + delta = -delta; + } + return delta; +} +function normalizeWheelEventDelta(evt) { + const deltaMode = evt.deltaMode; + let delta = normalizeWheelEventDirection(evt); + const MOUSE_PIXELS_PER_LINE = 30; + const MOUSE_LINES_PER_PAGE = 30; + if (deltaMode === WheelEvent.DOM_DELTA_PIXEL) { + delta /= MOUSE_PIXELS_PER_LINE * MOUSE_LINES_PER_PAGE; + } else if (deltaMode === WheelEvent.DOM_DELTA_LINE) { + delta /= MOUSE_LINES_PER_PAGE; + } + return delta; +} +function isValidRotation(angle) { + return Number.isInteger(angle) && angle % 90 === 0; +} +function isValidScrollMode(mode) { + return ( + Number.isInteger(mode) && + Object.values(ScrollMode).includes(mode) && + mode !== ScrollMode.UNKNOWN + ); +} +function isValidSpreadMode(mode) { + return ( + Number.isInteger(mode) && + Object.values(SpreadMode).includes(mode) && + mode !== SpreadMode.UNKNOWN + ); +} +function isPortraitOrientation(size) { + return size.width <= size.height; +} +const animationStarted = new Promise(function (resolve) { + window.requestAnimationFrame(resolve); +}); +const docStyle = document.documentElement.style; +function clamp(v, min, max) { + return Math.min(Math.max(v, min), max); +} +class ProgressBar { + #classList = null; + #disableAutoFetchTimeout = null; + #percent = 0; + #style = null; + #visible = true; + constructor(bar) { + this.#classList = bar.classList; + this.#style = bar.style; + } + get percent() { + return this.#percent; + } + set percent(val) { + this.#percent = clamp(val, 0, 100); + if (isNaN(val)) { + this.#classList.add("indeterminate"); + return; + } + this.#classList.remove("indeterminate"); + this.#style.setProperty("--progressBar-percent", `${this.#percent}%`); + } + setWidth(viewer) { + if (!viewer) { + return; + } + const container = viewer.parentNode; + const scrollbarWidth = container.offsetWidth - viewer.offsetWidth; + if (scrollbarWidth > 0) { + this.#style.setProperty( + "--progressBar-end-offset", + `${scrollbarWidth}px`, + ); + } + } + setDisableAutoFetch(delay = 5000) { + if (this.#percent === 100 || isNaN(this.#percent)) { + return; + } + if (this.#disableAutoFetchTimeout) { + clearTimeout(this.#disableAutoFetchTimeout); + } + this.show(); + this.#disableAutoFetchTimeout = setTimeout(() => { + this.#disableAutoFetchTimeout = null; + this.hide(); + }, delay); + } + hide() { + if (!this.#visible) { + return; + } + this.#visible = false; + this.#classList.add("hidden"); + } + show() { + if (this.#visible) { + return; + } + this.#visible = true; + this.#classList.remove("hidden"); + } +} +function getActiveOrFocusedElement() { + let curRoot = document; + let curActiveOrFocused = + curRoot.activeElement || curRoot.querySelector(":focus"); + while (curActiveOrFocused?.shadowRoot) { + curRoot = curActiveOrFocused.shadowRoot; + curActiveOrFocused = + curRoot.activeElement || curRoot.querySelector(":focus"); + } + return curActiveOrFocused; +} +function apiPageLayoutToViewerModes(layout) { + let scrollMode = ScrollMode.VERTICAL, + spreadMode = SpreadMode.NONE; + switch (layout) { + case "SinglePage": + scrollMode = ScrollMode.PAGE; + break; + case "OneColumn": + break; + case "TwoPageLeft": + scrollMode = ScrollMode.PAGE; + case "TwoColumnLeft": + spreadMode = SpreadMode.ODD; + break; + case "TwoPageRight": + scrollMode = ScrollMode.PAGE; + case "TwoColumnRight": + spreadMode = SpreadMode.EVEN; + break; + } + return { + scrollMode, + spreadMode, + }; +} +function apiPageModeToSidebarView(mode) { + switch (mode) { + case "UseNone": + return SidebarView.NONE; + case "UseThumbs": + return SidebarView.THUMBS; + case "UseOutlines": + return SidebarView.OUTLINE; + case "UseAttachments": + return SidebarView.ATTACHMENTS; + case "UseOC": + return SidebarView.LAYERS; + } + return SidebarView.NONE; +} +function toggleCheckedBtn(button, toggle, view = null) { + button.classList.toggle("toggled", toggle); + button.setAttribute("aria-checked", toggle); + view?.classList.toggle("hidden", !toggle); +} +function toggleExpandedBtn(button, toggle, view = null) { + button.classList.toggle("toggled", toggle); + button.setAttribute("aria-expanded", toggle); + view?.classList.toggle("hidden", !toggle); +} +const calcRound = (function () { + const e = document.createElement("div"); + e.style.width = "round(down, calc(1.6666666666666665 * 792px), 1px)"; + return e.style.width === "calc(1320px)" ? Math.fround : (x) => x; +})(); // ./web/app_options.js + +{ + var compatParams = new Map(); + const userAgent = navigator.userAgent || ""; + const platform = navigator.platform || ""; + const maxTouchPoints = navigator.maxTouchPoints || 1; + const isAndroid = /Android/.test(userAgent); + const isIOS = + /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent) || + (platform === "MacIntel" && maxTouchPoints > 1); + (function () { + if (isIOS || isAndroid) { + compatParams.set("maxCanvasPixels", 5242880); + } + })(); + (function () { + if (isAndroid) { + compatParams.set("useSystemFonts", false); + } + })(); +} +const OptionKind = { + BROWSER: 0x01, + VIEWER: 0x02, + API: 0x04, + WORKER: 0x08, + EVENT_DISPATCH: 0x10, + PREFERENCE: 0x80, +}; +const Type = { + BOOLEAN: 0x01, + NUMBER: 0x02, + OBJECT: 0x04, + STRING: 0x08, + UNDEFINED: 0x10, +}; +const defaultOptions = { + allowedGlobalEvents: { + value: null, + kind: OptionKind.BROWSER, + }, + canvasMaxAreaInBytes: { + value: -1, + kind: OptionKind.BROWSER + OptionKind.API, + }, + isInAutomation: { + value: false, + kind: OptionKind.BROWSER, + }, + localeProperties: { + value: { + lang: pageParams.get("lng") || navigator.language || "en-US", + }, + kind: OptionKind.BROWSER, + }, + nimbusDataStr: { + value: "", + kind: OptionKind.BROWSER, + }, + supportsCaretBrowsingMode: { + value: false, + kind: OptionKind.BROWSER, + }, + supportsDocumentFonts: { + value: true, + kind: OptionKind.BROWSER, + }, + supportsIntegratedFind: { + value: false, + kind: OptionKind.BROWSER, + }, + supportsMouseWheelZoomCtrlKey: { + value: true, + kind: OptionKind.BROWSER, + }, + supportsMouseWheelZoomMetaKey: { + value: true, + kind: OptionKind.BROWSER, + }, + supportsPinchToZoom: { + value: true, + kind: OptionKind.BROWSER, + }, + toolbarDensity: { + value: 0, + kind: OptionKind.BROWSER + OptionKind.EVENT_DISPATCH, + }, + altTextLearnMoreUrl: { + value: "", + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + annotationEditorMode: { + value: 0, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + annotationMode: { + value: 2, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + cursorToolOnLoad: { + value: 0, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + debuggerSrc: { + value: "./debugger.mjs", + kind: OptionKind.VIEWER, + }, + defaultZoomDelay: { + value: 400, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + defaultZoomValue: { + value: "", + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + disableHistory: { + value: false, + kind: OptionKind.VIEWER, + }, + disablePageLabels: { + value: false, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + enableAltText: { + value: false, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + enableAltTextModelDownload: { + value: true, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE + OptionKind.EVENT_DISPATCH, + }, + enableGuessAltText: { + value: true, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE + OptionKind.EVENT_DISPATCH, + }, + enableHighlightFloatingButton: { + value: false, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + enableNewAltTextWhenAddingImage: { + value: true, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + enablePermissions: { + value: false, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + enablePrintAutoRotate: { + value: true, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + enableScripting: { + value: true, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + enableUpdatedAddImage: { + value: false, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + externalLinkRel: { + value: "noopener noreferrer nofollow", + kind: OptionKind.VIEWER, + }, + externalLinkTarget: { + value: 0, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + highlightEditorColors: { + value: "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F", + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + historyUpdateUrl: { + value: false, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + ignoreDestinationZoom: { + value: false, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + imageResourcesPath: { + value: "./images/", + kind: OptionKind.VIEWER, + }, + maxCanvasPixels: { + value: 2 ** 25, + kind: OptionKind.VIEWER, + }, + forcePageColors: { + value: false, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + pageColorsBackground: { + value: "Canvas", + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + pageColorsForeground: { + value: "CanvasText", + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + pdfBugEnabled: { + value: false, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + printResolution: { + value: 150, + kind: OptionKind.VIEWER, + }, + sidebarViewOnLoad: { + value: -1, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + scrollModeOnLoad: { + value: -1, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + spreadModeOnLoad: { + value: -1, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + textLayerMode: { + value: 1, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + viewOnLoad: { + value: 0, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + cMapPacked: { + value: true, + kind: OptionKind.API, + }, + cMapUrl: { + value: "/assets/pdfjs/cmaps/", + kind: OptionKind.API, + }, + disableAutoFetch: { + value: false, + kind: OptionKind.API + OptionKind.PREFERENCE, + }, + disableFontFace: { + value: false, + kind: OptionKind.API + OptionKind.PREFERENCE, + }, + disableRange: { + value: false, + kind: OptionKind.API + OptionKind.PREFERENCE, + }, + disableStream: { + value: false, + kind: OptionKind.API + OptionKind.PREFERENCE, + }, + docBaseUrl: { + value: "", + kind: OptionKind.API, + }, + enableHWA: { + value: true, + kind: OptionKind.API + OptionKind.VIEWER + OptionKind.PREFERENCE, + }, + enableXfa: { + value: true, + kind: OptionKind.API + OptionKind.PREFERENCE, + }, + fontExtraProperties: { + value: false, + kind: OptionKind.API, + }, + isEvalSupported: { + value: true, + kind: OptionKind.API, + }, + isOffscreenCanvasSupported: { + value: true, + kind: OptionKind.API, + }, + maxImageSize: { + value: -1, + kind: OptionKind.API, + }, + pdfBug: { + value: false, + kind: OptionKind.API, + }, + standardFontDataUrl: { + value: "/assets/pdfjs/standard_fonts/", + kind: OptionKind.API, + }, + useSystemFonts: { + value: undefined, + kind: OptionKind.API, + type: Type.BOOLEAN + Type.UNDEFINED, + }, + verbosity: { + value: 1, + kind: OptionKind.API, + }, + workerPort: { + value: null, + kind: OptionKind.WORKER, + }, + workerSrc: { + value: "/assets/pdfjs/pdf.worker.mjs", + kind: OptionKind.WORKER, + }, +}; +{ + defaultOptions.defaultUrl = { + value: "compressed.tracemonkey-pldi-09.pdf", + kind: OptionKind.VIEWER, + }; + defaultOptions.sandboxBundleSrc = { + value: "/assets/pdfjs/pdf.sandbox.mjs", + kind: OptionKind.VIEWER, + }; + defaultOptions.viewerCssTheme = { + value: parseInt(pageParams.get("darkMode")) || 1, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + }; + defaultOptions.enableFakeMLManager = { + value: true, + kind: OptionKind.VIEWER, + }; +} +{ + defaultOptions.disablePreferences = { + value: false, + kind: OptionKind.VIEWER, + }; +} +class AppOptions { + static eventBus; + static #opts = new Map(); + static { + for (const name in defaultOptions) { + this.#opts.set(name, defaultOptions[name].value); + } + for (const [name, value] of compatParams) { + this.#opts.set(name, value); + } + this._hasInvokedSet = false; + this._checkDisablePreferences = () => { + if (this.get("disablePreferences")) { + return true; + } + if (this._hasInvokedSet) { + console.warn( + "The Preferences may override manually set AppOptions; " + + 'please use the "disablePreferences"-option to prevent that.', + ); + } + return false; + }; + } + static get(name) { + return this.#opts.get(name); + } + static getAll(kind = null, defaultOnly = false) { + const options = Object.create(null); + for (const name in defaultOptions) { + const defaultOpt = defaultOptions[name]; + if (kind && !(kind & defaultOpt.kind)) { + continue; + } + options[name] = !defaultOnly ? this.#opts.get(name) : defaultOpt.value; + } + return options; + } + static set(name, value) { + this.setAll({ + [name]: value, + }); + } + static setAll(options, prefs = false) { + this._hasInvokedSet ||= true; + let events; + for (const name in options) { + const defaultOpt = defaultOptions[name], + userOpt = options[name]; + if ( + !defaultOpt || + !( + typeof userOpt === typeof defaultOpt.value || + Type[(typeof userOpt).toUpperCase()] & defaultOpt.type + ) + ) { + continue; + } + const { kind } = defaultOpt; + if ( + prefs && + !(kind & OptionKind.BROWSER || kind & OptionKind.PREFERENCE) + ) { + continue; + } + if (this.eventBus && kind & OptionKind.EVENT_DISPATCH) { + (events ||= new Map()).set(name, userOpt); + } + this.#opts.set(name, userOpt); + } + if (events) { + for (const [name, value] of events) { + this.eventBus.dispatch(name.toLowerCase(), { + source: this, + value, + }); + } + } + } +} // ./web/pdf_link_service.js + +const DEFAULT_LINK_REL = "noopener noreferrer nofollow"; +const LinkTarget = { + NONE: 0, + SELF: 1, + BLANK: 2, + PARENT: 3, + TOP: 4, +}; +class PDFLinkService { + externalLinkEnabled = true; + constructor({ + eventBus, + externalLinkTarget = null, + externalLinkRel = null, + ignoreDestinationZoom = false, + } = {}) { + this.eventBus = eventBus; + this.externalLinkTarget = externalLinkTarget; + this.externalLinkRel = externalLinkRel; + this._ignoreDestinationZoom = ignoreDestinationZoom; + this.baseUrl = null; + this.pdfDocument = null; + this.pdfViewer = null; + this.pdfHistory = null; + } + setDocument(pdfDocument, baseUrl = null) { + this.baseUrl = baseUrl; + this.pdfDocument = pdfDocument; + } + setViewer(pdfViewer) { + this.pdfViewer = pdfViewer; + } + setHistory(pdfHistory) { + this.pdfHistory = pdfHistory; + } + get pagesCount() { + return this.pdfDocument ? this.pdfDocument.numPages : 0; + } + get page() { + return this.pdfDocument ? this.pdfViewer.currentPageNumber : 1; + } + set page(value) { + if (this.pdfDocument) { + this.pdfViewer.currentPageNumber = value; + } + } + get rotation() { + return this.pdfDocument ? this.pdfViewer.pagesRotation : 0; + } + set rotation(value) { + if (this.pdfDocument) { + this.pdfViewer.pagesRotation = value; + } + } + get isInPresentationMode() { + return this.pdfDocument ? this.pdfViewer.isInPresentationMode : false; + } + async goToDestination(dest) { + if (!this.pdfDocument) { + return; + } + let namedDest, explicitDest, pageNumber; + if (typeof dest === "string") { + namedDest = dest; + explicitDest = await this.pdfDocument.getDestination(dest); + } else { + namedDest = null; + explicitDest = await dest; + } + if (!Array.isArray(explicitDest)) { + console.error( + `goToDestination: "${explicitDest}" is not a valid destination array, for dest="${dest}".`, + ); + return; + } + const [destRef] = explicitDest; + if (destRef && typeof destRef === "object") { + pageNumber = this.pdfDocument.cachedPageNumber(destRef); + if (!pageNumber) { + try { + pageNumber = (await this.pdfDocument.getPageIndex(destRef)) + 1; + } catch { + console.error( + `goToDestination: "${destRef}" is not a valid page reference, for dest="${dest}".`, + ); + return; + } + } + } else if (Number.isInteger(destRef)) { + pageNumber = destRef + 1; + } + if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) { + console.error( + `goToDestination: "${pageNumber}" is not a valid page number, for dest="${dest}".`, + ); + return; + } + if (this.pdfHistory) { + this.pdfHistory.pushCurrentPosition(); + this.pdfHistory.push({ + namedDest, + explicitDest, + pageNumber, + }); + } + this.pdfViewer.scrollPageIntoView({ + pageNumber, + destArray: explicitDest, + ignoreDestinationZoom: this._ignoreDestinationZoom, + }); + } + goToPage(val) { + if (!this.pdfDocument) { + return; + } + const pageNumber = + (typeof val === "string" && this.pdfViewer.pageLabelToPageNumber(val)) || + val | 0; + if ( + !( + Number.isInteger(pageNumber) && + pageNumber > 0 && + pageNumber <= this.pagesCount + ) + ) { + console.error(`PDFLinkService.goToPage: "${val}" is not a valid page.`); + return; + } + if (this.pdfHistory) { + this.pdfHistory.pushCurrentPosition(); + this.pdfHistory.pushPage(pageNumber); + } + this.pdfViewer.scrollPageIntoView({ + pageNumber, + }); + } + addLinkAttributes(link, url, newWindow = false) { + if (!url || typeof url !== "string") { + throw new Error('A valid "url" parameter must provided.'); + } + const target = newWindow ? LinkTarget.BLANK : this.externalLinkTarget, + rel = this.externalLinkRel; + if (this.externalLinkEnabled) { + link.href = link.title = url; + } else { + link.href = ""; + link.title = `Disabled: ${url}`; + link.onclick = () => false; + } + let targetStr = ""; + switch (target) { + case LinkTarget.NONE: + break; + case LinkTarget.SELF: + targetStr = "_self"; + break; + case LinkTarget.BLANK: + targetStr = "_blank"; + break; + case LinkTarget.PARENT: + targetStr = "_parent"; + break; + case LinkTarget.TOP: + targetStr = "_top"; + break; + } + link.target = targetStr; + link.rel = typeof rel === "string" ? rel : DEFAULT_LINK_REL; + } + getDestinationHash(dest) { + if (typeof dest === "string") { + if (dest.length > 0) { + return this.getAnchorUrl("#" + escape(dest)); + } + } else if (Array.isArray(dest)) { + const str = JSON.stringify(dest); + if (str.length > 0) { + return this.getAnchorUrl("#" + escape(str)); + } + } + return this.getAnchorUrl(""); + } + getAnchorUrl(anchor) { + return this.baseUrl ? this.baseUrl + anchor : anchor; + } + setHash(hash) { + if (!this.pdfDocument) { + return; + } + let pageNumber, dest; + if (hash.includes("=")) { + const params = parseQueryString(hash); + if (params.has("search")) { + const query = params.get("search").replaceAll('"', ""), + phrase = params.get("phrase") === "true"; + this.eventBus.dispatch("findfromurlhash", { + source: this, + query: phrase ? query : query.match(/\S+/g), + }); + } + if (params.has("page")) { + pageNumber = params.get("page") | 0 || 1; + } + if (params.has("zoom")) { + const zoomArgs = params.get("zoom").split(","); + const zoomArg = zoomArgs[0]; + const zoomArgNumber = parseFloat(zoomArg); + if (!zoomArg.includes("Fit")) { + dest = [ + null, + { + name: "XYZ", + }, + zoomArgs.length > 1 ? zoomArgs[1] | 0 : null, + zoomArgs.length > 2 ? zoomArgs[2] | 0 : null, + zoomArgNumber ? zoomArgNumber / 100 : zoomArg, + ]; + } else if (zoomArg === "Fit" || zoomArg === "FitB") { + dest = [ + null, + { + name: zoomArg, + }, + ]; + } else if ( + zoomArg === "FitH" || + zoomArg === "FitBH" || + zoomArg === "FitV" || + zoomArg === "FitBV" + ) { + dest = [ + null, + { + name: zoomArg, + }, + zoomArgs.length > 1 ? zoomArgs[1] | 0 : null, + ]; + } else if (zoomArg === "FitR") { + if (zoomArgs.length !== 5) { + console.error( + 'PDFLinkService.setHash: Not enough parameters for "FitR".', + ); + } else { + dest = [ + null, + { + name: zoomArg, + }, + zoomArgs[1] | 0, + zoomArgs[2] | 0, + zoomArgs[3] | 0, + zoomArgs[4] | 0, + ]; + } + } else { + console.error( + `PDFLinkService.setHash: "${zoomArg}" is not a valid zoom value.`, + ); + } + } + if (dest) { + this.pdfViewer.scrollPageIntoView({ + pageNumber: pageNumber || this.page, + destArray: dest, + allowNegativeOffset: true, + }); + } else if (pageNumber) { + this.page = pageNumber; + } + if (params.has("pagemode")) { + this.eventBus.dispatch("pagemode", { + source: this, + mode: params.get("pagemode"), + }); + } + if (params.has("nameddest")) { + this.goToDestination(params.get("nameddest")); + } + return; + } + dest = unescape(hash); + try { + dest = JSON.parse(dest); + if (!Array.isArray(dest)) { + dest = dest.toString(); + } + } catch {} + if (typeof dest === "string" || PDFLinkService.#isValidExplicitDest(dest)) { + this.goToDestination(dest); + return; + } + console.error( + `PDFLinkService.setHash: "${unescape(hash)}" is not a valid destination.`, + ); + } + executeNamedAction(action) { + if (!this.pdfDocument) { + return; + } + switch (action) { + case "GoBack": + this.pdfHistory?.back(); + break; + case "GoForward": + this.pdfHistory?.forward(); + break; + case "NextPage": + this.pdfViewer.nextPage(); + break; + case "PrevPage": + this.pdfViewer.previousPage(); + break; + case "LastPage": + this.page = this.pagesCount; + break; + case "FirstPage": + this.page = 1; + break; + default: + break; + } + this.eventBus.dispatch("namedaction", { + source: this, + action, + }); + } + async executeSetOCGState(action) { + if (!this.pdfDocument) { + return; + } + const pdfDocument = this.pdfDocument, + optionalContentConfig = await this.pdfViewer.optionalContentConfigPromise; + if (pdfDocument !== this.pdfDocument) { + return; + } + optionalContentConfig.setOCGState(action); + this.pdfViewer.optionalContentConfigPromise = Promise.resolve( + optionalContentConfig, + ); + } + static #isValidExplicitDest(dest) { + if (!Array.isArray(dest) || dest.length < 2) { + return false; + } + const [page, zoom, ...args] = dest; + if ( + !( + typeof page === "object" && + Number.isInteger(page?.num) && + Number.isInteger(page?.gen) + ) && + !Number.isInteger(page) + ) { + return false; + } + if (!(typeof zoom === "object" && typeof zoom?.name === "string")) { + return false; + } + const argsLen = args.length; + let allowNull = true; + switch (zoom.name) { + case "XYZ": + if (argsLen < 2 || argsLen > 3) { + return false; + } + break; + case "Fit": + case "FitB": + return argsLen === 0; + case "FitH": + case "FitBH": + case "FitV": + case "FitBV": + if (argsLen > 1) { + return false; + } + break; + case "FitR": + if (argsLen !== 4) { + return false; + } + allowNull = false; + break; + default: + return false; + } + for (const arg of args) { + if (!(typeof arg === "number" || (allowNull && arg === null))) { + return false; + } + } + return true; + } +} +class SimpleLinkService extends PDFLinkService { + setDocument(pdfDocument, baseUrl = null) {} +} // ./web/pdfjs.js + +const { + AbortException, + AnnotationEditorLayer, + AnnotationEditorParamsType, + AnnotationEditorType, + AnnotationEditorUIManager, + AnnotationLayer, + AnnotationMode, + build, + ColorPicker, + createValidAbsoluteUrl, + DOMSVGFactory, + DrawLayer, + FeatureTest, + fetchData, + getDocument, + getFilenameFromUrl, + getPdfFilenameFromUrl: pdfjs_getPdfFilenameFromUrl, + getXfaPageViewport, + GlobalWorkerOptions, + ImageKind, + InvalidPDFException, + isDataScheme, + isPdfFile, + MissingPDFException, + noContextMenu, + normalizeUnicode, + OPS, + OutputScale, + PasswordResponses, + PDFDataRangeTransport, + PDFDateString, + PDFWorker, + PermissionFlag, + PixelsPerInch, + RenderingCancelledException, + setLayerDimensions, + shadow, + stopEvent, + TextLayer, + TouchManager, + UnexpectedResponseException, + Util, + VerbosityLevel, + version, + XfaLayer, +} = globalThis.pdfjsLib; // ./web/event_utils.js + +const WaitOnType = { + EVENT: "event", + TIMEOUT: "timeout", +}; +async function waitOnEventOrTimeout({ target, name, delay = 0 }) { + if ( + typeof target !== "object" || + !(name && typeof name === "string") || + !(Number.isInteger(delay) && delay >= 0) + ) { + throw new Error("waitOnEventOrTimeout - invalid parameters."); + } + const { promise, resolve } = Promise.withResolvers(); + const ac = new AbortController(); + function handler(type) { + ac.abort(); + clearTimeout(timeout); + resolve(type); + } + const evtMethod = target instanceof EventBus ? "_on" : "addEventListener"; + target[evtMethod](name, handler.bind(null, WaitOnType.EVENT), { + signal: ac.signal, + }); + const timeout = setTimeout(handler.bind(null, WaitOnType.TIMEOUT), delay); + return promise; +} +class EventBus { + #listeners = Object.create(null); + on(eventName, listener, options = null) { + this._on(eventName, listener, { + external: true, + once: options?.once, + signal: options?.signal, + }); + } + off(eventName, listener, options = null) { + this._off(eventName, listener); + } + dispatch(eventName, data) { + const eventListeners = this.#listeners[eventName]; + if (!eventListeners || eventListeners.length === 0) { + return; + } + let externalListeners; + for (const { listener, external, once } of eventListeners.slice(0)) { + if (once) { + this._off(eventName, listener); + } + if (external) { + (externalListeners ||= []).push(listener); + continue; + } + listener(data); + } + if (externalListeners) { + for (const listener of externalListeners) { + listener(data); + } + externalListeners = null; + } + } + _on(eventName, listener, options = null) { + let rmAbort = null; + if (options?.signal instanceof AbortSignal) { + const { signal } = options; + if (signal.aborted) { + console.error("Cannot use an `aborted` signal."); + return; + } + const onAbort = () => this._off(eventName, listener); + rmAbort = () => signal.removeEventListener("abort", onAbort); + signal.addEventListener("abort", onAbort); + } + const eventListeners = (this.#listeners[eventName] ||= []); + eventListeners.push({ + listener, + external: options?.external === true, + once: options?.once === true, + rmAbort, + }); + } + _off(eventName, listener, options = null) { + const eventListeners = this.#listeners[eventName]; + if (!eventListeners) { + return; + } + for (let i = 0, ii = eventListeners.length; i < ii; i++) { + const evt = eventListeners[i]; + if (evt.listener === listener) { + evt.rmAbort?.(); + eventListeners.splice(i, 1); + return; + } + } + } +} +class FirefoxEventBus extends EventBus { + #externalServices; + #globalEventNames; + #isInAutomation; + constructor(globalEventNames, externalServices, isInAutomation) { + super(); + this.#globalEventNames = globalEventNames; + this.#externalServices = externalServices; + this.#isInAutomation = isInAutomation; + } + dispatch(eventName, data) { + throw new Error("Not implemented: FirefoxEventBus.dispatch"); + } +} // ./web/external_services.js + +class BaseExternalServices { + updateFindControlState(data) {} + updateFindMatchesCount(data) {} + initPassiveLoading() {} + reportTelemetry(data) {} + async createL10n() { + throw new Error("Not implemented: createL10n"); + } + createScripting() { + throw new Error("Not implemented: createScripting"); + } + updateEditorStates(data) { + throw new Error("Not implemented: updateEditorStates"); + } + dispatchGlobalEvent(_event) {} +} // ./web/preferences.js + +class BasePreferences { + #defaults = Object.freeze({ + altTextLearnMoreUrl: "", + annotationEditorMode: 0, + annotationMode: 2, + cursorToolOnLoad: 0, + defaultZoomDelay: 400, + defaultZoomValue: "", + disablePageLabels: false, + enableAltText: false, + enableAltTextModelDownload: true, + enableGuessAltText: true, + enableHighlightFloatingButton: false, + enableNewAltTextWhenAddingImage: true, + enablePermissions: false, + enablePrintAutoRotate: true, + enableScripting: true, + enableUpdatedAddImage: false, + externalLinkTarget: 0, + highlightEditorColors: + "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F", + historyUpdateUrl: false, + ignoreDestinationZoom: false, + forcePageColors: false, + pageColorsBackground: "Canvas", + pageColorsForeground: "CanvasText", + pdfBugEnabled: false, + sidebarViewOnLoad: -1, + scrollModeOnLoad: -1, + spreadModeOnLoad: -1, + textLayerMode: 1, + viewOnLoad: 0, + disableAutoFetch: false, + disableFontFace: false, + disableRange: false, + disableStream: false, + enableHWA: true, + enableXfa: true, + viewerCssTheme: 0, + }); + #initializedPromise = null; + constructor() { + this.#initializedPromise = this._readFromStorage(this.#defaults).then( + ({ browserPrefs, prefs }) => { + if (AppOptions._checkDisablePreferences()) { + return; + } + AppOptions.setAll( + { + ...browserPrefs, + ...prefs, + }, + true, + ); + }, + ); + } + async _writeToStorage(prefObj) { + throw new Error("Not implemented: _writeToStorage"); + } + async _readFromStorage(prefObj) { + throw new Error("Not implemented: _readFromStorage"); + } + async reset() { + await this.#initializedPromise; + AppOptions.setAll(this.#defaults, true); + await this._writeToStorage(this.#defaults); + } + async set(name, value) { + await this.#initializedPromise; + AppOptions.setAll( + { + [name]: value, + }, + true, + ); + await this._writeToStorage(AppOptions.getAll(OptionKind.PREFERENCE)); + } + async get(name) { + await this.#initializedPromise; + return AppOptions.get(name); + } + get initializedPromise() { + return this.#initializedPromise; + } +} // ./node_modules/@fluent/bundle/esm/types.js + +class FluentType { + constructor(value) { + this.value = value; + } + valueOf() { + return this.value; + } +} +class FluentNone extends FluentType { + constructor(value = "???") { + super(value); + } + toString(scope) { + return `{${this.value}}`; + } +} +class FluentNumber extends FluentType { + constructor(value, opts = {}) { + super(value); + this.opts = opts; + } + toString(scope) { + try { + const nf = scope.memoizeIntlObject(Intl.NumberFormat, this.opts); + return nf.format(this.value); + } catch (err) { + scope.reportError(err); + return this.value.toString(10); + } + } +} +class FluentDateTime extends FluentType { + constructor(value, opts = {}) { + super(value); + this.opts = opts; + } + toString(scope) { + try { + const dtf = scope.memoizeIntlObject(Intl.DateTimeFormat, this.opts); + return dtf.format(this.value); + } catch (err) { + scope.reportError(err); + return new Date(this.value).toISOString(); + } + } +} // ./node_modules/@fluent/bundle/esm/resolver.js +const MAX_PLACEABLES = 100; +const FSI = "\u2068"; +const PDI = "\u2069"; +function match(scope, selector, key) { + if (key === selector) { + return true; + } + if ( + key instanceof FluentNumber && + selector instanceof FluentNumber && + key.value === selector.value + ) { + return true; + } + if (selector instanceof FluentNumber && typeof key === "string") { + let category = scope + .memoizeIntlObject(Intl.PluralRules, selector.opts) + .select(selector.value); + if (key === category) { + return true; + } + } + return false; +} +function getDefault(scope, variants, star) { + if (variants[star]) { + return resolvePattern(scope, variants[star].value); + } + scope.reportError(new RangeError("No default")); + return new FluentNone(); +} +function getArguments(scope, args) { + const positional = []; + const named = Object.create(null); + for (const arg of args) { + if (arg.type === "narg") { + named[arg.name] = resolveExpression(scope, arg.value); + } else { + positional.push(resolveExpression(scope, arg)); + } + } + return { + positional, + named, + }; +} +function resolveExpression(scope, expr) { + switch (expr.type) { + case "str": + return expr.value; + case "num": + return new FluentNumber(expr.value, { + minimumFractionDigits: expr.precision, + }); + case "var": + return resolveVariableReference(scope, expr); + case "mesg": + return resolveMessageReference(scope, expr); + case "term": + return resolveTermReference(scope, expr); + case "func": + return resolveFunctionReference(scope, expr); + case "select": + return resolveSelectExpression(scope, expr); + default: + return new FluentNone(); + } +} +function resolveVariableReference(scope, { name }) { + let arg; + if (scope.params) { + if (Object.prototype.hasOwnProperty.call(scope.params, name)) { + arg = scope.params[name]; + } else { + return new FluentNone(`$${name}`); + } + } else if ( + scope.args && + Object.prototype.hasOwnProperty.call(scope.args, name) + ) { + arg = scope.args[name]; + } else { + scope.reportError(new ReferenceError(`Unknown variable: $${name}`)); + return new FluentNone(`$${name}`); + } + if (arg instanceof FluentType) { + return arg; + } + switch (typeof arg) { + case "string": + return arg; + case "number": + return new FluentNumber(arg); + case "object": + if (arg instanceof Date) { + return new FluentDateTime(arg.getTime()); + } + default: + scope.reportError( + new TypeError(`Variable type not supported: $${name}, ${typeof arg}`), + ); + return new FluentNone(`$${name}`); + } +} +function resolveMessageReference(scope, { name, attr }) { + const message = scope.bundle._messages.get(name); + if (!message) { + scope.reportError(new ReferenceError(`Unknown message: ${name}`)); + return new FluentNone(name); + } + if (attr) { + const attribute = message.attributes[attr]; + if (attribute) { + return resolvePattern(scope, attribute); + } + scope.reportError(new ReferenceError(`Unknown attribute: ${attr}`)); + return new FluentNone(`${name}.${attr}`); + } + if (message.value) { + return resolvePattern(scope, message.value); + } + scope.reportError(new ReferenceError(`No value: ${name}`)); + return new FluentNone(name); +} +function resolveTermReference(scope, { name, attr, args }) { + const id = `-${name}`; + const term = scope.bundle._terms.get(id); + if (!term) { + scope.reportError(new ReferenceError(`Unknown term: ${id}`)); + return new FluentNone(id); + } + if (attr) { + const attribute = term.attributes[attr]; + if (attribute) { + scope.params = getArguments(scope, args).named; + const resolved = resolvePattern(scope, attribute); + scope.params = null; + return resolved; + } + scope.reportError(new ReferenceError(`Unknown attribute: ${attr}`)); + return new FluentNone(`${id}.${attr}`); + } + scope.params = getArguments(scope, args).named; + const resolved = resolvePattern(scope, term.value); + scope.params = null; + return resolved; +} +function resolveFunctionReference(scope, { name, args }) { + let func = scope.bundle._functions[name]; + if (!func) { + scope.reportError(new ReferenceError(`Unknown function: ${name}()`)); + return new FluentNone(`${name}()`); + } + if (typeof func !== "function") { + scope.reportError(new TypeError(`Function ${name}() is not callable`)); + return new FluentNone(`${name}()`); + } + try { + let resolved = getArguments(scope, args); + return func(resolved.positional, resolved.named); + } catch (err) { + scope.reportError(err); + return new FluentNone(`${name}()`); + } +} +function resolveSelectExpression(scope, { selector, variants, star }) { + let sel = resolveExpression(scope, selector); + if (sel instanceof FluentNone) { + return getDefault(scope, variants, star); + } + for (const variant of variants) { + const key = resolveExpression(scope, variant.key); + if (match(scope, sel, key)) { + return resolvePattern(scope, variant.value); + } + } + return getDefault(scope, variants, star); +} +function resolveComplexPattern(scope, ptn) { + if (scope.dirty.has(ptn)) { + scope.reportError(new RangeError("Cyclic reference")); + return new FluentNone(); + } + scope.dirty.add(ptn); + const result = []; + const useIsolating = scope.bundle._useIsolating && ptn.length > 1; + for (const elem of ptn) { + if (typeof elem === "string") { + result.push(scope.bundle._transform(elem)); + continue; + } + scope.placeables++; + if (scope.placeables > MAX_PLACEABLES) { + scope.dirty.delete(ptn); + throw new RangeError( + `Too many placeables expanded: ${scope.placeables}, ` + + `max allowed is ${MAX_PLACEABLES}`, + ); + } + if (useIsolating) { + result.push(FSI); + } + result.push(resolveExpression(scope, elem).toString(scope)); + if (useIsolating) { + result.push(PDI); + } + } + scope.dirty.delete(ptn); + return result.join(""); +} +function resolvePattern(scope, value) { + if (typeof value === "string") { + return scope.bundle._transform(value); + } + return resolveComplexPattern(scope, value); +} // ./node_modules/@fluent/bundle/esm/scope.js +class Scope { + constructor(bundle, errors, args) { + this.dirty = new WeakSet(); + this.params = null; + this.placeables = 0; + this.bundle = bundle; + this.errors = errors; + this.args = args; + } + reportError(error) { + if (!this.errors || !(error instanceof Error)) { + throw error; + } + this.errors.push(error); + } + memoizeIntlObject(ctor, opts) { + let cache = this.bundle._intls.get(ctor); + if (!cache) { + cache = {}; + this.bundle._intls.set(ctor, cache); + } + let id = JSON.stringify(opts); + if (!cache[id]) { + cache[id] = new ctor(this.bundle.locales, opts); + } + return cache[id]; + } +} // ./node_modules/@fluent/bundle/esm/builtins.js +function values(opts, allowed) { + const unwrapped = Object.create(null); + for (const [name, opt] of Object.entries(opts)) { + if (allowed.includes(name)) { + unwrapped[name] = opt.valueOf(); + } + } + return unwrapped; +} +const NUMBER_ALLOWED = [ + "unitDisplay", + "currencyDisplay", + "useGrouping", + "minimumIntegerDigits", + "minimumFractionDigits", + "maximumFractionDigits", + "minimumSignificantDigits", + "maximumSignificantDigits", +]; +function NUMBER(args, opts) { + let arg = args[0]; + if (arg instanceof FluentNone) { + return new FluentNone(`NUMBER(${arg.valueOf()})`); + } + if (arg instanceof FluentNumber) { + return new FluentNumber(arg.valueOf(), { + ...arg.opts, + ...values(opts, NUMBER_ALLOWED), + }); + } + if (arg instanceof FluentDateTime) { + return new FluentNumber(arg.valueOf(), { + ...values(opts, NUMBER_ALLOWED), + }); + } + throw new TypeError("Invalid argument to NUMBER"); +} +const DATETIME_ALLOWED = [ + "dateStyle", + "timeStyle", + "fractionalSecondDigits", + "dayPeriod", + "hour12", + "weekday", + "era", + "year", + "month", + "day", + "hour", + "minute", + "second", + "timeZoneName", +]; +function DATETIME(args, opts) { + let arg = args[0]; + if (arg instanceof FluentNone) { + return new FluentNone(`DATETIME(${arg.valueOf()})`); + } + if (arg instanceof FluentDateTime) { + return new FluentDateTime(arg.valueOf(), { + ...arg.opts, + ...values(opts, DATETIME_ALLOWED), + }); + } + if (arg instanceof FluentNumber) { + return new FluentDateTime(arg.valueOf(), { + ...values(opts, DATETIME_ALLOWED), + }); + } + throw new TypeError("Invalid argument to DATETIME"); +} // ./node_modules/@fluent/bundle/esm/memoizer.js +const cache = new Map(); +function getMemoizerForLocale(locales) { + const stringLocale = Array.isArray(locales) ? locales.join(" ") : locales; + let memoizer = cache.get(stringLocale); + if (memoizer === undefined) { + memoizer = new Map(); + cache.set(stringLocale, memoizer); + } + return memoizer; +} // ./node_modules/@fluent/bundle/esm/bundle.js +class FluentBundle { + constructor( + locales, + { functions, useIsolating = true, transform = (v) => v } = {}, + ) { + this._terms = new Map(); + this._messages = new Map(); + this.locales = Array.isArray(locales) ? locales : [locales]; + this._functions = { + NUMBER: NUMBER, + DATETIME: DATETIME, + ...functions, + }; + this._useIsolating = useIsolating; + this._transform = transform; + this._intls = getMemoizerForLocale(locales); + } + hasMessage(id) { + return this._messages.has(id); + } + getMessage(id) { + return this._messages.get(id); + } + addResource(res, { allowOverrides = false } = {}) { + const errors = []; + for (let i = 0; i < res.body.length; i++) { + let entry = res.body[i]; + if (entry.id.startsWith("-")) { + if (allowOverrides === false && this._terms.has(entry.id)) { + errors.push( + new Error(`Attempt to override an existing term: "${entry.id}"`), + ); + continue; + } + this._terms.set(entry.id, entry); + } else { + if (allowOverrides === false && this._messages.has(entry.id)) { + errors.push( + new Error(`Attempt to override an existing message: "${entry.id}"`), + ); + continue; + } + this._messages.set(entry.id, entry); + } + } + return errors; + } + formatPattern(pattern, args = null, errors = null) { + if (typeof pattern === "string") { + return this._transform(pattern); + } + let scope = new Scope(this, errors, args); + try { + let value = resolveComplexPattern(scope, pattern); + return value.toString(scope); + } catch (err) { + if (scope.errors && err instanceof Error) { + scope.errors.push(err); + return new FluentNone().toString(scope); + } + throw err; + } + } +} // ./node_modules/@fluent/bundle/esm/resource.js +const RE_MESSAGE_START = /^(-?[a-zA-Z][\w-]*) *= */gm; +const RE_ATTRIBUTE_START = /\.([a-zA-Z][\w-]*) *= */y; +const RE_VARIANT_START = /\*?\[/y; +const RE_NUMBER_LITERAL = /(-?[0-9]+(?:\.([0-9]+))?)/y; +const RE_IDENTIFIER = /([a-zA-Z][\w-]*)/y; +const RE_REFERENCE = /([$-])?([a-zA-Z][\w-]*)(?:\.([a-zA-Z][\w-]*))?/y; +const RE_FUNCTION_NAME = /^[A-Z][A-Z0-9_-]*$/; +const RE_TEXT_RUN = /([^{}\n\r]+)/y; +const RE_STRING_RUN = /([^\\"\n\r]*)/y; +const RE_STRING_ESCAPE = /\\([\\"])/y; +const RE_UNICODE_ESCAPE = /\\u([a-fA-F0-9]{4})|\\U([a-fA-F0-9]{6})/y; +const RE_LEADING_NEWLINES = /^\n+/; +const RE_TRAILING_SPACES = / +$/; +const RE_BLANK_LINES = / *\r?\n/g; +const RE_INDENT = /( *)$/; +const TOKEN_BRACE_OPEN = /{\s*/y; +const TOKEN_BRACE_CLOSE = /\s*}/y; +const TOKEN_BRACKET_OPEN = /\[\s*/y; +const TOKEN_BRACKET_CLOSE = /\s*] */y; +const TOKEN_PAREN_OPEN = /\s*\(\s*/y; +const TOKEN_ARROW = /\s*->\s*/y; +const TOKEN_COLON = /\s*:\s*/y; +const TOKEN_COMMA = /\s*,?\s*/y; +const TOKEN_BLANK = /\s+/y; +class FluentResource { + constructor(source) { + this.body = []; + RE_MESSAGE_START.lastIndex = 0; + let cursor = 0; + while (true) { + let next = RE_MESSAGE_START.exec(source); + if (next === null) { + break; + } + cursor = RE_MESSAGE_START.lastIndex; + try { + this.body.push(parseMessage(next[1])); + } catch (err) { + if (err instanceof SyntaxError) { + continue; + } + throw err; + } + } + function test(re) { + re.lastIndex = cursor; + return re.test(source); + } + function consumeChar(char, errorClass) { + if (source[cursor] === char) { + cursor++; + return true; + } + if (errorClass) { + throw new errorClass(`Expected ${char}`); + } + return false; + } + function consumeToken(re, errorClass) { + if (test(re)) { + cursor = re.lastIndex; + return true; + } + if (errorClass) { + throw new errorClass(`Expected ${re.toString()}`); + } + return false; + } + function match(re) { + re.lastIndex = cursor; + let result = re.exec(source); + if (result === null) { + throw new SyntaxError(`Expected ${re.toString()}`); + } + cursor = re.lastIndex; + return result; + } + function match1(re) { + return match(re)[1]; + } + function parseMessage(id) { + let value = parsePattern(); + let attributes = parseAttributes(); + if (value === null && Object.keys(attributes).length === 0) { + throw new SyntaxError("Expected message value or attributes"); + } + return { + id, + value, + attributes, + }; + } + function parseAttributes() { + let attrs = Object.create(null); + while (test(RE_ATTRIBUTE_START)) { + let name = match1(RE_ATTRIBUTE_START); + let value = parsePattern(); + if (value === null) { + throw new SyntaxError("Expected attribute value"); + } + attrs[name] = value; + } + return attrs; + } + function parsePattern() { + let first; + if (test(RE_TEXT_RUN)) { + first = match1(RE_TEXT_RUN); + } + if (source[cursor] === "{" || source[cursor] === "}") { + return parsePatternElements(first ? [first] : [], Infinity); + } + let indent = parseIndent(); + if (indent) { + if (first) { + return parsePatternElements([first, indent], indent.length); + } + indent.value = trim(indent.value, RE_LEADING_NEWLINES); + return parsePatternElements([indent], indent.length); + } + if (first) { + return trim(first, RE_TRAILING_SPACES); + } + return null; + } + function parsePatternElements(elements = [], commonIndent) { + while (true) { + if (test(RE_TEXT_RUN)) { + elements.push(match1(RE_TEXT_RUN)); + continue; + } + if (source[cursor] === "{") { + elements.push(parsePlaceable()); + continue; + } + if (source[cursor] === "}") { + throw new SyntaxError("Unbalanced closing brace"); + } + let indent = parseIndent(); + if (indent) { + elements.push(indent); + commonIndent = Math.min(commonIndent, indent.length); + continue; + } + break; + } + let lastIndex = elements.length - 1; + let lastElement = elements[lastIndex]; + if (typeof lastElement === "string") { + elements[lastIndex] = trim(lastElement, RE_TRAILING_SPACES); + } + let baked = []; + for (let element of elements) { + if (element instanceof Indent) { + element = element.value.slice(0, element.value.length - commonIndent); + } + if (element) { + baked.push(element); + } + } + return baked; + } + function parsePlaceable() { + consumeToken(TOKEN_BRACE_OPEN, SyntaxError); + let selector = parseInlineExpression(); + if (consumeToken(TOKEN_BRACE_CLOSE)) { + return selector; + } + if (consumeToken(TOKEN_ARROW)) { + let variants = parseVariants(); + consumeToken(TOKEN_BRACE_CLOSE, SyntaxError); + return { + type: "select", + selector, + ...variants, + }; + } + throw new SyntaxError("Unclosed placeable"); + } + function parseInlineExpression() { + if (source[cursor] === "{") { + return parsePlaceable(); + } + if (test(RE_REFERENCE)) { + let [, sigil, name, attr = null] = match(RE_REFERENCE); + if (sigil === "$") { + return { + type: "var", + name, + }; + } + if (consumeToken(TOKEN_PAREN_OPEN)) { + let args = parseArguments(); + if (sigil === "-") { + return { + type: "term", + name, + attr, + args, + }; + } + if (RE_FUNCTION_NAME.test(name)) { + return { + type: "func", + name, + args, + }; + } + throw new SyntaxError("Function names must be all upper-case"); + } + if (sigil === "-") { + return { + type: "term", + name, + attr, + args: [], + }; + } + return { + type: "mesg", + name, + attr, + }; + } + return parseLiteral(); + } + function parseArguments() { + let args = []; + while (true) { + switch (source[cursor]) { + case ")": + cursor++; + return args; + case undefined: + throw new SyntaxError("Unclosed argument list"); + } + args.push(parseArgument()); + consumeToken(TOKEN_COMMA); + } + } + function parseArgument() { + let expr = parseInlineExpression(); + if (expr.type !== "mesg") { + return expr; + } + if (consumeToken(TOKEN_COLON)) { + return { + type: "narg", + name: expr.name, + value: parseLiteral(), + }; + } + return expr; + } + function parseVariants() { + let variants = []; + let count = 0; + let star; + while (test(RE_VARIANT_START)) { + if (consumeChar("*")) { + star = count; + } + let key = parseVariantKey(); + let value = parsePattern(); + if (value === null) { + throw new SyntaxError("Expected variant value"); + } + variants[count++] = { + key, + value, + }; + } + if (count === 0) { + return null; + } + if (star === undefined) { + throw new SyntaxError("Expected default variant"); + } + return { + variants, + star, + }; + } + function parseVariantKey() { + consumeToken(TOKEN_BRACKET_OPEN, SyntaxError); + let key; + if (test(RE_NUMBER_LITERAL)) { + key = parseNumberLiteral(); + } else { + key = { + type: "str", + value: match1(RE_IDENTIFIER), + }; + } + consumeToken(TOKEN_BRACKET_CLOSE, SyntaxError); + return key; + } + function parseLiteral() { + if (test(RE_NUMBER_LITERAL)) { + return parseNumberLiteral(); + } + if (source[cursor] === '"') { + return parseStringLiteral(); + } + throw new SyntaxError("Invalid expression"); + } + function parseNumberLiteral() { + let [, value, fraction = ""] = match(RE_NUMBER_LITERAL); + let precision = fraction.length; + return { + type: "num", + value: parseFloat(value), + precision, + }; + } + function parseStringLiteral() { + consumeChar('"', SyntaxError); + let value = ""; + while (true) { + value += match1(RE_STRING_RUN); + if (source[cursor] === "\\") { + value += parseEscapeSequence(); + continue; + } + if (consumeChar('"')) { + return { + type: "str", + value, + }; + } + throw new SyntaxError("Unclosed string literal"); + } + } + function parseEscapeSequence() { + if (test(RE_STRING_ESCAPE)) { + return match1(RE_STRING_ESCAPE); + } + if (test(RE_UNICODE_ESCAPE)) { + let [, codepoint4, codepoint6] = match(RE_UNICODE_ESCAPE); + let codepoint = parseInt(codepoint4 || codepoint6, 16); + return codepoint <= 0xd7ff || 0xe000 <= codepoint + ? String.fromCodePoint(codepoint) + : "�"; + } + throw new SyntaxError("Unknown escape sequence"); + } + function parseIndent() { + let start = cursor; + consumeToken(TOKEN_BLANK); + switch (source[cursor]) { + case ".": + case "[": + case "*": + case "}": + case undefined: + return false; + case "{": + return makeIndent(source.slice(start, cursor)); + } + if (source[cursor - 1] === " ") { + return makeIndent(source.slice(start, cursor)); + } + return false; + } + function trim(text, re) { + return text.replace(re, ""); + } + function makeIndent(blank) { + let value = blank.replace(RE_BLANK_LINES, "\n"); + let length = RE_INDENT.exec(blank)[1].length; + return new Indent(value, length); + } + } +} +class Indent { + constructor(value, length) { + this.value = value; + this.length = length; + } +} // ./node_modules/@fluent/bundle/esm/index.js +// ./node_modules/@fluent/dom/esm/overlay.js +const reOverlay = /<|&#?\w+;/; +const TEXT_LEVEL_ELEMENTS = { + "http://www.w3.org/1999/xhtml": [ + "em", + "strong", + "small", + "s", + "cite", + "q", + "dfn", + "abbr", + "data", + "time", + "code", + "var", + "samp", + "kbd", + "sub", + "sup", + "i", + "b", + "u", + "mark", + "bdi", + "bdo", + "span", + "br", + "wbr", + ], +}; +const LOCALIZABLE_ATTRIBUTES = { + "http://www.w3.org/1999/xhtml": { + global: ["title", "aria-label", "aria-valuetext"], + a: ["download"], + area: ["download", "alt"], + input: ["alt", "placeholder"], + menuitem: ["label"], + menu: ["label"], + optgroup: ["label"], + option: ["label"], + track: ["label"], + img: ["alt"], + textarea: ["placeholder"], + th: ["abbr"], + }, + "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul": { + global: [ + "accesskey", + "aria-label", + "aria-valuetext", + "label", + "title", + "tooltiptext", + ], + description: ["value"], + key: ["key", "keycode"], + label: ["value"], + textbox: ["placeholder", "value"], + }, +}; +function translateElement(element, translation) { + const { value } = translation; + if (typeof value === "string") { + if ( + element.localName === "title" && + element.namespaceURI === "http://www.w3.org/1999/xhtml" + ) { + element.textContent = value; + } else if (!reOverlay.test(value)) { + element.textContent = value; + } else { + const templateElement = element.ownerDocument.createElementNS( + "http://www.w3.org/1999/xhtml", + "template", + ); + templateElement.innerHTML = value; + overlayChildNodes(templateElement.content, element); + } + } + overlayAttributes(translation, element); +} +function overlayChildNodes(fromFragment, toElement) { + for (const childNode of fromFragment.childNodes) { + if (childNode.nodeType === childNode.TEXT_NODE) { + continue; + } + if (childNode.hasAttribute("data-l10n-name")) { + const sanitized = getNodeForNamedElement(toElement, childNode); + fromFragment.replaceChild(sanitized, childNode); + continue; + } + if (isElementAllowed(childNode)) { + const sanitized = createSanitizedElement(childNode); + fromFragment.replaceChild(sanitized, childNode); + continue; + } + console.warn( + `An element of forbidden type "${childNode.localName}" was found in ` + + "the translation. Only safe text-level elements and elements with " + + "data-l10n-name are allowed.", + ); + fromFragment.replaceChild( + createTextNodeFromTextContent(childNode), + childNode, + ); + } + toElement.textContent = ""; + toElement.appendChild(fromFragment); +} +function hasAttribute(attributes, name) { + if (!attributes) { + return false; + } + for (let attr of attributes) { + if (attr.name === name) { + return true; + } + } + return false; +} +function overlayAttributes(fromElement, toElement) { + const explicitlyAllowed = toElement.hasAttribute("data-l10n-attrs") + ? toElement + .getAttribute("data-l10n-attrs") + .split(",") + .map((i) => i.trim()) + : null; + for (const attr of Array.from(toElement.attributes)) { + if ( + isAttrNameLocalizable(attr.name, toElement, explicitlyAllowed) && + !hasAttribute(fromElement.attributes, attr.name) + ) { + toElement.removeAttribute(attr.name); + } + } + if (!fromElement.attributes) { + return; + } + for (const attr of Array.from(fromElement.attributes)) { + if ( + isAttrNameLocalizable(attr.name, toElement, explicitlyAllowed) && + toElement.getAttribute(attr.name) !== attr.value + ) { + toElement.setAttribute(attr.name, attr.value); + } + } +} +function getNodeForNamedElement(sourceElement, translatedChild) { + const childName = translatedChild.getAttribute("data-l10n-name"); + const sourceChild = sourceElement.querySelector( + `[data-l10n-name="${childName}"]`, + ); + if (!sourceChild) { + console.warn(`An element named "${childName}" wasn't found in the source.`); + return createTextNodeFromTextContent(translatedChild); + } + if (sourceChild.localName !== translatedChild.localName) { + console.warn( + `An element named "${childName}" was found in the translation ` + + `but its type ${translatedChild.localName} didn't match the ` + + `element found in the source (${sourceChild.localName}).`, + ); + return createTextNodeFromTextContent(translatedChild); + } + sourceElement.removeChild(sourceChild); + const clone = sourceChild.cloneNode(false); + return shallowPopulateUsing(translatedChild, clone); +} +function createSanitizedElement(element) { + const clone = element.ownerDocument.createElement(element.localName); + return shallowPopulateUsing(element, clone); +} +function createTextNodeFromTextContent(element) { + return element.ownerDocument.createTextNode(element.textContent); +} +function isElementAllowed(element) { + const allowed = TEXT_LEVEL_ELEMENTS[element.namespaceURI]; + return allowed && allowed.includes(element.localName); +} +function isAttrNameLocalizable(name, element, explicitlyAllowed = null) { + if (explicitlyAllowed && explicitlyAllowed.includes(name)) { + return true; + } + const allowed = LOCALIZABLE_ATTRIBUTES[element.namespaceURI]; + if (!allowed) { + return false; + } + const attrName = name.toLowerCase(); + const elemName = element.localName; + if (allowed.global.includes(attrName)) { + return true; + } + if (!allowed[elemName]) { + return false; + } + if (allowed[elemName].includes(attrName)) { + return true; + } + if ( + element.namespaceURI === "http://www.w3.org/1999/xhtml" && + elemName === "input" && + attrName === "value" + ) { + const type = element.type.toLowerCase(); + if (type === "submit" || type === "button" || type === "reset") { + return true; + } + } + return false; +} +function shallowPopulateUsing(fromElement, toElement) { + toElement.textContent = fromElement.textContent; + overlayAttributes(fromElement, toElement); + return toElement; +} // ./node_modules/cached-iterable/src/cached_iterable.mjs +class CachedIterable extends Array { + static from(iterable) { + if (iterable instanceof this) { + return iterable; + } + return new this(iterable); + } +} // ./node_modules/cached-iterable/src/cached_sync_iterable.mjs +class CachedSyncIterable extends CachedIterable { + constructor(iterable) { + super(); + if (Symbol.iterator in Object(iterable)) { + this.iterator = iterable[Symbol.iterator](); + } else { + throw new TypeError("Argument must implement the iteration protocol."); + } + } + [Symbol.iterator]() { + const cached = this; + let cur = 0; + return { + next() { + if (cached.length <= cur) { + cached.push(cached.iterator.next()); + } + return cached[cur++]; + }, + }; + } + touchNext(count = 1) { + let idx = 0; + while (idx++ < count) { + const last = this[this.length - 1]; + if (last && last.done) { + break; + } + this.push(this.iterator.next()); + } + return this[this.length - 1]; + } +} // ./node_modules/cached-iterable/src/cached_async_iterable.mjs +class CachedAsyncIterable extends CachedIterable { + constructor(iterable) { + super(); + if (Symbol.asyncIterator in Object(iterable)) { + this.iterator = iterable[Symbol.asyncIterator](); + } else if (Symbol.iterator in Object(iterable)) { + this.iterator = iterable[Symbol.iterator](); + } else { + throw new TypeError("Argument must implement the iteration protocol."); + } + } + [Symbol.asyncIterator]() { + const cached = this; + let cur = 0; + return { + async next() { + if (cached.length <= cur) { + cached.push(cached.iterator.next()); + } + return cached[cur++]; + }, + }; + } + async touchNext(count = 1) { + let idx = 0; + while (idx++ < count) { + const last = this[this.length - 1]; + if (last && (await last).done) { + break; + } + this.push(this.iterator.next()); + } + return this[this.length - 1]; + } +} // ./node_modules/cached-iterable/src/index.mjs +// ./node_modules/@fluent/dom/esm/localization.js +class Localization { + constructor(resourceIds = [], generateBundles) { + this.resourceIds = resourceIds; + this.generateBundles = generateBundles; + this.onChange(true); + } + addResourceIds(resourceIds, eager = false) { + this.resourceIds.push(...resourceIds); + this.onChange(eager); + return this.resourceIds.length; + } + removeResourceIds(resourceIds) { + this.resourceIds = this.resourceIds.filter((r) => !resourceIds.includes(r)); + this.onChange(); + return this.resourceIds.length; + } + async formatWithFallback(keys, method) { + const translations = []; + let hasAtLeastOneBundle = false; + for await (const bundle of this.bundles) { + hasAtLeastOneBundle = true; + const missingIds = keysFromBundle(method, bundle, keys, translations); + if (missingIds.size === 0) { + break; + } + if (typeof console !== "undefined") { + const locale = bundle.locales[0]; + const ids = Array.from(missingIds).join(", "); + console.warn(`[fluent] Missing translations in ${locale}: ${ids}`); + } + } + if (!hasAtLeastOneBundle && typeof console !== "undefined") { + console.warn(`[fluent] Request for keys failed because no resource bundles got generated. + keys: ${JSON.stringify(keys)}. + resourceIds: ${JSON.stringify(this.resourceIds)}.`); + } + return translations; + } + formatMessages(keys) { + return this.formatWithFallback(keys, messageFromBundle); + } + formatValues(keys) { + return this.formatWithFallback(keys, valueFromBundle); + } + async formatValue(id, args) { + const [val] = await this.formatValues([ + { + id, + args, + }, + ]); + return val; + } + handleEvent() { + this.onChange(); + } + onChange(eager = false) { + this.bundles = CachedAsyncIterable.from( + this.generateBundles(this.resourceIds), + ); + if (eager) { + this.bundles.touchNext(2); + } + } +} +function valueFromBundle(bundle, errors, message, args) { + if (message.value) { + return bundle.formatPattern(message.value, args, errors); + } + return null; +} +function messageFromBundle(bundle, errors, message, args) { + const formatted = { + value: null, + attributes: null, + }; + if (message.value) { + formatted.value = bundle.formatPattern(message.value, args, errors); + } + let attrNames = Object.keys(message.attributes); + if (attrNames.length > 0) { + formatted.attributes = new Array(attrNames.length); + for (let [i, name] of attrNames.entries()) { + let value = bundle.formatPattern(message.attributes[name], args, errors); + formatted.attributes[i] = { + name, + value, + }; + } + } + return formatted; +} +function keysFromBundle(method, bundle, keys, translations) { + const messageErrors = []; + const missingIds = new Set(); + keys.forEach(({ id, args }, i) => { + if (translations[i] !== undefined) { + return; + } + let message = bundle.getMessage(id); + if (message) { + messageErrors.length = 0; + translations[i] = method(bundle, messageErrors, message, args); + if (messageErrors.length > 0 && typeof console !== "undefined") { + const locale = bundle.locales[0]; + const errors = messageErrors.join(", "); + console.warn( + `[fluent][resolver] errors in ${locale}/${id}: ${errors}.`, + ); + } + } else { + missingIds.add(id); + } + }); + return missingIds; +} // ./node_modules/@fluent/dom/esm/dom_localization.js +const L10NID_ATTR_NAME = "data-l10n-id"; +const L10NARGS_ATTR_NAME = "data-l10n-args"; +const L10N_ELEMENT_QUERY = `[${L10NID_ATTR_NAME}]`; +class DOMLocalization extends Localization { + constructor(resourceIds, generateBundles) { + super(resourceIds, generateBundles); + this.roots = new Set(); + this.pendingrAF = null; + this.pendingElements = new Set(); + this.windowElement = null; + this.mutationObserver = null; + this.observerConfig = { + attributes: true, + characterData: false, + childList: true, + subtree: true, + attributeFilter: [L10NID_ATTR_NAME, L10NARGS_ATTR_NAME], + }; + } + onChange(eager = false) { + super.onChange(eager); + if (this.roots) { + this.translateRoots(); + } + } + setAttributes(element, id, args) { + element.setAttribute(L10NID_ATTR_NAME, id); + if (args) { + element.setAttribute(L10NARGS_ATTR_NAME, JSON.stringify(args)); + } else { + element.removeAttribute(L10NARGS_ATTR_NAME); + } + return element; + } + getAttributes(element) { + return { + id: element.getAttribute(L10NID_ATTR_NAME), + args: JSON.parse(element.getAttribute(L10NARGS_ATTR_NAME) || null), + }; + } + connectRoot(newRoot) { + for (const root of this.roots) { + if ( + root === newRoot || + root.contains(newRoot) || + newRoot.contains(root) + ) { + throw new Error("Cannot add a root that overlaps with existing root."); + } + } + if (this.windowElement) { + if (this.windowElement !== newRoot.ownerDocument.defaultView) { + throw new Error(`Cannot connect a root: + DOMLocalization already has a root from a different window.`); + } + } else { + this.windowElement = newRoot.ownerDocument.defaultView; + this.mutationObserver = new this.windowElement.MutationObserver( + (mutations) => this.translateMutations(mutations), + ); + } + this.roots.add(newRoot); + this.mutationObserver.observe(newRoot, this.observerConfig); + } + disconnectRoot(root) { + this.roots.delete(root); + this.pauseObserving(); + if (this.roots.size === 0) { + this.mutationObserver = null; + if (this.windowElement && this.pendingrAF) { + this.windowElement.cancelAnimationFrame(this.pendingrAF); + } + this.windowElement = null; + this.pendingrAF = null; + this.pendingElements.clear(); + return true; + } + this.resumeObserving(); + return false; + } + translateRoots() { + const roots = Array.from(this.roots); + return Promise.all(roots.map((root) => this.translateFragment(root))); + } + pauseObserving() { + if (!this.mutationObserver) { + return; + } + this.translateMutations(this.mutationObserver.takeRecords()); + this.mutationObserver.disconnect(); + } + resumeObserving() { + if (!this.mutationObserver) { + return; + } + for (const root of this.roots) { + this.mutationObserver.observe(root, this.observerConfig); + } + } + translateMutations(mutations) { + for (const mutation of mutations) { + switch (mutation.type) { + case "attributes": + if (mutation.target.hasAttribute("data-l10n-id")) { + this.pendingElements.add(mutation.target); + } + break; + case "childList": + for (const addedNode of mutation.addedNodes) { + if (addedNode.nodeType === addedNode.ELEMENT_NODE) { + if (addedNode.childElementCount) { + for (const element of this.getTranslatables(addedNode)) { + this.pendingElements.add(element); + } + } else if (addedNode.hasAttribute(L10NID_ATTR_NAME)) { + this.pendingElements.add(addedNode); + } + } + } + break; + } + } + if (this.pendingElements.size > 0) { + if (this.pendingrAF === null) { + this.pendingrAF = this.windowElement.requestAnimationFrame(() => { + this.translateElements(Array.from(this.pendingElements)); + this.pendingElements.clear(); + this.pendingrAF = null; + }); + } + } + } + translateFragment(frag) { + return this.translateElements(this.getTranslatables(frag)); + } + async translateElements(elements) { + if (!elements.length) { + return undefined; + } + const keys = elements.map(this.getKeysForElement); + const translations = await this.formatMessages(keys); + return this.applyTranslations(elements, translations); + } + applyTranslations(elements, translations) { + this.pauseObserving(); + for (let i = 0; i < elements.length; i++) { + if (translations[i] !== undefined) { + translateElement(elements[i], translations[i]); + } + } + this.resumeObserving(); + } + getTranslatables(element) { + const nodes = Array.from(element.querySelectorAll(L10N_ELEMENT_QUERY)); + if ( + typeof element.hasAttribute === "function" && + element.hasAttribute(L10NID_ATTR_NAME) + ) { + nodes.push(element); + } + return nodes; + } + getKeysForElement(element) { + return { + id: element.getAttribute(L10NID_ATTR_NAME), + args: JSON.parse(element.getAttribute(L10NARGS_ATTR_NAME) || null), + }; + } +} // ./node_modules/@fluent/dom/esm/index.js +// ./web/l10n.js +class L10n { + #dir; + #elements; + #lang; + #l10n; + constructor({ lang, isRTL }, l10n = null) { + this.#lang = L10n.#fixupLangCode(lang); + this.#l10n = l10n; + this.#dir = isRTL ?? L10n.#isRTL(this.#lang) ? "rtl" : "ltr"; + } + _setL10n(l10n) { + this.#l10n = l10n; + } + getLanguage() { + return this.#lang; + } + getDirection() { + return this.#dir; + } + async get(ids, args = null, fallback) { + if (Array.isArray(ids)) { + ids = ids.map((id) => ({ + id, + })); + const messages = await this.#l10n.formatMessages(ids); + return messages.map((message) => message.value); + } + const messages = await this.#l10n.formatMessages([ + { + id: ids, + args, + }, + ]); + return messages[0]?.value || fallback; + } + async translate(element) { + (this.#elements ||= new Set()).add(element); + try { + this.#l10n.connectRoot(element); + await this.#l10n.translateRoots(); + } catch {} + } + async translateOnce(element) { + try { + await this.#l10n.translateElements([element]); + } catch (ex) { + console.error("translateOnce:", ex); + } + } + async destroy() { + if (this.#elements) { + for (const element of this.#elements) { + this.#l10n.disconnectRoot(element); + } + this.#elements.clear(); + this.#elements = null; + } + this.#l10n.pauseObserving(); + } + pause() { + this.#l10n.pauseObserving(); + } + resume() { + this.#l10n.resumeObserving(); + } + static #fixupLangCode(langCode) { + langCode = langCode?.toLowerCase() || "en-us"; + const PARTIAL_LANG_CODES = { + en: "en-us", + es: "es-es", + fy: "fy-nl", + ga: "ga-ie", + gu: "gu-in", + hi: "hi-in", + hy: "hy-am", + nb: "nb-no", + ne: "ne-np", + nn: "nn-no", + pa: "pa-in", + pt: "pt-pt", + sv: "sv-se", + zh: "zh-cn", + }; + return PARTIAL_LANG_CODES[langCode] || langCode; + } + static #isRTL(lang) { + const shortCode = lang.split("-", 1)[0]; + return ["ar", "he", "fa", "ps", "ur"].includes(shortCode); + } +} +const GenericL10n = null; // ./web/genericl10n.js + +function createBundle(lang, text) { + const resource = new FluentResource(text); + const bundle = new FluentBundle(lang); + const errors = bundle.addResource(resource); + if (errors.length) { + console.error("L10n errors", errors); + } + return bundle; +} +class genericl10n_GenericL10n extends L10n { + constructor(lang) { + super({ + lang, + }); + const generateBundles = !lang + ? genericl10n_GenericL10n.#generateBundlesFallback.bind( + genericl10n_GenericL10n, + this.getLanguage(), + ) + : genericl10n_GenericL10n.#generateBundles.bind( + genericl10n_GenericL10n, + "en-us", + this.getLanguage(), + ); + this._setL10n(new DOMLocalization([], generateBundles)); + } + static async *#generateBundles(defaultLang, baseLang) { + const { baseURL, paths } = await this.#getPaths(); + const langs = [baseLang]; + if (defaultLang !== baseLang) { + const shortLang = baseLang.split("-", 1)[0]; + if (shortLang !== baseLang) { + langs.push(shortLang); + } + langs.push(defaultLang); + } + for (const lang of langs) { + const bundle = await this.#createBundle(lang, baseURL, paths); + if (bundle) { + yield bundle; + } else if (lang === "en-us") { + yield this.#createBundleFallback(lang); + } + } + } + static async #createBundle(lang, baseURL, paths) { + const path = paths[lang]; + if (!path) { + return null; + } + const url = new URL(path, baseURL); + const text = await fetchData(url, "text"); + return createBundle(lang, text); + } + static async #getPaths() { + try { + const { href } = document.querySelector(`link[type="application/l10n"]`); + const paths = await fetchData(href, "json"); + return { + baseURL: href.replace(/[^/]*$/, "") || "./", + paths, + }; + } catch {} + return { + baseURL: "./", + paths: Object.create(null), + }; + } + static async *#generateBundlesFallback(lang) { + yield this.#createBundleFallback(lang); + } + static async #createBundleFallback(lang) { + const text = + 'pdfjs-previous-button =\n .title = Previous Page\npdfjs-previous-button-label = Previous\npdfjs-next-button =\n .title = Next Page\npdfjs-next-button-label = Next\npdfjs-page-input =\n .title = Page\npdfjs-of-pages = of { $pagesCount }\npdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount })\npdfjs-zoom-out-button =\n .title = Zoom Out\npdfjs-zoom-out-button-label = Zoom Out\npdfjs-zoom-in-button =\n .title = Zoom In\npdfjs-zoom-in-button-label = Zoom In\npdfjs-zoom-select =\n .title = Zoom\npdfjs-presentation-mode-button =\n .title = Switch to Presentation Mode\npdfjs-presentation-mode-button-label = Presentation Mode\npdfjs-open-file-button =\n .title = Open File\npdfjs-open-file-button-label = Open\npdfjs-print-button =\n .title = Print\npdfjs-print-button-label = Print\npdfjs-save-button =\n .title = Save\npdfjs-save-button-label = Save\npdfjs-download-button =\n .title = Download\npdfjs-download-button-label = Download\npdfjs-bookmark-button =\n .title = Current Page (View URL from Current Page)\npdfjs-bookmark-button-label = Current Page\npdfjs-tools-button =\n .title = Tools\npdfjs-tools-button-label = Tools\npdfjs-first-page-button =\n .title = Go to First Page\npdfjs-first-page-button-label = Go to First Page\npdfjs-last-page-button =\n .title = Go to Last Page\npdfjs-last-page-button-label = Go to Last Page\npdfjs-page-rotate-cw-button =\n .title = Rotate Clockwise\npdfjs-page-rotate-cw-button-label = Rotate Clockwise\npdfjs-page-rotate-ccw-button =\n .title = Rotate Counterclockwise\npdfjs-page-rotate-ccw-button-label = Rotate Counterclockwise\npdfjs-cursor-text-select-tool-button =\n .title = Enable Text Selection Tool\npdfjs-cursor-text-select-tool-button-label = Text Selection Tool\npdfjs-cursor-hand-tool-button =\n .title = Enable Hand Tool\npdfjs-cursor-hand-tool-button-label = Hand Tool\npdfjs-scroll-page-button =\n .title = Use Page Scrolling\npdfjs-scroll-page-button-label = Page Scrolling\npdfjs-scroll-vertical-button =\n .title = Use Vertical Scrolling\npdfjs-scroll-vertical-button-label = Vertical Scrolling\npdfjs-scroll-horizontal-button =\n .title = Use Horizontal Scrolling\npdfjs-scroll-horizontal-button-label = Horizontal Scrolling\npdfjs-scroll-wrapped-button =\n .title = Use Wrapped Scrolling\npdfjs-scroll-wrapped-button-label = Wrapped Scrolling\npdfjs-spread-none-button =\n .title = Do not join page spreads\npdfjs-spread-none-button-label = No Spreads\npdfjs-spread-odd-button =\n .title = Join page spreads starting with odd-numbered pages\npdfjs-spread-odd-button-label = Odd Spreads\npdfjs-spread-even-button =\n .title = Join page spreads starting with even-numbered pages\npdfjs-spread-even-button-label = Even Spreads\npdfjs-document-properties-button =\n .title = Document Properties\u2026\npdfjs-document-properties-button-label = Document Properties\u2026\npdfjs-document-properties-file-name = File name:\npdfjs-document-properties-file-size = File size:\npdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)\npdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)\npdfjs-document-properties-title = Title:\npdfjs-document-properties-author = Author:\npdfjs-document-properties-subject = Subject:\npdfjs-document-properties-keywords = Keywords:\npdfjs-document-properties-creation-date = Creation Date:\npdfjs-document-properties-modification-date = Modification Date:\npdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }\npdfjs-document-properties-creator = Creator:\npdfjs-document-properties-producer = PDF Producer:\npdfjs-document-properties-version = PDF Version:\npdfjs-document-properties-page-count = Page Count:\npdfjs-document-properties-page-size = Page Size:\npdfjs-document-properties-page-size-unit-inches = in\npdfjs-document-properties-page-size-unit-millimeters = mm\npdfjs-document-properties-page-size-orientation-portrait = portrait\npdfjs-document-properties-page-size-orientation-landscape = landscape\npdfjs-document-properties-page-size-name-a-three = A3\npdfjs-document-properties-page-size-name-a-four = A4\npdfjs-document-properties-page-size-name-letter = Letter\npdfjs-document-properties-page-size-name-legal = Legal\npdfjs-document-properties-page-size-dimension-string = { $width } \xD7 { $height } { $unit } ({ $orientation })\npdfjs-document-properties-page-size-dimension-name-string = { $width } \xD7 { $height } { $unit } ({ $name }, { $orientation })\npdfjs-document-properties-linearized = Fast Web View:\npdfjs-document-properties-linearized-yes = Yes\npdfjs-document-properties-linearized-no = No\npdfjs-document-properties-close-button = Close\npdfjs-print-progress-message = Preparing document for printing\u2026\npdfjs-print-progress-percent = { $progress }%\npdfjs-print-progress-close-button = Cancel\npdfjs-printing-not-supported = Warning: Printing is not fully supported by this browser.\npdfjs-printing-not-ready = Warning: The PDF is not fully loaded for printing.\npdfjs-toggle-sidebar-button =\n .title = Toggle Sidebar\npdfjs-toggle-sidebar-notification-button =\n .title = Toggle Sidebar (document contains outline/attachments/layers)\npdfjs-toggle-sidebar-button-label = Toggle Sidebar\npdfjs-document-outline-button =\n .title = Show Document Outline (double-click to expand/collapse all items)\npdfjs-document-outline-button-label = Document Outline\npdfjs-attachments-button =\n .title = Show Attachments\npdfjs-attachments-button-label = Attachments\npdfjs-layers-button =\n .title = Show Layers (double-click to reset all layers to the default state)\npdfjs-layers-button-label = Layers\npdfjs-thumbs-button =\n .title = Show Thumbnails\npdfjs-thumbs-button-label = Thumbnails\npdfjs-current-outline-item-button =\n .title = Find Current Outline Item\npdfjs-current-outline-item-button-label = Current Outline Item\npdfjs-findbar-button =\n .title = Find in Document\npdfjs-findbar-button-label = Find\npdfjs-additional-layers = Additional Layers\npdfjs-thumb-page-title =\n .title = Page { $page }\npdfjs-thumb-page-canvas =\n .aria-label = Thumbnail of Page { $page }\npdfjs-find-input =\n .title = Find\n .placeholder = Find in document\u2026\npdfjs-find-previous-button =\n .title = Find the previous occurrence of the phrase\npdfjs-find-previous-button-label = Previous\npdfjs-find-next-button =\n .title = Find the next occurrence of the phrase\npdfjs-find-next-button-label = Next\npdfjs-find-highlight-checkbox = Highlight All\npdfjs-find-match-case-checkbox-label = Match Case\npdfjs-find-match-diacritics-checkbox-label = Match Diacritics\npdfjs-find-entire-word-checkbox-label = Whole Words\npdfjs-find-reached-top = Reached top of document, continued from bottom\npdfjs-find-reached-bottom = Reached end of document, continued from top\npdfjs-find-match-count =\n { $total ->\n [one] { $current } of { $total } match\n *[other] { $current } of { $total } matches\n }\npdfjs-find-match-count-limit =\n { $limit ->\n [one] More than { $limit } match\n *[other] More than { $limit } matches\n }\npdfjs-find-not-found = Phrase not found\npdfjs-page-scale-width = Page Width\npdfjs-page-scale-fit = Page Fit\npdfjs-page-scale-auto = Automatic Zoom\npdfjs-page-scale-actual = Actual Size\npdfjs-page-scale-percent = { $scale }%\npdfjs-page-landmark =\n .aria-label = Page { $page }\npdfjs-loading-error = An error occurred while loading the PDF.\npdfjs-invalid-file-error = Invalid or corrupted PDF file.\npdfjs-missing-file-error = Missing PDF file.\npdfjs-unexpected-response-error = Unexpected server response.\npdfjs-rendering-error = An error occurred while rendering the page.\npdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }\npdfjs-text-annotation-type =\n .alt = [{ $type } Annotation]\npdfjs-password-label = Enter the password to open this PDF file.\npdfjs-password-invalid = Invalid password. Please try again.\npdfjs-password-ok-button = OK\npdfjs-password-cancel-button = Cancel\npdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fonts.\npdfjs-editor-free-text-button =\n .title = Text\npdfjs-editor-free-text-button-label = Text\npdfjs-editor-ink-button =\n .title = Draw\npdfjs-editor-ink-button-label = Draw\npdfjs-editor-stamp-button =\n .title = Add or edit images\npdfjs-editor-stamp-button-label = Add or edit images\npdfjs-editor-highlight-button =\n .title = Highlight\npdfjs-editor-highlight-button-label = Highlight\npdfjs-highlight-floating-button1 =\n .title = Highlight\n .aria-label = Highlight\npdfjs-highlight-floating-button-label = Highlight\npdfjs-editor-remove-ink-button =\n .title = Remove drawing\npdfjs-editor-remove-freetext-button =\n .title = Remove text\npdfjs-editor-remove-stamp-button =\n .title = Remove image\npdfjs-editor-remove-highlight-button =\n .title = Remove highlight\npdfjs-editor-free-text-color-input = Color\npdfjs-editor-free-text-size-input = Size\npdfjs-editor-ink-color-input = Color\npdfjs-editor-ink-thickness-input = Thickness\npdfjs-editor-ink-opacity-input = Opacity\npdfjs-editor-stamp-add-image-button =\n .title = Add image\npdfjs-editor-stamp-add-image-button-label = Add image\npdfjs-editor-free-highlight-thickness-input = Thickness\npdfjs-editor-free-highlight-thickness-title =\n .title = Change thickness when highlighting items other than text\npdfjs-free-text2 =\n .aria-label = Text Editor\n .default-content = Start typing\u2026\npdfjs-ink =\n .aria-label = Draw Editor\npdfjs-ink-canvas =\n .aria-label = User-created image\npdfjs-editor-alt-text-button =\n .aria-label = Alt text\npdfjs-editor-alt-text-button-label = Alt text\npdfjs-editor-alt-text-edit-button =\n .aria-label = Edit alt text\npdfjs-editor-alt-text-dialog-label = Choose an option\npdfjs-editor-alt-text-dialog-description = Alt text (alternative text) helps when people can\u2019t see the image or when it doesn\u2019t load.\npdfjs-editor-alt-text-add-description-label = Add a description\npdfjs-editor-alt-text-add-description-description = Aim for 1-2 sentences that describe the subject, setting, or actions.\npdfjs-editor-alt-text-mark-decorative-label = Mark as decorative\npdfjs-editor-alt-text-mark-decorative-description = This is used for ornamental images, like borders or watermarks.\npdfjs-editor-alt-text-cancel-button = Cancel\npdfjs-editor-alt-text-save-button = Save\npdfjs-editor-alt-text-decorative-tooltip = Marked as decorative\npdfjs-editor-alt-text-textarea =\n .placeholder = For example, \u201CA young man sits down at a table to eat a meal\u201D\npdfjs-editor-resizer-top-left =\n .aria-label = Top left corner \u2014 resize\npdfjs-editor-resizer-top-middle =\n .aria-label = Top middle \u2014 resize\npdfjs-editor-resizer-top-right =\n .aria-label = Top right corner \u2014 resize\npdfjs-editor-resizer-middle-right =\n .aria-label = Middle right \u2014 resize\npdfjs-editor-resizer-bottom-right =\n .aria-label = Bottom right corner \u2014 resize\npdfjs-editor-resizer-bottom-middle =\n .aria-label = Bottom middle \u2014 resize\npdfjs-editor-resizer-bottom-left =\n .aria-label = Bottom left corner \u2014 resize\npdfjs-editor-resizer-middle-left =\n .aria-label = Middle left \u2014 resize\npdfjs-editor-highlight-colorpicker-label = Highlight color\npdfjs-editor-colorpicker-button =\n .title = Change color\npdfjs-editor-colorpicker-dropdown =\n .aria-label = Color choices\npdfjs-editor-colorpicker-yellow =\n .title = Yellow\npdfjs-editor-colorpicker-green =\n .title = Green\npdfjs-editor-colorpicker-blue =\n .title = Blue\npdfjs-editor-colorpicker-pink =\n .title = Pink\npdfjs-editor-colorpicker-red =\n .title = Red\npdfjs-editor-highlight-show-all-button-label = Show all\npdfjs-editor-highlight-show-all-button =\n .title = Show all\npdfjs-editor-new-alt-text-dialog-edit-label = Edit alt text (image description)\npdfjs-editor-new-alt-text-dialog-add-label = Add alt text (image description)\npdfjs-editor-new-alt-text-textarea =\n .placeholder = Write your description here\u2026\npdfjs-editor-new-alt-text-description = Short description for people who can\u2019t see the image or when the image doesn\u2019t load.\npdfjs-editor-new-alt-text-disclaimer1 = This alt text was created automatically and may be inaccurate.\npdfjs-editor-new-alt-text-disclaimer-learn-more-url = Learn more\npdfjs-editor-new-alt-text-create-automatically-button-label = Create alt text automatically\npdfjs-editor-new-alt-text-not-now-button = Not now\npdfjs-editor-new-alt-text-error-title = Couldn\u2019t create alt text automatically\npdfjs-editor-new-alt-text-error-description = Please write your own alt text or try again later.\npdfjs-editor-new-alt-text-error-close-button = Close\npdfjs-editor-new-alt-text-ai-model-downloading-progress = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB)\n .aria-valuetext = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB)\npdfjs-editor-new-alt-text-added-button =\n .aria-label = Alt text added\npdfjs-editor-new-alt-text-added-button-label = Alt text added\npdfjs-editor-new-alt-text-missing-button =\n .aria-label = Missing alt text\npdfjs-editor-new-alt-text-missing-button-label = Missing alt text\npdfjs-editor-new-alt-text-to-review-button =\n .aria-label = Review alt text\npdfjs-editor-new-alt-text-to-review-button-label = Review alt text\npdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Created automatically: { $generatedAltText }\npdfjs-image-alt-text-settings-button =\n .title = Image alt text settings\npdfjs-image-alt-text-settings-button-label = Image alt text settings\npdfjs-editor-alt-text-settings-dialog-label = Image alt text settings\npdfjs-editor-alt-text-settings-automatic-title = Automatic alt text\npdfjs-editor-alt-text-settings-create-model-button-label = Create alt text automatically\npdfjs-editor-alt-text-settings-create-model-description = Suggests descriptions to help people who can\u2019t see the image or when the image doesn\u2019t load.\npdfjs-editor-alt-text-settings-download-model-label = Alt text AI model ({ $totalSize } MB)\npdfjs-editor-alt-text-settings-ai-model-description = Runs locally on your device so your data stays private. Required for automatic alt text.\npdfjs-editor-alt-text-settings-delete-model-button = Delete\npdfjs-editor-alt-text-settings-download-model-button = Download\npdfjs-editor-alt-text-settings-downloading-model-button = Downloading\u2026\npdfjs-editor-alt-text-settings-editor-title = Alt text editor\npdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor right away when adding an image\npdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text.\npdfjs-editor-alt-text-settings-close-button = Close\npdfjs-editor-undo-bar-message-highlight = Highlight removed\npdfjs-editor-undo-bar-message-freetext = Text removed\npdfjs-editor-undo-bar-message-ink = Drawing removed\npdfjs-editor-undo-bar-message-stamp = Image removed\npdfjs-editor-undo-bar-message-multiple =\n { $count ->\n [one] { $count } annotation removed\n *[other] { $count } annotations removed\n }\npdfjs-editor-undo-bar-undo-button =\n .title = Undo\npdfjs-editor-undo-bar-undo-button-label = Undo\npdfjs-editor-undo-bar-close-button =\n .title = Close\npdfjs-editor-undo-bar-close-button-label = Close'; + return createBundle(lang, text); + } +} // ./web/generic_scripting.js + +async function docProperties(pdfDocument) { + const url = "", + baseUrl = url.split("#", 1)[0]; + let { info, metadata, contentDispositionFilename, contentLength } = + await pdfDocument.getMetadata(); + if (!contentLength) { + const { length } = await pdfDocument.getDownloadInfo(); + contentLength = length; + } + return { + ...info, + baseURL: baseUrl, + filesize: contentLength, + filename: contentDispositionFilename || getPdfFilenameFromUrl(url), + metadata: metadata?.getRaw(), + authors: metadata?.get("dc:creator"), + numPages: pdfDocument.numPages, + URL: url, + }; +} +class GenericScripting { + constructor(sandboxBundleSrc) { + this._ready = new Promise((resolve, reject) => { + const sandbox = import(/*webpackIgnore: true*/ sandboxBundleSrc); + sandbox + .then((pdfjsSandbox) => { + resolve(pdfjsSandbox.QuickJSSandbox()); + }) + .catch(reject); + }); + } + async createSandbox(data) { + const sandbox = await this._ready; + sandbox.create(data); + } + async dispatchEventInSandbox(event) { + const sandbox = await this._ready; + setTimeout(() => sandbox.dispatchEvent(event), 0); + } + async destroySandbox() { + const sandbox = await this._ready; + sandbox.nukeSandbox(); + } +} // ./web/genericcom.js + +function initCom(app) {} +class Preferences extends BasePreferences { + async _writeToStorage(prefObj) { + localStorage.setItem("pdfjs.preferences", JSON.stringify(prefObj)); + } + async _readFromStorage(prefObj) { + return { + prefs: JSON.parse(localStorage.getItem("pdfjs.preferences")), + }; + } +} +class ExternalServices extends BaseExternalServices { + async createL10n() { + return new genericl10n_GenericL10n( + AppOptions.get("localeProperties")?.lang, + ); + } + createScripting() { + return new GenericScripting(AppOptions.get("sandboxBundleSrc")); + } +} +class MLManager { + async isEnabledFor(_name) { + return false; + } + async deleteModel(_service) { + return null; + } + isReady(_name) { + return false; + } + guess(_data) {} + toggleService(_name, _enabled) {} + static getFakeMLManager(options) { + return new FakeMLManager(options); + } +} +class FakeMLManager { + eventBus = null; + hasProgress = false; + constructor({ enableGuessAltText, enableAltTextModelDownload }) { + this.enableGuessAltText = enableGuessAltText; + this.enableAltTextModelDownload = enableAltTextModelDownload; + } + setEventBus(eventBus, abortSignal) { + this.eventBus = eventBus; + } + async isEnabledFor(_name) { + return this.enableGuessAltText; + } + async deleteModel(_name) { + this.enableAltTextModelDownload = false; + return null; + } + async loadModel(_name) {} + async downloadModel(_name) { + this.hasProgress = true; + const { promise, resolve } = Promise.withResolvers(); + const total = 1e8; + const end = 1.5 * total; + const increment = 5e6; + let loaded = 0; + const id = setInterval(() => { + loaded += increment; + if (loaded <= end) { + this.eventBus.dispatch("loadaiengineprogress", { + source: this, + detail: { + total, + totalLoaded: loaded, + finished: loaded + increment >= end, + }, + }); + return; + } + clearInterval(id); + this.hasProgress = false; + this.enableAltTextModelDownload = true; + resolve(true); + }, 900); + return promise; + } + isReady(_name) { + return this.enableAltTextModelDownload; + } + guess({ request: { data } }) { + return new Promise((resolve) => { + setTimeout(() => { + resolve( + data + ? { + output: "Fake alt text.", + } + : { + error: true, + }, + ); + }, 3000); + }); + } + toggleService(_name, enabled) { + this.enableGuessAltText = enabled; + } +} // ./web/new_alt_text_manager.js + +class NewAltTextManager { + #boundCancel = this.#cancel.bind(this); + #createAutomaticallyButton; + #currentEditor = null; + #cancelButton; + #descriptionContainer; + #dialog; + #disclaimer; + #downloadModel; + #downloadModelDescription; + #eventBus; + #firstTime = false; + #guessedAltText; + #hasAI = null; + #isEditing = null; + #imagePreview; + #imageData; + #isAILoading = false; + #wasAILoading = false; + #learnMore; + #notNowButton; + #overlayManager; + #textarea; + #title; + #uiManager; + #previousAltText = null; + constructor( + { + descriptionContainer, + dialog, + imagePreview, + cancelButton, + disclaimer, + notNowButton, + saveButton, + textarea, + learnMore, + errorCloseButton, + createAutomaticallyButton, + downloadModel, + downloadModelDescription, + title, + }, + overlayManager, + eventBus, + ) { + this.#cancelButton = cancelButton; + this.#createAutomaticallyButton = createAutomaticallyButton; + this.#descriptionContainer = descriptionContainer; + this.#dialog = dialog; + this.#disclaimer = disclaimer; + this.#notNowButton = notNowButton; + this.#imagePreview = imagePreview; + this.#textarea = textarea; + this.#learnMore = learnMore; + this.#title = title; + this.#downloadModel = downloadModel; + this.#downloadModelDescription = downloadModelDescription; + this.#overlayManager = overlayManager; + this.#eventBus = eventBus; + dialog.addEventListener("close", this.#close.bind(this)); + dialog.addEventListener("contextmenu", (event) => { + if (event.target !== this.#textarea) { + event.preventDefault(); + } + }); + cancelButton.addEventListener("click", this.#boundCancel); + notNowButton.addEventListener("click", this.#boundCancel); + saveButton.addEventListener("click", this.#save.bind(this)); + errorCloseButton.addEventListener("click", () => { + this.#toggleError(false); + }); + createAutomaticallyButton.addEventListener("click", async () => { + const checked = + createAutomaticallyButton.getAttribute("aria-pressed") !== "true"; + this.#currentEditor._reportTelemetry({ + action: "pdfjs.image.alt_text.ai_generation_check", + data: { + status: checked, + }, + }); + if (this.#uiManager) { + this.#uiManager.setPreference("enableGuessAltText", checked); + await this.#uiManager.mlManager.toggleService("altText", checked); + } + this.#toggleGuessAltText(checked, false); + }); + textarea.addEventListener("focus", () => { + this.#wasAILoading = this.#isAILoading; + this.#toggleLoading(false); + this.#toggleTitleAndDisclaimer(); + }); + textarea.addEventListener("blur", () => { + if (!textarea.value) { + this.#toggleLoading(this.#wasAILoading); + } + this.#toggleTitleAndDisclaimer(); + }); + textarea.addEventListener("input", () => { + this.#toggleTitleAndDisclaimer(); + }); + eventBus._on("enableguessalttext", ({ value }) => { + this.#toggleGuessAltText(value, false); + }); + this.#overlayManager.register(dialog); + this.#learnMore.addEventListener("click", () => { + this.#currentEditor._reportTelemetry({ + action: "pdfjs.image.alt_text.info", + data: { + topic: "alt_text", + }, + }); + }); + } + #toggleLoading(value) { + if (!this.#uiManager || this.#isAILoading === value) { + return; + } + this.#isAILoading = value; + this.#descriptionContainer.classList.toggle("loading", value); + } + #toggleError(value) { + if (!this.#uiManager) { + return; + } + this.#dialog.classList.toggle("error", value); + } + async #toggleGuessAltText(value, isInitial = false) { + if (!this.#uiManager) { + return; + } + this.#dialog.classList.toggle("aiDisabled", !value); + this.#createAutomaticallyButton.setAttribute("aria-pressed", value); + if (value) { + const { altTextLearnMoreUrl } = this.#uiManager.mlManager; + if (altTextLearnMoreUrl) { + this.#learnMore.href = altTextLearnMoreUrl; + } + this.#mlGuessAltText(isInitial); + } else { + this.#toggleLoading(false); + this.#isAILoading = false; + this.#toggleTitleAndDisclaimer(); + } + } + #toggleNotNow() { + this.#notNowButton.classList.toggle("hidden", !this.#firstTime); + this.#cancelButton.classList.toggle("hidden", this.#firstTime); + } + #toggleAI(value) { + if (!this.#uiManager || this.#hasAI === value) { + return; + } + this.#hasAI = value; + this.#dialog.classList.toggle("noAi", !value); + this.#toggleTitleAndDisclaimer(); + } + #toggleTitleAndDisclaimer() { + const visible = + this.#isAILoading || + (this.#guessedAltText && this.#guessedAltText === this.#textarea.value); + this.#disclaimer.hidden = !visible; + const isEditing = this.#isAILoading || !!this.#textarea.value; + if (this.#isEditing === isEditing) { + return; + } + this.#isEditing = isEditing; + this.#title.setAttribute( + "data-l10n-id", + isEditing + ? "pdfjs-editor-new-alt-text-dialog-edit-label" + : "pdfjs-editor-new-alt-text-dialog-add-label", + ); + } + async #mlGuessAltText(isInitial) { + if (this.#isAILoading) { + return; + } + if (this.#textarea.value) { + return; + } + if (isInitial && this.#previousAltText !== null) { + return; + } + this.#guessedAltText = this.#currentEditor.guessedAltText; + if (this.#previousAltText === null && this.#guessedAltText) { + this.#addAltText(this.#guessedAltText); + return; + } + this.#toggleLoading(true); + this.#toggleTitleAndDisclaimer(); + let hasError = false; + try { + const altText = await this.#currentEditor.mlGuessAltText( + this.#imageData, + false, + ); + if (altText) { + this.#guessedAltText = altText; + this.#wasAILoading = this.#isAILoading; + if (this.#isAILoading) { + this.#addAltText(altText); + } + } + } catch (e) { + console.error(e); + hasError = true; + } + this.#toggleLoading(false); + this.#toggleTitleAndDisclaimer(); + if (hasError && this.#uiManager) { + this.#toggleError(true); + } + } + #addAltText(altText) { + if (!this.#uiManager || this.#textarea.value) { + return; + } + this.#textarea.value = altText; + this.#toggleTitleAndDisclaimer(); + } + #setProgress() { + this.#downloadModel.classList.toggle("hidden", false); + const callback = async ({ detail: { finished, total, totalLoaded } }) => { + const ONE_MEGA_BYTES = 1e6; + totalLoaded = Math.min(0.99 * total, totalLoaded); + const totalSize = (this.#downloadModelDescription.ariaValueMax = + Math.round(total / ONE_MEGA_BYTES)); + const downloadedSize = (this.#downloadModelDescription.ariaValueNow = + Math.round(totalLoaded / ONE_MEGA_BYTES)); + this.#downloadModelDescription.setAttribute( + "data-l10n-args", + JSON.stringify({ + totalSize, + downloadedSize, + }), + ); + if (!finished) { + return; + } + this.#eventBus._off("loadaiengineprogress", callback); + this.#downloadModel.classList.toggle("hidden", true); + this.#toggleAI(true); + if (!this.#uiManager) { + return; + } + const { mlManager } = this.#uiManager; + mlManager.toggleService("altText", true); + this.#toggleGuessAltText(await mlManager.isEnabledFor("altText"), true); + }; + this.#eventBus._on("loadaiengineprogress", callback); + } + async editAltText(uiManager, editor, firstTime) { + if (this.#currentEditor || !editor) { + return; + } + if (firstTime && editor.hasAltTextData()) { + editor.altTextFinish(); + return; + } + this.#firstTime = firstTime; + let { mlManager } = uiManager; + let hasAI = !!mlManager; + this.#toggleTitleAndDisclaimer(); + if (mlManager && !mlManager.isReady("altText")) { + hasAI = false; + if (mlManager.hasProgress) { + this.#setProgress(); + } else { + mlManager = null; + } + } else { + this.#downloadModel.classList.toggle("hidden", true); + } + const isAltTextEnabledPromise = mlManager?.isEnabledFor("altText"); + this.#currentEditor = editor; + this.#uiManager = uiManager; + this.#uiManager.removeEditListeners(); + ({ altText: this.#previousAltText } = editor.altTextData); + this.#textarea.value = this.#previousAltText ?? ""; + const AI_MAX_IMAGE_DIMENSION = 224; + const MAX_PREVIEW_DIMENSION = 180; + let canvas, width, height; + if (mlManager) { + ({ + canvas, + width, + height, + imageData: this.#imageData, + } = editor.copyCanvas( + AI_MAX_IMAGE_DIMENSION, + MAX_PREVIEW_DIMENSION, + true, + )); + if (hasAI) { + this.#toggleGuessAltText(await isAltTextEnabledPromise, true); + } + } else { + ({ canvas, width, height } = editor.copyCanvas( + AI_MAX_IMAGE_DIMENSION, + MAX_PREVIEW_DIMENSION, + false, + )); + } + canvas.setAttribute("role", "presentation"); + const { style } = canvas; + style.width = `${width}px`; + style.height = `${height}px`; + this.#imagePreview.append(canvas); + this.#toggleNotNow(); + this.#toggleAI(hasAI); + this.#toggleError(false); + try { + await this.#overlayManager.open(this.#dialog); + } catch (ex) { + this.#close(); + throw ex; + } + } + #cancel() { + this.#currentEditor.altTextData = { + cancel: true, + }; + const altText = this.#textarea.value.trim(); + this.#currentEditor._reportTelemetry({ + action: "pdfjs.image.alt_text.dismiss", + data: { + alt_text_type: altText ? "present" : "empty", + flow: this.#firstTime ? "image_add" : "alt_text_edit", + }, + }); + this.#currentEditor._reportTelemetry({ + action: "pdfjs.image.image_added", + data: { + alt_text_modal: true, + alt_text_type: "skipped", + }, + }); + this.#finish(); + } + #finish() { + if (this.#overlayManager.active === this.#dialog) { + this.#overlayManager.close(this.#dialog); + } + } + #close() { + const canvas = this.#imagePreview.firstChild; + canvas.remove(); + canvas.width = canvas.height = 0; + this.#imageData = null; + this.#toggleLoading(false); + this.#uiManager?.addEditListeners(); + this.#currentEditor.altTextFinish(); + this.#uiManager?.setSelected(this.#currentEditor); + this.#currentEditor = null; + this.#uiManager = null; + } + #extractWords(text) { + return new Set( + text + .toLowerCase() + .split(/[^\p{L}\p{N}]+/gu) + .filter((x) => !!x), + ); + } + #save() { + const altText = this.#textarea.value.trim(); + this.#currentEditor.altTextData = { + altText, + decorative: false, + }; + this.#currentEditor.altTextData.guessedAltText = this.#guessedAltText; + if (this.#guessedAltText && this.#guessedAltText !== altText) { + const guessedWords = this.#extractWords(this.#guessedAltText); + const words = this.#extractWords(altText); + this.#currentEditor._reportTelemetry({ + action: "pdfjs.image.alt_text.user_edit", + data: { + total_words: guessedWords.size, + words_removed: guessedWords.difference(words).size, + words_added: words.difference(guessedWords).size, + }, + }); + } + this.#currentEditor._reportTelemetry({ + action: "pdfjs.image.image_added", + data: { + alt_text_modal: true, + alt_text_type: altText ? "present" : "empty", + }, + }); + this.#currentEditor._reportTelemetry({ + action: "pdfjs.image.alt_text.save", + data: { + alt_text_type: altText ? "present" : "empty", + flow: this.#firstTime ? "image_add" : "alt_text_edit", + }, + }); + this.#finish(); + } + destroy() { + this.#uiManager = null; + this.#finish(); + } +} +class ImageAltTextSettings { + #aiModelSettings; + #createModelButton; + #downloadModelButton; + #dialog; + #eventBus; + #mlManager; + #overlayManager; + #showAltTextDialogButton; + constructor( + { + dialog, + createModelButton, + aiModelSettings, + learnMore, + closeButton, + deleteModelButton, + downloadModelButton, + showAltTextDialogButton, + }, + overlayManager, + eventBus, + mlManager, + ) { + this.#dialog = dialog; + this.#aiModelSettings = aiModelSettings; + this.#createModelButton = createModelButton; + this.#downloadModelButton = downloadModelButton; + this.#showAltTextDialogButton = showAltTextDialogButton; + this.#overlayManager = overlayManager; + this.#eventBus = eventBus; + this.#mlManager = mlManager; + const { altTextLearnMoreUrl } = mlManager; + if (altTextLearnMoreUrl) { + learnMore.href = altTextLearnMoreUrl; + } + dialog.addEventListener("contextmenu", noContextMenu); + createModelButton.addEventListener("click", async (e) => { + const checked = this.#togglePref("enableGuessAltText", e); + await mlManager.toggleService("altText", checked); + this.#reportTelemetry({ + type: "stamp", + action: "pdfjs.image.alt_text.settings_ai_generation_check", + data: { + status: checked, + }, + }); + }); + showAltTextDialogButton.addEventListener("click", (e) => { + const checked = this.#togglePref("enableNewAltTextWhenAddingImage", e); + this.#reportTelemetry({ + type: "stamp", + action: "pdfjs.image.alt_text.settings_edit_alt_text_check", + data: { + status: checked, + }, + }); + }); + deleteModelButton.addEventListener("click", this.#delete.bind(this, true)); + downloadModelButton.addEventListener( + "click", + this.#download.bind(this, true), + ); + closeButton.addEventListener("click", this.#finish.bind(this)); + learnMore.addEventListener("click", () => { + this.#reportTelemetry({ + type: "stamp", + action: "pdfjs.image.alt_text.info", + data: { + topic: "ai_generation", + }, + }); + }); + eventBus._on("enablealttextmodeldownload", ({ value }) => { + if (value) { + this.#download(false); + } else { + this.#delete(false); + } + }); + this.#overlayManager.register(dialog); + } + #reportTelemetry(data) { + this.#eventBus.dispatch("reporttelemetry", { + source: this, + details: { + type: "editing", + data, + }, + }); + } + async #download(isFromUI = false) { + if (isFromUI) { + this.#downloadModelButton.disabled = true; + const span = this.#downloadModelButton.firstChild; + span.setAttribute( + "data-l10n-id", + "pdfjs-editor-alt-text-settings-downloading-model-button", + ); + await this.#mlManager.downloadModel("altText"); + span.setAttribute( + "data-l10n-id", + "pdfjs-editor-alt-text-settings-download-model-button", + ); + this.#createModelButton.disabled = false; + this.#setPref("enableGuessAltText", true); + this.#mlManager.toggleService("altText", true); + this.#setPref("enableAltTextModelDownload", true); + this.#downloadModelButton.disabled = false; + } + this.#aiModelSettings.classList.toggle("download", false); + this.#createModelButton.setAttribute("aria-pressed", true); + } + async #delete(isFromUI = false) { + if (isFromUI) { + await this.#mlManager.deleteModel("altText"); + this.#setPref("enableGuessAltText", false); + this.#setPref("enableAltTextModelDownload", false); + } + this.#aiModelSettings.classList.toggle("download", true); + this.#createModelButton.disabled = true; + this.#createModelButton.setAttribute("aria-pressed", false); + } + async open({ enableGuessAltText, enableNewAltTextWhenAddingImage }) { + const { enableAltTextModelDownload } = this.#mlManager; + this.#createModelButton.disabled = !enableAltTextModelDownload; + this.#createModelButton.setAttribute( + "aria-pressed", + enableAltTextModelDownload && enableGuessAltText, + ); + this.#showAltTextDialogButton.setAttribute( + "aria-pressed", + enableNewAltTextWhenAddingImage, + ); + this.#aiModelSettings.classList.toggle( + "download", + !enableAltTextModelDownload, + ); + await this.#overlayManager.open(this.#dialog); + this.#reportTelemetry({ + type: "stamp", + action: "pdfjs.image.alt_text.settings_displayed", + }); + } + #togglePref(name, { target }) { + const checked = target.getAttribute("aria-pressed") !== "true"; + this.#setPref(name, checked); + target.setAttribute("aria-pressed", checked); + return checked; + } + #setPref(name, value) { + this.#eventBus.dispatch("setpreference", { + source: this, + name, + value, + }); + } + #finish() { + if (this.#overlayManager.active === this.#dialog) { + this.#overlayManager.close(this.#dialog); + } + } +} // ./web/alt_text_manager.js + +class AltTextManager { + #clickAC = null; + #currentEditor = null; + #cancelButton; + #dialog; + #eventBus; + #hasUsedPointer = false; + #optionDescription; + #optionDecorative; + #overlayManager; + #saveButton; + #textarea; + #uiManager; + #previousAltText = null; + #resizeAC = null; + #svgElement = null; + #rectElement = null; + #container; + #telemetryData = null; + constructor( + { + dialog, + optionDescription, + optionDecorative, + textarea, + cancelButton, + saveButton, + }, + container, + overlayManager, + eventBus, + ) { + this.#dialog = dialog; + this.#optionDescription = optionDescription; + this.#optionDecorative = optionDecorative; + this.#textarea = textarea; + this.#cancelButton = cancelButton; + this.#saveButton = saveButton; + this.#overlayManager = overlayManager; + this.#eventBus = eventBus; + this.#container = container; + const onUpdateUIState = this.#updateUIState.bind(this); + dialog.addEventListener("close", this.#close.bind(this)); + dialog.addEventListener("contextmenu", (event) => { + if (event.target !== this.#textarea) { + event.preventDefault(); + } + }); + cancelButton.addEventListener("click", this.#finish.bind(this)); + saveButton.addEventListener("click", this.#save.bind(this)); + optionDescription.addEventListener("change", onUpdateUIState); + optionDecorative.addEventListener("change", onUpdateUIState); + this.#overlayManager.register(dialog); + } + #createSVGElement() { + if (this.#svgElement) { + return; + } + const svgFactory = new DOMSVGFactory(); + const svg = (this.#svgElement = svgFactory.createElement("svg")); + svg.setAttribute("width", "0"); + svg.setAttribute("height", "0"); + const defs = svgFactory.createElement("defs"); + svg.append(defs); + const mask = svgFactory.createElement("mask"); + defs.append(mask); + mask.setAttribute("id", "alttext-manager-mask"); + mask.setAttribute("maskContentUnits", "objectBoundingBox"); + let rect = svgFactory.createElement("rect"); + mask.append(rect); + rect.setAttribute("fill", "white"); + rect.setAttribute("width", "1"); + rect.setAttribute("height", "1"); + rect.setAttribute("x", "0"); + rect.setAttribute("y", "0"); + rect = this.#rectElement = svgFactory.createElement("rect"); + mask.append(rect); + rect.setAttribute("fill", "black"); + this.#dialog.append(svg); + } + async editAltText(uiManager, editor) { + if (this.#currentEditor || !editor) { + return; + } + this.#createSVGElement(); + this.#hasUsedPointer = false; + this.#clickAC = new AbortController(); + const clickOpts = { + signal: this.#clickAC.signal, + }, + onClick = this.#onClick.bind(this); + for (const element of [ + this.#optionDescription, + this.#optionDecorative, + this.#textarea, + this.#saveButton, + this.#cancelButton, + ]) { + element.addEventListener("click", onClick, clickOpts); + } + const { altText, decorative } = editor.altTextData; + if (decorative === true) { + this.#optionDecorative.checked = true; + this.#optionDescription.checked = false; + } else { + this.#optionDecorative.checked = false; + this.#optionDescription.checked = true; + } + this.#previousAltText = this.#textarea.value = altText?.trim() || ""; + this.#updateUIState(); + this.#currentEditor = editor; + this.#uiManager = uiManager; + this.#uiManager.removeEditListeners(); + this.#resizeAC = new AbortController(); + this.#eventBus._on("resize", this.#setPosition.bind(this), { + signal: this.#resizeAC.signal, + }); + try { + await this.#overlayManager.open(this.#dialog); + this.#setPosition(); + } catch (ex) { + this.#close(); + throw ex; + } + } + #setPosition() { + if (!this.#currentEditor) { + return; + } + const dialog = this.#dialog; + const { style } = dialog; + const { + x: containerX, + y: containerY, + width: containerW, + height: containerH, + } = this.#container.getBoundingClientRect(); + const { innerWidth: windowW, innerHeight: windowH } = window; + const { width: dialogW, height: dialogH } = dialog.getBoundingClientRect(); + const { x, y, width, height } = this.#currentEditor.getClientDimensions(); + const MARGIN = 10; + const isLTR = this.#uiManager.direction === "ltr"; + const xs = Math.max(x, containerX); + const xe = Math.min(x + width, containerX + containerW); + const ys = Math.max(y, containerY); + const ye = Math.min(y + height, containerY + containerH); + this.#rectElement.setAttribute("width", `${(xe - xs) / windowW}`); + this.#rectElement.setAttribute("height", `${(ye - ys) / windowH}`); + this.#rectElement.setAttribute("x", `${xs / windowW}`); + this.#rectElement.setAttribute("y", `${ys / windowH}`); + let left = null; + let top = Math.max(y, 0); + top += Math.min(windowH - (top + dialogH), 0); + if (isLTR) { + if (x + width + MARGIN + dialogW < windowW) { + left = x + width + MARGIN; + } else if (x > dialogW + MARGIN) { + left = x - dialogW - MARGIN; + } + } else if (x > dialogW + MARGIN) { + left = x - dialogW - MARGIN; + } else if (x + width + MARGIN + dialogW < windowW) { + left = x + width + MARGIN; + } + if (left === null) { + top = null; + left = Math.max(x, 0); + left += Math.min(windowW - (left + dialogW), 0); + if (y > dialogH + MARGIN) { + top = y - dialogH - MARGIN; + } else if (y + height + MARGIN + dialogH < windowH) { + top = y + height + MARGIN; + } + } + if (top !== null) { + dialog.classList.add("positioned"); + if (isLTR) { + style.left = `${left}px`; + } else { + style.right = `${windowW - left - dialogW}px`; + } + style.top = `${top}px`; + } else { + dialog.classList.remove("positioned"); + style.left = ""; + style.top = ""; + } + } + #finish() { + if (this.#overlayManager.active === this.#dialog) { + this.#overlayManager.close(this.#dialog); + } + } + #close() { + this.#currentEditor._reportTelemetry( + this.#telemetryData || { + action: "alt_text_cancel", + alt_text_keyboard: !this.#hasUsedPointer, + }, + ); + this.#telemetryData = null; + this.#removeOnClickListeners(); + this.#uiManager?.addEditListeners(); + this.#resizeAC?.abort(); + this.#resizeAC = null; + this.#currentEditor.altTextFinish(); + this.#currentEditor = null; + this.#uiManager = null; + } + #updateUIState() { + this.#textarea.disabled = this.#optionDecorative.checked; + } + #save() { + const altText = this.#textarea.value.trim(); + const decorative = this.#optionDecorative.checked; + this.#currentEditor.altTextData = { + altText, + decorative, + }; + this.#telemetryData = { + action: "alt_text_save", + alt_text_description: !!altText, + alt_text_edit: + !!this.#previousAltText && this.#previousAltText !== altText, + alt_text_decorative: decorative, + alt_text_keyboard: !this.#hasUsedPointer, + }; + this.#finish(); + } + #onClick(evt) { + if (evt.detail === 0) { + return; + } + this.#hasUsedPointer = true; + this.#removeOnClickListeners(); + } + #removeOnClickListeners() { + this.#clickAC?.abort(); + this.#clickAC = null; + } + destroy() { + this.#uiManager = null; + this.#finish(); + this.#svgElement?.remove(); + this.#svgElement = this.#rectElement = null; + } +} // ./web/annotation_editor_params.js + +class AnnotationEditorParams { + constructor(options, eventBus) { + this.eventBus = eventBus; + this.#bindListeners(options); + } + #bindListeners({ + editorFreeTextFontSize, + editorFreeTextColor, + editorInkColor, + editorInkThickness, + editorInkOpacity, + editorStampAddImage, + editorFreeHighlightThickness, + editorHighlightShowAll, + }) { + const dispatchEvent = (typeStr, value) => { + this.eventBus.dispatch("switchannotationeditorparams", { + source: this, + type: AnnotationEditorParamsType[typeStr], + value, + }); + }; + editorFreeTextFontSize.addEventListener("input", function () { + dispatchEvent("FREETEXT_SIZE", this.valueAsNumber); + }); + editorFreeTextColor.addEventListener("input", function () { + dispatchEvent("FREETEXT_COLOR", this.value); + }); + editorInkColor.addEventListener("input", function () { + dispatchEvent("INK_COLOR", this.value); + }); + editorInkThickness.addEventListener("input", function () { + dispatchEvent("INK_THICKNESS", this.valueAsNumber); + }); + editorInkOpacity.addEventListener("input", function () { + dispatchEvent("INK_OPACITY", this.valueAsNumber); + }); + editorStampAddImage.addEventListener("click", () => { + this.eventBus.dispatch("reporttelemetry", { + source: this, + details: { + type: "editing", + data: { + action: "pdfjs.image.add_image_click", + }, + }, + }); + dispatchEvent("CREATE"); + }); + editorFreeHighlightThickness.addEventListener("input", function () { + dispatchEvent("HIGHLIGHT_THICKNESS", this.valueAsNumber); + }); + editorHighlightShowAll.addEventListener("click", function () { + const checked = this.getAttribute("aria-pressed") === "true"; + this.setAttribute("aria-pressed", !checked); + dispatchEvent("HIGHLIGHT_SHOW_ALL", !checked); + }); + this.eventBus._on("annotationeditorparamschanged", (evt) => { + for (const [type, value] of evt.details) { + switch (type) { + case AnnotationEditorParamsType.FREETEXT_SIZE: + editorFreeTextFontSize.value = value; + break; + case AnnotationEditorParamsType.FREETEXT_COLOR: + editorFreeTextColor.value = value; + break; + case AnnotationEditorParamsType.INK_COLOR: + editorInkColor.value = value; + break; + case AnnotationEditorParamsType.INK_THICKNESS: + editorInkThickness.value = value; + break; + case AnnotationEditorParamsType.INK_OPACITY: + editorInkOpacity.value = value; + break; + case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS: + editorFreeHighlightThickness.value = value; + break; + case AnnotationEditorParamsType.HIGHLIGHT_FREE: + editorFreeHighlightThickness.disabled = !value; + break; + case AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL: + editorHighlightShowAll.setAttribute("aria-pressed", value); + break; + } + } + }); + } +} // ./web/caret_browsing.js + +const PRECISION = 1e-1; +class CaretBrowsingMode { + #mainContainer; + #toolBarHeight = 0; + #viewerContainer; + constructor(abortSignal, mainContainer, viewerContainer, toolbarContainer) { + this.#mainContainer = mainContainer; + this.#viewerContainer = viewerContainer; + if (!toolbarContainer) { + return; + } + this.#toolBarHeight = toolbarContainer.getBoundingClientRect().height; + const toolbarObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + if (entry.target === toolbarContainer) { + this.#toolBarHeight = Math.floor(entry.borderBoxSize[0].blockSize); + break; + } + } + }); + toolbarObserver.observe(toolbarContainer); + abortSignal.addEventListener("abort", () => toolbarObserver.disconnect(), { + once: true, + }); + } + #isOnSameLine(rect1, rect2) { + const top1 = rect1.y; + const bot1 = rect1.bottom; + const mid1 = rect1.y + rect1.height / 2; + const top2 = rect2.y; + const bot2 = rect2.bottom; + const mid2 = rect2.y + rect2.height / 2; + return (top1 <= mid2 && mid2 <= bot1) || (top2 <= mid1 && mid1 <= bot2); + } + #isUnderOver(rect, x, y, isUp) { + const midY = rect.y + rect.height / 2; + return ( + (isUp ? y >= midY : y <= midY) && + rect.x - PRECISION <= x && + x <= rect.right + PRECISION + ); + } + #isVisible(rect) { + return ( + rect.top >= this.#toolBarHeight && + rect.left >= 0 && + rect.bottom <= + (window.innerHeight || document.documentElement.clientHeight) && + rect.right <= (window.innerWidth || document.documentElement.clientWidth) + ); + } + #getCaretPosition(selection, isUp) { + const { focusNode, focusOffset } = selection; + const range = document.createRange(); + range.setStart(focusNode, focusOffset); + range.setEnd(focusNode, focusOffset); + const rect = range.getBoundingClientRect(); + return [rect.x, isUp ? rect.top : rect.bottom]; + } + static #caretPositionFromPoint(x, y) { + if (!document.caretPositionFromPoint) { + const { startContainer: offsetNode, startOffset: offset } = + document.caretRangeFromPoint(x, y); + return { + offsetNode, + offset, + }; + } + return document.caretPositionFromPoint(x, y); + } + #setCaretPositionHelper(selection, caretX, select, element, rect) { + rect ||= element.getBoundingClientRect(); + if (caretX <= rect.x + PRECISION) { + if (select) { + selection.extend(element.firstChild, 0); + } else { + selection.setPosition(element.firstChild, 0); + } + return; + } + if (rect.right - PRECISION <= caretX) { + const { lastChild } = element; + if (select) { + selection.extend(lastChild, lastChild.length); + } else { + selection.setPosition(lastChild, lastChild.length); + } + return; + } + const midY = rect.y + rect.height / 2; + let caretPosition = CaretBrowsingMode.#caretPositionFromPoint(caretX, midY); + let parentElement = caretPosition.offsetNode?.parentElement; + if (parentElement && parentElement !== element) { + const elementsAtPoint = document.elementsFromPoint(caretX, midY); + const savedVisibilities = []; + for (const el of elementsAtPoint) { + if (el === element) { + break; + } + const { style } = el; + savedVisibilities.push([el, style.visibility]); + style.visibility = "hidden"; + } + caretPosition = CaretBrowsingMode.#caretPositionFromPoint(caretX, midY); + parentElement = caretPosition.offsetNode?.parentElement; + for (const [el, visibility] of savedVisibilities) { + el.style.visibility = visibility; + } + } + if (parentElement !== element) { + if (select) { + selection.extend(element.firstChild, 0); + } else { + selection.setPosition(element.firstChild, 0); + } + return; + } + if (select) { + selection.extend(caretPosition.offsetNode, caretPosition.offset); + } else { + selection.setPosition(caretPosition.offsetNode, caretPosition.offset); + } + } + #setCaretPosition( + select, + selection, + newLineElement, + newLineElementRect, + caretX, + ) { + if (this.#isVisible(newLineElementRect)) { + this.#setCaretPositionHelper( + selection, + caretX, + select, + newLineElement, + newLineElementRect, + ); + return; + } + this.#mainContainer.addEventListener( + "scrollend", + this.#setCaretPositionHelper.bind( + this, + selection, + caretX, + select, + newLineElement, + null, + ), + { + once: true, + }, + ); + newLineElement.scrollIntoView(); + } + #getNodeOnNextPage(textLayer, isUp) { + while (true) { + const page = textLayer.closest(".page"); + const pageNumber = parseInt(page.getAttribute("data-page-number")); + const nextPage = isUp ? pageNumber - 1 : pageNumber + 1; + textLayer = this.#viewerContainer.querySelector( + `.page[data-page-number="${nextPage}"] .textLayer`, + ); + if (!textLayer) { + return null; + } + const walker = document.createTreeWalker(textLayer, NodeFilter.SHOW_TEXT); + const node = isUp ? walker.lastChild() : walker.firstChild(); + if (node) { + return node; + } + } + } + moveCaret(isUp, select) { + const selection = document.getSelection(); + if (selection.rangeCount === 0) { + return; + } + const { focusNode } = selection; + const focusElement = + focusNode.nodeType !== Node.ELEMENT_NODE + ? focusNode.parentElement + : focusNode; + const root = focusElement.closest(".textLayer"); + if (!root) { + return; + } + const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT); + walker.currentNode = focusNode; + const focusRect = focusElement.getBoundingClientRect(); + let newLineElement = null; + const nodeIterator = ( + isUp ? walker.previousSibling : walker.nextSibling + ).bind(walker); + while (nodeIterator()) { + const element = walker.currentNode.parentElement; + if (!this.#isOnSameLine(focusRect, element.getBoundingClientRect())) { + newLineElement = element; + break; + } + } + if (!newLineElement) { + const node = this.#getNodeOnNextPage(root, isUp); + if (!node) { + return; + } + if (select) { + const lastNode = + (isUp ? walker.firstChild() : walker.lastChild()) || focusNode; + selection.extend(lastNode, isUp ? 0 : lastNode.length); + const range = document.createRange(); + range.setStart(node, isUp ? node.length : 0); + range.setEnd(node, isUp ? node.length : 0); + selection.addRange(range); + return; + } + const [caretX] = this.#getCaretPosition(selection, isUp); + const { parentElement } = node; + this.#setCaretPosition( + select, + selection, + parentElement, + parentElement.getBoundingClientRect(), + caretX, + ); + return; + } + const [caretX, caretY] = this.#getCaretPosition(selection, isUp); + const newLineElementRect = newLineElement.getBoundingClientRect(); + if (this.#isUnderOver(newLineElementRect, caretX, caretY, isUp)) { + this.#setCaretPosition( + select, + selection, + newLineElement, + newLineElementRect, + caretX, + ); + return; + } + while (nodeIterator()) { + const element = walker.currentNode.parentElement; + const elementRect = element.getBoundingClientRect(); + if (!this.#isOnSameLine(newLineElementRect, elementRect)) { + break; + } + if (this.#isUnderOver(elementRect, caretX, caretY, isUp)) { + this.#setCaretPosition(select, selection, element, elementRect, caretX); + return; + } + } + this.#setCaretPosition( + select, + selection, + newLineElement, + newLineElementRect, + caretX, + ); + } +} // ./web/download_manager.js + +function download(blobUrl, filename) { + const a = document.createElement("a"); + if (!a.click) { + throw new Error('DownloadManager: "a.click()" is not supported.'); + } + a.href = blobUrl; + a.target = "_parent"; + if ("download" in a) { + a.download = filename; + } + (document.body || document.documentElement).append(a); + a.click(); + a.remove(); +} +class DownloadManager { + #openBlobUrls = new WeakMap(); + downloadData(data, filename, contentType) { + const blobUrl = URL.createObjectURL( + new Blob([data], { + type: contentType, + }), + ); + download(blobUrl, filename); + } + openOrDownloadData(data, filename, dest = null) { + const isPdfData = isPdfFile(filename); + const contentType = isPdfData ? "application/pdf" : ""; + if (isPdfData) { + let blobUrl = this.#openBlobUrls.get(data); + if (!blobUrl) { + blobUrl = URL.createObjectURL( + new Blob([data], { + type: contentType, + }), + ); + this.#openBlobUrls.set(data, blobUrl); + } + let viewerUrl; + viewerUrl = "?file=" + encodeURIComponent(blobUrl + "#" + filename); + if (dest) { + viewerUrl += `#${escape(dest)}`; + } + try { + window.open(viewerUrl); + return true; + } catch (ex) { + console.error("openOrDownloadData:", ex); + URL.revokeObjectURL(blobUrl); + this.#openBlobUrls.delete(data); + } + } + this.downloadData(data, filename, contentType); + return false; + } + download(data, url, filename) { + let blobUrl; + if (data) { + blobUrl = URL.createObjectURL( + new Blob([data], { + type: "application/pdf", + }), + ); + } else { + if (!createValidAbsoluteUrl(url, "http://example.com")) { + console.error(`download - not a valid URL: ${url}`); + return; + } + blobUrl = url + "#pdfjs.action=download"; + } + download(blobUrl, filename); + } +} // ./web/editor_undo_bar.js + +class EditorUndoBar { + #closeButton = null; + #container; + #eventBus = null; + #focusTimeout = null; + #initController = null; + isOpen = false; + #message; + #showController = null; + #undoButton; + static #l10nMessages = Object.freeze({ + highlight: "pdfjs-editor-undo-bar-message-highlight", + freetext: "pdfjs-editor-undo-bar-message-freetext", + stamp: "pdfjs-editor-undo-bar-message-stamp", + ink: "pdfjs-editor-undo-bar-message-ink", + _multiple: "pdfjs-editor-undo-bar-message-multiple", + }); + constructor({ container, message, undoButton, closeButton }, eventBus) { + this.#container = container; + this.#message = message; + this.#undoButton = undoButton; + this.#closeButton = closeButton; + this.#eventBus = eventBus; + } + destroy() { + this.#initController?.abort(); + this.#initController = null; + this.hide(); + } + show(undoAction, messageData) { + if (!this.#initController) { + this.#initController = new AbortController(); + const opts = { + signal: this.#initController.signal, + }; + const boundHide = this.hide.bind(this); + this.#container.addEventListener("contextmenu", noContextMenu, opts); + this.#closeButton.addEventListener("click", boundHide, opts); + this.#eventBus._on("beforeprint", boundHide, opts); + this.#eventBus._on("download", boundHide, opts); + } + this.hide(); + if (typeof messageData === "string") { + this.#message.setAttribute( + "data-l10n-id", + EditorUndoBar.#l10nMessages[messageData], + ); + } else { + this.#message.setAttribute( + "data-l10n-id", + EditorUndoBar.#l10nMessages._multiple, + ); + this.#message.setAttribute( + "data-l10n-args", + JSON.stringify({ + count: messageData, + }), + ); + } + this.isOpen = true; + this.#container.hidden = false; + this.#showController = new AbortController(); + this.#undoButton.addEventListener( + "click", + () => { + undoAction(); + this.hide(); + }, + { + signal: this.#showController.signal, + }, + ); + this.#focusTimeout = setTimeout(() => { + this.#container.focus(); + this.#focusTimeout = null; + }, 100); + } + hide() { + if (!this.isOpen) { + return; + } + this.isOpen = false; + this.#container.hidden = true; + this.#showController?.abort(); + this.#showController = null; + if (this.#focusTimeout) { + clearTimeout(this.#focusTimeout); + this.#focusTimeout = null; + } + } +} // ./web/overlay_manager.js + +class OverlayManager { + #overlays = new WeakMap(); + #active = null; + get active() { + return this.#active; + } + async register(dialog, canForceClose = false) { + if (typeof dialog !== "object") { + throw new Error("Not enough parameters."); + } else if (this.#overlays.has(dialog)) { + throw new Error("The overlay is already registered."); + } + this.#overlays.set(dialog, { + canForceClose, + }); + dialog.addEventListener("cancel", (evt) => { + this.#active = null; + }); + } + async open(dialog) { + if (!this.#overlays.has(dialog)) { + throw new Error("The overlay does not exist."); + } else if (this.#active) { + if (this.#active === dialog) { + throw new Error("The overlay is already active."); + } else if (this.#overlays.get(dialog).canForceClose) { + await this.close(); + } else { + throw new Error("Another overlay is currently active."); + } + } + this.#active = dialog; + dialog.showModal(); + } + async close(dialog = this.#active) { + if (!this.#overlays.has(dialog)) { + throw new Error("The overlay does not exist."); + } else if (!this.#active) { + throw new Error("The overlay is currently not active."); + } else if (this.#active !== dialog) { + throw new Error("Another overlay is currently active."); + } + dialog.close(); + this.#active = null; + } +} // ./web/password_prompt.js + +class PasswordPrompt { + #activeCapability = null; + #updateCallback = null; + #reason = null; + constructor(options, overlayManager, isViewerEmbedded = false) { + this.dialog = options.dialog; + this.label = options.label; + this.input = options.input; + this.submitButton = options.submitButton; + this.cancelButton = options.cancelButton; + this.overlayManager = overlayManager; + this._isViewerEmbedded = isViewerEmbedded; + this.submitButton.addEventListener("click", this.#verify.bind(this)); + this.cancelButton.addEventListener("click", this.close.bind(this)); + this.input.addEventListener("keydown", (e) => { + if (e.keyCode === 13) { + this.#verify(); + } + }); + this.overlayManager.register(this.dialog, true); + this.dialog.addEventListener("close", this.#cancel.bind(this)); + } + async open() { + await this.#activeCapability?.promise; + this.#activeCapability = Promise.withResolvers(); + try { + await this.overlayManager.open(this.dialog); + } catch (ex) { + this.#activeCapability.resolve(); + throw ex; + } + const passwordIncorrect = + this.#reason === PasswordResponses.INCORRECT_PASSWORD; + if (!this._isViewerEmbedded || passwordIncorrect) { + this.input.focus(); + } + this.label.setAttribute( + "data-l10n-id", + passwordIncorrect ? "pdfjs-password-invalid" : "pdfjs-password-label", + ); + } + async close() { + if (this.overlayManager.active === this.dialog) { + this.overlayManager.close(this.dialog); + } + } + #verify() { + const password = this.input.value; + if (password?.length > 0) { + this.#invokeCallback(password); + } + } + #cancel() { + this.#invokeCallback(new Error("PasswordPrompt cancelled.")); + this.#activeCapability.resolve(); + } + #invokeCallback(password) { + if (!this.#updateCallback) { + return; + } + this.close(); + this.input.value = ""; + this.#updateCallback(password); + this.#updateCallback = null; + } + async setUpdateCallback(updateCallback, reason) { + if (this.#activeCapability) { + await this.#activeCapability.promise; + } + this.#updateCallback = updateCallback; + this.#reason = reason; + } +} // ./web/base_tree_viewer.js + +const TREEITEM_OFFSET_TOP = -100; +const TREEITEM_SELECTED_CLASS = "selected"; +class BaseTreeViewer { + constructor(options) { + this.container = options.container; + this.eventBus = options.eventBus; + this._l10n = options.l10n; + this.reset(); + } + reset() { + this._pdfDocument = null; + this._lastToggleIsShow = true; + this._currentTreeItem = null; + this.container.textContent = ""; + this.container.classList.remove("treeWithDeepNesting"); + } + _dispatchEvent(count) { + throw new Error("Not implemented: _dispatchEvent"); + } + _bindLink(element, params) { + throw new Error("Not implemented: _bindLink"); + } + _normalizeTextContent(str) { + return removeNullCharacters(str, true) || "\u2013"; + } + _addToggleButton(div, hidden = false) { + const toggler = document.createElement("div"); + toggler.className = "treeItemToggler"; + if (hidden) { + toggler.classList.add("treeItemsHidden"); + } + toggler.onclick = (evt) => { + evt.stopPropagation(); + toggler.classList.toggle("treeItemsHidden"); + if (evt.shiftKey) { + const shouldShowAll = !toggler.classList.contains("treeItemsHidden"); + this._toggleTreeItem(div, shouldShowAll); + } + }; + div.prepend(toggler); + } + _toggleTreeItem(root, show = false) { + this._l10n.pause(); + this._lastToggleIsShow = show; + for (const toggler of root.querySelectorAll(".treeItemToggler")) { + toggler.classList.toggle("treeItemsHidden", !show); + } + this._l10n.resume(); + } + _toggleAllTreeItems() { + this._toggleTreeItem(this.container, !this._lastToggleIsShow); + } + _finishRendering(fragment, count, hasAnyNesting = false) { + if (hasAnyNesting) { + this.container.classList.add("treeWithDeepNesting"); + this._lastToggleIsShow = !fragment.querySelector(".treeItemsHidden"); + } + this._l10n.pause(); + this.container.append(fragment); + this._l10n.resume(); + this._dispatchEvent(count); + } + render(params) { + throw new Error("Not implemented: render"); + } + _updateCurrentTreeItem(treeItem = null) { + if (this._currentTreeItem) { + this._currentTreeItem.classList.remove(TREEITEM_SELECTED_CLASS); + this._currentTreeItem = null; + } + if (treeItem) { + treeItem.classList.add(TREEITEM_SELECTED_CLASS); + this._currentTreeItem = treeItem; + } + } + _scrollToCurrentTreeItem(treeItem) { + if (!treeItem) { + return; + } + this._l10n.pause(); + let currentNode = treeItem.parentNode; + while (currentNode && currentNode !== this.container) { + if (currentNode.classList.contains("treeItem")) { + const toggler = currentNode.firstElementChild; + toggler?.classList.remove("treeItemsHidden"); + } + currentNode = currentNode.parentNode; + } + this._l10n.resume(); + this._updateCurrentTreeItem(treeItem); + this.container.scrollTo( + treeItem.offsetLeft, + treeItem.offsetTop + TREEITEM_OFFSET_TOP, + ); + } +} // ./web/pdf_attachment_viewer.js + +class PDFAttachmentViewer extends BaseTreeViewer { + constructor(options) { + super(options); + this.downloadManager = options.downloadManager; + this.eventBus._on( + "fileattachmentannotation", + this.#appendAttachment.bind(this), + ); + } + reset(keepRenderedCapability = false) { + super.reset(); + this._attachments = null; + if (!keepRenderedCapability) { + this._renderedCapability = Promise.withResolvers(); + } + this._pendingDispatchEvent = false; + } + async _dispatchEvent(attachmentsCount) { + this._renderedCapability.resolve(); + if (attachmentsCount === 0 && !this._pendingDispatchEvent) { + this._pendingDispatchEvent = true; + await waitOnEventOrTimeout({ + target: this.eventBus, + name: "annotationlayerrendered", + delay: 1000, + }); + if (!this._pendingDispatchEvent) { + return; + } + } + this._pendingDispatchEvent = false; + this.eventBus.dispatch("attachmentsloaded", { + source: this, + attachmentsCount, + }); + } + _bindLink(element, { content, description, filename }) { + if (description) { + element.title = description; + } + element.onclick = () => { + this.downloadManager.openOrDownloadData(content, filename); + return false; + }; + } + render({ attachments, keepRenderedCapability = false }) { + if (this._attachments) { + this.reset(keepRenderedCapability); + } + this._attachments = attachments || null; + if (!attachments) { + this._dispatchEvent(0); + return; + } + const fragment = document.createDocumentFragment(); + let attachmentsCount = 0; + for (const name in attachments) { + const item = attachments[name]; + const div = document.createElement("div"); + div.className = "treeItem"; + const element = document.createElement("a"); + this._bindLink(element, item); + element.textContent = this._normalizeTextContent(item.filename); + div.append(element); + fragment.append(div); + attachmentsCount++; + } + this._finishRendering(fragment, attachmentsCount); + } + #appendAttachment(item) { + const renderedPromise = this._renderedCapability.promise; + renderedPromise.then(() => { + if (renderedPromise !== this._renderedCapability.promise) { + return; + } + const attachments = this._attachments || Object.create(null); + for (const name in attachments) { + if (item.filename === name) { + return; + } + } + attachments[item.filename] = item; + this.render({ + attachments, + keepRenderedCapability: true, + }); + }); + } +} // ./web/grab_to_pan.js + +const CSS_CLASS_GRAB = "grab-to-pan-grab"; +class GrabToPan { + #activateAC = null; + #mouseDownAC = null; + #scrollAC = null; + constructor({ element }) { + this.element = element; + this.document = element.ownerDocument; + const overlay = (this.overlay = document.createElement("div")); + overlay.className = "grab-to-pan-grabbing"; + } + activate() { + if (!this.#activateAC) { + this.#activateAC = new AbortController(); + this.element.addEventListener("mousedown", this.#onMouseDown.bind(this), { + capture: true, + signal: this.#activateAC.signal, + }); + this.element.classList.add(CSS_CLASS_GRAB); + } + } + deactivate() { + if (this.#activateAC) { + this.#activateAC.abort(); + this.#activateAC = null; + this.#endPan(); + this.element.classList.remove(CSS_CLASS_GRAB); + } + } + toggle() { + if (this.#activateAC) { + this.deactivate(); + } else { + this.activate(); + } + } + ignoreTarget(node) { + return node.matches( + "a[href], a[href] *, input, textarea, button, button *, select, option", + ); + } + #onMouseDown(event) { + if (event.button !== 0 || this.ignoreTarget(event.target)) { + return; + } + if (event.originalTarget) { + try { + event.originalTarget.tagName; + } catch { + return; + } + } + this.scrollLeftStart = this.element.scrollLeft; + this.scrollTopStart = this.element.scrollTop; + this.clientXStart = event.clientX; + this.clientYStart = event.clientY; + this.#mouseDownAC = new AbortController(); + const boundEndPan = this.#endPan.bind(this), + mouseOpts = { + capture: true, + signal: this.#mouseDownAC.signal, + }; + this.document.addEventListener( + "mousemove", + this.#onMouseMove.bind(this), + mouseOpts, + ); + this.document.addEventListener("mouseup", boundEndPan, mouseOpts); + this.#scrollAC = new AbortController(); + this.element.addEventListener("scroll", boundEndPan, { + capture: true, + signal: this.#scrollAC.signal, + }); + stopEvent(event); + const focusedElement = document.activeElement; + if (focusedElement && !focusedElement.contains(event.target)) { + focusedElement.blur(); + } + } + #onMouseMove(event) { + this.#scrollAC?.abort(); + this.#scrollAC = null; + if (!(event.buttons & 1)) { + this.#endPan(); + return; + } + const xDiff = event.clientX - this.clientXStart; + const yDiff = event.clientY - this.clientYStart; + this.element.scrollTo({ + top: this.scrollTopStart - yDiff, + left: this.scrollLeftStart - xDiff, + behavior: "instant", + }); + if (!this.overlay.parentNode) { + document.body.append(this.overlay); + } + } + #endPan() { + this.#mouseDownAC?.abort(); + this.#mouseDownAC = null; + this.#scrollAC?.abort(); + this.#scrollAC = null; + this.overlay.remove(); + } +} // ./web/pdf_cursor_tools.js + +class PDFCursorTools { + #active = CursorTool.SELECT; + #prevActive = null; + constructor({ container, eventBus, cursorToolOnLoad = CursorTool.SELECT }) { + this.container = container; + this.eventBus = eventBus; + this.#addEventListeners(); + Promise.resolve().then(() => { + this.switchTool(cursorToolOnLoad); + }); + } + get activeTool() { + return this.#active; + } + switchTool(tool) { + if (this.#prevActive !== null) { + return; + } + this.#switchTool(tool); + } + #switchTool(tool, disabled = false) { + if (tool === this.#active) { + if (this.#prevActive !== null) { + this.eventBus.dispatch("cursortoolchanged", { + source: this, + tool, + disabled, + }); + } + return; + } + const disableActiveTool = () => { + switch (this.#active) { + case CursorTool.SELECT: + break; + case CursorTool.HAND: + this._handTool.deactivate(); + break; + case CursorTool.ZOOM: + } + }; + switch (tool) { + case CursorTool.SELECT: + disableActiveTool(); + break; + case CursorTool.HAND: + disableActiveTool(); + this._handTool.activate(); + break; + case CursorTool.ZOOM: + default: + console.error(`switchTool: "${tool}" is an unsupported value.`); + return; + } + this.#active = tool; + this.eventBus.dispatch("cursortoolchanged", { + source: this, + tool, + disabled, + }); + } + #addEventListeners() { + this.eventBus._on("switchcursortool", (evt) => { + if (!evt.reset) { + this.switchTool(evt.tool); + } else if (this.#prevActive !== null) { + annotationEditorMode = AnnotationEditorType.NONE; + presentationModeState = PresentationModeState.NORMAL; + enableActive(); + } + }); + let annotationEditorMode = AnnotationEditorType.NONE, + presentationModeState = PresentationModeState.NORMAL; + const disableActive = () => { + this.#prevActive ??= this.#active; + this.#switchTool(CursorTool.SELECT, true); + }; + const enableActive = () => { + if ( + this.#prevActive !== null && + annotationEditorMode === AnnotationEditorType.NONE && + presentationModeState === PresentationModeState.NORMAL + ) { + this.#switchTool(this.#prevActive); + this.#prevActive = null; + } + }; + this.eventBus._on("annotationeditormodechanged", ({ mode }) => { + annotationEditorMode = mode; + if (mode === AnnotationEditorType.NONE) { + enableActive(); + } else { + disableActive(); + } + }); + this.eventBus._on("presentationmodechanged", ({ state }) => { + presentationModeState = state; + if (state === PresentationModeState.NORMAL) { + enableActive(); + } else if (state === PresentationModeState.FULLSCREEN) { + disableActive(); + } + }); + } + get _handTool() { + return shadow( + this, + "_handTool", + new GrabToPan({ + element: this.container, + }), + ); + } +} // ./web/pdf_document_properties.js + +const NON_METRIC_LOCALES = ["en-us", "en-lr", "my"]; +const US_PAGE_NAMES = { + "8.5x11": "pdfjs-document-properties-page-size-name-letter", + "8.5x14": "pdfjs-document-properties-page-size-name-legal", +}; +const METRIC_PAGE_NAMES = { + "297x420": "pdfjs-document-properties-page-size-name-a-three", + "210x297": "pdfjs-document-properties-page-size-name-a-four", +}; +function getPageName(size, isPortrait, pageNames) { + const width = isPortrait ? size.width : size.height; + const height = isPortrait ? size.height : size.width; + return pageNames[`${width}x${height}`]; +} +class PDFDocumentProperties { + #fieldData = null; + constructor( + { dialog, fields, closeButton }, + overlayManager, + eventBus, + l10n, + fileNameLookup, + ) { + this.dialog = dialog; + this.fields = fields; + this.overlayManager = overlayManager; + this.l10n = l10n; + this._fileNameLookup = fileNameLookup; + this.#reset(); + closeButton.addEventListener("click", this.close.bind(this)); + this.overlayManager.register(this.dialog); + eventBus._on("pagechanging", (evt) => { + this._currentPageNumber = evt.pageNumber; + }); + eventBus._on("rotationchanging", (evt) => { + this._pagesRotation = evt.pagesRotation; + }); + } + async open() { + await Promise.all([ + this.overlayManager.open(this.dialog), + this._dataAvailableCapability.promise, + ]); + const currentPageNumber = this._currentPageNumber; + const pagesRotation = this._pagesRotation; + if ( + this.#fieldData && + currentPageNumber === this.#fieldData._currentPageNumber && + pagesRotation === this.#fieldData._pagesRotation + ) { + this.#updateUI(); + return; + } + const [{ info, contentLength }, pdfPage] = await Promise.all([ + this.pdfDocument.getMetadata(), + this.pdfDocument.getPage(currentPageNumber), + ]); + const [ + fileName, + fileSize, + creationDate, + modificationDate, + pageSize, + isLinearized, + ] = await Promise.all([ + this._fileNameLookup(), + this.#parseFileSize(contentLength), + this.#parseDate(info.CreationDate), + this.#parseDate(info.ModDate), + this.#parsePageSize(getPageSizeInches(pdfPage), pagesRotation), + this.#parseLinearization(info.IsLinearized), + ]); + this.#fieldData = Object.freeze({ + fileName, + fileSize, + title: info.Title, + author: info.Author, + subject: info.Subject, + keywords: info.Keywords, + creationDate, + modificationDate, + creator: info.Creator, + producer: info.Producer, + version: info.PDFFormatVersion, + pageCount: this.pdfDocument.numPages, + pageSize, + linearized: isLinearized, + _currentPageNumber: currentPageNumber, + _pagesRotation: pagesRotation, + }); + this.#updateUI(); + const { length } = await this.pdfDocument.getDownloadInfo(); + if (contentLength === length) { + return; + } + const data = Object.assign(Object.create(null), this.#fieldData); + data.fileSize = await this.#parseFileSize(length); + this.#fieldData = Object.freeze(data); + this.#updateUI(); + } + async close() { + this.overlayManager.close(this.dialog); + } + setDocument(pdfDocument) { + if (this.pdfDocument) { + this.#reset(); + this.#updateUI(); + } + if (!pdfDocument) { + return; + } + this.pdfDocument = pdfDocument; + this._dataAvailableCapability.resolve(); + } + #reset() { + this.pdfDocument = null; + this.#fieldData = null; + this._dataAvailableCapability = Promise.withResolvers(); + this._currentPageNumber = 1; + this._pagesRotation = 0; + } + #updateUI() { + if (this.#fieldData && this.overlayManager.active !== this.dialog) { + return; + } + for (const id in this.fields) { + const content = this.#fieldData?.[id]; + this.fields[id].textContent = content || content === 0 ? content : "-"; + } + } + async #parseFileSize(b = 0) { + const kb = b / 1024, + mb = kb / 1024; + return kb + ? this.l10n.get( + mb >= 1 + ? "pdfjs-document-properties-size-mb" + : "pdfjs-document-properties-size-kb", + { + mb, + kb, + b, + }, + ) + : undefined; + } + async #parsePageSize(pageSizeInches, pagesRotation) { + if (!pageSizeInches) { + return undefined; + } + if (pagesRotation % 180 !== 0) { + pageSizeInches = { + width: pageSizeInches.height, + height: pageSizeInches.width, + }; + } + const isPortrait = isPortraitOrientation(pageSizeInches), + nonMetric = NON_METRIC_LOCALES.includes(this.l10n.getLanguage()); + let sizeInches = { + width: Math.round(pageSizeInches.width * 100) / 100, + height: Math.round(pageSizeInches.height * 100) / 100, + }; + let sizeMillimeters = { + width: Math.round(pageSizeInches.width * 25.4 * 10) / 10, + height: Math.round(pageSizeInches.height * 25.4 * 10) / 10, + }; + let nameId = + getPageName(sizeInches, isPortrait, US_PAGE_NAMES) || + getPageName(sizeMillimeters, isPortrait, METRIC_PAGE_NAMES); + if ( + !nameId && + !( + Number.isInteger(sizeMillimeters.width) && + Number.isInteger(sizeMillimeters.height) + ) + ) { + const exactMillimeters = { + width: pageSizeInches.width * 25.4, + height: pageSizeInches.height * 25.4, + }; + const intMillimeters = { + width: Math.round(sizeMillimeters.width), + height: Math.round(sizeMillimeters.height), + }; + if ( + Math.abs(exactMillimeters.width - intMillimeters.width) < 0.1 && + Math.abs(exactMillimeters.height - intMillimeters.height) < 0.1 + ) { + nameId = getPageName(intMillimeters, isPortrait, METRIC_PAGE_NAMES); + if (nameId) { + sizeInches = { + width: Math.round((intMillimeters.width / 25.4) * 100) / 100, + height: Math.round((intMillimeters.height / 25.4) * 100) / 100, + }; + sizeMillimeters = intMillimeters; + } + } + } + const [{ width, height }, unit, name, orientation] = await Promise.all([ + nonMetric ? sizeInches : sizeMillimeters, + this.l10n.get( + nonMetric + ? "pdfjs-document-properties-page-size-unit-inches" + : "pdfjs-document-properties-page-size-unit-millimeters", + ), + nameId && this.l10n.get(nameId), + this.l10n.get( + isPortrait + ? "pdfjs-document-properties-page-size-orientation-portrait" + : "pdfjs-document-properties-page-size-orientation-landscape", + ), + ]); + return this.l10n.get( + name + ? "pdfjs-document-properties-page-size-dimension-name-string" + : "pdfjs-document-properties-page-size-dimension-string", + { + width, + height, + unit, + name, + orientation, + }, + ); + } + async #parseDate(inputDate) { + const dateObj = PDFDateString.toDateObject(inputDate); + return dateObj + ? this.l10n.get("pdfjs-document-properties-date-time-string", { + dateObj: dateObj.valueOf(), + }) + : undefined; + } + #parseLinearization(isLinearized) { + return this.l10n.get( + isLinearized + ? "pdfjs-document-properties-linearized-yes" + : "pdfjs-document-properties-linearized-no", + ); + } +} // ./web/pdf_find_utils.js + +const CharacterType = { + SPACE: 0, + ALPHA_LETTER: 1, + PUNCT: 2, + HAN_LETTER: 3, + KATAKANA_LETTER: 4, + HIRAGANA_LETTER: 5, + HALFWIDTH_KATAKANA_LETTER: 6, + THAI_LETTER: 7, +}; +function isAlphabeticalScript(charCode) { + return charCode < 0x2e80; +} +function isAscii(charCode) { + return (charCode & 0xff80) === 0; +} +function isAsciiAlpha(charCode) { + return ( + (charCode >= 0x61 && charCode <= 0x7a) || + (charCode >= 0x41 && charCode <= 0x5a) + ); +} +function isAsciiDigit(charCode) { + return charCode >= 0x30 && charCode <= 0x39; +} +function isAsciiSpace(charCode) { + return ( + charCode === 0x20 || + charCode === 0x09 || + charCode === 0x0d || + charCode === 0x0a + ); +} +function isHan(charCode) { + return ( + (charCode >= 0x3400 && charCode <= 0x9fff) || + (charCode >= 0xf900 && charCode <= 0xfaff) + ); +} +function isKatakana(charCode) { + return charCode >= 0x30a0 && charCode <= 0x30ff; +} +function isHiragana(charCode) { + return charCode >= 0x3040 && charCode <= 0x309f; +} +function isHalfwidthKatakana(charCode) { + return charCode >= 0xff60 && charCode <= 0xff9f; +} +function isThai(charCode) { + return (charCode & 0xff80) === 0x0e00; +} +function getCharacterType(charCode) { + if (isAlphabeticalScript(charCode)) { + if (isAscii(charCode)) { + if (isAsciiSpace(charCode)) { + return CharacterType.SPACE; + } else if ( + isAsciiAlpha(charCode) || + isAsciiDigit(charCode) || + charCode === 0x5f + ) { + return CharacterType.ALPHA_LETTER; + } + return CharacterType.PUNCT; + } else if (isThai(charCode)) { + return CharacterType.THAI_LETTER; + } else if (charCode === 0xa0) { + return CharacterType.SPACE; + } + return CharacterType.ALPHA_LETTER; + } + if (isHan(charCode)) { + return CharacterType.HAN_LETTER; + } else if (isKatakana(charCode)) { + return CharacterType.KATAKANA_LETTER; + } else if (isHiragana(charCode)) { + return CharacterType.HIRAGANA_LETTER; + } else if (isHalfwidthKatakana(charCode)) { + return CharacterType.HALFWIDTH_KATAKANA_LETTER; + } + return CharacterType.ALPHA_LETTER; +} +let NormalizeWithNFKC; +function getNormalizeWithNFKC() { + NormalizeWithNFKC ||= ` ¨ª¯²-µ¸-º¼-¾IJ-ijĿ-ŀʼnſDŽ-njDZ-dzʰ-ʸ˘-ËË -ˤʹͺ;΄-΅·Ï-ϖϰ-ϲϴ-ϵϹևٵ-ٸक़-य़ড়-à§à§Ÿà¨³à¨¶à©™-ਜ਼ਫ਼ଡ଼-à­à¸³àº³à»œ-à»à¼Œà½ƒà½à½’བྷཛྷཀྵჼᴬ-ᴮᴰ-ᴺᴼ-áµáµ-ᵪᵸᶛ-ᶿẚ-ẛάέήίόύώΆ᾽-á¿á¿‰á¿‹á¿-á¿á¿“á¿›á¿-῟ΰΎ῭-`ΌΏ´-῾ - ‑‗․-… ″-‴‶-‷‼‾â‡-â‰â—âŸâ°-â±â´-₎â‚-ₜ₨℀-℃℅-ℇ℉-â„“â„•-â„–â„™-â„â„ -™ℤΩℨK-ℭℯ-ℱℳ-ℹ℻-⅀ⅅ-â…‰â…-ⅿ↉∬-∭∯-∰〈-〉①-⓪⨌⩴-⩶⫝̸ⱼ-ⱽⵯ⺟⻳⼀-⿕ 〶〸-〺゛-゜ゟヿㄱ-ㆎ㆒-㆟㈀-㈞㈠-㉇ã‰-㉾㊀-ã¿êšœ-êšê°êŸ²-ꟴꟸ-ꟹꭜ-ꭟꭩ豈-ï¨ï¨ï¨’凞-羽蘒諸逸-都飯-ï©­ï©°-龎ff-stﬓ-ﬗï¬ï¬Ÿ-זּטּ-לּמּנּ-ï­ï­ƒ-ï­„ï­†-ﮱﯓ-ï´½ïµ-ï¶ï¶’-ﷇﷰ-ï·¼ï¸-︙︰-﹄﹇-ï¹’ï¹”-﹦﹨-﹫ﹰ-ﹲﹴﹶ-ﻼï¼-하-ᅦᅧ-ï¿ï¿’-ᅲᅳ-ᅵ¢-₩`; + return NormalizeWithNFKC; +} // ./web/pdf_find_controller.js + +const FindState = { + FOUND: 0, + NOT_FOUND: 1, + WRAPPED: 2, + PENDING: 3, +}; +const FIND_TIMEOUT = 250; +const MATCH_SCROLL_OFFSET_TOP = -50; +const MATCH_SCROLL_OFFSET_LEFT = -400; +const CHARACTERS_TO_NORMALIZE = { + "\u2010": "-", + "\u2018": "'", + "\u2019": "'", + "\u201A": "'", + "\u201B": "'", + "\u201C": '"', + "\u201D": '"', + "\u201E": '"', + "\u201F": '"', + "\u00BC": "1/4", + "\u00BD": "1/2", + "\u00BE": "3/4", +}; +const DIACRITICS_EXCEPTION = new Set([ + 0x3099, 0x309a, 0x094d, 0x09cd, 0x0a4d, 0x0acd, 0x0b4d, 0x0bcd, 0x0c4d, + 0x0ccd, 0x0d3b, 0x0d3c, 0x0d4d, 0x0dca, 0x0e3a, 0x0eba, 0x0f84, 0x1039, + 0x103a, 0x1714, 0x1734, 0x17d2, 0x1a60, 0x1b44, 0x1baa, 0x1bab, 0x1bf2, + 0x1bf3, 0x2d7f, 0xa806, 0xa82c, 0xa8c4, 0xa953, 0xa9c0, 0xaaf6, 0xabed, + 0x0c56, 0x0f71, 0x0f72, 0x0f7a, 0x0f7b, 0x0f7c, 0x0f7d, 0x0f80, 0x0f74, +]); +let DIACRITICS_EXCEPTION_STR; +const DIACRITICS_REG_EXP = /\p{M}+/gu; +const SPECIAL_CHARS_REG_EXP = + /([.*+?^${}()|[\]\\])|(\p{P})|(\s+)|(\p{M})|(\p{L})/gu; +const NOT_DIACRITIC_FROM_END_REG_EXP = /([^\p{M}])\p{M}*$/u; +const NOT_DIACRITIC_FROM_START_REG_EXP = /^\p{M}*([^\p{M}])/u; +const SYLLABLES_REG_EXP = /[\uAC00-\uD7AF\uFA6C\uFACF-\uFAD1\uFAD5-\uFAD7]+/g; +const SYLLABLES_LENGTHS = new Map(); +const FIRST_CHAR_SYLLABLES_REG_EXP = + "[\\u1100-\\u1112\\ud7a4-\\ud7af\\ud84a\\ud84c\\ud850\\ud854\\ud857\\ud85f]"; +const NFKC_CHARS_TO_NORMALIZE = new Map(); +let noSyllablesRegExp = null; +let withSyllablesRegExp = null; +function normalize(text) { + const syllablePositions = []; + let m; + while ((m = SYLLABLES_REG_EXP.exec(text)) !== null) { + let { index } = m; + for (const char of m[0]) { + let len = SYLLABLES_LENGTHS.get(char); + if (!len) { + len = char.normalize("NFD").length; + SYLLABLES_LENGTHS.set(char, len); + } + syllablePositions.push([len, index++]); + } + } + let normalizationRegex; + if (syllablePositions.length === 0 && noSyllablesRegExp) { + normalizationRegex = noSyllablesRegExp; + } else if (syllablePositions.length > 0 && withSyllablesRegExp) { + normalizationRegex = withSyllablesRegExp; + } else { + const replace = Object.keys(CHARACTERS_TO_NORMALIZE).join(""); + const toNormalizeWithNFKC = getNormalizeWithNFKC(); + const CJK = "(?:\\p{Ideographic}|[\u3040-\u30FF])"; + const HKDiacritics = "(?:\u3099|\u309A)"; + const CompoundWord = "\\p{Ll}-\\n\\p{Lu}"; + const regexp = `([${replace}])|([${toNormalizeWithNFKC}])|(${HKDiacritics}\\n)|(\\p{M}+(?:-\\n)?)|(${CompoundWord})|(\\S-\\n)|(${CJK}\\n)|(\\n)`; + if (syllablePositions.length === 0) { + normalizationRegex = noSyllablesRegExp = new RegExp( + regexp + "|(\\u0000)", + "gum", + ); + } else { + normalizationRegex = withSyllablesRegExp = new RegExp( + regexp + `|(${FIRST_CHAR_SYLLABLES_REG_EXP})`, + "gum", + ); + } + } + const rawDiacriticsPositions = []; + while ((m = DIACRITICS_REG_EXP.exec(text)) !== null) { + rawDiacriticsPositions.push([m[0].length, m.index]); + } + let normalized = text.normalize("NFD"); + const positions = [0, 0]; + let rawDiacriticsIndex = 0; + let syllableIndex = 0; + let shift = 0; + let shiftOrigin = 0; + let eol = 0; + let hasDiacritics = false; + normalized = normalized.replace( + normalizationRegex, + (match, p1, p2, p3, p4, p5, p6, p7, p8, p9, i) => { + i -= shiftOrigin; + if (p1) { + const replacement = CHARACTERS_TO_NORMALIZE[p1]; + const jj = replacement.length; + for (let j = 1; j < jj; j++) { + positions.push(i - shift + j, shift - j); + } + shift -= jj - 1; + return replacement; + } + if (p2) { + let replacement = NFKC_CHARS_TO_NORMALIZE.get(p2); + if (!replacement) { + replacement = p2.normalize("NFKC"); + NFKC_CHARS_TO_NORMALIZE.set(p2, replacement); + } + const jj = replacement.length; + for (let j = 1; j < jj; j++) { + positions.push(i - shift + j, shift - j); + } + shift -= jj - 1; + return replacement; + } + if (p3) { + hasDiacritics = true; + if (i + eol === rawDiacriticsPositions[rawDiacriticsIndex]?.[1]) { + ++rawDiacriticsIndex; + } else { + positions.push(i - 1 - shift + 1, shift - 1); + shift -= 1; + shiftOrigin += 1; + } + positions.push(i - shift + 1, shift); + shiftOrigin += 1; + eol += 1; + return p3.charAt(0); + } + if (p4) { + const hasTrailingDashEOL = p4.endsWith("\n"); + const len = hasTrailingDashEOL ? p4.length - 2 : p4.length; + hasDiacritics = true; + let jj = len; + if (i + eol === rawDiacriticsPositions[rawDiacriticsIndex]?.[1]) { + jj -= rawDiacriticsPositions[rawDiacriticsIndex][0]; + ++rawDiacriticsIndex; + } + for (let j = 1; j <= jj; j++) { + positions.push(i - 1 - shift + j, shift - j); + } + shift -= jj; + shiftOrigin += jj; + if (hasTrailingDashEOL) { + i += len - 1; + positions.push(i - shift + 1, 1 + shift); + shift += 1; + shiftOrigin += 1; + eol += 1; + return p4.slice(0, len); + } + return p4; + } + if (p5) { + shiftOrigin += 1; + eol += 1; + return p5.replace("\n", ""); + } + if (p6) { + const len = p6.length - 2; + positions.push(i - shift + len, 1 + shift); + shift += 1; + shiftOrigin += 1; + eol += 1; + return p6.slice(0, -2); + } + if (p7) { + const len = p7.length - 1; + positions.push(i - shift + len, shift); + shiftOrigin += 1; + eol += 1; + return p7.slice(0, -1); + } + if (p8) { + positions.push(i - shift + 1, shift - 1); + shift -= 1; + shiftOrigin += 1; + eol += 1; + return " "; + } + if (i + eol === syllablePositions[syllableIndex]?.[1]) { + const newCharLen = syllablePositions[syllableIndex][0] - 1; + ++syllableIndex; + for (let j = 1; j <= newCharLen; j++) { + positions.push(i - (shift - j), shift - j); + } + shift -= newCharLen; + shiftOrigin += newCharLen; + } + return p9; + }, + ); + positions.push(normalized.length, shift); + const starts = new Uint32Array(positions.length >> 1); + const shifts = new Int32Array(positions.length >> 1); + for (let i = 0, ii = positions.length; i < ii; i += 2) { + starts[i >> 1] = positions[i]; + shifts[i >> 1] = positions[i + 1]; + } + return [normalized, [starts, shifts], hasDiacritics]; +} +function getOriginalIndex(diffs, pos, len) { + if (!diffs) { + return [pos, len]; + } + const [starts, shifts] = diffs; + const start = pos; + const end = pos + len - 1; + let i = binarySearchFirstItem(starts, (x) => x >= start); + if (starts[i] > start) { + --i; + } + let j = binarySearchFirstItem(starts, (x) => x >= end, i); + if (starts[j] > end) { + --j; + } + const oldStart = start + shifts[i]; + const oldEnd = end + shifts[j]; + const oldLen = oldEnd + 1 - oldStart; + return [oldStart, oldLen]; +} +class PDFFindController { + #state = null; + #updateMatchesCountOnProgress = true; + #visitedPagesCount = 0; + constructor({ linkService, eventBus, updateMatchesCountOnProgress = true }) { + this._linkService = linkService; + this._eventBus = eventBus; + this.#updateMatchesCountOnProgress = updateMatchesCountOnProgress; + this.onIsPageVisible = null; + this.#reset(); + eventBus._on("find", this.#onFind.bind(this)); + eventBus._on("findbarclose", this.#onFindBarClose.bind(this)); + } + get highlightMatches() { + return this._highlightMatches; + } + get pageMatches() { + return this._pageMatches; + } + get pageMatchesLength() { + return this._pageMatchesLength; + } + get selected() { + return this._selected; + } + get state() { + return this.#state; + } + setDocument(pdfDocument) { + if (this._pdfDocument) { + this.#reset(); + } + if (!pdfDocument) { + return; + } + this._pdfDocument = pdfDocument; + this._firstPageCapability.resolve(); + } + #onFind(state) { + if (!state) { + return; + } + const pdfDocument = this._pdfDocument; + const { type } = state; + if (this.#state === null || this.#shouldDirtyMatch(state)) { + this._dirtyMatch = true; + } + this.#state = state; + if (type !== "highlightallchange") { + this.#updateUIState(FindState.PENDING); + } + this._firstPageCapability.promise.then(() => { + if ( + !this._pdfDocument || + (pdfDocument && this._pdfDocument !== pdfDocument) + ) { + return; + } + this.#extractText(); + const findbarClosed = !this._highlightMatches; + const pendingTimeout = !!this._findTimeout; + if (this._findTimeout) { + clearTimeout(this._findTimeout); + this._findTimeout = null; + } + if (!type) { + this._findTimeout = setTimeout(() => { + this.#nextMatch(); + this._findTimeout = null; + }, FIND_TIMEOUT); + } else if (this._dirtyMatch) { + this.#nextMatch(); + } else if (type === "again") { + this.#nextMatch(); + if (findbarClosed && this.#state.highlightAll) { + this.#updateAllPages(); + } + } else if (type === "highlightallchange") { + if (pendingTimeout) { + this.#nextMatch(); + } else { + this._highlightMatches = true; + } + this.#updateAllPages(); + } else { + this.#nextMatch(); + } + }); + } + scrollMatchIntoView({ + element = null, + selectedLeft = 0, + pageIndex = -1, + matchIndex = -1, + }) { + if (!this._scrollMatches || !element) { + return; + } else if (matchIndex === -1 || matchIndex !== this._selected.matchIdx) { + return; + } else if (pageIndex === -1 || pageIndex !== this._selected.pageIdx) { + return; + } + this._scrollMatches = false; + const spot = { + top: MATCH_SCROLL_OFFSET_TOP, + left: selectedLeft + MATCH_SCROLL_OFFSET_LEFT, + }; + scrollIntoView(element, spot, true); + } + #reset() { + this._highlightMatches = false; + this._scrollMatches = false; + this._pdfDocument = null; + this._pageMatches = []; + this._pageMatchesLength = []; + this.#visitedPagesCount = 0; + this.#state = null; + this._selected = { + pageIdx: -1, + matchIdx: -1, + }; + this._offset = { + pageIdx: null, + matchIdx: null, + wrapped: false, + }; + this._extractTextPromises = []; + this._pageContents = []; + this._pageDiffs = []; + this._hasDiacritics = []; + this._matchesCountTotal = 0; + this._pagesToSearch = null; + this._pendingFindMatches = new Set(); + this._resumePageIdx = null; + this._dirtyMatch = false; + clearTimeout(this._findTimeout); + this._findTimeout = null; + this._firstPageCapability = Promise.withResolvers(); + } + get #query() { + const { query } = this.#state; + if (typeof query === "string") { + if (query !== this._rawQuery) { + this._rawQuery = query; + [this._normalizedQuery] = normalize(query); + } + return this._normalizedQuery; + } + return (query || []).filter((q) => !!q).map((q) => normalize(q)[0]); + } + #shouldDirtyMatch(state) { + const newQuery = state.query, + prevQuery = this.#state.query; + const newType = typeof newQuery, + prevType = typeof prevQuery; + if (newType !== prevType) { + return true; + } + if (newType === "string") { + if (newQuery !== prevQuery) { + return true; + } + } else if (JSON.stringify(newQuery) !== JSON.stringify(prevQuery)) { + return true; + } + switch (state.type) { + case "again": + const pageNumber = this._selected.pageIdx + 1; + const linkService = this._linkService; + return ( + pageNumber >= 1 && + pageNumber <= linkService.pagesCount && + pageNumber !== linkService.page && + !(this.onIsPageVisible?.(pageNumber) ?? true) + ); + case "highlightallchange": + return false; + } + return true; + } + #isEntireWord(content, startIdx, length) { + let match = content + .slice(0, startIdx) + .match(NOT_DIACRITIC_FROM_END_REG_EXP); + if (match) { + const first = content.charCodeAt(startIdx); + const limit = match[1].charCodeAt(0); + if (getCharacterType(first) === getCharacterType(limit)) { + return false; + } + } + match = content + .slice(startIdx + length) + .match(NOT_DIACRITIC_FROM_START_REG_EXP); + if (match) { + const last = content.charCodeAt(startIdx + length - 1); + const limit = match[1].charCodeAt(0); + if (getCharacterType(last) === getCharacterType(limit)) { + return false; + } + } + return true; + } + #convertToRegExpString(query, hasDiacritics) { + const { matchDiacritics } = this.#state; + let isUnicode = false; + query = query.replaceAll( + SPECIAL_CHARS_REG_EXP, + (match, p1, p2, p3, p4, p5) => { + if (p1) { + return `[ ]*\\${p1}[ ]*`; + } + if (p2) { + return `[ ]*${p2}[ ]*`; + } + if (p3) { + return "[ ]+"; + } + if (matchDiacritics) { + return p4 || p5; + } + if (p4) { + return DIACRITICS_EXCEPTION.has(p4.charCodeAt(0)) ? p4 : ""; + } + if (hasDiacritics) { + isUnicode = true; + return `${p5}\\p{M}*`; + } + return p5; + }, + ); + const trailingSpaces = "[ ]*"; + if (query.endsWith(trailingSpaces)) { + query = query.slice(0, query.length - trailingSpaces.length); + } + if (matchDiacritics) { + if (hasDiacritics) { + DIACRITICS_EXCEPTION_STR ||= String.fromCharCode( + ...DIACRITICS_EXCEPTION, + ); + isUnicode = true; + query = `${query}(?=[${DIACRITICS_EXCEPTION_STR}]|[^\\p{M}]|$)`; + } + } + return [isUnicode, query]; + } + #calculateMatch(pageIndex) { + const query = this.#query; + if (query.length === 0) { + return; + } + const pageContent = this._pageContents[pageIndex]; + const matcherResult = this.match(query, pageContent, pageIndex); + const matches = (this._pageMatches[pageIndex] = []); + const matchesLength = (this._pageMatchesLength[pageIndex] = []); + const diffs = this._pageDiffs[pageIndex]; + matcherResult?.forEach(({ index, length }) => { + const [matchPos, matchLen] = getOriginalIndex(diffs, index, length); + if (matchLen) { + matches.push(matchPos); + matchesLength.push(matchLen); + } + }); + if (this.#state.highlightAll) { + this.#updatePage(pageIndex); + } + if (this._resumePageIdx === pageIndex) { + this._resumePageIdx = null; + this.#nextPageMatch(); + } + const pageMatchesCount = matches.length; + this._matchesCountTotal += pageMatchesCount; + if (this.#updateMatchesCountOnProgress) { + if (pageMatchesCount > 0) { + this.#updateUIResultsCount(); + } + } else if (++this.#visitedPagesCount === this._linkService.pagesCount) { + this.#updateUIResultsCount(); + } + } + match(query, pageContent, pageIndex) { + const hasDiacritics = this._hasDiacritics[pageIndex]; + let isUnicode = false; + if (typeof query === "string") { + [isUnicode, query] = this.#convertToRegExpString(query, hasDiacritics); + } else { + query = query + .sort() + .reverse() + .map((q) => { + const [isUnicodePart, queryPart] = this.#convertToRegExpString( + q, + hasDiacritics, + ); + isUnicode ||= isUnicodePart; + return `(${queryPart})`; + }) + .join("|"); + } + if (!query) { + return undefined; + } + const { caseSensitive, entireWord } = this.#state; + const flags = `g${isUnicode ? "u" : ""}${caseSensitive ? "" : "i"}`; + query = new RegExp(query, flags); + const matches = []; + let match; + while ((match = query.exec(pageContent)) !== null) { + if ( + entireWord && + !this.#isEntireWord(pageContent, match.index, match[0].length) + ) { + continue; + } + matches.push({ + index: match.index, + length: match[0].length, + }); + } + return matches; + } + #extractText() { + if (this._extractTextPromises.length > 0) { + return; + } + let deferred = Promise.resolve(); + const textOptions = { + disableNormalization: true, + }; + for (let i = 0, ii = this._linkService.pagesCount; i < ii; i++) { + const { promise, resolve } = Promise.withResolvers(); + this._extractTextPromises[i] = promise; + deferred = deferred.then(() => { + return this._pdfDocument + .getPage(i + 1) + .then((pdfPage) => pdfPage.getTextContent(textOptions)) + .then( + (textContent) => { + const strBuf = []; + for (const textItem of textContent.items) { + strBuf.push(textItem.str); + if (textItem.hasEOL) { + strBuf.push("\n"); + } + } + [ + this._pageContents[i], + this._pageDiffs[i], + this._hasDiacritics[i], + ] = normalize(strBuf.join("")); + resolve(); + }, + (reason) => { + console.error( + `Unable to get text content for page ${i + 1}`, + reason, + ); + this._pageContents[i] = ""; + this._pageDiffs[i] = null; + this._hasDiacritics[i] = false; + resolve(); + }, + ); + }); + } + } + #updatePage(index) { + if (this._scrollMatches && this._selected.pageIdx === index) { + this._linkService.page = index + 1; + } + this._eventBus.dispatch("updatetextlayermatches", { + source: this, + pageIndex: index, + }); + } + #updateAllPages() { + this._eventBus.dispatch("updatetextlayermatches", { + source: this, + pageIndex: -1, + }); + } + #nextMatch() { + const previous = this.#state.findPrevious; + const currentPageIndex = this._linkService.page - 1; + const numPages = this._linkService.pagesCount; + this._highlightMatches = true; + if (this._dirtyMatch) { + this._dirtyMatch = false; + this._selected.pageIdx = this._selected.matchIdx = -1; + this._offset.pageIdx = currentPageIndex; + this._offset.matchIdx = null; + this._offset.wrapped = false; + this._resumePageIdx = null; + this._pageMatches.length = 0; + this._pageMatchesLength.length = 0; + this.#visitedPagesCount = 0; + this._matchesCountTotal = 0; + this.#updateAllPages(); + for (let i = 0; i < numPages; i++) { + if (this._pendingFindMatches.has(i)) { + continue; + } + this._pendingFindMatches.add(i); + this._extractTextPromises[i].then(() => { + this._pendingFindMatches.delete(i); + this.#calculateMatch(i); + }); + } + } + const query = this.#query; + if (query.length === 0) { + this.#updateUIState(FindState.FOUND); + return; + } + if (this._resumePageIdx) { + return; + } + const offset = this._offset; + this._pagesToSearch = numPages; + if (offset.matchIdx !== null) { + const numPageMatches = this._pageMatches[offset.pageIdx].length; + if ( + (!previous && offset.matchIdx + 1 < numPageMatches) || + (previous && offset.matchIdx > 0) + ) { + offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1; + this.#updateMatch(true); + return; + } + this.#advanceOffsetPage(previous); + } + this.#nextPageMatch(); + } + #matchesReady(matches) { + const offset = this._offset; + const numMatches = matches.length; + const previous = this.#state.findPrevious; + if (numMatches) { + offset.matchIdx = previous ? numMatches - 1 : 0; + this.#updateMatch(true); + return true; + } + this.#advanceOffsetPage(previous); + if (offset.wrapped) { + offset.matchIdx = null; + if (this._pagesToSearch < 0) { + this.#updateMatch(false); + return true; + } + } + return false; + } + #nextPageMatch() { + if (this._resumePageIdx !== null) { + console.error("There can only be one pending page."); + } + let matches = null; + do { + const pageIdx = this._offset.pageIdx; + matches = this._pageMatches[pageIdx]; + if (!matches) { + this._resumePageIdx = pageIdx; + break; + } + } while (!this.#matchesReady(matches)); + } + #advanceOffsetPage(previous) { + const offset = this._offset; + const numPages = this._linkService.pagesCount; + offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1; + offset.matchIdx = null; + this._pagesToSearch--; + if (offset.pageIdx >= numPages || offset.pageIdx < 0) { + offset.pageIdx = previous ? numPages - 1 : 0; + offset.wrapped = true; + } + } + #updateMatch(found = false) { + let state = FindState.NOT_FOUND; + const wrapped = this._offset.wrapped; + this._offset.wrapped = false; + if (found) { + const previousPage = this._selected.pageIdx; + this._selected.pageIdx = this._offset.pageIdx; + this._selected.matchIdx = this._offset.matchIdx; + state = wrapped ? FindState.WRAPPED : FindState.FOUND; + if (previousPage !== -1 && previousPage !== this._selected.pageIdx) { + this.#updatePage(previousPage); + } + } + this.#updateUIState(state, this.#state.findPrevious); + if (this._selected.pageIdx !== -1) { + this._scrollMatches = true; + this.#updatePage(this._selected.pageIdx); + } + } + #onFindBarClose(evt) { + const pdfDocument = this._pdfDocument; + this._firstPageCapability.promise.then(() => { + if ( + !this._pdfDocument || + (pdfDocument && this._pdfDocument !== pdfDocument) + ) { + return; + } + if (this._findTimeout) { + clearTimeout(this._findTimeout); + this._findTimeout = null; + } + if (this._resumePageIdx) { + this._resumePageIdx = null; + this._dirtyMatch = true; + } + this.#updateUIState(FindState.FOUND); + this._highlightMatches = false; + this.#updateAllPages(); + }); + } + #requestMatchesCount() { + const { pageIdx, matchIdx } = this._selected; + let current = 0, + total = this._matchesCountTotal; + if (matchIdx !== -1) { + for (let i = 0; i < pageIdx; i++) { + current += this._pageMatches[i]?.length || 0; + } + current += matchIdx + 1; + } + if (current < 1 || current > total) { + current = total = 0; + } + return { + current, + total, + }; + } + #updateUIResultsCount() { + this._eventBus.dispatch("updatefindmatchescount", { + source: this, + matchesCount: this.#requestMatchesCount(), + }); + } + #updateUIState(state, previous = false) { + if ( + !this.#updateMatchesCountOnProgress && + (this.#visitedPagesCount !== this._linkService.pagesCount || + state === FindState.PENDING) + ) { + return; + } + this._eventBus.dispatch("updatefindcontrolstate", { + source: this, + state, + previous, + entireWord: this.#state?.entireWord ?? null, + matchesCount: this.#requestMatchesCount(), + rawQuery: this.#state?.query ?? null, + }); + } +} // ./web/pdf_find_bar.js + +const MATCHES_COUNT_LIMIT = 1000; +class PDFFindBar { + #mainContainer; + #resizeObserver = new ResizeObserver(this.#resizeObserverCallback.bind(this)); + constructor(options, mainContainer, eventBus) { + this.opened = false; + this.bar = options.bar; + this.toggleButton = options.toggleButton; + this.findField = options.findField; + this.highlightAll = options.highlightAllCheckbox; + this.caseSensitive = options.caseSensitiveCheckbox; + this.matchDiacritics = options.matchDiacriticsCheckbox; + this.entireWord = options.entireWordCheckbox; + this.findMsg = options.findMsg; + this.findResultsCount = options.findResultsCount; + this.findPreviousButton = options.findPreviousButton; + this.findNextButton = options.findNextButton; + this.eventBus = eventBus; + this.#mainContainer = mainContainer; + const checkedInputs = new Map([ + [this.highlightAll, "highlightallchange"], + [this.caseSensitive, "casesensitivitychange"], + [this.entireWord, "entirewordchange"], + [this.matchDiacritics, "diacriticmatchingchange"], + ]); + this.toggleButton.addEventListener("click", () => { + this.toggle(); + }); + this.findField.addEventListener("input", () => { + this.dispatchEvent(""); + }); + this.bar.addEventListener("keydown", ({ keyCode, shiftKey, target }) => { + switch (keyCode) { + case 13: + if (target === this.findField) { + this.dispatchEvent("again", shiftKey); + } else if (checkedInputs.has(target)) { + target.checked = !target.checked; + this.dispatchEvent(checkedInputs.get(target)); + } + break; + case 27: + this.close(); + break; + } + }); + this.findPreviousButton.addEventListener("click", () => { + this.dispatchEvent("again", true); + }); + this.findNextButton.addEventListener("click", () => { + this.dispatchEvent("again", false); + }); + for (const [elem, evtName] of checkedInputs) { + elem.addEventListener("click", () => { + this.dispatchEvent(evtName); + }); + } + } + reset() { + this.updateUIState(); + } + dispatchEvent(type, findPrev = false) { + this.eventBus.dispatch("find", { + source: this, + type, + query: this.findField.value, + caseSensitive: this.caseSensitive.checked, + entireWord: this.entireWord.checked, + highlightAll: this.highlightAll.checked, + findPrevious: findPrev, + matchDiacritics: this.matchDiacritics.checked, + }); + } + updateUIState(state, previous, matchesCount) { + const { findField, findMsg } = this; + let findMsgId = "", + status = ""; + switch (state) { + case FindState.FOUND: + break; + case FindState.PENDING: + status = "pending"; + break; + case FindState.NOT_FOUND: + findMsgId = "pdfjs-find-not-found"; + status = "notFound"; + break; + case FindState.WRAPPED: + findMsgId = previous + ? "pdfjs-find-reached-top" + : "pdfjs-find-reached-bottom"; + break; + } + findField.setAttribute("data-status", status); + findField.setAttribute("aria-invalid", state === FindState.NOT_FOUND); + findMsg.setAttribute("data-status", status); + if (findMsgId) { + findMsg.setAttribute("data-l10n-id", findMsgId); + } else { + findMsg.removeAttribute("data-l10n-id"); + findMsg.textContent = ""; + } + this.updateResultsCount(matchesCount); + } + updateResultsCount({ current = 0, total = 0 } = {}) { + const { findResultsCount } = this; + if (total > 0) { + const limit = MATCHES_COUNT_LIMIT; + findResultsCount.setAttribute( + "data-l10n-id", + total > limit + ? "pdfjs-find-match-count-limit" + : "pdfjs-find-match-count", + ); + findResultsCount.setAttribute( + "data-l10n-args", + JSON.stringify({ + limit, + current, + total, + }), + ); + } else { + findResultsCount.removeAttribute("data-l10n-id"); + findResultsCount.textContent = ""; + } + } + open() { + if (!this.opened) { + this.#resizeObserver.observe(this.#mainContainer); + this.#resizeObserver.observe(this.bar); + this.opened = true; + toggleExpandedBtn(this.toggleButton, true, this.bar); + } + this.findField.select(); + this.findField.focus(); + } + close() { + if (!this.opened) { + return; + } + this.#resizeObserver.disconnect(); + this.opened = false; + toggleExpandedBtn(this.toggleButton, false, this.bar); + this.eventBus.dispatch("findbarclose", { + source: this, + }); + } + toggle() { + if (this.opened) { + this.close(); + } else { + this.open(); + } + } + #resizeObserverCallback() { + const { bar } = this; + bar.classList.remove("wrapContainers"); + const findbarHeight = bar.clientHeight; + const inputContainerHeight = bar.firstElementChild.clientHeight; + if (findbarHeight > inputContainerHeight) { + bar.classList.add("wrapContainers"); + } + } +} // ./web/pdf_history.js + +const HASH_CHANGE_TIMEOUT = 1000; +const POSITION_UPDATED_THRESHOLD = 50; +const UPDATE_VIEWAREA_TIMEOUT = 1000; +function getCurrentHash() { + return document.location.hash; +} +class PDFHistory { + #eventAbortController = null; + constructor({ linkService, eventBus }) { + this.linkService = linkService; + this.eventBus = eventBus; + this._initialized = false; + this._fingerprint = ""; + this.reset(); + this.eventBus._on("pagesinit", () => { + this._isPagesLoaded = false; + this.eventBus._on( + "pagesloaded", + (evt) => { + this._isPagesLoaded = !!evt.pagesCount; + }, + { + once: true, + }, + ); + }); + } + initialize({ fingerprint, resetHistory = false, updateUrl = false }) { + if (!fingerprint || typeof fingerprint !== "string") { + console.error( + 'PDFHistory.initialize: The "fingerprint" must be a non-empty string.', + ); + return; + } + if (this._initialized) { + this.reset(); + } + const reInitialized = + this._fingerprint !== "" && this._fingerprint !== fingerprint; + this._fingerprint = fingerprint; + this._updateUrl = updateUrl === true; + this._initialized = true; + this.#bindEvents(); + const state = window.history.state; + this._popStateInProgress = false; + this._blockHashChange = 0; + this._currentHash = getCurrentHash(); + this._numPositionUpdates = 0; + this._uid = this._maxUid = 0; + this._destination = null; + this._position = null; + if (!this.#isValidState(state, true) || resetHistory) { + const { hash, page, rotation } = this.#parseCurrentHash(true); + if (!hash || reInitialized || resetHistory) { + this.#pushOrReplaceState(null, true); + return; + } + this.#pushOrReplaceState( + { + hash, + page, + rotation, + }, + true, + ); + return; + } + const destination = state.destination; + this.#updateInternalState(destination, state.uid, true); + if (destination.rotation !== undefined) { + this._initialRotation = destination.rotation; + } + if (destination.dest) { + this._initialBookmark = JSON.stringify(destination.dest); + this._destination.page = null; + } else if (destination.hash) { + this._initialBookmark = destination.hash; + } else if (destination.page) { + this._initialBookmark = `page=${destination.page}`; + } + } + reset() { + if (this._initialized) { + this.#pageHide(); + this._initialized = false; + this.#unbindEvents(); + } + if (this._updateViewareaTimeout) { + clearTimeout(this._updateViewareaTimeout); + this._updateViewareaTimeout = null; + } + this._initialBookmark = null; + this._initialRotation = null; + } + push({ namedDest = null, explicitDest, pageNumber }) { + if (!this._initialized) { + return; + } + if (namedDest && typeof namedDest !== "string") { + console.error( + "PDFHistory.push: " + + `"${namedDest}" is not a valid namedDest parameter.`, + ); + return; + } else if (!Array.isArray(explicitDest)) { + console.error( + "PDFHistory.push: " + + `"${explicitDest}" is not a valid explicitDest parameter.`, + ); + return; + } else if (!this.#isValidPage(pageNumber)) { + if (pageNumber !== null || this._destination) { + console.error( + "PDFHistory.push: " + + `"${pageNumber}" is not a valid pageNumber parameter.`, + ); + return; + } + } + const hash = namedDest || JSON.stringify(explicitDest); + if (!hash) { + return; + } + let forceReplace = false; + if ( + this._destination && + (isDestHashesEqual(this._destination.hash, hash) || + isDestArraysEqual(this._destination.dest, explicitDest)) + ) { + if (this._destination.page) { + return; + } + forceReplace = true; + } + if (this._popStateInProgress && !forceReplace) { + return; + } + this.#pushOrReplaceState( + { + dest: explicitDest, + hash, + page: pageNumber, + rotation: this.linkService.rotation, + }, + forceReplace, + ); + if (!this._popStateInProgress) { + this._popStateInProgress = true; + Promise.resolve().then(() => { + this._popStateInProgress = false; + }); + } + } + pushPage(pageNumber) { + if (!this._initialized) { + return; + } + if (!this.#isValidPage(pageNumber)) { + console.error( + `PDFHistory.pushPage: "${pageNumber}" is not a valid page number.`, + ); + return; + } + if (this._destination?.page === pageNumber) { + return; + } + if (this._popStateInProgress) { + return; + } + this.#pushOrReplaceState({ + dest: null, + hash: `page=${pageNumber}`, + page: pageNumber, + rotation: this.linkService.rotation, + }); + if (!this._popStateInProgress) { + this._popStateInProgress = true; + Promise.resolve().then(() => { + this._popStateInProgress = false; + }); + } + } + pushCurrentPosition() { + if (!this._initialized || this._popStateInProgress) { + return; + } + this.#tryPushCurrentPosition(); + } + back() { + if (!this._initialized || this._popStateInProgress) { + return; + } + const state = window.history.state; + if (this.#isValidState(state) && state.uid > 0) { + window.history.back(); + } + } + forward() { + if (!this._initialized || this._popStateInProgress) { + return; + } + const state = window.history.state; + if (this.#isValidState(state) && state.uid < this._maxUid) { + window.history.forward(); + } + } + get popStateInProgress() { + return ( + this._initialized && + (this._popStateInProgress || this._blockHashChange > 0) + ); + } + get initialBookmark() { + return this._initialized ? this._initialBookmark : null; + } + get initialRotation() { + return this._initialized ? this._initialRotation : null; + } + #pushOrReplaceState(destination, forceReplace = false) { + const shouldReplace = forceReplace || !this._destination; + const newState = { + fingerprint: this._fingerprint, + uid: shouldReplace ? this._uid : this._uid + 1, + destination, + }; + this.#updateInternalState(destination, newState.uid); + let newUrl; + if (this._updateUrl && destination?.hash) { + const baseUrl = document.location.href.split("#", 1)[0]; + if (!baseUrl.startsWith("file://")) { + newUrl = `${baseUrl}#${destination.hash}`; + } + } + if (shouldReplace) { + window.history.replaceState(newState, "", newUrl); + } else { + window.history.pushState(newState, "", newUrl); + } + } + #tryPushCurrentPosition(temporary = false) { + if (!this._position) { + return; + } + let position = this._position; + if (temporary) { + position = Object.assign(Object.create(null), this._position); + position.temporary = true; + } + if (!this._destination) { + this.#pushOrReplaceState(position); + return; + } + if (this._destination.temporary) { + this.#pushOrReplaceState(position, true); + return; + } + if (this._destination.hash === position.hash) { + return; + } + if ( + !this._destination.page && + (POSITION_UPDATED_THRESHOLD <= 0 || + this._numPositionUpdates <= POSITION_UPDATED_THRESHOLD) + ) { + return; + } + let forceReplace = false; + if ( + this._destination.page >= position.first && + this._destination.page <= position.page + ) { + if (this._destination.dest !== undefined || !this._destination.first) { + return; + } + forceReplace = true; + } + this.#pushOrReplaceState(position, forceReplace); + } + #isValidPage(val) { + return ( + Number.isInteger(val) && val > 0 && val <= this.linkService.pagesCount + ); + } + #isValidState(state, checkReload = false) { + if (!state) { + return false; + } + if (state.fingerprint !== this._fingerprint) { + if (checkReload) { + if ( + typeof state.fingerprint !== "string" || + state.fingerprint.length !== this._fingerprint.length + ) { + return false; + } + const [perfEntry] = performance.getEntriesByType("navigation"); + if (perfEntry?.type !== "reload") { + return false; + } + } else { + return false; + } + } + if (!Number.isInteger(state.uid) || state.uid < 0) { + return false; + } + if (state.destination === null || typeof state.destination !== "object") { + return false; + } + return true; + } + #updateInternalState(destination, uid, removeTemporary = false) { + if (this._updateViewareaTimeout) { + clearTimeout(this._updateViewareaTimeout); + this._updateViewareaTimeout = null; + } + if (removeTemporary && destination?.temporary) { + delete destination.temporary; + } + this._destination = destination; + this._uid = uid; + this._maxUid = Math.max(this._maxUid, uid); + this._numPositionUpdates = 0; + } + #parseCurrentHash(checkNameddest = false) { + const hash = unescape(getCurrentHash()).substring(1); + const params = parseQueryString(hash); + const nameddest = params.get("nameddest") || ""; + let page = params.get("page") | 0; + if (!this.#isValidPage(page) || (checkNameddest && nameddest.length > 0)) { + page = null; + } + return { + hash, + page, + rotation: this.linkService.rotation, + }; + } + #updateViewarea({ location }) { + if (this._updateViewareaTimeout) { + clearTimeout(this._updateViewareaTimeout); + this._updateViewareaTimeout = null; + } + this._position = { + hash: location.pdfOpenParams.substring(1), + page: this.linkService.page, + first: location.pageNumber, + rotation: location.rotation, + }; + if (this._popStateInProgress) { + return; + } + if ( + POSITION_UPDATED_THRESHOLD > 0 && + this._isPagesLoaded && + this._destination && + !this._destination.page + ) { + this._numPositionUpdates++; + } + if (UPDATE_VIEWAREA_TIMEOUT > 0) { + this._updateViewareaTimeout = setTimeout(() => { + if (!this._popStateInProgress) { + this.#tryPushCurrentPosition(true); + } + this._updateViewareaTimeout = null; + }, UPDATE_VIEWAREA_TIMEOUT); + } + } + #popState({ state }) { + const newHash = getCurrentHash(), + hashChanged = this._currentHash !== newHash; + this._currentHash = newHash; + if (!state) { + this._uid++; + const { hash, page, rotation } = this.#parseCurrentHash(); + this.#pushOrReplaceState( + { + hash, + page, + rotation, + }, + true, + ); + return; + } + if (!this.#isValidState(state)) { + return; + } + this._popStateInProgress = true; + if (hashChanged) { + this._blockHashChange++; + waitOnEventOrTimeout({ + target: window, + name: "hashchange", + delay: HASH_CHANGE_TIMEOUT, + }).then(() => { + this._blockHashChange--; + }); + } + const destination = state.destination; + this.#updateInternalState(destination, state.uid, true); + if (isValidRotation(destination.rotation)) { + this.linkService.rotation = destination.rotation; + } + if (destination.dest) { + this.linkService.goToDestination(destination.dest); + } else if (destination.hash) { + this.linkService.setHash(destination.hash); + } else if (destination.page) { + this.linkService.page = destination.page; + } + Promise.resolve().then(() => { + this._popStateInProgress = false; + }); + } + #pageHide() { + if (!this._destination || this._destination.temporary) { + this.#tryPushCurrentPosition(); + } + } + #bindEvents() { + if (this.#eventAbortController) { + return; + } + this.#eventAbortController = new AbortController(); + const { signal } = this.#eventAbortController; + this.eventBus._on("updateviewarea", this.#updateViewarea.bind(this), { + signal, + }); + window.addEventListener("popstate", this.#popState.bind(this), { + signal, + }); + window.addEventListener("pagehide", this.#pageHide.bind(this), { + signal, + }); + } + #unbindEvents() { + this.#eventAbortController?.abort(); + this.#eventAbortController = null; + } +} +function isDestHashesEqual(destHash, pushHash) { + if (typeof destHash !== "string" || typeof pushHash !== "string") { + return false; + } + if (destHash === pushHash) { + return true; + } + const nameddest = parseQueryString(destHash).get("nameddest"); + if (nameddest === pushHash) { + return true; + } + return false; +} +function isDestArraysEqual(firstDest, secondDest) { + function isEntryEqual(first, second) { + if (typeof first !== typeof second) { + return false; + } + if (Array.isArray(first) || Array.isArray(second)) { + return false; + } + if (first !== null && typeof first === "object" && second !== null) { + if (Object.keys(first).length !== Object.keys(second).length) { + return false; + } + for (const key in first) { + if (!isEntryEqual(first[key], second[key])) { + return false; + } + } + return true; + } + return first === second || (Number.isNaN(first) && Number.isNaN(second)); + } + if (!(Array.isArray(firstDest) && Array.isArray(secondDest))) { + return false; + } + if (firstDest.length !== secondDest.length) { + return false; + } + for (let i = 0, ii = firstDest.length; i < ii; i++) { + if (!isEntryEqual(firstDest[i], secondDest[i])) { + return false; + } + } + return true; +} // ./web/pdf_layer_viewer.js + +class PDFLayerViewer extends BaseTreeViewer { + constructor(options) { + super(options); + this.eventBus._on("optionalcontentconfigchanged", (evt) => { + this.#updateLayers(evt.promise); + }); + this.eventBus._on("resetlayers", () => { + this.#updateLayers(); + }); + this.eventBus._on("togglelayerstree", this._toggleAllTreeItems.bind(this)); + } + reset() { + super.reset(); + this._optionalContentConfig = null; + this._optionalContentVisibility?.clear(); + this._optionalContentVisibility = null; + } + _dispatchEvent(layersCount) { + this.eventBus.dispatch("layersloaded", { + source: this, + layersCount, + }); + } + _bindLink(element, { groupId, input }) { + const setVisibility = () => { + const visible = input.checked; + this._optionalContentConfig.setVisibility(groupId, visible); + const cached = this._optionalContentVisibility.get(groupId); + if (cached) { + cached.visible = visible; + } + this.eventBus.dispatch("optionalcontentconfig", { + source: this, + promise: Promise.resolve(this._optionalContentConfig), + }); + }; + element.onclick = (evt) => { + if (evt.target === input) { + setVisibility(); + return true; + } else if (evt.target !== element) { + return true; + } + input.checked = !input.checked; + setVisibility(); + return false; + }; + } + _setNestedName(element, { name = null }) { + if (typeof name === "string") { + element.textContent = this._normalizeTextContent(name); + return; + } + element.setAttribute("data-l10n-id", "pdfjs-additional-layers"); + element.style.fontStyle = "italic"; + this._l10n.translateOnce(element); + } + _addToggleButton(div, { name = null }) { + super._addToggleButton(div, name === null); + } + _toggleAllTreeItems() { + if (!this._optionalContentConfig) { + return; + } + super._toggleAllTreeItems(); + } + render({ optionalContentConfig, pdfDocument }) { + if (this._optionalContentConfig) { + this.reset(); + } + this._optionalContentConfig = optionalContentConfig || null; + this._pdfDocument = pdfDocument || null; + const groups = optionalContentConfig?.getOrder(); + if (!groups) { + this._dispatchEvent(0); + return; + } + this._optionalContentVisibility = new Map(); + const fragment = document.createDocumentFragment(), + queue = [ + { + parent: fragment, + groups, + }, + ]; + let layersCount = 0, + hasAnyNesting = false; + while (queue.length > 0) { + const levelData = queue.shift(); + for (const groupId of levelData.groups) { + const div = document.createElement("div"); + div.className = "treeItem"; + const element = document.createElement("a"); + div.append(element); + if (typeof groupId === "object") { + hasAnyNesting = true; + this._addToggleButton(div, groupId); + this._setNestedName(element, groupId); + const itemsDiv = document.createElement("div"); + itemsDiv.className = "treeItems"; + div.append(itemsDiv); + queue.push({ + parent: itemsDiv, + groups: groupId.order, + }); + } else { + const group = optionalContentConfig.getGroup(groupId); + const input = document.createElement("input"); + this._bindLink(element, { + groupId, + input, + }); + input.type = "checkbox"; + input.checked = group.visible; + this._optionalContentVisibility.set(groupId, { + input, + visible: input.checked, + }); + const label = document.createElement("label"); + label.textContent = this._normalizeTextContent(group.name); + label.append(input); + element.append(label); + layersCount++; + } + levelData.parent.append(div); + } + } + this._finishRendering(fragment, layersCount, hasAnyNesting); + } + async #updateLayers(promise = null) { + if (!this._optionalContentConfig) { + return; + } + const pdfDocument = this._pdfDocument; + const optionalContentConfig = await (promise || + pdfDocument.getOptionalContentConfig({ + intent: "display", + })); + if (pdfDocument !== this._pdfDocument) { + return; + } + if (promise) { + for (const [groupId, cached] of this._optionalContentVisibility) { + const group = optionalContentConfig.getGroup(groupId); + if (group && cached.visible !== group.visible) { + cached.input.checked = cached.visible = !cached.visible; + } + } + return; + } + this.eventBus.dispatch("optionalcontentconfig", { + source: this, + promise: Promise.resolve(optionalContentConfig), + }); + this.render({ + optionalContentConfig, + pdfDocument: this._pdfDocument, + }); + } +} // ./web/pdf_outline_viewer.js + +class PDFOutlineViewer extends BaseTreeViewer { + constructor(options) { + super(options); + this.linkService = options.linkService; + this.downloadManager = options.downloadManager; + this.eventBus._on("toggleoutlinetree", this._toggleAllTreeItems.bind(this)); + this.eventBus._on( + "currentoutlineitem", + this._currentOutlineItem.bind(this), + ); + this.eventBus._on("pagechanging", (evt) => { + this._currentPageNumber = evt.pageNumber; + }); + this.eventBus._on("pagesloaded", (evt) => { + this._isPagesLoaded = !!evt.pagesCount; + this._currentOutlineItemCapability?.resolve(this._isPagesLoaded); + }); + this.eventBus._on("sidebarviewchanged", (evt) => { + this._sidebarView = evt.view; + }); + } + reset() { + super.reset(); + this._outline = null; + this._pageNumberToDestHashCapability = null; + this._currentPageNumber = 1; + this._isPagesLoaded = null; + this._currentOutlineItemCapability?.resolve(false); + this._currentOutlineItemCapability = null; + } + _dispatchEvent(outlineCount) { + this._currentOutlineItemCapability = Promise.withResolvers(); + if ( + outlineCount === 0 || + this._pdfDocument?.loadingParams.disableAutoFetch + ) { + this._currentOutlineItemCapability.resolve(false); + } else if (this._isPagesLoaded !== null) { + this._currentOutlineItemCapability.resolve(this._isPagesLoaded); + } + this.eventBus.dispatch("outlineloaded", { + source: this, + outlineCount, + currentOutlineItemPromise: this._currentOutlineItemCapability.promise, + }); + } + _bindLink( + element, + { url, newWindow, action, attachment, dest, setOCGState }, + ) { + const { linkService } = this; + if (url) { + linkService.addLinkAttributes(element, url, newWindow); + return; + } + if (action) { + element.href = linkService.getAnchorUrl(""); + element.onclick = () => { + linkService.executeNamedAction(action); + return false; + }; + return; + } + if (attachment) { + element.href = linkService.getAnchorUrl(""); + element.onclick = () => { + this.downloadManager.openOrDownloadData( + attachment.content, + attachment.filename, + ); + return false; + }; + return; + } + if (setOCGState) { + element.href = linkService.getAnchorUrl(""); + element.onclick = () => { + linkService.executeSetOCGState(setOCGState); + return false; + }; + return; + } + element.href = linkService.getDestinationHash(dest); + element.onclick = (evt) => { + this._updateCurrentTreeItem(evt.target.parentNode); + if (dest) { + linkService.goToDestination(dest); + } + return false; + }; + } + _setStyles(element, { bold, italic }) { + if (bold) { + element.style.fontWeight = "bold"; + } + if (italic) { + element.style.fontStyle = "italic"; + } + } + _addToggleButton(div, { count, items }) { + let hidden = false; + if (count < 0) { + let totalCount = items.length; + if (totalCount > 0) { + const queue = [...items]; + while (queue.length > 0) { + const { count: nestedCount, items: nestedItems } = queue.shift(); + if (nestedCount > 0 && nestedItems.length > 0) { + totalCount += nestedItems.length; + queue.push(...nestedItems); + } + } + } + if (Math.abs(count) === totalCount) { + hidden = true; + } + } + super._addToggleButton(div, hidden); + } + _toggleAllTreeItems() { + if (!this._outline) { + return; + } + super._toggleAllTreeItems(); + } + render({ outline, pdfDocument }) { + if (this._outline) { + this.reset(); + } + this._outline = outline || null; + this._pdfDocument = pdfDocument || null; + if (!outline) { + this._dispatchEvent(0); + return; + } + const fragment = document.createDocumentFragment(); + const queue = [ + { + parent: fragment, + items: outline, + }, + ]; + let outlineCount = 0, + hasAnyNesting = false; + while (queue.length > 0) { + const levelData = queue.shift(); + for (const item of levelData.items) { + const div = document.createElement("div"); + div.className = "treeItem"; + const element = document.createElement("a"); + this._bindLink(element, item); + this._setStyles(element, item); + element.textContent = this._normalizeTextContent(item.title); + div.append(element); + if (item.items.length > 0) { + hasAnyNesting = true; + this._addToggleButton(div, item); + const itemsDiv = document.createElement("div"); + itemsDiv.className = "treeItems"; + div.append(itemsDiv); + queue.push({ + parent: itemsDiv, + items: item.items, + }); + } + levelData.parent.append(div); + outlineCount++; + } + } + this._finishRendering(fragment, outlineCount, hasAnyNesting); + } + async _currentOutlineItem() { + if (!this._isPagesLoaded) { + throw new Error("_currentOutlineItem: All pages have not been loaded."); + } + if (!this._outline || !this._pdfDocument) { + return; + } + const pageNumberToDestHash = await this._getPageNumberToDestHash( + this._pdfDocument, + ); + if (!pageNumberToDestHash) { + return; + } + this._updateCurrentTreeItem(null); + if (this._sidebarView !== SidebarView.OUTLINE) { + return; + } + for (let i = this._currentPageNumber; i > 0; i--) { + const destHash = pageNumberToDestHash.get(i); + if (!destHash) { + continue; + } + const linkElement = this.container.querySelector(`a[href="${destHash}"]`); + if (!linkElement) { + continue; + } + this._scrollToCurrentTreeItem(linkElement.parentNode); + break; + } + } + async _getPageNumberToDestHash(pdfDocument) { + if (this._pageNumberToDestHashCapability) { + return this._pageNumberToDestHashCapability.promise; + } + this._pageNumberToDestHashCapability = Promise.withResolvers(); + const pageNumberToDestHash = new Map(), + pageNumberNesting = new Map(); + const queue = [ + { + nesting: 0, + items: this._outline, + }, + ]; + while (queue.length > 0) { + const levelData = queue.shift(), + currentNesting = levelData.nesting; + for (const { dest, items } of levelData.items) { + let explicitDest, pageNumber; + if (typeof dest === "string") { + explicitDest = await pdfDocument.getDestination(dest); + if (pdfDocument !== this._pdfDocument) { + return null; + } + } else { + explicitDest = dest; + } + if (Array.isArray(explicitDest)) { + const [destRef] = explicitDest; + if (destRef && typeof destRef === "object") { + pageNumber = pdfDocument.cachedPageNumber(destRef); + } else if (Number.isInteger(destRef)) { + pageNumber = destRef + 1; + } + if ( + Number.isInteger(pageNumber) && + (!pageNumberToDestHash.has(pageNumber) || + currentNesting > pageNumberNesting.get(pageNumber)) + ) { + const destHash = this.linkService.getDestinationHash(dest); + pageNumberToDestHash.set(pageNumber, destHash); + pageNumberNesting.set(pageNumber, currentNesting); + } + } + if (items.length > 0) { + queue.push({ + nesting: currentNesting + 1, + items, + }); + } + } + } + this._pageNumberToDestHashCapability.resolve( + pageNumberToDestHash.size > 0 ? pageNumberToDestHash : null, + ); + return this._pageNumberToDestHashCapability.promise; + } +} // ./web/pdf_presentation_mode.js + +const DELAY_BEFORE_HIDING_CONTROLS = 3000; +const ACTIVE_SELECTOR = "pdfPresentationMode"; +const CONTROLS_SELECTOR = "pdfPresentationModeControls"; +const MOUSE_SCROLL_COOLDOWN_TIME = 50; +const PAGE_SWITCH_THRESHOLD = 0.1; +const SWIPE_MIN_DISTANCE_THRESHOLD = 50; +const SWIPE_ANGLE_THRESHOLD = Math.PI / 6; +class PDFPresentationMode { + #state = PresentationModeState.UNKNOWN; + #args = null; + #fullscreenChangeAbortController = null; + #windowAbortController = null; + constructor({ container, pdfViewer, eventBus }) { + this.container = container; + this.pdfViewer = pdfViewer; + this.eventBus = eventBus; + this.contextMenuOpen = false; + this.mouseScrollTimeStamp = 0; + this.mouseScrollDelta = 0; + this.touchSwipeState = null; + } + async request() { + const { container, pdfViewer } = this; + if (this.active || !pdfViewer.pagesCount || !container.requestFullscreen) { + return false; + } + this.#addFullscreenChangeListeners(); + this.#notifyStateChange(PresentationModeState.CHANGING); + const promise = container.requestFullscreen(); + this.#args = { + pageNumber: pdfViewer.currentPageNumber, + scaleValue: pdfViewer.currentScaleValue, + scrollMode: pdfViewer.scrollMode, + spreadMode: null, + annotationEditorMode: null, + }; + if ( + pdfViewer.spreadMode !== SpreadMode.NONE && + !(pdfViewer.pageViewsReady && pdfViewer.hasEqualPageSizes) + ) { + console.warn( + "Ignoring Spread modes when entering PresentationMode, " + + "since the document may contain varying page sizes.", + ); + this.#args.spreadMode = pdfViewer.spreadMode; + } + if (pdfViewer.annotationEditorMode !== AnnotationEditorType.DISABLE) { + this.#args.annotationEditorMode = pdfViewer.annotationEditorMode; + } + try { + await promise; + pdfViewer.focus(); + return true; + } catch { + this.#removeFullscreenChangeListeners(); + this.#notifyStateChange(PresentationModeState.NORMAL); + } + return false; + } + get active() { + return ( + this.#state === PresentationModeState.CHANGING || + this.#state === PresentationModeState.FULLSCREEN + ); + } + #mouseWheel(evt) { + if (!this.active) { + return; + } + evt.preventDefault(); + const delta = normalizeWheelEventDelta(evt); + const currentTime = Date.now(); + const storedTime = this.mouseScrollTimeStamp; + if ( + currentTime > storedTime && + currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME + ) { + return; + } + if ( + (this.mouseScrollDelta > 0 && delta < 0) || + (this.mouseScrollDelta < 0 && delta > 0) + ) { + this.#resetMouseScrollState(); + } + this.mouseScrollDelta += delta; + if (Math.abs(this.mouseScrollDelta) >= PAGE_SWITCH_THRESHOLD) { + const totalDelta = this.mouseScrollDelta; + this.#resetMouseScrollState(); + const success = + totalDelta > 0 + ? this.pdfViewer.previousPage() + : this.pdfViewer.nextPage(); + if (success) { + this.mouseScrollTimeStamp = currentTime; + } + } + } + #notifyStateChange(state) { + this.#state = state; + this.eventBus.dispatch("presentationmodechanged", { + source: this, + state, + }); + } + #enter() { + this.#notifyStateChange(PresentationModeState.FULLSCREEN); + this.container.classList.add(ACTIVE_SELECTOR); + setTimeout(() => { + this.pdfViewer.scrollMode = ScrollMode.PAGE; + if (this.#args.spreadMode !== null) { + this.pdfViewer.spreadMode = SpreadMode.NONE; + } + this.pdfViewer.currentPageNumber = this.#args.pageNumber; + this.pdfViewer.currentScaleValue = "page-fit"; + if (this.#args.annotationEditorMode !== null) { + this.pdfViewer.annotationEditorMode = { + mode: AnnotationEditorType.NONE, + }; + } + }, 0); + this.#addWindowListeners(); + this.#showControls(); + this.contextMenuOpen = false; + document.getSelection().empty(); + } + #exit() { + const pageNumber = this.pdfViewer.currentPageNumber; + this.container.classList.remove(ACTIVE_SELECTOR); + setTimeout(() => { + this.#removeFullscreenChangeListeners(); + this.#notifyStateChange(PresentationModeState.NORMAL); + this.pdfViewer.scrollMode = this.#args.scrollMode; + if (this.#args.spreadMode !== null) { + this.pdfViewer.spreadMode = this.#args.spreadMode; + } + this.pdfViewer.currentScaleValue = this.#args.scaleValue; + this.pdfViewer.currentPageNumber = pageNumber; + if (this.#args.annotationEditorMode !== null) { + this.pdfViewer.annotationEditorMode = { + mode: this.#args.annotationEditorMode, + }; + } + this.#args = null; + }, 0); + this.#removeWindowListeners(); + this.#hideControls(); + this.#resetMouseScrollState(); + this.contextMenuOpen = false; + } + #mouseDown(evt) { + if (this.contextMenuOpen) { + this.contextMenuOpen = false; + evt.preventDefault(); + return; + } + if (evt.button !== 0) { + return; + } + if ( + evt.target.href && + evt.target.parentNode?.hasAttribute("data-internal-link") + ) { + return; + } + evt.preventDefault(); + if (evt.shiftKey) { + this.pdfViewer.previousPage(); + } else { + this.pdfViewer.nextPage(); + } + } + #contextMenu() { + this.contextMenuOpen = true; + } + #showControls() { + if (this.controlsTimeout) { + clearTimeout(this.controlsTimeout); + } else { + this.container.classList.add(CONTROLS_SELECTOR); + } + this.controlsTimeout = setTimeout(() => { + this.container.classList.remove(CONTROLS_SELECTOR); + delete this.controlsTimeout; + }, DELAY_BEFORE_HIDING_CONTROLS); + } + #hideControls() { + if (!this.controlsTimeout) { + return; + } + clearTimeout(this.controlsTimeout); + this.container.classList.remove(CONTROLS_SELECTOR); + delete this.controlsTimeout; + } + #resetMouseScrollState() { + this.mouseScrollTimeStamp = 0; + this.mouseScrollDelta = 0; + } + #touchSwipe(evt) { + if (!this.active) { + return; + } + if (evt.touches.length > 1) { + this.touchSwipeState = null; + return; + } + switch (evt.type) { + case "touchstart": + this.touchSwipeState = { + startX: evt.touches[0].pageX, + startY: evt.touches[0].pageY, + endX: evt.touches[0].pageX, + endY: evt.touches[0].pageY, + }; + break; + case "touchmove": + if (this.touchSwipeState === null) { + return; + } + this.touchSwipeState.endX = evt.touches[0].pageX; + this.touchSwipeState.endY = evt.touches[0].pageY; + evt.preventDefault(); + break; + case "touchend": + if (this.touchSwipeState === null) { + return; + } + let delta = 0; + const dx = this.touchSwipeState.endX - this.touchSwipeState.startX; + const dy = this.touchSwipeState.endY - this.touchSwipeState.startY; + const absAngle = Math.abs(Math.atan2(dy, dx)); + if ( + Math.abs(dx) > SWIPE_MIN_DISTANCE_THRESHOLD && + (absAngle <= SWIPE_ANGLE_THRESHOLD || + absAngle >= Math.PI - SWIPE_ANGLE_THRESHOLD) + ) { + delta = dx; + } else if ( + Math.abs(dy) > SWIPE_MIN_DISTANCE_THRESHOLD && + Math.abs(absAngle - Math.PI / 2) <= SWIPE_ANGLE_THRESHOLD + ) { + delta = dy; + } + if (delta > 0) { + this.pdfViewer.previousPage(); + } else if (delta < 0) { + this.pdfViewer.nextPage(); + } + break; + } + } + #addWindowListeners() { + if (this.#windowAbortController) { + return; + } + this.#windowAbortController = new AbortController(); + const { signal } = this.#windowAbortController; + const touchSwipeBind = this.#touchSwipe.bind(this); + window.addEventListener("mousemove", this.#showControls.bind(this), { + signal, + }); + window.addEventListener("mousedown", this.#mouseDown.bind(this), { + signal, + }); + window.addEventListener("wheel", this.#mouseWheel.bind(this), { + passive: false, + signal, + }); + window.addEventListener("keydown", this.#resetMouseScrollState.bind(this), { + signal, + }); + window.addEventListener("contextmenu", this.#contextMenu.bind(this), { + signal, + }); + window.addEventListener("touchstart", touchSwipeBind, { + signal, + }); + window.addEventListener("touchmove", touchSwipeBind, { + signal, + }); + window.addEventListener("touchend", touchSwipeBind, { + signal, + }); + } + #removeWindowListeners() { + this.#windowAbortController?.abort(); + this.#windowAbortController = null; + } + #addFullscreenChangeListeners() { + if (this.#fullscreenChangeAbortController) { + return; + } + this.#fullscreenChangeAbortController = new AbortController(); + window.addEventListener( + "fullscreenchange", + () => { + if (document.fullscreenElement) { + this.#enter(); + } else { + this.#exit(); + } + }, + { + signal: this.#fullscreenChangeAbortController.signal, + }, + ); + } + #removeFullscreenChangeListeners() { + this.#fullscreenChangeAbortController?.abort(); + this.#fullscreenChangeAbortController = null; + } +} // ./web/xfa_layer_builder.js + +class XfaLayerBuilder { + constructor({ + pdfPage, + annotationStorage = null, + linkService, + xfaHtml = null, + }) { + this.pdfPage = pdfPage; + this.annotationStorage = annotationStorage; + this.linkService = linkService; + this.xfaHtml = xfaHtml; + this.div = null; + this._cancelled = false; + } + async render(viewport, intent = "display") { + if (intent === "print") { + const parameters = { + viewport: viewport.clone({ + dontFlip: true, + }), + div: this.div, + xfaHtml: this.xfaHtml, + annotationStorage: this.annotationStorage, + linkService: this.linkService, + intent, + }; + this.div = document.createElement("div"); + parameters.div = this.div; + return XfaLayer.render(parameters); + } + const xfaHtml = await this.pdfPage.getXfa(); + if (this._cancelled || !xfaHtml) { + return { + textDivs: [], + }; + } + const parameters = { + viewport: viewport.clone({ + dontFlip: true, + }), + div: this.div, + xfaHtml, + annotationStorage: this.annotationStorage, + linkService: this.linkService, + intent, + }; + if (this.div) { + return XfaLayer.update(parameters); + } + this.div = document.createElement("div"); + parameters.div = this.div; + return XfaLayer.render(parameters); + } + cancel() { + this._cancelled = true; + } + hide() { + if (!this.div) { + return; + } + this.div.hidden = true; + } +} // ./web/print_utils.js + +function getXfaHtmlForPrinting(printContainer, pdfDocument) { + const xfaHtml = pdfDocument.allXfaHtml; + const linkService = new SimpleLinkService(); + const scale = Math.round(PixelsPerInch.PDF_TO_CSS_UNITS * 100) / 100; + for (const xfaPage of xfaHtml.children) { + const page = document.createElement("div"); + page.className = "xfaPrintedPage"; + printContainer.append(page); + const builder = new XfaLayerBuilder({ + pdfPage: null, + annotationStorage: pdfDocument.annotationStorage, + linkService, + xfaHtml: xfaPage, + }); + const viewport = getXfaPageViewport(xfaPage, { + scale, + }); + builder.render(viewport, "print"); + page.append(builder.div); + } +} // ./web/pdf_print_service.js + +let activeService = null; +let dialog = null; +let overlayManager = null; +let viewerApp = { + initialized: false, +}; +function renderPage( + activeServiceOnEntry, + pdfDocument, + pageNumber, + size, + printResolution, + optionalContentConfigPromise, + printAnnotationStoragePromise, +) { + const scratchCanvas = activeService.scratchCanvas; + const PRINT_UNITS = printResolution / PixelsPerInch.PDF; + scratchCanvas.width = Math.floor(size.width * PRINT_UNITS); + scratchCanvas.height = Math.floor(size.height * PRINT_UNITS); + const ctx = scratchCanvas.getContext("2d"); + ctx.save(); + ctx.fillStyle = "rgb(255, 255, 255)"; + ctx.fillRect(0, 0, scratchCanvas.width, scratchCanvas.height); + ctx.restore(); + return Promise.all([ + pdfDocument.getPage(pageNumber), + printAnnotationStoragePromise, + ]).then(function ([pdfPage, printAnnotationStorage]) { + const renderContext = { + canvasContext: ctx, + transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0], + viewport: pdfPage.getViewport({ + scale: 1, + rotation: size.rotation, + }), + intent: "print", + annotationMode: AnnotationMode.ENABLE_STORAGE, + optionalContentConfigPromise, + printAnnotationStorage, + }; + const renderTask = pdfPage.render(renderContext); + return renderTask.promise.catch((reason) => { + if (!(reason instanceof RenderingCancelledException)) { + console.error(reason); + } + throw reason; + }); + }); +} +class PDFPrintService { + constructor({ + pdfDocument, + pagesOverview, + printContainer, + printResolution, + printAnnotationStoragePromise = null, + }) { + this.pdfDocument = pdfDocument; + this.pagesOverview = pagesOverview; + this.printContainer = printContainer; + this._printResolution = printResolution || 150; + this._optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({ + intent: "print", + }); + this._printAnnotationStoragePromise = + printAnnotationStoragePromise || Promise.resolve(); + this.currentPage = -1; + this.scratchCanvas = document.createElement("canvas"); + } + layout() { + this.throwIfInactive(); + const body = document.querySelector("body"); + body.setAttribute("data-pdfjsprinting", true); + const { width, height } = this.pagesOverview[0]; + const hasEqualPageSizes = this.pagesOverview.every( + (size) => size.width === width && size.height === height, + ); + if (!hasEqualPageSizes) { + console.warn( + "Not all pages have the same size. The printed result may be incorrect!", + ); + } + this.pageStyleSheet = document.createElement("style"); + this.pageStyleSheet.textContent = `@page { size: ${width}pt ${height}pt;}`; + body.append(this.pageStyleSheet); + } + destroy() { + if (activeService !== this) { + return; + } + this.printContainer.textContent = ""; + const body = document.querySelector("body"); + body.removeAttribute("data-pdfjsprinting"); + if (this.pageStyleSheet) { + this.pageStyleSheet.remove(); + this.pageStyleSheet = null; + } + this.scratchCanvas.width = this.scratchCanvas.height = 0; + this.scratchCanvas = null; + activeService = null; + ensureOverlay().then(function () { + if (overlayManager.active === dialog) { + overlayManager.close(dialog); + } + }); + } + renderPages() { + if (this.pdfDocument.isPureXfa) { + getXfaHtmlForPrinting(this.printContainer, this.pdfDocument); + return Promise.resolve(); + } + const pageCount = this.pagesOverview.length; + const renderNextPage = (resolve, reject) => { + this.throwIfInactive(); + if (++this.currentPage >= pageCount) { + renderProgress(pageCount, pageCount); + resolve(); + return; + } + const index = this.currentPage; + renderProgress(index, pageCount); + renderPage( + this, + this.pdfDocument, + index + 1, + this.pagesOverview[index], + this._printResolution, + this._optionalContentConfigPromise, + this._printAnnotationStoragePromise, + ) + .then(this.useRenderedPage.bind(this)) + .then(function () { + renderNextPage(resolve, reject); + }, reject); + }; + return new Promise(renderNextPage); + } + useRenderedPage() { + this.throwIfInactive(); + const img = document.createElement("img"); + this.scratchCanvas.toBlob((blob) => { + img.src = URL.createObjectURL(blob); + }); + const wrapper = document.createElement("div"); + wrapper.className = "printedPage"; + wrapper.append(img); + this.printContainer.append(wrapper); + const { promise, resolve, reject } = Promise.withResolvers(); + img.onload = resolve; + img.onerror = reject; + promise + .catch(() => {}) + .then(() => { + URL.revokeObjectURL(img.src); + }); + return promise; + } + performPrint() { + this.throwIfInactive(); + return new Promise((resolve) => { + setTimeout(() => { + if (!this.active) { + resolve(); + return; + } + print.call(window); + setTimeout(resolve, 20); + }, 0); + }); + } + get active() { + return this === activeService; + } + throwIfInactive() { + if (!this.active) { + throw new Error("This print request was cancelled or completed."); + } + } +} +const print = window.print; +window.print = function () { + if (activeService) { + console.warn("Ignored window.print() because of a pending print job."); + return; + } + ensureOverlay().then(function () { + if (activeService) { + overlayManager.open(dialog); + } + }); + try { + dispatchEvent("beforeprint"); + } finally { + if (!activeService) { + console.error("Expected print service to be initialized."); + ensureOverlay().then(function () { + if (overlayManager.active === dialog) { + overlayManager.close(dialog); + } + }); + return; + } + const activeServiceOnEntry = activeService; + activeService + .renderPages() + .then(function () { + return activeServiceOnEntry.performPrint(); + }) + .catch(function () {}) + .then(function () { + if (activeServiceOnEntry.active) { + abort(); + } + }); + } +}; +function dispatchEvent(eventType) { + const event = new CustomEvent(eventType, { + bubbles: false, + cancelable: false, + detail: "custom", + }); + window.dispatchEvent(event); +} +function abort() { + if (activeService) { + activeService.destroy(); + dispatchEvent("afterprint"); + } +} +function renderProgress(index, total) { + dialog ||= document.getElementById("printServiceDialog"); + const progress = Math.round((100 * index) / total); + const progressBar = dialog.querySelector("progress"); + const progressPerc = dialog.querySelector(".relative-progress"); + progressBar.value = progress; + progressPerc.setAttribute( + "data-l10n-args", + JSON.stringify({ + progress, + }), + ); +} +window.addEventListener( + "keydown", + function (event) { + if ( + event.keyCode === 80 && + (event.ctrlKey || event.metaKey) && + !event.altKey && + (!event.shiftKey || window.chrome || window.opera) + ) { + window.print(); + event.preventDefault(); + event.stopImmediatePropagation(); + } + }, + true, +); +if ("onbeforeprint" in window) { + const stopPropagationIfNeeded = function (event) { + if (event.detail !== "custom") { + event.stopImmediatePropagation(); + } + }; + window.addEventListener("beforeprint", stopPropagationIfNeeded); + window.addEventListener("afterprint", stopPropagationIfNeeded); +} +let overlayPromise; +function ensureOverlay() { + if (!overlayPromise) { + overlayManager = viewerApp.overlayManager; + if (!overlayManager) { + throw new Error("The overlay manager has not yet been initialized."); + } + dialog ||= document.getElementById("printServiceDialog"); + overlayPromise = overlayManager.register(dialog, true); + document.getElementById("printCancel").onclick = abort; + dialog.addEventListener("close", abort); + } + return overlayPromise; +} +class PDFPrintServiceFactory { + static initGlobals(app) { + viewerApp = app; + } + static get supportsPrinting() { + return shadow(this, "supportsPrinting", true); + } + static createPrintService(params) { + if (activeService) { + throw new Error("The print service is created and active."); + } + return (activeService = new PDFPrintService(params)); + } +} // ./web/pdf_rendering_queue.js + +const CLEANUP_TIMEOUT = 30000; +class PDFRenderingQueue { + constructor() { + this.pdfViewer = null; + this.pdfThumbnailViewer = null; + this.onIdle = null; + this.highestPriorityPage = null; + this.idleTimeout = null; + this.printing = false; + this.isThumbnailViewEnabled = false; + Object.defineProperty(this, "hasViewer", { + value: () => !!this.pdfViewer, + }); + } + setViewer(pdfViewer) { + this.pdfViewer = pdfViewer; + } + setThumbnailViewer(pdfThumbnailViewer) { + this.pdfThumbnailViewer = pdfThumbnailViewer; + } + isHighestPriority(view) { + return this.highestPriorityPage === view.renderingId; + } + renderHighestPriority(currentlyVisiblePages) { + if (this.idleTimeout) { + clearTimeout(this.idleTimeout); + this.idleTimeout = null; + } + if (this.pdfViewer.forceRendering(currentlyVisiblePages)) { + return; + } + if ( + this.isThumbnailViewEnabled && + this.pdfThumbnailViewer?.forceRendering() + ) { + return; + } + if (this.printing) { + return; + } + if (this.onIdle) { + this.idleTimeout = setTimeout(this.onIdle.bind(this), CLEANUP_TIMEOUT); + } + } + getHighestPriority(visible, views, scrolledDown, preRenderExtra = false) { + const visibleViews = visible.views, + numVisible = visibleViews.length; + if (numVisible === 0) { + return null; + } + for (let i = 0; i < numVisible; i++) { + const view = visibleViews[i].view; + if (!this.isViewFinished(view)) { + return view; + } + } + const firstId = visible.first.id, + lastId = visible.last.id; + if (lastId - firstId + 1 > numVisible) { + const visibleIds = visible.ids; + for (let i = 1, ii = lastId - firstId; i < ii; i++) { + const holeId = scrolledDown ? firstId + i : lastId - i; + if (visibleIds.has(holeId)) { + continue; + } + const holeView = views[holeId - 1]; + if (!this.isViewFinished(holeView)) { + return holeView; + } + } + } + let preRenderIndex = scrolledDown ? lastId : firstId - 2; + let preRenderView = views[preRenderIndex]; + if (preRenderView && !this.isViewFinished(preRenderView)) { + return preRenderView; + } + if (preRenderExtra) { + preRenderIndex += scrolledDown ? 1 : -1; + preRenderView = views[preRenderIndex]; + if (preRenderView && !this.isViewFinished(preRenderView)) { + return preRenderView; + } + } + return null; + } + isViewFinished(view) { + return view.renderingState === RenderingStates.FINISHED; + } + renderView(view) { + switch (view.renderingState) { + case RenderingStates.FINISHED: + return false; + case RenderingStates.PAUSED: + this.highestPriorityPage = view.renderingId; + view.resume(); + break; + case RenderingStates.RUNNING: + this.highestPriorityPage = view.renderingId; + break; + case RenderingStates.INITIAL: + this.highestPriorityPage = view.renderingId; + view + .draw() + .finally(() => { + this.renderHighestPriority(); + }) + .catch((reason) => { + if (reason instanceof RenderingCancelledException) { + return; + } + console.error("renderView:", reason); + }); + break; + } + return true; + } +} // ./web/pdf_scripting_manager.js + +class PDFScriptingManager { + #closeCapability = null; + #destroyCapability = null; + #docProperties = null; + #eventAbortController = null; + #eventBus = null; + #externalServices = null; + #pdfDocument = null; + #pdfViewer = null; + #ready = false; + #scripting = null; + #willPrintCapability = null; + constructor({ eventBus, externalServices = null, docProperties = null }) { + this.#eventBus = eventBus; + this.#externalServices = externalServices; + this.#docProperties = docProperties; + } + setViewer(pdfViewer) { + this.#pdfViewer = pdfViewer; + } + async setDocument(pdfDocument) { + if (this.#pdfDocument) { + await this.#destroyScripting(); + } + this.#pdfDocument = pdfDocument; + if (!pdfDocument) { + return; + } + const [objects, calculationOrder, docActions] = await Promise.all([ + pdfDocument.getFieldObjects(), + pdfDocument.getCalculationOrderIds(), + pdfDocument.getJSActions(), + ]); + if (!objects && !docActions) { + await this.#destroyScripting(); + return; + } + if (pdfDocument !== this.#pdfDocument) { + return; + } + try { + this.#scripting = this.#initScripting(); + } catch (error) { + console.error("setDocument:", error); + await this.#destroyScripting(); + return; + } + const eventBus = this.#eventBus; + this.#eventAbortController = new AbortController(); + const { signal } = this.#eventAbortController; + eventBus._on( + "updatefromsandbox", + (event) => { + if (event?.source === window) { + this.#updateFromSandbox(event.detail); + } + }, + { + signal, + }, + ); + eventBus._on( + "dispatcheventinsandbox", + (event) => { + this.#scripting?.dispatchEventInSandbox(event.detail); + }, + { + signal, + }, + ); + eventBus._on( + "pagechanging", + ({ pageNumber, previous }) => { + if (pageNumber === previous) { + return; + } + this.#dispatchPageClose(previous); + this.#dispatchPageOpen(pageNumber); + }, + { + signal, + }, + ); + eventBus._on( + "pagerendered", + ({ pageNumber }) => { + if (!this._pageOpenPending.has(pageNumber)) { + return; + } + if (pageNumber !== this.#pdfViewer.currentPageNumber) { + return; + } + this.#dispatchPageOpen(pageNumber); + }, + { + signal, + }, + ); + eventBus._on( + "pagesdestroy", + async () => { + await this.#dispatchPageClose(this.#pdfViewer.currentPageNumber); + await this.#scripting?.dispatchEventInSandbox({ + id: "doc", + name: "WillClose", + }); + this.#closeCapability?.resolve(); + }, + { + signal, + }, + ); + try { + const docProperties = await this.#docProperties(pdfDocument); + if (pdfDocument !== this.#pdfDocument) { + return; + } + await this.#scripting.createSandbox({ + objects, + calculationOrder, + appInfo: { + platform: navigator.platform, + language: navigator.language, + }, + docInfo: { + ...docProperties, + actions: docActions, + }, + }); + eventBus.dispatch("sandboxcreated", { + source: this, + }); + } catch (error) { + console.error("setDocument:", error); + await this.#destroyScripting(); + return; + } + await this.#scripting?.dispatchEventInSandbox({ + id: "doc", + name: "Open", + }); + await this.#dispatchPageOpen(this.#pdfViewer.currentPageNumber, true); + Promise.resolve().then(() => { + if (pdfDocument === this.#pdfDocument) { + this.#ready = true; + } + }); + } + async dispatchWillSave() { + return this.#scripting?.dispatchEventInSandbox({ + id: "doc", + name: "WillSave", + }); + } + async dispatchDidSave() { + return this.#scripting?.dispatchEventInSandbox({ + id: "doc", + name: "DidSave", + }); + } + async dispatchWillPrint() { + if (!this.#scripting) { + return; + } + await this.#willPrintCapability?.promise; + this.#willPrintCapability = Promise.withResolvers(); + try { + await this.#scripting.dispatchEventInSandbox({ + id: "doc", + name: "WillPrint", + }); + } catch (ex) { + this.#willPrintCapability.resolve(); + this.#willPrintCapability = null; + throw ex; + } + await this.#willPrintCapability.promise; + } + async dispatchDidPrint() { + return this.#scripting?.dispatchEventInSandbox({ + id: "doc", + name: "DidPrint", + }); + } + get destroyPromise() { + return this.#destroyCapability?.promise || null; + } + get ready() { + return this.#ready; + } + get _pageOpenPending() { + return shadow(this, "_pageOpenPending", new Set()); + } + get _visitedPages() { + return shadow(this, "_visitedPages", new Map()); + } + async #updateFromSandbox(detail) { + const pdfViewer = this.#pdfViewer; + const isInPresentationMode = + pdfViewer.isInPresentationMode || pdfViewer.isChangingPresentationMode; + const { id, siblings, command, value } = detail; + if (!id) { + switch (command) { + case "clear": + console.clear(); + break; + case "error": + console.error(value); + break; + case "layout": + if (!isInPresentationMode) { + const modes = apiPageLayoutToViewerModes(value); + pdfViewer.spreadMode = modes.spreadMode; + } + break; + case "page-num": + pdfViewer.currentPageNumber = value + 1; + break; + case "print": + await pdfViewer.pagesPromise; + this.#eventBus.dispatch("print", { + source: this, + }); + break; + case "println": + console.log(value); + break; + case "zoom": + if (!isInPresentationMode) { + pdfViewer.currentScaleValue = value; + } + break; + case "SaveAs": + this.#eventBus.dispatch("download", { + source: this, + }); + break; + case "FirstPage": + pdfViewer.currentPageNumber = 1; + break; + case "LastPage": + pdfViewer.currentPageNumber = pdfViewer.pagesCount; + break; + case "NextPage": + pdfViewer.nextPage(); + break; + case "PrevPage": + pdfViewer.previousPage(); + break; + case "ZoomViewIn": + if (!isInPresentationMode) { + pdfViewer.increaseScale(); + } + break; + case "ZoomViewOut": + if (!isInPresentationMode) { + pdfViewer.decreaseScale(); + } + break; + case "WillPrintFinished": + this.#willPrintCapability?.resolve(); + this.#willPrintCapability = null; + break; + } + return; + } + if (isInPresentationMode && detail.focus) { + return; + } + delete detail.id; + delete detail.siblings; + const ids = siblings ? [id, ...siblings] : [id]; + for (const elementId of ids) { + const element = document.querySelector( + `[data-element-id="${elementId}"]`, + ); + if (element) { + element.dispatchEvent( + new CustomEvent("updatefromsandbox", { + detail, + }), + ); + } else { + this.#pdfDocument?.annotationStorage.setValue(elementId, detail); + } + } + } + async #dispatchPageOpen(pageNumber, initialize = false) { + const pdfDocument = this.#pdfDocument, + visitedPages = this._visitedPages; + if (initialize) { + this.#closeCapability = Promise.withResolvers(); + } + if (!this.#closeCapability) { + return; + } + const pageView = this.#pdfViewer.getPageView(pageNumber - 1); + if (pageView?.renderingState !== RenderingStates.FINISHED) { + this._pageOpenPending.add(pageNumber); + return; + } + this._pageOpenPending.delete(pageNumber); + const actionsPromise = (async () => { + const actions = await (!visitedPages.has(pageNumber) + ? pageView.pdfPage?.getJSActions() + : null); + if (pdfDocument !== this.#pdfDocument) { + return; + } + await this.#scripting?.dispatchEventInSandbox({ + id: "page", + name: "PageOpen", + pageNumber, + actions, + }); + })(); + visitedPages.set(pageNumber, actionsPromise); + } + async #dispatchPageClose(pageNumber) { + const pdfDocument = this.#pdfDocument, + visitedPages = this._visitedPages; + if (!this.#closeCapability) { + return; + } + if (this._pageOpenPending.has(pageNumber)) { + return; + } + const actionsPromise = visitedPages.get(pageNumber); + if (!actionsPromise) { + return; + } + visitedPages.set(pageNumber, null); + await actionsPromise; + if (pdfDocument !== this.#pdfDocument) { + return; + } + await this.#scripting?.dispatchEventInSandbox({ + id: "page", + name: "PageClose", + pageNumber, + }); + } + #initScripting() { + this.#destroyCapability = Promise.withResolvers(); + if (this.#scripting) { + throw new Error("#initScripting: Scripting already exists."); + } + return this.#externalServices.createScripting(); + } + async #destroyScripting() { + if (!this.#scripting) { + this.#pdfDocument = null; + this.#destroyCapability?.resolve(); + return; + } + if (this.#closeCapability) { + await Promise.race([ + this.#closeCapability.promise, + new Promise((resolve) => { + setTimeout(resolve, 1000); + }), + ]).catch(() => {}); + this.#closeCapability = null; + } + this.#pdfDocument = null; + try { + await this.#scripting.destroySandbox(); + } catch {} + this.#willPrintCapability?.reject(new Error("Scripting destroyed.")); + this.#willPrintCapability = null; + this.#eventAbortController?.abort(); + this.#eventAbortController = null; + this._pageOpenPending.clear(); + this._visitedPages.clear(); + this.#scripting = null; + this.#ready = false; + this.#destroyCapability?.resolve(); + } +} // ./web/pdf_sidebar.js + +const SIDEBAR_WIDTH_VAR = "--sidebar-width"; +const SIDEBAR_MIN_WIDTH = 200; +const SIDEBAR_RESIZING_CLASS = "sidebarResizing"; +const UI_NOTIFICATION_CLASS = "pdfSidebarNotification"; +class PDFSidebar { + #isRTL = false; + #mouseAC = null; + #outerContainerWidth = null; + #width = null; + constructor({ elements, eventBus, l10n }) { + this.isOpen = false; + this.active = SidebarView.THUMBS; + this.isInitialViewSet = false; + this.isInitialEventDispatched = false; + this.onToggled = null; + this.onUpdateThumbnails = null; + this.outerContainer = elements.outerContainer; + this.sidebarContainer = elements.sidebarContainer; + this.toggleButton = elements.toggleButton; + this.resizer = elements.resizer; + this.thumbnailButton = elements.thumbnailButton; + this.outlineButton = elements.outlineButton; + this.attachmentsButton = elements.attachmentsButton; + this.layersButton = elements.layersButton; + this.thumbnailView = elements.thumbnailView; + this.outlineView = elements.outlineView; + this.attachmentsView = elements.attachmentsView; + this.layersView = elements.layersView; + this._currentOutlineItemButton = elements.currentOutlineItemButton; + this.eventBus = eventBus; + this.#isRTL = l10n.getDirection() === "rtl"; + this.#addEventListeners(); + } + reset() { + this.isInitialViewSet = false; + this.isInitialEventDispatched = false; + this.#hideUINotification(true); + this.switchView(SidebarView.THUMBS); + this.outlineButton.disabled = false; + this.attachmentsButton.disabled = false; + this.layersButton.disabled = false; + this._currentOutlineItemButton.disabled = true; + } + get visibleView() { + return this.isOpen ? this.active : SidebarView.NONE; + } + setInitialView(view = SidebarView.NONE) { + if (this.isInitialViewSet) { + return; + } + this.isInitialViewSet = true; + if (view === SidebarView.NONE || view === SidebarView.UNKNOWN) { + this.#dispatchEvent(); + return; + } + this.switchView(view, true); + if (!this.isInitialEventDispatched) { + this.#dispatchEvent(); + } + } + switchView(view, forceOpen = false) { + const isViewChanged = view !== this.active; + let forceRendering = false; + switch (view) { + case SidebarView.NONE: + if (this.isOpen) { + this.close(); + } + return; + case SidebarView.THUMBS: + if (this.isOpen && isViewChanged) { + forceRendering = true; + } + break; + case SidebarView.OUTLINE: + if (this.outlineButton.disabled) { + return; + } + break; + case SidebarView.ATTACHMENTS: + if (this.attachmentsButton.disabled) { + return; + } + break; + case SidebarView.LAYERS: + if (this.layersButton.disabled) { + return; + } + break; + default: + console.error(`PDFSidebar.switchView: "${view}" is not a valid view.`); + return; + } + this.active = view; + toggleCheckedBtn( + this.thumbnailButton, + view === SidebarView.THUMBS, + this.thumbnailView, + ); + toggleCheckedBtn( + this.outlineButton, + view === SidebarView.OUTLINE, + this.outlineView, + ); + toggleCheckedBtn( + this.attachmentsButton, + view === SidebarView.ATTACHMENTS, + this.attachmentsView, + ); + toggleCheckedBtn( + this.layersButton, + view === SidebarView.LAYERS, + this.layersView, + ); + if (forceOpen && !this.isOpen) { + this.open(); + return; + } + if (forceRendering) { + this.onUpdateThumbnails(); + this.onToggled(); + } + if (isViewChanged) { + this.#dispatchEvent(); + } + } + open() { + if (this.isOpen) { + return; + } + this.isOpen = true; + toggleExpandedBtn(this.toggleButton, true); + this.outerContainer.classList.add("sidebarMoving", "sidebarOpen"); + if (this.active === SidebarView.THUMBS) { + this.onUpdateThumbnails(); + } + this.onToggled(); + this.#dispatchEvent(); + this.#hideUINotification(); + } + close(evt = null) { + if (!this.isOpen) { + return; + } + this.isOpen = false; + toggleExpandedBtn(this.toggleButton, false); + this.outerContainer.classList.add("sidebarMoving"); + this.outerContainer.classList.remove("sidebarOpen"); + this.onToggled(); + this.#dispatchEvent(); + if (evt?.detail > 0) { + this.toggleButton.blur(); + } + } + toggle(evt = null) { + if (this.isOpen) { + this.close(evt); + } else { + this.open(); + } + } + #dispatchEvent() { + if (this.isInitialViewSet) { + this.isInitialEventDispatched ||= true; + } + this.eventBus.dispatch("sidebarviewchanged", { + source: this, + view: this.visibleView, + }); + } + #showUINotification() { + this.toggleButton.setAttribute( + "data-l10n-id", + "pdfjs-toggle-sidebar-notification-button", + ); + if (!this.isOpen) { + this.toggleButton.classList.add(UI_NOTIFICATION_CLASS); + } + } + #hideUINotification(reset = false) { + if (this.isOpen || reset) { + this.toggleButton.classList.remove(UI_NOTIFICATION_CLASS); + } + if (reset) { + this.toggleButton.setAttribute( + "data-l10n-id", + "pdfjs-toggle-sidebar-button", + ); + } + } + #addEventListeners() { + const { eventBus, outerContainer } = this; + this.sidebarContainer.addEventListener("transitionend", (evt) => { + if (evt.target === this.sidebarContainer) { + outerContainer.classList.remove("sidebarMoving"); + eventBus.dispatch("resize", { + source: this, + }); + } + }); + this.toggleButton.addEventListener("click", (evt) => { + this.toggle(evt); + }); + this.thumbnailButton.addEventListener("click", () => { + this.switchView(SidebarView.THUMBS); + }); + this.outlineButton.addEventListener("click", () => { + this.switchView(SidebarView.OUTLINE); + }); + this.outlineButton.addEventListener("dblclick", () => { + eventBus.dispatch("toggleoutlinetree", { + source: this, + }); + }); + this.attachmentsButton.addEventListener("click", () => { + this.switchView(SidebarView.ATTACHMENTS); + }); + this.layersButton.addEventListener("click", () => { + this.switchView(SidebarView.LAYERS); + }); + this.layersButton.addEventListener("dblclick", () => { + eventBus.dispatch("resetlayers", { + source: this, + }); + }); + this._currentOutlineItemButton.addEventListener("click", () => { + eventBus.dispatch("currentoutlineitem", { + source: this, + }); + }); + const onTreeLoaded = (count, button, view) => { + button.disabled = !count; + if (count) { + this.#showUINotification(); + } else if (this.active === view) { + this.switchView(SidebarView.THUMBS); + } + }; + eventBus._on("outlineloaded", (evt) => { + onTreeLoaded(evt.outlineCount, this.outlineButton, SidebarView.OUTLINE); + evt.currentOutlineItemPromise.then((enabled) => { + if (!this.isInitialViewSet) { + return; + } + this._currentOutlineItemButton.disabled = !enabled; + }); + }); + eventBus._on("attachmentsloaded", (evt) => { + onTreeLoaded( + evt.attachmentsCount, + this.attachmentsButton, + SidebarView.ATTACHMENTS, + ); + }); + eventBus._on("layersloaded", (evt) => { + onTreeLoaded(evt.layersCount, this.layersButton, SidebarView.LAYERS); + }); + eventBus._on("presentationmodechanged", (evt) => { + if ( + evt.state === PresentationModeState.NORMAL && + this.visibleView === SidebarView.THUMBS + ) { + this.onUpdateThumbnails(); + } + }); + this.resizer.addEventListener("mousedown", (evt) => { + if (evt.button !== 0) { + return; + } + outerContainer.classList.add(SIDEBAR_RESIZING_CLASS); + this.#mouseAC = new AbortController(); + const opts = { + signal: this.#mouseAC.signal, + }; + window.addEventListener("mousemove", this.#mouseMove.bind(this), opts); + window.addEventListener("mouseup", this.#mouseUp.bind(this), opts); + window.addEventListener("blur", this.#mouseUp.bind(this), opts); + }); + eventBus._on("resize", (evt) => { + if (evt.source !== window) { + return; + } + this.#outerContainerWidth = null; + if (!this.#width) { + return; + } + if (!this.isOpen) { + this.#updateWidth(this.#width); + return; + } + outerContainer.classList.add(SIDEBAR_RESIZING_CLASS); + const updated = this.#updateWidth(this.#width); + Promise.resolve().then(() => { + outerContainer.classList.remove(SIDEBAR_RESIZING_CLASS); + if (updated) { + eventBus.dispatch("resize", { + source: this, + }); + } + }); + }); + } + get outerContainerWidth() { + return (this.#outerContainerWidth ||= this.outerContainer.clientWidth); + } + #updateWidth(width = 0) { + const maxWidth = Math.floor(this.outerContainerWidth / 2); + if (width > maxWidth) { + width = maxWidth; + } + if (width < SIDEBAR_MIN_WIDTH) { + width = SIDEBAR_MIN_WIDTH; + } + if (width === this.#width) { + return false; + } + this.#width = width; + docStyle.setProperty(SIDEBAR_WIDTH_VAR, `${width}px`); + return true; + } + #mouseMove(evt) { + let width = evt.clientX; + if (this.#isRTL) { + width = this.outerContainerWidth - width; + } + this.#updateWidth(width); + } + #mouseUp(evt) { + this.outerContainer.classList.remove(SIDEBAR_RESIZING_CLASS); + this.eventBus.dispatch("resize", { + source: this, + }); + this.#mouseAC?.abort(); + this.#mouseAC = null; + } +} // ./web/pdf_thumbnail_view.js + +const DRAW_UPSCALE_FACTOR = 2; +const MAX_NUM_SCALING_STEPS = 3; +const THUMBNAIL_WIDTH = 98; +class TempImageFactory { + static #tempCanvas = null; + static getCanvas(width, height) { + const tempCanvas = (this.#tempCanvas ||= document.createElement("canvas")); + tempCanvas.width = width; + tempCanvas.height = height; + const ctx = tempCanvas.getContext("2d", { + alpha: false, + }); + ctx.save(); + ctx.fillStyle = "rgb(255, 255, 255)"; + ctx.fillRect(0, 0, width, height); + ctx.restore(); + return [tempCanvas, tempCanvas.getContext("2d")]; + } + static destroyCanvas() { + const tempCanvas = this.#tempCanvas; + if (tempCanvas) { + tempCanvas.width = 0; + tempCanvas.height = 0; + } + this.#tempCanvas = null; + } +} +class PDFThumbnailView { + constructor({ + container, + eventBus, + id, + defaultViewport, + optionalContentConfigPromise, + linkService, + renderingQueue, + pageColors, + enableHWA, + }) { + this.id = id; + this.renderingId = "thumbnail" + id; + this.pageLabel = null; + this.pdfPage = null; + this.rotation = 0; + this.viewport = defaultViewport; + this.pdfPageRotate = defaultViewport.rotation; + this._optionalContentConfigPromise = optionalContentConfigPromise || null; + this.pageColors = pageColors || null; + this.enableHWA = enableHWA || false; + this.eventBus = eventBus; + this.linkService = linkService; + this.renderingQueue = renderingQueue; + this.renderTask = null; + this.renderingState = RenderingStates.INITIAL; + this.resume = null; + const anchor = document.createElement("a"); + anchor.href = linkService.getAnchorUrl("#page=" + id); + anchor.setAttribute("data-l10n-id", "pdfjs-thumb-page-title"); + anchor.setAttribute("data-l10n-args", this.#pageL10nArgs); + anchor.onclick = function () { + linkService.goToPage(id); + return false; + }; + this.anchor = anchor; + const div = document.createElement("div"); + div.className = "thumbnail"; + div.setAttribute("data-page-number", this.id); + this.div = div; + this.#updateDims(); + const img = document.createElement("div"); + img.className = "thumbnailImage"; + this._placeholderImg = img; + div.append(img); + anchor.append(div); + container.append(anchor); + } + #updateDims() { + const { width, height } = this.viewport; + const ratio = width / height; + this.canvasWidth = THUMBNAIL_WIDTH; + this.canvasHeight = (this.canvasWidth / ratio) | 0; + this.scale = this.canvasWidth / width; + const { style } = this.div; + style.setProperty("--thumbnail-width", `${this.canvasWidth}px`); + style.setProperty("--thumbnail-height", `${this.canvasHeight}px`); + } + setPdfPage(pdfPage) { + this.pdfPage = pdfPage; + this.pdfPageRotate = pdfPage.rotate; + const totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = pdfPage.getViewport({ + scale: 1, + rotation: totalRotation, + }); + this.reset(); + } + reset() { + this.cancelRendering(); + this.renderingState = RenderingStates.INITIAL; + this.div.removeAttribute("data-loaded"); + this.image?.replaceWith(this._placeholderImg); + this.#updateDims(); + if (this.image) { + this.image.removeAttribute("src"); + delete this.image; + } + } + update({ rotation = null }) { + if (typeof rotation === "number") { + this.rotation = rotation; + } + const totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = this.viewport.clone({ + scale: 1, + rotation: totalRotation, + }); + this.reset(); + } + cancelRendering() { + if (this.renderTask) { + this.renderTask.cancel(); + this.renderTask = null; + } + this.resume = null; + } + #getPageDrawContext(upscaleFactor = 1, enableHWA = this.enableHWA) { + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d", { + alpha: false, + willReadFrequently: !enableHWA, + }); + const outputScale = new OutputScale(); + canvas.width = (upscaleFactor * this.canvasWidth * outputScale.sx) | 0; + canvas.height = (upscaleFactor * this.canvasHeight * outputScale.sy) | 0; + const transform = outputScale.scaled + ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0] + : null; + return { + ctx, + canvas, + transform, + }; + } + #convertCanvasToImage(canvas) { + if (this.renderingState !== RenderingStates.FINISHED) { + throw new Error("#convertCanvasToImage: Rendering has not finished."); + } + const reducedCanvas = this.#reduceImage(canvas); + const image = document.createElement("img"); + image.className = "thumbnailImage"; + image.setAttribute("data-l10n-id", "pdfjs-thumb-page-canvas"); + image.setAttribute("data-l10n-args", this.#pageL10nArgs); + image.src = reducedCanvas.toDataURL(); + this.image = image; + this.div.setAttribute("data-loaded", true); + this._placeholderImg.replaceWith(image); + reducedCanvas.width = 0; + reducedCanvas.height = 0; + } + async #finishRenderTask(renderTask, canvas, error = null) { + if (renderTask === this.renderTask) { + this.renderTask = null; + } + if (error instanceof RenderingCancelledException) { + return; + } + this.renderingState = RenderingStates.FINISHED; + this.#convertCanvasToImage(canvas); + if (error) { + throw error; + } + } + async draw() { + if (this.renderingState !== RenderingStates.INITIAL) { + console.error("Must be in new state before drawing"); + return undefined; + } + const { pdfPage } = this; + if (!pdfPage) { + this.renderingState = RenderingStates.FINISHED; + throw new Error("pdfPage is not loaded"); + } + this.renderingState = RenderingStates.RUNNING; + const { ctx, canvas, transform } = + this.#getPageDrawContext(DRAW_UPSCALE_FACTOR); + const drawViewport = this.viewport.clone({ + scale: DRAW_UPSCALE_FACTOR * this.scale, + }); + const renderContinueCallback = (cont) => { + if (!this.renderingQueue.isHighestPriority(this)) { + this.renderingState = RenderingStates.PAUSED; + this.resume = () => { + this.renderingState = RenderingStates.RUNNING; + cont(); + }; + return; + } + cont(); + }; + const renderContext = { + canvasContext: ctx, + transform, + viewport: drawViewport, + optionalContentConfigPromise: this._optionalContentConfigPromise, + pageColors: this.pageColors, + }; + const renderTask = (this.renderTask = pdfPage.render(renderContext)); + renderTask.onContinue = renderContinueCallback; + const resultPromise = renderTask.promise.then( + () => this.#finishRenderTask(renderTask, canvas), + (error) => this.#finishRenderTask(renderTask, canvas, error), + ); + resultPromise.finally(() => { + canvas.width = 0; + canvas.height = 0; + this.eventBus.dispatch("thumbnailrendered", { + source: this, + pageNumber: this.id, + pdfPage: this.pdfPage, + }); + }); + return resultPromise; + } + setImage(pageView) { + if (this.renderingState !== RenderingStates.INITIAL) { + return; + } + const { thumbnailCanvas: canvas, pdfPage, scale } = pageView; + if (!canvas) { + return; + } + if (!this.pdfPage) { + this.setPdfPage(pdfPage); + } + if (scale < this.scale) { + return; + } + this.renderingState = RenderingStates.FINISHED; + this.#convertCanvasToImage(canvas); + } + #reduceImage(img) { + const { ctx, canvas } = this.#getPageDrawContext(1, true); + if (img.width <= 2 * canvas.width) { + ctx.drawImage( + img, + 0, + 0, + img.width, + img.height, + 0, + 0, + canvas.width, + canvas.height, + ); + return canvas; + } + let reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS; + let reducedHeight = canvas.height << MAX_NUM_SCALING_STEPS; + const [reducedImage, reducedImageCtx] = TempImageFactory.getCanvas( + reducedWidth, + reducedHeight, + ); + while (reducedWidth > img.width || reducedHeight > img.height) { + reducedWidth >>= 1; + reducedHeight >>= 1; + } + reducedImageCtx.drawImage( + img, + 0, + 0, + img.width, + img.height, + 0, + 0, + reducedWidth, + reducedHeight, + ); + while (reducedWidth > 2 * canvas.width) { + reducedImageCtx.drawImage( + reducedImage, + 0, + 0, + reducedWidth, + reducedHeight, + 0, + 0, + reducedWidth >> 1, + reducedHeight >> 1, + ); + reducedWidth >>= 1; + reducedHeight >>= 1; + } + ctx.drawImage( + reducedImage, + 0, + 0, + reducedWidth, + reducedHeight, + 0, + 0, + canvas.width, + canvas.height, + ); + return canvas; + } + get #pageL10nArgs() { + return JSON.stringify({ + page: this.pageLabel ?? this.id, + }); + } + setPageLabel(label) { + this.pageLabel = typeof label === "string" ? label : null; + this.anchor.setAttribute("data-l10n-args", this.#pageL10nArgs); + if (this.renderingState !== RenderingStates.FINISHED) { + return; + } + this.image?.setAttribute("data-l10n-args", this.#pageL10nArgs); + } +} // ./web/pdf_thumbnail_viewer.js + +const THUMBNAIL_SCROLL_MARGIN = -19; +const THUMBNAIL_SELECTED_CLASS = "selected"; +class PDFThumbnailViewer { + constructor({ + container, + eventBus, + linkService, + renderingQueue, + pageColors, + abortSignal, + enableHWA, + }) { + this.container = container; + this.eventBus = eventBus; + this.linkService = linkService; + this.renderingQueue = renderingQueue; + this.pageColors = pageColors || null; + this.enableHWA = enableHWA || false; + this.scroll = watchScroll( + this.container, + this.#scrollUpdated.bind(this), + abortSignal, + ); + this.#resetView(); + } + #scrollUpdated() { + this.renderingQueue.renderHighestPriority(); + } + getThumbnail(index) { + return this._thumbnails[index]; + } + #getVisibleThumbs() { + return getVisibleElements({ + scrollEl: this.container, + views: this._thumbnails, + }); + } + scrollThumbnailIntoView(pageNumber) { + if (!this.pdfDocument) { + return; + } + const thumbnailView = this._thumbnails[pageNumber - 1]; + if (!thumbnailView) { + console.error('scrollThumbnailIntoView: Invalid "pageNumber" parameter.'); + return; + } + if (pageNumber !== this._currentPageNumber) { + const prevThumbnailView = this._thumbnails[this._currentPageNumber - 1]; + prevThumbnailView.div.classList.remove(THUMBNAIL_SELECTED_CLASS); + thumbnailView.div.classList.add(THUMBNAIL_SELECTED_CLASS); + } + const { first, last, views } = this.#getVisibleThumbs(); + if (views.length > 0) { + let shouldScroll = false; + if (pageNumber <= first.id || pageNumber >= last.id) { + shouldScroll = true; + } else { + for (const { id, percent } of views) { + if (id !== pageNumber) { + continue; + } + shouldScroll = percent < 100; + break; + } + } + if (shouldScroll) { + scrollIntoView(thumbnailView.div, { + top: THUMBNAIL_SCROLL_MARGIN, + }); + } + } + this._currentPageNumber = pageNumber; + } + get pagesRotation() { + return this._pagesRotation; + } + set pagesRotation(rotation) { + if (!isValidRotation(rotation)) { + throw new Error("Invalid thumbnails rotation angle."); + } + if (!this.pdfDocument) { + return; + } + if (this._pagesRotation === rotation) { + return; + } + this._pagesRotation = rotation; + const updateArgs = { + rotation, + }; + for (const thumbnail of this._thumbnails) { + thumbnail.update(updateArgs); + } + } + cleanup() { + for (const thumbnail of this._thumbnails) { + if (thumbnail.renderingState !== RenderingStates.FINISHED) { + thumbnail.reset(); + } + } + TempImageFactory.destroyCanvas(); + } + #resetView() { + this._thumbnails = []; + this._currentPageNumber = 1; + this._pageLabels = null; + this._pagesRotation = 0; + this.container.textContent = ""; + } + setDocument(pdfDocument) { + if (this.pdfDocument) { + this.#cancelRendering(); + this.#resetView(); + } + this.pdfDocument = pdfDocument; + if (!pdfDocument) { + return; + } + const firstPagePromise = pdfDocument.getPage(1); + const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({ + intent: "display", + }); + firstPagePromise + .then((firstPdfPage) => { + const pagesCount = pdfDocument.numPages; + const viewport = firstPdfPage.getViewport({ + scale: 1, + }); + for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) { + const thumbnail = new PDFThumbnailView({ + container: this.container, + eventBus: this.eventBus, + id: pageNum, + defaultViewport: viewport.clone(), + optionalContentConfigPromise, + linkService: this.linkService, + renderingQueue: this.renderingQueue, + pageColors: this.pageColors, + enableHWA: this.enableHWA, + }); + this._thumbnails.push(thumbnail); + } + this._thumbnails[0]?.setPdfPage(firstPdfPage); + const thumbnailView = this._thumbnails[this._currentPageNumber - 1]; + thumbnailView.div.classList.add(THUMBNAIL_SELECTED_CLASS); + }) + .catch((reason) => { + console.error("Unable to initialize thumbnail viewer", reason); + }); + } + #cancelRendering() { + for (const thumbnail of this._thumbnails) { + thumbnail.cancelRendering(); + } + } + setPageLabels(labels) { + if (!this.pdfDocument) { + return; + } + if (!labels) { + this._pageLabels = null; + } else if ( + !(Array.isArray(labels) && this.pdfDocument.numPages === labels.length) + ) { + this._pageLabels = null; + console.error("PDFThumbnailViewer_setPageLabels: Invalid page labels."); + } else { + this._pageLabels = labels; + } + for (let i = 0, ii = this._thumbnails.length; i < ii; i++) { + this._thumbnails[i].setPageLabel(this._pageLabels?.[i] ?? null); + } + } + async #ensurePdfPageLoaded(thumbView) { + if (thumbView.pdfPage) { + return thumbView.pdfPage; + } + try { + const pdfPage = await this.pdfDocument.getPage(thumbView.id); + if (!thumbView.pdfPage) { + thumbView.setPdfPage(pdfPage); + } + return pdfPage; + } catch (reason) { + console.error("Unable to get page for thumb view", reason); + return null; + } + } + #getScrollAhead(visible) { + if (visible.first?.id === 1) { + return true; + } else if (visible.last?.id === this._thumbnails.length) { + return false; + } + return this.scroll.down; + } + forceRendering() { + const visibleThumbs = this.#getVisibleThumbs(); + const scrollAhead = this.#getScrollAhead(visibleThumbs); + const thumbView = this.renderingQueue.getHighestPriority( + visibleThumbs, + this._thumbnails, + scrollAhead, + ); + if (thumbView) { + this.#ensurePdfPageLoaded(thumbView).then(() => { + this.renderingQueue.renderView(thumbView); + }); + return true; + } + return false; + } +} // ./web/annotation_editor_layer_builder.js + +class AnnotationEditorLayerBuilder { + #annotationLayer = null; + #drawLayer = null; + #onAppend = null; + #structTreeLayer = null; + #textLayer = null; + #uiManager; + constructor(options) { + this.pdfPage = options.pdfPage; + this.accessibilityManager = options.accessibilityManager; + this.l10n = options.l10n; + this.l10n ||= new genericl10n_GenericL10n(); + this.annotationEditorLayer = null; + this.div = null; + this._cancelled = false; + this.#uiManager = options.uiManager; + this.#annotationLayer = options.annotationLayer || null; + this.#textLayer = options.textLayer || null; + this.#drawLayer = options.drawLayer || null; + this.#onAppend = options.onAppend || null; + this.#structTreeLayer = options.structTreeLayer || null; + } + async render(viewport, intent = "display") { + if (intent !== "display") { + return; + } + if (this._cancelled) { + return; + } + const clonedViewport = viewport.clone({ + dontFlip: true, + }); + if (this.div) { + this.annotationEditorLayer.update({ + viewport: clonedViewport, + }); + this.show(); + return; + } + const div = (this.div = document.createElement("div")); + div.className = "annotationEditorLayer"; + div.hidden = true; + div.dir = this.#uiManager.direction; + this.#onAppend?.(div); + this.annotationEditorLayer = new AnnotationEditorLayer({ + uiManager: this.#uiManager, + div, + structTreeLayer: this.#structTreeLayer, + accessibilityManager: this.accessibilityManager, + pageIndex: this.pdfPage.pageNumber - 1, + l10n: this.l10n, + viewport: clonedViewport, + annotationLayer: this.#annotationLayer, + textLayer: this.#textLayer, + drawLayer: this.#drawLayer, + }); + const parameters = { + viewport: clonedViewport, + div, + annotations: null, + intent, + }; + this.annotationEditorLayer.render(parameters); + this.show(); + } + cancel() { + this._cancelled = true; + if (!this.div) { + return; + } + this.annotationEditorLayer.destroy(); + } + hide() { + if (!this.div) { + return; + } + this.annotationEditorLayer.pause(true); + this.div.hidden = true; + } + show() { + if (!this.div || this.annotationEditorLayer.isInvisible) { + return; + } + this.div.hidden = false; + this.annotationEditorLayer.pause(false); + } +} // ./web/annotation_layer_builder.js + +class AnnotationLayerBuilder { + #onAppend = null; + #eventAbortController = null; + constructor({ + pdfPage, + linkService, + downloadManager, + annotationStorage = null, + imageResourcesPath = "", + renderForms = true, + enableScripting = false, + hasJSActionsPromise = null, + fieldObjectsPromise = null, + annotationCanvasMap = null, + accessibilityManager = null, + annotationEditorUIManager = null, + onAppend = null, + }) { + this.pdfPage = pdfPage; + this.linkService = linkService; + this.downloadManager = downloadManager; + this.imageResourcesPath = imageResourcesPath; + this.renderForms = renderForms; + this.annotationStorage = annotationStorage; + this.enableScripting = enableScripting; + this._hasJSActionsPromise = hasJSActionsPromise || Promise.resolve(false); + this._fieldObjectsPromise = fieldObjectsPromise || Promise.resolve(null); + this._annotationCanvasMap = annotationCanvasMap; + this._accessibilityManager = accessibilityManager; + this._annotationEditorUIManager = annotationEditorUIManager; + this.#onAppend = onAppend; + this.annotationLayer = null; + this.div = null; + this._cancelled = false; + this._eventBus = linkService.eventBus; + } + async render(viewport, options, intent = "display") { + if (this.div) { + if (this._cancelled || !this.annotationLayer) { + return; + } + this.annotationLayer.update({ + viewport: viewport.clone({ + dontFlip: true, + }), + }); + return; + } + const [annotations, hasJSActions, fieldObjects] = await Promise.all([ + this.pdfPage.getAnnotations({ + intent, + }), + this._hasJSActionsPromise, + this._fieldObjectsPromise, + ]); + if (this._cancelled) { + return; + } + const div = (this.div = document.createElement("div")); + div.className = "annotationLayer"; + this.#onAppend?.(div); + if (annotations.length === 0) { + this.hide(); + return; + } + this.annotationLayer = new AnnotationLayer({ + div, + accessibilityManager: this._accessibilityManager, + annotationCanvasMap: this._annotationCanvasMap, + annotationEditorUIManager: this._annotationEditorUIManager, + page: this.pdfPage, + viewport: viewport.clone({ + dontFlip: true, + }), + structTreeLayer: options?.structTreeLayer || null, + }); + await this.annotationLayer.render({ + annotations, + imageResourcesPath: this.imageResourcesPath, + renderForms: this.renderForms, + linkService: this.linkService, + downloadManager: this.downloadManager, + annotationStorage: this.annotationStorage, + enableScripting: this.enableScripting, + hasJSActions, + fieldObjects, + }); + if (this.linkService.isInPresentationMode) { + this.#updatePresentationModeState(PresentationModeState.FULLSCREEN); + } + if (!this.#eventAbortController) { + this.#eventAbortController = new AbortController(); + this._eventBus?._on( + "presentationmodechanged", + (evt) => { + this.#updatePresentationModeState(evt.state); + }, + { + signal: this.#eventAbortController.signal, + }, + ); + } + } + cancel() { + this._cancelled = true; + this.#eventAbortController?.abort(); + this.#eventAbortController = null; + } + hide() { + if (!this.div) { + return; + } + this.div.hidden = true; + } + hasEditableAnnotations() { + return !!this.annotationLayer?.hasEditableAnnotations(); + } + #updatePresentationModeState(state) { + if (!this.div) { + return; + } + let disableFormElements = false; + switch (state) { + case PresentationModeState.FULLSCREEN: + disableFormElements = true; + break; + case PresentationModeState.NORMAL: + break; + default: + return; + } + for (const section of this.div.childNodes) { + if (section.hasAttribute("data-internal-link")) { + continue; + } + section.inert = disableFormElements; + } + } +} // ./web/draw_layer_builder.js + +class DrawLayerBuilder { + #drawLayer = null; + constructor(options) { + this.pageIndex = options.pageIndex; + } + async render(intent = "display") { + if (intent !== "display" || this.#drawLayer || this._cancelled) { + return; + } + this.#drawLayer = new DrawLayer({ + pageIndex: this.pageIndex, + }); + } + cancel() { + this._cancelled = true; + if (!this.#drawLayer) { + return; + } + this.#drawLayer.destroy(); + this.#drawLayer = null; + } + setParent(parent) { + this.#drawLayer?.setParent(parent); + } + getDrawLayer() { + return this.#drawLayer; + } +} // ./web/struct_tree_layer_builder.js + +const PDF_ROLE_TO_HTML_ROLE = { + Document: null, + DocumentFragment: null, + Part: "group", + Sect: "group", + Div: "group", + Aside: "note", + NonStruct: "none", + P: null, + H: "heading", + Title: null, + FENote: "note", + Sub: "group", + Lbl: null, + Span: null, + Em: null, + Strong: null, + Link: "link", + Annot: "note", + Form: "form", + Ruby: null, + RB: null, + RT: null, + RP: null, + Warichu: null, + WT: null, + WP: null, + L: "list", + LI: "listitem", + LBody: null, + Table: "table", + TR: "row", + TH: "columnheader", + TD: "cell", + THead: "columnheader", + TBody: null, + TFoot: null, + Caption: null, + Figure: "figure", + Formula: null, + Artifact: null, +}; +const HEADING_PATTERN = /^H(\d+)$/; +class StructTreeLayerBuilder { + #promise; + #treeDom = null; + #treePromise; + #elementAttributes = new Map(); + #rawDims; + #elementsToAddToTextLayer = null; + constructor(pdfPage, rawDims) { + this.#promise = pdfPage.getStructTree(); + this.#rawDims = rawDims; + } + async render() { + if (this.#treePromise) { + return this.#treePromise; + } + const { promise, resolve, reject } = Promise.withResolvers(); + this.#treePromise = promise; + try { + this.#treeDom = this.#walk(await this.#promise); + } catch (ex) { + reject(ex); + } + this.#promise = null; + this.#treeDom?.classList.add("structTree"); + resolve(this.#treeDom); + return promise; + } + async getAriaAttributes(annotationId) { + try { + await this.render(); + return this.#elementAttributes.get(annotationId); + } catch {} + return null; + } + hide() { + if (this.#treeDom && !this.#treeDom.hidden) { + this.#treeDom.hidden = true; + } + } + show() { + if (this.#treeDom?.hidden) { + this.#treeDom.hidden = false; + } + } + #setAttributes(structElement, htmlElement) { + const { alt, id, lang } = structElement; + if (alt !== undefined) { + let added = false; + const label = removeNullCharacters(alt); + for (const child of structElement.children) { + if (child.type === "annotation") { + let attrs = this.#elementAttributes.get(child.id); + if (!attrs) { + attrs = new Map(); + this.#elementAttributes.set(child.id, attrs); + } + attrs.set("aria-label", label); + added = true; + } + } + if (!added) { + htmlElement.setAttribute("aria-label", label); + } + } + if (id !== undefined) { + htmlElement.setAttribute("aria-owns", id); + } + if (lang !== undefined) { + htmlElement.setAttribute("lang", removeNullCharacters(lang, true)); + } + } + #addImageInTextLayer(node, element) { + const { alt, bbox, children } = node; + const child = children?.[0]; + if (!this.#rawDims || !alt || !bbox || child?.type !== "content") { + return false; + } + const { id } = child; + if (!id) { + return false; + } + element.setAttribute("aria-owns", id); + const img = document.createElement("span"); + (this.#elementsToAddToTextLayer ||= new Map()).set(id, img); + img.setAttribute("role", "img"); + img.setAttribute("aria-label", removeNullCharacters(alt)); + const { pageHeight, pageX, pageY } = this.#rawDims; + const calc = "calc(var(--scale-factor)*"; + const { style } = img; + style.width = `${calc}${bbox[2] - bbox[0]}px)`; + style.height = `${calc}${bbox[3] - bbox[1]}px)`; + style.left = `${calc}${bbox[0] - pageX}px)`; + style.top = `${calc}${pageHeight - bbox[3] + pageY}px)`; + return true; + } + addElementsToTextLayer() { + if (!this.#elementsToAddToTextLayer) { + return; + } + for (const [id, img] of this.#elementsToAddToTextLayer) { + document.getElementById(id)?.append(img); + } + this.#elementsToAddToTextLayer.clear(); + this.#elementsToAddToTextLayer = null; + } + #walk(node) { + if (!node) { + return null; + } + const element = document.createElement("span"); + if ("role" in node) { + const { role } = node; + const match = role.match(HEADING_PATTERN); + if (match) { + element.setAttribute("role", "heading"); + element.setAttribute("aria-level", match[1]); + } else if (PDF_ROLE_TO_HTML_ROLE[role]) { + element.setAttribute("role", PDF_ROLE_TO_HTML_ROLE[role]); + } + if (role === "Figure" && this.#addImageInTextLayer(node, element)) { + return element; + } + } + this.#setAttributes(node, element); + if (node.children) { + if (node.children.length === 1 && "id" in node.children[0]) { + this.#setAttributes(node.children[0], element); + } else { + for (const kid of node.children) { + element.append(this.#walk(kid)); + } + } + } + return element; + } +} // ./web/text_accessibility.js + +class TextAccessibilityManager { + #enabled = false; + #textChildren = null; + #textNodes = new Map(); + #waitingElements = new Map(); + setTextMapping(textDivs) { + this.#textChildren = textDivs; + } + static #compareElementPositions(e1, e2) { + const rect1 = e1.getBoundingClientRect(); + const rect2 = e2.getBoundingClientRect(); + if (rect1.width === 0 && rect1.height === 0) { + return +1; + } + if (rect2.width === 0 && rect2.height === 0) { + return -1; + } + const top1 = rect1.y; + const bot1 = rect1.y + rect1.height; + const mid1 = rect1.y + rect1.height / 2; + const top2 = rect2.y; + const bot2 = rect2.y + rect2.height; + const mid2 = rect2.y + rect2.height / 2; + if (mid1 <= top2 && mid2 >= bot1) { + return -1; + } + if (mid2 <= top1 && mid1 >= bot2) { + return +1; + } + const centerX1 = rect1.x + rect1.width / 2; + const centerX2 = rect2.x + rect2.width / 2; + return centerX1 - centerX2; + } + enable() { + if (this.#enabled) { + throw new Error("TextAccessibilityManager is already enabled."); + } + if (!this.#textChildren) { + throw new Error("Text divs and strings have not been set."); + } + this.#enabled = true; + this.#textChildren = this.#textChildren.slice(); + this.#textChildren.sort(TextAccessibilityManager.#compareElementPositions); + if (this.#textNodes.size > 0) { + const textChildren = this.#textChildren; + for (const [id, nodeIndex] of this.#textNodes) { + const element = document.getElementById(id); + if (!element) { + this.#textNodes.delete(id); + continue; + } + this.#addIdToAriaOwns(id, textChildren[nodeIndex]); + } + } + for (const [element, isRemovable] of this.#waitingElements) { + this.addPointerInTextLayer(element, isRemovable); + } + this.#waitingElements.clear(); + } + disable() { + if (!this.#enabled) { + return; + } + this.#waitingElements.clear(); + this.#textChildren = null; + this.#enabled = false; + } + removePointerInTextLayer(element) { + if (!this.#enabled) { + this.#waitingElements.delete(element); + return; + } + const children = this.#textChildren; + if (!children || children.length === 0) { + return; + } + const { id } = element; + const nodeIndex = this.#textNodes.get(id); + if (nodeIndex === undefined) { + return; + } + const node = children[nodeIndex]; + this.#textNodes.delete(id); + let owns = node.getAttribute("aria-owns"); + if (owns?.includes(id)) { + owns = owns + .split(" ") + .filter((x) => x !== id) + .join(" "); + if (owns) { + node.setAttribute("aria-owns", owns); + } else { + node.removeAttribute("aria-owns"); + node.setAttribute("role", "presentation"); + } + } + } + #addIdToAriaOwns(id, node) { + const owns = node.getAttribute("aria-owns"); + if (!owns?.includes(id)) { + node.setAttribute("aria-owns", owns ? `${owns} ${id}` : id); + } + node.removeAttribute("role"); + } + addPointerInTextLayer(element, isRemovable) { + const { id } = element; + if (!id) { + return null; + } + if (!this.#enabled) { + this.#waitingElements.set(element, isRemovable); + return null; + } + if (isRemovable) { + this.removePointerInTextLayer(element); + } + const children = this.#textChildren; + if (!children || children.length === 0) { + return null; + } + const index = binarySearchFirstItem( + children, + (node) => + TextAccessibilityManager.#compareElementPositions(element, node) < 0, + ); + const nodeIndex = Math.max(0, index - 1); + const child = children[nodeIndex]; + this.#addIdToAriaOwns(id, child); + this.#textNodes.set(id, nodeIndex); + const parent = child.parentNode; + return parent?.classList.contains("markedContent") ? parent.id : null; + } + moveElementInDOM(container, element, contentElement, isRemovable) { + const id = this.addPointerInTextLayer(contentElement, isRemovable); + if (!container.hasChildNodes()) { + container.append(element); + return id; + } + const children = Array.from(container.childNodes).filter( + (node) => node !== element, + ); + if (children.length === 0) { + return id; + } + const elementToCompare = contentElement || element; + const index = binarySearchFirstItem( + children, + (node) => + TextAccessibilityManager.#compareElementPositions( + elementToCompare, + node, + ) < 0, + ); + if (index === 0) { + children[0].before(element); + } else { + children[index - 1].after(element); + } + return id; + } +} // ./web/text_highlighter.js + +class TextHighlighter { + #eventAbortController = null; + constructor({ findController, eventBus, pageIndex }) { + this.findController = findController; + this.matches = []; + this.eventBus = eventBus; + this.pageIdx = pageIndex; + this.textDivs = null; + this.textContentItemsStr = null; + this.enabled = false; + } + setTextMapping(divs, texts) { + this.textDivs = divs; + this.textContentItemsStr = texts; + } + enable() { + if (!this.textDivs || !this.textContentItemsStr) { + throw new Error("Text divs and strings have not been set."); + } + if (this.enabled) { + throw new Error("TextHighlighter is already enabled."); + } + this.enabled = true; + if (!this.#eventAbortController) { + this.#eventAbortController = new AbortController(); + this.eventBus._on( + "updatetextlayermatches", + (evt) => { + if (evt.pageIndex === this.pageIdx || evt.pageIndex === -1) { + this._updateMatches(); + } + }, + { + signal: this.#eventAbortController.signal, + }, + ); + } + this._updateMatches(); + } + disable() { + if (!this.enabled) { + return; + } + this.enabled = false; + this.#eventAbortController?.abort(); + this.#eventAbortController = null; + this._updateMatches(true); + } + _convertMatches(matches, matchesLength) { + if (!matches) { + return []; + } + const { textContentItemsStr } = this; + let i = 0, + iIndex = 0; + const end = textContentItemsStr.length - 1; + const result = []; + for (let m = 0, mm = matches.length; m < mm; m++) { + let matchIdx = matches[m]; + while (i !== end && matchIdx >= iIndex + textContentItemsStr[i].length) { + iIndex += textContentItemsStr[i].length; + i++; + } + if (i === textContentItemsStr.length) { + console.error("Could not find a matching mapping"); + } + const match = { + begin: { + divIdx: i, + offset: matchIdx - iIndex, + }, + }; + matchIdx += matchesLength[m]; + while (i !== end && matchIdx > iIndex + textContentItemsStr[i].length) { + iIndex += textContentItemsStr[i].length; + i++; + } + match.end = { + divIdx: i, + offset: matchIdx - iIndex, + }; + result.push(match); + } + return result; + } + _renderMatches(matches) { + if (matches.length === 0) { + return; + } + const { findController, pageIdx } = this; + const { textContentItemsStr, textDivs } = this; + const isSelectedPage = pageIdx === findController.selected.pageIdx; + const selectedMatchIdx = findController.selected.matchIdx; + const highlightAll = findController.state.highlightAll; + let prevEnd = null; + const infinity = { + divIdx: -1, + offset: undefined, + }; + function beginText(begin, className) { + const divIdx = begin.divIdx; + textDivs[divIdx].textContent = ""; + return appendTextToDiv(divIdx, 0, begin.offset, className); + } + function appendTextToDiv(divIdx, fromOffset, toOffset, className) { + let div = textDivs[divIdx]; + if (div.nodeType === Node.TEXT_NODE) { + const span = document.createElement("span"); + div.before(span); + span.append(div); + textDivs[divIdx] = span; + div = span; + } + const content = textContentItemsStr[divIdx].substring( + fromOffset, + toOffset, + ); + const node = document.createTextNode(content); + if (className) { + const span = document.createElement("span"); + span.className = `${className} appended`; + span.append(node); + div.append(span); + if (className.includes("selected")) { + const { left } = span.getClientRects()[0]; + const parentLeft = div.getBoundingClientRect().left; + return left - parentLeft; + } + return 0; + } + div.append(node); + return 0; + } + let i0 = selectedMatchIdx, + i1 = i0 + 1; + if (highlightAll) { + i0 = 0; + i1 = matches.length; + } else if (!isSelectedPage) { + return; + } + let lastDivIdx = -1; + let lastOffset = -1; + for (let i = i0; i < i1; i++) { + const match = matches[i]; + const begin = match.begin; + if (begin.divIdx === lastDivIdx && begin.offset === lastOffset) { + continue; + } + lastDivIdx = begin.divIdx; + lastOffset = begin.offset; + const end = match.end; + const isSelected = isSelectedPage && i === selectedMatchIdx; + const highlightSuffix = isSelected ? " selected" : ""; + let selectedLeft = 0; + if (!prevEnd || begin.divIdx !== prevEnd.divIdx) { + if (prevEnd !== null) { + appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset); + } + beginText(begin); + } else { + appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset); + } + if (begin.divIdx === end.divIdx) { + selectedLeft = appendTextToDiv( + begin.divIdx, + begin.offset, + end.offset, + "highlight" + highlightSuffix, + ); + } else { + selectedLeft = appendTextToDiv( + begin.divIdx, + begin.offset, + infinity.offset, + "highlight begin" + highlightSuffix, + ); + for (let n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) { + textDivs[n0].className = "highlight middle" + highlightSuffix; + } + beginText(end, "highlight end" + highlightSuffix); + } + prevEnd = end; + if (isSelected) { + findController.scrollMatchIntoView({ + element: textDivs[begin.divIdx], + selectedLeft, + pageIndex: pageIdx, + matchIndex: selectedMatchIdx, + }); + } + } + if (prevEnd) { + appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset); + } + } + _updateMatches(reset = false) { + if (!this.enabled && !reset) { + return; + } + const { findController, matches, pageIdx } = this; + const { textContentItemsStr, textDivs } = this; + let clearedUntilDivIdx = -1; + for (const match of matches) { + const begin = Math.max(clearedUntilDivIdx, match.begin.divIdx); + for (let n = begin, end = match.end.divIdx; n <= end; n++) { + const div = textDivs[n]; + div.textContent = textContentItemsStr[n]; + div.className = ""; + } + clearedUntilDivIdx = match.end.divIdx + 1; + } + if (!findController?.highlightMatches || reset) { + return; + } + const pageMatches = findController.pageMatches[pageIdx] || null; + const pageMatchesLength = findController.pageMatchesLength[pageIdx] || null; + this.matches = this._convertMatches(pageMatches, pageMatchesLength); + this._renderMatches(this.matches); + } +} // ./web/text_layer_builder.js + +class TextLayerBuilder { + #enablePermissions = false; + #onAppend = null; + #renderingDone = false; + #textLayer = null; + static #textLayers = new Map(); + static #selectionChangeAbortController = null; + constructor({ + pdfPage, + highlighter = null, + accessibilityManager = null, + enablePermissions = false, + onAppend = null, + }) { + this.pdfPage = pdfPage; + this.highlighter = highlighter; + this.accessibilityManager = accessibilityManager; + this.#enablePermissions = enablePermissions === true; + this.#onAppend = onAppend; + this.div = document.createElement("div"); + this.div.tabIndex = 0; + this.div.className = "textLayer"; + } + async render(viewport, textContentParams = null) { + if (this.#renderingDone && this.#textLayer) { + this.#textLayer.update({ + viewport, + onBefore: this.hide.bind(this), + }); + this.show(); + return; + } + this.cancel(); + this.#textLayer = new TextLayer({ + textContentSource: this.pdfPage.streamTextContent( + textContentParams || { + includeMarkedContent: true, + disableNormalization: true, + }, + ), + container: this.div, + viewport, + }); + const { textDivs, textContentItemsStr } = this.#textLayer; + this.highlighter?.setTextMapping(textDivs, textContentItemsStr); + this.accessibilityManager?.setTextMapping(textDivs); + await this.#textLayer.render(); + this.#renderingDone = true; + const endOfContent = document.createElement("div"); + endOfContent.className = "endOfContent"; + this.div.append(endOfContent); + this.#bindMouse(endOfContent); + this.#onAppend?.(this.div); + this.highlighter?.enable(); + this.accessibilityManager?.enable(); + } + hide() { + if (!this.div.hidden && this.#renderingDone) { + this.highlighter?.disable(); + this.div.hidden = true; + } + } + show() { + if (this.div.hidden && this.#renderingDone) { + this.div.hidden = false; + this.highlighter?.enable(); + } + } + cancel() { + this.#textLayer?.cancel(); + this.#textLayer = null; + this.highlighter?.disable(); + this.accessibilityManager?.disable(); + TextLayerBuilder.#removeGlobalSelectionListener(this.div); + } + #bindMouse(end) { + const { div } = this; + div.addEventListener("mousedown", () => { + div.classList.add("selecting"); + }); + div.addEventListener("copy", (event) => { + if (!this.#enablePermissions) { + const selection = document.getSelection(); + event.clipboardData.setData( + "text/plain", + removeNullCharacters(normalizeUnicode(selection.toString())), + ); + } + stopEvent(event); + }); + TextLayerBuilder.#textLayers.set(div, end); + TextLayerBuilder.#enableGlobalSelectionListener(); + } + static #removeGlobalSelectionListener(textLayerDiv) { + this.#textLayers.delete(textLayerDiv); + if (this.#textLayers.size === 0) { + this.#selectionChangeAbortController?.abort(); + this.#selectionChangeAbortController = null; + } + } + static #enableGlobalSelectionListener() { + if (this.#selectionChangeAbortController) { + return; + } + this.#selectionChangeAbortController = new AbortController(); + const { signal } = this.#selectionChangeAbortController; + const reset = (end, textLayer) => { + textLayer.append(end); + end.style.width = ""; + end.style.height = ""; + textLayer.classList.remove("selecting"); + }; + let isPointerDown = false; + document.addEventListener( + "pointerdown", + () => { + isPointerDown = true; + }, + { + signal, + }, + ); + document.addEventListener( + "pointerup", + () => { + isPointerDown = false; + this.#textLayers.forEach(reset); + }, + { + signal, + }, + ); + window.addEventListener( + "blur", + () => { + isPointerDown = false; + this.#textLayers.forEach(reset); + }, + { + signal, + }, + ); + document.addEventListener( + "keyup", + () => { + if (!isPointerDown) { + this.#textLayers.forEach(reset); + } + }, + { + signal, + }, + ); + var isFirefox, prevRange; + document.addEventListener( + "selectionchange", + () => { + const selection = document.getSelection(); + if (selection.rangeCount === 0) { + this.#textLayers.forEach(reset); + return; + } + const activeTextLayers = new Set(); + for (let i = 0; i < selection.rangeCount; i++) { + const range = selection.getRangeAt(i); + for (const textLayerDiv of this.#textLayers.keys()) { + if ( + !activeTextLayers.has(textLayerDiv) && + range.intersectsNode(textLayerDiv) + ) { + activeTextLayers.add(textLayerDiv); + } + } + } + for (const [textLayerDiv, endDiv] of this.#textLayers) { + if (activeTextLayers.has(textLayerDiv)) { + textLayerDiv.classList.add("selecting"); + } else { + reset(endDiv, textLayerDiv); + } + } + isFirefox ??= + getComputedStyle( + this.#textLayers.values().next().value, + ).getPropertyValue("-moz-user-select") === "none"; + if (isFirefox) { + return; + } + const range = selection.getRangeAt(0); + const modifyStart = + prevRange && + (range.compareBoundaryPoints(Range.END_TO_END, prevRange) === 0 || + range.compareBoundaryPoints(Range.START_TO_END, prevRange) === 0); + let anchor = modifyStart ? range.startContainer : range.endContainer; + if (anchor.nodeType === Node.TEXT_NODE) { + anchor = anchor.parentNode; + } + const parentTextLayer = anchor.parentElement?.closest(".textLayer"); + const endDiv = this.#textLayers.get(parentTextLayer); + if (endDiv) { + endDiv.style.width = parentTextLayer.style.width; + endDiv.style.height = parentTextLayer.style.height; + anchor.parentElement.insertBefore( + endDiv, + modifyStart ? anchor : anchor.nextSibling, + ); + } + prevRange = range.cloneRange(); + }, + { + signal, + }, + ); + } +} // ./web/pdf_page_view.js + +const DEFAULT_LAYER_PROPERTIES = null; +const LAYERS_ORDER = new Map([ + ["canvasWrapper", 0], + ["textLayer", 1], + ["annotationLayer", 2], + ["annotationEditorLayer", 3], + ["xfaLayer", 3], +]); +class PDFPageView { + #annotationMode = AnnotationMode.ENABLE_FORMS; + #canvasWrapper = null; + #enableHWA = false; + #hasRestrictedScaling = false; + #isEditing = false; + #layerProperties = null; + #loadingId = null; + #originalViewport = null; + #previousRotation = null; + #scaleRoundX = 1; + #scaleRoundY = 1; + #renderError = null; + #renderingState = RenderingStates.INITIAL; + #textLayerMode = TextLayerMode.ENABLE; + #useThumbnailCanvas = { + directDrawing: true, + initialOptionalContent: true, + regularAnnotations: true, + }; + #layers = [null, null, null, null]; + constructor(options) { + const container = options.container; + const defaultViewport = options.defaultViewport; + this.id = options.id; + this.renderingId = "page" + this.id; + this.#layerProperties = options.layerProperties || DEFAULT_LAYER_PROPERTIES; + this.pdfPage = null; + this.pageLabel = null; + this.rotation = 0; + this.scale = options.scale || DEFAULT_SCALE; + this.viewport = defaultViewport; + this.pdfPageRotate = defaultViewport.rotation; + this._optionalContentConfigPromise = + options.optionalContentConfigPromise || null; + this.#textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE; + this.#annotationMode = + options.annotationMode ?? AnnotationMode.ENABLE_FORMS; + this.imageResourcesPath = options.imageResourcesPath || ""; + this.maxCanvasPixels = + options.maxCanvasPixels ?? AppOptions.get("maxCanvasPixels"); + this.pageColors = options.pageColors || null; + this.#enableHWA = options.enableHWA || false; + this.eventBus = options.eventBus; + this.renderingQueue = options.renderingQueue; + this.l10n = options.l10n; + this.l10n ||= new genericl10n_GenericL10n(); + this.renderTask = null; + this.resume = null; + this._isStandalone = !this.renderingQueue?.hasViewer(); + this._container = container; + this._annotationCanvasMap = null; + this.annotationLayer = null; + this.annotationEditorLayer = null; + this.textLayer = null; + this.xfaLayer = null; + this.structTreeLayer = null; + this.drawLayer = null; + const div = document.createElement("div"); + div.className = "page"; + div.setAttribute("data-page-number", this.id); + div.setAttribute("role", "region"); + div.setAttribute("data-l10n-id", "pdfjs-page-landmark"); + div.setAttribute( + "data-l10n-args", + JSON.stringify({ + page: this.id, + }), + ); + this.div = div; + this.#setDimensions(); + container?.append(div); + if (this._isStandalone) { + container?.style.setProperty( + "--scale-factor", + this.scale * PixelsPerInch.PDF_TO_CSS_UNITS, + ); + if (this.pageColors?.background) { + container?.style.setProperty( + "--page-bg-color", + this.pageColors.background, + ); + } + const { optionalContentConfigPromise } = options; + if (optionalContentConfigPromise) { + optionalContentConfigPromise.then((optionalContentConfig) => { + if ( + optionalContentConfigPromise !== this._optionalContentConfigPromise + ) { + return; + } + this.#useThumbnailCanvas.initialOptionalContent = + optionalContentConfig.hasInitialVisibility; + }); + } + if (!options.l10n) { + this.l10n.translate(this.div); + } + } + } + #addLayer(div, name) { + const pos = LAYERS_ORDER.get(name); + const oldDiv = this.#layers[pos]; + this.#layers[pos] = div; + if (oldDiv) { + oldDiv.replaceWith(div); + return; + } + for (let i = pos - 1; i >= 0; i--) { + const layer = this.#layers[i]; + if (layer) { + layer.after(div); + return; + } + } + this.div.prepend(div); + } + get renderingState() { + return this.#renderingState; + } + set renderingState(state) { + if (state === this.#renderingState) { + return; + } + this.#renderingState = state; + if (this.#loadingId) { + clearTimeout(this.#loadingId); + this.#loadingId = null; + } + switch (state) { + case RenderingStates.PAUSED: + this.div.classList.remove("loading"); + break; + case RenderingStates.RUNNING: + this.div.classList.add("loadingIcon"); + this.#loadingId = setTimeout(() => { + this.div.classList.add("loading"); + this.#loadingId = null; + }, 0); + break; + case RenderingStates.INITIAL: + case RenderingStates.FINISHED: + this.div.classList.remove("loadingIcon", "loading"); + break; + } + } + #setDimensions() { + const { viewport } = this; + if (this.pdfPage) { + if (this.#previousRotation === viewport.rotation) { + return; + } + this.#previousRotation = viewport.rotation; + } + setLayerDimensions(this.div, viewport, true, false); + } + setPdfPage(pdfPage) { + if ( + this._isStandalone && + (this.pageColors?.foreground === "CanvasText" || + this.pageColors?.background === "Canvas") + ) { + this._container?.style.setProperty( + "--hcm-highlight-filter", + pdfPage.filterFactory.addHighlightHCMFilter( + "highlight", + "CanvasText", + "Canvas", + "HighlightText", + "Highlight", + ), + ); + this._container?.style.setProperty( + "--hcm-highlight-selected-filter", + pdfPage.filterFactory.addHighlightHCMFilter( + "highlight_selected", + "CanvasText", + "Canvas", + "HighlightText", + "Highlight", + ), + ); + } + this.pdfPage = pdfPage; + this.pdfPageRotate = pdfPage.rotate; + const totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = pdfPage.getViewport({ + scale: this.scale * PixelsPerInch.PDF_TO_CSS_UNITS, + rotation: totalRotation, + }); + this.#setDimensions(); + this.reset(); + } + destroy() { + this.reset(); + this.pdfPage?.cleanup(); + } + hasEditableAnnotations() { + return !!this.annotationLayer?.hasEditableAnnotations(); + } + get _textHighlighter() { + return shadow( + this, + "_textHighlighter", + new TextHighlighter({ + pageIndex: this.id - 1, + eventBus: this.eventBus, + findController: this.#layerProperties.findController, + }), + ); + } + #dispatchLayerRendered(name, error) { + this.eventBus.dispatch(name, { + source: this, + pageNumber: this.id, + error, + }); + } + async #renderAnnotationLayer() { + let error = null; + try { + await this.annotationLayer.render( + this.viewport, + { + structTreeLayer: this.structTreeLayer, + }, + "display", + ); + } catch (ex) { + console.error("#renderAnnotationLayer:", ex); + error = ex; + } finally { + this.#dispatchLayerRendered("annotationlayerrendered", error); + } + } + async #renderAnnotationEditorLayer() { + let error = null; + try { + await this.annotationEditorLayer.render(this.viewport, "display"); + } catch (ex) { + console.error("#renderAnnotationEditorLayer:", ex); + error = ex; + } finally { + this.#dispatchLayerRendered("annotationeditorlayerrendered", error); + } + } + async #renderDrawLayer() { + try { + await this.drawLayer.render("display"); + } catch (ex) { + console.error("#renderDrawLayer:", ex); + } + } + async #renderXfaLayer() { + let error = null; + try { + const result = await this.xfaLayer.render(this.viewport, "display"); + if (result?.textDivs && this._textHighlighter) { + this.#buildXfaTextContentItems(result.textDivs); + } + } catch (ex) { + console.error("#renderXfaLayer:", ex); + error = ex; + } finally { + if (this.xfaLayer?.div) { + this.l10n.pause(); + this.#addLayer(this.xfaLayer.div, "xfaLayer"); + this.l10n.resume(); + } + this.#dispatchLayerRendered("xfalayerrendered", error); + } + } + async #renderTextLayer() { + if (!this.textLayer) { + return; + } + let error = null; + try { + await this.textLayer.render(this.viewport); + } catch (ex) { + if (ex instanceof AbortException) { + return; + } + console.error("#renderTextLayer:", ex); + error = ex; + } + this.#dispatchLayerRendered("textlayerrendered", error); + this.#renderStructTreeLayer(); + } + async #renderStructTreeLayer() { + if (!this.textLayer) { + return; + } + const treeDom = await this.structTreeLayer?.render(); + if (treeDom) { + this.l10n.pause(); + this.structTreeLayer?.addElementsToTextLayer(); + if (this.canvas && treeDom.parentNode !== this.canvas) { + this.canvas.append(treeDom); + } + this.l10n.resume(); + } + this.structTreeLayer?.show(); + } + async #buildXfaTextContentItems(textDivs) { + const text = await this.pdfPage.getTextContent(); + const items = []; + for (const item of text.items) { + items.push(item.str); + } + this._textHighlighter.setTextMapping(textDivs, items); + this._textHighlighter.enable(); + } + #resetCanvas() { + const { canvas } = this; + if (!canvas) { + return; + } + canvas.remove(); + canvas.width = canvas.height = 0; + this.canvas = null; + this.#originalViewport = null; + } + reset({ + keepAnnotationLayer = false, + keepAnnotationEditorLayer = false, + keepXfaLayer = false, + keepTextLayer = false, + keepCanvasWrapper = false, + } = {}) { + this.cancelRendering({ + keepAnnotationLayer, + keepAnnotationEditorLayer, + keepXfaLayer, + keepTextLayer, + }); + this.renderingState = RenderingStates.INITIAL; + const div = this.div; + const childNodes = div.childNodes, + annotationLayerNode = + (keepAnnotationLayer && this.annotationLayer?.div) || null, + annotationEditorLayerNode = + (keepAnnotationEditorLayer && this.annotationEditorLayer?.div) || null, + xfaLayerNode = (keepXfaLayer && this.xfaLayer?.div) || null, + textLayerNode = (keepTextLayer && this.textLayer?.div) || null, + canvasWrapperNode = (keepCanvasWrapper && this.#canvasWrapper) || null; + for (let i = childNodes.length - 1; i >= 0; i--) { + const node = childNodes[i]; + switch (node) { + case annotationLayerNode: + case annotationEditorLayerNode: + case xfaLayerNode: + case textLayerNode: + case canvasWrapperNode: + continue; + } + node.remove(); + const layerIndex = this.#layers.indexOf(node); + if (layerIndex >= 0) { + this.#layers[layerIndex] = null; + } + } + div.removeAttribute("data-loaded"); + if (annotationLayerNode) { + this.annotationLayer.hide(); + } + if (annotationEditorLayerNode) { + this.annotationEditorLayer.hide(); + } + if (xfaLayerNode) { + this.xfaLayer.hide(); + } + if (textLayerNode) { + this.textLayer.hide(); + } + this.structTreeLayer?.hide(); + if (!keepCanvasWrapper && this.#canvasWrapper) { + this.#canvasWrapper = null; + this.#resetCanvas(); + } + } + toggleEditingMode(isEditing) { + if (!this.hasEditableAnnotations()) { + return; + } + this.#isEditing = isEditing; + this.reset({ + keepAnnotationLayer: true, + keepAnnotationEditorLayer: true, + keepXfaLayer: true, + keepTextLayer: true, + keepCanvasWrapper: true, + }); + } + update({ + scale = 0, + rotation = null, + optionalContentConfigPromise = null, + drawingDelay = -1, + }) { + this.scale = scale || this.scale; + if (typeof rotation === "number") { + this.rotation = rotation; + } + if (optionalContentConfigPromise instanceof Promise) { + this._optionalContentConfigPromise = optionalContentConfigPromise; + optionalContentConfigPromise.then((optionalContentConfig) => { + if ( + optionalContentConfigPromise !== this._optionalContentConfigPromise + ) { + return; + } + this.#useThumbnailCanvas.initialOptionalContent = + optionalContentConfig.hasInitialVisibility; + }); + } + this.#useThumbnailCanvas.directDrawing = true; + const totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = this.viewport.clone({ + scale: this.scale * PixelsPerInch.PDF_TO_CSS_UNITS, + rotation: totalRotation, + }); + this.#setDimensions(); + if (this._isStandalone) { + this._container?.style.setProperty("--scale-factor", this.viewport.scale); + } + if (this.canvas) { + let onlyCssZoom = false; + if (this.#hasRestrictedScaling) { + if (this.maxCanvasPixels === 0) { + onlyCssZoom = true; + } else if (this.maxCanvasPixels > 0) { + const { width, height } = this.viewport; + const { sx, sy } = this.outputScale; + onlyCssZoom = + ((Math.floor(width) * sx) | 0) * ((Math.floor(height) * sy) | 0) > + this.maxCanvasPixels; + } + } + const postponeDrawing = drawingDelay >= 0 && drawingDelay < 1000; + if (postponeDrawing || onlyCssZoom) { + if ( + postponeDrawing && + !onlyCssZoom && + this.renderingState !== RenderingStates.FINISHED + ) { + this.cancelRendering({ + keepAnnotationLayer: true, + keepAnnotationEditorLayer: true, + keepXfaLayer: true, + keepTextLayer: true, + cancelExtraDelay: drawingDelay, + }); + this.renderingState = RenderingStates.FINISHED; + this.#useThumbnailCanvas.directDrawing = false; + } + this.cssTransform({ + redrawAnnotationLayer: true, + redrawAnnotationEditorLayer: true, + redrawXfaLayer: true, + redrawTextLayer: !postponeDrawing, + hideTextLayer: postponeDrawing, + }); + if (postponeDrawing) { + return; + } + this.eventBus.dispatch("pagerendered", { + source: this, + pageNumber: this.id, + cssTransform: true, + timestamp: performance.now(), + error: this.#renderError, + }); + return; + } + } + this.cssTransform({}); + this.reset({ + keepAnnotationLayer: true, + keepAnnotationEditorLayer: true, + keepXfaLayer: true, + keepTextLayer: true, + keepCanvasWrapper: true, + }); + } + cancelRendering({ + keepAnnotationLayer = false, + keepAnnotationEditorLayer = false, + keepXfaLayer = false, + keepTextLayer = false, + cancelExtraDelay = 0, + } = {}) { + if (this.renderTask) { + this.renderTask.cancel(cancelExtraDelay); + this.renderTask = null; + } + this.resume = null; + if (this.textLayer && (!keepTextLayer || !this.textLayer.div)) { + this.textLayer.cancel(); + this.textLayer = null; + } + if ( + this.annotationLayer && + (!keepAnnotationLayer || !this.annotationLayer.div) + ) { + this.annotationLayer.cancel(); + this.annotationLayer = null; + this._annotationCanvasMap = null; + } + if (this.structTreeLayer && !this.textLayer) { + this.structTreeLayer = null; + } + if ( + this.annotationEditorLayer && + (!keepAnnotationEditorLayer || !this.annotationEditorLayer.div) + ) { + if (this.drawLayer) { + this.drawLayer.cancel(); + this.drawLayer = null; + } + this.annotationEditorLayer.cancel(); + this.annotationEditorLayer = null; + } + if (this.xfaLayer && (!keepXfaLayer || !this.xfaLayer.div)) { + this.xfaLayer.cancel(); + this.xfaLayer = null; + this._textHighlighter?.disable(); + } + } + cssTransform({ + redrawAnnotationLayer = false, + redrawAnnotationEditorLayer = false, + redrawXfaLayer = false, + redrawTextLayer = false, + hideTextLayer = false, + }) { + const { canvas } = this; + if (!canvas) { + return; + } + const originalViewport = this.#originalViewport; + if (this.viewport !== originalViewport) { + const relativeRotation = + (360 + this.viewport.rotation - originalViewport.rotation) % 360; + if (relativeRotation === 90 || relativeRotation === 270) { + const { width, height } = this.viewport; + const scaleX = height / width; + const scaleY = width / height; + canvas.style.transform = `rotate(${relativeRotation}deg) scale(${scaleX},${scaleY})`; + } else { + canvas.style.transform = + relativeRotation === 0 ? "" : `rotate(${relativeRotation}deg)`; + } + } + if (redrawAnnotationLayer && this.annotationLayer) { + this.#renderAnnotationLayer(); + } + if (redrawAnnotationEditorLayer && this.annotationEditorLayer) { + if (this.drawLayer) { + this.#renderDrawLayer(); + } + this.#renderAnnotationEditorLayer(); + } + if (redrawXfaLayer && this.xfaLayer) { + this.#renderXfaLayer(); + } + if (this.textLayer) { + if (hideTextLayer) { + this.textLayer.hide(); + this.structTreeLayer?.hide(); + } else if (redrawTextLayer) { + this.#renderTextLayer(); + } + } + } + get width() { + return this.viewport.width; + } + get height() { + return this.viewport.height; + } + getPagePoint(x, y) { + return this.viewport.convertToPdfPoint(x, y); + } + async #finishRenderTask(renderTask, error = null) { + if (renderTask === this.renderTask) { + this.renderTask = null; + } + if (error instanceof RenderingCancelledException) { + this.#renderError = null; + return; + } + this.#renderError = error; + this.renderingState = RenderingStates.FINISHED; + this.#useThumbnailCanvas.regularAnnotations = !renderTask.separateAnnots; + this.eventBus.dispatch("pagerendered", { + source: this, + pageNumber: this.id, + cssTransform: false, + timestamp: performance.now(), + error: this.#renderError, + }); + if (error) { + throw error; + } + } + async draw() { + if (this.renderingState !== RenderingStates.INITIAL) { + console.error("Must be in new state before drawing"); + this.reset(); + } + const { div, l10n, pageColors, pdfPage, viewport } = this; + if (!pdfPage) { + this.renderingState = RenderingStates.FINISHED; + throw new Error("pdfPage is not loaded"); + } + this.renderingState = RenderingStates.RUNNING; + let canvasWrapper = this.#canvasWrapper; + if (!canvasWrapper) { + canvasWrapper = this.#canvasWrapper = document.createElement("div"); + canvasWrapper.classList.add("canvasWrapper"); + this.#addLayer(canvasWrapper, "canvasWrapper"); + } + if ( + !this.textLayer && + this.#textLayerMode !== TextLayerMode.DISABLE && + !pdfPage.isPureXfa + ) { + this._accessibilityManager ||= new TextAccessibilityManager(); + this.textLayer = new TextLayerBuilder({ + pdfPage, + highlighter: this._textHighlighter, + accessibilityManager: this._accessibilityManager, + enablePermissions: + this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS, + onAppend: (textLayerDiv) => { + this.l10n.pause(); + this.#addLayer(textLayerDiv, "textLayer"); + this.l10n.resume(); + }, + }); + } + if ( + !this.annotationLayer && + this.#annotationMode !== AnnotationMode.DISABLE + ) { + const { + annotationStorage, + annotationEditorUIManager, + downloadManager, + enableScripting, + fieldObjectsPromise, + hasJSActionsPromise, + linkService, + } = this.#layerProperties; + this._annotationCanvasMap ||= new Map(); + this.annotationLayer = new AnnotationLayerBuilder({ + pdfPage, + annotationStorage, + imageResourcesPath: this.imageResourcesPath, + renderForms: this.#annotationMode === AnnotationMode.ENABLE_FORMS, + linkService, + downloadManager, + enableScripting, + hasJSActionsPromise, + fieldObjectsPromise, + annotationCanvasMap: this._annotationCanvasMap, + accessibilityManager: this._accessibilityManager, + annotationEditorUIManager, + onAppend: (annotationLayerDiv) => { + this.#addLayer(annotationLayerDiv, "annotationLayer"); + }, + }); + } + const renderContinueCallback = (cont) => { + showCanvas?.(false); + if (this.renderingQueue && !this.renderingQueue.isHighestPriority(this)) { + this.renderingState = RenderingStates.PAUSED; + this.resume = () => { + this.renderingState = RenderingStates.RUNNING; + cont(); + }; + return; + } + cont(); + }; + const { width, height } = viewport; + const canvas = document.createElement("canvas"); + canvas.setAttribute("role", "presentation"); + const hasHCM = !!(pageColors?.background && pageColors?.foreground); + const prevCanvas = this.canvas; + const updateOnFirstShow = !prevCanvas && !hasHCM; + this.canvas = canvas; + this.#originalViewport = viewport; + let showCanvas = (isLastShow) => { + if (updateOnFirstShow) { + canvasWrapper.prepend(canvas); + showCanvas = null; + return; + } + if (!isLastShow) { + return; + } + if (prevCanvas) { + prevCanvas.replaceWith(canvas); + prevCanvas.width = prevCanvas.height = 0; + } else { + canvasWrapper.prepend(canvas); + } + showCanvas = null; + }; + const ctx = canvas.getContext("2d", { + alpha: false, + willReadFrequently: !this.#enableHWA, + }); + const outputScale = (this.outputScale = new OutputScale()); + if (this.maxCanvasPixels === 0) { + const invScale = 1 / this.scale; + outputScale.sx *= invScale; + outputScale.sy *= invScale; + this.#hasRestrictedScaling = true; + } else if (this.maxCanvasPixels > 0) { + const pixelsInViewport = width * height; + const maxScale = Math.sqrt(this.maxCanvasPixels / pixelsInViewport); + if (outputScale.sx > maxScale || outputScale.sy > maxScale) { + outputScale.sx = maxScale; + outputScale.sy = maxScale; + this.#hasRestrictedScaling = true; + } else { + this.#hasRestrictedScaling = false; + } + } + const sfx = approximateFraction(outputScale.sx); + const sfy = approximateFraction(outputScale.sy); + const canvasWidth = (canvas.width = floorToDivide( + calcRound(width * outputScale.sx), + sfx[0], + )); + const canvasHeight = (canvas.height = floorToDivide( + calcRound(height * outputScale.sy), + sfy[0], + )); + const pageWidth = floorToDivide(calcRound(width), sfx[1]); + const pageHeight = floorToDivide(calcRound(height), sfy[1]); + outputScale.sx = canvasWidth / pageWidth; + outputScale.sy = canvasHeight / pageHeight; + if (this.#scaleRoundX !== sfx[1]) { + div.style.setProperty("--scale-round-x", `${sfx[1]}px`); + this.#scaleRoundX = sfx[1]; + } + if (this.#scaleRoundY !== sfy[1]) { + div.style.setProperty("--scale-round-y", `${sfy[1]}px`); + this.#scaleRoundY = sfy[1]; + } + const transform = outputScale.scaled + ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0] + : null; + const renderContext = { + canvasContext: ctx, + transform, + viewport, + annotationMode: this.#annotationMode, + optionalContentConfigPromise: this._optionalContentConfigPromise, + annotationCanvasMap: this._annotationCanvasMap, + pageColors, + isEditing: this.#isEditing, + }; + const renderTask = (this.renderTask = pdfPage.render(renderContext)); + renderTask.onContinue = renderContinueCallback; + const resultPromise = renderTask.promise.then( + async () => { + showCanvas?.(true); + await this.#finishRenderTask(renderTask); + this.structTreeLayer ||= new StructTreeLayerBuilder( + pdfPage, + viewport.rawDims, + ); + this.#renderTextLayer(); + if (this.annotationLayer) { + await this.#renderAnnotationLayer(); + } + const { annotationEditorUIManager } = this.#layerProperties; + if (!annotationEditorUIManager) { + return; + } + this.drawLayer ||= new DrawLayerBuilder({ + pageIndex: this.id, + }); + await this.#renderDrawLayer(); + this.drawLayer.setParent(canvasWrapper); + this.annotationEditorLayer ||= new AnnotationEditorLayerBuilder({ + uiManager: annotationEditorUIManager, + pdfPage, + l10n, + structTreeLayer: this.structTreeLayer, + accessibilityManager: this._accessibilityManager, + annotationLayer: this.annotationLayer?.annotationLayer, + textLayer: this.textLayer, + drawLayer: this.drawLayer.getDrawLayer(), + onAppend: (annotationEditorLayerDiv) => { + this.#addLayer(annotationEditorLayerDiv, "annotationEditorLayer"); + }, + }); + this.#renderAnnotationEditorLayer(); + }, + (error) => { + if (!(error instanceof RenderingCancelledException)) { + showCanvas?.(true); + } else { + prevCanvas?.remove(); + this.#resetCanvas(); + } + return this.#finishRenderTask(renderTask, error); + }, + ); + if (pdfPage.isPureXfa) { + if (!this.xfaLayer) { + const { annotationStorage, linkService } = this.#layerProperties; + this.xfaLayer = new XfaLayerBuilder({ + pdfPage, + annotationStorage, + linkService, + }); + } + this.#renderXfaLayer(); + } + div.setAttribute("data-loaded", true); + this.eventBus.dispatch("pagerender", { + source: this, + pageNumber: this.id, + }); + return resultPromise; + } + setPageLabel(label) { + this.pageLabel = typeof label === "string" ? label : null; + this.div.setAttribute( + "data-l10n-args", + JSON.stringify({ + page: this.pageLabel ?? this.id, + }), + ); + if (this.pageLabel !== null) { + this.div.setAttribute("data-page-label", this.pageLabel); + } else { + this.div.removeAttribute("data-page-label"); + } + } + get thumbnailCanvas() { + const { directDrawing, initialOptionalContent, regularAnnotations } = + this.#useThumbnailCanvas; + return directDrawing && initialOptionalContent && regularAnnotations + ? this.canvas + : null; + } +} // ./web/pdf_viewer.js + +const DEFAULT_CACHE_SIZE = 10; +const PagesCountLimit = { + FORCE_SCROLL_MODE_PAGE: 10000, + FORCE_LAZY_PAGE_INIT: 5000, + PAUSE_EAGER_PAGE_INIT: 250, +}; +function isValidAnnotationEditorMode(mode) { + return ( + Object.values(AnnotationEditorType).includes(mode) && + mode !== AnnotationEditorType.DISABLE + ); +} +class PDFPageViewBuffer { + #buf = new Set(); + #size = 0; + constructor(size) { + this.#size = size; + } + push(view) { + const buf = this.#buf; + if (buf.has(view)) { + buf.delete(view); + } + buf.add(view); + if (buf.size > this.#size) { + this.#destroyFirstView(); + } + } + resize(newSize, idsToKeep = null) { + this.#size = newSize; + const buf = this.#buf; + if (idsToKeep) { + const ii = buf.size; + let i = 1; + for (const view of buf) { + if (idsToKeep.has(view.id)) { + buf.delete(view); + buf.add(view); + } + if (++i > ii) { + break; + } + } + } + while (buf.size > this.#size) { + this.#destroyFirstView(); + } + } + has(view) { + return this.#buf.has(view); + } + [Symbol.iterator]() { + return this.#buf.keys(); + } + #destroyFirstView() { + const firstView = this.#buf.keys().next().value; + firstView?.destroy(); + this.#buf.delete(firstView); + } +} +class PDFViewer { + #buffer = null; + #altTextManager = null; + #annotationEditorHighlightColors = null; + #annotationEditorMode = AnnotationEditorType.NONE; + #annotationEditorUIManager = null; + #annotationMode = AnnotationMode.ENABLE_FORMS; + #containerTopLeft = null; + #editorUndoBar = null; + #enableHWA = false; + #enableHighlightFloatingButton = false; + #enablePermissions = false; + #enableUpdatedAddImage = false; + #enableNewAltTextWhenAddingImage = false; + #eventAbortController = null; + #mlManager = null; + #switchAnnotationEditorModeAC = null; + #switchAnnotationEditorModeTimeoutId = null; + #getAllTextInProgress = false; + #hiddenCopyElement = null; + #interruptCopyCondition = false; + #previousContainerHeight = 0; + #resizeObserver = new ResizeObserver(this.#resizeObserverCallback.bind(this)); + #scrollModePageState = null; + #scaleTimeoutId = null; + #supportsPinchToZoom = true; + #textLayerMode = TextLayerMode.ENABLE; + constructor(options) { + const viewerVersion = "4.10.38"; + if (version !== viewerVersion) { + throw new Error( + `The API version "${version}" does not match the Viewer version "${viewerVersion}".`, + ); + } + this.container = options.container; + this.viewer = options.viewer || options.container.firstElementChild; + if (this.container?.tagName !== "DIV" || this.viewer?.tagName !== "DIV") { + throw new Error("Invalid `container` and/or `viewer` option."); + } + if ( + this.container.offsetParent && + getComputedStyle(this.container).position !== "absolute" + ) { + throw new Error("The `container` must be absolutely positioned."); + } + this.#resizeObserver.observe(this.container); + this.eventBus = options.eventBus; + this.linkService = options.linkService || new SimpleLinkService(); + this.downloadManager = options.downloadManager || null; + this.findController = options.findController || null; + this.#altTextManager = options.altTextManager || null; + this.#editorUndoBar = options.editorUndoBar || null; + if (this.findController) { + this.findController.onIsPageVisible = (pageNumber) => + this._getVisiblePages().ids.has(pageNumber); + } + this._scriptingManager = options.scriptingManager || null; + this.#textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE; + this.#annotationMode = + options.annotationMode ?? AnnotationMode.ENABLE_FORMS; + this.#annotationEditorMode = + options.annotationEditorMode ?? AnnotationEditorType.NONE; + this.#annotationEditorHighlightColors = + options.annotationEditorHighlightColors || null; + this.#enableHighlightFloatingButton = + options.enableHighlightFloatingButton === true; + this.#enableUpdatedAddImage = options.enableUpdatedAddImage === true; + this.#enableNewAltTextWhenAddingImage = + options.enableNewAltTextWhenAddingImage === true; + this.imageResourcesPath = options.imageResourcesPath || ""; + this.enablePrintAutoRotate = options.enablePrintAutoRotate || false; + this.removePageBorders = options.removePageBorders || false; + this.maxCanvasPixels = options.maxCanvasPixels; + this.l10n = options.l10n; + this.l10n ||= new genericl10n_GenericL10n(); + this.#enablePermissions = options.enablePermissions || false; + this.pageColors = options.pageColors || null; + this.#mlManager = options.mlManager || null; + this.#enableHWA = options.enableHWA || false; + this.#supportsPinchToZoom = options.supportsPinchToZoom !== false; + this.defaultRenderingQueue = !options.renderingQueue; + if (this.defaultRenderingQueue) { + this.renderingQueue = new PDFRenderingQueue(); + this.renderingQueue.setViewer(this); + } else { + this.renderingQueue = options.renderingQueue; + } + const { abortSignal } = options; + abortSignal?.addEventListener( + "abort", + () => { + this.#resizeObserver.disconnect(); + this.#resizeObserver = null; + }, + { + once: true, + }, + ); + this.scroll = watchScroll( + this.container, + this._scrollUpdate.bind(this), + abortSignal, + ); + this.presentationModeState = PresentationModeState.UNKNOWN; + this._resetView(); + if (this.removePageBorders) { + this.viewer.classList.add("removePageBorders"); + } + this.#updateContainerHeightCss(); + this.eventBus._on("thumbnailrendered", ({ pageNumber, pdfPage }) => { + const pageView = this._pages[pageNumber - 1]; + if (!this.#buffer.has(pageView)) { + pdfPage?.cleanup(); + } + }); + if (!options.l10n) { + this.l10n.translate(this.container); + } + } + get pagesCount() { + return this._pages.length; + } + getPageView(index) { + return this._pages[index]; + } + getCachedPageViews() { + return new Set(this.#buffer); + } + get pageViewsReady() { + return this._pages.every((pageView) => pageView?.pdfPage); + } + get renderForms() { + return this.#annotationMode === AnnotationMode.ENABLE_FORMS; + } + get enableScripting() { + return !!this._scriptingManager; + } + get currentPageNumber() { + return this._currentPageNumber; + } + set currentPageNumber(val) { + if (!Number.isInteger(val)) { + throw new Error("Invalid page number."); + } + if (!this.pdfDocument) { + return; + } + if (!this._setCurrentPageNumber(val, true)) { + console.error(`currentPageNumber: "${val}" is not a valid page.`); + } + } + _setCurrentPageNumber(val, resetCurrentPageView = false) { + if (this._currentPageNumber === val) { + if (resetCurrentPageView) { + this.#resetCurrentPageView(); + } + return true; + } + if (!(0 < val && val <= this.pagesCount)) { + return false; + } + const previous = this._currentPageNumber; + this._currentPageNumber = val; + this.eventBus.dispatch("pagechanging", { + source: this, + pageNumber: val, + pageLabel: this._pageLabels?.[val - 1] ?? null, + previous, + }); + if (resetCurrentPageView) { + this.#resetCurrentPageView(); + } + return true; + } + get currentPageLabel() { + return this._pageLabels?.[this._currentPageNumber - 1] ?? null; + } + set currentPageLabel(val) { + if (!this.pdfDocument) { + return; + } + let page = val | 0; + if (this._pageLabels) { + const i = this._pageLabels.indexOf(val); + if (i >= 0) { + page = i + 1; + } + } + if (!this._setCurrentPageNumber(page, true)) { + console.error(`currentPageLabel: "${val}" is not a valid page.`); + } + } + get currentScale() { + return this._currentScale !== UNKNOWN_SCALE + ? this._currentScale + : DEFAULT_SCALE; + } + set currentScale(val) { + if (isNaN(val)) { + throw new Error("Invalid numeric scale."); + } + if (!this.pdfDocument) { + return; + } + this.#setScale(val, { + noScroll: false, + }); + } + get currentScaleValue() { + return this._currentScaleValue; + } + set currentScaleValue(val) { + if (!this.pdfDocument) { + return; + } + this.#setScale(val, { + noScroll: false, + }); + } + get pagesRotation() { + return this._pagesRotation; + } + set pagesRotation(rotation) { + if (!isValidRotation(rotation)) { + throw new Error("Invalid pages rotation angle."); + } + if (!this.pdfDocument) { + return; + } + rotation %= 360; + if (rotation < 0) { + rotation += 360; + } + if (this._pagesRotation === rotation) { + return; + } + this._pagesRotation = rotation; + const pageNumber = this._currentPageNumber; + this.refresh(true, { + rotation, + }); + if (this._currentScaleValue) { + this.#setScale(this._currentScaleValue, { + noScroll: true, + }); + } + this.eventBus.dispatch("rotationchanging", { + source: this, + pagesRotation: rotation, + pageNumber, + }); + if (this.defaultRenderingQueue) { + this.update(); + } + } + get firstPagePromise() { + return this.pdfDocument ? this._firstPageCapability.promise : null; + } + get onePageRendered() { + return this.pdfDocument ? this._onePageRenderedCapability.promise : null; + } + get pagesPromise() { + return this.pdfDocument ? this._pagesCapability.promise : null; + } + get _layerProperties() { + const self = this; + return shadow(this, "_layerProperties", { + get annotationEditorUIManager() { + return self.#annotationEditorUIManager; + }, + get annotationStorage() { + return self.pdfDocument?.annotationStorage; + }, + get downloadManager() { + return self.downloadManager; + }, + get enableScripting() { + return !!self._scriptingManager; + }, + get fieldObjectsPromise() { + return self.pdfDocument?.getFieldObjects(); + }, + get findController() { + return self.findController; + }, + get hasJSActionsPromise() { + return self.pdfDocument?.hasJSActions(); + }, + get linkService() { + return self.linkService; + }, + }); + } + #initializePermissions(permissions) { + const params = { + annotationEditorMode: this.#annotationEditorMode, + annotationMode: this.#annotationMode, + textLayerMode: this.#textLayerMode, + }; + if (!permissions) { + return params; + } + if ( + !permissions.includes(PermissionFlag.COPY) && + this.#textLayerMode === TextLayerMode.ENABLE + ) { + params.textLayerMode = TextLayerMode.ENABLE_PERMISSIONS; + } + if (!permissions.includes(PermissionFlag.MODIFY_CONTENTS)) { + params.annotationEditorMode = AnnotationEditorType.DISABLE; + } + if ( + !permissions.includes(PermissionFlag.MODIFY_ANNOTATIONS) && + !permissions.includes(PermissionFlag.FILL_INTERACTIVE_FORMS) && + this.#annotationMode === AnnotationMode.ENABLE_FORMS + ) { + params.annotationMode = AnnotationMode.ENABLE; + } + return params; + } + async #onePageRenderedOrForceFetch(signal) { + if ( + document.visibilityState === "hidden" || + !this.container.offsetParent || + this._getVisiblePages().views.length === 0 + ) { + return; + } + const hiddenCapability = Promise.withResolvers(), + ac = new AbortController(); + document.addEventListener( + "visibilitychange", + () => { + if (document.visibilityState === "hidden") { + hiddenCapability.resolve(); + } + }, + { + signal: + typeof AbortSignal.any === "function" + ? AbortSignal.any([signal, ac.signal]) + : signal, + }, + ); + await Promise.race([ + this._onePageRenderedCapability.promise, + hiddenCapability.promise, + ]); + ac.abort(); + } + async getAllText() { + const texts = []; + const buffer = []; + for ( + let pageNum = 1, pagesCount = this.pdfDocument.numPages; + pageNum <= pagesCount; + ++pageNum + ) { + if (this.#interruptCopyCondition) { + return null; + } + buffer.length = 0; + const page = await this.pdfDocument.getPage(pageNum); + const { items } = await page.getTextContent(); + for (const item of items) { + if (item.str) { + buffer.push(item.str); + } + if (item.hasEOL) { + buffer.push("\n"); + } + } + texts.push(removeNullCharacters(buffer.join(""))); + } + return texts.join("\n"); + } + #copyCallback(textLayerMode, event) { + const selection = document.getSelection(); + const { focusNode, anchorNode } = selection; + if ( + anchorNode && + focusNode && + selection.containsNode(this.#hiddenCopyElement) + ) { + if ( + this.#getAllTextInProgress || + textLayerMode === TextLayerMode.ENABLE_PERMISSIONS + ) { + stopEvent(event); + return; + } + this.#getAllTextInProgress = true; + const { classList } = this.viewer; + classList.add("copyAll"); + const ac = new AbortController(); + window.addEventListener( + "keydown", + (ev) => (this.#interruptCopyCondition = ev.key === "Escape"), + { + signal: ac.signal, + }, + ); + this.getAllText() + .then(async (text) => { + if (text !== null) { + await navigator.clipboard.writeText(text); + } + }) + .catch((reason) => { + console.warn( + `Something goes wrong when extracting the text: ${reason.message}`, + ); + }) + .finally(() => { + this.#getAllTextInProgress = false; + this.#interruptCopyCondition = false; + ac.abort(); + classList.remove("copyAll"); + }); + stopEvent(event); + } + } + setDocument(pdfDocument) { + if (this.pdfDocument) { + this.eventBus.dispatch("pagesdestroy", { + source: this, + }); + this._cancelRendering(); + this._resetView(); + this.findController?.setDocument(null); + this._scriptingManager?.setDocument(null); + this.#annotationEditorUIManager?.destroy(); + this.#annotationEditorUIManager = null; + } + this.pdfDocument = pdfDocument; + if (!pdfDocument) { + return; + } + const pagesCount = pdfDocument.numPages; + const firstPagePromise = pdfDocument.getPage(1); + const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({ + intent: "display", + }); + const permissionsPromise = this.#enablePermissions + ? pdfDocument.getPermissions() + : Promise.resolve(); + const { eventBus, pageColors, viewer } = this; + this.#eventAbortController = new AbortController(); + const { signal } = this.#eventAbortController; + if (pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE) { + console.warn( + "Forcing PAGE-scrolling for performance reasons, given the length of the document.", + ); + const mode = (this._scrollMode = ScrollMode.PAGE); + eventBus.dispatch("scrollmodechanged", { + source: this, + mode, + }); + } + this._pagesCapability.promise.then( + () => { + eventBus.dispatch("pagesloaded", { + source: this, + pagesCount, + }); + }, + () => {}, + ); + const onBeforeDraw = (evt) => { + const pageView = this._pages[evt.pageNumber - 1]; + if (!pageView) { + return; + } + this.#buffer.push(pageView); + }; + eventBus._on("pagerender", onBeforeDraw, { + signal, + }); + const onAfterDraw = (evt) => { + if (evt.cssTransform) { + return; + } + this._onePageRenderedCapability.resolve({ + timestamp: evt.timestamp, + }); + eventBus._off("pagerendered", onAfterDraw); + }; + eventBus._on("pagerendered", onAfterDraw, { + signal, + }); + Promise.all([firstPagePromise, permissionsPromise]) + .then(([firstPdfPage, permissions]) => { + if (pdfDocument !== this.pdfDocument) { + return; + } + this._firstPageCapability.resolve(firstPdfPage); + this._optionalContentConfigPromise = optionalContentConfigPromise; + const { annotationEditorMode, annotationMode, textLayerMode } = + this.#initializePermissions(permissions); + if (textLayerMode !== TextLayerMode.DISABLE) { + const element = (this.#hiddenCopyElement = + document.createElement("div")); + element.id = "hiddenCopyElement"; + viewer.before(element); + } + if ( + typeof AbortSignal.any === "function" && + annotationEditorMode !== AnnotationEditorType.DISABLE + ) { + const mode = annotationEditorMode; + if (pdfDocument.isPureXfa) { + console.warn("Warning: XFA-editing is not implemented."); + } else if (isValidAnnotationEditorMode(mode)) { + this.#annotationEditorUIManager = new AnnotationEditorUIManager( + this.container, + viewer, + this.#altTextManager, + eventBus, + pdfDocument, + pageColors, + this.#annotationEditorHighlightColors, + this.#enableHighlightFloatingButton, + this.#enableUpdatedAddImage, + this.#enableNewAltTextWhenAddingImage, + this.#mlManager, + this.#editorUndoBar, + this.#supportsPinchToZoom, + ); + eventBus.dispatch("annotationeditoruimanager", { + source: this, + uiManager: this.#annotationEditorUIManager, + }); + if (mode !== AnnotationEditorType.NONE) { + if (mode === AnnotationEditorType.STAMP) { + this.#mlManager?.loadModel("altText"); + } + this.#annotationEditorUIManager.updateMode(mode); + } + } else { + console.error(`Invalid AnnotationEditor mode: ${mode}`); + } + } + const viewerElement = + this._scrollMode === ScrollMode.PAGE ? null : viewer; + const scale = this.currentScale; + const viewport = firstPdfPage.getViewport({ + scale: scale * PixelsPerInch.PDF_TO_CSS_UNITS, + }); + viewer.style.setProperty("--scale-factor", viewport.scale); + if (pageColors?.background) { + viewer.style.setProperty("--page-bg-color", pageColors.background); + } + if ( + pageColors?.foreground === "CanvasText" || + pageColors?.background === "Canvas" + ) { + viewer.style.setProperty( + "--hcm-highlight-filter", + pdfDocument.filterFactory.addHighlightHCMFilter( + "highlight", + "CanvasText", + "Canvas", + "HighlightText", + "Highlight", + ), + ); + viewer.style.setProperty( + "--hcm-highlight-selected-filter", + pdfDocument.filterFactory.addHighlightHCMFilter( + "highlight_selected", + "CanvasText", + "Canvas", + "HighlightText", + "ButtonText", + ), + ); + } + for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) { + const pageView = new PDFPageView({ + container: viewerElement, + eventBus, + id: pageNum, + scale, + defaultViewport: viewport.clone(), + optionalContentConfigPromise, + renderingQueue: this.renderingQueue, + textLayerMode, + annotationMode, + imageResourcesPath: this.imageResourcesPath, + maxCanvasPixels: this.maxCanvasPixels, + pageColors, + l10n: this.l10n, + layerProperties: this._layerProperties, + enableHWA: this.#enableHWA, + }); + this._pages.push(pageView); + } + this._pages[0]?.setPdfPage(firstPdfPage); + if (this._scrollMode === ScrollMode.PAGE) { + this.#ensurePageViewVisible(); + } else if (this._spreadMode !== SpreadMode.NONE) { + this._updateSpreadMode(); + } + this.#onePageRenderedOrForceFetch(signal).then(async () => { + if (pdfDocument !== this.pdfDocument) { + return; + } + this.findController?.setDocument(pdfDocument); + this._scriptingManager?.setDocument(pdfDocument); + if (this.#hiddenCopyElement) { + document.addEventListener( + "copy", + this.#copyCallback.bind(this, textLayerMode), + { + signal, + }, + ); + } + if (this.#annotationEditorUIManager) { + eventBus.dispatch("annotationeditormodechanged", { + source: this, + mode: this.#annotationEditorMode, + }); + } + if ( + pdfDocument.loadingParams.disableAutoFetch || + pagesCount > PagesCountLimit.FORCE_LAZY_PAGE_INIT + ) { + this._pagesCapability.resolve(); + return; + } + let getPagesLeft = pagesCount - 1; + if (getPagesLeft <= 0) { + this._pagesCapability.resolve(); + return; + } + for (let pageNum = 2; pageNum <= pagesCount; ++pageNum) { + const promise = pdfDocument.getPage(pageNum).then( + (pdfPage) => { + const pageView = this._pages[pageNum - 1]; + if (!pageView.pdfPage) { + pageView.setPdfPage(pdfPage); + } + if (--getPagesLeft === 0) { + this._pagesCapability.resolve(); + } + }, + (reason) => { + console.error( + `Unable to get page ${pageNum} to initialize viewer`, + reason, + ); + if (--getPagesLeft === 0) { + this._pagesCapability.resolve(); + } + }, + ); + if (pageNum % PagesCountLimit.PAUSE_EAGER_PAGE_INIT === 0) { + await promise; + } + } + }); + eventBus.dispatch("pagesinit", { + source: this, + }); + pdfDocument.getMetadata().then(({ info }) => { + if (pdfDocument !== this.pdfDocument) { + return; + } + if (info.Language) { + viewer.lang = info.Language; + } + }); + if (this.defaultRenderingQueue) { + this.update(); + } + }) + .catch((reason) => { + console.error("Unable to initialize viewer", reason); + this._pagesCapability.reject(reason); + }); + } + setPageLabels(labels) { + if (!this.pdfDocument) { + return; + } + if (!labels) { + this._pageLabels = null; + } else if ( + !(Array.isArray(labels) && this.pdfDocument.numPages === labels.length) + ) { + this._pageLabels = null; + console.error(`setPageLabels: Invalid page labels.`); + } else { + this._pageLabels = labels; + } + for (let i = 0, ii = this._pages.length; i < ii; i++) { + this._pages[i].setPageLabel(this._pageLabels?.[i] ?? null); + } + } + _resetView() { + this._pages = []; + this._currentPageNumber = 1; + this._currentScale = UNKNOWN_SCALE; + this._currentScaleValue = null; + this._pageLabels = null; + this.#buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE); + this._location = null; + this._pagesRotation = 0; + this._optionalContentConfigPromise = null; + this._firstPageCapability = Promise.withResolvers(); + this._onePageRenderedCapability = Promise.withResolvers(); + this._pagesCapability = Promise.withResolvers(); + this._scrollMode = ScrollMode.VERTICAL; + this._previousScrollMode = ScrollMode.UNKNOWN; + this._spreadMode = SpreadMode.NONE; + this.#scrollModePageState = { + previousPageNumber: 1, + scrollDown: true, + pages: [], + }; + this.#eventAbortController?.abort(); + this.#eventAbortController = null; + this.viewer.textContent = ""; + this._updateScrollMode(); + this.viewer.removeAttribute("lang"); + this.#hiddenCopyElement?.remove(); + this.#hiddenCopyElement = null; + this.#cleanupSwitchAnnotationEditorMode(); + } + #ensurePageViewVisible() { + if (this._scrollMode !== ScrollMode.PAGE) { + throw new Error("#ensurePageViewVisible: Invalid scrollMode value."); + } + const pageNumber = this._currentPageNumber, + state = this.#scrollModePageState, + viewer = this.viewer; + viewer.textContent = ""; + state.pages.length = 0; + if (this._spreadMode === SpreadMode.NONE && !this.isInPresentationMode) { + const pageView = this._pages[pageNumber - 1]; + viewer.append(pageView.div); + state.pages.push(pageView); + } else { + const pageIndexSet = new Set(), + parity = this._spreadMode - 1; + if (parity === -1) { + pageIndexSet.add(pageNumber - 1); + } else if (pageNumber % 2 !== parity) { + pageIndexSet.add(pageNumber - 1); + pageIndexSet.add(pageNumber); + } else { + pageIndexSet.add(pageNumber - 2); + pageIndexSet.add(pageNumber - 1); + } + const spread = document.createElement("div"); + spread.className = "spread"; + if (this.isInPresentationMode) { + const dummyPage = document.createElement("div"); + dummyPage.className = "dummyPage"; + spread.append(dummyPage); + } + for (const i of pageIndexSet) { + const pageView = this._pages[i]; + if (!pageView) { + continue; + } + spread.append(pageView.div); + state.pages.push(pageView); + } + viewer.append(spread); + } + state.scrollDown = pageNumber >= state.previousPageNumber; + state.previousPageNumber = pageNumber; + } + _scrollUpdate() { + if (this.pagesCount === 0) { + return; + } + this.update(); + } + #scrollIntoView(pageView, pageSpot = null) { + const { div, id } = pageView; + if (this._currentPageNumber !== id) { + this._setCurrentPageNumber(id); + } + if (this._scrollMode === ScrollMode.PAGE) { + this.#ensurePageViewVisible(); + this.update(); + } + if (!pageSpot && !this.isInPresentationMode) { + const left = div.offsetLeft + div.clientLeft, + right = left + div.clientWidth; + const { scrollLeft, clientWidth } = this.container; + if ( + this._scrollMode === ScrollMode.HORIZONTAL || + left < scrollLeft || + right > scrollLeft + clientWidth + ) { + pageSpot = { + left: 0, + top: 0, + }; + } + } + scrollIntoView(div, pageSpot); + if (!this._currentScaleValue && this._location) { + this._location = null; + } + } + #isSameScale(newScale) { + return ( + newScale === this._currentScale || + Math.abs(newScale - this._currentScale) < 1e-15 + ); + } + #setScaleUpdatePages( + newScale, + newValue, + { noScroll = false, preset = false, drawingDelay = -1, origin = null }, + ) { + this._currentScaleValue = newValue.toString(); + if (this.#isSameScale(newScale)) { + if (preset) { + this.eventBus.dispatch("scalechanging", { + source: this, + scale: newScale, + presetValue: newValue, + }); + } + return; + } + this.viewer.style.setProperty( + "--scale-factor", + newScale * PixelsPerInch.PDF_TO_CSS_UNITS, + ); + const postponeDrawing = drawingDelay >= 0 && drawingDelay < 1000; + this.refresh(true, { + scale: newScale, + drawingDelay: postponeDrawing ? drawingDelay : -1, + }); + if (postponeDrawing) { + this.#scaleTimeoutId = setTimeout(() => { + this.#scaleTimeoutId = null; + this.refresh(); + }, drawingDelay); + } + const previousScale = this._currentScale; + this._currentScale = newScale; + if (!noScroll) { + let page = this._currentPageNumber, + dest; + if ( + this._location && + !(this.isInPresentationMode || this.isChangingPresentationMode) + ) { + page = this._location.pageNumber; + dest = [ + null, + { + name: "XYZ", + }, + this._location.left, + this._location.top, + null, + ]; + } + this.scrollPageIntoView({ + pageNumber: page, + destArray: dest, + allowNegativeOffset: true, + }); + if (Array.isArray(origin)) { + const scaleDiff = newScale / previousScale - 1; + const [top, left] = this.containerTopLeft; + this.container.scrollLeft += (origin[0] - left) * scaleDiff; + this.container.scrollTop += (origin[1] - top) * scaleDiff; + } + } + this.eventBus.dispatch("scalechanging", { + source: this, + scale: newScale, + presetValue: preset ? newValue : undefined, + }); + if (this.defaultRenderingQueue) { + this.update(); + } + } + get #pageWidthScaleFactor() { + if ( + this._spreadMode !== SpreadMode.NONE && + this._scrollMode !== ScrollMode.HORIZONTAL + ) { + return 2; + } + return 1; + } + #setScale(value, options) { + let scale = parseFloat(value); + if (scale > 0) { + options.preset = false; + this.#setScaleUpdatePages(scale, value, options); + } else { + const currentPage = this._pages[this._currentPageNumber - 1]; + if (!currentPage) { + return; + } + let hPadding = SCROLLBAR_PADDING, + vPadding = VERTICAL_PADDING; + if (this.isInPresentationMode) { + hPadding = vPadding = 4; + if (this._spreadMode !== SpreadMode.NONE) { + hPadding *= 2; + } + } else if (this.removePageBorders) { + hPadding = vPadding = 0; + } else if (this._scrollMode === ScrollMode.HORIZONTAL) { + [hPadding, vPadding] = [vPadding, hPadding]; + } + const pageWidthScale = + (((this.container.clientWidth - hPadding) / currentPage.width) * + currentPage.scale) / + this.#pageWidthScaleFactor; + const pageHeightScale = + ((this.container.clientHeight - vPadding) / currentPage.height) * + currentPage.scale; + switch (value) { + case "page-actual": + scale = 1; + break; + case "page-width": + scale = pageWidthScale; + break; + case "page-height": + scale = pageHeightScale; + break; + case "page-fit": + scale = Math.min(pageWidthScale, pageHeightScale); + break; + case "auto": + const horizontalScale = isPortraitOrientation(currentPage) + ? pageWidthScale + : Math.min(pageHeightScale, pageWidthScale); + scale = Math.min(MAX_AUTO_SCALE, horizontalScale); + break; + default: + console.error(`#setScale: "${value}" is an unknown zoom value.`); + return; + } + options.preset = true; + this.#setScaleUpdatePages(scale, value, options); + } + } + #resetCurrentPageView() { + const pageView = this._pages[this._currentPageNumber - 1]; + if (this.isInPresentationMode) { + this.#setScale(this._currentScaleValue, { + noScroll: true, + }); + } + this.#scrollIntoView(pageView); + } + pageLabelToPageNumber(label) { + if (!this._pageLabels) { + return null; + } + const i = this._pageLabels.indexOf(label); + if (i < 0) { + return null; + } + return i + 1; + } + scrollPageIntoView({ + pageNumber, + destArray = null, + allowNegativeOffset = false, + ignoreDestinationZoom = false, + }) { + if (!this.pdfDocument) { + return; + } + const pageView = + Number.isInteger(pageNumber) && this._pages[pageNumber - 1]; + if (!pageView) { + console.error( + `scrollPageIntoView: "${pageNumber}" is not a valid pageNumber parameter.`, + ); + return; + } + if (this.isInPresentationMode || !destArray) { + this._setCurrentPageNumber(pageNumber, true); + return; + } + let x = 0, + y = 0; + let width = 0, + height = 0, + widthScale, + heightScale; + const changeOrientation = pageView.rotation % 180 !== 0; + const pageWidth = + (changeOrientation ? pageView.height : pageView.width) / + pageView.scale / + PixelsPerInch.PDF_TO_CSS_UNITS; + const pageHeight = + (changeOrientation ? pageView.width : pageView.height) / + pageView.scale / + PixelsPerInch.PDF_TO_CSS_UNITS; + let scale = 0; + switch (destArray[1].name) { + case "XYZ": + x = destArray[2]; + y = destArray[3]; + scale = destArray[4]; + x = x !== null ? x : 0; + y = y !== null ? y : pageHeight; + break; + case "Fit": + case "FitB": + scale = "page-fit"; + break; + case "FitH": + case "FitBH": + y = destArray[2]; + scale = "page-width"; + if (y === null && this._location) { + x = this._location.left; + y = this._location.top; + } else if (typeof y !== "number" || y < 0) { + y = pageHeight; + } + break; + case "FitV": + case "FitBV": + x = destArray[2]; + width = pageWidth; + height = pageHeight; + scale = "page-height"; + break; + case "FitR": + x = destArray[2]; + y = destArray[3]; + width = destArray[4] - x; + height = destArray[5] - y; + let hPadding = SCROLLBAR_PADDING, + vPadding = VERTICAL_PADDING; + if (this.removePageBorders) { + hPadding = vPadding = 0; + } + widthScale = + (this.container.clientWidth - hPadding) / + width / + PixelsPerInch.PDF_TO_CSS_UNITS; + heightScale = + (this.container.clientHeight - vPadding) / + height / + PixelsPerInch.PDF_TO_CSS_UNITS; + scale = Math.min(Math.abs(widthScale), Math.abs(heightScale)); + break; + default: + console.error( + `scrollPageIntoView: "${destArray[1].name}" is not a valid destination type.`, + ); + return; + } + if (!ignoreDestinationZoom) { + if (scale && scale !== this._currentScale) { + this.currentScaleValue = scale; + } else if (this._currentScale === UNKNOWN_SCALE) { + this.currentScaleValue = DEFAULT_SCALE_VALUE; + } + } + if (scale === "page-fit" && !destArray[4]) { + this.#scrollIntoView(pageView); + return; + } + const boundingRect = [ + pageView.viewport.convertToViewportPoint(x, y), + pageView.viewport.convertToViewportPoint(x + width, y + height), + ]; + let left = Math.min(boundingRect[0][0], boundingRect[1][0]); + let top = Math.min(boundingRect[0][1], boundingRect[1][1]); + if (!allowNegativeOffset) { + left = Math.max(left, 0); + top = Math.max(top, 0); + } + this.#scrollIntoView(pageView, { + left, + top, + }); + } + _updateLocation(firstPage) { + const currentScale = this._currentScale; + const currentScaleValue = this._currentScaleValue; + const normalizedScaleValue = + parseFloat(currentScaleValue) === currentScale + ? Math.round(currentScale * 10000) / 100 + : currentScaleValue; + const pageNumber = firstPage.id; + const currentPageView = this._pages[pageNumber - 1]; + const container = this.container; + const topLeft = currentPageView.getPagePoint( + container.scrollLeft - firstPage.x, + container.scrollTop - firstPage.y, + ); + const intLeft = Math.round(topLeft[0]); + const intTop = Math.round(topLeft[1]); + let pdfOpenParams = `#page=${pageNumber}`; + if (!this.isInPresentationMode) { + pdfOpenParams += `&zoom=${normalizedScaleValue},${intLeft},${intTop}`; + } + this._location = { + pageNumber, + scale: normalizedScaleValue, + top: intTop, + left: intLeft, + rotation: this._pagesRotation, + pdfOpenParams, + }; + } + update() { + const visible = this._getVisiblePages(); + const visiblePages = visible.views, + numVisiblePages = visiblePages.length; + if (numVisiblePages === 0) { + return; + } + const newCacheSize = Math.max(DEFAULT_CACHE_SIZE, 2 * numVisiblePages + 1); + this.#buffer.resize(newCacheSize, visible.ids); + this.renderingQueue.renderHighestPriority(visible); + const isSimpleLayout = + this._spreadMode === SpreadMode.NONE && + (this._scrollMode === ScrollMode.PAGE || + this._scrollMode === ScrollMode.VERTICAL); + const currentId = this._currentPageNumber; + let stillFullyVisible = false; + for (const page of visiblePages) { + if (page.percent < 100) { + break; + } + if (page.id === currentId && isSimpleLayout) { + stillFullyVisible = true; + break; + } + } + this._setCurrentPageNumber( + stillFullyVisible ? currentId : visiblePages[0].id, + ); + this._updateLocation(visible.first); + this.eventBus.dispatch("updateviewarea", { + source: this, + location: this._location, + }); + } + #switchToEditAnnotationMode() { + const visible = this._getVisiblePages(); + const pagesToRefresh = []; + const { ids, views } = visible; + for (const page of views) { + const { view } = page; + if (!view.hasEditableAnnotations()) { + ids.delete(view.id); + continue; + } + pagesToRefresh.push(page); + } + if (pagesToRefresh.length === 0) { + return null; + } + this.renderingQueue.renderHighestPriority({ + first: pagesToRefresh[0], + last: pagesToRefresh.at(-1), + views: pagesToRefresh, + ids, + }); + return ids; + } + containsElement(element) { + return this.container.contains(element); + } + focus() { + this.container.focus(); + } + get _isContainerRtl() { + return getComputedStyle(this.container).direction === "rtl"; + } + get isInPresentationMode() { + return this.presentationModeState === PresentationModeState.FULLSCREEN; + } + get isChangingPresentationMode() { + return this.presentationModeState === PresentationModeState.CHANGING; + } + get isHorizontalScrollbarEnabled() { + return this.isInPresentationMode + ? false + : this.container.scrollWidth > this.container.clientWidth; + } + get isVerticalScrollbarEnabled() { + return this.isInPresentationMode + ? false + : this.container.scrollHeight > this.container.clientHeight; + } + _getVisiblePages() { + const views = + this._scrollMode === ScrollMode.PAGE + ? this.#scrollModePageState.pages + : this._pages, + horizontal = this._scrollMode === ScrollMode.HORIZONTAL, + rtl = horizontal && this._isContainerRtl; + return getVisibleElements({ + scrollEl: this.container, + views, + sortByVisibility: true, + horizontal, + rtl, + }); + } + cleanup() { + for (const pageView of this._pages) { + if (pageView.renderingState !== RenderingStates.FINISHED) { + pageView.reset(); + } + } + } + _cancelRendering() { + for (const pageView of this._pages) { + pageView.cancelRendering(); + } + } + async #ensurePdfPageLoaded(pageView) { + if (pageView.pdfPage) { + return pageView.pdfPage; + } + try { + const pdfPage = await this.pdfDocument.getPage(pageView.id); + if (!pageView.pdfPage) { + pageView.setPdfPage(pdfPage); + } + return pdfPage; + } catch (reason) { + console.error("Unable to get page for page view", reason); + return null; + } + } + #getScrollAhead(visible) { + if (visible.first?.id === 1) { + return true; + } else if (visible.last?.id === this.pagesCount) { + return false; + } + switch (this._scrollMode) { + case ScrollMode.PAGE: + return this.#scrollModePageState.scrollDown; + case ScrollMode.HORIZONTAL: + return this.scroll.right; + } + return this.scroll.down; + } + forceRendering(currentlyVisiblePages) { + const visiblePages = currentlyVisiblePages || this._getVisiblePages(); + const scrollAhead = this.#getScrollAhead(visiblePages); + const preRenderExtra = + this._spreadMode !== SpreadMode.NONE && + this._scrollMode !== ScrollMode.HORIZONTAL; + const pageView = this.renderingQueue.getHighestPriority( + visiblePages, + this._pages, + scrollAhead, + preRenderExtra, + ); + if (pageView) { + this.#ensurePdfPageLoaded(pageView).then(() => { + this.renderingQueue.renderView(pageView); + }); + return true; + } + return false; + } + get hasEqualPageSizes() { + const firstPageView = this._pages[0]; + for (let i = 1, ii = this._pages.length; i < ii; ++i) { + const pageView = this._pages[i]; + if ( + pageView.width !== firstPageView.width || + pageView.height !== firstPageView.height + ) { + return false; + } + } + return true; + } + getPagesOverview() { + let initialOrientation; + return this._pages.map((pageView) => { + const viewport = pageView.pdfPage.getViewport({ + scale: 1, + }); + const orientation = isPortraitOrientation(viewport); + if (initialOrientation === undefined) { + initialOrientation = orientation; + } else if ( + this.enablePrintAutoRotate && + orientation !== initialOrientation + ) { + return { + width: viewport.height, + height: viewport.width, + rotation: (viewport.rotation - 90) % 360, + }; + } + return { + width: viewport.width, + height: viewport.height, + rotation: viewport.rotation, + }; + }); + } + get optionalContentConfigPromise() { + if (!this.pdfDocument) { + return Promise.resolve(null); + } + if (!this._optionalContentConfigPromise) { + console.error("optionalContentConfigPromise: Not initialized yet."); + return this.pdfDocument.getOptionalContentConfig({ + intent: "display", + }); + } + return this._optionalContentConfigPromise; + } + set optionalContentConfigPromise(promise) { + if (!(promise instanceof Promise)) { + throw new Error(`Invalid optionalContentConfigPromise: ${promise}`); + } + if (!this.pdfDocument) { + return; + } + if (!this._optionalContentConfigPromise) { + return; + } + this._optionalContentConfigPromise = promise; + this.refresh(false, { + optionalContentConfigPromise: promise, + }); + this.eventBus.dispatch("optionalcontentconfigchanged", { + source: this, + promise, + }); + } + get scrollMode() { + return this._scrollMode; + } + set scrollMode(mode) { + if (this._scrollMode === mode) { + return; + } + if (!isValidScrollMode(mode)) { + throw new Error(`Invalid scroll mode: ${mode}`); + } + if (this.pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE) { + return; + } + this._previousScrollMode = this._scrollMode; + this._scrollMode = mode; + this.eventBus.dispatch("scrollmodechanged", { + source: this, + mode, + }); + this._updateScrollMode(this._currentPageNumber); + } + _updateScrollMode(pageNumber = null) { + const scrollMode = this._scrollMode, + viewer = this.viewer; + viewer.classList.toggle( + "scrollHorizontal", + scrollMode === ScrollMode.HORIZONTAL, + ); + viewer.classList.toggle("scrollWrapped", scrollMode === ScrollMode.WRAPPED); + if (!this.pdfDocument || !pageNumber) { + return; + } + if (scrollMode === ScrollMode.PAGE) { + this.#ensurePageViewVisible(); + } else if (this._previousScrollMode === ScrollMode.PAGE) { + this._updateSpreadMode(); + } + if (this._currentScaleValue && isNaN(this._currentScaleValue)) { + this.#setScale(this._currentScaleValue, { + noScroll: true, + }); + } + this._setCurrentPageNumber(pageNumber, true); + this.update(); + } + get spreadMode() { + return this._spreadMode; + } + set spreadMode(mode) { + if (this._spreadMode === mode) { + return; + } + if (!isValidSpreadMode(mode)) { + throw new Error(`Invalid spread mode: ${mode}`); + } + this._spreadMode = mode; + this.eventBus.dispatch("spreadmodechanged", { + source: this, + mode, + }); + this._updateSpreadMode(this._currentPageNumber); + } + _updateSpreadMode(pageNumber = null) { + if (!this.pdfDocument) { + return; + } + const viewer = this.viewer, + pages = this._pages; + if (this._scrollMode === ScrollMode.PAGE) { + this.#ensurePageViewVisible(); + } else { + viewer.textContent = ""; + if (this._spreadMode === SpreadMode.NONE) { + for (const pageView of this._pages) { + viewer.append(pageView.div); + } + } else { + const parity = this._spreadMode - 1; + let spread = null; + for (let i = 0, ii = pages.length; i < ii; ++i) { + if (spread === null) { + spread = document.createElement("div"); + spread.className = "spread"; + viewer.append(spread); + } else if (i % 2 === parity) { + spread = spread.cloneNode(false); + viewer.append(spread); + } + spread.append(pages[i].div); + } + } + } + if (!pageNumber) { + return; + } + if (this._currentScaleValue && isNaN(this._currentScaleValue)) { + this.#setScale(this._currentScaleValue, { + noScroll: true, + }); + } + this._setCurrentPageNumber(pageNumber, true); + this.update(); + } + _getPageAdvance(currentPageNumber, previous = false) { + switch (this._scrollMode) { + case ScrollMode.WRAPPED: { + const { views } = this._getVisiblePages(), + pageLayout = new Map(); + for (const { id, y, percent, widthPercent } of views) { + if (percent === 0 || widthPercent < 100) { + continue; + } + let yArray = pageLayout.get(y); + if (!yArray) { + pageLayout.set(y, (yArray ||= [])); + } + yArray.push(id); + } + for (const yArray of pageLayout.values()) { + const currentIndex = yArray.indexOf(currentPageNumber); + if (currentIndex === -1) { + continue; + } + const numPages = yArray.length; + if (numPages === 1) { + break; + } + if (previous) { + for (let i = currentIndex - 1, ii = 0; i >= ii; i--) { + const currentId = yArray[i], + expectedId = yArray[i + 1] - 1; + if (currentId < expectedId) { + return currentPageNumber - expectedId; + } + } + } else { + for (let i = currentIndex + 1, ii = numPages; i < ii; i++) { + const currentId = yArray[i], + expectedId = yArray[i - 1] + 1; + if (currentId > expectedId) { + return expectedId - currentPageNumber; + } + } + } + if (previous) { + const firstId = yArray[0]; + if (firstId < currentPageNumber) { + return currentPageNumber - firstId + 1; + } + } else { + const lastId = yArray[numPages - 1]; + if (lastId > currentPageNumber) { + return lastId - currentPageNumber + 1; + } + } + break; + } + break; + } + case ScrollMode.HORIZONTAL: { + break; + } + case ScrollMode.PAGE: + case ScrollMode.VERTICAL: { + if (this._spreadMode === SpreadMode.NONE) { + break; + } + const parity = this._spreadMode - 1; + if (previous && currentPageNumber % 2 !== parity) { + break; + } else if (!previous && currentPageNumber % 2 === parity) { + break; + } + const { views } = this._getVisiblePages(), + expectedId = previous ? currentPageNumber - 1 : currentPageNumber + 1; + for (const { id, percent, widthPercent } of views) { + if (id !== expectedId) { + continue; + } + if (percent > 0 && widthPercent === 100) { + return 2; + } + break; + } + break; + } + } + return 1; + } + nextPage() { + const currentPageNumber = this._currentPageNumber, + pagesCount = this.pagesCount; + if (currentPageNumber >= pagesCount) { + return false; + } + const advance = this._getPageAdvance(currentPageNumber, false) || 1; + this.currentPageNumber = Math.min(currentPageNumber + advance, pagesCount); + return true; + } + previousPage() { + const currentPageNumber = this._currentPageNumber; + if (currentPageNumber <= 1) { + return false; + } + const advance = this._getPageAdvance(currentPageNumber, true) || 1; + this.currentPageNumber = Math.max(currentPageNumber - advance, 1); + return true; + } + updateScale({ drawingDelay, scaleFactor = null, steps = null, origin }) { + if (steps === null && scaleFactor === null) { + throw new Error( + "Invalid updateScale options: either `steps` or `scaleFactor` must be provided.", + ); + } + if (!this.pdfDocument) { + return; + } + let newScale = this._currentScale; + if (scaleFactor > 0 && scaleFactor !== 1) { + newScale = Math.round(newScale * scaleFactor * 100) / 100; + } else if (steps) { + const delta = steps > 0 ? DEFAULT_SCALE_DELTA : 1 / DEFAULT_SCALE_DELTA; + const round = steps > 0 ? Math.ceil : Math.floor; + steps = Math.abs(steps); + do { + newScale = round((newScale * delta).toFixed(2) * 10) / 10; + } while (--steps > 0); + } + newScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, newScale)); + this.#setScale(newScale, { + noScroll: false, + drawingDelay, + origin, + }); + } + increaseScale(options = {}) { + this.updateScale({ + ...options, + steps: options.steps ?? 1, + }); + } + decreaseScale(options = {}) { + this.updateScale({ + ...options, + steps: -(options.steps ?? 1), + }); + } + #updateContainerHeightCss(height = this.container.clientHeight) { + if (height !== this.#previousContainerHeight) { + this.#previousContainerHeight = height; + docStyle.setProperty("--viewer-container-height", `${height}px`); + } + } + #resizeObserverCallback(entries) { + for (const entry of entries) { + if (entry.target === this.container) { + this.#updateContainerHeightCss( + Math.floor(entry.borderBoxSize[0].blockSize), + ); + this.#containerTopLeft = null; + break; + } + } + } + get containerTopLeft() { + return (this.#containerTopLeft ||= [ + this.container.offsetTop, + this.container.offsetLeft, + ]); + } + #cleanupSwitchAnnotationEditorMode() { + this.#switchAnnotationEditorModeAC?.abort(); + this.#switchAnnotationEditorModeAC = null; + if (this.#switchAnnotationEditorModeTimeoutId !== null) { + clearTimeout(this.#switchAnnotationEditorModeTimeoutId); + this.#switchAnnotationEditorModeTimeoutId = null; + } + } + get annotationEditorMode() { + return this.#annotationEditorUIManager + ? this.#annotationEditorMode + : AnnotationEditorType.DISABLE; + } + set annotationEditorMode({ mode, editId = null, isFromKeyboard = false }) { + if (!this.#annotationEditorUIManager) { + throw new Error(`The AnnotationEditor is not enabled.`); + } + if (this.#annotationEditorMode === mode) { + return; + } + if (!isValidAnnotationEditorMode(mode)) { + throw new Error(`Invalid AnnotationEditor mode: ${mode}`); + } + if (!this.pdfDocument) { + return; + } + if (mode === AnnotationEditorType.STAMP) { + this.#mlManager?.loadModel("altText"); + } + const { eventBus } = this; + const updater = () => { + this.#cleanupSwitchAnnotationEditorMode(); + this.#annotationEditorMode = mode; + this.#annotationEditorUIManager.updateMode(mode, editId, isFromKeyboard); + eventBus.dispatch("annotationeditormodechanged", { + source: this, + mode, + }); + }; + if ( + mode === AnnotationEditorType.NONE || + this.#annotationEditorMode === AnnotationEditorType.NONE + ) { + const isEditing = mode !== AnnotationEditorType.NONE; + if (!isEditing) { + this.pdfDocument.annotationStorage.resetModifiedIds(); + } + for (const pageView of this._pages) { + pageView.toggleEditingMode(isEditing); + } + const idsToRefresh = this.#switchToEditAnnotationMode(); + if (isEditing && idsToRefresh) { + this.#cleanupSwitchAnnotationEditorMode(); + this.#switchAnnotationEditorModeAC = new AbortController(); + const signal = AbortSignal.any([ + this.#eventAbortController.signal, + this.#switchAnnotationEditorModeAC.signal, + ]); + eventBus._on( + "pagerendered", + ({ pageNumber }) => { + idsToRefresh.delete(pageNumber); + if (idsToRefresh.size === 0) { + this.#switchAnnotationEditorModeTimeoutId = setTimeout( + updater, + 0, + ); + } + }, + { + signal, + }, + ); + return; + } + } + updater(); + } + refresh(noUpdate = false, updateArgs = Object.create(null)) { + if (!this.pdfDocument) { + return; + } + for (const pageView of this._pages) { + pageView.update(updateArgs); + } + if (this.#scaleTimeoutId !== null) { + clearTimeout(this.#scaleTimeoutId); + this.#scaleTimeoutId = null; + } + if (!noUpdate) { + this.update(); + } + } +} // ./web/secondary_toolbar.js + +class SecondaryToolbar { + #opts; + constructor(options, eventBus) { + this.#opts = options; + const buttons = [ + { + element: options.presentationModeButton, + eventName: "presentationmode", + close: true, + }, + { + element: options.printButton, + eventName: "print", + close: true, + }, + { + element: options.downloadButton, + eventName: "download", + close: true, + }, + { + element: options.viewBookmarkButton, + eventName: null, + close: true, + }, + { + element: options.firstPageButton, + eventName: "firstpage", + close: true, + }, + { + element: options.lastPageButton, + eventName: "lastpage", + close: true, + }, + { + element: options.pageRotateCwButton, + eventName: "rotatecw", + close: false, + }, + { + element: options.pageRotateCcwButton, + eventName: "rotateccw", + close: false, + }, + { + element: options.cursorSelectToolButton, + eventName: "switchcursortool", + eventDetails: { + tool: CursorTool.SELECT, + }, + close: true, + }, + { + element: options.cursorHandToolButton, + eventName: "switchcursortool", + eventDetails: { + tool: CursorTool.HAND, + }, + close: true, + }, + { + element: options.scrollPageButton, + eventName: "switchscrollmode", + eventDetails: { + mode: ScrollMode.PAGE, + }, + close: true, + }, + { + element: options.scrollVerticalButton, + eventName: "switchscrollmode", + eventDetails: { + mode: ScrollMode.VERTICAL, + }, + close: true, + }, + { + element: options.scrollHorizontalButton, + eventName: "switchscrollmode", + eventDetails: { + mode: ScrollMode.HORIZONTAL, + }, + close: true, + }, + { + element: options.scrollWrappedButton, + eventName: "switchscrollmode", + eventDetails: { + mode: ScrollMode.WRAPPED, + }, + close: true, + }, + { + element: options.spreadNoneButton, + eventName: "switchspreadmode", + eventDetails: { + mode: SpreadMode.NONE, + }, + close: true, + }, + { + element: options.spreadOddButton, + eventName: "switchspreadmode", + eventDetails: { + mode: SpreadMode.ODD, + }, + close: true, + }, + { + element: options.spreadEvenButton, + eventName: "switchspreadmode", + eventDetails: { + mode: SpreadMode.EVEN, + }, + close: true, + }, + { + element: options.imageAltTextSettingsButton, + eventName: "imagealttextsettings", + close: true, + }, + { + element: options.documentPropertiesButton, + eventName: "documentproperties", + close: true, + }, + ]; + buttons.push({ + element: options.openFileButton, + eventName: "openfile", + close: true, + }); + this.eventBus = eventBus; + this.opened = false; + this.#bindListeners(buttons); + this.reset(); + } + get isOpen() { + return this.opened; + } + setPageNumber(pageNumber) { + this.pageNumber = pageNumber; + this.#updateUIState(); + } + setPagesCount(pagesCount) { + this.pagesCount = pagesCount; + this.#updateUIState(); + } + reset() { + this.pageNumber = 0; + this.pagesCount = 0; + this.#updateUIState(); + this.eventBus.dispatch("switchcursortool", { + source: this, + reset: true, + }); + this.#scrollModeChanged({ + mode: ScrollMode.VERTICAL, + }); + this.#spreadModeChanged({ + mode: SpreadMode.NONE, + }); + } + #updateUIState() { + const { + firstPageButton, + lastPageButton, + pageRotateCwButton, + pageRotateCcwButton, + } = this.#opts; + firstPageButton.disabled = this.pageNumber <= 1; + lastPageButton.disabled = this.pageNumber >= this.pagesCount; + pageRotateCwButton.disabled = this.pagesCount === 0; + pageRotateCcwButton.disabled = this.pagesCount === 0; + } + #bindListeners(buttons) { + const { eventBus } = this; + const { toggleButton } = this.#opts; + toggleButton.addEventListener("click", this.toggle.bind(this)); + for (const { element, eventName, close, eventDetails } of buttons) { + element.addEventListener("click", (evt) => { + if (eventName !== null) { + eventBus.dispatch(eventName, { + source: this, + ...eventDetails, + }); + } + if (close) { + this.close(); + } + eventBus.dispatch("reporttelemetry", { + source: this, + details: { + type: "buttons", + data: { + id: element.id, + }, + }, + }); + }); + } + eventBus._on("cursortoolchanged", this.#cursorToolChanged.bind(this)); + eventBus._on("scrollmodechanged", this.#scrollModeChanged.bind(this)); + eventBus._on("spreadmodechanged", this.#spreadModeChanged.bind(this)); + } + #cursorToolChanged({ tool, disabled }) { + const { cursorSelectToolButton, cursorHandToolButton } = this.#opts; + toggleCheckedBtn(cursorSelectToolButton, tool === CursorTool.SELECT); + toggleCheckedBtn(cursorHandToolButton, tool === CursorTool.HAND); + cursorSelectToolButton.disabled = disabled; + cursorHandToolButton.disabled = disabled; + } + #scrollModeChanged({ mode }) { + const { + scrollPageButton, + scrollVerticalButton, + scrollHorizontalButton, + scrollWrappedButton, + spreadNoneButton, + spreadOddButton, + spreadEvenButton, + } = this.#opts; + toggleCheckedBtn(scrollPageButton, mode === ScrollMode.PAGE); + toggleCheckedBtn(scrollVerticalButton, mode === ScrollMode.VERTICAL); + toggleCheckedBtn(scrollHorizontalButton, mode === ScrollMode.HORIZONTAL); + toggleCheckedBtn(scrollWrappedButton, mode === ScrollMode.WRAPPED); + const forceScrollModePage = + this.pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE; + scrollPageButton.disabled = forceScrollModePage; + scrollVerticalButton.disabled = forceScrollModePage; + scrollHorizontalButton.disabled = forceScrollModePage; + scrollWrappedButton.disabled = forceScrollModePage; + const isHorizontal = mode === ScrollMode.HORIZONTAL; + spreadNoneButton.disabled = isHorizontal; + spreadOddButton.disabled = isHorizontal; + spreadEvenButton.disabled = isHorizontal; + } + #spreadModeChanged({ mode }) { + const { spreadNoneButton, spreadOddButton, spreadEvenButton } = this.#opts; + toggleCheckedBtn(spreadNoneButton, mode === SpreadMode.NONE); + toggleCheckedBtn(spreadOddButton, mode === SpreadMode.ODD); + toggleCheckedBtn(spreadEvenButton, mode === SpreadMode.EVEN); + } + open() { + if (this.opened) { + return; + } + this.opened = true; + const { toggleButton, toolbar } = this.#opts; + toggleExpandedBtn(toggleButton, true, toolbar); + } + close() { + if (!this.opened) { + return; + } + this.opened = false; + const { toggleButton, toolbar } = this.#opts; + toggleExpandedBtn(toggleButton, false, toolbar); + } + toggle() { + if (this.opened) { + this.close(); + } else { + this.open(); + } + } +} // ./web/toolbar.js + +class Toolbar { + #opts; + constructor(options, eventBus, toolbarDensity = 0) { + this.#opts = options; + this.eventBus = eventBus; + const buttons = [ + { + element: options.previous, + eventName: "previouspage", + }, + { + element: options.next, + eventName: "nextpage", + }, + { + element: options.zoomIn, + eventName: "zoomin", + }, + { + element: options.zoomOut, + eventName: "zoomout", + }, + { + element: options.print, + eventName: "print", + }, + { + element: options.download, + eventName: "download", + }, + { + element: options.editorFreeTextButton, + eventName: "switchannotationeditormode", + eventDetails: { + get mode() { + const { classList } = options.editorFreeTextButton; + return classList.contains("toggled") + ? AnnotationEditorType.NONE + : AnnotationEditorType.FREETEXT; + }, + }, + }, + { + element: options.editorHighlightButton, + eventName: "switchannotationeditormode", + eventDetails: { + get mode() { + const { classList } = options.editorHighlightButton; + return classList.contains("toggled") + ? AnnotationEditorType.NONE + : AnnotationEditorType.HIGHLIGHT; + }, + }, + }, + { + element: options.editorInkButton, + eventName: "switchannotationeditormode", + eventDetails: { + get mode() { + const { classList } = options.editorInkButton; + return classList.contains("toggled") + ? AnnotationEditorType.NONE + : AnnotationEditorType.INK; + }, + }, + }, + { + element: options.editorStampButton, + eventName: "switchannotationeditormode", + eventDetails: { + get mode() { + const { classList } = options.editorStampButton; + return classList.contains("toggled") + ? AnnotationEditorType.NONE + : AnnotationEditorType.STAMP; + }, + }, + telemetry: { + type: "editing", + data: { + action: "pdfjs.image.icon_click", + }, + }, + }, + ]; + this.#bindListeners(buttons); + this.#updateToolbarDensity({ + value: toolbarDensity, + }); + this.reset(); + } + #updateToolbarDensity({ value }) { + let name = "normal"; + switch (value) { + case 1: + name = "compact"; + break; + case 2: + name = "touch"; + break; + } + document.documentElement.setAttribute("data-toolbar-density", name); + } + #setAnnotationEditorUIManager(uiManager, parentContainer) { + const colorPicker = new ColorPicker({ + uiManager, + }); + uiManager.setMainHighlightColorPicker(colorPicker); + parentContainer.append(colorPicker.renderMainDropdown()); + } + setPageNumber(pageNumber, pageLabel) { + this.pageNumber = pageNumber; + this.pageLabel = pageLabel; + this.#updateUIState(false); + } + setPagesCount(pagesCount, hasPageLabels) { + this.pagesCount = pagesCount; + this.hasPageLabels = hasPageLabels; + this.#updateUIState(true); + } + setPageScale(pageScaleValue, pageScale) { + this.pageScaleValue = (pageScaleValue || pageScale).toString(); + this.pageScale = pageScale; + this.#updateUIState(false); + } + reset() { + this.pageNumber = 0; + this.pageLabel = null; + this.hasPageLabels = false; + this.pagesCount = 0; + this.pageScaleValue = DEFAULT_SCALE_VALUE; + this.pageScale = DEFAULT_SCALE; + this.#updateUIState(true); + this.updateLoadingIndicatorState(); + this.#editorModeChanged({ + mode: AnnotationEditorType.DISABLE, + }); + } + #bindListeners(buttons) { + const { eventBus } = this; + const { + editorHighlightColorPicker, + editorHighlightButton, + pageNumber, + scaleSelect, + } = this.#opts; + const self = this; + for (const { element, eventName, eventDetails, telemetry } of buttons) { + element.addEventListener("click", (evt) => { + if (eventName !== null) { + eventBus.dispatch(eventName, { + source: this, + ...eventDetails, + isFromKeyboard: evt.detail === 0, + }); + } + if (telemetry) { + eventBus.dispatch("reporttelemetry", { + source: this, + details: telemetry, + }); + } + }); + } + pageNumber.addEventListener("click", function () { + this.select(); + }); + pageNumber.addEventListener("change", function () { + eventBus.dispatch("pagenumberchanged", { + source: self, + value: this.value, + }); + }); + scaleSelect.addEventListener("change", function () { + if (this.value === "custom") { + return; + } + eventBus.dispatch("scalechanged", { + source: self, + value: this.value, + }); + }); + scaleSelect.addEventListener("click", function ({ target }) { + if ( + this.value === self.pageScaleValue && + target.tagName.toUpperCase() === "OPTION" + ) { + this.blur(); + } + }); + scaleSelect.oncontextmenu = noContextMenu; + eventBus._on( + "annotationeditormodechanged", + this.#editorModeChanged.bind(this), + ); + eventBus._on("showannotationeditorui", ({ mode }) => { + switch (mode) { + case AnnotationEditorType.HIGHLIGHT: + editorHighlightButton.click(); + break; + } + }); + eventBus._on("toolbardensity", this.#updateToolbarDensity.bind(this)); + if (editorHighlightColorPicker) { + eventBus._on( + "annotationeditoruimanager", + ({ uiManager }) => { + this.#setAnnotationEditorUIManager( + uiManager, + editorHighlightColorPicker, + ); + }, + { + once: true, + }, + ); + } + } + #editorModeChanged({ mode }) { + const { + editorFreeTextButton, + editorFreeTextParamsToolbar, + editorHighlightButton, + editorHighlightParamsToolbar, + editorInkButton, + editorInkParamsToolbar, + editorStampButton, + editorStampParamsToolbar, + } = this.#opts; + toggleExpandedBtn( + editorFreeTextButton, + mode === AnnotationEditorType.FREETEXT, + editorFreeTextParamsToolbar, + ); + toggleExpandedBtn( + editorHighlightButton, + mode === AnnotationEditorType.HIGHLIGHT, + editorHighlightParamsToolbar, + ); + toggleExpandedBtn( + editorInkButton, + mode === AnnotationEditorType.INK, + editorInkParamsToolbar, + ); + toggleExpandedBtn( + editorStampButton, + mode === AnnotationEditorType.STAMP, + editorStampParamsToolbar, + ); + const isDisable = mode === AnnotationEditorType.DISABLE; + editorFreeTextButton.disabled = isDisable; + editorHighlightButton.disabled = isDisable; + editorInkButton.disabled = isDisable; + editorStampButton.disabled = isDisable; + } + #updateUIState(resetNumPages = false) { + const { pageNumber, pagesCount, pageScaleValue, pageScale } = this; + const opts = this.#opts; + if (resetNumPages) { + if (this.hasPageLabels) { + opts.pageNumber.type = "text"; + opts.numPages.setAttribute("data-l10n-id", "pdfjs-page-of-pages"); + } else { + opts.pageNumber.type = "number"; + opts.numPages.setAttribute("data-l10n-id", "pdfjs-of-pages"); + opts.numPages.setAttribute( + "data-l10n-args", + JSON.stringify({ + pagesCount, + }), + ); + } + opts.pageNumber.max = pagesCount; + } + if (this.hasPageLabels) { + opts.pageNumber.value = this.pageLabel; + opts.numPages.setAttribute( + "data-l10n-args", + JSON.stringify({ + pageNumber, + pagesCount, + }), + ); + } else { + opts.pageNumber.value = pageNumber; + } + opts.previous.disabled = pageNumber <= 1; + opts.next.disabled = pageNumber >= pagesCount; + opts.zoomOut.disabled = pageScale <= MIN_SCALE; + opts.zoomIn.disabled = pageScale >= MAX_SCALE; + let predefinedValueFound = false; + for (const option of opts.scaleSelect.options) { + if (option.value !== pageScaleValue) { + option.selected = false; + continue; + } + option.selected = true; + predefinedValueFound = true; + } + if (!predefinedValueFound) { + opts.customScaleOption.selected = true; + opts.customScaleOption.setAttribute( + "data-l10n-args", + JSON.stringify({ + scale: Math.round(pageScale * 10000) / 100, + }), + ); + } + } + updateLoadingIndicatorState(loading = false) { + const { pageNumber } = this.#opts; + pageNumber.classList.toggle("loading", loading); + } +} // ./web/view_history.js + +const DEFAULT_VIEW_HISTORY_CACHE_SIZE = 20; +class ViewHistory { + constructor(fingerprint, cacheSize = DEFAULT_VIEW_HISTORY_CACHE_SIZE) { + this.fingerprint = fingerprint; + this.cacheSize = cacheSize; + this._initializedPromise = this._readFromStorage().then((databaseStr) => { + const database = JSON.parse(databaseStr || "{}"); + let index = -1; + if (!Array.isArray(database.files)) { + database.files = []; + } else { + while (database.files.length >= this.cacheSize) { + database.files.shift(); + } + for (let i = 0, ii = database.files.length; i < ii; i++) { + const branch = database.files[i]; + if (branch.fingerprint === this.fingerprint) { + index = i; + break; + } + } + } + if (index === -1) { + index = + database.files.push({ + fingerprint: this.fingerprint, + }) - 1; + } + this.file = database.files[index]; + this.database = database; + }); + } + async _writeToStorage() { + const databaseStr = JSON.stringify(this.database); + localStorage.setItem("pdfjs.history", databaseStr); + } + async _readFromStorage() { + return localStorage.getItem("pdfjs.history"); + } + async set(name, val) { + await this._initializedPromise; + this.file[name] = val; + return this._writeToStorage(); + } + async setMultiple(properties) { + await this._initializedPromise; + for (const name in properties) { + this.file[name] = properties[name]; + } + return this._writeToStorage(); + } + async get(name, defaultValue) { + await this._initializedPromise; + const val = this.file[name]; + return val !== undefined ? val : defaultValue; + } + async getMultiple(properties) { + await this._initializedPromise; + const values = Object.create(null); + for (const name in properties) { + const val = this.file[name]; + values[name] = val !== undefined ? val : properties[name]; + } + return values; + } +} // ./web/app.js + +const FORCE_PAGES_LOADED_TIMEOUT = 10000; +const ViewOnLoad = { + UNKNOWN: -1, + PREVIOUS: 0, + INITIAL: 1, +}; +const PDFViewerApplication = { + initialBookmark: document.location.hash.substring(1), + _initializedCapability: { + ...Promise.withResolvers(), + settled: false, + }, + appConfig: null, + pdfDocument: null, + pdfLoadingTask: null, + printService: null, + pdfViewer: null, + pdfThumbnailViewer: null, + pdfRenderingQueue: null, + pdfPresentationMode: null, + pdfDocumentProperties: null, + pdfLinkService: null, + pdfHistory: null, + pdfSidebar: null, + pdfOutlineViewer: null, + pdfAttachmentViewer: null, + pdfLayerViewer: null, + pdfCursorTools: null, + pdfScriptingManager: null, + store: null, + downloadManager: null, + overlayManager: null, + preferences: new Preferences(), + toolbar: null, + secondaryToolbar: null, + eventBus: null, + l10n: null, + annotationEditorParams: null, + imageAltTextSettings: null, + isInitialViewSet: false, + isViewerEmbedded: window.parent !== window, + url: "", + baseUrl: "", + mlManager: null, + _downloadUrl: "", + _eventBusAbortController: null, + _windowAbortController: null, + _globalAbortController: new AbortController(), + documentInfo: null, + metadata: null, + _contentDispositionFilename: null, + _contentLength: null, + _saveInProgress: false, + _wheelUnusedTicks: 0, + _wheelUnusedFactor: 1, + _touchManager: null, + _touchUnusedTicks: 0, + _touchUnusedFactor: 1, + _PDFBug: null, + _hasAnnotationEditors: false, + _title: document.title, + _printAnnotationStoragePromise: null, + _isCtrlKeyDown: false, + _caretBrowsing: null, + _isScrolling: false, + editorUndoBar: null, + async initialize(appConfig) { + this.appConfig = appConfig; + try { + await this.preferences.initializedPromise; + } catch (ex) { + console.error("initialize:", ex); + } + if (AppOptions.get("pdfBugEnabled")) { + await this._parseHashParams(); + } + let mode; + switch (AppOptions.get("viewerCssTheme")) { + case 1: + mode = "is-light"; + break; + case 2: + mode = "is-dark"; + break; + } + if (mode) { + document.documentElement.classList.add(mode); + } + this.l10n = await this.externalServices.createL10n(); + document.getElementsByTagName("html")[0].dir = this.l10n.getDirection(); + this.l10n.translate(appConfig.appContainer || document.documentElement); + if ( + this.isViewerEmbedded && + AppOptions.get("externalLinkTarget") === LinkTarget.NONE + ) { + AppOptions.set("externalLinkTarget", LinkTarget.TOP); + } + await this._initializeViewerComponents(); + this.bindEvents(); + this.bindWindowEvents(); + this._initializedCapability.settled = true; + this._initializedCapability.resolve(); + }, + async _parseHashParams() { + const hash = document.location.hash.substring(1); + if (!hash) { + return; + } + const { mainContainer, viewerContainer } = this.appConfig, + params = parseQueryString(hash); + const loadPDFBug = async () => { + if (this._PDFBug) { + return; + } + const { PDFBug } = await import( + /*webpackIgnore: true*/ AppOptions.get("debuggerSrc") + ); + this._PDFBug = PDFBug; + }; + if (params.get("disableworker") === "true") { + try { + GlobalWorkerOptions.workerSrc ||= AppOptions.get("workerSrc"); + await import(/*webpackIgnore: true*/ PDFWorker.workerSrc); + } catch (ex) { + console.error("_parseHashParams:", ex); + } + } + if (params.has("textlayer")) { + switch (params.get("textlayer")) { + case "off": + AppOptions.set("textLayerMode", TextLayerMode.DISABLE); + break; + case "visible": + case "shadow": + case "hover": + viewerContainer.classList.add(`textLayer-${params.get("textlayer")}`); + try { + await loadPDFBug(); + this._PDFBug.loadCSS(); + } catch (ex) { + console.error("_parseHashParams:", ex); + } + break; + } + } + if (params.has("pdfbug")) { + AppOptions.setAll({ + pdfBug: true, + fontExtraProperties: true, + }); + const enabled = params.get("pdfbug").split(","); + try { + await loadPDFBug(); + this._PDFBug.init(mainContainer, enabled); + } catch (ex) { + console.error("_parseHashParams:", ex); + } + } + if (params.has("locale")) { + AppOptions.set("localeProperties", { + lang: params.get("locale"), + }); + } + const opts = { + disableAutoFetch: (x) => x === "true", + disableFontFace: (x) => x === "true", + disableHistory: (x) => x === "true", + disableRange: (x) => x === "true", + disableStream: (x) => x === "true", + verbosity: (x) => x | 0, + }; + for (const name in opts) { + const check = opts[name], + key = name.toLowerCase(); + if (params.has(key)) { + AppOptions.set(name, check(params.get(key))); + } + } + }, + async _initializeViewerComponents() { + const { appConfig, externalServices, l10n } = this; + const eventBus = new EventBus(); + this.eventBus = AppOptions.eventBus = eventBus; + this.mlManager?.setEventBus(eventBus, this._globalAbortController.signal); + this.overlayManager = new OverlayManager(); + const pdfRenderingQueue = new PDFRenderingQueue(); + pdfRenderingQueue.onIdle = this._cleanup.bind(this); + this.pdfRenderingQueue = pdfRenderingQueue; + const pdfLinkService = new PDFLinkService({ + eventBus, + externalLinkTarget: AppOptions.get("externalLinkTarget"), + externalLinkRel: AppOptions.get("externalLinkRel"), + ignoreDestinationZoom: AppOptions.get("ignoreDestinationZoom"), + }); + this.pdfLinkService = pdfLinkService; + const downloadManager = (this.downloadManager = new DownloadManager()); + const findController = new PDFFindController({ + linkService: pdfLinkService, + eventBus, + updateMatchesCountOnProgress: true, + }); + this.findController = findController; + const pdfScriptingManager = new PDFScriptingManager({ + eventBus, + externalServices, + docProperties: this._scriptingDocProperties.bind(this), + }); + this.pdfScriptingManager = pdfScriptingManager; + const container = appConfig.mainContainer, + viewer = appConfig.viewerContainer; + const annotationEditorMode = AppOptions.get("annotationEditorMode"); + const pageColors = + AppOptions.get("forcePageColors") || + window.matchMedia("(forced-colors: active)").matches + ? { + background: AppOptions.get("pageColorsBackground"), + foreground: AppOptions.get("pageColorsForeground"), + } + : null; + let altTextManager; + if (AppOptions.get("enableUpdatedAddImage")) { + altTextManager = appConfig.newAltTextDialog + ? new NewAltTextManager( + appConfig.newAltTextDialog, + this.overlayManager, + eventBus, + ) + : null; + } else { + altTextManager = appConfig.altTextDialog + ? new AltTextManager( + appConfig.altTextDialog, + container, + this.overlayManager, + eventBus, + ) + : null; + } + if (appConfig.editorUndoBar) { + this.editorUndoBar = new EditorUndoBar(appConfig.editorUndoBar, eventBus); + } + const enableHWA = AppOptions.get("enableHWA"); + const pdfViewer = new PDFViewer({ + container, + viewer, + eventBus, + renderingQueue: pdfRenderingQueue, + linkService: pdfLinkService, + downloadManager, + altTextManager, + editorUndoBar: this.editorUndoBar, + findController, + scriptingManager: + AppOptions.get("enableScripting") && pdfScriptingManager, + l10n, + textLayerMode: AppOptions.get("textLayerMode"), + annotationMode: AppOptions.get("annotationMode"), + annotationEditorMode, + annotationEditorHighlightColors: AppOptions.get("highlightEditorColors"), + enableHighlightFloatingButton: AppOptions.get( + "enableHighlightFloatingButton", + ), + enableUpdatedAddImage: AppOptions.get("enableUpdatedAddImage"), + enableNewAltTextWhenAddingImage: AppOptions.get( + "enableNewAltTextWhenAddingImage", + ), + imageResourcesPath: AppOptions.get("imageResourcesPath"), + enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"), + maxCanvasPixels: AppOptions.get("maxCanvasPixels"), + enablePermissions: AppOptions.get("enablePermissions"), + pageColors, + mlManager: this.mlManager, + abortSignal: this._globalAbortController.signal, + enableHWA, + supportsPinchToZoom: this.supportsPinchToZoom, + }); + this.pdfViewer = pdfViewer; + pdfRenderingQueue.setViewer(pdfViewer); + pdfLinkService.setViewer(pdfViewer); + pdfScriptingManager.setViewer(pdfViewer); + if (appConfig.sidebar?.thumbnailView) { + this.pdfThumbnailViewer = new PDFThumbnailViewer({ + container: appConfig.sidebar.thumbnailView, + eventBus, + renderingQueue: pdfRenderingQueue, + linkService: pdfLinkService, + pageColors, + abortSignal: this._globalAbortController.signal, + enableHWA, + }); + pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer); + } + if (!this.isViewerEmbedded && !AppOptions.get("disableHistory")) { + this.pdfHistory = new PDFHistory({ + linkService: pdfLinkService, + eventBus, + }); + pdfLinkService.setHistory(this.pdfHistory); + } + if (!this.supportsIntegratedFind && appConfig.findBar) { + this.findBar = new PDFFindBar( + appConfig.findBar, + appConfig.principalContainer, + eventBus, + ); + } + if (appConfig.annotationEditorParams) { + if ( + typeof AbortSignal.any === "function" && + annotationEditorMode !== AnnotationEditorType.DISABLE + ) { + this.annotationEditorParams = new AnnotationEditorParams( + appConfig.annotationEditorParams, + eventBus, + ); + } else { + for (const id of ["editorModeButtons", "editorModeSeparator"]) { + document.getElementById(id)?.classList.add("hidden"); + } + } + } + if ( + this.mlManager && + appConfig.secondaryToolbar?.imageAltTextSettingsButton + ) { + this.imageAltTextSettings = new ImageAltTextSettings( + appConfig.altTextSettingsDialog, + this.overlayManager, + eventBus, + this.mlManager, + ); + } + if (appConfig.documentProperties) { + this.pdfDocumentProperties = new PDFDocumentProperties( + appConfig.documentProperties, + this.overlayManager, + eventBus, + l10n, + () => this._docFilename, + ); + } + if (appConfig.secondaryToolbar?.cursorHandToolButton) { + this.pdfCursorTools = new PDFCursorTools({ + container, + eventBus, + cursorToolOnLoad: AppOptions.get("cursorToolOnLoad"), + }); + } + if (appConfig.toolbar) { + this.toolbar = new Toolbar( + appConfig.toolbar, + eventBus, + AppOptions.get("toolbarDensity"), + ); + } + if (appConfig.secondaryToolbar) { + if (AppOptions.get("enableAltText")) { + appConfig.secondaryToolbar.imageAltTextSettingsButton?.classList.remove( + "hidden", + ); + appConfig.secondaryToolbar.imageAltTextSettingsSeparator?.classList.remove( + "hidden", + ); + } + this.secondaryToolbar = new SecondaryToolbar( + appConfig.secondaryToolbar, + eventBus, + ); + } + if ( + this.supportsFullscreen && + appConfig.secondaryToolbar?.presentationModeButton + ) { + this.pdfPresentationMode = new PDFPresentationMode({ + container, + pdfViewer, + eventBus, + }); + } + if (appConfig.passwordOverlay) { + this.passwordPrompt = new PasswordPrompt( + appConfig.passwordOverlay, + this.overlayManager, + this.isViewerEmbedded, + ); + } + if (appConfig.sidebar?.outlineView) { + this.pdfOutlineViewer = new PDFOutlineViewer({ + container: appConfig.sidebar.outlineView, + eventBus, + l10n, + linkService: pdfLinkService, + downloadManager, + }); + } + if (appConfig.sidebar?.attachmentsView) { + this.pdfAttachmentViewer = new PDFAttachmentViewer({ + container: appConfig.sidebar.attachmentsView, + eventBus, + l10n, + downloadManager, + }); + } + if (appConfig.sidebar?.layersView) { + this.pdfLayerViewer = new PDFLayerViewer({ + container: appConfig.sidebar.layersView, + eventBus, + l10n, + }); + } + if (appConfig.sidebar) { + this.pdfSidebar = new PDFSidebar({ + elements: appConfig.sidebar, + eventBus, + l10n, + }); + this.pdfSidebar.onToggled = this.forceRendering.bind(this); + this.pdfSidebar.onUpdateThumbnails = () => { + for (const pageView of pdfViewer.getCachedPageViews()) { + if (pageView.renderingState === RenderingStates.FINISHED) { + this.pdfThumbnailViewer + .getThumbnail(pageView.id - 1) + ?.setImage(pageView); + } + } + this.pdfThumbnailViewer.scrollThumbnailIntoView( + pdfViewer.currentPageNumber, + ); + }; + } + }, + async run(config) { + await this.initialize(config); + const { appConfig, eventBus } = this; + let file; + const queryString = document.location.search.substring(1); + const params = parseQueryString(queryString); + file = params.get("file") ?? AppOptions.get("defaultUrl"); + validateFileURL(file); + const fileInput = (this._openFileInput = document.createElement("input")); + fileInput.id = "fileInput"; + fileInput.hidden = true; + fileInput.type = "file"; + fileInput.value = null; + document.body.append(fileInput); + fileInput.addEventListener("change", function (evt) { + const { files } = evt.target; + if (!files || files.length === 0) { + return; + } + eventBus.dispatch("fileinputchange", { + source: this, + fileInput: evt.target, + }); + }); + appConfig.mainContainer.addEventListener("dragover", function (evt) { + for (const item of evt.dataTransfer.items) { + if (item.type === "application/pdf") { + evt.dataTransfer.dropEffect = + evt.dataTransfer.effectAllowed === "copy" ? "copy" : "move"; + stopEvent(evt); + return; + } + } + }); + appConfig.mainContainer.addEventListener("drop", function (evt) { + if (evt.dataTransfer.files?.[0].type !== "application/pdf") { + return; + } + stopEvent(evt); + eventBus.dispatch("fileinputchange", { + source: this, + fileInput: evt.dataTransfer, + }); + }); + if (!AppOptions.get("supportsDocumentFonts")) { + AppOptions.set("disableFontFace", true); + this.l10n.get("pdfjs-web-fonts-disabled").then((msg) => { + console.warn(msg); + }); + } + if (!this.supportsPrinting) { + appConfig.toolbar?.print?.classList.add("hidden"); + appConfig.secondaryToolbar?.printButton.classList.add("hidden"); + } + if (!this.supportsFullscreen) { + appConfig.secondaryToolbar?.presentationModeButton.classList.add( + "hidden", + ); + } + if (this.supportsIntegratedFind) { + appConfig.findBar?.toggleButton?.classList.add("hidden"); + } + if (file) { + this.open({ + url: file, + }); + } else { + this._hideViewBookmark(); + } + }, + get externalServices() { + return shadow(this, "externalServices", new ExternalServices()); + }, + get initialized() { + return this._initializedCapability.settled; + }, + get initializedPromise() { + return this._initializedCapability.promise; + }, + updateZoom(steps, scaleFactor, origin) { + if (this.pdfViewer.isInPresentationMode) { + return; + } + this.pdfViewer.updateScale({ + drawingDelay: AppOptions.get("defaultZoomDelay"), + steps, + scaleFactor, + origin, + }); + }, + zoomIn() { + this.updateZoom(1); + }, + zoomOut() { + this.updateZoom(-1); + }, + zoomReset() { + if (this.pdfViewer.isInPresentationMode) { + return; + } + this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE; + }, + touchPinchCallback(origin, prevDistance, distance) { + if (this.supportsPinchToZoom) { + const newScaleFactor = this._accumulateFactor( + this.pdfViewer.currentScale, + distance / prevDistance, + "_touchUnusedFactor", + ); + this.updateZoom(null, newScaleFactor, origin); + } else { + const PIXELS_PER_LINE_SCALE = 30; + const ticks = this._accumulateTicks( + (distance - prevDistance) / PIXELS_PER_LINE_SCALE, + "_touchUnusedTicks", + ); + this.updateZoom(ticks, null, origin); + } + }, + touchPinchEndCallback() { + this._touchUnusedTicks = 0; + this._touchUnusedFactor = 1; + }, + get pagesCount() { + return this.pdfDocument ? this.pdfDocument.numPages : 0; + }, + get page() { + return this.pdfViewer.currentPageNumber; + }, + set page(val) { + this.pdfViewer.currentPageNumber = val; + }, + get supportsPrinting() { + return PDFPrintServiceFactory.supportsPrinting; + }, + get supportsFullscreen() { + return shadow(this, "supportsFullscreen", document.fullscreenEnabled); + }, + get supportsPinchToZoom() { + return shadow( + this, + "supportsPinchToZoom", + AppOptions.get("supportsPinchToZoom"), + ); + }, + get supportsIntegratedFind() { + return shadow( + this, + "supportsIntegratedFind", + AppOptions.get("supportsIntegratedFind"), + ); + }, + get loadingBar() { + const barElement = document.getElementById("loadingBar"); + const bar = barElement ? new ProgressBar(barElement) : null; + return shadow(this, "loadingBar", bar); + }, + get supportsMouseWheelZoomCtrlKey() { + return shadow( + this, + "supportsMouseWheelZoomCtrlKey", + AppOptions.get("supportsMouseWheelZoomCtrlKey"), + ); + }, + get supportsMouseWheelZoomMetaKey() { + return shadow( + this, + "supportsMouseWheelZoomMetaKey", + AppOptions.get("supportsMouseWheelZoomMetaKey"), + ); + }, + get supportsCaretBrowsingMode() { + return AppOptions.get("supportsCaretBrowsingMode"); + }, + moveCaret(isUp, select) { + this._caretBrowsing ||= new CaretBrowsingMode( + this._globalAbortController.signal, + this.appConfig.mainContainer, + this.appConfig.viewerContainer, + this.appConfig.toolbar?.container, + ); + this._caretBrowsing.moveCaret(isUp, select); + }, + setTitleUsingUrl(url = "", downloadUrl = null) { + this.url = url; + this.baseUrl = url.split("#", 1)[0]; + if (downloadUrl) { + this._downloadUrl = + downloadUrl === url ? this.baseUrl : downloadUrl.split("#", 1)[0]; + } + if (isDataScheme(url)) { + this._hideViewBookmark(); + } + let title = pdfjs_getPdfFilenameFromUrl(url, ""); + if (!title) { + try { + title = decodeURIComponent(getFilenameFromUrl(url)); + } catch {} + } + this.setTitle(title || url); + }, + setTitle(title = this._title) { + this._title = title; + if (this.isViewerEmbedded) { + return; + } + const editorIndicator = + this._hasAnnotationEditors && !this.pdfRenderingQueue.printing; + document.title = `${editorIndicator ? "* " : ""}${title}`; + }, + get _docFilename() { + return ( + this._contentDispositionFilename || pdfjs_getPdfFilenameFromUrl(this.url) + ); + }, + _hideViewBookmark() { + const { secondaryToolbar } = this.appConfig; + secondaryToolbar?.viewBookmarkButton.classList.add("hidden"); + if (secondaryToolbar?.presentationModeButton.classList.contains("hidden")) { + document.getElementById("viewBookmarkSeparator")?.classList.add("hidden"); + } + }, + async close() { + this._unblockDocumentLoadEvent(); + this._hideViewBookmark(); + if (!this.pdfLoadingTask) { + return; + } + if ( + this.pdfDocument?.annotationStorage.size > 0 && + this._annotationStorageModified + ) { + try { + await this.save(); + } catch {} + } + const promises = []; + promises.push(this.pdfLoadingTask.destroy()); + this.pdfLoadingTask = null; + if (this.pdfDocument) { + this.pdfDocument = null; + this.pdfThumbnailViewer?.setDocument(null); + this.pdfViewer.setDocument(null); + this.pdfLinkService.setDocument(null); + this.pdfDocumentProperties?.setDocument(null); + } + this.pdfLinkService.externalLinkEnabled = true; + this.store = null; + this.isInitialViewSet = false; + this.url = ""; + this.baseUrl = ""; + this._downloadUrl = ""; + this.documentInfo = null; + this.metadata = null; + this._contentDispositionFilename = null; + this._contentLength = null; + this._saveInProgress = false; + this._hasAnnotationEditors = false; + promises.push( + this.pdfScriptingManager.destroyPromise, + this.passwordPrompt.close(), + ); + this.setTitle(); + this.pdfSidebar?.reset(); + this.pdfOutlineViewer?.reset(); + this.pdfAttachmentViewer?.reset(); + this.pdfLayerViewer?.reset(); + this.pdfHistory?.reset(); + this.findBar?.reset(); + this.toolbar?.reset(); + this.secondaryToolbar?.reset(); + this._PDFBug?.cleanup(); + await Promise.all(promises); + }, + async open(args) { + if (this.pdfLoadingTask) { + await this.close(); + } + const workerParams = AppOptions.getAll(OptionKind.WORKER); + Object.assign(GlobalWorkerOptions, workerParams); + if (args.url) { + this.setTitleUsingUrl(args.originalUrl || args.url, args.url); + } + const apiParams = AppOptions.getAll(OptionKind.API); + const loadingTask = getDocument({ + ...apiParams, + ...args, + }); + this.pdfLoadingTask = loadingTask; + loadingTask.onPassword = (updateCallback, reason) => { + if (this.isViewerEmbedded) { + this._unblockDocumentLoadEvent(); + } + this.pdfLinkService.externalLinkEnabled = false; + this.passwordPrompt.setUpdateCallback(updateCallback, reason); + this.passwordPrompt.open(); + }; + loadingTask.onProgress = ({ loaded, total }) => { + this.progress(loaded / total); + }; + return loadingTask.promise.then( + (pdfDocument) => { + this.load(pdfDocument); + }, + (reason) => { + if (loadingTask !== this.pdfLoadingTask) { + return undefined; + } + let key = "pdfjs-loading-error"; + if (reason instanceof InvalidPDFException) { + key = "pdfjs-invalid-file-error"; + } else if (reason instanceof MissingPDFException) { + key = "pdfjs-missing-file-error"; + } else if (reason instanceof UnexpectedResponseException) { + key = "pdfjs-unexpected-response-error"; + } + return this._documentError(key, { + message: reason.message, + }).then(() => { + throw reason; + }); + }, + ); + }, + async download() { + let data; + try { + data = await this.pdfDocument.getData(); + } catch {} + this.downloadManager.download(data, this._downloadUrl, this._docFilename); + }, + async save() { + if (this._saveInProgress) { + return; + } + this._saveInProgress = true; + await this.pdfScriptingManager.dispatchWillSave(); + try { + const data = await this.pdfDocument.saveDocument(); + this.downloadManager.download(data, this._downloadUrl, this._docFilename); + } catch (reason) { + console.error(`Error when saving the document:`, reason); + await this.download(); + } finally { + await this.pdfScriptingManager.dispatchDidSave(); + this._saveInProgress = false; + } + if (this._hasAnnotationEditors) { + this.externalServices.reportTelemetry({ + type: "editing", + data: { + type: "save", + stats: this.pdfDocument?.annotationStorage.editorStats, + }, + }); + } + }, + async downloadOrSave() { + const { classList } = this.appConfig.appContainer; + classList.add("wait"); + await (this.pdfDocument?.annotationStorage.size > 0 + ? this.save() + : this.download()); + classList.remove("wait"); + }, + async _documentError(key, moreInfo = null) { + this._unblockDocumentLoadEvent(); + const message = await this._otherError( + key || "pdfjs-loading-error", + moreInfo, + ); + this.eventBus.dispatch("documenterror", { + source: this, + message, + reason: moreInfo?.message ?? null, + }); + }, + async _otherError(key, moreInfo = null) { + const message = await this.l10n.get(key); + const moreInfoText = [`PDF.js v${version || "?"} (build: ${build || "?"})`]; + if (moreInfo) { + moreInfoText.push(`Message: ${moreInfo.message}`); + if (moreInfo.stack) { + moreInfoText.push(`Stack: ${moreInfo.stack}`); + } else { + if (moreInfo.filename) { + moreInfoText.push(`File: ${moreInfo.filename}`); + } + if (moreInfo.lineNumber) { + moreInfoText.push(`Line: ${moreInfo.lineNumber}`); + } + } + } + console.error(`${message}\n\n${moreInfoText.join("\n")}`); + return message; + }, + progress(level) { + const percent = Math.round(level * 100); + if (!this.loadingBar || percent <= this.loadingBar.percent) { + return; + } + this.loadingBar.percent = percent; + if ( + this.pdfDocument?.loadingParams.disableAutoFetch ?? + AppOptions.get("disableAutoFetch") + ) { + this.loadingBar.setDisableAutoFetch(); + } + }, + load(pdfDocument) { + this.pdfDocument = pdfDocument; + pdfDocument.getDownloadInfo().then(({ length }) => { + this._contentLength = length; + this.loadingBar?.hide(); + firstPagePromise.then(() => { + this.eventBus.dispatch("documentloaded", { + source: this, + }); + }); + }); + const pageLayoutPromise = pdfDocument.getPageLayout().catch(() => {}); + const pageModePromise = pdfDocument.getPageMode().catch(() => {}); + const openActionPromise = pdfDocument.getOpenAction().catch(() => {}); + this.toolbar?.setPagesCount(pdfDocument.numPages, false); + this.secondaryToolbar?.setPagesCount(pdfDocument.numPages); + this.pdfLinkService.setDocument(pdfDocument); + this.pdfDocumentProperties?.setDocument(pdfDocument); + const pdfViewer = this.pdfViewer; + pdfViewer.setDocument(pdfDocument); + const { firstPagePromise, onePageRendered, pagesPromise } = pdfViewer; + this.pdfThumbnailViewer?.setDocument(pdfDocument); + const storedPromise = (this.store = new ViewHistory( + pdfDocument.fingerprints[0], + )) + .getMultiple({ + page: null, + zoom: DEFAULT_SCALE_VALUE, + scrollLeft: "0", + scrollTop: "0", + rotation: null, + sidebarView: SidebarView.UNKNOWN, + scrollMode: ScrollMode.UNKNOWN, + spreadMode: SpreadMode.UNKNOWN, + }) + .catch(() => {}); + firstPagePromise.then((pdfPage) => { + this.loadingBar?.setWidth(this.appConfig.viewerContainer); + this._initializeAnnotationStorageCallbacks(pdfDocument); + Promise.all([ + animationStarted, + storedPromise, + pageLayoutPromise, + pageModePromise, + openActionPromise, + ]) + .then(async ([timeStamp, stored, pageLayout, pageMode, openAction]) => { + const viewOnLoad = AppOptions.get("viewOnLoad"); + this._initializePdfHistory({ + fingerprint: pdfDocument.fingerprints[0], + viewOnLoad, + initialDest: openAction?.dest, + }); + const initialBookmark = this.initialBookmark; + const zoom = AppOptions.get("defaultZoomValue"); + let hash = zoom ? `zoom=${zoom}` : null; + let rotation = null; + let sidebarView = AppOptions.get("sidebarViewOnLoad"); + let scrollMode = AppOptions.get("scrollModeOnLoad"); + let spreadMode = AppOptions.get("spreadModeOnLoad"); + if (stored?.page && viewOnLoad !== ViewOnLoad.INITIAL) { + hash = + `page=${stored.page}&zoom=${zoom || stored.zoom},` + + `${stored.scrollLeft},${stored.scrollTop}`; + rotation = parseInt(stored.rotation, 10); + if (sidebarView === SidebarView.UNKNOWN) { + sidebarView = stored.sidebarView | 0; + } + if (scrollMode === ScrollMode.UNKNOWN) { + scrollMode = stored.scrollMode | 0; + } + if (spreadMode === SpreadMode.UNKNOWN) { + spreadMode = stored.spreadMode | 0; + } + } + if (pageMode && sidebarView === SidebarView.UNKNOWN) { + sidebarView = apiPageModeToSidebarView(pageMode); + } + if ( + pageLayout && + scrollMode === ScrollMode.UNKNOWN && + spreadMode === SpreadMode.UNKNOWN + ) { + const modes = apiPageLayoutToViewerModes(pageLayout); + spreadMode = modes.spreadMode; + } + this.setInitialView(hash, { + rotation, + sidebarView, + scrollMode, + spreadMode, + }); + this.eventBus.dispatch("documentinit", { + source: this, + }); + if (!this.isViewerEmbedded) { + pdfViewer.focus(); + } + await Promise.race([ + pagesPromise, + new Promise((resolve) => { + setTimeout(resolve, FORCE_PAGES_LOADED_TIMEOUT); + }), + ]); + if (!initialBookmark && !hash) { + return; + } + if (pdfViewer.hasEqualPageSizes) { + return; + } + this.initialBookmark = initialBookmark; + pdfViewer.currentScaleValue = pdfViewer.currentScaleValue; + this.setInitialView(hash); + }) + .catch(() => { + this.setInitialView(); + }) + .then(function () { + pdfViewer.update(); + }); + }); + pagesPromise.then( + () => { + this._unblockDocumentLoadEvent(); + this._initializeAutoPrint(pdfDocument, openActionPromise); + }, + (reason) => { + this._documentError("pdfjs-loading-error", { + message: reason.message, + }); + }, + ); + onePageRendered.then((data) => { + this.externalServices.reportTelemetry({ + type: "pageInfo", + timestamp: data.timestamp, + }); + if (this.pdfOutlineViewer) { + pdfDocument.getOutline().then((outline) => { + if (pdfDocument !== this.pdfDocument) { + return; + } + this.pdfOutlineViewer.render({ + outline, + pdfDocument, + }); + }); + } + if (this.pdfAttachmentViewer) { + pdfDocument.getAttachments().then((attachments) => { + if (pdfDocument !== this.pdfDocument) { + return; + } + this.pdfAttachmentViewer.render({ + attachments, + }); + }); + } + if (this.pdfLayerViewer) { + pdfViewer.optionalContentConfigPromise.then((optionalContentConfig) => { + if (pdfDocument !== this.pdfDocument) { + return; + } + this.pdfLayerViewer.render({ + optionalContentConfig, + pdfDocument, + }); + }); + } + }); + this._initializePageLabels(pdfDocument); + this._initializeMetadata(pdfDocument); + }, + async _scriptingDocProperties(pdfDocument) { + if (!this.documentInfo) { + await new Promise((resolve) => { + this.eventBus._on("metadataloaded", resolve, { + once: true, + }); + }); + if (pdfDocument !== this.pdfDocument) { + return null; + } + } + if (!this._contentLength) { + await new Promise((resolve) => { + this.eventBus._on("documentloaded", resolve, { + once: true, + }); + }); + if (pdfDocument !== this.pdfDocument) { + return null; + } + } + return { + ...this.documentInfo, + baseURL: this.baseUrl, + filesize: this._contentLength, + filename: this._docFilename, + metadata: this.metadata?.getRaw(), + authors: this.metadata?.get("dc:creator"), + numPages: this.pagesCount, + URL: this.url, + }; + }, + async _initializeAutoPrint(pdfDocument, openActionPromise) { + const [openAction, jsActions] = await Promise.all([ + openActionPromise, + this.pdfViewer.enableScripting ? null : pdfDocument.getJSActions(), + ]); + if (pdfDocument !== this.pdfDocument) { + return; + } + let triggerAutoPrint = openAction?.action === "Print"; + if (jsActions) { + console.warn("Warning: JavaScript support is not enabled"); + for (const name in jsActions) { + if (triggerAutoPrint) { + break; + } + switch (name) { + case "WillClose": + case "WillSave": + case "DidSave": + case "WillPrint": + case "DidPrint": + continue; + } + triggerAutoPrint = jsActions[name].some((js) => + AutoPrintRegExp.test(js), + ); + } + } + if (triggerAutoPrint) { + this.triggerPrinting(); + } + }, + async _initializeMetadata(pdfDocument) { + const { info, metadata, contentDispositionFilename, contentLength } = + await pdfDocument.getMetadata(); + if (pdfDocument !== this.pdfDocument) { + return; + } + this.documentInfo = info; + this.metadata = metadata; + this._contentDispositionFilename ??= contentDispositionFilename; + this._contentLength ??= contentLength; + console.log( + `PDF ${pdfDocument.fingerprints[0]} [${info.PDFFormatVersion} ` + + `${(info.Producer || "-").trim()} / ${(info.Creator || "-").trim()}] ` + + `(PDF.js: ${version || "?"} [${build || "?"}])`, + ); + let pdfTitle = info.Title; + const metadataTitle = metadata?.get("dc:title"); + if (metadataTitle) { + if ( + metadataTitle !== "Untitled" && + !/[\uFFF0-\uFFFF]/g.test(metadataTitle) + ) { + pdfTitle = metadataTitle; + } + } + if (pdfTitle) { + this.setTitle( + `${pdfTitle} - ${this._contentDispositionFilename || this._title}`, + ); + } else if (this._contentDispositionFilename) { + this.setTitle(this._contentDispositionFilename); + } + if ( + info.IsXFAPresent && + !info.IsAcroFormPresent && + !pdfDocument.isPureXfa + ) { + if (pdfDocument.loadingParams.enableXfa) { + console.warn("Warning: XFA Foreground documents are not supported"); + } else { + console.warn("Warning: XFA support is not enabled"); + } + } else if ( + (info.IsAcroFormPresent || info.IsXFAPresent) && + !this.pdfViewer.renderForms + ) { + console.warn("Warning: Interactive form support is not enabled"); + } + if (info.IsSignaturesPresent) { + console.warn("Warning: Digital signatures validation is not supported"); + } + this.eventBus.dispatch("metadataloaded", { + source: this, + }); + }, + async _initializePageLabels(pdfDocument) { + const labels = await pdfDocument.getPageLabels(); + if (pdfDocument !== this.pdfDocument) { + return; + } + if (!labels || AppOptions.get("disablePageLabels")) { + return; + } + const numLabels = labels.length; + let standardLabels = 0, + emptyLabels = 0; + for (let i = 0; i < numLabels; i++) { + const label = labels[i]; + if (label === (i + 1).toString()) { + standardLabels++; + } else if (label === "") { + emptyLabels++; + } else { + break; + } + } + if (standardLabels >= numLabels || emptyLabels >= numLabels) { + return; + } + const { pdfViewer, pdfThumbnailViewer, toolbar } = this; + pdfViewer.setPageLabels(labels); + pdfThumbnailViewer?.setPageLabels(labels); + toolbar?.setPagesCount(numLabels, true); + toolbar?.setPageNumber( + pdfViewer.currentPageNumber, + pdfViewer.currentPageLabel, + ); + }, + _initializePdfHistory({ fingerprint, viewOnLoad, initialDest = null }) { + if (!this.pdfHistory) { + return; + } + this.pdfHistory.initialize({ + fingerprint, + resetHistory: viewOnLoad === ViewOnLoad.INITIAL, + updateUrl: AppOptions.get("historyUpdateUrl"), + }); + if (this.pdfHistory.initialBookmark) { + this.initialBookmark = this.pdfHistory.initialBookmark; + this.initialRotation = this.pdfHistory.initialRotation; + } + if ( + initialDest && + !this.initialBookmark && + viewOnLoad === ViewOnLoad.UNKNOWN + ) { + this.initialBookmark = JSON.stringify(initialDest); + this.pdfHistory.push({ + explicitDest: initialDest, + pageNumber: null, + }); + } + }, + _initializeAnnotationStorageCallbacks(pdfDocument) { + if (pdfDocument !== this.pdfDocument) { + return; + } + const { annotationStorage } = pdfDocument; + annotationStorage.onSetModified = () => { + window.addEventListener("beforeunload", beforeUnload); + this._annotationStorageModified = true; + }; + annotationStorage.onResetModified = () => { + window.removeEventListener("beforeunload", beforeUnload); + delete this._annotationStorageModified; + }; + annotationStorage.onAnnotationEditor = (typeStr) => { + this._hasAnnotationEditors = !!typeStr; + this.setTitle(); + }; + }, + setInitialView( + storedHash, + { rotation, sidebarView, scrollMode, spreadMode } = {}, + ) { + const setRotation = (angle) => { + if (isValidRotation(angle)) { + this.pdfViewer.pagesRotation = angle; + } + }; + const setViewerModes = (scroll, spread) => { + if (isValidScrollMode(scroll)) { + this.pdfViewer.scrollMode = scroll; + } + if (isValidSpreadMode(spread)) { + this.pdfViewer.spreadMode = spread; + } + }; + this.isInitialViewSet = true; + this.pdfSidebar?.setInitialView(sidebarView); + setViewerModes(scrollMode, spreadMode); + if (this.initialBookmark) { + setRotation(this.initialRotation); + delete this.initialRotation; + this.pdfLinkService.setHash(this.initialBookmark); + this.initialBookmark = null; + } else if (storedHash) { + setRotation(rotation); + this.pdfLinkService.setHash(storedHash); + } + this.toolbar?.setPageNumber( + this.pdfViewer.currentPageNumber, + this.pdfViewer.currentPageLabel, + ); + this.secondaryToolbar?.setPageNumber(this.pdfViewer.currentPageNumber); + if (!this.pdfViewer.currentScaleValue) { + this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE; + } + }, + _cleanup() { + if (!this.pdfDocument) { + return; + } + this.pdfViewer.cleanup(); + this.pdfThumbnailViewer?.cleanup(); + this.pdfDocument.cleanup(AppOptions.get("fontExtraProperties")); + }, + forceRendering() { + this.pdfRenderingQueue.printing = !!this.printService; + this.pdfRenderingQueue.isThumbnailViewEnabled = + this.pdfSidebar?.visibleView === SidebarView.THUMBS; + this.pdfRenderingQueue.renderHighestPriority(); + }, + beforePrint() { + this._printAnnotationStoragePromise = this.pdfScriptingManager + .dispatchWillPrint() + .catch(() => {}) + .then(() => this.pdfDocument?.annotationStorage.print); + if (this.printService) { + return; + } + if (!this.supportsPrinting) { + this._otherError("pdfjs-printing-not-supported"); + return; + } + if (!this.pdfViewer.pageViewsReady) { + this.l10n.get("pdfjs-printing-not-ready").then((msg) => { + window.alert(msg); + }); + return; + } + this.printService = PDFPrintServiceFactory.createPrintService({ + pdfDocument: this.pdfDocument, + pagesOverview: this.pdfViewer.getPagesOverview(), + printContainer: this.appConfig.printContainer, + printResolution: AppOptions.get("printResolution"), + printAnnotationStoragePromise: this._printAnnotationStoragePromise, + }); + this.forceRendering(); + this.setTitle(); + this.printService.layout(); + if (this._hasAnnotationEditors) { + this.externalServices.reportTelemetry({ + type: "editing", + data: { + type: "print", + stats: this.pdfDocument?.annotationStorage.editorStats, + }, + }); + } + }, + afterPrint() { + if (this._printAnnotationStoragePromise) { + this._printAnnotationStoragePromise.then(() => { + this.pdfScriptingManager.dispatchDidPrint(); + }); + this._printAnnotationStoragePromise = null; + } + if (this.printService) { + this.printService.destroy(); + this.printService = null; + this.pdfDocument?.annotationStorage.resetModified(); + } + this.forceRendering(); + this.setTitle(); + }, + rotatePages(delta) { + this.pdfViewer.pagesRotation += delta; + }, + requestPresentationMode() { + this.pdfPresentationMode?.request(); + }, + triggerPrinting() { + if (this.supportsPrinting) { + window.print(); + } + }, + bindEvents() { + if (this._eventBusAbortController) { + return; + } + const ac = (this._eventBusAbortController = new AbortController()); + const opts = { + signal: ac.signal, + }; + const { + eventBus, + externalServices, + pdfDocumentProperties, + pdfViewer, + preferences, + } = this; + eventBus._on("resize", onResize.bind(this), opts); + eventBus._on("hashchange", onHashchange.bind(this), opts); + eventBus._on("beforeprint", this.beforePrint.bind(this), opts); + eventBus._on("afterprint", this.afterPrint.bind(this), opts); + eventBus._on("pagerender", onPageRender.bind(this), opts); + eventBus._on("pagerendered", onPageRendered.bind(this), opts); + eventBus._on("updateviewarea", onUpdateViewarea.bind(this), opts); + eventBus._on("pagechanging", onPageChanging.bind(this), opts); + eventBus._on("scalechanging", onScaleChanging.bind(this), opts); + eventBus._on("rotationchanging", onRotationChanging.bind(this), opts); + eventBus._on("sidebarviewchanged", onSidebarViewChanged.bind(this), opts); + eventBus._on("pagemode", onPageMode.bind(this), opts); + eventBus._on("namedaction", onNamedAction.bind(this), opts); + eventBus._on( + "presentationmodechanged", + (evt) => (pdfViewer.presentationModeState = evt.state), + opts, + ); + eventBus._on( + "presentationmode", + this.requestPresentationMode.bind(this), + opts, + ); + eventBus._on( + "switchannotationeditormode", + (evt) => (pdfViewer.annotationEditorMode = evt), + opts, + ); + eventBus._on("print", this.triggerPrinting.bind(this), opts); + eventBus._on("download", this.downloadOrSave.bind(this), opts); + eventBus._on("firstpage", () => (this.page = 1), opts); + eventBus._on("lastpage", () => (this.page = this.pagesCount), opts); + eventBus._on("nextpage", () => pdfViewer.nextPage(), opts); + eventBus._on("previouspage", () => pdfViewer.previousPage(), opts); + eventBus._on("zoomin", this.zoomIn.bind(this), opts); + eventBus._on("zoomout", this.zoomOut.bind(this), opts); + eventBus._on("zoomreset", this.zoomReset.bind(this), opts); + eventBus._on("pagenumberchanged", onPageNumberChanged.bind(this), opts); + eventBus._on( + "scalechanged", + (evt) => (pdfViewer.currentScaleValue = evt.value), + opts, + ); + eventBus._on("rotatecw", this.rotatePages.bind(this, 90), opts); + eventBus._on("rotateccw", this.rotatePages.bind(this, -90), opts); + eventBus._on( + "optionalcontentconfig", + (evt) => (pdfViewer.optionalContentConfigPromise = evt.promise), + opts, + ); + eventBus._on( + "switchscrollmode", + (evt) => (pdfViewer.scrollMode = evt.mode), + opts, + ); + eventBus._on( + "scrollmodechanged", + onViewerModesChanged.bind(this, "scrollMode"), + opts, + ); + eventBus._on( + "switchspreadmode", + (evt) => (pdfViewer.spreadMode = evt.mode), + opts, + ); + eventBus._on( + "spreadmodechanged", + onViewerModesChanged.bind(this, "spreadMode"), + opts, + ); + eventBus._on( + "imagealttextsettings", + onImageAltTextSettings.bind(this), + opts, + ); + eventBus._on( + "documentproperties", + () => pdfDocumentProperties?.open(), + opts, + ); + eventBus._on("findfromurlhash", onFindFromUrlHash.bind(this), opts); + eventBus._on( + "updatefindmatchescount", + onUpdateFindMatchesCount.bind(this), + opts, + ); + eventBus._on( + "updatefindcontrolstate", + onUpdateFindControlState.bind(this), + opts, + ); + eventBus._on("fileinputchange", onFileInputChange.bind(this), opts); + eventBus._on("openfile", onOpenFile.bind(this), opts); + }, + bindWindowEvents() { + if (this._windowAbortController) { + return; + } + this._windowAbortController = new AbortController(); + const { + eventBus, + appConfig: { mainContainer }, + pdfViewer, + _windowAbortController: { signal }, + } = this; + if (typeof AbortSignal.any === "function") { + this._touchManager = new TouchManager({ + container: window, + isPinchingDisabled: () => pdfViewer.isInPresentationMode, + isPinchingStopped: () => this.overlayManager?.active, + onPinching: this.touchPinchCallback.bind(this), + onPinchEnd: this.touchPinchEndCallback.bind(this), + signal, + }); + } + function addWindowResolutionChange(evt = null) { + if (evt) { + pdfViewer.refresh(); + } + const mediaQueryList = window.matchMedia( + `(resolution: ${window.devicePixelRatio || 1}dppx)`, + ); + mediaQueryList.addEventListener("change", addWindowResolutionChange, { + once: true, + signal, + }); + } + addWindowResolutionChange(); + window.addEventListener("wheel", onWheel.bind(this), { + passive: false, + signal, + }); + window.addEventListener("click", onClick.bind(this), { + signal, + }); + window.addEventListener("keydown", onKeyDown.bind(this), { + signal, + }); + window.addEventListener("keyup", onKeyUp.bind(this), { + signal, + }); + window.addEventListener( + "resize", + () => + eventBus.dispatch("resize", { + source: window, + }), + { + signal, + }, + ); + window.addEventListener( + "hashchange", + () => { + eventBus.dispatch("hashchange", { + source: window, + hash: document.location.hash.substring(1), + }); + }, + { + signal, + }, + ); + window.addEventListener( + "beforeprint", + () => + eventBus.dispatch("beforeprint", { + source: window, + }), + { + signal, + }, + ); + window.addEventListener( + "afterprint", + () => + eventBus.dispatch("afterprint", { + source: window, + }), + { + signal, + }, + ); + window.addEventListener( + "updatefromsandbox", + (evt) => { + eventBus.dispatch("updatefromsandbox", { + source: window, + detail: evt.detail, + }); + }, + { + signal, + }, + ); + if (!("onscrollend" in document.documentElement)) { + return; + } + ({ scrollTop: this._lastScrollTop, scrollLeft: this._lastScrollLeft } = + mainContainer); + const scrollend = () => { + ({ scrollTop: this._lastScrollTop, scrollLeft: this._lastScrollLeft } = + mainContainer); + this._isScrolling = false; + mainContainer.addEventListener("scroll", scroll, { + passive: true, + signal, + }); + mainContainer.removeEventListener("scrollend", scrollend); + mainContainer.removeEventListener("blur", scrollend); + }; + const scroll = () => { + if (this._isCtrlKeyDown) { + return; + } + if ( + this._lastScrollTop === mainContainer.scrollTop && + this._lastScrollLeft === mainContainer.scrollLeft + ) { + return; + } + mainContainer.removeEventListener("scroll", scroll); + this._isScrolling = true; + mainContainer.addEventListener("scrollend", scrollend, { + signal, + }); + mainContainer.addEventListener("blur", scrollend, { + signal, + }); + }; + mainContainer.addEventListener("scroll", scroll, { + passive: true, + signal, + }); + }, + unbindEvents() { + this._eventBusAbortController?.abort(); + this._eventBusAbortController = null; + }, + unbindWindowEvents() { + this._windowAbortController?.abort(); + this._windowAbortController = null; + this._touchManager = null; + }, + async testingClose() { + this.unbindEvents(); + this.unbindWindowEvents(); + this._globalAbortController?.abort(); + this._globalAbortController = null; + this.findBar?.close(); + await Promise.all([this.l10n?.destroy(), this.close()]); + }, + _accumulateTicks(ticks, prop) { + if ((this[prop] > 0 && ticks < 0) || (this[prop] < 0 && ticks > 0)) { + this[prop] = 0; + } + this[prop] += ticks; + const wholeTicks = Math.trunc(this[prop]); + this[prop] -= wholeTicks; + return wholeTicks; + }, + _accumulateFactor(previousScale, factor, prop) { + if (factor === 1) { + return 1; + } + if ((this[prop] > 1 && factor < 1) || (this[prop] < 1 && factor > 1)) { + this[prop] = 1; + } + const newFactor = + Math.floor(previousScale * factor * this[prop] * 100) / + (100 * previousScale); + this[prop] = factor / newFactor; + return newFactor; + }, + _unblockDocumentLoadEvent() { + document.blockUnblockOnload?.(false); + this._unblockDocumentLoadEvent = () => {}; + }, + get scriptingReady() { + return this.pdfScriptingManager.ready; + }, +}; +initCom(PDFViewerApplication); +{ + PDFPrintServiceFactory.initGlobals(PDFViewerApplication); +} +{ + const HOSTED_VIEWER_ORIGINS = [ + "null", + "http://mozilla.github.io", + "https://mozilla.github.io", + ]; + var validateFileURL = function (file) { + if (!file) { + return; + } + try { + } catch (ex) { + PDFViewerApplication._documentError("pdfjs-loading-error", { + message: ex.message, + }); + throw ex; + } + }; + var onFileInputChange = function (evt) { + if (this.pdfViewer?.isInPresentationMode) { + return; + } + const file = evt.fileInput.files[0]; + this.open({ + url: URL.createObjectURL(file), + originalUrl: file.name, + }); + }; + var onOpenFile = function (evt) { + this._openFileInput?.click(); + }; +} +function onPageRender({ pageNumber }) { + if (pageNumber === this.page) { + this.toolbar?.updateLoadingIndicatorState(true); + } +} +function onPageRendered({ pageNumber, error }) { + if (pageNumber === this.page) { + this.toolbar?.updateLoadingIndicatorState(false); + } + if (this.pdfSidebar?.visibleView === SidebarView.THUMBS) { + const pageView = this.pdfViewer.getPageView(pageNumber - 1); + const thumbnailView = this.pdfThumbnailViewer?.getThumbnail(pageNumber - 1); + if (pageView) { + thumbnailView?.setImage(pageView); + } + } + if (error) { + this._otherError("pdfjs-rendering-error", error); + } +} +function onPageMode({ mode }) { + let view; + switch (mode) { + case "thumbs": + view = SidebarView.THUMBS; + break; + case "bookmarks": + case "outline": + view = SidebarView.OUTLINE; + break; + case "attachments": + view = SidebarView.ATTACHMENTS; + break; + case "layers": + view = SidebarView.LAYERS; + break; + case "none": + view = SidebarView.NONE; + break; + default: + console.error('Invalid "pagemode" hash parameter: ' + mode); + return; + } + this.pdfSidebar?.switchView(view, true); +} +function onNamedAction(evt) { + switch (evt.action) { + case "GoToPage": + this.appConfig.toolbar?.pageNumber.select(); + break; + case "Find": + if (!this.supportsIntegratedFind) { + this.findBar?.toggle(); + } + break; + case "Print": + this.triggerPrinting(); + break; + case "SaveAs": + this.downloadOrSave(); + break; + } +} +function onSidebarViewChanged({ view }) { + this.pdfRenderingQueue.isThumbnailViewEnabled = view === SidebarView.THUMBS; + if (this.isInitialViewSet) { + this.store?.set("sidebarView", view).catch(() => {}); + } +} +function onUpdateViewarea({ location }) { + if (this.isInitialViewSet) { + this.store + ?.setMultiple({ + page: location.pageNumber, + zoom: location.scale, + scrollLeft: location.left, + scrollTop: location.top, + rotation: location.rotation, + }) + .catch(() => {}); + } + if (this.appConfig.secondaryToolbar) { + this.appConfig.secondaryToolbar.viewBookmarkButton.href = + this.pdfLinkService.getAnchorUrl(location.pdfOpenParams); + } +} +function onViewerModesChanged(name, evt) { + if (this.isInitialViewSet && !this.pdfViewer.isInPresentationMode) { + this.store?.set(name, evt.mode).catch(() => {}); + } +} +function onResize() { + const { pdfDocument, pdfViewer, pdfRenderingQueue } = this; + if (pdfRenderingQueue.printing && window.matchMedia("print").matches) { + return; + } + if (!pdfDocument) { + return; + } + const currentScaleValue = pdfViewer.currentScaleValue; + if ( + currentScaleValue === "auto" || + currentScaleValue === "page-fit" || + currentScaleValue === "page-width" + ) { + pdfViewer.currentScaleValue = currentScaleValue; + } + pdfViewer.update(); +} +function onHashchange(evt) { + const hash = evt.hash; + if (!hash) { + return; + } + if (!this.isInitialViewSet) { + this.initialBookmark = hash; + } else if (!this.pdfHistory?.popStateInProgress) { + this.pdfLinkService.setHash(hash); + } +} +function onPageNumberChanged(evt) { + const { pdfViewer } = this; + if (evt.value !== "") { + this.pdfLinkService.goToPage(evt.value); + } + if ( + evt.value !== pdfViewer.currentPageNumber.toString() && + evt.value !== pdfViewer.currentPageLabel + ) { + this.toolbar?.setPageNumber( + pdfViewer.currentPageNumber, + pdfViewer.currentPageLabel, + ); + } +} +function onImageAltTextSettings() { + this.imageAltTextSettings?.open({ + enableGuessAltText: AppOptions.get("enableGuessAltText"), + enableNewAltTextWhenAddingImage: AppOptions.get( + "enableNewAltTextWhenAddingImage", + ), + }); +} +function onFindFromUrlHash(evt) { + this.eventBus.dispatch("find", { + source: evt.source, + type: "", + query: evt.query, + caseSensitive: false, + entireWord: false, + highlightAll: true, + findPrevious: false, + matchDiacritics: true, + }); +} +function onUpdateFindMatchesCount({ matchesCount }) { + if (this.supportsIntegratedFind) { + this.externalServices.updateFindMatchesCount(matchesCount); + } else { + this.findBar?.updateResultsCount(matchesCount); + } +} +function onUpdateFindControlState({ + state, + previous, + entireWord, + matchesCount, + rawQuery, +}) { + if (this.supportsIntegratedFind) { + this.externalServices.updateFindControlState({ + result: state, + findPrevious: previous, + entireWord, + matchesCount, + rawQuery, + }); + } else { + this.findBar?.updateUIState(state, previous, matchesCount); + } +} +function onScaleChanging(evt) { + this.toolbar?.setPageScale(evt.presetValue, evt.scale); + this.pdfViewer.update(); +} +function onRotationChanging(evt) { + if (this.pdfThumbnailViewer) { + this.pdfThumbnailViewer.pagesRotation = evt.pagesRotation; + } + this.forceRendering(); + this.pdfViewer.currentPageNumber = evt.pageNumber; +} +function onPageChanging({ pageNumber, pageLabel }) { + this.toolbar?.setPageNumber(pageNumber, pageLabel); + this.secondaryToolbar?.setPageNumber(pageNumber); + if (this.pdfSidebar?.visibleView === SidebarView.THUMBS) { + this.pdfThumbnailViewer?.scrollThumbnailIntoView(pageNumber); + } + const currentPage = this.pdfViewer.getPageView(pageNumber - 1); + this.toolbar?.updateLoadingIndicatorState( + currentPage?.renderingState === RenderingStates.RUNNING, + ); +} +function onWheel(evt) { + const { + pdfViewer, + supportsMouseWheelZoomCtrlKey, + supportsMouseWheelZoomMetaKey, + supportsPinchToZoom, + } = this; + if (pdfViewer.isInPresentationMode) { + return; + } + const deltaMode = evt.deltaMode; + let scaleFactor = Math.exp(-evt.deltaY / 100); + const isBuiltInMac = false; + const isPinchToZoom = + evt.ctrlKey && + !this._isCtrlKeyDown && + deltaMode === WheelEvent.DOM_DELTA_PIXEL && + evt.deltaX === 0 && + (Math.abs(scaleFactor - 1) < 0.05 || isBuiltInMac) && + evt.deltaZ === 0; + const origin = [evt.clientX, evt.clientY]; + if ( + isPinchToZoom || + (evt.ctrlKey && supportsMouseWheelZoomCtrlKey) || + (evt.metaKey && supportsMouseWheelZoomMetaKey) + ) { + evt.preventDefault(); + if ( + this._isScrolling || + document.visibilityState === "hidden" || + this.overlayManager.active + ) { + return; + } + if (isPinchToZoom && supportsPinchToZoom) { + scaleFactor = this._accumulateFactor( + pdfViewer.currentScale, + scaleFactor, + "_wheelUnusedFactor", + ); + this.updateZoom(null, scaleFactor, origin); + } else { + const delta = normalizeWheelEventDirection(evt); + let ticks = 0; + if ( + deltaMode === WheelEvent.DOM_DELTA_LINE || + deltaMode === WheelEvent.DOM_DELTA_PAGE + ) { + ticks = + Math.abs(delta) >= 1 + ? Math.sign(delta) + : this._accumulateTicks(delta, "_wheelUnusedTicks"); + } else { + const PIXELS_PER_LINE_SCALE = 30; + ticks = this._accumulateTicks( + delta / PIXELS_PER_LINE_SCALE, + "_wheelUnusedTicks", + ); + } + this.updateZoom(ticks, null, origin); + } + } +} +function closeSecondaryToolbar(evt) { + if (!this.secondaryToolbar?.isOpen) { + return; + } + const appConfig = this.appConfig; + if ( + this.pdfViewer.containsElement(evt.target) || + (appConfig.toolbar?.container.contains(evt.target) && + !appConfig.secondaryToolbar?.toggleButton.contains(evt.target)) + ) { + this.secondaryToolbar.close(); + } +} +function closeEditorUndoBar(evt) { + if (!this.editorUndoBar?.isOpen) { + return; + } + if (this.appConfig.secondaryToolbar?.toolbar.contains(evt.target)) { + this.editorUndoBar.hide(); + } +} +function onClick(evt) { + closeSecondaryToolbar.call(this, evt); + closeEditorUndoBar.call(this, evt); +} +function onKeyUp(evt) { + if (evt.key === "Control") { + this._isCtrlKeyDown = false; + } +} +function onKeyDown(evt) { + this._isCtrlKeyDown = evt.key === "Control"; + if ( + this.editorUndoBar?.isOpen && + evt.keyCode !== 9 && + evt.keyCode !== 16 && + !( + (evt.keyCode === 13 || evt.keyCode === 32) && + getActiveOrFocusedElement() === this.appConfig.editorUndoBar.undoButton + ) + ) { + this.editorUndoBar.hide(); + } + if (this.overlayManager.active) { + return; + } + const { eventBus, pdfViewer } = this; + const isViewerInPresentationMode = pdfViewer.isInPresentationMode; + let handled = false, + ensureViewerFocused = false; + const cmd = + (evt.ctrlKey ? 1 : 0) | + (evt.altKey ? 2 : 0) | + (evt.shiftKey ? 4 : 0) | + (evt.metaKey ? 8 : 0); + if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) { + switch (evt.keyCode) { + case 70: + if (!this.supportsIntegratedFind && !evt.shiftKey) { + this.findBar?.open(); + handled = true; + } + break; + case 71: + if (!this.supportsIntegratedFind) { + const { state } = this.findController; + if (state) { + const newState = { + source: window, + type: "again", + findPrevious: cmd === 5 || cmd === 12, + }; + eventBus.dispatch("find", { + ...state, + ...newState, + }); + } + handled = true; + } + break; + case 61: + case 107: + case 187: + case 171: + this.zoomIn(); + handled = true; + break; + case 173: + case 109: + case 189: + this.zoomOut(); + handled = true; + break; + case 48: + case 96: + if (!isViewerInPresentationMode) { + setTimeout(() => { + this.zoomReset(); + }); + handled = false; + } + break; + case 38: + if (isViewerInPresentationMode || this.page > 1) { + this.page = 1; + handled = true; + ensureViewerFocused = true; + } + break; + case 40: + if (isViewerInPresentationMode || this.page < this.pagesCount) { + this.page = this.pagesCount; + handled = true; + ensureViewerFocused = true; + } + break; + } + } + if (cmd === 1 || cmd === 8) { + switch (evt.keyCode) { + case 83: + eventBus.dispatch("download", { + source: window, + }); + handled = true; + break; + case 79: + { + eventBus.dispatch("openfile", { + source: window, + }); + handled = true; + } + break; + } + } + if (cmd === 3 || cmd === 10) { + switch (evt.keyCode) { + case 80: + this.requestPresentationMode(); + handled = true; + this.externalServices.reportTelemetry({ + type: "buttons", + data: { + id: "presentationModeKeyboard", + }, + }); + break; + case 71: + if (this.appConfig.toolbar) { + this.appConfig.toolbar.pageNumber.select(); + handled = true; + } + break; + } + } + if (handled) { + if (ensureViewerFocused && !isViewerInPresentationMode) { + pdfViewer.focus(); + } + evt.preventDefault(); + return; + } + const curElement = getActiveOrFocusedElement(); + const curElementTagName = curElement?.tagName.toUpperCase(); + if ( + curElementTagName === "INPUT" || + curElementTagName === "TEXTAREA" || + curElementTagName === "SELECT" || + (curElementTagName === "BUTTON" && + (evt.keyCode === 13 || evt.keyCode === 32)) || + curElement?.isContentEditable + ) { + if (evt.keyCode !== 27) { + return; + } + } + if (cmd === 0) { + let turnPage = 0, + turnOnlyIfPageFit = false; + switch (evt.keyCode) { + case 38: + if (this.supportsCaretBrowsingMode) { + this.moveCaret(true, false); + handled = true; + break; + } + case 33: + if (pdfViewer.isVerticalScrollbarEnabled) { + turnOnlyIfPageFit = true; + } + turnPage = -1; + break; + case 8: + if (!isViewerInPresentationMode) { + turnOnlyIfPageFit = true; + } + turnPage = -1; + break; + case 37: + if (this.supportsCaretBrowsingMode) { + return; + } + if (pdfViewer.isHorizontalScrollbarEnabled) { + turnOnlyIfPageFit = true; + } + case 75: + case 80: + turnPage = -1; + break; + case 27: + if (this.secondaryToolbar?.isOpen) { + this.secondaryToolbar.close(); + handled = true; + } + if (!this.supportsIntegratedFind && this.findBar?.opened) { + this.findBar.close(); + handled = true; + } + break; + case 40: + if (this.supportsCaretBrowsingMode) { + this.moveCaret(false, false); + handled = true; + break; + } + case 34: + if (pdfViewer.isVerticalScrollbarEnabled) { + turnOnlyIfPageFit = true; + } + turnPage = 1; + break; + case 13: + case 32: + if (!isViewerInPresentationMode) { + turnOnlyIfPageFit = true; + } + turnPage = 1; + break; + case 39: + if (this.supportsCaretBrowsingMode) { + return; + } + if (pdfViewer.isHorizontalScrollbarEnabled) { + turnOnlyIfPageFit = true; + } + case 74: + case 78: + turnPage = 1; + break; + case 36: + if (isViewerInPresentationMode || this.page > 1) { + this.page = 1; + handled = true; + ensureViewerFocused = true; + } + break; + case 35: + if (isViewerInPresentationMode || this.page < this.pagesCount) { + this.page = this.pagesCount; + handled = true; + ensureViewerFocused = true; + } + break; + case 83: + this.pdfCursorTools?.switchTool(CursorTool.SELECT); + break; + case 72: + this.pdfCursorTools?.switchTool(CursorTool.HAND); + break; + case 82: + this.rotatePages(90); + break; + case 115: + this.pdfSidebar?.toggle(); + break; + } + if ( + turnPage !== 0 && + (!turnOnlyIfPageFit || pdfViewer.currentScaleValue === "page-fit") + ) { + if (turnPage > 0) { + pdfViewer.nextPage(); + } else { + pdfViewer.previousPage(); + } + handled = true; + } + } + if (cmd === 4) { + switch (evt.keyCode) { + case 13: + case 32: + if ( + !isViewerInPresentationMode && + pdfViewer.currentScaleValue !== "page-fit" + ) { + break; + } + pdfViewer.previousPage(); + handled = true; + break; + case 38: + this.moveCaret(true, true); + handled = true; + break; + case 40: + this.moveCaret(false, true); + handled = true; + break; + case 82: + this.rotatePages(-90); + break; + } + } + if (!handled && !isViewerInPresentationMode) { + if ( + (evt.keyCode >= 33 && evt.keyCode <= 40) || + (evt.keyCode === 32 && curElementTagName !== "BUTTON") + ) { + ensureViewerFocused = true; + } + } + if (ensureViewerFocused && !pdfViewer.containsElement(curElement)) { + pdfViewer.focus(); + } + if (handled) { + evt.preventDefault(); + } +} +function beforeUnload(evt) { + evt.preventDefault(); + evt.returnValue = ""; + return false; +} // ./web/viewer.js + +const pdfjsVersion = "4.10.38"; +const pdfjsBuild = "f9bea397f"; +const AppConstants = { + LinkTarget: LinkTarget, + RenderingStates: RenderingStates, + ScrollMode: ScrollMode, + SpreadMode: SpreadMode, +}; +window.PDFViewerApplication = PDFViewerApplication; +window.PDFViewerApplicationConstants = AppConstants; +window.PDFViewerApplicationOptions = AppOptions; +function getViewerConfiguration() { + return { + appContainer: document.body, + principalContainer: document.getElementById("mainContainer"), + mainContainer: document.getElementById("viewerContainer"), + viewerContainer: document.getElementById("viewer"), + toolbar: { + container: document.getElementById("toolbarContainer"), + numPages: document.getElementById("numPages"), + pageNumber: document.getElementById("pageNumber"), + scaleSelect: document.getElementById("scaleSelect"), + customScaleOption: document.getElementById("customScaleOption"), + previous: document.getElementById("previous"), + next: document.getElementById("next"), + zoomIn: document.getElementById("zoomInButton"), + zoomOut: document.getElementById("zoomOutButton"), + print: document.getElementById("printButton"), + editorFreeTextButton: document.getElementById("editorFreeTextButton"), + editorFreeTextParamsToolbar: document.getElementById( + "editorFreeTextParamsToolbar", + ), + editorHighlightButton: document.getElementById("editorHighlightButton"), + editorHighlightParamsToolbar: document.getElementById( + "editorHighlightParamsToolbar", + ), + editorHighlightColorPicker: document.getElementById( + "editorHighlightColorPicker", + ), + editorInkButton: document.getElementById("editorInkButton"), + editorInkParamsToolbar: document.getElementById("editorInkParamsToolbar"), + editorStampButton: document.getElementById("editorStampButton"), + editorStampParamsToolbar: document.getElementById( + "editorStampParamsToolbar", + ), + download: document.getElementById("downloadButton"), + }, + secondaryToolbar: { + toolbar: document.getElementById("secondaryToolbar"), + toggleButton: document.getElementById("secondaryToolbarToggleButton"), + presentationModeButton: document.getElementById("presentationMode"), + openFileButton: document.getElementById("secondaryOpenFile"), + printButton: document.getElementById("secondaryPrint"), + downloadButton: document.getElementById("secondaryDownload"), + viewBookmarkButton: document.getElementById("viewBookmark"), + firstPageButton: document.getElementById("firstPage"), + lastPageButton: document.getElementById("lastPage"), + pageRotateCwButton: document.getElementById("pageRotateCw"), + pageRotateCcwButton: document.getElementById("pageRotateCcw"), + cursorSelectToolButton: document.getElementById("cursorSelectTool"), + cursorHandToolButton: document.getElementById("cursorHandTool"), + scrollPageButton: document.getElementById("scrollPage"), + scrollVerticalButton: document.getElementById("scrollVertical"), + scrollHorizontalButton: document.getElementById("scrollHorizontal"), + scrollWrappedButton: document.getElementById("scrollWrapped"), + spreadNoneButton: document.getElementById("spreadNone"), + spreadOddButton: document.getElementById("spreadOdd"), + spreadEvenButton: document.getElementById("spreadEven"), + imageAltTextSettingsButton: document.getElementById( + "imageAltTextSettings", + ), + imageAltTextSettingsSeparator: document.getElementById( + "imageAltTextSettingsSeparator", + ), + documentPropertiesButton: document.getElementById("documentProperties"), + }, + sidebar: { + outerContainer: document.getElementById("outerContainer"), + sidebarContainer: document.getElementById("sidebarContainer"), + toggleButton: document.getElementById("sidebarToggleButton"), + resizer: document.getElementById("sidebarResizer"), + thumbnailButton: document.getElementById("viewThumbnail"), + outlineButton: document.getElementById("viewOutline"), + attachmentsButton: document.getElementById("viewAttachments"), + layersButton: document.getElementById("viewLayers"), + thumbnailView: document.getElementById("thumbnailView"), + outlineView: document.getElementById("outlineView"), + attachmentsView: document.getElementById("attachmentsView"), + layersView: document.getElementById("layersView"), + currentOutlineItemButton: document.getElementById("currentOutlineItem"), + }, + findBar: { + bar: document.getElementById("findbar"), + toggleButton: document.getElementById("viewFindButton"), + findField: document.getElementById("findInput"), + highlightAllCheckbox: document.getElementById("findHighlightAll"), + caseSensitiveCheckbox: document.getElementById("findMatchCase"), + matchDiacriticsCheckbox: document.getElementById("findMatchDiacritics"), + entireWordCheckbox: document.getElementById("findEntireWord"), + findMsg: document.getElementById("findMsg"), + findResultsCount: document.getElementById("findResultsCount"), + findPreviousButton: document.getElementById("findPreviousButton"), + findNextButton: document.getElementById("findNextButton"), + }, + passwordOverlay: { + dialog: document.getElementById("passwordDialog"), + label: document.getElementById("passwordText"), + input: document.getElementById("password"), + submitButton: document.getElementById("passwordSubmit"), + cancelButton: document.getElementById("passwordCancel"), + }, + documentProperties: { + dialog: document.getElementById("documentPropertiesDialog"), + closeButton: document.getElementById("documentPropertiesClose"), + fields: { + fileName: document.getElementById("fileNameField"), + fileSize: document.getElementById("fileSizeField"), + title: document.getElementById("titleField"), + author: document.getElementById("authorField"), + subject: document.getElementById("subjectField"), + keywords: document.getElementById("keywordsField"), + creationDate: document.getElementById("creationDateField"), + modificationDate: document.getElementById("modificationDateField"), + creator: document.getElementById("creatorField"), + producer: document.getElementById("producerField"), + version: document.getElementById("versionField"), + pageCount: document.getElementById("pageCountField"), + pageSize: document.getElementById("pageSizeField"), + linearized: document.getElementById("linearizedField"), + }, + }, + altTextDialog: { + dialog: document.getElementById("altTextDialog"), + optionDescription: document.getElementById("descriptionButton"), + optionDecorative: document.getElementById("decorativeButton"), + textarea: document.getElementById("descriptionTextarea"), + cancelButton: document.getElementById("altTextCancel"), + saveButton: document.getElementById("altTextSave"), + }, + newAltTextDialog: { + dialog: document.getElementById("newAltTextDialog"), + title: document.getElementById("newAltTextTitle"), + descriptionContainer: document.getElementById( + "newAltTextDescriptionContainer", + ), + textarea: document.getElementById("newAltTextDescriptionTextarea"), + disclaimer: document.getElementById("newAltTextDisclaimer"), + learnMore: document.getElementById("newAltTextLearnMore"), + imagePreview: document.getElementById("newAltTextImagePreview"), + createAutomatically: document.getElementById( + "newAltTextCreateAutomatically", + ), + createAutomaticallyButton: document.getElementById( + "newAltTextCreateAutomaticallyButton", + ), + downloadModel: document.getElementById("newAltTextDownloadModel"), + downloadModelDescription: document.getElementById( + "newAltTextDownloadModelDescription", + ), + error: document.getElementById("newAltTextError"), + errorCloseButton: document.getElementById("newAltTextCloseButton"), + cancelButton: document.getElementById("newAltTextCancel"), + notNowButton: document.getElementById("newAltTextNotNow"), + saveButton: document.getElementById("newAltTextSave"), + }, + altTextSettingsDialog: { + dialog: document.getElementById("altTextSettingsDialog"), + createModelButton: document.getElementById("createModelButton"), + aiModelSettings: document.getElementById("aiModelSettings"), + learnMore: document.getElementById("altTextSettingsLearnMore"), + deleteModelButton: document.getElementById("deleteModelButton"), + downloadModelButton: document.getElementById("downloadModelButton"), + showAltTextDialogButton: document.getElementById( + "showAltTextDialogButton", + ), + altTextSettingsCloseButton: document.getElementById( + "altTextSettingsCloseButton", + ), + closeButton: document.getElementById("altTextSettingsCloseButton"), + }, + annotationEditorParams: { + editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"), + editorFreeTextColor: document.getElementById("editorFreeTextColor"), + editorInkColor: document.getElementById("editorInkColor"), + editorInkThickness: document.getElementById("editorInkThickness"), + editorInkOpacity: document.getElementById("editorInkOpacity"), + editorStampAddImage: document.getElementById("editorStampAddImage"), + editorFreeHighlightThickness: document.getElementById( + "editorFreeHighlightThickness", + ), + editorHighlightShowAll: document.getElementById("editorHighlightShowAll"), + }, + printContainer: document.getElementById("printContainer"), + editorUndoBar: { + container: document.getElementById("editorUndoBar"), + message: document.getElementById("editorUndoBarMessage"), + undoButton: document.getElementById("editorUndoBarUndoButton"), + closeButton: document.getElementById("editorUndoBarCloseButton"), + }, + }; +} +function webViewerLoad() { + const config = getViewerConfiguration(); + const event = new CustomEvent("webviewerloaded", { + bubbles: true, + cancelable: true, + detail: { + source: window, + }, + }); + try { + parent.document.dispatchEvent(event); + } catch (ex) { + console.error("webviewerloaded:", ex); + document.dispatchEvent(event); + } + PDFViewerApplication.run(config); +} +document.blockUnblockOnload?.(true); +if ( + document.readyState === "interactive" || + document.readyState === "complete" +) { + webViewerLoad(); +} else { + document.addEventListener("DOMContentLoaded", webViewerLoad, true); +} + +var __webpack_exports__PDFViewerApplication = + __webpack_exports__.PDFViewerApplication; +var __webpack_exports__PDFViewerApplicationConstants = + __webpack_exports__.PDFViewerApplicationConstants; +var __webpack_exports__PDFViewerApplicationOptions = + __webpack_exports__.PDFViewerApplicationOptions; +export { + __webpack_exports__PDFViewerApplication as PDFViewerApplication, + __webpack_exports__PDFViewerApplicationConstants as PDFViewerApplicationConstants, + __webpack_exports__PDFViewerApplicationOptions as PDFViewerApplicationOptions, +}; + +//# sourceMappingURL=viewer.mjs.map diff --git a/public/locales/de-DE/application.json b/public/locales/de-DE/application.json new file mode 100755 index 0000000..67b9c76 --- /dev/null +++ b/public/locales/de-DE/application.json @@ -0,0 +1,1111 @@ +{ + "login": { + "lastStep": "Letzter Schritt", + "siginToYourAccount": "Bei Ihrem Konto anmelden", + "createNewAccount": "Neues Konto erstellen", + "enterPassword": "Passwort eingeben", + "enterPasswordHint": "Bitte geben Sie das Passwort für {{email}} ein", + "paswordlessHint": "Das Konto {{email}} ist ein passwortloses Konto. Bitte wählen Sie eine der folgenden Authentifizierungsmethoden:", + "noAccountSignupNow": "Noch kein Konto? <0>Jetzt registrieren", + "haveAccountSignInNow": "Bereits ein Konto? <0>Jetzt anmelden", + "privacyPolicy": "Datenschutzrichtlinie", + "termOfUse": "Nutzungsbedingungen", + "signupHint": "Das Konto {{email}} existiert nicht. Möchten Sie sich jetzt registrieren?", + "accountNotFoundHint": "Das Konto {{email}} existiert nicht.", + "or": "Oder", + "selectAccountToUse": "Konto auswählen", + "useOtherAccount": "Anderes Konto verwenden", + "email": "E-Mail", + "password": "Passwort", + "captcha": "CAPTCHA", + "captchaError": "CAPTCHA konnte nicht geladen werden: {{message}}", + "signIn": "Anmelden", + "signUp": "Registrieren", + "signUpAccount": "Konto registrieren", + "useFIDO2": "Mit Passkey anmelden", + "usePassword": "Mit Passwort anmelden", + "forgetPassword": "Passwort vergessen?", + "2FA": "Zwei-Faktor-Authentifizierung", + "input2FACode": "Bitte geben Sie den 6-stelligen 2FA-Code ein", + "passwordNotMatch": "Die Passwörter stimmen nicht überein", + "findMyPassword": "Passwort wiederfinden", + "passwordReset": "Passwort wurde zurückgesetzt", + "newPassword": "Neues Passwort", + "repeatNewPassword": "Neues Passwort wiederholen", + "repeatPassword": "Passwort wiederholen", + "resetPassword": "Passwort zurücksetzen", + "backToSingIn": "Zurück zur Anmeldung", + "sendMeAnEmail": "E-Mail senden", + "resetEmailSent": "Eine E-Mail wurde gesendet, bitte überprüfen Sie Ihren Posteingang", + "browserNotSupport": "Nicht unterstützt vom aktuellen Browser oder der Umgebung", + "success": "Anmeldung erfolgreich", + "signUpSuccess": "Registrierung erfolgreich", + "activateSuccess": "Aktivierung erfolgreich", + "accountActivated": "Ihr Konto wurde erfolgreich aktiviert", + "title": "Bei {{title}} anmelden", + "sinUpTitle": "Bei {{title}} registrieren", + "activateTitle": "Konto aktivieren", + "activateDescription": "Eine Aktivierungs-E-Mail wurde an Ihre E-Mail-Adresse gesendet. Bitte besuchen Sie den Link in der E-Mail, um Ihre Registrierung abzuschließen.", + "continue": "Weiter", + "back": "Zurück", + "logout": "Abmelden", + "signingOut": "Wird abgemeldet...", + "loggedOut": "Sie wurden abgemeldet", + "clickToRefresh": "Klicken Sie, um das CAPTCHA zu aktualisieren", + "switchLanguage": "Sprache wechseln" + }, + "navbar": { + "notBefore": "Nicht vor", + "notAfter": "Nicht nach", + "minimum": "Minimum", + "maximum": "Maximum", + "fileSize": "Dateigröße", + "searchBase": "Suchpfad", + "searchInBase": "Suchen in <0>", + "conditionDuplicate": "Bedingung bereits vorhanden", + "fileType": "Dateityp", + "addCondition": "Bedingung hinzufügen", + "notNameOpOr": "Alle Schlüsselwörter müssen enthalten sein", + "caseFolding": "Groß-/Kleinschreibung ignorieren", + "keywords": "Schlüsselwörter", + "fileNameKeywordsHelp": "Nach Eingabe Enter drücken, um Schlüsselwort hinzuzufügen", + "advancedSearch": "Erweiterte Suche", + "searchFilesTitle": "Dateien suchen", + "searchIn": "Suchen nach <0>{{keywords}}", + "recentlyViewed": "Kürzlich angesehen", + "searchFiles": "Dateien suchen...", + "showMore": "Mehr anzeigen", + "myFiles": "Meine Dateien", + "hisFiles": "Seine Dateien", + "trash": "Papierkorb", + "sharedWithMe": "Mit mir geteilt", + "myShare": "Meine Freigaben", + "remoteDownload": "Remote-Download", + "connect": "Verbindung & Einbindung", + "taskQueue": "Hintergrundaufgaben", + "setting": "Einstellungen", + "videos": "Videos", + "photos": "Bilder", + "music": "Musik", + "documents": "Dokumente", + "addATag": "Tag hinzufügen...", + "addTagDialog": { + "selectFolder": "Ordner auswählen", + "fileSelector": "Dateikategorie", + "folderLink": "Ordner-Verknüpfung", + "tagName": "Tag-Name", + "matchPattern": "Dateiname-Muster", + "matchPatternDescription": "Sie können <0>* als Platzhalter verwenden. Zum Beispiel bedeutet <1>*.png PNG-Bilder. Mehrere Regeln werden mit \"ODER\" verknüpft.", + "icon": "Symbol:", + "color": "Farbe:", + "folderPath": "Ordnerpfad" + }, + "storage": "Speicherplatz", + "storageDetail": "{{used}} verwendet, {{total}} gesamt", + "notLoginIn": "Nicht angemeldet", + "visitor": "Besucher", + "objectsSelected": "{{num}} Objekte ausgewählt", + "searchPlaceholder": "Drücken Sie <0>/ zum Suchen", + "backToHomepage": "Zurück zur Startseite", + "darkModeSwitch": "Dunklen Modus einstellen", + "toDarkMode": "Dunkel", + "toLightMode": "Hell", + "myProfile": "Mein Profil", + "dashboard": "Dashboard" + }, + "fileManager": { + "currentStoragePolicy": "Aktuelle Speicherrichtlinie: {{policy}}", + "customProps": "Benutzerdefinierte Eigenschaften", + "rating": "Bewertung", + "description": "Beschreibung", + "add": "Hinzufügen", + "clickToEdit": "Klicken zum Bearbeiten...", + "clickToEditSelect": "Klicken zum Auswählen...", + "enterUrl": "URL eingeben...", + "searchUser": "Benutzer suchen...", + "typeToSearch": "Spitzname oder E-Mail eingeben...", + "searchProperty": "Dateien mit gleicher Eigenschaft suchen", + "quality": "Qualität", + "audioTrack": "Audiospur", + "auto": "Automatisch", + "default": "Standard", + "shareWithMeEmpty": "Keine Freigaben von anderen gefunden", + "shareWithMeEmptyDes": "Um hier Freigaben von anderen zu sehen, speichern Sie beim Besuch einer Freigabe die Verknüpfung in der oberen rechten Ecke an einem beliebigen Ort in Ihren Dateien.", + "selectAll": "Alle auswählen", + "selectNone": "Auswahl aufheben", + "invertSelection": "Auswahl umkehren", + "imageSize": "Bildgröße", + "focalLength": "Brennweite", + "columnExisted": "Spalte bereits vorhanden", + "metadataColumn": "Metadaten ({{metadata}})", + "column": "Spalte", + "listColumnSetting": "Spalteneinstellungen", + "addColumn": "Spalte hinzufügen", + "failedLoadPreview": "Vorschau konnte nicht geladen werden", + "recursiveLimitReached": "Suchtiefe erreicht", + "recursiveLimitReachedDes": "Das System hat die Suche in tieferen Verzeichnissen gestoppt. Versuchen Sie, den Suchbereich zu verkleinern.", + "searchConditions": "{{num}} Bedingungen", + "createDate": "Erstellungsdatum", + "updatedDate": "Änderungsdatum", + "cameraMake": "Kamerahersteller", + "cameraModel": "Kameramodell", + "lensModel": "Objektivmodell", + "lensMake": "Objektivhersteller", + "metadataKey": "Schlüssel", + "metadataValue": "Wert", + "metadata": "Metadaten", + "symbolicFile": "Verknüpfung", + "relocation": "Speicherrichtlinie übertragen", + "downloadingFile": "\"{{name}}\" wird heruntergeladen, bitte schließen Sie diese Seite nicht...", + "mountOwner": "Nur der Eigentümer des aktuellen Verzeichnisses kann eine Richtlinie einbinden", + "uploading": "Wird hochgeladen", + "noActionsCanBeDone": "Keine Aktionen verfügbar", + "newFileName": "Neue Datei.{{ext}}", + "newDocumentType": "{{display_name}} (.{{ext}})", + "text": "Text", + "diagram": "Diagramm", + "whiteboard": "Whiteboard", + "selectApplications": "Anwendungen auswählen...", + "newlyCreatedFolder": "Neuer Ordner", + "expandAllApp": "Alle Anwendungen erweitern", + "epubViewer": "ePub-Reader", + "googledocs": "Google Docs Online-Reader", + "m365viewer": "Microsoft Office Online-Reader", + "pdfViewer": "PDF-Reader", + "archivePreview": "Archiv-Vorschau", + "extractSelected": "Ausgewählte Dateien extrahieren", + "viewerFileSizeWarning": "Die geöffnete Dateigröße ({{file_size}}) überschreitet das Limit von {{app}} ({{max}}) und funktioniert möglicherweise nicht ordnungsgemäß.", + "testSubtitleStyle": "Untertitel-Stil testen AaBbCc", + "color": "Farbe", + "fontSize": "Schriftgröße", + "disableSubtitle": "Untertitel deaktivieren", + "noSubtitle": "Keine ASS/SRT/VTT-Untertiteldateien im aktuellen Verzeichnis gefunden.", + "subtitleStyles": "Untertitel-Stile", + "subtitles": "Untertitel", + "markdownEditor": "Markdown-Editor", + "saveSuccess": "Erfolgreich gespeichert um {{time}}", + "drawioLng": "de", + "charset": "Kodierung", + "textType": "Texttyp", + "fileSaved": "Datei gespeichert", + "failedToLoadFile": "Datei konnte nicht geladen werden: {{msg}}", + "monacoEditor": "Monaco Code-Editor", + "preparingOpenFile": "Datei wird zum Öffnen vorbereitet...", + "openWithDescription": "Wählen Sie eine Anwendung zum Öffnen der .{{ext}}-Datei.", + "openWith": "Öffnen mit", + "readOnly": "Nur lesen", + "save": "Speichern", + "noMoreImages": "Keine weiteren Bilder auf dieser Seite", + "imageViewer": "Bildbetrachter", + "logFileDeleteShare": "Freigabe-Link löschen", + "logFileEditShare": "Freigabe-Link bearbeiten", + "deleteShareWarning": "Sind Sie sicher, dass Sie diesen Freigabe-Link löschen möchten?", + "edit": "Bearbeiten", + "editAndReactivate": "Bearbeiten und reaktivieren", + "yes": "Ja", + "no": "Nein", + "permanentValid": "Dauerhaft gültig", + "manageShares": "Freigabe-Links verwalten", + "manageDirectLinks": "Direkte Links verwalten", + "deleteLinkConfirm": "Sind Sie sicher, dass Sie diesen direkten Link löschen möchten?", + "directLinkNotFound": "Der gesuchte direkte Link existiert nicht mehr.", + "versionNotFound": "Die gesuchte Version existiert nicht mehr.", + "setNow": "Jetzt einstellen", + "permissionNotSet": "Für diese Datei wurden noch keine Berechtigungen festgelegt", + "permissionNotSetDes": "Wenn keine Berechtigungen festgelegt sind, werden die Berechtigungen des übergeordneten Verzeichnisses oder Freigabe-Links befolgt", + "permissions": "Berechtigungen", + "logFileUpdateMetadata": "Datei-Metadaten aktualisieren", + "all": "Alle", + "updatesOnly": "Nur Aktualisierungen", + "readsOnly": "Nur Lesevorgänge", + "myActivitiesOnly": "Meine Aktivitäten", + "logUpdateView": "Ansichtseinstellungen aktualisieren", + "logDeleteDirectLink": "Direkten Link löschen", + "logFileImported": "Aus externer Datei importiert", + "logGetDirectLink": "<0>Direkten Link erhalten", + "logFileMount": "An Speicherrichtlinie \"{{name}}\" gebunden", + "lookForThisVersion": "Diese Version suchen", + "logFileThumbGenerated": "Miniaturansicht-Generierung ausgelöst", + "logFileLivePhotoUploaded": "Live Photo hochgeladen", + "logFileCreate": "Dieses Objekt erstellen", + "logFileRename": "Objekt von \"{{originalName}}\" in \"{{newName}}\" umbenennen", + "logFileSetPermission": "Dateiberechtigungen ändern", + "logFileEntityUpload": "Dateiinhalt aktualisieren", + "logFileCopyFrom": "Dieses Objekt erstellen, kopiert von <0> nach <1>", + "logFileCopyTo": "Von <0> nach <1> kopiert", + "logFileMoveTo": "Von <0> nach <1> verschoben", + "logFileMoveToTrash": "Dieses Objekt von <0> in den Papierkorb verschoben", + "logFileShare": "Dieses Objekt geteilt", + "logFileSetCurrentVersion": "Dateiversion auf <0> zurückgesetzt", + "logFileDeleteVersion": "Version gelöscht, erstellt am <0>", + "logEntityDownloaded": "Dieses Objekt heruntergeladen oder gelesen", + "logDirectLinkDownloaded": "Dieses Objekt über <0>direkten Link gelesen", + "logRelocate": "Speicherrichtlinie auf {{newPolicy}} übertragen", + "logCreateArchive": "Zur Archivdatei <0> hinzugefügt", + "logExtractArchive": "Nach <0> extrahiert", + "deleteVersionWarning": "Sind Sie sicher, dass Sie diese Version löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "setAsCurrent": "Als aktuelle Version setzen", + "current": "[Aktuelle Version]", + "createdBy": "Erstellt von", + "manageVersions": "Versionen verwalten", + "livePhoto": "Live Photo", + "version": "Version", + "actions": "Aktionen", + "versionEntity": "Dateidaten und Versionsverlauf", + "data": "Daten", + "owned": "Besitzt diese Datei", + "ownedSymbolic": "Besitzt diese Verknüpfung", + "expires": "Ablaufzeit", + "originalLocation": "Ursprünglicher Ort", + "myPermissions": "Meine Berechtigungen", + "descendant": "Untergeordnete Objekte", + "folderChildren": "{{files}} Dateien, {{folders}} Ordner", + "moreThan": "Mehr als {{text}}", + "calculate": "Berechnen", + "unset": "Nicht gesetzt", + "folder": "Ordner", + "file": "Datei", + "symbolicLink": "Verknüpfung ({{srcType}})", + "type": "Typ", + "storageUsed": "Verwendeter Speicher", + "location": "Ort", + "basicInfo": "Grundinformationen", + "format": "Format", + "duration": "Dauer", + "artist": "Künstler", + "album": "Album", + "title": "Titel", + "resolution": "Auflösung", + "takenAt": "Aufgenommen am", + "software": "Software", + "copyright": "Urheberrecht", + "exposureBias": "Belichtungskorrektur", + "flash": "Blitz", + "copyToClipboard": "In Zwischenablage kopieren", + "searchSomething": "\"{{text}}\" suchen...", + "iso": "ISO", + "exposureValue": "{{num}} Sekunden", + "exposure": "Belichtung", + "aperture": "Blende", + "address": "Adresse", + "street": "Straße", + "locality": "Ort", + "place": "Stadt", + "district": "Bezirk", + "region": "Provinz", + "country": "Land", + "mediaInfo": "Medieninformationen", + "details": "Details", + "activity": "Aktivität", + "goToSharedLink": "Zu Freigabe-Link gehen", + "saveShortcut": "Freigabe als Verknüpfung speichern", + "customizeIcon": "Symbol anpassen", + "tags": "Tags", + "apply": "Anwenden", + "customizeColor": "Farbe anpassen", + "folderColor": "Ordnerfarbe", + "restore": "Wiederherstellen", + "unpin": "Anheftung entfernen", + "youDontHaveReadPermissionToThisFile": "Sie haben keine Berechtigung, diesen Inhalt zu lesen", + "anonymousAccessDenied": "Sie haben keine Berechtigung, diesen Inhalt zu lesen. Bitte versuchen Sie, sich anzumelden.", + "sharedWithOthers": "Mit anderen geteilt", + "new": "Neu", + "open": "Öffnen", + "openParentFolder": "Übergeordneten Ordner öffnen", + "download": "Herunterladen", + "batchDownload": "Batch-Download", + "share": "Teilen", + "rename": "Umbenennen", + "organize": "Organisieren", + "pin": "An Seitenleiste anheften", + "pinAlias": "Anzeigealias", + "optional": "Optional", + "move": "Verschieben", + "delete": "Löschen", + "moreActions": "Weitere Aktionen", + "refresh": "Aktualisieren", + "createArchive": "Archiv erstellen", + "resetThumbnail": "Beschädigte Miniaturansicht zurücksetzen", + "resetThumbnailRequested": "Zurücksetzen der Miniaturansicht angefordert.", + "noFileCanResetThumbnail": "Keine Dateien zum Zurücksetzen der Miniaturansicht verfügbar.", + "newFolder": "Neuen Ordner erstellen", + "newFile": "Neue Datei erstellen", + "showFullPath": "Pfad anzeigen", + "listView": "Listenansicht", + "gridView": "Rasteransicht", + "galleryView": "Galerieansicht", + "paginationSize": "Seitengröße", + "paginationOption": "{{option}} / Seite", + "noPagination": "Keine Paginierung", + "sortMethod": "Sortierung", + "sortMethods": { + "A-Z": "A-Z", + "Z-A": "Z-A", + "oldestUploaded": "Älteste hochgeladen", + "newestUploaded": "Neueste hochgeladen", + "oldestModified": "Älteste geändert", + "newestModified": "Neueste geändert", + "smallest": "Kleinste", + "largest": "Größte" + }, + "shareCreateBy": "Erstellt von {{nick}}", + "name": "Name", + "size": "Größe", + "lastModified": "Zuletzt geändert", + "currentFolder": "Aktueller Ordner", + "backToParentFolder": "Übergeordneter Ordner", + "folders": "Ordner", + "files": "Dateien", + "listError": "Fehler beim Anfordern", + "dropFileHere": "Dateien hierher ziehen", + "orClickUploadButton": "oder auf die Schaltfläche \"Neu\" oben links klicken, um Dateien hinzuzufügen", + "nothingFound": "Nichts gefunden", + "uploadFiles": "Dateien hochladen", + "uploadFolder": "Ordner hochladen", + "newRemoteDownloads": "Remote-Download", + "enter": "Eingeben", + "getSourceLink": "Direkten Link abrufen", + "createRemoteDownloadForTorrent": "Remote-Download-Aufgabe erstellen", + "extractArchive": "Archiv extrahieren", + "createShareLink": "Freigabe-Link erstellen", + "viewDetails": "Details anzeigen", + "copy": "Kopieren", + "bytes": " ({{bytes}} Bytes)", + "storagePolicy": "Speicherrichtlinie", + "inheritedFromParent": "(vom übergeordneten Verzeichnis übernommen)", + "childFolders": "Enthaltene Ordner", + "childFiles": "Enthaltene Dateien", + "childCount": "{{num}} Elemente", + "parentFolder": "Übergeordneter Ordner", + "rootFolder": "Stammordner", + "modifiedAt": "Geändert am", + "createdAt": "Erstellt am", + "statisticAt": "Statistik am", + "musicPlayer": "Musikplayer", + "closeAndStop": "Schließen und stoppen", + "playInBackground": "Im Hintergrund abspielen", + "copyTo": "Kopieren nach", + "copyToDst": "Kopieren nach <0>", + "moveTo": "Verschieben nach", + "moveToDst": "Verschieben nach <0>", + "errorReadFileContent": "Dateiinhalt konnte nicht gelesen werden: {{msg}}", + "wordWrap": "Zeilenumbruch", + "pdfLoadingError": "PDF konnte nicht geladen werden: {{msg}}", + "subtitleSwitchTo": "Untertitel gewechselt zu: {{subtitle}}", + "noSubtitleAvailable": "Keine Untertiteldateien im Videoverzeichnis verfügbar (unterstützt: ASS/SRT/VTT)", + "subtitle": "Untertitel auswählen", + "playlist": "Wiedergabeliste", + "openInExternalPlayer": "In externem Player öffnen", + "repeatMode": "Wiederholungsmodus", + "listRepeat": "Liste wiederholen", + "singleRepeat": "Einzeln wiederholen", + "shuffle": "Zufallswiedergabe", + "playbackSpeed": "Wiedergabegeschwindigkeit", + "searchResult": "Suchergebnis", + "preparingBathDownload": "Batch-Download wird vorbereitet...", + "preparingDownload": "Download wird vorbereitet...", + "browserDownload": "Browser-Download in lokales Verzeichnis", + "browserDownloadDescription": "Der Browser lädt die Dateistruktur nacheinander in das von Ihnen angegebene lokale Verzeichnis herunter.", + "browserBatchDownload": "Browser-seitiges Batch-Packen", + "browserBatchDownloadDescription": "Der Browser lädt in Echtzeit herunter und packt als ZIP-Datei, kann keine Daten größer als 4GB herunterladen.", + "serverBatchDownload": "Server-seitiges Batch-Packen", + "serverBatchDownloadDescription": "Der Server packt als ZIP-Datei und sendet in Echtzeit zum Client-Download, unterstützt keine Freigabe-Verknüpfungen.", + "selectArchiveMethod": "Batch-Download-Methode auswählen", + "batchDownloadStarted": "Batch-Download gestartet, bitte schließen Sie diese Seite nicht...", + "batchDownloadError": "Fehler beim Packen: {{msg}}", + "userDenied": "Benutzer verweigert", + "directoryDownloadReplace": "Diese Datei ersetzen", + "directoryDownloadReplaceDescription": "Wird die lokale \"{{name}}\" überschreiben", + "directoryDownloadSkip": "Diese Datei überspringen", + "directoryDownloadSkipDescription": "Wird den Download von \"{{name}}\" überspringen", + "selectDirectoryDuplicationMethod": "Datei-Duplikat", + "directoryDownloadReplaceAll": "Diese Datei und alle nachfolgenden Duplikate ersetzen", + "directoryDownloadReplaceAllDescription": "Wird die lokale \"{{name}}\" überschreiben und Auswahl merken", + "directoryDownloadSkipAll": "Diese Datei und alle nachfolgenden Duplikate überspringen", + "directoryDownloadSkipAllDescription": "Wird den Download von \"{{name}}\" überspringen und Auswahl merken", + "directoryDownloadStarted": "Download gestartet, bitte schließen Sie diesen Tab nicht", + "directoryDownloadFinished": "Download abgeschlossen, keine fehlgeschlagenen Objekte", + "directoryDownloadFinishedWithError": "Download abgeschlossen, {{failed}} Objekte fehlgeschlagen", + "directoryDownloadPermissionError": "Keine Berechtigung, bitte erlauben Sie Lesen/Schreiben lokaler Dateien", + "back": "Zurück", + "view": "Ansicht", + "layout": "Layout", + "thumbnails": "Miniaturansichten", + "on": "Ein", + "off": "Aus", + "viewSetting": "Ansichtseinstellungen", + "saved": "Gespeichert", + "notSet": "Nicht gesetzt", + "deleteViewSetting": "Ansichtseinstellung löschen" + }, + "modals": { + "includePasswordInShareLink": "Passwort in Link einbeziehen", + "includePasswordInShareLinkDes": "Wenn aktiviert, enthält der Freigabe-Link das Passwort, sodass beim Zugriff über diesen Link kein Passwort eingegeben werden muss.", + "showFileName": "Dateiname anzeigen", + "forceDownload": "Download erzwingen", + "archiveFile": "Archivdatei", + "cancelDownload": "Download abbrechen", + "always": "Immer", + "justOnce": "Nur einmal", + "quality": "Qualität", + "saveAsOtherFormat": "Als anderes Format speichern", + "conflictDes1": "Dateiversionskonflikt, mögliche Gründe:", + "conflictDes2": "<0>Die Datei wurde nach dem Öffnen von anderer Stelle mit einer neuen Version aktualisiert.<1>Wenn Sie unter einem neuen Dateinamen oder an einem neuen Ort gespeichert haben, existiert möglicherweise bereits eine Datei mit demselben Namen.", + "saveAs": "Speichern unter", + "versionConflict": "Versionskonflikt", + "overwrite": "Überschreiben", + "editShareLink": "Freigabe-Link bearbeiten", + "clearPermissions": "Berechtigungseinstellungen löschen", + "shortcutCreated": "Verknüpfung erstellt", + "createShortcut": "Verknüpfung erstellen", + "createShortcutTo": "Verknüpfung in <0> erstellen", + "read": "Anzeigen", + "readDes": "Für Dateien: Inhalt, Metadaten und andere Details anzeigen; für Verzeichnisse: Unterdateien und deren Metadaten anzeigen.", + "createDes": "Nur für Verzeichnisse gültig: neue Dateien erstellen oder hochladen, Dateien hinein verschieben oder kopieren.", + "update": "Ändern", + "updateDes": "Metadaten ändern, Objekte umbenennen, Aktivitätsverlauf anzeigen; für Dateien: Inhalt aktualisieren.", + "delete": "Löschen", + "deleteDes": "Objekte löschen oder an andere Orte verschieben.", + "noAccess": "Keine Berechtigung", + "targetExisted": "Ziel bereits vorhanden", + "explicitAccess": "Explizite Zugriffsberechtigungen", + "generalAccess": "Allgemeine Zugriffsberechtigungen", + "users": "Benutzer", + "groups": "Benutzergruppen", + "builtinCollections": "Integrierte Sammlungen", + "everyone": "Alle anderen", + "otherGroup": "Andere Benutzergruppen", + "sameGroup": "Dieselbe Benutzergruppe wie ich", + "anonymous": "Anonyme Besucher", + "noResults": "Keine Ergebnisse", + "searchGroupUser": "Benutzer oder Benutzergruppen suchen...", + "resetToDefault": "Auf Standard zurücksetzen", + "duplicateTag": "Tag \"{{tag}}\" bereits vorhanden", + "colorForTag": "Farbe für neues Tag anpassen", + "enterForNewTag": "Enter drücken, um neues Tag hinzuzufügen", + "manageTags": "Tags verwalten", + "onlyOwner": "Nur der Dateieigentümer kann diese Datei zwangsweise entsperren", + "forceUnlock": "Zwangsweise entsperren", + "forceUnlockAll": "Alle zwangsweise entsperren", + "forceUnlockDes": "Zwangsweises Entsperren kann zu abnormalem Dateistatus führen. Es wird empfohlen, zu warten, bis die Datei aktiv freigegeben wird. Möchten Sie wirklich entsperren?", + "webdav": "WebDAV", + "soft-delete": "In Papierkorb verschieben", + "updateMetadata": "Metadaten aktualisieren", + "upload": "Hochladen", + "moveCopy": "Verschieben oder kopieren", + "view": "Anzeigen", + "cannotPerformAction": "Verschieben oder kopieren hierher nicht unterstützt", + "cannotMoveCopyToChild": "Kann nicht in Unterverzeichnis verschieben oder kopieren", + "copySuccess": "{{num}} Dateien erfolgreich kopiert", + "moveSuccess": "{{num}} Dateien erfolgreich verschoben", + "setPermission": "Berechtigungen setzen", + "unknownParent": "Unbekanntes übergeordnetes Verzeichnis", + "unknownParentDes": "Das belegte Verzeichnis ist ein übergeordnetes Verzeichnis des geteilten Verzeichnisses, es gehört nicht Ihnen", + "lockConflictTitle": "Datei wird verwendet", + "lockConflictDescription": "Der Vorgang kann nicht abgeschlossen werden, da die folgenden Dateien gerade verwendet werden. Bitte versuchen Sie es später erneut. Wenn Sie der Dateieigentümer sind und sicher sind, dass die Datei nicht verwendet wird, können Sie die Datei zwangsweise entsperren und erneut versuchen.", + "usedBy": "Verwendet von", + "application": "Anwendung", + "errorDetailsTitle": "Fehlerdetails", + "processingMoving": "Dateien werden verschoben...", + "processingCopying": "Dateien werden kopiert...", + "processingRestoring": "Dateien werden wiederhergestellt...", + "fileRestored": "{{num}} Dateien an ursprünglichen Ort wiederhergestellt", + "duplicatedObjectName": "Neuer Name ist identisch mit vorhandener Datei", + "newNameLengthError": "Dateiname muss zwischen 1-255 Zeichen lang sein", + "newNameCharacterError": "Dateiname darf folgende Zeichen nicht enthalten: \\ / : * ? \" < > |", + "newNameDotError": "Dateiname darf nicht \".\" oder \"..\" sein", + "taskCreated": "Aufgabe erstellt", + "taskCreateFailed": "{{failed}} Aufgaben konnten nicht erstellt werden: {{details}}", + "linkCopied": "Link kopiert", + "getSourceLinkTitle": "Direkten Datei-Link abrufen", + "sourceLink": "Direkter Datei-Link", + "folderName": "Ordnername", + "create": "Erstellen", + "fileName": "Dateiname", + "renameDescription": "Neuen Namen für <0>{{name}} eingeben:", + "newName": "Neuer Name", + "moveToDescription": "Verschieben nach <0>{{name}}", + "saveToTitle": "Speichern unter", + "saveToTitleDescription": "Speichern unter <0>{{name}}", + "deleteTitle": "Objekt löschen", + "deleteOneDescription": "Sind Sie sicher, dass Sie <0>{{name}} in den Papierkorb verschieben möchten?", + "deleteMultipleDescription": "Sind Sie sicher, dass Sie diese {{num}} Objekte in den Papierkorb verschieben möchten?", + "deleteOneDescriptionHard": "Sind Sie sicher, dass Sie <0>{{name}} dauerhaft löschen möchten?", + "trashRetention": "Dateien im Papierkorb werden nach <0>{{num}} automatisch gelöscht.", + "deleteMultipleDescriptionHard": "Sind Sie sicher, dass Sie diese {{num}} Objekte dauerhaft löschen möchten?", + "newRemoteDownloadTitle": "Neue Remote-Download-Aufgabe erstellen", + "remoteDownloadURL": "Download-Link", + "remoteDownloadURLDescription": "Download-Adresse eingeben, eine pro Zeile", + "remoteDownloadDst": "Herunterladen nach", + "processNode": "Verarbeitungsknoten", + "remoteDownloadNodeAuto": "Automatisch zuweisen", + "createTask": "Aufgabe erstellen", + "downloadToDst": "Herunterladen nach <0>{{name}}", + "downloadTo": "Herunterladen nach", + "decompressTo": "Extrahieren nach", + "decompressToDst": "Extrahieren nach <0>{{name}}", + "defaultEncoding": "Standard", + "chineseMajorEncoding": "Häufige vereinfachte chinesische Kodierungen", + "selectEncoding": "ZIP-Dateikodierung", + "password": "Archiv-Passwort", + "passwordDescription": "Wenn das Archiv nicht verschlüsselt ist, lassen Sie dieses Feld leer.", + "noEncodingSelected": "Keine Kodierung ausgewählt", + "listingFiles": "Dateien werden aufgelistet...", + "listingFileError": "Fehler beim Auflisten der Dateien: {{message}}", + "generatingSourceLinks": "Direkte Links werden generiert...", + "noFileCanGenerateSourceLink": "Keine Dateien können direkte Links generieren", + "sourceBatchSizeExceeded": "Die aktuelle Benutzergruppe kann maximal {{limit}} Dateien gleichzeitig direkte Links generieren", + "zipFileName": "Archiv-Dateiname", + "shareLinkShareContent": "Ich teile mit Ihnen: {{name}} Link: {{link}}", + "shareLinkPasswordInfo": " Passwort: {{password}}", + "createShareLink": "Freigabe-Link erstellen", + "privateShare": "Link mit Passwort schützen", + "privateShareDes": "Wenn aktiviert, ist ein Passwort erforderlich, um auf den Freigabe-Link zuzugreifen.", + "useCustomPassword": "Benutzerdefiniertes Freigabe-Passwort", + "expireAfterDownload": "Nach Download automatisch ablaufen", + "sharePassword": "Freigabe-Passwort", + "randomlyGenerate": "Zufällig generieren", + "expireAutomatically": "Automatisch nach Timeout ablaufen", + "downloadLimitOptions": "{{num}} Downloads", + "or": "oder", + "5minutes": "5 Minuten", + "1hour": "1 Stunde", + "1day": "1 Tag", + "7days": "7 Tage", + "30days": "30 Tage", + "custom": "Benutzerdefiniert", + "minutes": "Minuten", + "downloads": "Downloads", + "expirePrefix": "Läuft ab nach ", + "expireSuffix": "", + "allowPreview": "Vorschau erlauben", + "allowPreviewDescription": "Ob Dateiinhalt auf der Freigabe-Seite in der Vorschau angezeigt werden darf", + "shareLink": "Freigabe-Link", + "sendLink": "Link senden", + "directoryDownloadReplaceNotifiction": "{{name}} überschrieben", + "directoryDownloadSkipNotifiction": "{{name}} übersprungen", + "directoryDownloadTitle": "Batch-Download-Protokoll", + "directoryDownloadStarted": "Download von \"{{name}}\" gestartet", + "directoryDownloadFinished": "Download von \"{{name}}\" abgeschlossen", + "directoryDownloadError": "Fehler aufgetreten: {{msg}}", + "directoryDownloadErrorNotification": "Fehler beim Download von {{name}}: {{msg}}", + "directoryDownloadAutoscroll": "Automatisches Scrollen", + "directoryDownloadCancelled": "Download abgebrochen", + "advanceOptions": "Erweiterte Optionen", + "skipSoftDelete": "Dateien dauerhaft löschen", + "skipSoftDeleteDes": "Papierkorb überspringen, Dateien direkt löschen", + "unlinkOnly": "Physische Dateien behalten", + "unlinkOnlyDes": "Nur Dateieinträge löschen, physische Dateien werden nicht gelöscht", + "shareView": "Freigabe-Ansichtseinstellungen", + "shareViewDes": "Wenn aktiviert, können andere Benutzer beim Zugriff auf diesen geteilten Ordner Ihre auf dem Server gespeicherten Ansichtseinstellungen (Layout, Sortierung usw.) sehen.", + "showReadme": "README-Datei anzeigen", + "showReadmeDes": "Wenn aktiviert, wird die <0>README.md-Datei (Groß-/Kleinschreibung beachten) im Verzeichnis automatisch für Besucher angezeigt.", + "viewSetting": "Ansichtseinstellungen", + "saved": "Gespeichert", + "notSet": "Nicht gesetzt", + "deleteViewSetting": "Ansichtseinstellung löschen" + }, + "uploader": { + "fileCopyName": "Kopie_", + "overwriteTooltip": "Vorhandene Dateien bei Namenskonflikt überschreiben, gilt nur für neu hinzugefügte Aufgaben", + "rename": "Mit neuem Dateinamen erneut versuchen", + "overwrite": "Vorhandene Datei überschreiben", + "pasteFilesHere": "Dateien hier einfügen", + "clipboardDefaultFileName": "Zwischenablage {{date}}.png", + "uploadFromClipboard": "Aus Zwischenablage hochladen", + "uploadList": "Upload-Liste", + "fileNotMatchError": "Ausgewählte Datei stimmt nicht mit Originaldatei überein", + "unknownError": "Unbekannter Fehler aufgetreten: {{msg}}", + "taskListEmpty": "Keine Upload-Aufgaben", + "hideTaskList": "Liste ausblenden", + "uploadTasks": "Upload-Warteschlange", + "moreActions": "Weitere Aktionen", + "addNewFiles": "Neue Dateien hinzufügen", + "toggleTaskList": "Warteschlange erweitern/minimieren", + "pendingInQueue": "In Warteschlange...", + "preparing": "Wird vorbereitet...", + "processing": "Wird verarbeitet...", + "progressDescription": "{{uploaded}} hochgeladen, {{total}} gesamt - {{percentage}}%", + "progressDescriptionFull": "{{speed}} {{uploaded}} hochgeladen, {{total}} gesamt - {{percentage}}%", + "progressDescriptionPlaceHolder": "Hochgeladen - ", + "uploaded": "Hochgeladen", + "rootFolder": "Stammordner", + "unknownStatus": "Unbekannt", + "resumed": "Wiederaufnahme", + "resumable": "Fortschritt kann wiederaufgenommen werden", + "retry": "Erneut versuchen", + "deleteTask": "Aufgabeneintrag löschen", + "cancelAndDelete": "Abbrechen und löschen", + "selectAndResume": "Gleiche Datei auswählen und Upload fortsetzen", + "fileName": "Dateiname:", + "fileSize": "Dateigröße:", + "sessionExpiredIn": "Läuft ab in <0>", + "chunkDescription": "({{total}} Chunks, jeweils {{size}})", + "noChunks": "(Keine Chunks)", + "destination": "Zielort:", + "storagePolicy": "Speicherrichtlinie:", + "uploadSession": "Upload-Sitzung:", + "errorDetails": "Fehlerdetails:", + "uploadSessionCleaned": "Upload-Sitzung bereinigt", + "hideCompletedTooltip": "Abgeschlossene, fehlgeschlagene und abgebrochene Aufgaben in der Liste nicht anzeigen", + "hideCompleted": "Abgeschlossene Aufgaben ausblenden", + "addTimeAscTooltip": "Zuerst hinzugefügte Aufgaben zuerst anzeigen", + "addTimeAsc": "Zuerst hinzugefügt oben", + "addTimeDescTooltip": "Zuletzt hinzugefügte Aufgaben zuerst anzeigen", + "addTimeDesc": "Zuletzt hinzugefügt oben", + "showInstantSpeedTooltip": "Upload-Geschwindigkeit einzelner Aufgaben als Momentangeschwindigkeit anzeigen", + "showInstantSpeed": "Momentangeschwindigkeit", + "showAvgSpeedTooltip": "Upload-Geschwindigkeit einzelner Aufgaben als Durchschnittsgeschwindigkeit anzeigen", + "showAvgSpeed": "Durchschnittsgeschwindigkeit", + "cleanAllSessionTooltip": "Alle unvollständigen Upload-Sitzungen auf dem Server löschen", + "cleanAllSession": "Alle Upload-Sitzungen löschen", + "cleanCompletedTooltip": "Abgeschlossene, fehlgeschlagene und abgebrochene Aufgaben aus der Liste entfernen", + "cleanCompleted": "Abgeschlossene Aufgaben löschen", + "retryFailedTasks": "Alle fehlgeschlagenen Aufgaben erneut versuchen", + "retryFailedTasksTooltip": "Alle fehlgeschlagenen Aufgaben in der Warteschlange erneut versuchen", + "setConcurrentTooltip": "Anzahl gleichzeitiger Aufgaben festlegen", + "setConcurrent": "Parallelität einstellen", + "sizeExceedLimitError": "Dateigröße überschreitet Speicherrichtlinien-Limit (Maximum: {{max}})", + "suffixNotAllowedError": "Speicherrichtlinie unterstützt das Hochladen von Dateien mit dieser Erweiterung nicht", + "regexpNotAllowedError": "Speicherrichtlinie unterstützt das Hochladen von Dateien mit diesem Namen nicht", + "suffixAllowed": "(Unterstützte Erweiterungen: {{supported}})", + "suffixDenied": "(Verbotene Erweiterungen: {{denied}})", + "createUploadSessionError": "Upload-Sitzung konnte nicht erstellt werden", + "deleteUploadSessionError": "Upload-Sitzung konnte nicht gelöscht werden", + "requestError": "Anfrage fehlgeschlagen: {{msg}} ({{url}})", + "chunkUploadError": "Chunk [{{index}}] Upload fehlgeschlagen", + "conflictError": "Upload-Aufgabe für gleichnamige Datei wird bereits verarbeitet", + "chunkUploadErrorWithMsg": "Chunk-Upload fehlgeschlagen: {{msg}}", + "chunkUploadErrorWithRetryAfter": "(Bitte nach {{retryAfter}} Sekunden erneut versuchen)", + "emptyFileError": "Hochladen leerer Dateien zu OneDrive derzeit nicht unterstützt, bitte erstellen Sie leere Dateien über die Schaltfläche \"Datei erstellen\"", + "finishUploadError": "Datei-Upload konnte nicht abgeschlossen werden", + "finishUploadErrorWithMsg": "Datei-Upload konnte nicht abgeschlossen werden: {{msg}}", + "ossFinishUploadError": "Datei-Upload konnte nicht abgeschlossen werden: {{msg}} ({{code}})", + "cosUploadFailed": "Upload fehlgeschlagen: {{msg}} ({{code}})", + "upyunUploadFailed": "Upload fehlgeschlagen: {{msg}}", + "parseResponseError": "Antwort konnte nicht geparst werden: {{msg}} ({{content}})", + "concurrentTaskNumber": "Anzahl gleichzeitiger Upload-Aufgaben", + "dropFileHere": "Maus loslassen, um Upload zu starten" + }, + "share": { + "free": "Kostenlos", + "price": "Preis", + "points": "{{num}} Punkte", + "statistics": "Statistiken", + "expireAt": "Läuft ab <0>", + "expireAfterDownloads": "Läuft nach {{downloads}} Downloads ab", + "somebodyShare": "{{name}}s Freigabe", + "expiredLink": "Abgelaufene Freigabe", + "sharedBy": "<0>{{nick}} hat {{num}} Dateien mit Ihnen geteilt", + "files": "1 Datei", + "files_other": "{{count}} Dateien", + "statisticsViews": "{{views}} Aufrufe", + "statisticsDownloads": "{{downloads}} Downloads", + "views": "{{count}} Aufruf", + "views_other": "{{count}} Aufrufe", + "downloads": "{{count}} Download", + "downloads_other": "{{count}} Downloads", + "privateShareTitle": "{{nick}}s verschlüsselte Freigabe", + "enterPassword": "Freigabe-Passwort", + "continue": "Fortfahren", + "shareCanceled": "Freigabe-Link wurde gelöscht", + "listLoadingError": "Laden fehlgeschlagen", + "sharedFiles": "Meine Freigaben", + "createdAtDesc": "Neueste", + "createdAtAsc": "Älteste", + "noRecords": "Keine Freigabe-Einträge.", + "sourceNotFound": "[Originalobjekt existiert nicht]", + "expired": "Abgelaufen", + "changeToPublic": "Zu öffentlicher Freigabe ändern", + "changeToPrivate": "Zu privater Freigabe ändern", + "viewPassword": "Passwort anzeigen", + "disablePreview": "Vorschau deaktivieren", + "enablePreview": "Vorschau aktivieren", + "cancelShare": "Freigabe abbrechen", + "sharePassword": "Freigabe-Passwort", + "readmeError": "README-Inhalt konnte nicht gelesen werden: {{msg}}", + "enterKeywords": "Bitte Suchbegriffe eingeben", + "searchResult": "Suchergebnis", + "sharedAt": "Geteilt am <0>", + "pleaseLogin": "Bitte melden Sie sich zuerst an", + "cannotShare": "Diese Datei kann nicht in der Vorschau angezeigt werden", + "preview": "Vorschau", + "incorrectPassword": "Falsches Passwort", + "shareNotExist": "Freigabe existiert nicht oder ist abgelaufen", + "copyLinkToClipboard": "Link in Zwischenablage kopieren" + }, + "download": { + "noFilesFound": "Keine Dateien gefunden", + "filterByName": "Nach Name filtern", + "selectAll": "Alle auswählen", + "reverseSelect": "Auswahl umkehren", + "cancelTaskConfirm": "Sind Sie sicher, dass Sie diese Aufgabe abbrechen möchten?", + "saveChanges": "Änderungen speichern", + "failedToLoad": "Laden fehlgeschlagen", + "active": "Aktiv", + "finished": "Abgeschlossen", + "activeEmpty": "Keine aktiven Download-Aufgaben", + "finishedEmpty": "Keine abgeschlossenen Aufgaben", + "loadMore": "Mehr laden", + "taskFileDeleted": "Datei wurde gelöscht", + "unknownTaskName": "[Unbekannt]", + "taskCanceled": "Aufgabe abgebrochen, Status wird später aktualisiert", + "operationSubmitted": "Operation erfolgreich, Status wird später aktualisiert", + "deleteThisFile": "Diese Datei löschen", + "openDstFolder": "Zielordner öffnen", + "selectDownloadingFile": "Zu downloadende Dateien auswählen", + "cancelTask": "Aufgabe abbrechen", + "updatedAt": "Aktualisiert am:", + "uploaded": "Upload-Größe", + "uploadSpeed": "Upload-Geschwindigkeit", + "InfoHash": "InfoHash", + "seederCount": "Seeder:", + "seeding": "Seeding:", + "downloadNode": "Knoten:", + "isSeeding": "Ja", + "notSeeding": "Nein", + "chunkSize": "Chunk-Größe:", + "chunkNumbers": "Chunk-Anzahl", + "taskDeleted": "Erfolgreich gelöscht", + "transferFailed": "Dateiübertragung fehlgeschlagen", + "downloadFailed": "Download-Fehler: {{msg}}", + "canceledStatus": "Abgebrochen", + "finishedStatus": "Abgeschlossen", + "pending": "Abgeschlossen, Übertragung in Warteschlange", + "transferring": "Wird übertragen", + "deleteRecord": "Eintrag löschen", + "createdAt": "Erstellungsdatum:", + "unknownSize": "Unbekannte Dateigröße" + }, + "setting": { + "notifyStoragePolicyChange": "Benachrichtigen, wenn ich in Verzeichnisse mit unterschiedlichen Speicherrichtlinien wechsle", + "notifyStoragePolicyChangeDes": "Wenn aktiviert, wird beim Betreten von Verzeichnissen mit gebundenen unterschiedlichen Speicherrichtlinien eine Benachrichtigung angezeigt.", + "treeView": "Baumansicht", + "autoExpandTreeView": "Baumansicht automatisch erweitern", + "autoExpandTreeViewDes": "Wenn aktiviert, wird der Dateibaum in der Seitenleiste dem aktuellen Verzeichnis folgen und automatisch erweitert.", + "syncView": "Ansichtseinstellungen", + "syncViewDes": "Ob Ansichtseinstellungen für jedes Verzeichnis gespeichert und mit dem Server synchronisiert werden sollen.", + "syncViewOn": "Mit Server synchronisieren", + "syncViewOff": "Nicht synchronisieren", + "reason": "Grund", + "change": "Änderung", + "success": "Erfolgreich", + "loginWithPasskey": "Passkey - {{name}}", + "loginWith": "Anmelde-Methode", + "result": "Ergebnis", + "device": "Gerät", + "ip": "IP", + "time": "Zeit", + "recentSignIn": "Kürzliche Anmelde-Aktivitäten", + "noAuthenticator": "Passkey hinzufügen, um sich mit Gesicht, Fingerabdruck oder USB-Schlüssel anzumelden", + "neverUsed": "Nie verwendet", + "usedAt": "Zuletzt verwendet am <0>", + "passkeyName": "{browser} auf {os}", + "passwordlessHint": "Dieses Konto ist ein passwortloses Konto", + "versionRetentionMax": "Maximale Versionsanzahl, 0 bedeutet unbegrenzt", + "versionRetentionEnabledExt": "Aktivierte Dateierweiterungen", + "versionRetentionEnabledExtDes": "Enter drücken zum Hinzufügen, leer lassen aktiviert für alle Dateien", + "enableVersionRetention": "Versionsspeicherung aktivieren", + "enableVersionRetentionDes": "Wenn aktiviert, speichert das System historische Versionen für qualifizierte Dateien", + "versionRetention": "Versionsspeicherung", + "languageDes": "Anwendungssprache und bevorzugte E-Mail-Sprache einstellen", + "timezoneDes": "Anzeigezeit-Zone einstellen, standardmäßig folgt sie der Systemzeit-Zone", + "unlinkConfirm": "Sind Sie sicher, dass Sie die Kontoverknüpfung aufheben möchten?", + "notLinked": "Nicht verknüpft", + "linkedAt": "Verknüpft am <0>", + "accountLinking": "Kontoverknüpfung", + "nickNameDes": "Name für öffentliche Anzeige, kann echter Name oder Spitzname sein", + "cropAvatar": "Avatar zuschneiden", + "finance": "Finanzen", + "preference": "Einstellungen", + "accountCreatedAt": "Erstellt am <0>", + "shoeQr": "Anzeigen", + "deviceNothing": "Aktuelle Benutzergruppe unterstützt WebDAV nicht", + "connectionInfo": "Verbindungsinformationen", + "proxyTooltip": "Server leitet alle Datei-Download-Anfragen weiter", + "readonlyTooltip": "Benutzer kann über dieses Konto nur Dateien lesen", + "rootFolderIn": "<0> auswählen", + "createWebDavAccount": "WebDAV-Konto erstellen", + "editWebDavAccount": "{{name}} bearbeiten", + "seeding": "Seeding", + "awaitSeeding": "Warten auf Seeding", + "awaitSeedingDes": "Warten auf Abschluss des Download-Task-Seeding.", + "downloadTransferDes": "Dateien an Zielort übertragen.", + "downloadDes": "Angegebene Dateien herunterladen.", + "retryErrorHistory": "Wiederholungs-Fehlerverlauf", + "retryCount": "Wiederholungsanzahl", + "resumeAt": "Nächste Wiederaufnahme", + "executeDuration": "Netto-Ausführungszeit", + "input": "Eingabe", + "output": "Ausgabe", + "suspended": " (Pausiert)", + "updatedAt": "Aktualisiert am", + "taskDetails": "Aufgabendetails", + "partialSuccessWarning": "{{num}} Objekte konnten nicht verarbeitet werden und wurden übersprungen.", + "sendTask": "Aufgabe senden", + "sendTaskDes": "Aufgabe an Verarbeitungsknoten senden.", + "downloaded": "Heruntergeladen", + "importingFiles": "Dateien importieren", + "importingFilesDes": "Dateien abrufen und in angegebenes Verzeichnis importieren.", + "importedFiles": "Importierte Dateien", + "indexedFiles": "Indizierte Dateien", + "extractedFiles": "Extrahierte Dateianzahl", + "extractedFilesSize": "Extrahierte Dateigröße", + "extractingFiles": "Dateien extrahieren", + "extractingFilesDes": "Alle Dateien in angegebenes Verzeichnis extrahieren.", + "downloadingZip": "Archiv abrufen", + "downloadingZipDes": "Archiv in temporären Arbeitsbereich herunterladen.", + "progressNotAvailable": "Fortschrittsinformationen noch nicht verfügbar", + "uploadedSize": "Übertragene Dateien", + "archivedFiles": "Verarbeitete Dateianzahl", + "transferredFiles": "Übertragene Dateianzahl", + "archivedFilesSize": "Verarbeitete Dateigröße", + "createArchiveFinishing": "Neue Dateiänderungen übermitteln.", + "indexForArchiveDes": "Alle zu komprimierenden Dateien abrufen.", + "prepare": "Vorbereiten", + "preparingWorkspaceDes": "Temporären Arbeitsbereich vorbereiten.", + "compressFiles": "Archiv erstellen", + "compressFilesDes": "Dateien in temporären Arbeitsbereich komprimieren.", + "uploadArchiveFileDes": "Archiv an Zielort übertragen.", + "uploadWorker": "Upload-Thread #{{num}}", + "relocatedEntities": "Übertragene Entitäten", + "queueToStart": "Warteschlange zum Start", + "indexingFiles": "Dateien abrufen", + "indexingFilesDes": "Alle zu übertragenden Dateien abrufen und sperren.", + "transferring": "Übertragen", + "transferringRelocateDes": "Daten an neue Speicherrichtlinie übertragen.", + "committingChanges": "Änderungen übermitteln", + "relocateFinishing": "Dateientitäten auf neue Speicherrichtlinie aktualisieren.", + "autoRefresh": "Automatisch aktualisieren", + "avatarUpdated": "Avatar aktualisiert, neuester Avatar-Anzeige kann verzögert sein", + "nickChanged": "Spitzname geändert, wirksam nach Aktualisierung", + "settingSaved": "Einstellungen gespeichert", + "themeColorChanged": "Themenfarbe geändert", + "profile": "Profil", + "avatar": "Avatar", + "uid": "UID", + "nickname": "Spitzname", + "group": "Benutzergruppe", + "regTime": "Registrierungszeit", + "security": "Passwort und Sicherheit", + "profilePage": "Profilseite", + "publicShareOnly": "Nur passwortlose Freigabe-Links anzeigen", + "publicShareOnlyDes": "Nur Freigabe-Links ohne Passwort auf der Profilseite anzeigen.", + "allShare": "Alle Freigaben", + "allShareDes": "Alle Freigabe-Links auf der Profilseite anzeigen (einschließlich passwortgeschützter). Für passwortgeschützte Freigaben müssen Benutzer noch das Passwort eingeben.", + "hideShare": "Alle Freigabe-Links ausblenden", + "hideShareDes": "Alle Freigabe-Links auf der Profilseite ausblenden.", + "userHideShare": "Benutzer hat Freigabe-Link-Liste ausgeblendet", + "accountPassword": "Anmelde-Passwort", + "2fa": "Zwei-Faktor-Authentifizierung", + "enabled": "Aktiviert", + "disabled": "Nicht aktiviert", + "appearance": "Erscheinungsbild", + "themeColor": "Themenfarbe", + "darkMode": "Dunkler Modus", + "syncWithSystem": "System", + "fileList": "Dateiliste", + "timeZone": "Zeitzone", + "webdavServer": "Verbindungsadresse", + "userName": "Benutzername", + "manageAccount": "Kontoverwaltung", + "uploadImage": "Aus Datei hochladen", + "useGravatar": "Gravatar-Avatar verwenden", + "changeNick": "Spitzname ändern", + "originalPassword": "Aktuelles Passwort", + "enable2FA": "2FA aktivieren", + "disable2FA": "2FA deaktivieren", + "2faDescription": "Bitte verwenden Sie eine beliebige 2FA-App oder Passwort-Manager-Software mit 2FA-Unterstützung, um den QR-Code zu scannen und diese Website hinzuzufügen. Nach dem Scannen geben Sie den 6-stelligen Verifizierungscode ein, den die 2FA-App bereitstellt, um 2FA zu aktivieren.", + "inputCurrent2FACode": "Geben Sie den aktuellen 6-stelligen Verifizierungscode aus der 2FA-App ein:", + "timeZoneCode": "IANA-Zeitzone-Identifikator", + "authenticatorRemoved": "Anmeldedaten entfernt", + "authenticatorAdded": "Authentifikator hinzugefügt", + "browserNotSupported": "Aktueller Browser oder Umgebung nicht unterstützt", + "removedAuthenticator": "Anmeldedaten entfernen", + "removedAuthenticatorConfirm": "Sind Sie sicher, dass Sie diese Anmeldedaten widerrufen möchten?", + "addNewAuthenticator": "Neue Anmeldedaten hinzufügen", + "hardwareAuthenticator": "Passkey", + "copied": "In Zwischenablage kopiert", + "pleaseManuallyCopy": "Aktueller Browser nicht unterstützt, bitte manuell kopieren", + "webdavAccounts": "WebDAV-Kontoverwaltung", + "webdavHint": "WebDAV-Adresse ist: {{url}}; Anmelde-Benutzername ist einheitlich: {{name}}; Passwort ist das Passwort des erstellten Kontos.", + "annotation": "Anmerkungsname", + "rootFolder": "Relatives Stammverzeichnis", + "createdAt": "Erstellungsdatum", + "action": "Aktion", + "readonlyOn": "Nur lesen", + "readonlyOff": "Lesen/Schreiben", + "proxy": "Reverse Proxy", + "none": "Keine", + "proxied": "Weitergeleitet", + "delete": "Löschen", + "listEmpty": "Keine Einträge", + "createNewAccount": "Neues Konto erstellen", + "taskType": "Aufgabentyp", + "taskStatus": "Status", + "taskProgress": "Aufgabenfortschritt", + "errorDetails": "Fehlermeldung", + "queueing": "In Warteschlange", + "processing": "Wird verarbeitet", + "failed": "Fehlgeschlagen", + "canceled": "Abgebrochen", + "finished": "Abgeschlossen", + "fileTransfer": "Dateiübertragung", + "fileRecycle": "Datei-Recycling", + "importFiles": "Externes Verzeichnis importieren", + "transferProgress": "{{num}} Dateien abgeschlossen", + "waiting": "Wartend", + "compressing": "Komprimieren", + "decompressing": "Extrahieren", + "downloading": "Herunterladen", + "indexing": "Indizieren", + "listing": "Einfügen", + "allShares": "Alle Freigaben", + "trendingShares": "Beliebte Freigaben", + "totalShares": "Gesamte Freigaben", + "fileName": "Dateiname", + "shareDate": "Freigabe-Datum", + "downloadNumber": "Download-Anzahl", + "viewNumber": "Aufruf-Anzahl", + "language": "Sprache", + "iOSApp": "iOS/iPadOS Client", + "connectByiOS": "Über iOS/iPadOS-Gerät mit <0>{{title}} verbinden", + "downloadOurApp": "Unsere App herunterladen und installieren:", + "fillInEndpoint": "QR-Code unten mit der App scannen (andere Scan-Apps sind ungültig):", + "loginApp": "Bindung abgeschlossen, Sie können den Client jetzt verwenden. Wenn beim QR-Code-Scannen Probleme auftreten, können Sie auch versuchen, Benutzername und Passwort manuell einzugeben.", + "relocateFileTo": "<0>{{more}} Speicherrichtlinie auf {{policy}} übertragen", + "extractFileTo": "<0>{{more}} nach <1> extrahieren", + "createArchiveTo": "<0>{{more}} nach <1> packen", + "importFileTo": "Dateien aus {{policy}} nach <0> importieren" + }, + "vas": { + "points": "Punkte", + "paid": "Bezahlt", + "fulfillFailedStatus": "Erfüllung fehlgeschlagen", + "unpaid": "Unbezahlt", + "amount": "Betrag", + "tradeNo": "Transaktionsnummer", + "payments": "Bestellungen", + "creditReasonShareGain": "Freigabe gekauft", + "creditReasonSharePay": "Shop-Verbrauch", + "creditReasonRecharge": "Aufladung", + "creditChanges": "Punkteänderungen", + "payXPoints": "<0> bezahlen", + "pointsPayAvailable": "Dieses Produkt unterstützt Punktezahlung, Sie können im nächsten Schritt <0> zum Einlösen auswählen.", + "payAmount": "{{price}} bezahlen", + "purchaseSomething": "{{name}} kaufen", + "redeem": "Einlösen", + "shop": "Shop", + "resumeTicket": "Wiederherstellungsbeleg", + "resumeTicketDes": "Sie können ihn in der Bestellbestätigungs-E-Mail finden, die nach der Zahlung gesendet wird", + "restorePurchase": "Kauf wiederherstellen", + "restorePurchaseDes": "Kauf über \"Wiederherstellungsbeleg\" in der Bestellbestätigungs-E-Mail wiederherstellen", + "paymentSuccess": "Zahlung erfolgreich", + "fulfillFailed": "Bestellerfüllung fehlgeschlagen, bitte kontaktieren Sie den Website-Administrator.", + "paidButton": "Ich habe bezahlt", + "payInNewWindow": "Bitte schließen Sie die Zahlung im neuen Popup-Fenster ab. Schließen Sie diese Seite nicht vor Abschluss der Zahlung. Wenn das neue Fenster nicht erscheint, <0>klicken Sie hier.", + "paymentFailedTitle": "Zahlungsverarbeitung fehlgeschlagen", + "paymentEmailHelper": "Da Sie nicht angemeldet sind, benötigen wir Ihre E-Mail-Adresse, um den Kaufbeleg zu senden", + "payEquivalentCash": "Gleichwertigen Betrag bezahlen: {{num}}", + "payWithCash": "Mit Bargeld bezahlen", + "recharge": "Aufladen", + "pointsBalance": "Punktestand: {{num}}", + "loginRequired": "Anmeldung erforderlich", + "payWithPoints": "Mit Punkten bezahlen", + "purchaseLogin": "Bitte <0>melden Sie sich an, bevor Sie fortfahren", + "noAvailableSharePurchaseMethod": "Keine verfügbaren Kaufmethoden", + "purchaseShareLink": "Freigabe-Link kaufen", + "loginWith": "Mit {{name}} anmelden", + "sso": "SSO", + "qq": "QQ", + "quota": "Speicherkontingent", + "exceedQuota": "Ihr verwendeter Speicher hat das Speicherkontingent überschritten, bitte löschen Sie überschüssige Dateien oder kaufen Sie Speicher", + "extendStorage": "Speicher erweitern", + "folderPolicySwitched": "Speicherrichtlinie des aktuellen Verzeichnisses wurde auf \"{{name}}\" umgestellt", + "switchFolderPolicy": "Verzeichnis-Speicherrichtlinie wechseln", + "setPolicyForFolder": "Speicherrichtlinie für aktuelles Verzeichnis festlegen: ", + "manageMount": "Bindungen verwalten", + "saveToMyFiles": "In meinen Dateien speichern", + "report": "Missbrauch melden", + "reportTarget": "Meldeobjekt", + "reportReason": "Grund", + "reportReasonOptions": ["Urheberrechtsverletzung", "Schädlicher Inhalt", "Spam", "Andere"], + "reportDescription": "Zusätzliche Beschreibung", + "reportAbuseSuccess": "Meldung eingereicht", + "migrateStoragePolicy": "Speicherrichtlinie übertragen", + "fileSaved": "Datei gespeichert", + "sharePurchaseTitle": "Dieser Freigabe-Link erfordert eine Zahlung von <0> für den Zugriff nach dem Kauf", + "payToDownload": "Bezahlen zum Herunterladen", + "creditToBePaid": "Zu zahlende Punkte", + "creditGainPredict": "Voraussichtlich {{num}} Punkte pro Kauf", + "creditPrice": " ({{num}} Punkte)", + "creditFree": " (Kostenlos)", + "cancelSubscription": "Kündigung erfolgreich, Änderungen werden in wenigen Minuten bis Stunden wirksam", + "qqUnlinked": "Verknüpfung mit QQ-Konto aufgehoben", + "groupExpire": "(Läuft ab <0>)", + "manuallyCancelSubscription": "Aktuelle Benutzergruppe manuell kündigen", + "qqAccount": "QQ-Konto", + "connect": "Verknüpfen", + "unlink": "Verknüpfung aufheben", + "credits": "Punkte", + "cancelSubscriptionTitle": "Benutzergruppe kündigen", + "cancelSubscriptionWarning": "Sie werden zur ursprünglichen Benutzergruppe zurückkehren und der bezahlte Betrag kann nicht erstattet werden. Möchten Sie fortfahren?", + "mountPolicy": "Speicherrichtlinien-Bindung", + "mountDescription": "Nach dem Binden einer Speicherrichtlinie an ein Verzeichnis werden neue Dateien, die in dieses Verzeichnis oder seine Unterverzeichnisse hochgeladen werden, mit der gebundenen Speicherrichtlinie gespeichert. Kopieren und Verschieben in dieses Verzeichnis wendet die gebundene Speicherrichtlinie nicht an; bei mehreren übergeordneten Verzeichnissen mit angegebenen Speicherrichtlinien wird die Speicherrichtlinie des nächstgelegenen übergeordneten Verzeichnisses gewählt.", + "mountNewFolder": "Neues Verzeichnis binden", + "nsfw": "Pornografische Inhalte", + "malware": "Enthält Viren", + "copyright": "Urheberrechtsverletzung", + "inappropriateStatements": "Unangemessene Aussagen", + "other": "Andere", + "groupBaseQuota": "Benutzergruppen-Basis-Speicher - {{size}}", + "validPackQuota": "Erweiterungsspeicher - {{size}}", + "used": "Verwendet - {{size}}", + "total": "Gesamtspeicher - {{size}}", + "validStorage": "Gültige Erweiterung", + "buyStorage": "Speicher kaufen", + "useGiftCode": "Aktivierungscode einlösen", + "packName": "Speicherpaket-Name", + "activationDate": "Aktivierungsdatum", + "validDuration": "Gültigkeitsdauer", + "expiredAt": "Ablaufdatum", + "days": "{{num}} Tage", + "pleaseInputGiftCode": "Bitte Einlösecode eingeben", + "pleaseSelectAStoragePack": "Bitte wählen Sie zuerst ein Speicherpaket", + "paymentMethod": "Zahlungsmethode", + "noAvailableMethod": "Keine verfügbaren Zahlungsmethoden", + "alipay": "Alipay QR-Code", + "wechatPay": "WeChat QR-Code", + "payByCredits": "Punktezahlung", + "purchaseDuration": "Kaufdauer", + "creditsNum": "Aufladepunkte-Anzahl", + "store": "Shop", + "storageExpansion": "Speichererweiterung", + "membership": "Mitgliedschaft", + "buyCredits": "Punkte aufladen", + "subtotal": "Aktuelle Kosten:", + "creditsTotalNum": "{{num}} Punkte", + "purchaseNow": "Jetzt kaufen", + "recommended": "Empfohlen", + "enterGiftCode": "Einlösecode eingeben", + "qrcodeAlipay": "Bitte verwenden Sie Alipay, um den QR-Code unten zu scannen und die Zahlung abzuschließen. Diese Seite wird nach Abschluss der Zahlung automatisch aktualisiert.", + "qrcodeWechat": "Bitte verwenden Sie WeChat, um den QR-Code unten zu scannen und die Zahlung abzuschließen. Diese Seite wird nach Abschluss der Zahlung automatisch aktualisiert.", + "qrcodeCustom": "Bitte scannen Sie den QR-Code unten, um die Zahlung abzuschließen, oder <0>öffnen Sie den Zahlungslink direkt. Diese Seite wird nach Abschluss der Zahlung automatisch aktualisiert.", + "paymentCompleted": "Zahlung abgeschlossen", + "productDelivered": "Ihr gekauftes Produkt wurde geliefert.", + "confirmRedeem": "Einlösung bestätigen", + "productType": "Produkt", + "qyt": "Menge:", + "duration": "Dauer:", + "subscribe": "Benutzergruppe kaufen", + "selected": "Ausgewählt:", + "paymentQrcode": "Zahlungs-QR-Code", + "validDurationDays": "{{num}} Tage", + "reportSuccessful": "Meldung erfolgreich", + "additionalDescription": "Zusätzliche Beschreibung", + "announcement": "Ankündigung", + "dontShowAgain": "Nicht mehr anzeigen", + "openPaymentLink": "Zahlungslink direkt öffnen", + "creditReasonAdjust": "Manuelle Anpassung" + } +} diff --git a/public/locales/de-DE/common.json b/public/locales/de-DE/common.json new file mode 100755 index 0000000..98d2275 --- /dev/null +++ b/public/locales/de-DE/common.json @@ -0,0 +1,120 @@ +{ + "pageNotFound": "Seite nicht gefunden", + "unknownError": "Unbekannter Fehler", + "errLoadingSiteConfig": "Fehler beim Laden der Website-Konfiguration: ", + "newVersionRefresh": "Eine neue Version der aktuellen Seite ist verfügbar.", + "update": "Aktualisieren", + "errorDetails": "Details", + "renderError": "Fehler beim Rendern der Seite. Bitte versuchen Sie, diese Seite zu aktualisieren.", + "ok": "OK", + "cancel": "Abbrechen", + "select": "Auswählen", + "copyToClipboard": "Kopieren", + "close": "Schließen", + "dismiss": "Verwerfen", + "intlDateTime": "{{val, datetime}}", + "seconds": "s [Sekunden]", + "minutes": "m [Minuten] s [Sekunden]", + "hours": "H [Stunden] m [Minuten]", + "days": "{{d}} Tage", + "timeAgoLocaleCode": "de_DE", + "forEditorLocaleCode": "de-DE", + "artPlayerLocaleCode": "de", + "requestID": "Anfrage-ID: {{id}}", + "object": "Objekt", + "error": "Fehler", + "areYouSure": "Sind Sie sicher?", + "incorrectSizeInput": "Fehlerhafte Größenangabe", + "of": "von", + "rowsPerPage": "Zeilen pro Seite", + "custom": "Benutzerdefiniert", + "enter": "Eingeben", + "captcha": { + "cap": { + "human": "Ich bin ein Mensch", + "verifying": "Verifizierung läuft...", + "verified": "Sie sind ein Mensch" + } + }, + "errors": { + "401": "Bitte melden Sie sich an.", + "403": "Sie sind nicht berechtigt, diese Aktion auszuführen.", + "404": "Ressource nicht gefunden.", + "409": "Konflikt. ({{message}})", + "40001": "Ungültige Eingabeparameter ({{message}}).", + "40002": "Upload fehlgeschlagen.", + "40003": "Ordner konnte nicht erstellt werden.", + "40004": "Objekt mit demselben Namen existiert bereits.", + "40005": "Signatur abgelaufen.", + "40006": "Nicht unterstützter Richtlinientyp.", + "40007": "Die aktuelle Gruppe hat keine Berechtigung für diese Aktion.", + "40011": "Upload-Sitzung existiert nicht oder ist abgelaufen.", + "40012": "Ungültiger Chunk-Index. ({{message}})", + "40013": "Ungültige Inhaltslänge. ({{message}})", + "40014": "Batch-Größenlimit für das Abrufen von Quelllinks überschritten.", + "40015": "Aria2-Batch-Größenlimit überschritten.", + "40016": "Pfad nicht gefunden.", + "40017": "Dieses Konto wurde gesperrt.", + "40018": "Dieses Konto ist nicht aktiviert.", + "40019": "Diese Funktion ist nicht aktiviert.", + "40020": "Ungültige oder abgelaufene Anmeldedaten.", + "40021": "Benutzer nicht gefunden.", + "40022": "Verifizierungscode ist nicht korrekt.", + "40023": "Anmeldesitzung existiert nicht.", + "40024": "WebAuthn kann nicht initialisiert werden.", + "40025": "Authentifizierung fehlgeschlagen.", + "40026": "CAPTCHA-Code ist nicht korrekt.", + "40027": "Verifizierung fehlgeschlagen. Bitte aktualisieren Sie die Seite und versuchen Sie es erneut.", + "40028": "E-Mail-Zustellung fehlgeschlagen.", + "40029": "Dieser Link ist ungültig.", + "40030": "Dieser Link ist abgelaufen.", + "40032": "Diese E-Mail-Adresse wird bereits verwendet.", + "40033": "Dieses Konto ist nicht aktiviert. Aktivierungs-E-Mail wurde erneut gesendet.", + "40034": "Dieser Benutzer kann nicht aktiviert werden.", + "40035": "Speicherrichtlinie nicht gefunden.", + "40039": "Gruppe nicht gefunden.", + "40044": "Datei nicht gefunden.", + "40045": "Objekte unter dem angegebenen Ordner konnten nicht aufgelistet werden.", + "40047": "Dateisystem konnte nicht initialisiert werden.", + "40048": "Aufgabe konnte nicht erstellt werden.", + "40049": "Dateigröße überschreitet das Limit.", + "40050": "Dateityp nicht erlaubt.", + "40051": "Unzureichendes Speicherkontingent.", + "40052": "Dieser Dateiname oder diese Erweiterung ist nicht erlaubt.", + "40053": "Diese Aktion kann nicht auf den Stammordner angewendet werden.", + "40054": "Eine Datei mit demselben Namen wird bereits in diesem Ordner hochgeladen. Bitte bereinigen Sie die Upload-Sitzungen.", + "40055": "Datei-Metadaten stimmen nicht überein.", + "40056": "Nicht unterstützter komprimierter Dateityp.", + "40057": "Verfügbare Speicherrichtlinie hat sich geändert. Bitte aktualisieren Sie die Dateiliste und fügen Sie diese Aufgabe erneut hinzu.", + "40058": "Diese Freigabe existiert nicht oder ist bereits abgelaufen.", + "40069": "Falsches Passwort.", + "40070": "Diese Freigabe unterstützt keine Vorschau.", + "40071": "Ungültige Signatur.", + "40073": "Datei wird verwendet.", + "40074": "Zu viele Dateien ausgewählt.", + "40079": "Maximale Anzahl durchlaufener Dateien überschritten. Versuchen Sie, den Umfang der Operation zu verringern.", + "40081": "Operation nicht vollständig erfolgreich.", + "40082": "Nur der Dateieigentümer kann diese Aktion ausführen.", + "40080": "Falsche E-Mail oder falsches Passwort.", + "50001": "Datenbankoperation fehlgeschlagen. ({{message}})", + "50002": "Fehler beim Signieren der URL oder Anfrage. ({{message}})", + "50004": "I/O-Operation fehlgeschlagen. ({{message}})", + "50005": "Interner Fehler.", + "50010": "Gewünschter Knoten ist nicht verfügbar.", + "50011": "Fehler beim Abfragen der Datei-Metadaten." + }, + "vasErrors": { + "40031": "Dieser E-Mail-Anbieter ist verboten. Bitte wechseln Sie zu einem anderen.", + "40059": "Sie können Ihre eigene Freigabe nicht speichern.", + "40062": "Unzureichende Credits.", + "40063": "Ihre aktuelle Mitgliedschaft ist noch nicht abgelaufen. Bitte gehen Sie zur Einstellungsseite, um die Mitgliedschaft manuell zu kündigen.", + "40064": "Sie befinden sich bereits in dieser Mitgliedschaft.", + "40065": "Ungültiger Geschenkcode.", + "40066": "Sie haben bereits eine verknüpfte Identität. Bitte entfernen Sie diese zuerst.", + "40067": "Diese Identität ist bereits mit einem anderen Konto verknüpft.", + "40068": "Diese Identität ist mit keinem Konto verknüpft.", + "40072": "Sie sind Administrator und können keine andere Gruppe erwerben.", + "40084": "Ihr Konto ist passwortlos. Sie müssen mindestens ein verknüpftes Konto behalten.", + "40085": "Der Gesamtbetrag dieser Bestellung ist zu gering für den Checkout." + } +} diff --git a/public/locales/de-DE/dashboard.json b/public/locales/de-DE/dashboard.json new file mode 100755 index 0000000..efdf253 --- /dev/null +++ b/public/locales/de-DE/dashboard.json @@ -0,0 +1,1612 @@ +{ + "errors": { + "40036": "Standard-Speicherrichtlinie kann nicht gelöscht werden.", + "40037": "Einige Datei-Blobs verwenden diese Richtlinie, bitte löschen Sie zuerst diese Datei-Blobs.", + "40038": "{{message}} Gruppe(n) verwenden diese Richtlinie, bitte trennen Sie zuerst diese Gruppen.", + "40040": "Diese Aktion kann nicht für die Systemgruppe ausgeführt werden.", + "40041": "{{message}} Benutzer sind noch in dieser Gruppe, bitte löschen oder trennen Sie zuerst diese Benutzer.", + "40042": "Die Gruppe des System-Benutzers kann nicht geändert werden.", + "40043": "Diese Aktion kann nicht für den Standard-Benutzer ausgeführt werden.", + "40046": "Diese Aktion kann nicht für den Master-Knoten ausgeführt werden.", + "40060": "Slave-Knoten kann keine Callback-Anfrage an Master senden, bitte überprüfen Sie die Master-Knoten-Einstellung: Grundeinstellungen - Site-Informationen - Site-URL, stellen Sie sicher, dass der Slave-Knoten auf diese URL zugreifen kann. ({{message}})", + "40061": "Cloudreve-Version stimmt nicht überein. ({{message}})", + "40086": "Der Knoten wird von folgenden Speicherrichtlinien verwendet: {{message}}.", + "50008": "Einstellung konnte nicht aktualisiert werden. ({{message}})", + "50009": "CORS-Richtlinie konnte nicht hinzugefügt werden." + }, + "nav": { + "summary": "Übersicht", + "settings": "Einstellungen", + "basicSetting": "Grundeinstellungen", + "email": "E-Mail", + "transportation": "Übertragung", + "appearance": "Erscheinungsbild", + "image": "Bilder", + "captcha": "Captcha", + "storagePolicy": "Speicherrichtlinie", + "nodes": "Knoten", + "groups": "Gruppen", + "users": "Benutzer", + "files": "Dateien", + "entities": "Datei-Blobs", + "shares": "Freigaben", + "tasks": "Hintergrundaufgaben", + "remoteDownload": "Remote-Download", + "generalTasks": "Allgemein", + "title": "Dashboard", + "dashboard": "Cloudreve Dashboard", + "userSession": "Benutzersitzung", + "fileSystem": "Dateisystem", + "mediaProcessing": "Medienverarbeitung", + "queue": "Warteschlange", + "events": "Ereignisse", + "server": "Server", + "customProps": "Benutzerdefinierte Eigenschaften", + "abuseReport": "Missbrauchsmeldung" + }, + "summary": { + "generatedAt": "Erstellt am <0>", + "confirmSiteURLTitle": "Site-URL bestätigen", + "siteURLNotMatch": "Die von Ihnen festgelegte Site-URL enthält nicht die aktuelle ({{current}}), möchten Sie sie zur Liste hinzufügen?", + "setAsPrimary": "Als primäre Site-URL festlegen", + "setAsPrimaryDes": "{{current}} als primäre Site-URL festlegen, die für die Kommunikation mit externen Diensten und den Empfang von Callbacks verwendet wird. Bitte verwenden Sie eine URL, die über WAN zugänglich ist.", + "setAsSecondary": "Zu sekundären URLs hinzufügen", + "setAsSecondaryDes": "{{current}} zu sekundären URLs hinzufügen, Cloudreve wird automatisch auswählen, ob es basierend auf der tatsächlich vom Benutzer aufgerufenen URL verwendet werden soll.", + "siteURLDescription": "Diese Einstellung ist sehr wichtig, stellen Sie sicher, dass sie mit der tatsächlichen URL Ihrer Site übereinstimmt. Sie können diese Einstellung unter Einstellungen - Grundeinstellungen ändern.", + "ignore": "Ignorieren", + "changeIt": "Ändern", + "trend": "Trend", + "summary": "Zusammenfassung", + "totalUsers": "Benutzer", + "totalFilesAndFolders": "Dateien und Ordner", + "shareLinks": "Freigabe-Links", + "totalBlobs": "Blobs", + "homepage": "Startseite", + "github": "GitHub", + "documents": "Dokumentation", + "discordCommunity": "Discord-Community", + "telegram": "Telegram-Gruppe", + "forum": "GitHub Discussions", + "buyPro": "Auf Pro upgraden", + "publishedAt": "veröffentlicht am <0>", + "licenseExpireAt": "Lizenz-Ablaufdatum", + "permanentLicense": "Permanente Lizenz", + "offlineLicenseExpireAy": "Offline-Lizenz-Ablaufdatum", + "offlineLicenseDes": "Cloudreve wird die Offline-Lizenz automatisch aktualisieren, bevor sie abläuft, wenn Ihr Server mit dem Netzwerk verbunden ist.", + "licensedDomains": "Lizenzierte Domains", + "renew": "Offline-Lizenz aktualisieren", + "manageLicense": "Lizenz verwalten", + "volPurchase": "Die Client-VOL-Lizenz muss separat über das <0>Lizenzverwaltungs-Dashboard erworben werden. Die VOL-Lizenz ermöglicht es Ihren Benutzern, kostenlos über den <1>Cloudreve iOS-Client eine Verbindung zu Ihrer Site herzustellen, ohne dass Benutzer ein Abonnement für die iOS-App selbst bezahlen müssen. Nach dem Kauf einer Lizenz klicken Sie bitte unten auf \"Offline-Lizenz aktualisieren\".", + "iosVol": "iOS-Client-Volumenlizenz (VOL)", + "refreshSuccessfully": "Erfolgreich aktualisiert.", + "manualRefresh": "Offline-Lizenz manuell aktualisieren", + "manualRefreshDes": "Die automatische Aktualisierung der Offline-Lizenz ist fehlgeschlagen, bitte versuchen Sie, sich beim <0>Lizenzverwaltungs-Dashboard anzumelden, um die neueste Offline-Lizenz zu erhalten und sie unten einzufügen.", + "announcement": "Ankündigung" + }, + "queue": { + "queueName_io_intense": "IO-Intensiv", + "queueName_io_intenseDes": "Warteschlange für die Verarbeitung großer Mengen von IO-Operationen, einschließlich: Speicherrichtlinien-Transfer, Dekomprimierung, Komprimierung.", + "queueName_media_meta": "Medien-Metadaten-Extraktion", + "queueName_media_metaDes": "Wird verwendet, um Metadaten aus Mediendateien zu extrahieren.", + "queueName_recycle": "Blob-Recycling", + "queueName_recycleDes": "Wird verwendet, um abgelaufene Datei-Blobs zu löschen.", + "queueName_thumb": "Miniaturansichten-Generierung", + "queueName_thumbDes": "Wird verwendet, um Miniaturansichten für Dateien zu generieren.", + "queueName_remote_download": "Remote-Download", + "queueName_remote_downloadDes": "Wird verwendet, um Remote-Download-Aufgaben zu verarbeiten.", + "failed": "Fehlgeschlagen ({{count}})", + "success": "Erfolgreich ({{count}})", + "suspending": "Angehalten ({{count}})", + "busyWorker": "In Bearbeitung ({{count}})", + "submited": "Eingereicht ({{count}})", + "editQueueSettings": "Warteschlangen-Einstellungen bearbeiten - {{name}}", + "workerNum": "Worker-Threads", + "workerNumDes": "Maximale Anzahl von Aufgaben, die parallel in der Aufgaben-Warteschlange ausgeführt werden", + "maxExecution": "Maximale Ausführungszeit", + "maxExecutionDes": "Maximale Ausführungszeit (Sekunden) für eine Aufgabe, danach wird die Aufgabe beendet.", + "backoffFactor": "Backoff-Faktor", + "backoffFactorDes": "Wachstumsfaktor für Aufgaben-Wiederholungszeit-Intervalle.", + "backoffMaxDuration": "Maximale Backoff-Zeit", + "backoffMaxDurationDes": "Maximale Backoff-Zeit (Sekunden) für Aufgaben-Wiederholungen.", + "maxRetry": "Maximale Wiederholungen", + "maxRetryDes": "Maximale Anzahl von Wiederholungen nach einem Aufgabenfehler.", + "retryDelay": "Wiederholungsverzögerung", + "retryDelayDes": "Anfangsverzögerungszeit (Sekunden) für Aufgaben-Wiederholungen." + }, + "settings": { + "headlessFooter": "Landing-Page-Fußzeile", + "headlessFooterDes": "Benutzerdefinierter HTML-Inhalt, der am Ende der Anmelde-, Registrierungs- und Callback-Ergebnisseiten angezeigt wird.", + "headlessBottom": "Landing-Page-Unterteil", + "headlessBottomDes": "Benutzerdefinierter HTML-Inhalt, der am Ende der Anmelde-, Registrierungs- und Callback-Ergebnisseiten angezeigt wird.", + "customHTML": "Benutzerdefiniertes HTML", + "customHTMLDes": "Benutzerdefinierte HTML-Inhalte an der voreingestellten Position der Site einfügen.", + "sidebarBottom": "Seitenleiste unten", + "sidebarBottomDes": "Benutzerdefinierter HTML-Inhalt, der am Ende der Seitenleiste angezeigt wird.", + "addNavItem": "Navigationselement hinzufügen", + "customNavItems": "Benutzerdefinierte Seitenleisten-Elemente", + "customNavItemsDes": "Sie können benutzerdefinierte Elemente zur Seitenleiste hinzufügen, und Benutzer werden beim Klicken zum entsprechenden Link weitergeleitet.", + "navItemUrl": "Link", + "iconifyNamePlaceholder": "Iconify-Icon-Bezeichner, z.B. fluent:home-24-regular", + "imageUrl": "Bild-URL", + "iconifyName": "Iconify-Icon-Name", + "oidc": "OpenID Connect (OIDC)", + "oidcDes": "OpenID Connect (OIDC) ist ein offenes Authentifizierungsprotokoll für die Identitätsverifikation zwischen verschiedenen Systemen. Nach dem Erstellen einer Anwendung in einer Drittanbieter-Identitätsplattform fügen Sie bitte <0>{{url}} zum Feld \"Redirect URI\" hinzu. Weitere Details finden Sie in der <1>Dokumentation.", + "clientID": "Client-ID", + "clientIDDes": "Die Client-ID der in der Drittanbieter-Identitätsplattform erstellten Anwendung.", + "clientSecret": "Client-Secret", + "clientSecretDes": "Das Client-Secret der in der Drittanbieter-Identitätsplattform erstellten Anwendung.", + "scope": "Bereich", + "scopeDes": "Zusätzliche anzufordernde Bereiche, getrennt durch Kommas <0>,. Standardmäßig fordert Cloudreve <0>openid, <0>email und <0>profile an; hier nicht wiederholen.", + "oidcWellknown": "OIDC Wellknown-Konfiguration", + "oidcWellknownDes": "Wellknown-Dokument der Drittanbieter-Identitätsplattform, das die Konfigurationsinformationen von OpenID Connect enthält.", + "importFromWellknown": "Von URL importieren", + "importOidc": "OIDC Wellknown-Konfiguration importieren", + "oidcWellknownUrl": "Wellknown-URL", + "oidcWellknownUrlDes": "URL des Wellknown-Dokuments der Drittanbieter-Identitätsplattform, wie <0>https://accounts.google.com/.well-known/openid-configuration.", + "resetUrl": "URL zurücksetzen", + "exceedToleranceDays": "Toleranztage für Sperrung", + "activateUrl": "Aktivierungs-URL", + "domainNotLicensed": "Domain nicht lizenziert", + "domainNotLicensedDes": "Die von Ihnen festgelegte Site-URL enthält eine nicht autorisierte Domain, bitte fügen Sie diese Subdomain im <0>Lizenzverwaltungs-Dashboard hinzu und klicken Sie auf die Schaltfläche unten, um die Lizenz zu aktualisieren und es erneut zu versuchen.", + "showSettings": "Einstellungen anzeigen", + "perPage": "{{num}} pro Seite", + "noNodes": "Keine Knoten verfügbar.", + "extractMediaMeta": "Medien-Metadaten extrahieren", + "extractMediaMetaDes": "Medien-Datei-Metadaten für Anzeige und Suche extrahieren. Standardmäßig verwenden nicht-lokale Speicherrichtlinien nur den \"Nativen in Speicherrichtlinie\"-Generator. Sie können die Miniaturansichten-Fähigkeit von Drittanbieter-Speicherrichtlinien erweitern, indem Sie die \"Extraktor-Proxy\"-Funktion auf der Speicherrichtlinien-Einstellungsseite aktivieren. Weitere Details finden Sie in der <0>Dokumentation.", + "exif": "EXIF", + "exifDes": "EXIF-Metadaten aus Bilddateien für Anzeige und Suche extrahieren.", + "music": "Musik-Metadaten", + "musicDes": "Metadaten aus Musikdateien extrahieren, einschließlich Titel, Künstler, Album, etc.", + "ffprobe": "FFprobe", + "ffprobeDes": "FFprobe verwenden, um Metadaten aus Video- und Audiodateien zu extrahieren.", + "maxSizeLocal": "Max. Dateigröße (Lokaler Speicher)", + "maxSizeLocalDes": "Maximale Dateigröße für Metadaten-Extraktion, wenn die Datei in der lokalen Speicherrichtlinie gespeichert ist, 0 bedeutet keine Begrenzung.", + "maxSizeRemote": "Max. Dateigröße (Remote-Speicher)", + "maxSizeRemoteDes": "Maximale Dateigröße für Metadaten-Extraktion, wenn die Datei in Drittanbieter-Speicherrichtlinien gespeichert ist, 0 bedeutet keine Begrenzung.", + "exifBruteForce": "Brute-Force bei Bedarf verwenden", + "exifBruteForceDes": "Wenn aktiviert, wird die gesamte Datei gescannt, um EXIF-Daten zu finden, falls sie nicht am Standard-Header-Standort gefunden werden können. Dies kann die Verarbeitungszeit erhöhen, aber EXIF-Daten an nicht-standardmäßigen Standorten finden.", + "musicCover": "Musik-Cover", + "musicCoverDes": "Album-Cover aus Musikdateien extrahieren, unterstützt ID3 (v1, 2.2, 2.3 und 2.4) Container. Dieser Generator hängt von einem anderen Bild-Miniaturansichten-Generator ab (Cloudreve eingebaut oder VIPS).", + "geocoding": "Geokodierung", + "geocodingDes": "Adressinformationen mit dem Mapbox-Dienst basierend auf den in den Medien-EXIF aufgezeichneten Koordinateninformationen abrufen.", + "mapboxAK": "Mapbox API-Schlüssel", + "mapboxAKDes": "API-Schlüssel, der in der Mapbox-Konsole erstellt wurde.", + "geocodingDependencyWarning": "Der Geokodierungs-Generator hängt vom EXIF-Generator ab, bitte aktivieren Sie den EXIF-Generator.", + "notAppliedToNativeGenerator": "{{prefix}}Nicht anwendbar auf nativen Generator von Speicherrichtlinien.", + "notAppliedToOneDriveNativeGenerator": "{{prefix}}Nicht anwendbar auf nativen Generator von OneDrive oder SharePoint Speicherrichtlinien.", + "fileBlobMargin": "Datei-Blob-URL-Cache-Marge (Sekunden)", + "fileBlobMarginDes": "Wenn derselbe Datei-Blob mehrmals angefordert wird, wird dieselbe URL wiederverwendet, wenn die ursprüngliche URL eine verbleibende Gültigkeitsdauer größer als die Marge hat.", + "fileBlobTimeout": "Datei-Blob-URL-TTL (Sekunden)", + "fileBlobTimeoutDes": "Begrenzt die Gültigkeitsdauer der temporären URL, die beim Öffnen oder Herunterladen von Dateien durch Benutzer erhalten wird, nur anwendbar auf lokale Speicherrichtlinien, WebDAV oder über Cloudreve weitergeleitete Datei-Downloads.", + "wopiSessionTimeout": "WOPI-Sitzungs-TTL (Sekunden)", + "wopiSessionTimeoutDes": "Begrenzt die Gültigkeitsdauer einer einzelnen Sitzung, wenn Benutzer Dateien mit WOPI bearbeiten. Nach Ablauf müssen Benutzer die Datei erneut von Cloudreve öffnen.", + "oauthRefresh": "Aktualisierungsintervall für OAuth-Speicherrichtlinie", + "oauthRefreshDes": "Legt fest, wie oft die OAuth-Anmeldedaten für Speicherrichtlinien (z.B. OneDrive), die OAuth erfordern, aktualisiert werden. Dies kann das Ablaufen von Anmeldedaten aufgrund langer Inaktivitätsperioden verhindern", + "transitParallelNum": "Max. parallele Relay-Übertragungen", + "transitParallelNumDes": "Die maximale Anzahl paralleler Uploads, wenn eine einzelne serverseitige Datei-Relay-Übertragungsaufgabe mehrere Dateien enthält.", + "failedChunkRetry": "Maximale Anzahl von Wiederholungen für Chunk-Upload-Fehler", + "failedChunkRetryDes": "Die maximale Anzahl von Wiederholungen für Chunk-Upload-Fehler, nur anwendbar auf serverseitige Uploads oder Relay-Übertragungen.", + "cacheChunks": "Streaming-Chunks zwischenspeichern", + "cacheChunksDes": "Wenn aktiviert, werden die Chunk-Daten während der Streaming-Übertragung im System-Temporärverzeichnis zwischengespeichert, damit sie für das Wiederholen fehlgeschlagener Chunk-Uploads verwendet werden können;\n Wenn deaktiviert, nehmen Streaming-Übertragung-Chunk-Uploads keinen zusätzlichen Speicherplatz ein, aber der gesamte Upload schlägt sofort fehl, wenn der Chunk-Upload fehlschlägt.", + "folderPropsTimeout": "Ordnerstatistik-Cache-TTL (Sekunden)", + "folderPropsTimeoutDes": "Die Gültigkeitsdauer des Ergebnis-Caches, wenn Benutzer Ordnerstatistiken (Größe, Anzahl der Dateien, etc.) berechnen.", + "slaveAPIExpiration": "Slave-API-Signatur-TTL (Sekunden)", + "slaveAPIExpirationDes": "Die Signatur-Gültigkeitsdauer, die vom Master-Knoten beim Zugriff auf die Slave-Knoten-API verwendet wird.", + "uploadSessionTimeout": "Upload-Sitzungs-TTL (Sekunden)", + "uploadSessionDes": "In einer gültigen Upload-Sitzungsperiode können Benutzer für unterstützte Speicherrichtlinien unvollendete Aufgaben fortsetzen. Der maximale Wert, der eingestellt werden kann, ist durch die Regeln verschiedener Speicherrichtlinien-Anbieter begrenzt.", + "archiveTimeout": "Serverseitige Batch-Download-Sitzungs-TTL (Sekunden)", + "advanceOptions": "Erweiterte Optionen", + "emojiOptions": "Emoji-Optionen", + "addCategorize": "Kategorie hinzufügen", + "category": "Kategorie", + "searchQuery": "Datei-Kategorisierungsabfrage", + "importWopi": "WOPI-App-Einstellungen importieren", + "wopiEndpoint": "WOPI Discovery Endpoint", + "wopiDes": "Erweitern Sie Cloudreeves Online-Vorschau- und Bearbeitungsfähigkeiten durch Integration mit Online-Dokumentenverarbeitungssystemen, die das WOPI-Protokoll unterstützen. Bitte geben Sie hier die WOPI-Service-Discovery-Adresse ein, wie <0>https://example.com/hosting/discovery. Weitere Details finden Sie in der <1>Dokumentation.", + "embeddedWebpageViewer": "Eingebetteter Webseiten-Viewer", + "wopiViewer": "WOPI-Anwendung", + "ext": "Erweiterung", + "invalidWopiActionMapping": "Ungültige WOPI-Action-Zuordnung", + "woapiActionMapping": "WOPI-Action-Zuordnungen", + "drawioHost": "DrawIO-Instanz", + "drawioHostDes": "Sie können die URL für eine selbst gehostete Instanz verwenden.", + "openInNew": "In neuem Fenster öffnen", + "openInNewDes": "Wenn aktiviert, öffnet sich direkt ein neuer Tab, um diese Anwendung zu öffnen.", + "maxSize": "Max. Dateigröße", + "maxSizeDes": "Die maximale Dateigröße, die von dieser Anwendung unterstützt wird. 0 bedeutet keine Begrenzung. Wenn die Datei diese Größe überschreitet, wird sie trotzdem geöffnet, aber Benutzer werden gewarnt.", + "srcEncodedVar": "URL-kodierte Datei-Blob-temporäre Zugriffs-URL", + "srcVar": "Datei-Blob-temporäre Zugriffs-URL", + "srcBase64Var": "Base64-kodierte Datei-Blob-temporäre Zugriffs-URL", + "nameEncodedVar": "URL-kodierter Dateiname", + "versionEntityVar": "Die Blob-ID der geöffneten Dateiversion, leer bedeutet die neueste Version.", + "fileIdVar": "Datei-ID", + "userIdVar": "Benutzer-ID, leer wenn nicht angemeldet.", + "userDisplayNameVar": "URL-kodierter Benutzer-Anzeigename.", + "fileViewers": "Dateianwendungen", + "addViewer": "Anwendung hinzufügen", + "viewerGroupTitle": "Anwendungsgruppe #{{index}}", + "viewerType": "Typ", + "viewerPlatform": "Plattform", + "viewerPlatformDes": "Wählen Sie die entsprechende Plattform aus, um die Anwendung nur auf dieser Plattform anzuzeigen.", + "viewerPlatformPC": "Desktop", + "viewerPlatformMobile": "Mobil", + "viewerPlatformAll": "Alle", + "displayName": "Anzeigename", + "displayNameDes": "Anzeigename für Benutzer, unterstützt i18next-Schlüssel.", + "viewerEnabled": "Aktiviert", + "newFileAction": "Neue Datei-Aktionen", + "newFileActionDes": "Durch Hinzufügen dieser Zuordnung sehen Benutzer diese Anwendungsoption beim Klicken auf die \"Neu\"-Schaltfläche.", + "addNewFileAction": "Zuordnung hinzufügen", + "builtinViewerType": "Eingebaute Anwendung", + "wopiViewerType": "WOPI", + "customViewerType": "Angepasst", + "nMapping": "{{num}} Zuordnung(en)", + "editViewerTitle": "{{name}} bearbeiten", + "builtInIconUrlDes": "Diese eingebaute Anwendung hat ein Standard-Icon. Wenn die Icon-URL leer gelassen wird, wird das Standard-Icon verwendet.", + "viewerUrl": "Anwendungs-URL", + "viewerUrlDes": "URL der angepassten Anwendung, <0>magische Variablen werden unterstützt.", + "addIcon": "Icon hinzufügen", + "exts": "Erweiterungsliste", + "icon": "Icon", + "iconUrl": "Icon-URL", + "iconColor": "Farbe", + "iconColorDark": "Farbe (Dunkler Modus)", + "fileIcons": "Datei-Icons", + "builtinIcon": "Eingebaut", + "mimeMapping": "MIME-Typ-Zuordnung", + "mimeMappingDes": "MIME-Typ-Zuordnung im JSON-Format, wobei der Schlüssel die Dateierweiterung und der Wert der MIME-Typ ist. Cloudreve bestimmt den Datei-MIME-Typ basierend auf der Dateierweiterung und dieser Einstellung.", + "mapProvider": "Kartenanbieter", + "mapProviderDes": "Kartenanbieter, der zur Anzeige von Medienstandortinformationen verwendet wird.", + "mapGoogle": "Google Maps (Leaflet)", + "mapOpenStreetMap": "OpenStreetMap (Leaflet)", + "mapboxMap": "OpenStreetMap (Mapbox)", + "mapboxAccessToken": "Mapbox-Zugriffstoken", + "mapboxAccessTokenDes": "Öffentlicher Zugriffstoken, der in der <0>Mapbox-Konsole erstellt wurde.", + "tileType": "Standard-Kacheltyp", + "tileTypeDes": "Standard-Kacheltyp für Google Maps.", + "tileTypeTerrain": "Gelände", + "tileTypeSatellite": "Satellit", + "tileTypeGeneral": "Regulär", + "maxPageSize": "Max. Seitengröße", + "maxPageSizeDes": "Begrenzt die maximale Anzahl von Dateien, die Benutzer pro Seite anpassen können.", + "maxRecursiveSearch": "Max. rekursive Suchanzahl", + "maxRecursiveSearchDes": "Die maximale Anzahl rekursiver Suchen, die beim Suchen nach Dateien erlaubt sind. Wenn die Anzahl der gesuchten Dateien diese Grenze überschreitet, wird die Suche gestoppt und der Benutzer gewarnt.", + "maxBatchSize": "Max. Batch-Größe", + "maxBatchSizeDes": "Die maximale Anzahl von Dateien, die Benutzer in einem Batch bearbeiten können, nur die oberste Ebene wird gezählt, und die Anzahl der Dateien in Unterverzeichnissen wird nicht gezählt.", + "defaultPagination": "Paginierungsmethode für Dateiliste", + "cursorPagination": "Cursor-Paginierung", + "cursorPaginationDes": "Weitere Dateien werden automatisch geladen, wenn der Benutzer nach unten scrollt. Diese Methode funktioniert besser bei großen Dateilisten, aber die Gesamtzahl der Seiten kann nicht gesehen werden.", + "offsetPagination": "Offset-Paginierung", + "offsetPaginationDes": "Paginierungsnavigation wird am Ende der Seite angezeigt; Benutzer können die Gesamtzahl der Seiten sehen und zu einer bestimmten Seite springen. Diese Methode funktioniert etwas schlechter bei großen Dateilisten.", + "defaultPaginationDes": "Cursor-Paginierung wird beim Suchen erzwungen verwendet, unabhängig von den obigen Einstellungen.", + "publicResourceMaxAge": "Statische Ressourcen-Cache-Max-Age (Sekunden)", + "publicResourceMaxAgeDes": "Das Max-Age des Caches für öffentlich zugängliche statische Ressourcen (z.B. Dateien, Miniaturansichten und Benutzerprofilbilder).", + "cronDes": "{{des}} Eine korrekte <0>Cron-Syntax ist hier erforderlich. Ein Neustart von Cloudreve ist erforderlich, damit es wirksam wird.", + "entityCollectInterval": "Datei-Blob-Recycling-Intervall", + "entityCollectIntervalDes": "Legt fest, wie oft abgelaufene Datei-Blobs gescannt und gelöscht werden.", + "trashBinInterval": "Papierkorb-Scan-Intervall", + "trashBinIntervalDes": "Legt fest, wie oft abgelaufene Dateien im Papierkorb gescannt und gelöscht werden.", + "logtoName": "Name der Anmeldemethode", + "logtoNameDes": "Name der Anmeldemethode, der Benutzern angezeigt wird. Standard ist \"SSO\", unterstützt i18next-Schlüssel.", + "logtoDirectSSO": "Direkte Anmeldung", + "logtoDirectSSODes": "Wenn Sie den Logto-Anmeldebildschirm überspringen und direkt zur Drittanbieter-Anmeldung oder SSO springen möchten, geben Sie bitte hier die Kennung des sozialen Connectors ein. Weitere Details finden Sie in der <0>Logto-Dokumentation.", + "logtoEndpoint": "Logto-Endpunkt", + "logtoEndpointDes": "Die Logto-Endpunkt-URL, die vom Anwendungsverwaltungspanel erhalten wurde, die eine selbst gehostete Instanz sein kann.", + "logtoKey": "Anwendungsgeheimnis", + "logtoKeyDes": "Anwendungsgeheimnis, das auf der Anwendungsverwaltungsseite erstellt wurde.", + "logtoAppIDDes": "Anwendungs-ID, die auf der Anwendungsverwaltungsseite erstellt wurde.", + "logto": "Logto", + "logtoDes": "Mit <0>Logto können Sie mehr Drittanbieter-Plattform-Anmeldungen erreichen, wie Apple, GitHub, Microsoft Entra ID, Google, SMS, etc. Bitte erstellen Sie eine \"Traditionelle Webanwendung\" im Logto-Verwaltungsportal und fügen Sie <1>{{url}} zu den \"Redirect URIs\" hinzu.", + "thirdPartySignIn": "Drittanbieter-Anmeldung", + "logo": "LOGO", + "logoDes": "URL des LOGOs, bitte stellen Sie verschiedene Logos für dunkle und helle Modi zur Verfügung.", + "dark": "Dunkler Modus", + "light": "Heller Modus", + "tosUrl": "Nutzungsbedingungen-URL", + "tosUrlDes": "Wird in der Fußzeile der Anmelde- oder Registrierungsseite angezeigt, leer lassen, um nicht anzuzeigen.", + "privacyUrl": "Datenschutzrichtlinien-URL", + "privacyUrlDes": "Wird in der Fußzeile der Anmelde- oder Registrierungsseite angezeigt, leer lassen, um nicht anzuzeigen.", + "addSecondary": "Sekundäre Site-URL hinzufügen", + "secondarySiteURL": "Sekundär", + "secondaryDes": "Sie können auch andere sekundäre URLs hinzufügen, Cloudreve wird automatisch auswählen, ob es basierend auf der tatsächlich vom Benutzer aufgerufenen URL verwendet werden soll. Ihre Site-URL muss lizenziert sein.", + "primarySiteURL": "Primär", + "primarySiteURLDes": "Primäre Site-URL wird für die Kommunikation mit externen Diensten und den Empfang von Callbacks (z.B. Zahlung, Speicheranbieter) verwendet, bitte verwenden Sie eine URL, die über WAN zugänglich ist.", + "revert": "Änderungen rückgängig machen", + "saved": "Einstellungen gespeichert.", + "save": "Speichern", + "basicInformation": "Grundinformationen", + "mainTitle": "Site-Name", + "mainTitleDes": "Name der Instanz.", + "siteDescription": "Site-Beschreibung", + "siteDescriptionDes": "Beschreibung der Website, die möglicherweise in der Zusammenfassung der geteilten Seite angezeigt wird.", + "siteURL": "Site-URL", + "customFooterHTML": "Benutzerdefinierte Fußzeilen-HTML", + "customFooterHTMLDes": "Benutzerdefinierter HTML-Code, der am Ende der Seite eingefügt wird.", + "announcement": "Ankündigung", + "announcementDes": "Ankündigungen, die angemeldeten Benutzern angezeigt werden. Leerer Wert wird nicht angezeigt. Nach der Änderung dieses Inhalts werden alle Benutzer die Ankündigung erneut sehen.", + "supportHTML": "HTML oder Klartext eingeben.", + "branding": "Branding", + "smallIcon": "Kleines Icon", + "smallIconDes": "URL des kleinen Icons, ico- oder svg-Format. Dieses Icon wird auch in Browser-Tabs, Lesezeichen und Desktop-Verknüpfungen angezeigt.", + "mediumIcon": "Mittleres Icon", + "mediumIconDes": "URL des mittleren Icons, bevorzugte Größe 192x192, png-Format.", + "largeIcon": "Großes Icon", + "largeIconDes": "URL des großen Icons, bevorzugte Größe 512x512, png-Format. Dieses Icon wird auch beim Wechseln des Kontos in der iOS-App angezeigt.", + "displayMode": "Anzeigemodus", + "displayModeDes": "Der Anzeigemodus einer PWA-Anwendung nach der Installation.", + "themeColor": "Theme-Farbe", + "themeColorDes": "CSS-Farbwert, der die Farbe der Statusleiste auf dem PWA-Startbildschirm, die Statusleiste auf der Inhaltsseite und die Adressleiste beeinflusst.", + "backgroundColor": "Hintergrundfarbe", + "backgroundColorDes": "CSS-Farbwert.", + "hint": "Hinweis", + "webauthnNoHttps": "Web Authn erfordert, dass Ihre Website HTTPS-aktiviert ist, und bestätigen Sie bitte, dass in Einstellungen - Grundeinstellungen - Site-URL auch HTTPS verwendet wird.", + "accountManagement": "Konten", + "allowNewRegistrations": "Neue Anmeldungen akzeptieren", + "allowNewRegistrationsDes": "Nach der Deaktivierung können keine neuen Benutzer registriert werden, es sei denn, sie werden manuell von Administratoren hinzugefügt.", + "emailActivation": "E-Mail-Aktivierung", + "emailActivationDes": "Nach der Aktivierung müssen neue Benutzer auf den Aktivierungslink in der E-Mail klicken, um die Anmeldung abzuschließen. Bitte stellen Sie sicher, dass die <0>E-Mail-Zustellungseinstellungen korrekt sind, sonst wird die Aktivierungs-E-Mail nicht zugestellt.", + "captchaForSignup": "Captcha für Anmeldungen", + "captchaForSignupDes": "Ob das Captcha für Anmeldungen aktiviert werden soll.", + "captchaForLogin": "Captcha für Anmeldungen", + "captchaForLoginDes": "Ob das Captcha für Anmeldungen aktiviert werden soll.", + "captchaForReset": "Captcha für Passwort zurücksetzen", + "captchaForResetDes": "Ob das Captcha für das Zurücksetzen des Passworts aktiviert werden soll.", + "captchaForAbuseReport": "Captcha für Missbrauchsmeldung", + "captchaForAbuseReportDes": "Ob das Captcha für Missbrauchsmeldungen aktiviert werden soll.", + "webauthnDes": "Ob Benutzern erlaubt werden soll, sich mit Hardware-Authentifizierungsgeräten anzumelden, wie: Gesicht, Fingerabdruck oder USB-Schlüssel; die Site muss HTTPS aktiviert haben.", + "webauthn": "Mit Passkeys anmelden", + "defaultSymbolics": "Standard-Freigabe-Verknüpfungen", + "defaultSymbolicsDes": "Standard-Freigabe-Verknüpfungen im Stammverzeichnis neuer Benutzer. Bitte suchen Sie nach Freigabe-Links nach ID, Sie können die ID auf der linken Seite der <0>Freigabeliste sehen.", + "searchShare": "Freigabe-ID suchen...", + "defaultGroup": "Standardgruppe", + "defaultGroupDes": "Die anfängliche Benutzergruppe nach der Benutzerregistrierung.", + "testMailSent": "Test-E-Mail wurde gesendet.", + "testSMTPSettings": "SMTP-Einstellungen testen", + "testSMTPTooltip": "Cloudreve wird Ihre aktuellen SMTP-Einstellungen verwenden, um eine Test-E-Mail zu senden, keine Notwendigkeit, Einstellungen vor dem Testen zu speichern.", + "recipient": "Empfänger", + "send": "Senden", + "smtp": "SMTP", + "senderName": "Absendername", + "senderNameDes": "Der Absendername, der in der E-Mail angezeigt wird.", + "senderAddress": "Absenderadresse", + "senderAddressDes": "E-Mail-Adresse des Absenders.", + "smtpServer": "SMTP-Server", + "smtpServerDes": "SMTP-Server-Adresse, ohne Portnummer.", + "smtpPort": "SMTP-Port", + "smtpPortDes": "Port des SMTP-Servers.", + "smtpUsername": "SMTP-Benutzername", + "smtpUsernameDes": "SMTP-Benutzername, normalerweise derselbe wie die Absenderadresse.", + "smtpPassword": "SMTP-Passwort", + "smtpPasswordDes": "Passwort des Absender-Postfachs.", + "replyToAddress": "Antwort-an-Adresse", + "replyToAddressDes": "Das Postfach, das verwendet wird, um Antwort-E-Mails zu empfangen, wenn Benutzer auf vom System gesendete E-Mails antworten.", + "enforceSSL": "SSL-Verbindung erzwingen", + "enforceSSLDes": "Ob eine SSL-verschlüsselte Verbindung erzwungen werden soll. Wenn Sie keine E-Mails senden können, können Sie dies ausschalten und Cloudreve wird versuchen, STARTTLS zu verwenden und zu entscheiden, ob verschlüsselte Verbindungen verwendet werden sollen.", + "smtpTTL": "SMTP-Verbindungs-TTL (Sekunden)", + "smtpTTLDes": "SMTP-Verbindungen, die während der TTL-Periode etabliert wurden, werden von neuen E-Mail-Zustellungsanfragen wiederverwendet.", + "emailTemplates": "E-Mail-Vorlagen", + "activateNewUser": "Neuen Benutzer aktivieren", + "resetPassword": "Passwort zurücksetzen", + "sendTestEmail": "Test-E-Mail senden", + "transportation": "Übertragung", + "workerNum": "Anzahl der Worker", + "workerNumDes": "Die maximale Anzahl von Aufgaben, die parallel von der Master-Knoten-Aufgaben-Warteschlange ausgeführt werden, ein Neustart von Cloudreve ist erforderlich, damit es wirksam wird.", + "tempFolder": "Temp-Ordner", + "tempFolderDes": "Wird verwendet, um temporäre Dateien zu speichern, die von Aufgaben wie Dekomprimierung, Komprimierung usw. generiert werden.", + "textEditMaxSize": "Max. Größe bearbeitbarer Dokumentdateien", + "textEditMaxSizeDes": "Die maximale Größe einer Dokumentdatei, die online bearbeitet werden kann, Dateien über diese Größe können nicht online bearbeitet werden. Diese Einstellung gilt für Online-Web-Editoren wie Klartext, Code und Office-Dokumente (WOPI).", + "resetConnection": "Verbindung nach fehlgeschlagenem Upload zurücksetzen", + "resetConnectionDes": "Wenn aktiviert, wird der Server die Verbindung zwangsweise zurücksetzen, wenn die Upload-Verifizierung fehlschlägt.", + "batchDownload": "Batch-Download", + "previewURL": "Vorschau-URL", + "cannotDeleteDefaultTheme": "Standard-Theme kann nicht gelöscht werden.", + "themeConfig": "Konfigurationen", + "actions": "Aktionen", + "wrongFormat": "Falsches Format.", + "avatar": "Avatar", + "gravatarServer": "Gravatar-Server", + "gravatarServerDes": "URL des Gravatar-Spiegelservers.", + "avatarFilePath": "Avatar-Dateipfad", + "avatarFilePathDes": "Pfad zum Speichern der Avatar-Dateien der Benutzer, relativ zum Cloudreve-Datenordner.", + "avatarSize": "Max. Avatar-Dateigröße", + "avatarSizeDes": "Maximale Größe der Avatar-Dateien, die Benutzer hochladen können.", + "avatarImageSize": "Bildgröße (px)", + "avatarImageSizeDes": "Ausgewähltes Profilbild wird auf die angegebene Größe in Pixeln geändert.", + "filePreview": "Dateivorschau", + "thumbnails": "Miniaturansichten", + "thumbnailDoc": "Weitere Informationen zu Miniaturansichten finden Sie im <0>Dokument.", + "thumbnailDocLink": "https://docs.cloudreve.org/usage/thumbnails", + "thumbnailBasic": "Grundlegend", + "generators": "Miniaturansichten-Generatoren", + "thumbMaxSize": "Maximale ursprüngliche Dateigröße", + "thumbMaxSizeDes": "Die maximale ursprüngliche Dateigröße, für die Miniaturansichten generiert werden können, Miniaturansichten werden nicht generiert, wenn Dateien diese Größe überschreiten.", + "generatorProxyWarning": "Standardmäßig verwenden nicht-lokale Speicherrichtlinien nur den \"Nativen in Speicherrichtlinie\"-Generator. Sie können die Miniaturansichten-Fähigkeit von Drittanbieter-Speicherrichtlinien erweitern, indem Sie die \"Generator-Proxy\"-Funktion auf der Speicherrichtlinien-Einstellungsseite aktivieren. Weitere Details finden Sie in der <0>Dokumentation.", + "policyBuiltin": "Nativ in Speicherrichtlinie", + "policyBuiltinDes": "Verwenden Sie die native API vom Speicheranbieter, um Miniaturansichten zu verarbeiten. Für lokale und S3-Richtlinien ist dieser Generator nicht verfügbar und wird automatisch auf andere Generatoren zurückgreifen. Für andere Speicherrichtlinien gehen Sie bitte zur Speicherrichtlinien-Einstellungsseite, um diesen Generator zu konfigurieren.", + "cloudreveBuiltin": "Cloudreve eingebaut", + "cloudreveBuiltinDes": "Nur Bilder in PNG-, JPEG-, GIF-Formaten werden mit Cloudreeves eingebauten Bildverarbeitungsfähigkeiten unterstützt.", + "libreOffice": "LibreOffice", + "libreOfficeDes": "Verwenden Sie LibreOffice, um Miniaturansichten für Office-Dokumente zu generieren. Dieser Generator hängt von einem anderen Bild-Miniaturansichten-Generator ab (Cloudreve eingebaut oder VIPS).", + "libraw": "LibRaw / DCRaw", + "librawDes": "Verwenden Sie LibRaws DCRaw-Beispielprogramm oder die ursprüngliche DCRaw-Ausführbare Datei, um Miniaturansichten für RAW-Bilder zu generieren.", + "vips": "VIPS", + "vipsDes": "Verwenden Sie libvips, um Miniaturansichten-Bilder zu verarbeiten, unterstützen mehr Bildformate und verbrauchen weniger Ressourcen.", + "thumbDependencyWarning": "LibreOffice oder Musik-Cover-Generator hängen von Cloudreve eingebauten oder VIPS-Generatoren ab, bitte aktivieren Sie einen von beiden.", + "ffmpeg": "FFmpeg", + "ffmpegDes": "Verwenden Sie FFmpeg, um Video-Miniaturansichten zu generieren.", + "executable": "Ausführbare Datei", + "executableDes": "Der Pfad oder Befehl der ausführbaren Datei des Drittanbieter-Generators.", + "executableTest": "Test", + "executableTestSuccess": "Generator funktioniert, Version: {{version}}", + "generatorExts": "Verfügbare Erweiterungen", + "generatorExtsDes": "Liste der verfügbaren Dateierweiterungen für diesen Generator, bitte verwenden Sie Komma , um mehrere zu trennen.", + "ffmpegSeek": "Miniaturansichten-Aufnahmeort", + "ffmpegSeekDes": "Definieren Sie die Miniaturansichten-Abfangzeit, es wird empfohlen, einen kleineren Wert zu wählen, um den Generierungsprozess zu beschleunigen. Wenn die tatsächliche Länge des Videos überschritten wird, schlägt die Miniaturansichten-Generierung fehl.", + "ffmpegExtraArgs": "Zusätzliche Eingabeargumente", + "ffmpegExtraArgsDes": "Zusätzliche Eingabeargumente für den Aufruf von FFmpeg.", + "generatorProxy": "Generator-Proxy", + "enableThumbProxy": "Generator-Proxy verwenden", + "proxyPolicyList": "Aktivierte Speicherrichtlinie", + "proxyPolicyListDes": "Mehrfach auswählbar. Wenn aktiviert, werden Dateien, deren Speicherrichtlinie keine native Generierung unterstützt, ihre Miniaturansichten von Cloudreve proxy-generiert.", + "thumbWidth": "Max. Breite", + "thumbHeight": "Max. Höhe", + "thumbSuffix": "Blob-Datei-Suffix", + "thumbSuffixDes": "Das Suffix, das an den ursprünglichen Blob-Dateinamen für die generierte Miniaturansicht angehängt wird, ", + "thumbFormat": "Bildformat", + "thumbFormatDes": "Bevorzugtes Bildformat, wenn der Generator es nicht unterstützt, wird es automatisch auf jpg-Format herabgestuft.", + "thumbQuality": "Qualität", + "thumbQualityDes": "Komprimierungsqualität in Prozent, nur gültig für jpg- und webp-Kodierung. ", + "thumbGC": "GC nach Miniaturansichten-Generierung ausführen", + "captcha": "Captcha", + "captchaType": "Captcha-Typ", + "captchaTypeDes": "Captcha-Typ und -Anbieter auswählen.", + "plainCaptcha": "Einfache Grafik", + "reCaptchaV2": "reCAPTCHA V2", + "turnstile": "Cloudflare Turnstile", + "turnstileSiteKey": "Site-Schlüssel", + "turnstileSiteKSecret": "Geheimnis", + "cap": "Cap", + "capInstanceURL": "Instanz-URL", + "capInstanceURLDes": "Die URL Ihres selbst gehosteten Cap-Servers. Weitere Details finden Sie in der <0>Standalone-Modus-Dokumentation.", + "capSiteKey": "Site-Schlüssel", + "capSiteKeyDes": "Der Site-Schlüssel aus Ihrem Cap-Server-Dashboard.", + "capSecretKey": "Geheimer Schlüssel", + "capSecretKeyDes": "Der geheime Schlüssel aus Ihrem Cap-Server-Dashboard.", + "capAssetServer": "Asset-Server-Quelle", + "capAssetServerDes": "Wählen Sie die Quelle zum Laden von Cap-Captcha-statischen Assets. Die Verwendung eines selbst bereitgestellten Servers erfordert das Setzen von Umgebungsvariablen auf der Serverseite, bitte beziehen Sie sich auf <0>Asset-Server aktivieren.", + "capAssetServerJsdelivr": "jsDelivr CDN", + "capAssetServerUnpkg": "unpkg CDN", + "capAssetServerInstance": "Selbst gehosteter Server", + "captchaProvider": "Captcha-Anbieter", + "captchaWidth": "Breite", + "captchaHeight": "Höhe", + "captchaLength": "Länge", + "captchaLengthDes": "Die Länge der Zeichen im Captcha.", + "captchaMode": "Modus", + "captchaModeNumber": "Zahlen", + "captchaModeLetter": "Buchstaben", + "captchaModeMath": "Mathematik", + "captchaModeNumberLetter": "Zahlen + Buchstaben", + "captchaElement": "Elemente innerhalb des Captcha-Bildes.", + "complexOfNoiseText": "Komplexität von Rausch-Text", + "complexOfNoiseDot": "Komplexität von Rausch-Punkten", + "showHollowLine": "Hohle Linien anzeigen", + "showNoiseDot": "Rausch-Punkte anzeigen", + "showNoiseText": "Rausch-Text anzeigen", + "showSlimeLine": "Schleimlinien anzeigen", + "showSineLine": "Sinus-Linien anzeigen", + "siteKey": "Site-Schlüssel", + "siteKeyDes": "Sie finden ihn auf der <0>App-Verwaltungsseite.", + "siteSecret": "Geheimnis", + "siteSecretDes": "Sie finden es auf der <0>App-Verwaltungsseite.", + "secretID": "SecretId", + "secretIDDes": "Sie finden es auf der <0>Zugriffsverwaltungsseite.", + "secretKey": "SecretKey", + "secretKeyDes": "Sie finden es auf der <0>Zugriffsverwaltungsseite.", + "tCaptchaAppID": "APPID", + "tCaptchaAppIDDes": "Sie finden es auf der <0>Captcha-Verwaltungsseite.", + "tCaptchaSecretKey": "App-Secret-Schlüssel", + "tCaptchaSecretKeyDes": "Sie finden es auf der <0>Captcha-Verwaltungsseite.", + "staticResourceCache": "Öffentliche statische Ressourcen-Cache", + "staticResourceCacheDes": "Max-Age des Caches für öffentlich zugängliche statische Ressourcen (z.B. lokaler Richtlinien-Quelllink, Download-Link).", + "creditSystem": "Kreditsystem", + "creditAndVAS": "Kredit und VAS", + "enableCredit": "Kreditsystem aktivieren", + "enableCreditDes": "Kreditsystem aktivieren, um Benutzern zu erlauben, Preise für ihre Freigabe-Links zu setzen.", + "creditPrice": "Kreditpreis", + "creditPriceDes": "Preis für das Aufladen von Kreditpunkten mit Geld (in kleinster Währungseinheit). 0 eingeben, um Kreditaufladung zu deaktivieren.", + "shareScoreRate": "Provisionsrate des Freigabe-Besitzers", + "shareScoreRateDes": "Prozentsatz (1-100) der Kreditpunkte, die Freigabe-Besitzer erhalten, wenn ihre Freigabe-Links gekauft werden.", + "cronNotifyUser": "Scan-Intervall für Benutzer über dem Limit", + "cronNotifyUserDes": "Benutzer über dem Limit scannen und E-Mail-Erinnerungen senden, ", + "cronBanUser": "Benutzer-Sperr-Zeitplan", + "cronBanUserDes": "Benutzer scannen und sperren, die Speicherlimits und Pufferperioden überschreiten", + "anonymousPurchase": "Anonymer Kauf", + "anonymousPurchaseDes": "Nicht angemeldeten Benutzern erlauben, Freigabe-Links direkt zu kaufen", + "shopNavEnabled": "Shop-Navigation anzeigen", + "shopNavEnabledDes": "\"Shop\"-Elemente in der Seitenleisten-Navigation anzeigen", + "paymentSettings": "Zahlungseinstellungen", + "currencyCode": "Währungscode", + "currencyCodeDes": "Drei-Buchstaben-Währungscode (z.B. USD, CNY, EUR)", + "currencySymbol": "Währungssymbol", + "currencySymbolDes": "Anzuzeigendes Währungssymbol (z.B. $, Â¥, €)", + "currencyUnit": "Währungseinheit", + "currencyUnitDes": "Minimale Währungseinheit (z.B. 100 für Dollar/Cent)", + "paymentProviders": "Zahlungsanbieter", + "providerName": "Anbietername, wird Benutzern angezeigt.", + "providerType": "Anbietertyp", + "providerKey": "Geheimer Schlüssel", + "selectCurrency": "Häufige Währung auswählen", + "addPaymentProvider": "Zahlungsanbieter hinzufügen", + "stripeProvider": "Stripe", + "weixinProvider": "WeChat Pay", + "alipayProvider": "Alipay", + "customProvider": "Benutzerdefinierter Zahlungsanbieter", + "customProviderDes": "Erstellen Sie ein Plugin, um sich mit anderen Zahlungs-Gateways zu verbinden, siehe <0>Dokumentation für weitere Details.", + "providerKeyDes": "API-Geheimschlüssel von Stripe.", + "storageProductSettings": "Speicherprodukt", + "storageProductsDes": "Konfigurieren Sie Produkte, die Benutzer kaufen können, um ihren Speicherplatz zu erweitern.", + "addStorageProduct": "SKU hinzufügen", + "editStorageProduct": "SKU bearbeiten", + "storageSize": "Speichergröße", + "storageSizeBytes": "In dieser SKU enthaltene Größe", + "duration": "Dauer", + "durationSeconds": "Dauer in Sekunden (z.B. 2592000 für 30 Tage)", + "price": "Preis", + "priceInUnits": "Preis (in kleinster Währungseinheit)", + "priceInUnitsDes": "Preis wird angezeigt als:", + "chipLabel": "Label (optional)", + "chipLabelHelp": "Ein kurzes Textlabel, das neben dem Produktnamen angezeigt wird", + "usePoints": "Zahlung mit Punkten erlauben", + "points": "Punkte", + "pointsHelp": "Anzahl der Punkte, die erforderlich sind, um dieses Produkt zu kaufen", + "pointsUnit": "Punkte", + "groupProductSettings": "Gruppenprodukt", + "groupProductsDes": "Konfigurieren Sie Produkte, die Benutzer kaufen können, um bestimmten Benutzergruppen beizutreten.", + "addGroupProduct": "Gruppenprodukt hinzufügen", + "editGroupProduct": "Gruppenprodukt bearbeiten", + "groupId": "Gruppen-ID", + "groupIdHelp": "Die Benutzergruppe, zu der nach dem Kauf dieses Produkts gewechselt wird.", + "description": "Beschreibung", + "descriptionHelp": "Geben Sie Funktionen oder Vorteile ein, einen pro Zeile", + "receiptEmailTemplate": "Zahlungsbeleg-Vorlage", + "receiptEmailTemplateDes": "E-Mail-Vorlage, die an Benutzer gesendet wird, wenn eine Zahlung bestätigt wird.", + "activationEmailTemplate": "Kontoaktivierungs-Vorlage", + "activationEmailTemplateDes": "E-Mail-Vorlage, die an Benutzer gesendet wird, um ihre Konten zu aktivieren.", + "quotaExceededEmailTemplate": "Speicherkontingent überschritten-Vorlage", + "quotaExceededEmailTemplateDes": "E-Mail-Vorlage, die an Benutzer gesendet wird, wenn sie ihr Speicherkontingent überschreiten.", + "resetPasswordEmailTemplate": "Passwort-Reset-Vorlage", + "resetPasswordEmailTemplateDes": "E-Mail-Vorlage, die an Benutzer gesendet wird, wenn sie ein Passwort-Reset anfordern.", + "preferredLanguage": "Bevorzugte Sprache", + "setAsPreferredLanguage": "Als bevorzugte Sprache festlegen", + "setAsPreferredLanguageDes": "Wenn die Sprache des Benutzers nicht festgelegt ist, wird diese bevorzugte Sprache verwendet.", + "alreadyAsPreferredLanguageDes": "Die aktuelle Sprache ist bereits als bevorzugte Sprache festgelegt. Wenn die Sprache des Benutzers nicht festgelegt ist, wird diese bevorzugte Sprache verwendet.", + "addLanguage": "Sprache hinzufügen", + "removeLanguage": "Sprache entfernen", + "removeLanguageBtn": "Sprache entfernen", + "cannotRemovePreferredLanguageDes": "Die bevorzugte Sprache kann nicht entfernt werden. Bitte legen Sie eine andere Sprache als bevorzugte Sprache fest und versuchen Sie es erneut.", + "languageCodeDes": "Bitte wählen Sie die Sprache aus, die Sie hinzufügen möchten.", + "emailSubject": "E-Mail-Betreff", + "emailSubjectDes": "Die Betreffzeile der E-Mail. Sie können <0>magische Variablen verwenden, um den E-Mail-Betreff anzupassen.", + "emailBody": "E-Mail-Inhalt", + "emailBodyDes": "HTML-Inhalt der E-Mail. Sie können <0>magische Variablen verwenden, um den E-Mail-Inhalt anzupassen.", + "orderTitle": "Bestelltitel", + "themeOptions": "Theme-Optionen", + "themeOptionsDes": "Konfigurieren Sie benutzerdefinierte Theme-Optionen für Ihre Site. Diese Themes werden für Benutzer zur Auswahl in ihren Einstellungen verfügbar sein.", + "primaryColor": "Primärfarbe", + "secondaryColor": "Sekundärfarbe", + "primaryColorDark": "Primärfarbe (Dunkel)", + "secondaryColorDark": "Sekundärfarbe (Dunkel)", + "addThemeOption": "Theme-Option hinzufügen", + "editThemeOption": "Theme-Option bearbeiten", + "invalidThemeConfig": "Ungültige Theme-Konfiguration. Bitte überprüfen Sie Ihre JSON-Syntax.", + "themeConfiguration": "Theme-Konfiguration", + "themePreview": "Theme-Vorschau", + "lightTheme": "Helles Theme", + "darkTheme": "Dunkles Theme", + "previewTitle": "Vorschau-Titel", + "previewTextField": "Eingabefeld", + "previewPrimary": "Primär", + "invalidThemePreview": "Ungültige Theme-Konfiguration für Vorschau", + "duplicateThemeColor": "Ein Theme mit dieser Primärfarbe existiert bereits. Bitte wählen Sie eine andere Farbe.", + "themeDes": "Vollständige verfügbare Konfigurationen können unter <0>Standard-Theme-Viewer - Material-UI eingesehen werden.", + "defaultTheme": "Standard", + "auditLog": "Ereignisse", + "auditLogDes": "Konfigurieren Sie, welche Ereignisse aufgezeichnet werden sollen. Einige Ereignisse könnten vom System verwendet werden, um zusätzliche Funktionen bereitzustellen, z.B. Dateiaktivität und Anmeldeaktivität.", + "systemEvents": "Systemereignisse", + "systemEventsDes": "Ereignisse im Zusammenhang mit Systemoperationen und -status.", + "userEvents": "Benutzerereignisse", + "userEventsDes": "Ereignisse im Zusammenhang mit Benutzerkonten, Authentifizierung und Profiländerungen.", + "fileEvents": "Dateiereignisse", + "fileEventsDes": "Ereignisse im Zusammenhang mit Dateioperationen wie Upload, Download und Änderung.", + "shareEvents": "Freigabeereignisse", + "shareEventsDes": "Ereignisse im Zusammenhang mit Dateifreigabe und Link-Zugriff.", + "versionEvents": "Versionsereignisse", + "versionEventsDes": "Ereignisse im Zusammenhang mit Dateiversionsverwaltung.", + "mediaEvents": "Medienereignisse", + "mediaEventsDes": "Ereignisse im Zusammenhang mit Medienverarbeitung wie Miniaturansichten-Generierung.", + "filesystemEvents": "Dateisystemereignisse", + "filesystemEventsDes": "Ereignisse im Zusammenhang mit Dateisystemoperationen wie Mounting und Archivverarbeitung.", + "webdavEvents": "WebDAV-Ereignisse", + "webdavEventsDes": "Ereignisse im Zusammenhang mit WebDAV-Kontoverwaltung und -zugriff.", + "paymentEvents": "Zahlungsereignisse", + "paymentEventsDes": "Ereignisse im Zusammenhang mit Zahlungen, Punkten und Mitgliedschaftsverwaltung.", + "emailEvents": "E-Mail-Ereignisse", + "emailEventsDes": "Ereignisse im Zusammenhang mit E-Mail-Versand und Benachrichtigungen.", + "toggleAll": "Alle umschalten", + "toggleAllDes": "Alle Ereignisse in dieser Kategorie aktivieren oder deaktivieren.", + "event": { + "file_imported": "Externe Datei importiert", + "server_start": "Server-Start", + "user_signup": "Benutzer-Anmeldung", + "email_sent": "E-Mail gesendet", + "user_activated": "Benutzer aktiviert", + "user_login_failed": "Anmeldung fehlgeschlagen", + "user_login": "Benutzer-Anmeldung", + "user_token_refresh": "Token-Aktualisierung", + "file_create": "Datei erstellt", + "file_rename": "Datei umbenannt", + "set_file_permission": "Berechtigung geändert", + "entity_uploaded": "Datei hochgeladen oder aktualisiert", + "entity_downloaded": "Datei heruntergeladen", + "copy_from": "Kopieren von", + "copy_to": "Kopieren nach", + "move_to": "Verschieben nach", + "delete_file": "Datei gelöscht", + "move_to_trash": "In Papierkorb verschieben", + "share": "Freigabe erstellt", + "share_link_viewed": "Freigabe-Link angesehen", + "set_current_version": "Aktuelle Version festlegen", + "delete_version": "Version löschen", + "thumb_generated": "Miniaturansicht generiert", + "live_photo_uploaded": "Live-Foto hochgeladen", + "update_metadata": "Metadaten aktualisiert", + "edit_share": "Freigabe bearbeitet", + "delete_share": "Freigabe gelöscht", + "mount": "Einbinden", + "relocate": "Verschieben", + "create_archive": "Archiv erstellen", + "extract_archive": "Archiv extrahieren", + "webdav_login_failed": "WebDAV-Anmeldung fehlgeschlagen", + "webdav_account_create": "WebDAV-Konto erstellt", + "webdav_account_update": "WebDAV-Konto aktualisiert", + "webdav_account_delete": "WebDAV-Konto gelöscht", + "payment_created": "Zahlung erstellt", + "points_change": "Punkte geändert", + "payment_paid": "Zahlung bezahlt", + "payment_fulfilled": "Bestellung erfüllt", + "payment_fulfill_failed": "Bestellung erfüllen fehlgeschlagen", + "storage_added": "Speicher hinzugefügt", + "group_changed": "Gruppe geändert", + "user_exceed_quota_notified": "Kontingent überschritten Benachrichtigung", + "user_changed": "Benutzerstatus geändert", + "get_direct_link": "Direktlink erhalten", + "link_account": "Externes Konto verknüpfen", + "unlink_account": "Externes Konto trennen", + "change_nick": "Spitzname ändern", + "change_avatar": "Avatar ändern", + "membership_unsubscribe": "Mitgliedschaft kündigen", + "change_password": "Passwort ändern", + "enable_2fa": "2FA aktivieren", + "disable_2fa": "2FA deaktivieren", + "add_passkey": "Passkey hinzufügen", + "remove_passkey": "Passkey entfernen", + "redeem_gift_code": "Geschenkcode einlösen", + "update_view": "Ansichtseinstellung geändert", + "delete_direct_link": "Direktlink löschen", + "report_abuse": "Missbrauch melden" + }, + "server": "Server", + "tempPath": "Temporärer Pfad", + "tempPathDes": "Das Verzeichnis zum Speichern temporärer Dateien, relativ zum Cloudreve-Datenverzeichnis. Bitte stellen Sie sicher, dass keine Warteschlangen-Aufgaben laufen, bevor Sie es ändern.", + "siteID": "Site-ID", + "siteIDDes": "Eine eindeutige ID zur Identifizierung der Site, normalerweise nicht zu ändern.", + "siteSecretKey": "Master-Schlüssel", + "siteSecretKeyDes": "Der Master-Schlüssel, der zur Verschlüsselung von Benutzer-Tokens und Signaturen verwendet wird. Nach der Rotation werden alle Benutzer-Tokens und Signaturen ungültig. Es wird nach dem Neustart von Cloudreve wirksam.", + "rotateSecretKey": "Master-Schlüssel rotieren", + "hashidSalt": "HashID-Salt", + "hashidSaltDes": "Der Salt-Wert, der zur Generierung von HashID verwendet wird. Bitte seien Sie vorsichtig beim Ändern, da es bestehende Direktlinks und Freigabe-Links ungültig macht.", + "accessTokenTTL": "Zugriffs-Token-TTL", + "accessTokenTTLDes": "Die TTL von Zugriffs-Tokens, in Sekunden.", + "refreshTokenTTL": "Aktualisierungs-Token-TTL", + "refreshTokenTTLDes": "Die TTL von Aktualisierungs-Tokens, in Sekunden. Es beeinflusst die Dauer des Benutzer-Anmeldestatus.", + "cronGarbageCollect": "Garbage-Collection-Scan-Intervall", + "cronGarbageCollectDes": "Legt fest, wie oft abgelaufene Daten in temporären Dateien und KV-Speicher gescannt und recycelt werden.", + "startWithProtocol": "Muss mit http:// oder https:// beginnen", + "tlsWarning": "Die aktuelle Site verwendet https, das Eingeben einer http-URL hier kann Ausnahmen verursachen.", + "blobUrlCache": "Blob-URL-Cache", + "clearBlobUrlCache": "Blob-URL-Cache löschen", + "clearBlobUrlCacheDes": "Um die Cache-Trefferrate zu erhöhen, speichert und wiederverwendet Cloudreve Blob-URLs zwischen. Wenn sich die CDN-Adresse oder andere Einstellungen ändern, löschen Sie bitte den Cache.", + "cacheCleared": "Cache gelöscht." + }, + "giftCodes": { + "giftCodesSettings": "Geschenkcodes", + "generateGiftCodes": "Geschenkcodes generieren", + "giftCodeQuantity": "Anzahl", + "giftCodeQuantityHelp": "Anzahl der zu generierenden Geschenkcodes", + "giftCodeProductType": "Produkttyp", + "giftCodeTypePoints": "Punkte", + "giftCodeTypeStorage": "Speicher", + "giftCodeTypeGroup": "Gruppe", + "giftCodePointsAmount": "Punkteanzahl", + "giftCodePointsAmountHelp": "Anzahl der Punkte, die beim Einlösen des Codes gutgeschrieben werden", + "giftCodeProduct": "Produkt", + "selectStorageProduct": "Speicherprodukt auswählen", + "selectGroupProduct": "Gruppenprodukt auswählen", + "giftCodeType": "Typ", + "giftCodeAmount": "Menge", + "giftCode": "Geschenkcode", + "giftCodeStatus": "Status", + "giftCodeUsedBy": "Verwendet von", + "giftCodeUsed": "Verwendet", + "giftCodeUnused": "Verfügbar", + "giftCodeDeleted": "Geschenkcode erfolgreich gelöscht", + "giftCodesGenerated": "Geschenkcodes erfolgreich generiert", + "noGiftCodes": "Keine Geschenkcodes verfügbar", + "generatedCodesTitle": "Generierte Geschenkcodes", + "generatedCodesDescription": "Kopieren Sie diese Geschenkcodes, um sie mit Benutzern zu teilen. Jeder Code kann einmal verwendet werden.", + "copyAndClose": "Kopieren und schließen", + "duratonTimes": "Anzahl", + "duratonTimesDes": "Wie viele Mengen des Produkts sind in jedem Geschenkcode enthalten.", + "unknownProduct": "Unbekanntes Produkt" + }, + "policy": { + "acceleratedDomainUpload": "Übertragungsbeschleunigungsdomäne für Upload verwenden", + "acceleratedDomainUploadDes": "Wenn aktiviert, wird die <0>Übertragungsbeschleunigungsdomäne von Qiniu beim Hochladen von Dateien verwendet.", + "compare": "Vergleichen", + "deletePolicyConfirmation": "Sind Sie sicher, dass Sie die Speicherrichtlinie {{name}} löschen möchten?", + "streamSaver": "Download über Browser", + "streamSaverDes": "Wenn aktiviert, werden die Download-Anfragen der Benutzer vom Browser verarbeitet. Aufgrund der Beschränkung der OneDrive-Speicherrichtlinie kann der Dateiname der vom Benutzer direkt heruntergeladenen Datei nicht mit dem Dateinamen in Cloudreve übereinstimmen. Die Verwendung des Browsers für die Verarbeitung von Downloads kann dieses Problem lösen.", + "oauthCallbackFailed": "Autorisierung fehlgeschlagen", + "httpsRequired": "Entra ID-Anwendung erfordert HTTPS-Weiterleitungs-URL, aber die aktuelle Site verwendet HTTP, was nach der Anmeldung zu einem Weiterleitungsfehler führen kann. Bitte ersetzen Sie das HTTPS in der Browser-Adressleiste manuell durch HTTP.", + "authorizeMicrosoft": "Mit Microsoft anmelden", + "redirectUrl": "Weiterleitungs-URL", + "redirectUrlDes": "Die aktuelle Anzeige ist die neueste Weiterleitungs-URL, die den Anforderungen entspricht. Bitte bestätigen Sie, ob die Weiterleitungs-URL in den Anwendungseinstellungen mit der aktuellen übereinstimmt.", + "authorizeOneDrive": "Entra ID-Anwendungseinstellungen bestätigen", + "authorizeOneDriveDes": "Bitte bestätigen Sie, ob die folgenden Entra ID-Anwendungsinformationen noch gültig sind. Falls erforderlich, nehmen Sie bitte Änderungen vor.", + "authorizeNow": "Autorisieren", + "authorizeAgain": "Erneut autorisieren", + "notGranted": "Kein autorisiertes Konto, Speicherrichtlinie kann nicht verwendet werden.", + "granted": "Konto autorisiert, Anmeldedaten aktualisiert am <0>{{time}}.", + "grantedNotRefresh": "Konto autorisiert, Anmeldedaten seit dem letzten Start nicht aktualisiert.", + "batchDeleteSize": "Maximale Batch-Löschgröße", + "batchDeleteSizeDes": "Begrenzt die maximale Anzahl von Dateien, die in einer einzigen API-Anfrage gelöscht werden können. Diese Einstellung wirkt sich nicht auf die Batch-Dateilöschung durch Benutzer aus. Wenn nicht ausgefüllt, wird der Standardwert <0>1000 verwendet. Dies ist der maximal zulässige Wert für die offizielle S3-API.", + "bucketPolicy": "Bucket-Richtlinie", + "cdnOrCustomDomain": "CDN oder benutzerdefinierte CNAME", + "bucketDomain": "Bucket-Domäne", + "bucketDomainDes": "Geben Sie die CDN-beschleunigte Domäne oder benutzerdefinierte CNAME-Domäne ein, die Sie für den Speicher-Bucket gebunden haben.", + "storageNodeInternal": "Speicherknoten (Intranet-Endpunkt)", + "chunkSizeDesOssObs": "Erlaubter Bereich: 100 KB ~ 5 GB.", + "chunkSizeDesQiniuCos": "Erlaubter Bereich: 1 MB ~ 1 GB.", + "chunkSizeDesS3": "Erlaubter Bereich: 5 MB ~ 5 GB.", + "thisIsACustomDomain": "Dies ist eine benutzerdefinierte Domäne", + "thisIsACustomDomainDes": "Wenn Sie eine benutzerdefinierte Domäne an den Speicher-Bucket gebunden haben und den Bucket über die benutzerdefinierte Domäne verwalten müssen, aktivieren Sie bitte diese Option. Nach der Aktivierung wird Cloudreve nicht versuchen, den Bucket-Namen in der Anfrage-Domäne anzuhängen.", + "addedManually": "Ich habe es manuell eingestellt", + "origin": "Quelle", + "allowMethods": "Erlaubte Methoden", + "exposeHeaders": "Offenlegte Header", + "allowHeaders": "Erlaubte Header", + "maxAge": "Maximale Alterung", + "accessCredential": "Zugangsberechtigungsnachweis", + "downloadTrafficDiagram": "Download-Verkehrspfad-Demonstration", + "downloadRelay": "Download-Weiterleitung", + "downloadRelayDes": "Wenn aktiviert, werden die Download-Anfragen der Benutzer über Cloudreve weitergeleitet.", + "download": "Download", + "downloadCdn": "Download-CDN", + "useDownloadCdn": "CDN für Download-Verkehr verwenden", + "skipSign": "URL-Signatur für CDN überspringen", + "skipSignDes": "Wenn Sie \"Quellenauthentifizierung verwenden\" für diese Domäne in den Bucket-Einstellungen aktiviert haben, aktivieren Sie bitte diese Option.", + "cdnHost": "CDN-Host", + "downloadCdnDes": "Der Host, das Protokoll und der Port der URL, die Benutzer für den Zugriff auf Dateien verwenden, werden durch den von Ihnen angegebenen CDN-Host ersetzt.", + "mediaExtractorProxy": "Proxy-Medienextraktion", + "mediaExtractorProxyDes": "Aktivieren Sie diese Funktion, um Medienmetadaten aus Dateien zu extrahieren, die von den nativen Extraktoren des Speicheranbieters nicht unterstützt werden. Bitte konfigurieren Sie den Medienextraktor in <0>Medienverarbeitung.", + "mediaExtractorNative": "native Extraktoren", + "mediaExtractorOss": "Intelligent Media Management (IMM)", + "mediaExtractorQiniu": "Qiniu DORA", + "mediaExtractorCos": "Tencent Cloud Datenverarbeitung", + "mediaExtractorObs": "Bildverarbeitungsservice", + "nativeMediaMetaExts": "Aktivierte Dateierweiterungen für <0>{{name}}", + "nativeMediaMetaExtsGeneralDes": "Durch Kommas getrennt, leerer Wert bedeutet <0>{{name}} deaktivieren.", + "nativeMediaMetaExtsRemote": "Für Slave-Speicher wird standardmäßig EXIF und Musik-Metadaten unterstützt. Sie können dies überschreiben, indem Sie den Slave-Knoten mit weiteren Extraktoren konfigurieren.", + "nativeMediaMetaExtOss": "Der Intelligent Media Management (IMM) Service unterstützt die Verarbeitung von Audio, Video und Bildern. Die Bildverarbeitung erfordert keine manuelle Konfiguration, aber wenn Sie Audio oder Video verarbeiten müssen, müssen Sie IMM manuell aktivieren und an den Bucket binden. Bitte beziehen Sie sich auf die <0>Dokumentation für die Bindung. Nach der Bindung fügen Sie bitte die Erweiterungen hinzu, die Sie verarbeiten möchten, zum obigen Feld.", + "nativeMediaMetaExtQiniu": "Der Qiniu DORA Service unterstützt die Verarbeitung gängiger Audio-, Video- und Bildformate. Es ist keine zusätzliche Konfiguration erforderlich. Bitte geben Sie die Erweiterungen ein, die Sie oben verarbeiten möchten.", + "nativeMediaMetaExtCos": "Der Tencent Cloud Datenverarbeitungsservice unterstützt die Verarbeitung von Audio, Video und Bildern. Die Bildverarbeitung erfordert keine manuelle Konfiguration, aber wenn Sie Audio oder Video verarbeiten müssen, gehen Sie bitte zuerst zu <0>Tencent Cloud Datenverarbeitung, um den Speicher-Bucket zu aktivieren und zu binden, dann gehen Sie zu Bucket-Einstellungen - Medienverarbeitung, um den Bildverarbeitungsservice zu aktivieren. Nach der Bindung fügen Sie bitte die Erweiterungen hinzu, die Sie verarbeiten möchten, zum obigen Feld.", + "nativeMediaMetaExtObs": "Der Bildverarbeitungsservice unterstützt <0>die Extraktion von Bild-EXIF. Es ist keine manuelle Konfiguration erforderlich, fügen Sie einfach die Erweiterungen hinzu, die Sie oben verarbeiten möchten.", + "thumbProxy": "Proxy-Miniaturbildgenerierung", + "thumbProxyDes": "Aktivieren Sie diese Funktion, um Miniaturbilder für Dateien zu generieren, die nicht den nativen Miniaturbild-Bedingungen entsprechen. Cloudreve wird versuchen, Miniaturbilder zu generieren und sie auf die Speicherseite hochzuladen. Bitte konfigurieren Sie den Miniaturbild-Generator in <0>Medienverarbeitung.", + "nativeThumbnailMaxSize": "Maximale Größe nativer Miniaturbilder", + "nativeThumbnailMaxSizeDes": "Geben Sie 0 ein, um das Größenlimit zu deaktivieren. Dateien, die größer als diese Größe sind, verwenden keine nativen Miniaturbilder.", + "nativeThumbNailsSupportAllExts": "Für alle Dateierweiterungen aktivieren", + "nativeThumbNails": "Dateierweiterungen für native Miniaturbilder", + "nativeThumbNailsGeneralDes": "Durch Kommas getrennt, leerer Wert bedeutet native Miniaturbilder deaktivieren. Für die oben aufgelisteten Dateierweiterungen wird Cloudreve die native Miniaturbild-Funktion des Speicheranbieters verwenden, um Miniaturbilder zu generieren.", + "nativeThumbNailsGeneralRemote": "Für Slave-Speicher ist die eingebaute Unterstützung einfache Bilder und Musik-Cover-Miniaturbilder. Sie können dies überschreiben, indem Sie den Slave-Knoten mit mehr Generatoren konfigurieren.", + "nativeThumbNailsGeneralOss": "Für Alibaba Cloud OSS-Speicher wird der <0>Bildverarbeitungsservice verwendet, um Miniaturbilder zu generieren.", + "nativeThumbNailsGeneralQiniu": "Für Qiniu Cloud-Speicher wird der <0>Bildgrundverarbeitung (imageView2)-Service verwendet, um Miniaturbilder zu generieren.", + "nativeThumbNailsGeneralCos": "Für Tencent Cloud COS-Speicher wird der <0>Tencent Cloud Datenverarbeitungsservice verwendet, um Miniaturbilder zu generieren.", + "nativeThumbNailsGeneralObs": "Für Huawei Cloud OBS-Speicher wird der <0>Bildverarbeitungsservice verwendet, um Miniaturbilder zu generieren.", + "nativeThumbNailsGeneralUpyun": "Für Upyun-Speicher wird der <0>Bildverarbeitungsservice verwendet, um Miniaturbilder zu generieren.", + "preallocate": "Festplattenspeicher vorab zuweisen", + "preallocateDes": "Wenn aktiviert, wird der Upload-Anfrage des Benutzers Festplattenspeicher auf dem Speicherknoten vorab zugewiesen, und unterstützt auch parallele Chunk-Uploads. Nur wirksam unter Linux oder Darwin.", + "chunkConcurrency": "Parallele Chunk-Uploads", + "chunkConcurrencyDes": "Legt die Anzahl der gleichzeitigen Chunk-Uploads beim direkten Web-Upload fest.", + "sourceWebEdit": "Web-Online-Bearbeitung", + "uploadRelay": "Upload-Relay", + "uploadRelayDes": "Wenn aktiviert, werden die Upload-Anfragen der Benutzer über Cloudreve an den Speicherknoten weitergeleitet. Da keine Chunk-Uploads durchgeführt werden können, passen Sie bitte das maximale Upload-Größenlimit des Webservers entsprechend an.", + "customProxy": "Benutzerdefinierter Proxy", + "storageNode": "Speicheranbieter", + "sourceWeb": "Web / Offizielle App", + "sourceDav": "WebDAV", + "uploadTrafficDiagram": "Upload-Traffic-Pfad-Demonstration", + "node": "Speicherknoten", + "nodeDes": "Bitte wählen Sie einen Slave-Knoten für die Dateispeicherung. Sie können Slave-Speicherknoten in <0>Knotenliste erstellen oder verwalten.", + "noBindedGroupWarning": "Die aktuelle Speicherrichtlinie ist an keine Benutzergruppe gebunden. Bitte gehen Sie zu <0>Gruppenliste, um die aktuelle Speicherrichtlinie an eine Benutzergruppe zu binden.", + "nameRuleImmutable": "Das Ändern der Einstellungen wirkt sich nicht auf vorhandene Dateien in der Speicherrichtlinie aus. Der Blob-Pfad ist nach der Erstellung fest, auch wenn sich die magischen Variablen darin ändern, wird der Pfad nicht aktualisiert.", + "uniqueVarRequired": "Bitte fügen Sie mindestens eine eindeutige Variable entweder im Verzeichnispfad oder Blob-Namen hinzu: {uuid}, {randomkey8}, {randomkey16}.", + "storageAndUpload": "Speicher und Upload", + "blobFolderNaming": "Blob-Speicherverzeichnis", + "blobFolderNamingDes": "Das Verzeichnis, in dem Datei-Blobs gespeichert werden. Sie können <0>magische Variablen verwenden.", + "blobNameDes": "Der Name des Datei-Blobs. Sie können <0>magische Variablen verwenden. Stellen Sie sicher, dass er absolut eindeutig ist, auch bei mehreren Uploads derselben Datei im selben Pfad in kurzer Zeit.", + "blobName": "Blob-Name", + "basicInfo": "Grundinformationen", + "editX": "{{name}} bearbeiten", + "noGroupBinded": "Keine Gruppe gebunden", + "create": "Erstellen", + "addXStoragePolicy": "{{type}} Speicherrichtlinie hinzufügen", + "loadSummary": "Zusammenfassung laden", + "policySummary": "{{count}} Datei-Blobs ({{size}})", + "sharp": "#", + "name": "Name", + "type": "Typ", + "childFiles": "Untergeordnete Dateien", + "totalSize": "Gesamtgröße", + "actions": "Aktionen", + "authSuccess": "Autorisierung erteilt.", + "policyDeleted": "Richtlinie gelöscht.", + "newStoragePolicy": "Neue Speicherrichtlinie", + "all": "Alle", + "local": "Lokal", + "remote": "Remote-Knoten", + "qiniu": "Qiniu", + "upyun": "Upyun", + "oss": "Alibaba Cloud OSS", + "cos": "Tencent Cloud COS", + "onedrive": "OneDrive", + "s3": "S3-kompatibel", + "ks3": "Kingsoft Cloud S3", + "obs": "Huawei Cloud OBS", + "load_balance": "Lastausgleich", + "childPolicy": "Untergeordnete Speicherrichtlinie", + "childPolicyDes": "Wählen Sie die untergeordneten Speicherrichtlinien aus, die zum Lastausgleich-Pool hinzugefügt werden sollen.", + "weight": "Gewicht", + "addTargetPolicy": "Untergeordnete Richtlinie hinzufügen", + "selectPolicies": "Richtlinien auswählen", + "selectPoliciesDes": "Wählen Sie Speicherrichtlinien aus, die zum Lastausgleich-Pool hinzugefügt werden sollen.", + "loadBalanceDes": "Bei Verwendung der lastausgeglichenen Speicherrichtlinie werden neue Uploads basierend auf dem Gewicht zufällig auf verschiedene untergeordnete Speicherrichtlinien verteilt.", + "xChildPolicies": "{{count}} untergeordnete Speicherrichtlinien", + "refresh": "Aktualisieren", + "delete": "Löschen", + "edit": "Bearbeiten", + "selectAStorageProvider": "Speicheranbieter auswählen", + "maxSizeOfSingleFile": "Maximale Einzeldateigröße", + "maxSizeOfSingleFileDes": "Geben Sie 0 ein, um das Limit zu deaktivieren.", + "enterFileExt": "Durch Semikolon-Kommas getrennt, leer lassen, um alle Dateierweiterungen zuzulassen.", + "extList": "Dateierweiterungs-Einschränkungen", + "noLimit": "Keine Begrenzung", + "whitelist": "Erlauben", + "blacklist": "Verweigern", + "fileNameRegex": "Dateiname-Regex-Regeln", + "fileNameRegexDes": "Regulärer Ausdruck zum Abgleichen von Dateinamen, leer lassen für keine Einschränkung.", + "chunkSizeDes": "Geben Sie die Chunk-Größe für Chunk-Uploads an. Ein Wert von 0 bedeutet, dass keine Chunk-Uploads verwendet werden, aber die maximale Upload-Größe kann durch den Webserver begrenzt sein.", + "chunkSizeDesSuffix": "{{prefix}} Mit Chunk-Upload werden die von Benutzern hochgeladenen Dateien in Chunks aufgeteilt und einzeln auf die Speicherseite hochgeladen. Nach einer Upload-Unterbrechung können Benutzer wählen, vom letzten hochgeladenen Chunk aus weiterzumachen.", + "chunkSize": "Chunk-Größe", + "policyName": "Der Anzeigename der Speicherrichtlinie, wird auch für die Präsentation für Benutzer verwendet.", + "magicVar": { + "fileNameMagicVar": "Dateiname-Magie-Variablen", + "pathMagicVar": "Pfad-Magie-Variablen", + "variable": "Variable", + "description": "Beschreibung", + "example": "Beispiel", + "16digitsRandomString": "16-stellige Zufallszeichenfolge", + "8digitsRandomString": "8-stellige Zufallszeichenfolge", + "secondTimestamp": "Zeitstempel", + "nanoTimestamp": "Nano-Zeitstempel", + "uid": "Benutzer-ID", + "originalFileName": "Ursprünglicher Dateiname", + "originFileNameNoext": "Ursprünglicher Dateiname ohne Erweiterung", + "extension": "Dateierweiterungsname", + "uuidV4": "UUID V4", + "date": "Datum", + "dateAndTime": "Datum und Uhrzeit", + "randomNumber": "Zufallszahl im Bereich", + "year": "Jahr", + "month": "Monat", + "day": "Tag", + "hour": "Stunde", + "minute": "Minute", + "second": "Sekunde", + "path": "Der ursprüngliche Pfad beim Hochladen der Datei durch den Benutzer" + }, + "storageBucket": "Speicher-Bucket", + "wanSiteURLDes": "Bevor Sie diese Richtlinie verwenden, stellen Sie bitte sicher, dass die Adresse, die Sie in Grundeinstellungen - Site-Informationen - Site-URL eingegeben haben, mit der tatsächlichen Adresse übereinstimmt und <0>ordnungsgemäß über WAN zugänglich ist.", + "enterQiniuBucket": "Gehen Sie zum <0>Qiniu-Dashboard, um einen Speicher-Bucket zu erstellen. Geben Sie den \"Bucket-Namen\" ein, den Sie gerade erstellt haben.", + "aclType": "Zugriffskontrolltyp", + "accessTypePulic": "Öffentlich lesen, privat schreiben", + "accessTypePrivate": "Privat lesen/schreiben", + "accessType": "Zugriffstyp", + "qiniuBucketName": "Bucket-Name", + "cosObsBucketName": "Bucket-Name", + "bucketType": "Bucket-ACL", + "bucketTypeDes": "Wählen Sie den ACL-Typ für den gerade erstellten Bucket aus.", + "privateBucket": "Privat", + "privateDes": "Cloudreve wird die Datei-URL signieren.", + "publicBucket": "Öffentlich lesen", + "publicStorage": "Öffentlich", + "publicDes": "Nicht empfohlen, Cloudreve wird direkt den direkten Link der Datei zurückgeben, was den Zugriff auf Dateien nicht effektiv kontrollieren kann.", + "bucketCDNDes": "Geben Sie den CDN-beschleunigten Domainnamen ein, den Sie für den Speicher-Bucket gebunden haben.", + "bucketCDNDomain": "CDN-Domain", + "qiniuCredentialDes": "Gehen Sie zu Persönliches Zentrum - Credential-Management im Qiniu-Dashboard und geben Sie die erhaltenen AK, SK ein.", + "ak": "AK", + "sk": "SK", + "cannotEnableForPrivateBucket": "Wenn diese Funktion für private Buckets aktiviert ist, müssen Sie \"Umgeleiteten Quell-Link verwenden\" für Benutzergruppen aktivieren.", + "chunkSizeLabelQiniu": "Geben Sie die Chunk-Größe für wiederaufnehmbare Uploads an. Erlaubter Bereich ist 1 MB - 1 GB.", + "corsSettingStep": "CORS-Richtlinie", + "corsPolicyAdded": "CORS-Richtlinie wurde hinzugefügt.", + "createOSSBucketDes": "Gehen Sie zum <0>OSS-Dashboard, um einen Bucket zu erstellen. Nur <1>Standard und <2>IA Speicherklassen werden unterstützt.", + "bucketName": "Bucket-Name", + "publicReadBucket": "Öffentlich lesen", + "ossEndpointDes": "Gehen Sie zur Bucket-Übersichtsseite, geben Sie den <2>Port unter dem Abschnitt <1>Zugriff über Internet auf der <0>Endpoint-Seite ein.", + "ossEndpointDesInternalHint": "Wenn Sie einen Intranet- oder benutzerdefinierten Domain-Endpoint konfigurieren müssen, können Sie dies nach dem Erstellen der Speicherrichtlinie einstellen.", + "obsEndpointCnameHint": "Wenn Sie einen benutzerdefinierten Domain-Endpoint konfigurieren müssen, können Sie dies nach dem Erstellen der Speicherrichtlinie einstellen.", + "endpoint": "EndPoint", + "ossLANEndpointDes": "Leer lassen bedeutet nicht verwenden. Wenn Ihr Cloudreve in Alibaba Cloud-Rechendiensten bereitgestellt ist, die sich in derselben Verfügbarkeitszone wie der OSS-Bucket befinden, können Sie zusätzlich einen Intranet-Endpoint angeben. Cloudreve wird versuchen, diesen Endpoint auf der Serverseite zu verwenden, um Verkehrskosten zu reduzieren.", + "intranetEndPoint": "Intranet-Endpoint", + "ossCDNDes": "Möchten Sie Alibaba Cloud CDN verwenden, um den Dateizugriff zu beschleunigen?", + "createOSSCDNDes": "Gehen Sie zum <0>Alibaba Cloud CDN-Dashboard, um eine CDN-Domain zu erstellen. Die Quelle des CDN sollte Ihr OSS-Bucket sein. Geben Sie die CDN-Domain ein und wählen Sie, ob Sie HTTPS verwenden möchten:", + "ossAKDes": "Erhalten Sie Ihren AccessKey auf der Seite <0>Sicherheitsinformations-Management. Sie können auch einen AccessKey mit <1>AliyunOSSFullAccess-Berechtigung in der <2>RAM-Zugriffskontrolle erstellen.", + "shouldNotContainSpace": "Darf keine Leerzeichen enthalten.", + "nameThePolicyFirst": "Benennen Sie die Speicherrichtlinie:", + "chunkSizeLabelOSS": "Geben Sie die Chunk-Größe für wiederaufnehmbare Uploads an. Erlaubter Bereich ist 100 KB - 5 GB.", + "ossCORSDes": "Diese Speicherrichtlinie erfordert eine CORS-Richtlinie, um das Hochladen vom Browser zu ermöglichen. Cloudreve kann sie automatisch für Sie einrichten, oder Sie können sie manuell einrichten, indem Sie den Schritten in der Dokumentation folgen. Wenn Sie bereits die CORS-Richtlinie für diesen Bucket eingerichtet haben, kann dieser Schritt übersprungen werden.", + "letCloudreveHelpMe": "Lassen Sie Cloudreve es für mich einrichten", + "skip": "Überspringen", + "createUpyunBucketDes": "Geben Sie den Namen des Speicherdienstes ein, den Sie im <0>Upyun-Dashboard erstellt haben.", + "storageServiceName": "Service-Name", + "operatorName": "Operator-Name", + "operatorPassword": "Operator-Passwort", + "tokenStatus": "Token-Anti-Hotlinking", + "upyunTokenDes": "Es wird dringend empfohlen, Token-Anti-Hotlinking zu aktivieren. Gehen Sie zum <0>Feature-Konfiguration-Panel des erstellten Speicherdienstes, gehen Sie zum <1>Zugriffskontrolle-Tab, aktivieren Sie Token-Anti-Hotlinking und setzen Sie ein Geheimnis.", + "tokenEnabled": "Token-Anti-Hotlinking aktivieren", + "tokenDisabled": "Token-Anti-Hotlinking nicht verwenden", + "upyunTokenSecretDes": "Geben Sie das Geheimnis des Token-Anti-Hotlinking ein.", + "upyunTokenSecret": "Token-Anti-Hotlinking-Geheimnis", + "createCOSBucketDes": "Gehen Sie zum <0>COS-Dashboard, um einen Speicher-Bucket zu erstellen. Gehen Sie zur Grundkonfigurationsseite des erstellten Buckets und kopieren Sie den <1>Bucket-Namen nach oben.", + "obsBucketDes": "Gehen Sie zum <0>OBS-Dashboard, um einen Speicher-Bucket zu erstellen. Geben Sie den <1>Bucket-Namen ein, den Sie gerade erstellt haben. Die Speicherklasse unterstützt nur <2>Standard oder <3>Seltener Zugriff.", + "cosPrivateRW": "Privat Lesen/Schreiben", + "cosPublicRW": "Öffentlich Lesen und Privat Schreiben", + "cosAccessDomainDes": "Auf der Übersichtsseite des erstellten Buckets füllen Sie die <1>Zugangs-Domain aus, die unter dem Abschnitt <0>Domain-Informationen angegeben ist. Sie können auch Ihre CNAME-Domain oder CDN-Beschleunigungs-Domain verwenden.", + "obsEndpointDes": "Auf der Übersichtsseite des erstellten Buckets füllen Sie den <1>Endpoint aus, der unter dem Abschnitt <0>Domain-Informationen angegeben ist.", + "accessDomain": "Zugangs-Domain", + "cosCDNDomainDes": "Gehen Sie zur <0>Tencent Cloud CDN-Verwaltungskonsole, um eine CDN-Beschleunigungs-Domain zu erstellen und die Quellseite auf den gerade erstellten COS-Bucket zu setzen. Geben Sie den CDN-Domain-Namen unten ein und wählen Sie, ob Sie HTTPS verwenden möchten.", + "cosCredentialDes": "Geben Sie die Zugriffsschlüssel ein, die Sie von der <0>Zugriffsschlüssel-Seite von Tencent Cloud erhalten haben. Bitte stellen Sie sicher, dass das Schlüsselpaar Zugriffsberechtigung auf COS-Dienste hat. Sie können auch einen <2>Unterbenutzer mit <1>Programmatischer Zugriff-Berechtigung erstellen und ihm Zugriff auf den COS-Dienst gewähren.", + "obsCredentialDes": "Geben Sie die Zugriffsschlüssel ein, die Sie von der <0>Zugriffsschlüssel-Seite von Huawei Cloud erhalten haben. Sie können auch einen <2>IAM-Benutzer mit <1>Programmatischer Zugriff-Berechtigung erstellen und ihm <3>OBS OperateAccess-Berechtigung gewähren.", + "grantAccess": "Zugriff gewähren", + "grantAccessLater": "Nach dem Erstellen der Speicherrichtlinie müssen Sie sich anmelden und Zugriff auf der Speicherrichtlinien-Einstellungsseite gewähren.", + "odHttpsWarning": "Sie müssen HTTPS aktivieren, um OneDrive/SharePoint-Speicherrichtlinien zu verwenden. Nach der Aktivierung stellen Sie sicher, dass Sie Einstellungen - Grundlagen - Site-Informationen - Site-URL ändern.", + "creatAadAppDes": "Gehen Sie zum <0>Microsoft Entra ID-Dashboard, nach dem Anmelden gehen Sie zum <1>Microsoft Entra ID-Admin-Panel. Sie können optional ein anderes Konto als das zum Speichern von Dateien verwendete verwenden, um sich anzumelden.", + "createAadAppDes2": "Gehen Sie zum <0>App-Registrierungen-Menü auf der linken Seite und klicken Sie auf die <1>Neue Registrierung-Schaltfläche. Füllen Sie das Anwendungsregistrierungsformular aus. Stellen Sie sicher, dass <2>Unterstützte Kontotypen als <3>Konten in jedem Organisationsverzeichnis (Jedes Azure AD-Verzeichnis - Mehrere Mandanten) und persönliche Microsoft-Konten (z.B. Skype, Xbox) ausgewählt ist; <4>Umleitungs-URI (optional) ist als <5>Web ausgewählt und füllen Sie <6>{{url}} aus; Für andere Felder lassen Sie es einfach als Standard.", + "entraIdApp": "Entra ID-App-Informationen", + "aadAppIDDes": "Gehen Sie zur <0>Übersicht-Seite in der Anwendungsverwaltung, der Wert von <1>Anwendung (Client) ID.", + "aadAppID": "Anwendung (Client) ID", + "addAppSecretDes": "Die Art, ein Client-Geheimnis zu erstellen: Gehen Sie zum <0>Zertifikate & Geheimnisse-Menü auf der linken Seite, klicken Sie auf die <1>Neues Client-Geheimnis-Schaltfläche und wählen Sie die längste Zeit für <2>Läuft ab. Sie müssen nach Ablauf des alten ein neues Client-Geheimnis erstellen und das neue in den Speicherrichtlinien-Einstellungen aktualisieren.", + "aadAppSecret": "Client-Geheimnis", + "aadAccountCloud": "Microsoft Graph-Endpoint", + "aadAccountCloudDes": "Bitte wählen Sie den Endpoint entsprechend dem Microsoft 365-Kontotyp aus, den Sie verwenden.", + "multiTenant": "Weltweite öffentliche Cloud", + "gallatin": "21V chinesische Cloud", + "sharePointDes": "Möchten Sie Dateien in SharePoint speichern?", + "saveToOneDrive": "Dateien im Standard-OneDrive speichern", + "spSiteURL": "SharePoint-Site-URL", + "odReverseProxyURLDes": "Möchten Sie einen benutzerdefinierten Reverse-Proxy-Server für den Datei-Download verwenden?", + "odReverseProxyURL": "URL des Reverse-Proxy-Servers", + "chunkSizeDesOd": "Erlaubter Bereich: 5 MB ~ 5GB, OneDrive erfordert, dass es ein ganzzahliges Vielfaches von 320 KiB (327,680 Bytes) sein muss.", + "limitOdTPSDes": "OneDrive API-Anfrage-Frequenz begrenzen", + "tps": "TPS-Limit", + "tpsDes": "Leer lassen bedeutet kein Limit. Begrenzen Sie diese Speicherrichtlinie auf die maximale Anzahl von API-Anfragen, die pro Sekunde an OneDrive gesendet werden. Anfragen, die diese Frequenz überschreiten, werden ratenbegrenzt. Wenn mehrere Cloudreve-Knoten Dateien übertragen, verwenden sie jeweils ihren eigenen Token-Bucket, also skalieren Sie diese Zahl entsprechend in diesem Zustand herunter.", + "tpsBurst": "TPS-Burst", + "tpsBurstDes": "Wenn die Anfrage inaktiv ist, kann Cloudreve eine bestimmte Anzahl von Slots für zukünftige Verkehrsstöße reservieren.", + "odOauthDes": "Sie müssen jedoch auf die Schaltfläche unten klicken und mit der Microsoft-Konto-Anmeldung autorisieren, um die Initialisierung abzuschließen, bevor Sie sie verwenden können. Sie können später auf der Speicherrichtlinien-Listenseite erneut autorisieren.", + "gotoAuthPage": "Gehen Sie zur Autorisierungsseite", + "s3BucketDes": "Gehen Sie zum AWS S3-Dashboard, um einen Bucket zu erstellen, geben Sie den <0>Bucket-Namen ein, den Sie gerade erstellt haben:", + "s3EndpointDes": "Geben Sie den EndPoint (geografischen Knoten) des Speicher-Buckets im vollständigen URL-Format an, z.B. <0>https://bucket.region.example.com.", + "selectRegionDes": "Geben Sie den Regionscode des Speicher-Buckets ein, z.B. <0>us-east-1. Für Nicht-AWS S3-kompatible Speicheranbieter lesen Sie bitte deren Dokumentation, wie Sie dieses Feld ausfüllen.", + "chunkSizeLabelS3": "Geben Sie die Chunk-Größe für wiederaufnehmbare Uploads an. Erlaubter Bereich ist 5 MB - 5 GB.", + "policyEndpoint": "Endpoint.", + "s3Region": "Region", + "s3EndpointPathStyle": "Wählen Sie das Format der S3-Endpoint-Adresse. Einige Drittanbieter-S3-kompatible Speicherrichtlinien erfordern möglicherweise diese Option, um zu funktionieren. Wenn aktiviert, erzwingen wir die Verwendung von pfadähnlichen Formatierungsadressen, wie <0>http://s3.amazonaws.com/BUCKET/KEY.", + "usePathEndpoint": "Pfad-Stil erzwingen", + "thumbExt": "Erweiterungen, die Miniaturbilder unterstützen", + "thumbExtDes": "Leer lassen bedeutet, dass das von der Speicherrichtlinie vordefinierte Set verwendet wird. Nicht gültig für lokale, S3-Speicherrichtlinien.", + "driverRoot": "Treiber-Root", + "driverRootDes": "Wählen Sie, wo Dateien in Ihrem OneDrive-Konto gespeichert werden sollen. Das Ändern dieser Option macht vorhandene Dateien in der Speicherrichtlinie unzugänglich.", + "saveToDefaultOneDrive": "Dateien im Standard-OneDrive-Treiber speichern", + "saveToSharePoint": "Dateien in SharePoint speichern", + "sharePointUrlDes": "Geben Sie die SharePoint-Site-URL ein. Nach dem Verlust des Fokus konvertiert das System sie automatisch in die korrekte Treiber-Kennung.", + "ks3selectRegionDes": "Geben Sie den Regionscode des Speicher-Buckets ein, z.B. <0>BEIJING.", + "ks3EndpointPathStyle": "Wählen Sie das Format der KS3-Endpoint-Adresse.", + "ossRegionDes": "Geben Sie den Regionscode ein, in dem sich der Bucket befindet, z.B. <0>cn-hangzhou. Sie können die entsprechende Region in der Tabelle <1>OSS-Regionen und Endpunkte finden und die entsprechende <2>Regions-ID ausfüllen." + }, + "node": { + "slave": "Slave", + "master": "Master", + "noCapabilities": "Keine Funktionen aktiviert.", + "active": "Aktiv", + "suspended": "Gesperrt", + "deleteNodeConfirmation": "Sind Sie sicher, dass Sie den Knoten {{name}} löschen möchten?", + "editNode": "Knoten {{node}} bearbeiten", + "thisIsMasterNodes": "Sie bearbeiten einen Master-Knoten, der die aktuelle Site bedient.", + "enableNode": "Knoten aktivieren", + "enableNodeDes": "Nach der Aktivierung akzeptiert und verarbeitet der Knoten die aktivierten Funktionen.", + "name": "Name", + "nameNode": "Knotenname, wird auch zur Anzeige für Benutzer verwendet.", + "type": "Typ", + "server": "Knoten-Endpoint", + "serverDes": "Endpoint für die Knotenkommunikation. Wenn Sie Dateien auf diesem Knoten speichern möchten, wird diese Adresse auch der Benutzerseite für Datei-Uploads zur Verfügung gestellt.", + "loadBalancerRankDes": "Geben Sie ein Lastausgleichsgewicht für diesen Knoten an. Der Wert ist eine ganze Zahl. Je höher der Wert, desto höher die Wahrscheinlichkeit, ausgewählt zu werden.", + "loadBalancerRank": "Lastausgleichsgewicht", + "slaveSecret": "Slave-Geheimnis", + "slaveSecretDes": "Geheimnis für die Kommunikation zwischen Slave-Knoten und Master-Knoten. Muss mit <1>Secret unter <0>Slave in der Slave-Konfigurationsdatei übereinstimmen.", + "testNode": "Knotenkommunikation testen", + "testNodeSuccess": "Knotenkommunikation erfolgreich", + "createArchiveDes": "Akzeptiert Aufgaben-Anfragen zum Erstellen von Archivdateien.", + "extractArchiveDes": "Akzeptiert Aufgaben-Anfragen zum Extrahieren von Dateien.", + "remoteDownloadDes": "Akzeptiert Offline-Download-Aufgaben-Anfragen. Nach der Aktivierung müssen Sie auch die Offline-Download-bezogenen Informationen unten konfigurieren.", + "downloader": "Downloader", + "aria2Des": "Bitte starten Sie Aria2 auf dem Zielknotenserver mit demselben Benutzer/Berechtigung wie Cloudreve läuft und aktivieren Sie den RPC-Service in der Aria2-Konfigurationsdatei. Weitere Informationen und Anleitungen finden Sie im Abschnitt \"Offline-Download\" der Dokumentation.", + "qbittorrentDes": "Bitte starten Sie qBittorrent auf dem Zielknotenserver mit demselben Benutzer/Berechtigung wie Cloudreve läuft und aktivieren Sie den \"Web UI\"-Service in den qBittorrent-Einstellungen. Weitere Informationen und Anleitungen finden Sie im Abschnitt \"Offline-Download\" der Dokumentation.", + "rpcServer": "RPC-Server-Adresse", + "rpcServerHelpDes": "Vollständige RPC-Server-Adresse einschließlich Port, z.B.: <0>http://127.0.0.1:6800/.", + "rpcToken": "RPC-Autorisierungs-Token", + "rpcTokenHelpDes": "Wenn Sie ein Token/Geheimnis in der Aria2-Konfiguration festgelegt haben, geben Sie es hier ein.", + "qbUsername": "Benutzername", + "qbPassword": "Passwort", + "qbUsernameHelpDes": "qBittorrent Web UI Benutzername.", + "qbPasswordHelpDes": "qBittorrent Web UI Passwort.", + "tempDownloadPath": "Temporärer Download-Pfad", + "tempDownloadPathHelpDes": "Temporärer Pfad zum Speichern von Dateien während des Downloads. Stellen Sie sicher, dass dieser Pfad für sowohl Aria2/qBittorrent als auch Cloudreve zugänglich ist.", + "intervalDes": "Intervall für die Überwachung von Download-Aufgaben.", + "interval": "Überwachungsintervall", + "ignoreSSLDes": "SSL-Zertifikatsfehler bei der Kommunikation mit RPC-Server ignorieren.", + "ignoreSSL": "SSL-Zertifikat ignorieren", + "maxDownloads": "Maximale gleichzeitige Downloads", + "maxDownloadsDes": "Maximale Anzahl gleichzeitiger Download-Aufgaben.", + "maxDownloadSpeed": "Maximale Download-Geschwindigkeit", + "maxDownloadSpeedDes": "Maximale Download-Geschwindigkeit pro Aufgabe in Bytes pro Sekunde. 0 bedeutet unbegrenzt.", + "maxUploadSpeed": "Maximale Upload-Geschwindigkeit", + "maxUploadSpeedDes": "Maximale Upload-Geschwindigkeit pro Aufgabe in Bytes pro Sekunde. 0 bedeutet unbegrenzt.", + "capabilities": "Funktionen", + "nodeDeleted": "Knoten gelöscht.", + "nodeCreated": "Knoten erstellt.", + "addNode": "Knoten hinzufügen", + "newNode": "Neuer Knoten", + "nodeUpdated": "Knoten aktualisiert.", + "aria2": "Aria2", + "qbittorrent": "qBittorrent", + "runCrSlave": "Führen Sie Cloudreve auf dem Knoten mit derselben Version wie der Master aus und starten Sie es mit der folgenden Konfigurationsdatei:", + "keepIfUpload": "Wenn Sie diesen Knoten in Zukunft für Speicherrichtlinien verwenden müssen, behalten Sie bitte die folgende CORS-Konfiguration bei.", + "storeFiles": "Dateien speichern", + "storeFilesDes": "Verwenden Sie diesen Knoten zum Speichern von Benutzerdateien.", + "storeFilesHint": "Wenn Sie diesen Knoten für Speicherrichtlinien verwenden möchten, erstellen Sie bitte eine Slave-Speicherrichtlinie und wählen Sie diesen Knoten aus.", + "runCrWithConfig": "Speichern Sie die obige Datei als <0>config.ini-Datei und starten Sie Cloudreve mit dieser Datei: <0>./cloudreve -c config.ini. Eine Slave-Cloudreve-Instanz kann mehrere Cloudreve-Master-Knoten bedienen; fügen Sie einfach diesen Slave-Knoten zu allen Master-Knoten hinzu und halten Sie das Geheimnis gleich.", + "inputServer": "Geben Sie den Knoten-Endpoint ein:", + "testButton": "Sie können auf die Schaltfläche unten klicken, um zu testen, ob die Kommunikation erfolgreich ist.", + "hostHeaderHint": "Wenn ein Signatur-Fehler auftritt, überprüfen Sie bitte, ob der Reverse-Proxy vor dem Knoten den <0>Host-Header weitergibt.", + "features": "Aktivierte Funktionen", + "remoteDownload": "Remote-Download", + "refresh": "Aktualisieren" + }, + "group": { + "countUser": "Anzahl", + "anonymous": "Anonyme Benutzergruppe", + "sysGroup": "System-Benutzergruppe", + "adminGroup": "Admin-Benutzergruppe", + "#": "#", + "name": "Name", + "type": "Speicherrichtlinie", + "count": "Unterbenutzer", + "size": "Speicherquota", + "nameOfGroup": "Name", + "nameOfGroupDes": "Name der Gruppe, wird zur Anzeige für Benutzer verwendet.", + "availablePolicies": "Verfügbare Speicherrichtlinien", + "availablePoliciesDes": "Wählen Sie die Speicherrichtlinien aus, die diese Gruppe verwenden kann. Das Ändern dieser Einstellung wirkt sich nicht auf die von Benutzern hochgeladenen Dateien aus.", + "initialStorageQuota": "Anfängliches Speicherquota", + "initialStorageQuotaDes": "Maximaler Speicher, der von einem einzelnen Benutzer in dieser Gruppe verwendet werden kann.", + "isAdmin": "Admin-Gruppe", + "isAdminDes": "Wenn aktiviert, haben Benutzer in dieser Gruppe Admin-Berechtigungen.", + "share": "Teilen", + "allowCreateShareLink": "Freigabe-Link erstellen", + "allowCreateShareLinkDes": "Wenn deaktiviert, können Benutzer keine Freigabe-Links erstellen.", + "shareFree": "Kostenloser Freigabe-Link", + "shareFreeDes": "Wenn aktiviert, können Benutzer auf alle kostenpflichtigen Freigabe-Links zugreifen, ohne zu kaufen.", + "fileManagement": "Dateiverwaltung", + "allowWabDAV": "WebDAV", + "allowWabDAVDes": "Wenn deaktiviert, können Benutzer nicht über das WebDAV-Protokoll eine Verbindung zum Speicher herstellen", + "allowWabDAVProxy": "WebDAV-Proxy", + "allowWabDAVProxyDes": "Wenn aktiviert, können Benutzer das WebDAV so konfigurieren, dass es beim Herunterladen von Dateien über Cloudreve geleitet wird.", + "compressTask": "Komprimierungs-/Dekomprimierungsaufgaben", + "compressTaskDes": "Wenn aktiviert, können Benutzer Dateien online komprimieren/dekomprimieren.", + "compressSize": "Maximale Dateigröße für Komprimierung", + "compressSizeDes": "Die maximale Gesamtdateigröße von Komprimierungsaufträgen, die der Benutzer erstellen kann. Geben Sie 0 ein, um kein Limit anzugeben. Dieses Limit wird beim Erstellen von Komprimierungsaufgaben nicht überprüft, und wenn die Gesamtgröße der ursprünglichen Dateien dieses Limit bei der Ausführung überschreitet, schlägt die Aufgabe fehl.", + "decompressSize": "Maximale Dateigröße für Dekomprimierung", + "decompressSizeDes": "Die maximale Gesamtdateigröße von Dekomprimierungsaufträgen, die der Benutzer erstellen kann. Geben Sie 0 ein, um kein Limit anzugeben.", + "allowRemoteDownload": "Remote-Download", + "allowRemoteDownloadDes": "Ob Benutzer Remote-Download-Aufgaben erstellen dürfen. Wenn Sie Remote-Download verwenden müssen, benötigen Sie auch Knoten mit aktiviertem Remote-Download in der <0>Knotenliste.", + "aria2Options": "Downloader-Job-Optionen", + "aria2OptionsDes": "Zusätzliche Parameter für Downloader (qBittorrent oder Aria2), geschrieben im JSON-Schlüssel-Wert-Format, siehe die offizielle Downloader-Dokumentation für verfügbare Parameter.", + "aria2BatchSize": "Max. Batch-Größe von Remote-Download-Aufgaben", + "aria2BatchSizeDes": "Maximale Anzahl für das Einreichen von Batch-Remote-Download-Aufgaben. Geben Sie 0 ein, um kein Limit anzugeben.", + "migratePolicy": "Speicherrichtlinie verlagern", + "migratePolicyDes": "Ob der Benutzer eine Speicherrichtlinien-Verlagerungsaufgabe erstellt.", + "advanceDelete": "Erweiterte Datei-Löschoptionen", + "advanceDeleteDes": "Einmal aktiviert, können Benutzer wählen, ob physische Dateien beim Löschen von Dateien beibehalten werden sollen. Bitte aktivieren Sie diese Option nur für vertrauenswürdige Benutzergruppen.", + "allowSelectNode": "Knoten-Auswahl erlauben", + "allowSelectNodeDes": "Wenn aktiviert, kann der Benutzer den bevorzugten Knoten vor dem Erstellen von Aufgaben auswählen. Wenn deaktiviert, wird der Knoten vom System innerhalb der erlaubten Knoten für die Gruppe lastausgeglichen.", + "allowedNodes": "Erlaubte Knoten", + "allowedNodesDes": "Geben Sie die Knoten an, die diese Gruppe zum Erstellen von Aufgaben verwenden kann. Leere Liste bedeutet, dass alle Knoten verfügbar sind. Benutzer können nur Knoten innerhalb dieser Liste auswählen oder vom Lastausgleich zugewiesen bekommen. Derzeit sind die abgedeckten Aufgaben: Remote-Download, Dateikomprimierung/-dekomprimierung. Andere Aufgaben werden dem Master-Knoten zugewiesen.", + "allNodes": "Alle Knoten", + "esclateAnonymity": "Anonymität eskalieren", + "esclateAnonymityDes": "Wenn aktiviert, können Benutzer höhere Berechtigungen für anonyme Benutzer zuweisen (schreiben/löschen/erstellen). Wenn deaktiviert, können Benutzer anonymen Benutzern nur schreibgeschützte Berechtigung zuweisen. Das Ändern dieser Einstellung wirkt sich nicht auf vorhandene Freigabe-Links oder Dateien aus.", + "allowDownloadShare": "Auf geteilte Links zugreifen", + "allowDownloadShareDes": "Wenn deaktiviert, können Benutzer keine geteilten Links anderer anzeigen. Diese Einstellung hat Vorrang vor den Freigabe-Link-Berechtigungseinstellungen.", + "deletedNode": "Gelöschter Knoten #{{id}}", + "maxWalkedFiles": "Max. durchlaufene Dateien", + "maxWalkedFilesDes": "Bei einigen Operationen, die eine tiefe Durchquerung von Dateien erfordern, die maximale Anzahl von Dateien, die durchlaufen werden dürfen.", + "trashBinDuration": "Papierkorb-Dauer (Sekunden)", + "trashBinDurationDes": "Die Aufbewahrungszeit von Dateien im Papierkorb. Dateien werden nach Ablauf der Zeit dauerhaft gelöscht. Das Ändern dieser Einstellung wirkt sich nicht auf Dateien aus, die sich bereits im Papierkorb befinden.", + "serverSideBatchDownload": "Serverseitiger Batch-Download", + "serverSideBatchDownloadDes": "Ob Benutzer mehrere Dateien auswählen dürfen, um den serverseitigen Relay-Batch-Download zu verwenden. Nach der Deaktivierung können Benutzer immer noch die reine browserbasierte Batch-Download-Funktion verwenden.", + "uploadDownload": "Upload und Download", + "getDirectLink": "Direkten Link abrufen", + "getDirectLinkDes": "Ob Benutzer den direkten Link der Datei abrufen dürfen.", + "bathSourceLinkLimit": "Max. Größe von Batch-Direktlinks", + "bathSourceLinkLimitDes": "Die maximale Anzahl von Dateien, die Benutzer in einem einzigen Batch direkte Links erhalten dürfen. Geben Sie 0 ein, bedeutet keine Batch-Generierung direkter Links ist erlaubt.", + "redirectedSource": "Umgeleiteten direkten Link verwenden", + "redirectedSourceDes": "Empfohlen zu aktivieren. Wenn aktiviert, wird der direkte Link zur Datei, den der Benutzer erhält, von Cloudreve mit einem kürzeren Link umgeleitet. Wenn deaktiviert, wird der direkte Link zur Datei, den der Benutzer erhält, zur ursprünglichen URL zur Datei und ist an die Dateiversion gebunden. Einige Richtlinien erzeugen nicht-umgeleitete direkte Links, die nicht persistent bleiben; siehe Cloudreve-Dokumente für Details.", + "reuseDirectLink": "Vorhandenen direkten Link wiederverwenden", + "reuseDirectLinkDes": "Wenn aktiviert, verwenden mehrere Anfragen für den direkten Link derselben Datei den vorhandenen Umleitungslink wieder.", + "downloadSpeedLimit": "Max. Download-Geschwindigkeit", + "downloadSpeedLimitDes": "Geben Sie 0 ein, um kein Limit anzugeben. Wenn die Beschränkung aktiviert ist, wird die maximale Download-Geschwindigkeit begrenzt, wenn Benutzer alle Dateien unter der Speicherrichtlinie herunterladen, die das Geschwindigkeitslimit unterstützt.", + "anonymousHint": "Diese Benutzergruppe entspricht dem anonymen Besucher, der nicht angemeldet ist.", + "create": "Erstellen", + "copyFromExisting": "Von vorhandener Gruppe kopieren?", + "notCopy": "Nicht kopieren", + "confirmDelete": "Sind Sie sicher, dass Sie die Gruppe {{group}} löschen möchten?", + "new": "Neue Gruppe", + "editGroup": "{{group}} bearbeiten" + }, + "user": { + "createdAt": "Erstellt am", + "originUserGroup": "Ursprüngliche Benutzergruppe", + "originUserGroupDes": "Benutzergruppe, zu der der Benutzer vor dem Kauf der aktuellen Gruppe gehörte. Die aktuelle Gruppe kehrt nach Ablauf zu dieser Gruppe zurück.", + "noOriginUserGroup": "Nein", + "groupExpired": "Gruppen-Ablaufdatum", + "groupExpiredDes": "ISO8601-Format des Gruppen-Ablaufdatums, leer lassen bedeutet, dass die aktuelle Gruppe dauerhaft gültig ist.", + "openUserFiles": "Benutzerdateien öffnen", + "id": "ID", + "idValue": "{{id}} ({{hash_id}})", + "avatar": "Avatar", + "removeAvatar": "Profilbild entfernen", + "userDialogTitle": "Benutzerdetails", + "2FAEnabled": "2FA aktiviert", + "qqEnabled": "QQ verknüpft", + "logtoEnabled": "Logto verknüpft", + "oidcEnabled": "OIDC verknüpft", + "deleted": "Benutzer gelöscht.", + "new": "Neuer Benutzer", + "filter": "Filter", + "emptyNoFilter": "Leer lassen bedeutet kein Filter.", + "selectedObjects": "{{num}} Objekte ausgewählt.", + "nick": "Anzeigename", + "email": "E-Mail", + "group": "Gruppe", + "status": "Status", + "usedStorage": "Verwendeter Speicher", + "status_active": "Aktiv", + "status_inactive": "Inaktiv", + "status_manual_banned": "Manuell gesperrt", + "status_sys_banned": "System gesperrt", + "toggleBan": "Sperren/Entsperren", + "filterCondition": "Filterbedingungen", + "all": "Alle", + "userStatus": "Benutzerstatus", + "apply": "Anwenden", + "editUser": "{{nick}} bearbeiten", + "password": "Passwort", + "passwordDes": "Leer lassen bedeutet keine Änderung.", + "groupDes": "Gruppe, zu der der Benutzer gehört.", + "2FA": "2FA", + "notEnabled": "Nicht aktiviert", + "reset2Fa": "Deaktivieren", + "reset": "Zurücksetzen", + "confirmDelete": "Sind Sie sicher, dass Sie den Benutzer {{user}} löschen möchten?", + "deleteXUsers": "{{num}} Benutzer löschen", + "confirmBatchDelete": "Sind Sie sicher, dass Sie {{num}} Benutzer löschen möchten?", + "calibrateStorage": "Speicher kalibrieren", + "calibrateStorageSuccess": "Speicher erfolgreich kalibriert." + }, + "file": { + "deleteXFiles": "{{num}} Dateien löschen", + "confirmBatchDelete": "Sind Sie sicher, dass Sie {{num}} Dateien löschen möchten?", + "confirmDelete": "Sind Sie sicher, dass Sie die Datei {{file}} löschen möchten?", + "haveShares": "Geteilt", + "haveDirectLinks": "Hat umgeleitete direkte Links", + "directLinkId": "Link-Identifikator", + "directLinks": "Umgeleitete direkte Links", + "noRecords": "Keine Einträge", + "speed": "Geschwindigkeitsbegrenzung", + "downloads": "Downloads", + "shareLink": "Freigabe-Links", + "shareLinkNum": "{{num}} (<0>Anzeigen)", + "blobType": "Typ", + "noEntities": "Keine Blobs", + "blobs": "Blobs", + "creator": "Ersteller", + "source": "Quelle", + "key": "Schlüssel", + "value": "Wert", + "isPublic": "Öffentlich", + "noMetadata": "Keine Metadaten", + "metadata": "Metadaten", + "id": "ID", + "primaryStoragePolicy": "Primäre Speicherrichtlinie", + "fileDialogTitle": "Dateidetails", + "name": "Dateiname", + "deleteAsync": "Löschaufgabe wird im Hintergrund ausgeführt.", + "forceDelete": "Löschen erzwingen", + "size": "Größe", + "sizeUsed": "Verwendeter Speicher", + "uploader": "Besitzer", + "createdAt": "Erstellt am", + "uploading": "Wird hochgeladen", + "unknownUploader": "Unbekannt", + "uploaderID": "Besitzer-ID", + "searchFileName": "Dateiname suchen", + "storagePolicy": "Speicherrichtlinie", + "selectTargetUser": "Zielbenutzer auswählen", + "importTaskCreated": "Importaufgabe erstellt, Sie können den Status in der Hintergrundaufgabenliste anzeigen.", + "manuallyPathOnly": "Die ausgewählte Speicherrichtlinie unterstützt nur die manuelle Eingabe des Pfads.", + "selectFolder": "Ordner auswählen", + "import": "Importieren", + "importExternalFolder": "Externe Ordner importieren", + "importExternalFolderDes": "Sie können vorhandene Dateien und Verzeichnisstrukturen aus Ihrer Speicherrichtlinie in Cloudreve importieren. Der Importvorgang nimmt keinen zusätzlichen physischen Speicher in Anspruch, zieht aber trotzdem das verwendete Speicherkontingent des Benutzers normal ab.", + "storagePolicyDes": "Wählen Sie die Speicherrichtlinie aus, in der die zu importierenden Dateien derzeit gespeichert sind.", + "targetUser": "Zielbenutzer", + "targetUserDes": "Wählen Sie aus, in wessen Dateisystem Sie die Dateien importieren möchten.", + "srcFolderPath": "Quellordnerpfad", + "select": "Auswählen", + "selectSrcDes": "Der Pfad des zu importierenden Verzeichnisses auf der Speicherseite.", + "dstFolderPath": "Zielordnerpfad", + "dstFolderPathDes": "Pfad im Dateisystem des Benutzers, um alle importierten Dateien zu speichern.", + "recursivelyImport": "Rekursiv importieren", + "recursivelyImportDes": "Ob alle Unterverzeichnisse unter dem Verzeichnis rekursiv importiert werden sollen.", + "createImportTask": "Importaufgabe erstellen", + "unlink": "Verknüpfung aufheben (Physische Datei behalten)", + "searchUser": "Benutzer nach Name oder E-Mail suchen...", + "extractMediaMeta": "Medieninformationen extrahieren", + "extractMediaMetaDes": "Ob Medieninformationen für jede Datei während des Imports extrahiert werden sollen.", + "importWarning": "Warnung", + "importWarnings": [ + "Nach dem Import wird die physische Datei von Cloudreve übernommen, bitte ändern Sie sie danach nicht extern.", + "Importieren Sie dieselbe Datei nicht mehrmals.", + "Wenn die Datei des Benutzers in Konflikt steht, wird diese Datei übersprungen." + ], + "otherConditions": "Andere Bedingungen", + "shareLinkExisted": "Hat Freigabe-Link", + "directLinkExisted": "Hat direkten Link", + "isUploading": "Wird hochgeladen" + }, + "entity": { + "refenenceCount": "Referenzzähler", + "waitForRecycle": "Wartet auf Recycling", + "entityDialogTitle": "Blob-Details", + "uploadSessionID": "Upload-Sitzungs-ID", + "referredFiles": "Referenzierte Dateien", + "confirmBatchDelete": "Sind Sie sicher, dass Sie {{num}} Blobs löschen möchten?", + "deleteXEntities": "{{num}} Blobs löschen", + "forceDelete": "Löschen erzwingen", + "forceDeleteDes": "Ob der Blob-Datensatz gelöscht werden soll, unabhängig davon, ob die physische Datei gelöscht wird." + }, + "event": { + "cleanup": "Bereinigung", + "cleanupAuditLog": "Ereignisbereinigung", + "cleanupAuditLogDescription": "Alle Ereignisse löschen, die die folgenden Bedingungen erfüllen:", + "cleanupNotAfter": "Vor diesem Datum", + "cleanupEventTypes": "Ereignistypen", + "cleanupEventTypesDes": "Wählen Sie die zu bereinigenden Ereignistypen aus. Leer lassen, um alle Typen zu bereinigen.", + "initiator": "Initiator", + "event": "Ereignis", + "userID": "Benutzer-ID", + "ip": "IP", + "type": "Typ", + "correlationId": "Korrelations-ID", + "fileID": "Datei-ID", + "emailSend": "E-Mail \"{{title}}\" an {{email}} senden", + "emailFailed": "E-Mail-Warteschlange konnte nicht gestartet werden", + "signinFailed": "Anmeldung fehlgeschlagen: {{reason}}", + "createDavAccount": "WebDAV-Konto erstellen: {{account}}", + "updateDavAccount": "WebDAV-Konto aktualisieren: {{account}}", + "deleteDavAccount": "WebDAV-Konto löschen: {{account}}", + "pointsChange": "Punkteänderung: {{points}}", + "storageAdded": "{{size}} Speicher gekauft", + "nickChange": "Anzeigename von {{old}} zu {{new}} geändert", + "eventDialogTitle": "Ereignisdetails", + "userAgent": "Benutzeragent", + "linkedUser": "Verknüpfter Benutzer", + "datetime": "Zeit", + "linkedFile": "Verknüpfte Datei", + "linkedEntity": "Verknüpfter Blob", + "linkedShare": "Verknüpfte Freigabe", + "rawContent": "Roher Inhalt", + "confirmDelete": "Sind Sie sicher, dass Sie dieses Ereignis löschen möchten?", + "deleteXEvents": "{{num}} Ereignisse löschen", + "confirmBatchDelete": "Sind Sie sicher, dass Sie {{num}} Ereignisse löschen möchten?" + }, + "share": { + "confirmBatchDelete": "Sind Sie sicher, dass Sie {{num}} Freigaben löschen möchten?", + "confirmDelete": "Sind Sie sicher, dass Sie diese Freigabe löschen möchten?", + "deleteXShares": "{{num}} Freigaben löschen", + "shareDialogTitle": "Freigabedetails", + "shareLink": "Freigabe-Link", + "deleted": "Datei gelöscht", + "srcFileName": "Quelldatei", + "views": "Aufrufe", + "downloads": "Downloads", + "price": "Preis", + "autoExpire": "Automatisches Ablaufen", + "owner": "Besitzer", + "createdAt": "Erstellt am", + "private": "Von Profilseite ausblenden", + "yes": "Ja", + "no": "Nein", + "afterNDownloads": "Nach {{num}} Download(s).", + "none": "Keine", + "srcType": "Quellobjekttyp", + "folder": "Ordner", + "file": "Datei" + }, + "task": { + "cleanupTasks": "Aufgaben bereinigen", + "cleanupTasksDescription": "Alle Aufgaben bereinigen, die die folgenden Bedingungen erfüllen:", + "cleanupNotAfter": "Vor diesem Datum", + "cleanupTaskTypes": "Aufgabentypen", + "cleanupTaskTypesDes": "Wählen Sie die zu bereinigenden Aufgabentypen aus. Leer lassen, um alle Typen zu bereinigen.", + "cleanupTaskStatuses": "Aufgabenstatus", + "cleanupTaskStatusesDes": "Wählen Sie die zu bereinigenden Aufgabenstatus aus. Leer lassen, um alle abgeschlossenen Status-Aufgaben zu bereinigen.", + "confirmDelete": "Sind Sie sicher, dass Sie diese Aufgabe löschen möchten?", + "confirmBatchDelete": "Sind Sie sicher, dass Sie {{num}} Aufgaben löschen möchten?", + "deleteXTasks": "{{num}} Aufgaben löschen", + "blobID": "Blob-ID", + "retryIndex": "Wiederholungsindex", + "entityError": "Blobs, die nicht recycelt werden konnten", + "updatedAt": "Aktualisiert am", + "taskDialogTitle": "Aufgabendetails", + "explicitEntityRecycle": "Explizit Dateien-Blobs recyceln: {{blobs}}", + "entityRecycleRoutine": "Dateien-Blob scannen und recyceln", + "mediaMetadata": "Medien-Metadaten von Blob <0>#{{entityID}} extrahieren", + "uploadSentinelCheck": "Status der Upload-Sitzung {{uploadSessionID}} prüfen", + "remoteDownload": "Remote-Download: ", + "owner": "Besitzer", + "content": "Inhalt", + "status": "Status", + "create_archive": "Archiv erstellen", + "extract_archive": "Archiv extrahieren", + "relocate": "Verlagern", + "remote_download": "Remote-Download", + "media_meta": "Medien-Metadaten", + "entity_recycle_routine": "Entity-Recycling-Routine", + "explicit_entity_recycle": "Explizites Entity-Recycling", + "upload_sentinel_check": "Upload-Sentinel-Prüfung", + "import": "Externer Import", + "type": "Typ", + "node": "Verteilter Knoten", + "createdBy": "Erstellt von", + "ready": "Bereit", + "downloading": "Wird heruntergeladen", + "paused": "Pausiert", + "seeding": "Seeding", + "error": "Fehler", + "finished": "Abgeschlossen", + "canceled": "Abgebrochen/Gestoppt", + "unknown": "Unbekannt", + "errorMsg": "Fehlermeldung" + }, + "payment": { + "tradeNo": "Handels-Nr.", + "productType": "Produkttyp", + "providerID": "Anbieter-ID", + "status": "Status", + "deleteXPayments": "{{num}} Zahlungen löschen" + }, + "customProps": { + "add": "Hinzufügen", + "type": "Typ", + "default": "Standardwert", + "actions": "Aktionen", + "text": "Text", + "number": "Zahl", + "boolean": "Checkbox", + "select": "Einfachauswahl", + "multiSelect": "Mehrfachauswahl", + "user": "Benutzer", + "link": "Link", + "rating": "Bewertung", + "addProp": "Eigenschaft hinzufügen", + "editProp": "Eigenschaft bearbeiten", + "icon": "Symbol", + "iconDes": "<0>Iconify Symbolname, leer lassen um das Symbol zu verstecken.", + "id": "ID", + "idDes": "Eigenschafts-ID, stellen Sie sicher, dass sie über alle Eigenschaften hinweg eindeutig ist.", + "idPatternDes": "Nur Buchstaben, Zahlen, Unterstriche und Bindestriche sind erlaubt.", + "minLength": "Mindestlänge", + "maxLength": "Maximallänge", + "emptyLimit": "Leer lassen, um nicht zu begrenzen.", + "minValue": "Mindestwert", + "maxValue": "Maximalwert", + "options": "Optionen", + "optionsDes": "Eine Option pro Zeile." + }, + "vas": { + "disableSubAddressEmail": "Sub-Adress-E-Mail deaktivieren", + "disableSubAddressEmailDes": "Nach der Aktivierung können E-Mail-Adressen mit <0>+ nicht für die Registrierung verwendet werden.", + "confirmDelete": "Sind Sie sicher, dass Sie diese Bestellungen löschen möchten?", + "vas": "VAS", + "reports": "Berichte", + "orders": "Zahlungen", + "initialFiles": "Anfangsdateien", + "initialFilesDes": "Geben Sie die Dateien an, die der Benutzer nach der Registrierung anfänglich besitzt. Geben Sie eine Datei-ID ein, um vorhandene Dateien zu suchen.", + "filterEmailProvider": "E-Mail-Anbieter filtern", + "filterEmailProviderDisabled": "Deaktiviert", + "filterEmailProviderWhitelist": "Whitelist", + "filterEmailProviderBlacklist": "Blacklist", + "filterEmailProviderDes": "E-Mail-Anbieter für die Registrierung einschränken, Drittanbieter-SSO-Anmeldung ist nicht eingeschränkt.", + "filterEmailProviderRule": "E-Mail-Domain-Filterregeln", + "filterEmailProviderRuleDes": "Mehrere Felder mit Semikolon-Komma trennen.", + "qqConnect": "QQ Connect", + "qqConnectHint": "Beim Erstellen der Anwendung füllen Sie bitte die Callback-URL aus: {{url}}", + "enableQQConnect": "QQ Connect aktivieren", + "enableQQConnectDes": "Ob die Verknüpfung mit QQ erlaubt werden soll, QQ zur Anmeldung auf der Website verwenden.", + "loginWithoutBinding": "Anmeldung ohne Registrierung", + "loginWithoutBindingDes": "Nach der Aktivierung erstellt das System ein Konto für Benutzer, die sich über Drittanbieter anmelden, aber kein verknüpftes Konto haben. Benutzer, die sich auf diese Weise anmelden, können sich in Zukunft nur über diesen Drittanbieter anmelden.", + "appid": "APP ID", + "appidDes": "Die APP ID, die von der Anwendungsverwaltungsseite erhalten wurde.", + "appKey": "APP KEY", + "appKeyDes": "Der APP KEY, der von der Anwendungsverwaltungsseite erhalten wurde.", + "overuseReminder": "Überverbrauchserinnerung", + "overuseReminderDes": "Erinnerungs-E-Mail-Vorlage, die an Benutzer gesendet wird, nachdem ihre Kapazität das Limit aufgrund abgelaufener VAS überschreitet.", + "vasSetting": "VAS-Einstellungen", + "storagePack": "Speicherpakete", + "purchasableGroups": "Mitgliedschaften", + "giftCodes": "Geschenkcodes", + "enable": "Aktivieren", + "appID": "App-ID", + "appIDDes": "APPID der Zahlungsanwendung.", + "rsaPrivate": "RSA-Anwendungs-Privatschlüssel", + "rsaPrivateDes": "Der RSA2 (SHA256) Privatschlüssel für die Zahlungsanwendung, normalerweise von Ihnen generiert. Für Details siehe <0>RSA-Schlüssel generieren.", + "alipayPublicKey": "Alipay öffentlicher Schlüssel", + "alipayPublicKeyDes": "Von Alipay bereitgestellt, verfügbar in Anwendungsverwaltung - Anwendungsinformationen - API-Signaturmethode.", + "wechatPay": "WeChat Pay", + "applicationID": "Anwendungs-ID", + "applicationIDDes": "Öffentliche Nummer oder mobile Anwendungs-APPID, die von Händlern beantragt wurde.", + "merchantID": "Händlernummer", + "merchantIDDes": "Die von WeChat Pay generierte und ausgegebene Händlernummer.", + "apiV3Secret": "API v3 Geheimnis", + "apiV3SecretDes": "Der Händler muss das Geheimnis in [Händlerplattform] - [API-Sicherheit] festlegen, bevor er WeChat Pay anfordert. Die Länge des Schlüssels beträgt 32 Bytes.", + "mcCertificateSerial": "Händlerzertifikat-Seriennummer", + "mcCertificateSerialDes": "Navigieren Sie zu [API-Sicherheit] - [API-Zertifikat] - [Zertifikat anzeigen], um die Händler-API-Zertifikat-Seriennummer anzuzeigen.", + "mcAPISecret": "Händler-API-Geheimnis", + "mcAPISecretDes": "Inhalt der Geheimdatei apiclient_key.pem.", + "payjs": "PAYJS", + "payjsWarning": "Dieser Service wird von <0>PAYJS, einer Drittanbieterplattform, bereitgestellt, und alle daraus entstehenden Streitigkeiten liegen nicht in der Verantwortung der Cloudreve-Entwickler.", + "mcNumber": "Händlernummer", + "mcNumberDes": "Verfügbar auf der PAYJS-Admin-Panel-Startseite.", + "communicationSecret": "Kommunikationsschlüssel", + "otherSettings": "Andere Einstellungen", + "banBufferPeriod": "Sperrpufferperiode (Sekunden)", + "banBufferPeriodDes": "Die maximale Zeitdauer, die ein Benutzer den Kapazitätsüberschreitungsstatus aufrechterhalten kann, darüber hinaus wird der Benutzer vom System gesperrt.", + "allowSellShares": "Preisgestaltung für Freigaben erlauben", + "allowSellSharesDes": "Einmal aktiviert können Benutzer einen Kreditpreis für das Teilen festlegen und Kredite werden für das Herunterladen abgezogen.", + "creditPriceRatio": "Kredit-Ankunftsrate (%)", + "creditPriceRatioDes": "Die Rate der Kredite, die tatsächlich beim Teiler ankommen, für den Kauf einer Freigabe mit einem festgelegten Preis für den Download.", + "creditPrice": "Kreditpreis (Pfennig)", + "creditPriceDes": "Preis beim Aufladen von Krediten", + "add": "Hinzufügen", + "name": "Name", + "price": "Preis", + "duration": "Dauer", + "size": "Größe", + "actions": "Aktionen", + "orCredits": " Oder {{num}} Kredite", + "highlight": "Hervorheben", + "yes": "Ja", + "no": "Nein", + "productName": "Produktname", + "qyt": "Menge", + "code": "Code", + "status": "Status", + "invalidProduct": "Ungültiges Produkt", + "used": "Verwendet", + "notUsed": "Nicht verwendet", + "generatingResult": "Ergebnis", + "addStoragePack": "Speicherpaket hinzufügen", + "editStoragePack": "Speicherpaket bearbeiten", + "productNameDes": "Produktanzeigename", + "packSizeDes": "Größe des Speicherpakets", + "durationDay": "Dauer (Tag)", + "durationDayDes": "Gültigkeitsdauer jedes Speicherpakets.", + "priceYuan": "Preis (Yuan)", + "packPriceDes": "Preis des Speicherpakets.", + "priceCredits": "Preis (Kredite)", + "priceCreditsDes": "Der Preis bei der Verwendung von Krediten zum Kauf, 0 eingeben bedeutet, dass Sie keine Kredite zum Kauf verwenden können.", + "editMembership": "Mitgliedschaft bearbeiten", + "addMembership": "Mitgliedschaft hinzufügen", + "group": "Gruppe", + "groupDes": "Benutzergruppen, die nach dem Kauf aktualisiert wurden.", + "durationGroupDes": "Die Gültigkeit der Kaufzeit der Benutzergruppen-Einheit, die nach dem Kauf aktualisiert wurde.", + "groupPriceDes": "Mitgliedschaftspreis", + "productDescription": "Produktbeschreibung (Einmal pro Zeile)", + "productDescriptionDes": "Beschreibung des Produkts, das auf der Kaufseite angezeigt wird.", + "highlightDes": "Nach der Aktivierung wird es auf der Produktauswahlseite hervorgehoben.", + "generateGiftCode": "Geschenkcodes generieren", + "numberOfCodes": "Anzahl der Codes", + "numberOfCodesDes": "Anzahl der zu generierenden Geschenkcodes.", + "linkedProduct": "Verknüpftes Produkt", + "productQyt": "Produktmenge", + "productQytDes": "Für Kreditprodukte ist dies die Anzahl der Punkte und andere Produkte sind Vielfache der Dauer.", + "freeDownload": "Geteilte Dateien kostenlos herunterladen", + "freeDownloadDes": "Nach der Aktivierung kann der Benutzer kostenpflichtige Freigaben kostenlos herunterladen.", + "credits": "Kredite", + "markSuccessful": "Erfolgreich markiert.", + "markAsResolved": "Als gelöst markieren", + "reportedContent": "Gemeldeter Inhalt", + "reason": "Grund", + "description": "Beschreibung", + "reportTime": "Gemeldet am", + "invalid": "[Ungültig]", + "deleteShare": "Freigabe-Link löschen", + "orderDeleted": "Bestellung gelöscht.", + "orderName": "Name", + "product": "Produkt", + "productDes": "Produktname", + "orderNumber": "Handels-Nr.", + "amount": "Betrag", + "paidBy": "Bezahlt mit", + "orderOwner": "Erstellt von", + "unpaid": "Unbezahlt", + "paid": "Bezahlt", + "shareLink": "Geteilter Link", + "mobileApp": "Mobile Anwendung", + "showAppPromotion": "Werbeseite anzeigen", + "showAppPromotionDes": "Nach der Aktivierung kann der Benutzer die Anleitungsseite für mobile Anwendungen auf der Seite \"Verbinden & Einbinden\" sehen.", + "customPaymentName": "Name der Zahlungsmethode", + "customPaymentNameDes": "Name der Zahlungsmethode, die dem Benutzer angezeigt wird.", + "customPaymentSecretDes": "Geheimer Schlüssel zum Signieren von Zahlungsanfragen.", + "customPaymentEndpoint": "Zahlungs-API-URL", + "customPaymentEndpointDes": "URL, die beim Erstellen einer Zahlungsbestellung angefordert werden soll.", + "appFeedback": "Feedback-URL", + "appForum": "Benutzerforum-URL", + "appLinkDes": "Wird im mobilen Client angezeigt, leer lassen um Menüelement zu verstecken. Diese Einstellung wird nur wirksam, wenn die VOL-Lizenz gültig ist." + }, + "abuseReport": { + "deleteXAbuseReports": "{{num}} Missbrauchsmeldungen löschen", + "folderPath": "Ordnerpfad", + "reporter": "Melder", + "shareLink": "Geteilter Link <0>#{{id}}", + "deletedShare": "Gelöschter geteilter Link", + "deletedUser": "Gelöschter Benutzer", + "confirmDelete": "Sind Sie sicher, dass Sie diese Missbrauchsmeldung löschen möchten?", + "confirmBatchDelete": "Sind Sie sicher, dass Sie {{num}} Missbrauchsmeldungen löschen möchten?", + "reporterID": "Melder-Benutzer-ID", + "reportedUserID": "Gemeldete Benutzer-ID", + "shareID": "Freigabe-ID", + "reason": "Grund" + } +} diff --git a/public/locales/de-DE/image_editor.json b/public/locales/de-DE/image_editor.json new file mode 100755 index 0000000..f56e8c9 --- /dev/null +++ b/public/locales/de-DE/image_editor.json @@ -0,0 +1,113 @@ +{ + "name": "Name", + "save": "Speichern", + "saveAs": "Speichern unter", + "back": "Zurück", + "loading": "Lädt...", + "resetOperations": "Alle Operationen zurücksetzen/löschen", + "changesLoseWarningHint": "Wenn Sie die Schaltfläche „Zurücksetzen“ drücken, gehen Ihre Änderungen verloren. Möchten Sie fortfahren?", + "discardChangesWarningHint": "Wenn Sie das Modal schließen, wird Ihre letzte Änderung nicht gespeichert.", + "cancel": "Abbrechen", + "apply": "Anwenden", + "warning": "Warnung", + "confirm": "Bestätigen", + "discardChanges": "Änderungen verwerfen", + "undoTitle": "Letzte Operation rückgängig machen", + "redoTitle": "Letzte Operation wiederholen", + "showImageTitle": "Originalbild anzeigen", + "zoomInTitle": "Vergrößern", + "zoomOutTitle": "Verkleinern", + "toggleZoomMenuTitle": "Zoom-Menü umschalten", + "adjustTab": "Anpassen", + "finetuneTab": "Feintuning", + "filtersTab": "Filter", + "watermarkTab": "Wasserzeichen", + "annotateTabLabel": "Kommentieren", + "resize": "Größe ändern", + "resizeTab": "Größe ändern", + "imageName": "Bildname", + "invalidImageError": "Ungültiges Bild bereitgestellt.", + "uploadImageError": "Fehler beim Hochladen des Bildes.", + "areNotImages": "sind keine Bilder", + "isNotImage": "ist kein Bild", + "toBeUploaded": "hochzuladen", + "cropTool": "Zuschneiden", + "original": "Original", + "custom": "Benutzerdefiniert", + "square": "Quadrat", + "landscape": "Querformat", + "portrait": "Hochformat", + "ellipse": "Ellipse", + "classicTv": "Klassisches TV", + "cinemascope": "Cinemascope", + "arrowTool": "Pfeil", + "blurTool": "Unschärfe", + "brightnessTool": "Helligkeit", + "contrastTool": "Kontrast", + "ellipseTool": "Ellipse", + "unFlipX": "X-Spiegelung aufheben", + "flipX": "X spiegeln", + "unFlipY": "Y-Spiegelung aufheben", + "flipY": "Y spiegeln", + "hsvTool": "HSV", + "hue": "Farbton", + "brightness": "Helligkeit", + "saturation": "Sättigung", + "value": "Wert", + "imageTool": "Bild", + "importing": "Importiert...", + "addImage": "+ Bild hinzufügen", + "uploadImage": "Bild hochladen", + "fromGallery": "Aus Galerie", + "lineTool": "Linie", + "penTool": "Stift", + "polygonTool": "Polygon", + "sides": "Seiten", + "rectangleTool": "Rechteck", + "cornerRadius": "Eckenradius", + "resizeWidthTitle": "Breite in Pixeln", + "resizeHeightTitle": "Höhe in Pixeln", + "toggleRatioLockTitle": "Seitenverhältnis-Sperre umschalten", + "resetSize": "Auf ursprüngliche Bildgröße zurücksetzen", + "rotateTool": "Drehen", + "textTool": "Text", + "textSpacings": "Textabstände", + "textAlignment": "Textausrichtung", + "fontFamily": "Schriftfamilie", + "size": "Größe", + "letterSpacing": "Zeichenabstand", + "lineHeight": "Zeilenhöhe", + "warmthTool": "Wärme", + "addWatermark": "+ Wasserzeichen hinzufügen", + "addTextWatermark": "+ Text-Wasserzeichen hinzufügen", + "addWatermarkTitle": "Wasserzeichen-Typ auswählen", + "uploadWatermark": "Wasserzeichen hochladen", + "addWatermarkAsText": "Als Text hinzufügen", + "padding": "Innenabstand", + "paddings": "Innenabstände", + "shadow": "Schatten", + "horizontal": "Horizontal", + "vertical": "Vertikal", + "blur": "Unschärfe", + "opacity": "Deckkraft", + "transparency": "Transparenz", + "position": "Position", + "stroke": "Umrandung", + "saveAsModalTitle": "Speichern unter", + "extension": "Erweiterung", + "format": "Format", + "nameIsRequired": "Name ist erforderlich.", + "quality": "Qualität", + "imageDimensionsHoverTitle": "Gespeicherte Bildgröße (Breite x Höhe)", + "cropSizeLowerThanResizedWarning": "Hinweis: Der ausgewählte Zuschneidebereich ist kleiner als die angewendete Größenänderung, was zu Qualitätseinbußen führen kann", + "actualSize": "Tatsächliche Größe (100%)", + "fitSize": "Angepasste Größe", + "addImageTitle": "Bild zum Hinzufügen auswählen...", + "mutualizedFailedToLoadImg": "Fehler beim Laden des Bildes.", + "tabsMenu": "Menü", + "download": "Herunterladen", + "width": "Breite", + "height": "Höhe", + "plus": "+", + "cropItemNoEffect": "Keine Vorschau für dieses Zuschneideelement verfügbar" +} diff --git a/public/locales/de-DE/markdown_editor.json b/public/locales/de-DE/markdown_editor.json new file mode 100755 index 0000000..394c876 --- /dev/null +++ b/public/locales/de-DE/markdown_editor.json @@ -0,0 +1,114 @@ +{ + "frontmatterEditor": { + "title": "Dokument-Frontmatter bearbeiten", + "key": "Schlüssel", + "value": "Wert", + "addEntry": "Eintrag hinzufügen" + }, + "dialogControls": { + "save": "Speichern", + "cancel": "Abbrechen" + }, + "uploadImage": { + "dialogTitle": "Bild hochladen", + "uploadInstructions": "Laden Sie ein Bild von Ihrem Gerät hoch:", + "addViaUrlInstructions": "Oder fügen Sie ein Bild über eine URL / relativen Pfad hinzu (relativ zur aktuellen Datei):", + "autoCompletePlaceholder": "Wählen Sie eine Bild-URL aus oder fügen Sie sie ein", + "addViaUrlInstructionsNoUpload": "Bild-URL:", + "alt": "Alt-Text:", + "title": "Titel:" + }, + "imageEditor": { + "deleteImage": "Bild löschen", + "editImage": "Bild bearbeiten" + }, + "createLink": { + "url": "URL", + "urlPlaceholder": "Wählen Sie eine URL aus oder fügen Sie sie ein", + "title": "Titel", + "saveTooltip": "URL setzen", + "cancelTooltip": "Änderung abbrechen" + }, + "linkPreview": { + "open": "{{url}} in neuem Fenster öffnen", + "edit": "Link-URL bearbeiten", + "copyToClipboard": "In Zwischenablage kopieren", + "copied": "Kopiert!", + "remove": "Link entfernen" + }, + "table": { + "deleteTable": "Tabelle löschen", + "columnMenu": "Spaltenmenü", + "textAlignment": "Textausrichtung", + "alignLeft": "Linksbündig", + "alignCenter": "Zentriert", + "alignRight": "Rechtsbündig", + "insertColumnLeft": "Spalte links von dieser einfügen", + "insertColumnRight": "Spalte rechts von dieser einfügen", + "deleteColumn": "Diese Spalte löschen", + "rowMenu": "Zeilenmenü", + "insertRowAbove": "Zeile oberhalb dieser einfügen", + "insertRowBelow": "Zeile unterhalb dieser einfügen", + "deleteRow": "Diese Zeile löschen" + }, + "toolbar": { + "blockTypes": { + "paragraph": "Absatz", + "quote": "Zitat", + "heading": "Überschrift {{level}}" + }, + "blockTypeSelect": { + "selectBlockTypeTooltip": "Blocktyp auswählen", + "placeholder": "Blocktyp" + }, + "toggleGroup": "Gruppe umschalten", + "removeBold": "Fett entfernen", + "bold": "Fett", + "removeItalic": "Kursiv entfernen", + "italic": "Kursiv", + "underline": "Unterstreichen entfernen", + "removeUnderline": "Unterstreichen", + "removeInlineCode": "Code-Format entfernen", + "inlineCode": "Inline-Code-Format", + "link": "Link erstellen", + "richText": "Rich Text", + "diffMode": "Diff-Modus", + "source": "Quellcode-Modus", + "admonition": "Hinweis einfügen", + "codeBlock": "Codeblock einfügen", + "editFrontmatter": "Frontmatter bearbeiten", + "insertFrontmatter": "Frontmatter einfügen", + "image": "Bild einfügen", + "insertSandpack": "Sandpack einfügen", + "table": "Tabelle einfügen", + "thematicBreak": "Thematische Trennung einfügen", + "bulletedList": "Aufzählung", + "numberedList": "Nummerierte Liste", + "checkList": "Checkliste", + "deleteSandpack": "Diesen Codeblock löschen", + "undo": "Rückgängig {{shortcut}}", + "redo": "Wiederholen {{shortcut}}", + "superscript": "Hochgestellt", + "subscript": "Tiefgestellt", + "strikethrough": "Durchgestrichen", + "removeSubscript": "Tiefstellung entfernen", + "removeSuperscript": "Hochstellung entfernen", + "removeStrikethrough": "Durchstreichung entfernen" + }, + "admonitions": { + "note": "Hinweis", + "tip": "Tipp", + "danger": "Gefahr", + "info": "Info", + "caution": "Vorsicht", + "changeType": "Hinweistyp auswählen", + "placeholder": "Hinweistyp" + }, + "codeBlock": { + "language": "Codeblock-Sprache", + "selectLanguage": "Codeblock-Sprache auswählen" + }, + "contentArea": { + "editableMarkdown": "bearbeitbares Markdown" + } +} diff --git a/public/locales/en-US/application.json b/public/locales/en-US/application.json new file mode 100755 index 0000000..594220c --- /dev/null +++ b/public/locales/en-US/application.json @@ -0,0 +1,919 @@ +{ + "login": { + "lastStep": "Last step", + "siginToYourAccount": "Sign in to your account", + "createNewAccount": "Create a new account", + "enterPassword": "Enter your password", + "enterPasswordHint": "Please enter password for {{email}}", + "paswordlessHint": "Account {{email}} is passwordless, please select sign-in method below to log in:", + "noAccountSignupNow": "No account? <0>Sign up now", + "haveAccountSignInNow": "Got an account already? <0>Sign in now", + "privacyPolicy": "Privacy policy", + "termOfUse": "Terms of use", + "email": "Email", + "signupHint": "The account {{email}} does not exist, do you want to signup now?", + "accountNotFoundHint": "The account \"{{email}}\" you entered does not exist.", + "or": "Or", + "selectAccountToUse": "Select an account to use", + "useOtherAccount": "Use another account", + "password": "Password", + "captcha": "CAPTCHA", + "captchaError": "Cannot load CAPTCHA: {{message}}", + "signIn": "Sign in", + "signUp": "Sign up", + "signUpAccount": "Sign up", + "useFIDO2": "Use Passkey", + "usePassword": "Use Password", + "forgetPassword": "Forgot password?", + "2FA": "2FA Verification", + "input2FACode": "Please enter the six-digit 2FA verification code", + "passwordNotMatch": "Those passwords didn't match.", + "findMyPassword": "Find my password", + "passwordReset": "Password has been reset.", + "newPassword": "New password", + "repeatNewPassword": "Repeat the new password", + "repeatPassword": "Repeat the password", + "resetPassword": "Reset my password", + "backToSingIn": "Back to sign in", + "sendMeAnEmail": "Send me an email", + "resetEmailSent": "An email has been sent, please pay attention to check.", + "browserNotSupport": "Not supported by current browser or environment.", + "success": "Sign in successful", + "signUpSuccess": "Sign up successful", + "activateSuccess": "Sign up completed", + "accountActivated": "Your account has been successfully activated.", + "title": "Sign in to {{title}}", + "sinUpTitle": "Sign up to {{title}}", + "activateTitle": "Activate your account", + "activateDescription": "An activation email has been sent to your email address, please visit the link in the email to complete your sign-up.", + "continue": "Next", + "back": "Back", + "logout": "Sign out", + "signingOut": "Signing out...", + "loggedOut": "You are signed out now.", + "clickToRefresh": "Click to refresh", + "switchLanguage": "Switch Language" + }, + "navbar": { + "notBefore": "Not before", + "notAfter": "Not after", + "minimum": "Minimum", + "maximum": "Maximum", + "fileSize": "File size", + "searchBase": "Search in", + "searchInBase": "Search in <0>", + "conditionDuplicate": "Condition already exists.", + "fileType": "File type", + "addCondition": "Add conditions", + "notNameOpOr": "All keywords must present", + "caseFolding": "Case folding", + "keywords": "Keywords", + "fileNameKeywordsHelp": "Press enter to add new keyword.", + "advancedSearch": "Advanced search", + "searchFilesTitle": "Search files", + "searchIn": "Search <0>{{keywords}}", + "recentlyViewed": "Recently viewed", + "searchFiles": "Search files...", + "showMore": "More", + "myFiles": "My Files", + "hisFiles": "His/Her files", + "myShare": "Shared by me", + "trash": "Trash", + "sharedWithMe": "Shared with me", + "remoteDownload": "Remote Download", + "connect": "Connect & Mount", + "taskQueue": "Background Tasks", + "setting": "Settings", + "videos": "Videos", + "photos": "Photos", + "music": "Music", + "documents": "Documents", + "addATag": "Add a tag...", + "addTagDialog": { + "selectFolder": "Select a Folder", + "fileSelector": "File Selector", + "folderLink": "Folder Shortcut", + "tagName": "Tag name", + "matchPattern": "Match pattern(s) of file name", + "matchPatternDescription": "You can use <0>* as a wildcard. For example, <1>*.png means match png format images. Multi-line rules will operate in an \"or\" relationship with each other.", + "icon": "Icon:", + "color": "Color:", + "folderPath": "Path to the folder" + }, + "storage": "Storage", + "storageDetail": "{{used}} of {{total}} used", + "notLoginIn": "Signed out", + "visitor": "Anonymous", + "objectsSelected": "{{num}} selected", + "searchPlaceholder": "Type <0>/ to search", + "backToHomepage": "Back to homepage", + "darkModeSwitch": "Dark theme switch", + "toDarkMode": "Dark", + "toLightMode": "Light", + "myProfile": "My profile", + "dashboard": "Dashboard" + }, + "fileManager": { + "customProps": "Custom properties", + "rating": "Rating", + "description": "Description", + "add": "Add", + "clickToEdit": "Click to edit...", + "clickToEditSelect": "Click to select...", + "enterUrl": "Enter URL...", + "searchUser": "Search user...", + "typeToSearch": "Enter name or email...", + "searchProperty": "Search files with the same property", + "permissions": "Permissions", + "quality": "Quality", + "audioTrack": "Audio", + "auto": "Auto", + "default": "Default", + "shareWithMeEmpty": "No shared files found", + "shareWithMeEmptyDes": "If you need to see others' shares here, please save the shortcut to any location in your files when you visit a shared link.", + "selectAll": "Select all", + "selectNone": "Select none", + "invertSelection": "Invert selection", + "imageSize": "Image size", + "focalLength": "Focal length", + "columnExisted": "Column already exists.", + "metadataColumn": "Metadata ({{metadata}})", + "column": "Column", + "listColumnSetting": "Column setting", + "addColumn": "Add columns", + "failedLoadPreview": "Failed to load preview.", + "recursiveLimitReached": "Search depth limit reached.", + "recursiveLimitReachedDes": "The system has stopped searching deeper folders, please try to narrow down the search scope.", + "searchConditions": "{{num}} condition(s)", + "createDate": "Date created", + "updatedDate": "Date updated", + "cameraMake": "Camera maker", + "cameraModel": "Camera model", + "lensModel": "Lens model", + "lensMake": "Lens maker", + "metadataKey": "Key", + "metadataValue": "Value", + "metadata": "Metadata", + "symbolicFile": "Symbolic link", + "relocation": "Relocate storage policy", + "downloadingFile": "Downloading \"{{name}}\", please do not close this page...", + "mountOwner": "Only the owner of current folder can mount policies.", + "uploading": "Uploading", + "noActionsCanBeDone": "No actions can be done.", + "newFileName": "New file.{{ext}}", + "newDocumentType": "{{display_name}} (.{{ext}})", + "text": "Text", + "diagram": "Diagram", + "whiteboard": "Whiteboard", + "selectApplications": "Select applications...", + "newlyCreatedFolder": "New folder", + "expandAllApp": "Expand all applications", + "epubViewer": "ePub Reader", + "googledocs": "Google Docs Viewer", + "m365viewer": "Microsoft Office Online Viewer", + "pdfViewer": "PDF Viewer", + "archivePreview": "Archive Preview", + "extractSelected": "Extract Selected Files", + "viewerFileSizeWarning": "Size of opened file ({{file_size}}) exceed limit ({{max}}) of {{app}}, it might not work properly.", + "testSubtitleStyle": "Test subtitle style AaBbCc", + "color": "Color", + "fontSize": "Font size", + "disableSubtitle": "Disable subtitle", + "noSubtitle": "No ASS/SRT/VTT subtitle files found under current folder.", + "subtitleStyles": "Subtitle styles", + "subtitles": "Subtitles", + "markdownEditor": "Markdown Editor", + "saveSuccess": "Saved successfully at {{time}}", + "drawioLng": "en", + "charset": "Charset", + "textType": "Text type", + "fileSaved": "File saved.", + "failedToLoadFile": "Failed to load file: {{msg}}", + "monacoEditor": "Monaco Code Editor", + "preparingOpenFile": "Preparing to open file...", + "openWithDescription": "Select an application to open .{{ext}} file.", + "openWith": "Open with", + "readOnly": "Read only", + "save": "Save", + "noMoreImages": "No images found in current page.", + "imageViewer": "Image Viewer", + "logFileDeleteShare": "Deleted a share link", + "logFileEditShare": "Edited a share link", + "deleteShareWarning": "Are you sure to delete this share link?", + "edit": "Edit", + "editAndReactivate": "Edit and reactivate", + "yes": "Yes", + "no": "No", + "permanentValid": "Permanent", + "manageShares": "Manage share links", + "manageDirectLinks": "Manage direct links", + "deleteLinkConfirm": "Are you sure to delete this direct link?", + "directLinkNotFound": "The direct link you are looking for does not exist.", + "versionNotFound": "The version you are looking for does not exist.", + "deleteVersionWarning": "Are you sure to delete this version? This operation cannot be undone.", + "setAsCurrent": "Set as current version", + "current": "[Current]", + "createdBy": "Created by", + "manageVersions": "Manage versions", + "livePhoto": "Live Photo", + "version": "Version", + "actions": "Actions", + "versionEntity": "File data and versions", + "data": "Data", + "owned": "Owned", + "ownedSymbolic": "Owned (Symbolic link)", + "expires": "Expires", + "originalLocation": "Original location", + "descendant": "Descendant", + "folderChildren": "{{files}} file(s), {{folders}} folder(s)", + "moreThan": "More than {{text}}", + "calculate": "Calculate", + "unset": "Unset", + "folder": "Folder", + "file": "File", + "symbolicLink": "Symbolic link ({{srcType}})", + "type": "Type", + "storageUsed": "Storage used", + "location": "Location", + "basicInfo": "Basic info", + "format": "Format", + "duration": "Duration", + "artist": "Artist", + "album": "Album", + "title": "Title", + "resolution": "Resolution", + "takenAt": "Taken at", + "software": "Software", + "copyright": "Copyright", + "exposureBias": "Exposure bias", + "flash": "Flash", + "copyToClipboard": "Copy to clipboard", + "searchSomething": "Search \"{{text}}\"...", + "iso": "ISO", + "exposureValue": "{{num}} s", + "exposure": "Exposure", + "aperture": "Aperture", + "address": "Address", + "street": "Street", + "locality": "Locality", + "place": "City", + "district": "District", + "region": "Province", + "country": "Country", + "mediaInfo": "Media info", + "details": "Details", + "activity": "Activity", + "goToSharedLink": "Go to shared link", + "saveShortcut": "Save share link as shortcut", + "customizeIcon": "Customize icon", + "tags": "Tags", + "apply": "Apply", + "customizeColor": "Customize color", + "folderColor": "Folder color", + "restore": "Restore", + "unpin": "Unpin", + "youDontHaveReadPermissionToThisFile": "You don't have access permission.", + "anonymousAccessDenied": "You don't have access permission, please try to sign in.", + "sharedWithOthers": "Shared with others", + "new": "New", + "open": "Open", + "openParentFolder": "Go to parent folder", + "download": "Download", + "batchDownload": "Download in batch", + "share": "Share", + "rename": "Rename", + "organize": "Organize", + "pin": "Pin to sidebar", + "pinAlias": "Display name", + "optional": "Optional", + "move": "Move", + "delete": "Delete", + "moreActions": "More actions", + "refresh": "Refresh", + "createArchive": "Create archive file", + "resetThumbnail": "Reset broken thumbnail", + "resetThumbnailRequested": "Thumbnail reset requested.", + "noFileCanResetThumbnail": "No files available for thumbnail reset.", + "newFolder": "New folder", + "newFile": "New file", + "showFullPath": "Show full path", + "listView": "List", + "gridView": "Grid", + "galleryView": "Gallery", + "paginationSize": "Pagination", + "paginationOption": "{{option}} / page", + "noPagination": "No pagination", + "sortMethod": "Sort", + "sortMethods": { + "A-Z": "A-Z", + "Z-A": "Z-A", + "oldestUploaded": "Oldest uploaded", + "newestUploaded": "Newest uploaded", + "oldestModified": "Oldest modified", + "newestModified": "Newest modified", + "smallest": "Smallest", + "largest": "Largest" + }, + "shareCreateBy": "Created by {{nick}}", + "name": "Name", + "size": "Size", + "lastModified": "Last modified", + "currentFolder": "Current Folder", + "backToParentFolder": "Back to the parent", + "folders": "Folders", + "files": "Files", + "listError": "Failed to list files", + "dropFileHere": "Drag and drop the file here", + "orClickUploadButton": "Or click the \"New\" button at the top left to add a file", + "nothingFound": "Nothing was found", + "uploadFiles": "Upload files", + "uploadFolder": "Upload folder", + "newRemoteDownloads": "New remote download", + "enter": "Enter", + "getSourceLink": "Get direct link", + "createRemoteDownloadForTorrent": "New remote download", + "extractArchive": "Extract archive", + "createShareLink": "Share", + "viewDetails": "View details", + "copy": "Copy", + "bytes": " ({{bytes}} Bytes)", + "storagePolicy": "Storage policy", + "childFolders": "Child folders", + "childFiles": "Child files", + "childCount": "{{num}}", + "parentFolder": "Parent folder", + "rootFolder": "Root folder", + "modifiedAt": "Modified at", + "createdAt": "Created at", + "statisticAt": "Statistic at", + "musicPlayer": "Music Player", + "closeAndStop": "Close and stop", + "playInBackground": "Play in background", + "copyTo": "Copy to", + "copyToDst": "Copy to <0>", + "moveTo": "Move to", + "moveToDst": "Move to <0>", + "errorReadFileContent": "Failed to read file content: {{msg}}", + "wordWrap": "Word wrap", + "pdfLoadingError": "Failed to load PDF: {{msg}}", + "subtitleSwitchTo": "Subtitle switched to: {{subtitle}}", + "noSubtitleAvailable": "No available subtitle files in the video folder (supported: ASS/SRT/VTT)", + "subtitle": "Subtitles", + "playlist": "Playlist", + "openInExternalPlayer": "Open in external player", + "repeatMode": "Repeat Mode", + "listRepeat": "List Repeat", + "singleRepeat": "Single Repeat", + "shuffle": "Shuffle", + "playbackSpeed": "Playback Speed", + "searchResult": "Search Results", + "preparingBathDownload": "Preparing batch download...", + "preparingDownload": "Preparing to download...", + "browserDownload": "Browser-side download to a local folder", + "browserDownloadDescription": "Your browser downloads files one by one and retain the folder structure to the local directory you specified.", + "browserBatchDownload": "Browser-side archiving", + "browserBatchDownloadDescription": "Downloaded and packaged to a Zip file by the browser in real time, it cannot handle data more than 4GB.", + "serverBatchDownload": "Server-side archiving", + "serverBatchDownloadDescription": "Archive by the server to a Zip file and sent to the client for download on-the-fly, share link shortcut is not supported.", + "selectArchiveMethod": "Select archive method", + "batchDownloadStarted": "Batch download has started, please do not close this tab...", + "batchDownloadError": "Failed to archive: {{msg}}", + "userDenied": "User denied.", + "directoryDownloadReplace": "Replace", + "directoryDownloadReplaceDescription": "Local file \"{{name}}\" will be replaced by the downloaded file.", + "directoryDownloadSkip": "Skip", + "directoryDownloadSkipDescription": "\"{{name}}\" will be skipped.", + "selectDirectoryDuplicationMethod": "Duplicated file", + "directoryDownloadReplaceAll": "Replace all", + "directoryDownloadReplaceAllDescription": "All files with the same name will be replaced by the downloaded files.", + "directoryDownloadSkipAll": "Skip all", + "directoryDownloadSkipAllDescription": "All files with the same name will be skipped.", + "directoryDownloadStarted": "Download started, please do not close this tab.", + "directoryDownloadFinished": "Download finished, no failed objects.", + "directoryDownloadFinishedWithError": "Download finished, {{failed}} object failed.", + "directoryDownloadPermissionError": "Permission denied, please allow read and write local files.", + "back": "Back", + "view": "View", + "layout": "Layout", + "thumbnails": "Thumbnails", + "on": "On", + "off": "Off", + "viewSetting": "View setting", + "saved": "Saved", + "notSet": "Not set", + "deleteViewSetting": "Delete view setting" + }, + "modals": { + "includePasswordInShareLink": "Include password in share link", + "includePasswordInShareLinkDes": "If selected, password will be included in the share link, and no password is required when accessing the share link.", + "showFileName": "Show file name", + "forceDownload": "Force download", + "archiveFile": "Archive file", + "cancelDownload": "Cancel download", + "always": "Always", + "justOnce": "Just once", + "quality": "Quality", + "saveAsOtherFormat": "Save as other format", + "conflictDes1": "File version conflict, possible reasons are:", + "conflictDes2": "<0>The file was updated to a new version from elsewhere after you opened it.<1>If you saved it with a new name or a new location, the file name already exists.", + "saveAs": "Save as", + "versionConflict": "Version conflict", + "overwrite": "Overwrite", + "editShareLink": "Edit share link", + "clearPermissions": "Clear permission settings", + "shortcutCreated": "Shortcut created.", + "createShortcut": "Create shortcut", + "createShortcutTo": "Create shortcut at <0>", + "targetExisted": "Target already exists.", + "users": "Users", + "groups": "Groups", + "noResults": "No results", + "resetToDefault": "Reset to default", + "duplicateTag": "Tag \"{{tag}}\" already exists.", + "colorForTag": "Customize color for new tags", + "enterForNewTag": "Press enter to add new tag.", + "manageTags": "Manage tags", + "onlyOwner": "Only the owner of this file can force unlock it.", + "forceUnlock": "Force Unlock", + "forceUnlockAll": "Force Unlock All", + "forceUnlockDes": "Forcing unlock may corrupt the file state, we recommend waiting for the file being released proactively, are you sure to continue unlocking?", + "webdav": "WebDAV", + "soft-delete": "Move to trash bin", + "updateMetadata": "Update metadata", + "upload": "Upload", + "moveCopy": "Move or copy", + "view": "View", + "cannotPerformAction": "Moving or copying files to here is not supported.", + "cannotMoveCopyToChild": "Cannot move or copy to descendant folder.", + "copySuccess": "{{num}} file(s) copied successfully.", + "moveSuccess": "{{num}} file(s) moved successfully.", + "unknownParent": "Unknown parent", + "unknownParentDes": "The occupied folder is the parent folder of a shared folder, and it's not owned by you.", + "lockConflictTitle": "File occupied", + "lockConflictDescription": "This operation cannot complete because following file(s) is currently used by others, please try again later. If you are the file owner and you are sure that the file is not in use, you can force unlock the file and retry.", + "application": "Application", + "errorDetailsTitle": "Error details", + "processingMoving": "Moving files...", + "processingCopying": "Copying files...", + "processingRestoring": "Restoring files...", + "fileRestored": "{{num}} file(s) restored to its original location.", + "duplicatedObjectName": "Duplicated file name.", + "newNameLengthError": "Length of file name must be in range of 1 to 255.", + "newNameCharacterError": "Name must not contain any of those characters: \\ / : * ? \" < > |", + "newNameDotError": "Name cannot be \".\" or \"..\"", + "taskCreated": "Task created.", + "taskCreateFailed": "{{failed}} task(s) failed to be created: {{details}}.", + "linkCopied": "Link copied.", + "getSourceLinkTitle": "Get direct link", + "sourceLink": "Direct link", + "folderName": "Folder name", + "create": "Create", + "fileName": "File name", + "renameDescription": "Enter the new name for <0>{{name}}:", + "newName": "New name", + "moveToDescription": "Move to <0>{{name}}", + "saveToTitle": "Save to", + "saveToTitleDescription": "Save to <0>{{name}}", + "deleteTitle": "Delete objects", + "deleteOneDescription": "Are you sure to move <0>{{name}} to trash bin?", + "deleteMultipleDescription": "Are you sure to move those {{num}} objects to trash bin?", + "deleteOneDescriptionHard": "Are you sure to permanently delete <0>{{name}}?", + "trashRetention": "Files in the trash bin will be deleted after <0>{{num}}.", + "deleteMultipleDescriptionHard": "Are you sure to permanently delete those {{num}} objects?", + "newRemoteDownloadTitle": "New remote download task", + "remoteDownloadURL": "Download target URL", + "remoteDownloadURLDescription": "Paste the download URL, one URL per line", + "remoteDownloadDst": "Download to", + "processNode": "Target node", + "remoteDownloadNodeAuto": "Auto dispatch", + "createTask": "Create task", + "downloadToDst": "Download to <0>{{name}}", + "downloadTo": "Download to", + "decompressTo": "Extract to", + "decompressToDst": "Extract to <0>{{name}}", + "defaultEncoding": "Default", + "chineseMajorEncoding": "", + "selectEncoding": "ZIP file encoding", + "password": "Password", + "passwordDescription": "If the archive file is not encrypted, please leave this field blank.", + "noEncodingSelected": "No encoding method selected", + "listingFiles": "Listing files...", + "listingFileError": "Failed to list files: {{message}}", + "generatingSourceLinks": "Generating source links...", + "noFileCanGenerateSourceLink": "There is no file that can be used to generate source link", + "sourceBatchSizeExceeded": "The current user group can generate source links for a maximum of {{limit}} files at the same time.", + "zipFileName": "Archive file name", + "shareLinkShareContent": "I shared with you: {{name}} Link: {{link}}", + "shareLinkPasswordInfo": "Password: {{password}}", + "createShareLink": "Create share link", + "privateShare": "Protect with password", + "privateShareDes": "If selected, password is required to access the share link.", + "useCustomPassword": "Custom share link password", + "expireAfterDownload": "Expire after being downloaded", + "sharePassword": "Share password", + "randomlyGenerate": "Random", + "expireAutomatically": "Automatic expiration", + "downloadLimitOptions": "{{num}} downloads", + "or": "Or after", + "5minutes": "5 minutes", + "1hour": "1 hour", + "1day": "1 day", + "7days": "7 days", + "30days": "30 days", + "custom": "Custom", + "minutes": "minutes", + "downloads": "downloads", + "expireSuffix": "", + "expirePrefix": "Expire after", + "allowPreview": "Enable preview", + "allowPreviewDescription": "Whether to allow preview of file content from the share link", + "shareLink": "Share link", + "sendLink": "Send the link", + "directoryDownloadReplaceNotifiction": "Overwrite {{name}}", + "directoryDownloadSkipNotifiction": "Skipped {{name}}", + "directoryDownloadTitle": "Batch download logs", + "directoryDownloadStarted": "Start downloading \"{{name}}\"", + "directoryDownloadFinished": "Download finished \"{{name}}\"", + "directoryDownloadError": "Error: {{msg}}", + "directoryDownloadErrorNotification": "Error occurs while download {{name}}: {{msg}}", + "directoryDownloadAutoscroll": "Auto scroll", + "directoryDownloadCancelled": "Download cancelled", + "advanceOptions": "Advanced options", + "skipSoftDelete": "Permanently delete", + "skipSoftDeleteDes": "Skip moving to trash bin, permanently delete", + "unlinkOnly": "Keep physical files", + "unlinkOnlyDes": "Delete file records only, physical files will not be deleted.", + "shareView": "Share view setting", + "shareViewDes": "If selected, other users can see your view setting (layout, sorting, etc.) saved on the server server when accessing this shared folder.", + "showReadme": "Show README file", + "showReadmeDes": "If selected, the <0>README.md file (case-sensitive) in the directory will be automatically displayed for visitors.", + "viewSetting": "View setting", + "saved": "Saved", + "notSet": "Not set", + "deleteViewSetting": "Delete view setting" + }, + "uploader": { + "fileCopyName": "Copy of ", + "overwriteTooltip": "Overwrite existing file if there's conflict, only works for newly added tasks.", + "rename": "Retry with new name", + "overwrite": "Overwrite existing file", + "pasteFilesHere": "Paste files here", + "clipboardDefaultFileName": "Clipboard {{date}}.png", + "uploadFromClipboard": "Upload from clipboard", + "uploadList": "Upload tasks", + "fileNotMatchError": "The selected file does not match the original file.", + "unknownError": "Unknown error occurs: {{msg}}", + "taskListEmpty": "No upload task.", + "hideTaskList": "Hide the list", + "uploadTasks": "Upload tasks", + "moreActions": "More actions", + "addNewFiles": "Add new files", + "toggleTaskList": "Expand/Collapse the list", + "pendingInQueue": "Pending in queue...", + "preparing": "Preparing...", + "processing": "Processing...", + "progressDescription": "{{uploaded}} uploaded, {{total}} total - {{percentage}}%", + "progressDescriptionFull": "{{uploaded}} uploaded, {{total}} total - {{percentage}}% ({{speed}})", + "progressDescriptionPlaceHolder": " - uploaded", + "uploaded": "Uploaded", + "rootFolder": "Root folder", + "unknownStatus": "Unknown", + "resumed": "Resumed", + "resumable": "Resumable", + "retry": "Retry", + "deleteTask": "Delete task", + "cancelAndDelete": "Cancel and delete", + "selectAndResume": "Select the same file and resume uploading", + "fileName": "Name: ", + "fileSize": "Size: ", + "sessionExpiredIn": "Expires <0>", + "chunkDescription": "({{total}} chunks, {{size}} each)", + "noChunks": "(No chunks)", + "destination": "Destination: ", + "uploadSession": "Upload session: ", + "storagePolicy": "Storage policy: ", + "errorDetails": "Error details: ", + "uploadSessionCleaned": "All upload sessions cleared.", + "hideCompletedTooltip": "Hide completed, failed and cancelled tasks.", + "hideCompleted": "Hide completed tasks", + "addTimeAscTooltip": "Tasks added first are ranked first.", + "addTimeAsc": "Oldest to newest", + "addTimeDescTooltip": "Latest added first are ranked first.", + "addTimeDesc": "Newest to oldest", + "showInstantSpeedTooltip": "Task upload speeds are shown as instantaneous speed.", + "showInstantSpeed": "Instantaneous speed", + "showAvgSpeedTooltip": "Task upload speeds are shown as average speeds.", + "showAvgSpeed": "Average speed", + "cleanAllSessionTooltip": "Clear all outstanding upload sessions on the server side.", + "cleanAllSession": "Clear all upload sessions", + "cleanCompletedTooltip": "Clear completed, failed, and cancelled tasks", + "cleanCompleted": "Clear completed tasks", + "retryFailedTasks": "Retry all failed tasks", + "retryFailedTasksTooltip": "Retry all failed tasks in current queue", + "setConcurrentTooltip": "Set the max number of tasks that can be uploaded simultaneously.", + "setConcurrent": "Set concurrent task limit", + "sizeExceedLimitError": "File size exceeds storage policy limits. (Maximum: {{max}})", + "suffixNotAllowedError": "The storage policy does not support uploading files with this extension.", + "regexpNotAllowedError": "The storage policy does not support uploading files with this name.", + "suffixAllowed": " (Supported:{{supported}})", + "suffixDenied": " (Denied:{{denied}})", + "createUploadSessionError": "Unable to create upload session", + "deleteUploadSessionError": "Unable to delete upload session", + "requestError": "Request failed: {{msg}} ({{url}}).", + "chunkUploadError": "Failed to upload chunk [{{index}}].", + "conflictError": "The upload task for files with the same name is already being processed.", + "chunkUploadErrorWithMsg": "Chunk upload failed: {{msg}}", + "chunkUploadErrorWithRetryAfter": "(Please retry after {{retryAfter}}s)", + "emptyFileError": "Uploading empty files to OneDrive is not supported, please create empty files via the Create File button.", + "finishUploadError": "Unable to complete file upload.", + "finishUploadErrorWithMsg": "Unable to complete file upload: {{msg}}", + "ossFinishUploadError": "Unable to complete file upload: {{msg}} ({{code}})", + "cosUploadFailed": "Upload failed: {{msg}} ({{code}})", + "upyunUploadFailed": "Upload failed: {{msg}}", + "parseResponseError": "Unable to parse response: {{msg}} ({{content}})", + "concurrentTaskNumber": "Concurrent task limit", + "dropFileHere": "Drop file to upload" + }, + "share": { + "statistics": "Statistics", + "expireAt": "Expire <0>", + "expireAfterDownloads": "Expire after {{downloads}} download(s)", + "somebodyShare": "Shared by {{name}}", + "expiredLink": "Expired share", + "sharedBy": "<0>{{nick}} shared $t(share.files, {\"count\": {{num}} }) to you.", + "files": "1 file", + "files_other": "{{count}} files", + "statisticsViews": "$t(share.views, {\"count\": {{views}} })", + "statisticsDownloads": "$t(share.downloads, {\"count\": {{downloads}} })", + "views": "{{count}} view", + "views_other": "{{count}} views", + "downloads": "{{count}} download", + "downloads_other": "{{count}} downloads", + "privateShareTitle": "Private share from {{nick}}", + "enterPassword": "Share password", + "continue": "Continue", + "shareCanceled": "Share link is deleted.", + "listLoadingError": "Failed to load.", + "sharedFiles": "Shared files", + "createdAtDesc": "Newest", + "createdAtAsc": "Oldest", + "noRecords": "No shared files.", + "sourceNotFound": "[Source not exist]", + "expired": "Expired", + "changeToPublic": "Make it public", + "changeToPrivate": "Make it private", + "viewPassword": "View password", + "disablePreview": "Disable preview", + "enablePreview": "Enable preview", + "cancelShare": "Cancel share", + "sharePassword": "Share password", + "readmeError": "Cannot load README: {{msg}}", + "enterKeywords": "Please enter search keywords.", + "searchResult": "Search results", + "sharedAt": "Shared at <0>", + "pleaseLogin": "Please sign in first.", + "cannotShare": "This file cannot be previewed.", + "preview": "Preview", + "incorrectPassword": "Password incorrect.", + "shareNotExist": "Share link is invalid or expired.", + "copyLinkToClipboard": "Copy link to clipboard" + }, + "download": { + "noFilesFound": "No files found", + "filterByName": "Filter by name", + "selectAll": "Select all", + "reverseSelect": "Reverse select", + "cancelTaskConfirm": "Are you sure to cancel this download task?", + "saveChanges": "Save changes", + "failedToLoad": "Failed to load.", + "active": "Active", + "finished": "Finished", + "activeEmpty": "No ongoing download task.", + "finishedEmpty": "No finished download task.", + "loadMore": "Load more", + "taskFileDeleted": "File deleted.", + "unknownTaskName": "[Unknown]", + "taskCanceled": "Download task cancelled, status will be updated later", + "operationSubmitted": "Operation submitted, status will be updated later", + "deleteThisFile": "Delete this file", + "openDstFolder": "Open target folder", + "selectDownloadingFile": "Select files to download", + "cancelTask": "Cancel", + "updatedAt": "Updated at: ", + "uploaded": "Uploaded", + "uploadSpeed": "Upload speed", + "InfoHash": "InfoHash", + "seederCount": "Seeders:", + "seeding": "Seeding: ", + "downloadNode": "Node: ", + "isSeeding": "Yes", + "notSeeding": "No", + "chunkSize": "Chunk size:", + "chunkNumbers": "Chunks", + "taskDeleted": "Task deleted.", + "transferFailed": "Failed to transfer files.", + "downloadFailed": "Download failed: {{msg}}", + "canceledStatus": "Canceled", + "finishedStatus": "Finished", + "pending": "Finished, transfer pending in queue", + "transferring": "Finished, transferring", + "deleteRecord": "Delete record", + "createdAt": "Created at: ", + "unknownSize": "Unknown file size" + }, + "setting": { + "treeView": "Tree view", + "autoExpandTreeView": "Auto expand tree view", + "autoExpandTreeViewDes": "When enabled, the file tree in the sidebar will follow the current directory and expand automatically.", + "syncView": "View settings", + "syncViewDes": "Remember the view settings of each directory and synchronize to the server.", + "syncViewOn": "Sync to server", + "syncViewOff": "Do not sync", + "noAuthenticator": "Add a passkey to sign in using fingerprint, face or USB key.", + "neverUsed": "Never used", + "usedAt": "Last used at <0>", + "passkeyName": "{browser} on {os}", + "versionRetentionMax": "Maximum number of versions, 0 means no limit.", + "versionRetentionEnabledExt": "Enabled file extensions", + "versionRetentionEnabledExtDes": "Press enter to add, leave blank to enable for all files", + "enableVersionRetention": "Enable version retention", + "enableVersionRetentionDes": "If enabled, historical versions of files that meet the conditions will be retained.", + "versionRetention": "Version retention", + "languageDes": "Select the display language and preferred email language.", + "timezoneDes": "Set the display timezone, default is system timezone", + "nickNameDes": "This is your public display name. It can be your real name or a pseudonym.", + "cropAvatar": "Crop avatar", + "preference": "Preference", + "accountCreatedAt": "Created at <0>", + "shoeQr": "Show", + "deviceNothing": "WebDAV is not supported in your user group.", + "connectionInfo": "Connection details", + "proxyTooltip": "Proxy all file download requests.", + "readonlyTooltip": "User can only read files through this account.", + "blockSysFilesUpload": "Block system files upload", + "blockSysFilesUploadTooltip": "When enabled, files starting with <0>. will be blocked from upload.", + "rootFolderIn": "Select <0>", + "createWebDavAccount": "Create WebDAV account", + "editWebDavAccount": "Edit {{name}}", + "seeding": "Seeding", + "awaitSeeding": "Await seeding", + "awaitSeedingDes": "Await seeding completion.", + "downloadTransferDes": "Transfer files to destination.", + "downloadDes": "Download desired files.", + "retryErrorHistory": "Retry error history", + "retryCount": "Retried", + "resumeAt": "Resume at", + "executeDuration": "Execution duration", + "input": "Input", + "output": "Output", + "suspended": " (Suspended)", + "updatedAt": "Updated at", + "taskDetails": "Task details", + "partialSuccessWarning": "Failed to process {{num}} object(s), they were skipped.", + "sendTask": "Send task", + "sendTaskDes": "Send the task to a node to process.", + "downloaded": "Downloaded", + "importingFiles": "Import files", + "importingFilesDes": "Index files and importing them to the specified folder.", + "importedFiles": "Imported files", + "indexedFiles": "Indexed files", + "extractedFiles": "Extracted files", + "extractedFilesSize": "Extracted files size", + "extractingFiles": "Extracting files", + "extractingFilesDes": "Extract all files to the given folder.", + "downloadingZip": "Download archive", + "downloadingZipDes": "Download archive to temporary workspace.", + "progressNotAvailable": "Progress not available yet.", + "uploadedSize": "Relocated size", + "archivedFiles": "Processed files", + "transferredFiles": "Relocated files", + "archivedFilesSize": "Processed file size", + "createArchiveFinishing": "Commit changes for new files", + "indexForArchiveDes": "Index for files to be archived.", + "prepare": "Prepare", + "preparingWorkspaceDes": "Prepare temporary workspace.", + "compressFiles": "Create archive", + "compressFilesDes": "Create archive to temporary workspace.", + "uploadArchiveFileDes": "Transfer archive file to the target folder.", + "uploadWorker": "Upload worker #{{num}}", + "queueToStart": "Queue to start", + "indexingFiles": "Index files", + "indexingFilesDes": "Index all files to be transferred and lock them.", + "transferring": "Transfer", + "committingChanges": "Commit changes", + "autoRefresh": "Auto refresh", + "avatarUpdated": "The avatar has been updated and will take effect with a delay.", + "nickChanged": "Nickname changed and will take effect after refreshing.", + "settingSaved": "Setting saved.", + "themeColorChanged": "Theme color changed.", + "profile": "Profile", + "avatar": "Profile picture", + "uid": "UID", + "nickname": "Display name", + "group": "Group", + "regTime": "Sign up date", + "security": "Password and security", + "profilePage": "Public profile", + "publicShareOnly": "Public share only", + "publicShareOnlyDes": "Only show shares without password on the profile page.", + "allShare": "All shares", + "allShareDes": "Show all shares on the profile page (including password-protected shares). Users still need to enter a password to access them.", + "hideShare": "Hide all shares", + "hideShareDes": "Hide all shares on the profile page.", + "userHideShare": "User has hidden the share list", + "accountPassword": "Password", + "2fa": "2FA authentication", + "enabled": "Enabled", + "disabled": "Disabled", + "appearance": "Appearance", + "themeColor": "Theme color", + "darkMode": "Dark mode", + "syncWithSystem": "System", + "fileList": "File list", + "timeZone": "Timezone", + "webdavServer": "Server", + "userName": "Username", + "manageAccount": "Manage accounts", + "uploadImage": "Upload from file", + "useGravatar": "Use Gravatar ", + "changeNick": "Change nickname", + "originalPassword": "Current password", + "enable2FA": "Enable 2FA authentication", + "disable2FA": "Disable 2FA authentication", + "2faDescription": "Please use any 2FA mobile app or password management software that supports 2FA to scan the QR code to add this site. After scanning, please fill in the 6-digit verification code given by the 2FA app to enable 2FA.", + "inputCurrent2FACode": "Enter current 2FA verification code.", + "timeZoneCode": "IANA timezone code", + "authenticatorRemoved": "Authenticator removed.", + "authenticatorAdded": "Authenticator added.", + "browserNotSupported": "Not supported by current browser or environment.", + "removedAuthenticator": "Remove authenticator", + "removedAuthenticatorConfirm": "Are you sure to remove this authenticator?", + "addNewAuthenticator": "Add a passkey", + "hardwareAuthenticator": "Hardware authenticator", + "copied": "Copied to clipboard.", + "pleaseManuallyCopy": "Current browser does not support, please copy manually.", + "webdavAccounts": "WebDAV Accounts", + "webdavHint": "WebDAV server: {{url}}; Username: {{name}} ; The password is the password of the created account below.", + "annotation": "Annotation", + "rootFolder": "Relative root folder", + "createdAt": "Created at", + "action": "Actions", + "readonlyOn": "Readonly", + "readonlyOff": "Read & Write", + "proxy": "Reverse Proxy", + "none": "None", + "proxied": "Proxied", + "delete": "Delete", + "listEmpty": "No records.", + "createNewAccount": "Create new account", + "taskType": "Task type", + "taskStatus": "Status", + "taskProgress": "Task progress", + "errorDetails": "Error details", + "queueing": "Queueing", + "processing": "Processing", + "failed": "Failed", + "canceled": "Canceled", + "finished": "Finished", + "fileTransfer": "File transfer", + "fileRecycle": "File recycle", + "importFiles": "Import external files", + "transferProgress": "{{num}} files done", + "waiting": "Pending", + "compressing": "Compressing", + "decompressing": "Decompressing", + "downloading": "Downloading", + "indexing": "Indexing", + "listing": "Inserting", + "allShares": "Shared", + "trendingShares": "Trending", + "totalShares": "Created shares", + "fileName": "File name", + "shareDate": "Shared at", + "downloadNumber": "Downloads", + "viewNumber": "Views", + "language": "Language", + "iOSApp": "iOS/iPadOS App", + "connectByiOS": "Connect to <0>{{title}} through iOS/iPadOS devices.", + "downloadOurApp": "Download our APP:", + "fillInEndpoint": "Scan below QR Code with our App (DO NOT use other app to scan):", + "loginApp": "You can start using the App now. If you encounter problems with the QR Code, you can also try to manually enter your username and password to log in.", + "relocateFileTo": "Relocate storage policy to {{policy}} for <0>{{more}}", + "extractFileTo": "Extract <0>{{more}} to <1>", + "createArchiveTo": "Create archive file to <1> for <0>{{more}}", + "importFileTo": "Import files from {{policy}} to <0>" + }, + "vas": { + "points": "Points", + "quota": "Quota", + "used": "Used - {{size}}", + "total": "Total - {{size}}", + "report": "Report abuse", + "validDurationDays": "{{num}} days", + "reportTarget": "Report target", + "reportReason": "Reason", + "reportReasonOptions": ["Copyright infringement", "Harmful content", "Spam", "Other"], + "reportDescription": "Additional description", + "reportAbuseSuccess": "Report submitted." + } +} diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json new file mode 100755 index 0000000..3d12da6 --- /dev/null +++ b/public/locales/en-US/common.json @@ -0,0 +1,106 @@ +{ + "pageNotFound": "Page not found", + "unknownError": "Unknown error", + "errLoadingSiteConfig": "Unable to load site configuration: ", + "newVersionRefresh": "A new version of the current page is available.", + "update": "Update", + "errorDetails": "Details", + "renderError": "There is an error in the page rendering, please try refreshing this page.", + "ok": "OK", + "cancel": "Cancel", + "select": "Select", + "copyToClipboard": "Copy", + "close": "Close", + "dismiss": "Dismiss", + "intlDateTime": "{{val, datetime}}", + "seconds": "s [seconds]", + "minutes": "m [minutes] s [seconds]", + "hours": "H [hours] m [minutes]", + "days": "{{d}} days", + "timeAgoLocaleCode": "en_US", + "forEditorLocaleCode": "en", + "artPlayerLocaleCode": "en", + "requestID": "Request ID: {{id}}", + "object": "Object", + "error": "Error", + "areYouSure": "Are you sure?", + "incorrectSizeInput": "Incorrect size input", + "of": "of", + "rowsPerPage": "Rows per page", + "custom": "Custom", + "enter": "Enter", + "captcha": { + "cap": { + "human": "I'm a human", + "verifying": "Verifying...", + "verified": "You're a human" + } + }, + "errors": { + "401": "Please login.", + "403": "You are not allowed to perform this action.", + "404": "Resource not found.", + "409": "Conflict. ({{message}})", + "40001": "Invalid input parameters ({{message}}).", + "40002": "Upload failed.", + "40003": "Failed to create folder.", + "40004": "Object with the same name already exist.", + "40005": "Signature expired.", + "40006": "Not supported policy type.", + "40007": "Current group have no permission to perform such action.", + "40011": "Upload session not exist or expired.", + "40012": "Invalid chunk index. ({{message}})", + "40013": "Invalid content length. ({{message}})", + "40014": "Exceed batch size limit of getting source link.", + "40015": "Exceed aria2 batch size limit.", + "40016": "Path not found.", + "40017": "This account has been blocked.", + "40018": "This account is not activated.", + "40019": "This feature is not enabled.", + "40020": "Invalid or expired credential.", + "40021": "User not found.", + "40022": "Verification code not correct.", + "40023": "Login session not exist.", + "40024": "Cannot initialize WebAuthn.", + "40025": "Authentication failed.", + "40026": "CAPTCHA code is not correct.", + "40027": "Verification failed, please refresh the page and retry.", + "40028": "Email delivery failed.", + "40029": "This link is invalid.", + "40030": "This link is expired.", + "40032": "This email is already in use.", + "40033": "This account is not activated, activation email has been resent.", + "40034": "This user cannot be activated.", + "40035": "Storage policy not found.", + "40039": "Group not found.", + "40044": "File not found.", + "40045": "Failed to list objects under given folder.", + "40047": "Failed to initialize filesystem.", + "40048": "Failed to create task", + "40049": "File size exceed limit.", + "40050": "File type not allowed.", + "40051": "Insufficient storage quota.", + "40052": "This file name or extension is not allowed.", + "40053": "Cannot perform such action on root folder", + "40054": "File with the same name is already being uploaded under this folder, please cleanup upload sessions.", + "40055": "File metadata mismatch.", + "40056": "Unsupported compressed file type.", + "40057": "Available storage policy has changed, please refresh the file list and add this task again.", + "40058": "This share does not exist or already expired.", + "40069": "Incorrect password.", + "40070": "This share doesn't support preview.", + "40071": "Invalid signature.", + "40073": "File being occupied.", + "40074": "Too many files selected.", + "40079": "Max walked files limit exceeded, try to narrow down the scope of the operation.", + "40081": "Operation not fully succeeded.", + "40082": "Only file owner can perform this action.", + "40080": "Incorrect email or password.", + "50001": "Database operation failed. ({{message}})", + "50002": "Failed to sign the URL or request. ({{message}})", + "50004": "I/O operation failed. ({{message}})", + "50005": "Internal error.", + "50010": "Desired node is unavailable.", + "50011": "Failed to query file metadata." + } +} diff --git a/public/locales/en-US/dashboard.json b/public/locales/en-US/dashboard.json new file mode 100755 index 0000000..29fb1cf --- /dev/null +++ b/public/locales/en-US/dashboard.json @@ -0,0 +1,1623 @@ +{ + "errors": { + "40036": "Default storage policy cannot be deleted.", + "40037": "Some file blob(s) are using this policy, please delete those file blobs first.", + "40038": "{{message}} group(s) are using this policy, please unlink those groups first.", + "40040": "Cannot perform such action on system group.", + "40041": "{{message}} users are still in this group, please delete or unlink those users first.", + "40042": "Cannot change the group of system user group.", + "40043": "Cannot perform such action on default user.", + "40046": "Cannot perform such action on master node.", + "40060": "Slave node cannot send callback request to master, please check master node setting: Basic - Site Information - Site URL, please make sure slave node can access this url. ({{message}})", + "40061": "Mismatched Cloudreve version. ({{message}})", + "40086": "The node is being used by the following storage policies: {{message}}.", + "50008": "Failed to update setting. ({{message}})", + "50009": "Failed to add CORS policy." + }, + "nav": { + "summary": "Summary", + "settings": "Settings", + "basicSetting": "Basic", + "email": "Email", + "transportation": "Transmission", + "appearance": "Appearance", + "image": "Images", + "captcha": "Captcha", + "storagePolicy": "Storage Policy", + "nodes": "Nodes", + "groups": "Groups", + "users": "Users", + "files": "Files", + "entities": "File Blobs", + "shares": "Shares", + "tasks": "Background Tasks", + "remoteDownload": "Remote Download", + "generalTasks": "General", + "title": "Dashboard", + "dashboard": "Cloudreve Dashboard", + "userSession": "User session", + "fileSystem": "Filesystem", + "mediaProcessing": "Media processing", + "queue": "Queue", + "events": "Events", + "server": "Server", + "customProps": "Custom properties", + "abuseReport": "Abuse report" + }, + "summary": { + "generatedAt": "Generated at <0>", + "confirmSiteURLTitle": "Confirm site URL", + "siteURLNotMatch": "The site URL you set does not contains the current one ({{current}}), do you want to add it to the list?", + "setAsPrimary": "Set as primary site URL", + "setAsPrimaryDes": "Set {{current}} as the primary site URL, used for communication with external services and receiving callbacks. Please use a URL that can be accessed by WAN.", + "setAsSecondary": "Add to secondary URLs", + "setAsSecondaryDes": "Add {{current}} to secondary URLs, Cloudreve will automatically select whether to use it based on the URL actually accessed by the user.", + "siteURLDescription": "This setting is very important, make sure it matches the actual URL of your site. You can change this setting in Settings - Basic.", + "ignore": "Ignore", + "changeIt": "Change it", + "trend": "Trend", + "summary": "Summary", + "totalUsers": "Users", + "totalFilesAndFolders": "Files and Folders", + "shareLinks": "Share links", + "totalBlobs": "Blobs", + "homepage": "Homepage", + "github": "GitHub", + "documents": "Documents", + "discordCommunity": "Discord community", + "telegram": "Telegram group", + "forum": "GitHub Discussions", + "buyPro": "Upgrade to Pro", + "publishedAt": "published at <0>", + "licenseExpireAt": "License expiration date", + "permanentLicense": "Permanent license", + "offlineLicenseExpireAy": "Offline license expiration date", + "offlineLicenseDes": "Cloudreve will automatically update the offline license before it expires if your server is connected to the network.", + "licensedDomains": "Licensed domains", + "renew": "Refresh offline license", + "manageLicense": "Manage license", + "volPurchase": "The client VOL license needs to be purchased separately from the <0>License Management Dashboard. The VOL license allows your users to connect to your site using the <1>Cloudreve iOS for free, without the need for users to pay for a subscription for the iOS app itself. After purchasing a license, please click \"Refresh offline license\" below.", + "iosVol": "iOS client volume license (VOL)", + "refreshSuccessfully": "Refreshed successfully.", + "manualRefresh": "Manually refresh offline license", + "manualRefreshDes": "Failed to refresh offline license automatically, please try to log in to the <0>License Management Dashboard to get the latest offline license and paste it below." + }, + "queue": { + "queueName_io_intense": "IO Intensive", + "queueName_io_intenseDes": "Queue for handling large amounts of IO operations, including: storage policy transfer, decompression, compression.", + "queueName_media_meta": "Media Metadata Extraction", + "queueName_media_metaDes": "Used to extract metadata from media files.", + "queueName_recycle": "Blob Recycling", + "queueName_recycleDes": "Used to delete expired file blobs.", + "queueName_thumb": "Thumbnail Generation", + "queueName_thumbDes": "Used to generate thumbnails for files.", + "queueName_remote_download": "Remote Download", + "queueName_remote_downloadDes": "Used to process remote download tasks.", + "failed": "Failed ({{count}})", + "success": "Success ({{count}})", + "suspending": "Suspended ({{count}})", + "busyWorker": "Processing ({{count}})", + "submited": "Submitted ({{count}})", + "editQueueSettings": "Edit queue settings - {{name}}", + "workerNum": "Worker threads", + "workerNumDes": "Maximum number of tasks to be executed in parallel in the task queue", + "maxExecution": "Maximum execution time", + "maxExecutionDes": "Maximum execution time (seconds) for a task, after which the task will be terminated.", + "backoffFactor": "Backoff factor", + "backoffFactorDes": "Growth factor for task retry time intervals.", + "backoffMaxDuration": "Maximum backoff time", + "backoffMaxDurationDes": "Maximum backoff time (seconds) for task retries.", + "maxRetry": "Maximum retries", + "maxRetryDes": "Maximum number of retries after a task failure.", + "retryDelay": "Retry delay", + "retryDelayDes": "Initial delay time (seconds) for task retries." + }, + "settings": { + "headlessFooter": "Landing page footer", + "headlessFooterDes": "Custom HTML content displayed at the bottom of the login, sign up and callback result pages.", + "headlessBottom": "Landing page bottom", + "headlessBottomDes": "Custom HTML content displayed at the bottom of the login, sign up and callback result pages.", + "customHTML": "Custom HTML", + "customHTMLDes": "Insert custom HTML content at the preset position of the site.", + "sidebarBottom": "Sidebar bottom", + "sidebarBottomDes": "Custom HTML content displayed at the bottom of the sidebar.", + "addNavItem": "Add navigation item", + "customNavItems": "Custom sidebar items", + "customNavItemsDes": "You can add custom items to the sidebar, and users will be redirected to the corresponding link when clicked.", + "navItemUrl": "Link", + "iconifyNamePlaceholder": "Iconify icon identifier, e.g. fluent:home-24-regular", + "imageUrl": "Image URL", + "iconifyName": "Iconify icon name", + "oidc": "OpenID Connect (OIDC)", + "oidcDes": "OpenID Connect (OIDC) is an open authentication protocol for identity verification between different systems. After creating an application in a third-party identity platform, please add <0>{{url}} to the \"Redirect URI\" field. For more details, please refer to the <1>documentation.", + "clientID": "Client ID", + "clientIDDes": "The client ID of the application created in the third-party identity platform.", + "clientSecret": "Client secret", + "clientSecretDes": "The client secret of the application created in the third-party identity platform.", + "scope": "Scope", + "scopeDes": "Additional scopes to request, separated by commas <0>,. By default, Cloudreve will request <0>openid, <0>email and <0>profile; no need to repeat here.", + "oidcWellknown": "OIDC Wellknown Config", + "oidcWellknownDes": "Wellknown document of the third-party identity platform, containing the configuration information of OpenID Connect.", + "importFromWellknown": "Import from URL", + "importOidc": "Import OIDC Wellknown Config", + "oidcWellknownUrl": "Wellknown URL", + "oidcWellknownUrlDes": "URL of the wellknown document of the third-party identity platform, such as <0>https://accounts.google.com/.well-known/openid-configuration.", + "resetUrl": "Reset URL", + "exceedToleranceDays": "Tolerance days for banning", + "activateUrl": "Activate URL", + "domainNotLicensed": "Domain not licensed", + "domainNotLicensedDes": "The site URL you set contains an unauthorized domain, please add this subdomain in the <0>License Management Dashboard and click the button below to update the license and try again.", + "showSettings": "Show settings", + "perPage": "{{num}} per page", + "noNodes": "No nodes available.", + "extractMediaMeta": "Extract media metadata", + "extractMediaMetaDes": "Extract media file metadata for display and search. By default, non-local storage policies will only use the \"Native in storage policy\" generator. You can extend the thumbnail capability of third-party storage policies by enabling the \"Extractor proxy\" feature in storage policy setting page. For more details, please refer to the <0>documentation.", + "exif": "EXIF", + "exifDes": "Extract EXIF metadata from image files for display and search.", + "music": "Music metadata", + "musicDes": "Extract metadata from music files, including title, artist, album, etc.", + "ffprobe": "FFprobe", + "ffprobeDes": "Use FFprobe to extract metadata from video and audio files.", + "maxSizeLocal": "Max file size (Local storage)", + "maxSizeLocalDes": "Maximum file size for metadata extraction when the file is stored in local storage policy, 0 means no limit.", + "maxSizeRemote": "Max file size (Remote storage)", + "maxSizeRemoteDes": "Maximum file size for metadata extraction when the file is stored in third-party storage policies, 0 means no limit.", + "exifBruteForce": "Use brute force if necessary", + "exifBruteForceDes": "When enabled, the entire file will be scanned to find EXIF data if it cannot be found in the standard header location. This may increase processing time but can find EXIF data in non-standard locations.", + "musicCover": "Music cover", + "musicCoverDes": "Extract album cover from music files, supports ID3 (v1, 2.2, 2.3 and 2.4) container. This generator depends on any other image thumbnail generator (Cloudreve built-in or VIPS).", + "geocoding": "Geocoding", + "geocodingDes": "Get address information using Mapbox service based on coordinate information recorded in media EXIF.", + "mapboxAK": "Mapbox API Key", + "mapboxAKDes": "API key created in Mapbox console.", + "geocodingDependencyWarning": "Geocoding generator depends on EXIF generator, please enable EXIF generator.", + "notAppliedToNativeGenerator": "{{prefix}}Not applicable to native generator of storage policies.", + "notAppliedToOneDriveNativeGenerator": "{{prefix}}Not applicable to native generator of OneDrive or SharePoint storage policies.", + "fileBlobMargin": "File Blob URL Cache Margin (seconds)", + "fileBlobMarginDes": "When the same file Blob is requested multiple times, if the initial URL has a remaining validity period greater than the margin, the same URL will be reused.", + "fileBlobTimeout": "File Blob URL TTL (seconds)", + "fileBlobTimeoutDes": "Limit the validity period of the temporary URL obtained when users open or download files, only applicable to local storage policies, WebDAV, or files downloaded through Cloudreve relaying.", + "wopiSessionTimeout": "WOPI session TTL (seconds)", + "wopiSessionTimeoutDes": "Limit the validity period of a single session when users edit files using WOPI. After expiration, users need to reopen the file from Cloudreve.", + "oauthRefresh": "Refresh interval for OAuth storage policy", + "oauthRefreshDes": "Set how often to refresh the OAuth credentials for storage policies (e.g. OneDrive) that require OAuth. This can prevent credential expiration due to long periods of inactivity", + "transitParallelNum": "Max parallel relaying transfers", + "transitParallelNumDes": "The maximum number of parallel uploads when a single server-side file relaying transfer task contains multiple files.", + "failedChunkRetry": "Maximum number of retries for chunk upload failures", + "failedChunkRetryDes": "The maximum number of retries for chunk upload failures, only applicable to server-side uploads or relaying chunk upload.", + "cacheChunks": "Cache streaming chunks", + "cacheChunksDes": "If enabled, the chunk data will be cached in the system temporary directory during streaming transfer, so that it can be used for retrying failed chunk uploads;\n If disabled, streaming transfer chunk uploads will not take up extra disk space, but the entire upload will fail immediately if the chunk upload fails.", + "folderPropsTimeout": "Folder statistics cache TTL (seconds)", + "folderPropsTimeoutDes": "The validity period of the result cache when users calculate folder statistics (size, number of files, etc.).", + "slaveAPIExpiration": "Slave API signature TTL (seconds)", + "slaveAPIExpirationDes": "The signature validity period used by the master node when accessing the slave node API.", + "uploadSessionTimeout": "Upload session TTL (seconds)", + "uploadSessionDes": "In a valid upload session period, for supported storage policies, users can resume unfinished tasks. The maximum value that can be set is limited by the rules of different storage policy providers.", + "archiveTimeout": "Server-side batch download session TTL (seconds)", + "advanceOptions": "Advanced options", + "emojiOptions": "Emoji options", + "addCategorize": "Add a category", + "category": "Category", + "searchQuery": "File categorize query", + "importWopi": "Import WOPI app settings", + "wopiEndpoint": "WOPI Discovery Endpoint", + "wopiDes": "Extend Cloudreve's online preview and editing capabilities by integrating with online document processing systems that support the WOPI protocol. Please fill in the WOPI service discovery address here, such as <0>https://example.com/hosting/discovery. For more details, please refer to <1>documentation.", + "embeddedWebpageViewer": "Embedded Webpage Viewer", + "wopiViewer": "WOPI Application", + "ext": "Extension", + "invalidWopiActionMapping": "Invalid WOPI action mapping", + "woapiActionMapping": "WOPI action mappings", + "drawioHost": "DrawIO instance", + "drawioHostDes": "You can use URL for self-hosted instance.", + "openInNew": "Open in new window", + "openInNewDes": "If checked, it will directly pop up a new tab to open this application.", + "maxSize": "Max file size", + "maxSizeDes": "The maximum file size supported by this application. 0 means no limit. If the file exceeds this size, it will still be opened, but users will be warned.", + "srcEncodedVar": "URL-encoded file Blob temporary access URL", + "srcVar": "File blob temporary access URL", + "srcBase64Var": "Base64-encoded File blob temporary access URL", + "nameEncodedVar": "URL-encoded file name", + "versionEntityVar": "The Blob ID of the opened file version, empty means the latest version.", + "fileIdVar": "File ID", + "userIdVar": "User ID, empty when not logged in.", + "userDisplayNameVar": "URL-encoded user display name.", + "fileViewers": "File applications", + "addViewer": "Add an application", + "viewerGroupTitle": "Application group #{{index}}", + "viewerType": "Type", + "viewerPlatform": "Platform", + "viewerPlatformDes": "Select the corresponding platform to display the application only on that platform.", + "viewerPlatformPC": "Desktop", + "viewerPlatformMobile": "Mobile", + "viewerPlatformAll": "All", + "displayName": "Display name", + "displayNameDes": "Display name to users, support i18next key.", + "viewerEnabled": "Enabled", + "newFileAction": "New file actions", + "newFileActionDes": "By adding this mapping, users will see this application option when clicking the \"New\" button.", + "addNewFileAction": "Add a mapping", + "builtinViewerType": "Builtin application", + "wopiViewerType": "WOPI", + "customViewerType": "Customized", + "nMapping": "{{num}} mapping(s)", + "editViewerTitle": "Edit {{name}}", + "builtInIconUrlDes": "This built-in application has a default icon. When the icon URL is left blank, the default icon will be used.", + "viewerUrl": "Application URL", + "viewerUrlDes": "URL of customized application, <0>magical variables are supported.", + "addIcon": "Add an icon", + "exts": "Extension list", + "icon": "Icon", + "iconUrl": "Icon URL", + "iconColor": "Color", + "iconColorDark": "Color (Dark mode)", + "fileIcons": "File icons", + "builtinIcon": "Built in", + "mimeMapping": "MIME type mapping", + "mimeMappingDes": "MIME type mapping in JSON format, where the key is the file extension and the value is the MIME type. Cloudreve will determine the file MIME type based on the file extension and this setting.", + "mapProvider": "Map provider", + "mapProviderDes": "Map provider used to display media location information.", + "mapGoogle": "Google Maps (Leaflet)", + "mapOpenStreetMap": "OpenStreetMap (Leaflet)", + "mapboxMap": "OpenStreetMap (Mapbox)", + "mapboxAccessToken": "Mapbox Access Token", + "mapboxAccessTokenDes": "Public access token created in <0>Mapbox Console.", + "tileType": "Default tile type", + "tileTypeDes": "Default tile type for Google Maps.", + "tileTypeTerrain": "Terrain", + "tileTypeSatellite": "Satellite", + "tileTypeGeneral": "Regular", + "maxPageSize": "Max page size", + "maxPageSizeDes": "Limit the maximum number of files that users can adjust per page.", + "maxRecursiveSearch": "Max recursive search count", + "maxRecursiveSearchDes": "The maximum number of recursive searches allowed when searching for files. If the number of files searched exceeds this limit, the search will stop and warn the user.", + "maxBatchSize": "Max batch size", + "maxBatchSizeDes": "The maximum number of files that users can operate in a batch, only the top-level will be counted, and the number of files under subdirectories will not be counted.", + "defaultPagination": "Pagination method for file list", + "cursorPagination": "Cursor pagination", + "cursorPaginationDes": "More files will be automatically loaded when the user scrolls to the bottom. This method performs better for large file lists, but the total number of pages cannot be seen.", + "offsetPagination": "Offset pagination", + "offsetPaginationDes": "Pagination navigation will be displayed at the bottom of the page; users can see the total number of pages and jump to a specific page. This method performs slightly worse for large file lists.", + "defaultPaginationDes": "Cursor pagination will be forced to use when searching, regardless of the above settings.", + "publicResourceMaxAge": "Static resource cache max age (seconds)", + "publicResourceMaxAgeDes": "The max age of cache for publicly accessible static resources (e.g. files, thumbnails and user profile pictures).", + "cronDes": "{{des}} A correct <0>Cron syntax is required here. Restarting Cloudreve is needed to take effect.", + "entityCollectInterval": "File Blob recycle interval", + "entityCollectIntervalDes": "Set how often to scan and delete expired file blobs.", + "trashBinInterval": "Trash bin scan interval", + "trashBinIntervalDes": "Set how often to scan and delete expired files in the trash bin.", + "logtoName": "Sign-in method name", + "logtoNameDes": "Name of the sign-in method, displayed to users. Default is \"SSO\", support i18next key.", + "logtoDirectSSO": "Direct sign-in", + "logtoDirectSSODes": "If you want to skip the Logto login screen and directly jump to the third-party login or SSO, please fill in the identifier of the social connector here. For details, please refer to <0>Logto documentation.", + "logtoEndpoint": "Logto endpoint", + "logtoEndpointDes": "The Logto endpoint url obtained from the application management panel, which can be a self-hosted instance.", + "logtoKey": "Application secret", + "logtoKeyDes": "Application secret created in the application management page.", + "logtoAppIDDes": "Application ID created in the application management page.", + "logto": "Logto", + "logtoDes": "With <0>Logto, you can achieve more third-party platform sign-ins, such as Apple, GitHub, Microsoft Entra ID, Google, SMS, etc. Please create a \"Traditional Web Application\" in the Logto management portal and add <1>{{url}} to the \"Redirect URIs\".", + "thirdPartySignIn": "Third-party sign-in", + "logo": "LOGO", + "logoDes": "URL of the LOGO, please provide different logos for dark and light modes.", + "dark": "Dark mode", + "light": "Light mode", + "tosUrl": "Terms of service URL", + "tosUrlDes": "Will be displayed in the footer of the login or registration page, leave it blank to not display.", + "privacyUrl": "Privacy policy URL", + "privacyUrlDes": "Will be displayed in the footer of the login or registration page, leave it blank to not display.", + "addSecondary": "Add secondary site URL", + "secondarySiteURL": "Secondary", + "secondaryDes": "You can also add other secondary URLs, Cloudreve will automatically select whether to use it based on the URL actually accessed by the user.", + "primarySiteURL": "Primary", + "primarySiteURLDes": "Primary site URL is used for communication with external services and receiving callbacks (e.g. storage provider), please use a URL that can be accessed by WAN.", + "revert": "Revert changes", + "saved": "Settings saved.", + "save": "Save", + "basicInformation": "Basic Information", + "mainTitle": "Site name", + "mainTitleDes": "Name of the instance.", + "siteDescription": "Site description", + "siteDescriptionDes": "Description of the website, which may be displayed in the shared page summary.", + "siteURL": "Site URL", + "customFooterHTML": "Custom footer HTML", + "customFooterHTMLDes": "Custom HTML code inserted at the bottom of the page.", + "announcement": "Announcement", + "announcementDes": "Announcements displayed to logged-in users. Blank value will not be displayed. After this content is changed, all users will see the announcement again.", + "supportHTML": "Enter HTML or plain text.", + "branding": "Branding", + "smallIcon": "Small icon", + "smallIconDes": "URL of the small icon, ico or svg format. This icon will also be shown in browser tabs, bookmarks and desktop shortcuts.", + "mediumIcon": "Medium icon", + "mediumIconDes": "URL of the medium icon, prefer size at 192x192, png format.", + "largeIcon": "Large icon", + "largeIconDes": "URL of the large icon, prefer size at 512x512, png format. This icon will also be shown while switching account in iOS app.", + "displayMode": "Display mode", + "displayModeDes": "The display mode of a PWA application after it's installed.", + "themeColor": "Theme color", + "themeColorDes": "CSS color value that affect the color of the status bar on the PWA launch screen, the status bar in the content page, and the address bar.", + "backgroundColor": "Background color", + "backgroundColorDes": "CSS color value.", + "hint": "Hint", + "webauthnNoHttps": "Web Authn requires your website to be HTTPS enabled, and please confirm that in Settings - Basic - Site URL also uses HTTPS.", + "accountManagement": "Accounts", + "allowNewRegistrations": "Accept new signups", + "allowNewRegistrationsDes": "After disabled, no new users can be registered, unless manually added by admins.", + "emailActivation": "Email activation", + "emailActivationDes": "After enabled, new users need to click the activation link in the email to complete signups. Please make sure the <0>email delivery settings are correct, otherwise the activation email will not be delivered.", + "captchaForSignup": "Captcha for signups", + "captchaForSignupDes": "Whether to enable the captcha for signups.", + "captchaForLogin": "Captcha for logins", + "captchaForLoginDes": "Whether to enable the captcha for logins.", + "captchaForReset": "Captcha for resetting password", + "captchaForResetDes": "Whether to enable the captcha for resetting password.", + "captchaForAbuseReport": "Captcha for abuse report", + "captchaForAbuseReportDes": "Whether to enable the captcha for abuse report.", + "webauthnDes": "Whether to allow users to sign-in with hardware authentication devices, such as: face, fingerprint or USB key; the site must enable HTTPS.", + "webauthn": "Sign-in with Passkeys", + "defaultSymbolics": "Default share shortcuts", + "defaultSymbolicsDes": "Default share shortcuts in the root directory of new users. Please search for share links by ID, you can see the ID on the left side of the <0>share list.", + "searchShare": "Search share ID...", + "defaultGroup": "Default group", + "defaultGroupDes": "The initial user group after user registration.", + "testMailSent": "Test email is sent.", + "testSMTPSettings": "Test SMTP settings", + "testSMTPTooltip": "Cloudreve will use your current SMTP settings to send a test email, no need to save settings before testing.", + "recipient": "Recipient", + "send": "Send", + "smtp": "SMTP", + "senderName": "Sender name", + "senderNameDes": "The sender's name displayed in the email.", + "senderAddress": "Sender address", + "senderAddressDes": "Email address of the sender.", + "smtpServer": "SMTP server", + "smtpServerDes": "SMTP server address, without port number.", + "smtpPort": "SMTP Port", + "smtpPortDes": "SMTP server port number.", + "smtpUsername": "SMTP Username", + "smtpUsernameDes": "SMTP username, generally the same as the sender address.", + "smtpPassword": "SMTP Password", + "smtpPasswordDes": "Password of the sender mailbox.", + "replyToAddress": "Reply to address", + "replyToAddressDes": "The mailbox used to receive reply emails when users reply to emails sent by the system.", + "enforceSSL": "Enforce SSL connection", + "enforceSSLDes": "Whether to enforce an SSL encrypted connection. If you cannot send emails, you can turn this off and Cloudreve will try to use STARTTLS and decide whether to use encrypted connections.", + "smtpTTL": "SMTP connection TTL (seconds)", + "smtpTTLDes": "SMTP connections established during the TTL period will be reused by new mail delivery requests.", + "emailTemplates": "Email Templates", + "activateNewUser": "Activate new user", + "resetPassword": "Reset password", + "sendTestEmail": "Send test email", + "transportation": "Transmission", + "workerNum": "Number of worker", + "workerNumDes": "The maximum number of tasks to be executed in parallel by the master node task queue, restarting Cloudreve is needed to take effect.", + "tempFolder": "Temp folder", + "tempFolderDes": "Used to store temporary files generated by tasks such as decompression, compression, etc.", + "textEditMaxSize": "Max size of editable document files", + "textEditMaxSizeDes": "The maximum size of a document file that can be edited online, files beyond this size cannot be edited online. This setting applies to online Web editors such as plain text, code and Office documents (WOPI).", + "resetConnection": "Reset connection after failed upload", + "resetConnectionDes": "If enabled, the server will force to reset the connection if upload verification fails.", + "batchDownload": "Batch download", + "previewURL": "Preview URL", + "cannotDeleteDefaultTheme": "Cannot delete default theme.", + "themeConfig": "Configs", + "actions": "Actions", + "wrongFormat": "Incorrect format.", + "avatar": "Avatar", + "gravatarServer": "Gravatar server", + "gravatarServerDes": "URL of Gravatar mirror server.", + "avatarFilePath": "Avatar file path", + "avatarFilePathDes": "Path to save user's avatar files, relative to the Cloudreve data folder.", + "avatarSize": "Max avatar file size", + "avatarSizeDes": "Maximum size of avatar files that users can upload.", + "avatarImageSize": "Image size (px)", + "avatarImageSizeDes": "Selected profile image will be resized to the given size, in pixels.", + "filePreview": "File Preview", + "thumbnails": "Thumbnails", + "thumbnailDoc": "For more information about thumbnail, see the <0>document.", + "thumbnailDocLink": "https://docs.cloudreve.org/usage/thumbnails", + "thumbnailBasic": "Basic", + "generators": "Thumbnail generators", + "thumbMaxSize": "Maximum original file size", + "thumbMaxSizeDes": "The maximum original file size for which thumbnails can be generated, thumbnails will not be generated if files exceed this size.", + "generatorProxyWarning": "By default, non-local storage policies will only use the \"Native in storage policy\" generator. You can extend the thumbnail capability of third-party storage policies by enabling the \"Generator proxy\" feature in storage policy setting page. For more details, please refer to the <0>documentation.", + "policyBuiltin": "Native in storage policy", + "policyBuiltinDes": "Use the native API from storage provider to process thumbnails. For local and S3 policy, this generator is not available and will automatically fallback to other generators. For other storage policies, please go to storage policy setting page to configure this generator.", + "cloudreveBuiltin": "Cloudreve built-in", + "cloudreveBuiltinDes": "Only images in PNG, JPEG, GIF formats are supported using Cloudreve's built-in image processing capabilities.", + "libreOffice": "LibreOffice", + "libreOfficeDes": "Use LibreOffice to generate thumbnails for Office documents. This generator depends on any other image thumbnail generator (Cloudreve built-in or VIPS).", + "libraw": "LibRaw / DCRaw", + "librawDes": "Use LibRaw's DCRaw sample program, or the original DCRaw executable to generate thumbnails for RAW images.", + "vips": "VIPS", + "vipsDes": "Use libvips to process thumbnail images, support more image formats, and consume less resources.", + "thumbDependencyWarning": "LibreOffice or music cover generator depend on Cloudreve built-in or VIPS generators, please enable either one.", + "ffmpeg": "FFmpeg", + "ffmpegDes": "Use FFmpeg to generate video thumbnails.", + "executable": "Executable", + "executableDes": "The path or command of the third-party generator executable.", + "executableTest": "Test", + "executableTestSuccess": "Generator works, version: {{version}}", + "generatorExts": "Available extensions", + "generatorExtsDes": "List of available file extensions for this generator, please use comma , to separate multiple ones.", + "ffmpegSeek": "Thumbnail capture location", + "ffmpegSeekDes": "Define the thumbnail interception time, it is recommended to choose a smaller value to speed up the generation process. If the actual length of the video is exceeded, the thumbnail generation will fail.", + "ffmpegExtraArgs": "Extra input arguments", + "ffmpegExtraArgsDes": "Extra input arguments for calling FFmpeg.", + "generatorProxy": "Generator proxy", + "enableThumbProxy": "Use generator proxy", + "proxyPolicyList": "Enabled storage policy", + "proxyPolicyListDes": "Multi-selectable. If enabled, files whose storage policy does not support native generation, its thumbnails will be proxy generated by the Cloudreve.", + "thumbWidth": "Max width", + "thumbHeight": "Max height", + "thumbSuffix": "Blob file suffix", + "thumbSuffixDes": "The suffix appended to the original Blob file name for the generated thumbnail, ", + "thumbFormat": "Image format", + "thumbFormatDes": "Preferred image format, if the generator does not support it, it will automatically downgrade to jpg format.", + "thumbQuality": "Quality", + "thumbQualityDes": "Compression quality percentage, valid only for jpg and webp format.", + "thumbGC": "Run GC after thumb generated", + "captcha": "Captcha", + "captchaType": "Captcha type", + "captchaTypeDes": "Select captcha type and provider.", + "plainCaptcha": "Plain graphic", + "reCaptchaV2": "reCAPTCHA V2", + "turnstile": "Cloudflare Turnstile", + "turnstileSiteKey": "Site Key", + "turnstileSiteKSecret": "Secret", + "cap": "Cap", + "capInstanceURL": "Instance URL", + "capInstanceURLDes": "The URL of your self-hosted Cap server. For more details, see the <0>standalone mode documentation.", + "capSiteKey": "Site Key", + "capSiteKeyDes": "The site key from your Cap server dashboard.", + "capSecretKey": "Secret Key", + "capSecretKeyDes": "The secret key from your Cap server dashboard.", + "capAssetServer": "Asset Server Source", + "capAssetServerDes": "Choose the source for loading Cap captcha static assets. Using self-deployed server requires setting environment variables on the server side, please refer to <0>enable asset server.", + "capAssetServerJsdelivr": "jsDelivr CDN", + "capAssetServerUnpkg": "unpkg CDN", + "capAssetServerInstance": "Self-hosted server", + "captchaProvider": "Captcha provider", + "captchaWidth": "Width", + "captchaHeight": "Height", + "captchaLength": "Length", + "captchaLengthDes": "The length of characters in the captcha.", + "captchaMode": "Mode", + "captchaModeNumber": "Numbers", + "captchaModeLetter": "Letters", + "captchaModeMath": "Math", + "captchaModeNumberLetter": "Numbers + Letters", + "captchaElement": "Elements inside of the captcha image.", + "complexOfNoiseText": "Complex of noise text", + "complexOfNoiseDot": "Complex of noise dots", + "showHollowLine": "Show hollow lines", + "showNoiseDot": "Show noise dots", + "showNoiseText": "Show noise text", + "showSlimeLine": "Show slime lines", + "showSineLine": "Show sine lines", + "siteKey": "Site Key", + "siteKeyDes": "You can find it at <0>App Management Page.", + "siteSecret": "Secret", + "siteSecretDes": "You can find it at <0>App Management Page.", + "secretID": "SecretId", + "secretIDDes": "You can find it at <0>Access Management Page.", + "secretKey": "SecretKey", + "secretKeyDes": "You can find it at <0>Access Management Page.", + "tCaptchaAppID": "APPID", + "tCaptchaAppIDDes": "You can find it at <0>Captcha Management Page.", + "tCaptchaSecretKey": "App Secret Key", + "tCaptchaSecretKeyDes": "You can find it at <0>Captcha Management Page.", + "staticResourceCache": "Public static resources cache", + "staticResourceCacheDes": "Max age of cache for publicly accessible static resources (e.g. local policy source link, download link).", + "creditSystem": "Credit system", + "creditAndVAS": "Credit and VAS", + "enableCredit": "Enable credit system", + "enableCreditDes": "Enable credit system to allow users to set prices for their share links.", + "creditPrice": "Credit price", + "creditPriceDes": "Price for recharging credit points with money (in minimum currency unit). Fill 0 to disable credit recharge.", + "shareScoreRate": "Share owner's commission rate", + "shareScoreRateDes": "Percentage (1-100) of credit points that share owners receive when their share links are purchased.", + "cronNotifyUser": "Scan interval for over-limit users", + "cronNotifyUserDes": "Scan and send email reminders to over-limit users, ", + "cronBanUser": "User ban schedule", + "cronBanUserDes": "Scan and ban users exceeding storage limits and buffer periods", + "anonymousPurchase": "Anonymous purchase", + "anonymousPurchaseDes": "Allow non-logged-in users to purchase share links directly", + "shopNavEnabled": "Show Shop Navigation", + "shopNavEnabledDes": "Display 'Shop' items in the sidebar navigation", + "paymentSettings": "Payment settings", + "currencyCode": "Currency code", + "currencyCodeDes": "Three-letter currency code (e.g., USD, CNY, EUR).", + "currencySymbol": "Currency symbol", + "currencySymbolDes": "Currency symbol to display (e.g., $, Â¥, €).", + "currencyUnit": "Currency unit", + "currencyUnitDes": "Minimum currency unit (e.g., 100 for dollars/cents).", + "paymentProviders": "Payment providers", + "providerName": "Provider name, used to display to users.", + "providerType": "Provider type", + "providerKey": "Secret key", + "selectCurrency": "Select common currency", + "addPaymentProvider": "Add payment provider", + "stripeProvider": "Stripe", + "weixinProvider": "WeChat Pay", + "alipayProvider": "Alipay", + "customProvider": "Custom payment provider", + "customProviderDes": "Create a plugin to connect to other payment gateways, see <0>documentation for further details.", + "providerKeyDes": "API secret key from Stripe.", + "storageProductSettings": "Storage product", + "storageProductsDes": "Configure products that users can purchase to extend their storage space.", + "addStorageProduct": "Add SKU", + "editStorageProduct": "Edit SKU", + "storageSize": "Storage size", + "storageSizeBytes": "Size included in this SKU", + "duration": "Duration", + "durationSeconds": "Duration in seconds (e.g. 2592000 for 30 days)", + "price": "Price", + "priceInUnits": "Price (in minimum currency unit)", + "priceInUnitsDes": "Price will be displayed as:", + "chipLabel": "Label (optional)", + "chipLabelHelp": "A short text label displayed next to the product name", + "usePoints": "Allow paying with points", + "points": "Points", + "pointsHelp": "Number of points required to purchase this product", + "pointsUnit": "points", + "groupProductSettings": "Group product", + "groupProductsDes": "Configure products that users can purchase to join specific user groups.", + "addGroupProduct": "Add group product", + "editGroupProduct": "Edit group product", + "groupId": "Group ID", + "groupIdHelp": "The user group to upgrade to after purchasing this product.", + "description": "Description", + "descriptionHelp": "Enter features or benefits, one per line", + "receiptEmailTemplate": "Payment receipt template", + "receiptEmailTemplateDes": "Email template sent to users when a payment is confirmed.", + "activationEmailTemplate": "Account activation template", + "activationEmailTemplateDes": "Email template sent to users to activate their accounts.", + "quotaExceededEmailTemplate": "Storage quota exceeded template", + "quotaExceededEmailTemplateDes": "Email template sent to users when they exceed their storage quota.", + "resetPasswordEmailTemplate": "Password reset template", + "resetPasswordEmailTemplateDes": "Email template sent to users when they request a password reset.", + "preferredLanguage": "Preferred language", + "setAsPreferredLanguage": "Set as preferred language", + "setAsPreferredLanguageDes": "This language will be used as the default email template if the user's language preference cannot be determined.", + "alreadyAsPreferredLanguageDes": "This language is already set as the preferred language. If the user's language preference cannot be determined, this email template will be used.", + "addLanguage": "Add language", + "removeLanguage": "Remove language", + "removeLanguageBtn": "Remove language", + "cannotRemovePreferredLanguageDes": "Cannot remove the preferred language. Please set another language as the preferred language and try again.", + "languageCodeDes": "Please select the language you want to add.", + "emailSubject": "Email saveChanges", + "emailSubjectDes": "The subject line of the email. You can use <0>magic variables to customize the email subject.", + "emailBody": "Email body", + "emailBodyDes": "HTML content of the email. You can use <0>magic variables to customize the email content.", + "orderTitle": "Order title", + "themeOptions": "Theme options", + "themeOptionsDes": "Configure custom theme options for your site. These themes will be available for users to select in their preferences.", + "primaryColor": "Primary color", + "secondaryColor": "Secondary color", + "primaryColorDark": "Primary color (Dark)", + "secondaryColorDark": "Secondary color (Dark)", + "addThemeOption": "Add theme option", + "editThemeOption": "Edit theme option", + "invalidThemeConfig": "Invalid theme configuration. Please check your JSON syntax.", + "themeConfiguration": "Theme configuration", + "themePreview": "Theme preview", + "lightTheme": "Light theme", + "darkTheme": "Dark theme", + "previewTitle": "Preview title", + "previewTextField": "Input field", + "previewPrimary": "Primary", + "invalidThemePreview": "Invalid theme configuration for preview", + "duplicateThemeColor": "A theme with this primary color already exists. Please choose a different color.", + "themeDes": "Full available configurations can be referred at <0>Default theme viewer - Material-UI.", + "defaultTheme": "Default", + "auditLog": "Events", + "auditLogDes": "Configure which events should be recorded. Some events might be used by the system to provide additional features, e.g. file activity and sign in activity.", + "systemEvents": "System events", + "systemEventsDes": "Events related to system operations and status.", + "userEvents": "User events", + "userEventsDes": "Events related to user accounts, authentication, and profile changes.", + "fileEvents": "File events", + "fileEventsDes": "Events related to file operations such as upload, download, and modification.", + "shareEvents": "Share events", + "shareEventsDes": "Events related to file sharing and link access.", + "versionEvents": "Version events", + "versionEventsDes": "Events related to file version management.", + "mediaEvents": "Media events", + "mediaEventsDes": "Events related to media processing such as thumbnail generation.", + "filesystemEvents": "Filesystem events", + "filesystemEventsDes": "Events related to filesystem operations such as mounting and archive handling.", + "webdavEvents": "WebDAV events", + "webdavEventsDes": "Events related to WebDAV account management and access.", + "paymentEvents": "Payment events", + "paymentEventsDes": "Events related to payments, points, and membership management.", + "emailEvents": "Email events", + "emailEventsDes": "Events related to email sending and notifications.", + "toggleAll": "Toggle all", + "toggleAllDes": "Enable or disable all events in this category.", + "event": { + "file_imported": "External file imported", + "server_start": "Server start", + "user_signup": "User signup", + "email_sent": "Email sent", + "user_activated": "User activated", + "user_login_failed": "Login failed", + "user_login": "User login", + "user_token_refresh": "Token refresh", + "file_create": "File created", + "file_rename": "File renamed", + "set_file_permission": "Permission changed", + "entity_uploaded": "File uploaded or updated", + "entity_downloaded": "File downloaded", + "copy_from": "Copy from", + "copy_to": "Copy to", + "move_to": "Move to", + "delete_file": "File deleted", + "move_to_trash": "Move to trash", + "share": "Share created", + "share_link_viewed": "Share link viewed", + "set_current_version": "Set current version", + "delete_version": "Delete version", + "thumb_generated": "Thumbnail generated", + "live_photo_uploaded": "Live photo uploaded", + "update_metadata": "Metadata updated", + "edit_share": "Share edited", + "delete_share": "Share deleted", + "mount": "Mount", + "relocate": "Relocate", + "create_archive": "Create archive", + "extract_archive": "Extract archive", + "webdav_login_failed": "WebDAV login failed", + "webdav_account_create": "WebDAV account created", + "webdav_account_update": "WebDAV account updated", + "webdav_account_delete": "WebDAV account deleted", + "payment_created": "Payment created", + "points_change": "Points changed", + "payment_paid": "Payment paid", + "payment_fulfilled": "Order fulfilled", + "payment_fulfill_failed": "Order fulfill failed", + "storage_added": "Storage added", + "group_changed": "Group changed", + "user_exceed_quota_notified": "Quota exceeded notification", + "user_changed": "User status changed", + "get_direct_link": "Get direct link", + "link_account": "Link external account", + "unlink_account": "Unlink external account", + "change_nick": "Change nickname", + "change_avatar": "Change avatar", + "membership_unsubscribe": "Membership unsubscribe", + "change_password": "Change password", + "enable_2fa": "Enable 2FA", + "disable_2fa": "Disable 2FA", + "add_passkey": "Add passkey", + "remove_passkey": "Remove passkey", + "redeem_gift_code": "Redeem gift code", + "update_view": "Changed view setting", + "delete_direct_link": "Delete direct link", + "report_abuse": "Report abuse" + }, + "server": "Server", + "tempPath": "Temporary path", + "tempPathDes": "The directory for storing temporary files, relative to the Cloudreve data directory. Please ensure that no queue tasks are running before modifying it.", + "siteID": "Site ID", + "siteIDDes": "A unique ID for identifying the site, generally not needed to be modified.", + "siteSecretKey": "Master key", + "siteSecretKeyDes": "The master key used to encrypt user tokens and signatures. After rotation, all user tokens and signatures will be invalid. It takes effect after restarting Cloudreve.", + "rotateSecretKey": "Rotate master key", + "hashidSalt": "HashID salt", + "hashidSaltDes": "The salt value used to generate HashID. Please be cautious when changing it, as it will invalidate existing direct links and share links.", + "accessTokenTTL": "Access token TTL", + "accessTokenTTLDes": "The TTL of access tokens, in seconds.", + "refreshTokenTTL": "Refresh token TTL", + "refreshTokenTTLDes": "The TTL of refresh tokens, in seconds. It affects the duration of user login status.", + "cronGarbageCollect": "Garbage collection scan interval", + "cronGarbageCollectDes": "Set how often to scan and recycle expired data in temporary files and KV storage.", + "startWithProtocol": "Must start with http:// or https://", + "tlsWarning": "The current site is using https, filling in an http URL here may cause exceptions.", + "blobUrlCache": "Blob URL cache", + "clearBlobUrlCache": "Clear Blob URL cache", + "clearBlobUrlCacheDes": "To increase cache hit rate, Cloudreve caches and reuses Blob URLs. When the CDN address or other settings change, please clear the cache.", + "cacheCleared": "Cache cleared." + }, + "giftCodes": { + "giftCodesSettings": "Gift Codes", + "generateGiftCodes": "Generate Gift Codes", + "giftCodeQuantity": "Quantity", + "giftCodeQuantityHelp": "Number of gift codes to generate", + "giftCodeProductType": "Product Type", + "giftCodeTypePoints": "Points", + "giftCodeTypeStorage": "Storage", + "giftCodeTypeGroup": "Group", + "giftCodePointsAmount": "Points Amount", + "giftCodePointsAmountHelp": "Number of points to credit when code is redeemed", + "giftCodeProduct": "Product", + "selectStorageProduct": "Select storage product", + "selectGroupProduct": "Select group product", + "giftCodeType": "Type", + "giftCodeAmount": "Amount", + "giftCode": "Gift Code", + "giftCodeStatus": "Status", + "giftCodeUsedBy": "Used by", + "giftCodeUsed": "Used", + "giftCodeUnused": "Available", + "giftCodeDeleted": "Gift code deleted successfully", + "giftCodesGenerated": "Gift codes generated successfully", + "noGiftCodes": "No gift codes available", + "generatedCodesTitle": "Generated Gift Codes", + "generatedCodesDescription": "Copy these gift codes to share with users. Each code can be used once.", + "copyAndClose": "Copy and Close", + "duratonTimes": "Quantity", + "duratonTimesDes": "How many quantities of the product is included in each gift code.", + "unknownProduct": "Unknown Product" + }, + "policy": { + "acceleratedDomainUpload": "Use transfer acceleration domain for upload", + "acceleratedDomainUploadDes": "When enabled, the <0>transfer acceleration domain of Qiniu will be used when uploading files.", + "compare": "Compare", + "deletePolicyConfirmation": "Are you sure you want to delete the storage policy {{name}}?", + "streamSaver": "Download via browser", + "streamSaverDes": "When enabled, users' download requests will be handled by the browser. Due to the OneDrive storage policy limitation, the file name of the file downloaded directly by users cannot be the same as the file name in Cloudreve, using the browser to handle downloads can solve this problem.", + "oauthCallbackFailed": "Authorization failed", + "httpsRequired": "Entra ID application requires HTTPS redirect URL, but the current site is using HTTP, which may cause redirect failure after login, please manually replace the HTTPS in the browser address bar with HTTP.", + "authorizeMicrosoft": "Sign-in with Microsoft", + "redirectUrl": "Redirect URL", + "redirectUrlDes": "The current display is the latest redirect URL that meets the requirements. Please confirm if the redirect URL in the application settings is consistent with the current one.", + "authorizeOneDrive": "Confirm Entra ID application settings", + "authorizeOneDriveDes": "Please confirm if the following Entra ID application information is still valid. If needed, please make changes.", + "authorizeNow": "Authorize", + "authorizeAgain": "Authorize again", + "notGranted": "No authorized account, storage policy cannot be used.", + "granted": "Account authorized, credential refreshed at <0>{{time}}.", + "grantedNotRefresh": "Account authorized, credential not refreshed since last startup.", + "batchDeleteSize": "Maximum batch delete size", + "batchDeleteSizeDes": "Limit the maximum number of files that can be deleted in a single API request. This setting will not affect user batch file deletion. If not filled, the default value <0>1000 will be used. This is the maximum allowed value for official S3 API.", + "bucketPolicy": "Bucket policy", + "cdnOrCustomDomain": "CDN or custom CNAME", + "bucketDomain": "Bucket domain", + "bucketDomainDes": "Fill in the CDN-accelerated domain or custom CNAME domain you have bound for the storage bucket.", + "storageNodeInternal": "Storage node (Intranet Endpoint)", + "chunkSizeDesOssObs": "Allowed range: 100 KB ~ 5 GB.", + "chunkSizeDesQiniuCos": "Allowed range: 1 MB ~ 1 GB.", + "chunkSizeDesS3": "Allowed range: 5 MB ~ 5 GB.", + "thisIsACustomDomain": "This is a custom domain", + "thisIsACustomDomainDes": "If you have bound a custom domain to the storage bucket, and need to manage the bucket via the custom domain, please check this option. After enabled, Cloudreve will not attempt to append the Bucket name in the request domain.", + "addedManually": "I have set it manually", + "origin": "Origin", + "allowMethods": "Allowed Methods", + "exposeHeaders": "Expose Headers", + "allowHeaders": "Allowed Headers", + "maxAge": "Max Age", + "accessCredential": "Access credential", + "downloadTrafficDiagram": "Download traffic path demonstration", + "downloadRelay": "Download relay", + "downloadRelayDes": "When enabled, users' download requests will be proxied by Cloudreve.", + "download": "Download", + "downloadCdn": "Download CDN", + "useDownloadCdn": "Use CDN for download traffic", + "skipSign": "Skip URL signature for CDN", + "skipSignDes": "If you have enabled \"Use source auth\" for this domain in bucket settings, please check this option.", + "cdnHost": "CDN host", + "downloadCdnDes": "The host, protocol, and port of the URL that users use to access files will be replaced with the CDN host you specified.", + "mediaExtractorProxy": "Proxy media extraction", + "mediaExtractorProxyDes": "Enable this feature to extract media metadata from files that are not supported by the storage provider's native extractors. Please configure the media extractor in <0>Media processing.", + "mediaExtractorNative": "native extractors", + "mediaExtractorOss": "Intelligent Media Management (IMM)", + "mediaExtractorQiniu": "Qiniu DORA", + "mediaExtractorCos": "Tencent Cloud Data Processing", + "mediaExtractorObs": "image processing service", + "mediaExtractorUpyun": "image processing service", + "nativeMediaMetaExts": "Enabled file extensions for <0>{{name}}", + "nativeMediaMetaExtsGeneralDes": "Separated by commas, empty value means disable <0>{{name}}.", + "nativeMediaMetaExtsRemote": "For slave storage, the default support is EXIF and music metadata, you can override this by configuring the slave node with more extractors.", + "nativeMediaMetaExtOss": " The Intelligent Media Management (IMM) service supports processing audio, video, and images. Image processing does not require manual configuration, but if you need to process audio or video, you need to manually activate IMM and bind it to the Bucket, please refer to <0>document for binding. After binding, please add the extensions you want to process to the above field.", + "nativeMediaMetaExtQiniu": "The Qiniu DORA service supports processing common audio, video, and images, no additional configuration is required, please fill in the extensions you want to process above.", + "nativeMediaMetaExtCos": "The Tencent Cloud Data Processing service supports processing audio, video, and images. Image processing does not require manual configuration, but if you need to process audio or video, please first go to <0>Tencent Cloud Data Processing to activate and bind the storage bucket, then go to Bucket settings - Media processing to activate the image processing service. After binding, please add the extensions you want to process to the above field.", + "nativeMediaMetaExtObs": "The image processing service supports <0>extracting image EXIF. No manual configuration is required, just add the extensions you want to process above.", + "nativeMediaMetaExtUpyun": "The image processing service supports <0>extracting image EXIF. No manual configuration is required, just add the extensions you want to process above.", + "thumbProxy": "Proxy thumbnail generation", + "thumbProxyDes": "Enable this feature to generate thumbnails for files that do not meet the native thumbnail conditions. Cloudreve will try to generate thumbnails and upload them to the storage side. Please configure the thumbnail generator in <0>Media processing.", + "nativeThumbnailMaxSize": "Max size of native thumbnails", + "nativeThumbnailMaxSizeDes": "Enter 0 to disable the size limit, files larger than this size will not use native thumbnails.", + "nativeThumbNailsSupportAllExts": "Enable for all file extension", + "nativeThumbNails": "File extensions for native thumbnails", + "nativeThumbNailsGeneralDes": "Separated by commas, empty value means disable native thumb, for the file extensions listed above, Cloudreve will use the native thumbnail feature of the storage provider to generate thumbnails.", + "nativeThumbNailsGeneralRemote": " For slave storage, the builtin support is simple image and music cover thumbnails, you can override this by configuring the slave node with more generators.", + "nativeThumbNailsGeneralOss": "For Alibaba Cloud OSS storage, <0>image processing service will be used to generate thumbnails.", + "nativeThumbNailsGeneralQiniu": "For Qiniu Cloud storage, <0>image basic processing(imageView2) service will be used to generate thumbnails.", + "nativeThumbNailsGeneralCos": "For Tencent Cloud COS storage, <0>Tencent Cloud Data Processing service will be used to generate thumbnails.", + "nativeThumbNailsGeneralObs": "For Huawei Cloud OBS storage, <0>image processing service will be used to generate thumbnails.", + "nativeThumbNailsGeneralUpyun": "For Upyun storage, <0>image processing service will be used to generate thumbnails.", + "preallocate": "Pre-allocate disk space", + "preallocateDes": "When enabled, the user's upload request will be pre-allocated disk space on the storage node, and also supports parallel chunk upload. Only effective on Linux or Darwin.", + "chunkConcurrency": "Concurrent chunk uploads", + "chunkConcurrencyDes": "Set the number of concurrent chunk uploads when using direct web upload.", + "sourceWebEdit": "Web online editing", + "uploadRelay": "Upload relay", + "uploadRelayDes": "If enabled, users' upload requests will be relayed to the storage node via Cloudreve, due to the inability to perform chunked uploads, please adjust the maximum upload size limit of the web server accordingly.", + "customProxy": "Custom proxy", + "storageNode": "Storage provider", + "sourceWeb": "Web / Official app", + "sourceDav": "WebDAV", + "uploadTrafficDiagram": "Upload traffic path demonstration", + "node": "Storage node", + "nodeDes": "Please select a slave node for file storage, you can create or manage slave storage nodes in <0>Node list.", + "noBindedGroupWarning": "The current storage policy is not bound to any user group, please go to <0>Group list to bind the current storage policy to a user group.", + "nameRuleImmutable": " Modifying settings will not affect existing files in the storage policy. The Blob path is fixed after creation, even if the magic variables in it change, the path will not be updated.", + "uniqueVarRequired": "Please include at least one unique variable in either the directory path or blob name: {uuid}, {randomkey8}, {randomkey16}.", + "storageAndUpload": "Storage and Upload", + "blobFolderNaming": "Blob Storage Directory", + "blobFolderNamingDes": "The directory where file Blobs are stored, you can use <0>magic variables.", + "blobNameDes": "The name of the file Blob, you can use <0>magic variables, make sure it's absolutely unique, even for multiple uploads of the same file name in same path in a short time.", + "blobName": "Blob Name", + "basicInfo": "Basic info", + "editX": "Edit {{name}}", + "noGroupBinded": "No group binded", + "create": "Create", + "addXStoragePolicy": "Add {{type}} storage policy", + "loadSummary": "Load summary", + "policySummary": "{{count}} file Blobs ({{size}})", + "sharp": "#", + "name": "Name", + "type": "Type", + "childFiles": "Chile files", + "totalSize": "Total size", + "actions": "Actions", + "authSuccess": "Authorization granted.", + "policyDeleted": "Policy deleted.", + "newStoragePolicy": "New storage policy", + "all": "All", + "local": "Local", + "remote": "Remote Node", + "qiniu": "Qiniu", + "upyun": "Upyun", + "oss": "Alibaba Cloud OSS", + "cos": "Tencent Cloud COS", + "onedrive": "OneDrive", + "s3": "S3 Compatible", + "ks3": "Kingsoft Cloud S3", + "obs": "Huawei Cloud OBS", + "load_balance": "Load Balance", + "childPolicy": "Child Storage Policy", + "childPolicyDes": "Select the child storage policies to add to the load balance pool.", + "weight": "Weight", + "addTargetPolicy": "Add Child Policy", + "selectPolicies": "Select Policies", + "selectPoliciesDes": "Select storage policies to add to the load balance pool.", + "loadBalanceDes": "When using the load balanced storage policy, new uploads will be randomly distributed to different child storage policies based on weight.", + "xChildPolicies": "{{count}} child storage policies", + "refresh": "Refresh", + "delete": "Delete", + "edit": "Edit", + "selectAStorageProvider": "Select a storage provider", + "maxSizeOfSingleFile": "Max single file size", + "maxSizeOfSingleFileDes": "Enter 0 to disable the limit.", + "enterFileExt": "Separated by semi-colon commas, leave blank to allow all file extensions.", + "extList": "File extension restrictions", + "noLimit": "No limit", + "whitelist": "Allow", + "blacklist": "Deny", + "fileNameRegex": "File name regex rules", + "fileNameRegexDes": "Regular expression to match file names, leave blank for no restriction.", + "chunkSizeDes": "Specify the chunk size for chunked uploads. A value of 0 means no chunked uploads are used, but the maximum upload size may be limited by the web server.", + "chunkSizeDesSuffix": "{{prefix}} With chunked upload, the files uploaded by users will be sliced into chunks and uploaded to the storage side one by one. After the upload is interrupted, users can choose to continue uploading from the last uploaded chunk.", + "chunkSize": "Chunk size", + "policyName": "The display name of the storage policy, also used to be presented to users.", + "magicVar": { + "fileNameMagicVar": "File name magic variables", + "pathMagicVar": "Path magic variables", + "variable": "Variable", + "description": "Description", + "example": "Example", + "16digitsRandomString": "16 digits random string", + "8digitsRandomString": "8 digits random string", + "secondTimestamp": "Timestamp", + "nanoTimestamp": "Nano timestamp", + "uid": "User ID", + "originalFileName": "Original file name", + "originFileNameNoext": "Original file name without ext", + "extension": "File extension name", + "uuidV4": "UUID V4", + "date": "Date", + "dateAndTime": "Date and time", + "randomNumber": "Random number within range", + "year": "Year", + "month": "Month", + "day": "Day", + "hour": "Hour", + "minute": "Minute", + "second": "Second", + "path": "The initial path while user uploads the file" + }, + "storageBucket": "Storage bucket", + "wanSiteURLDes": "Before using this policy, please make sure that the address you entered in Basic Settings - Site Information - Site URL matches the actual address and <0>can be accessed properly by WAN.", + "enterQiniuBucket": "Go to <0>Qiniu dashboard to create a storage bucket. Enter the \"Bucket name\" you just created.", + "aclType": "Access control type", + "accessTypePulic": "Public read private write", + "accessTypePrivate": "Private read/write", + "accessType": "Access type", + "qiniuBucketName": "Bucket name", + "cosObsBucketName": "Bucket name", + "bucketType": "Bucket ACL", + "bucketTypeDes": "Select the type of ACL for the bucket you just created.", + "privateBucket": "Private", + "privateDes": "Cloudreve will sign the file URL.", + "publicBucket": "Public read", + "publicStorage": "Public", + "publicDes": "Not recommended, Cloudreve will directly return the file's direct link, which cannot effectively control the access of files.", + "bucketCDNDes": "Fill in the CDN-accelerated domain name you have bound for the storage bucket.", + "bucketCDNDomain": "CDN domain", + "qiniuCredentialDes": "Go to Personal Center - Credential Management in the Qiniu dashboard and fill in the obtained AK, SK.", + "ak": "AK", + "sk": "SK", + "cannotEnableForPrivateBucket": "If this feature is enabled for private bucket, you need to enable \"Use redirected source link\" for user groups.", + "chunkSizeLabelQiniu": "Specify the chunk size for resumable uploads. Allowed range is 1 MB - 1 GB.", + "corsSettingStep": "CORS policy", + "corsPolicyAdded": "CORS policy is added.", + "createOSSBucketDes": "Go to <0>OSS Dashboard to create a Bucket. Only <1>Standard and <2>IA storage classes are supported.", + "bucketName": "Bucket name", + "publicReadBucket": "Public read", + "ossEndpointDes": "Go to Bucket summary page, enter the <2>Port under <1>Access Over Internet section, in <0>Endpoint page.", + "ossEndpointDesInternalHint": "If you need to configure Intranet or custom domain endpoint, you can set it after creating the storage policy.", + "obsEndpointCnameHint": "If you need to configure custom domain endpoint, you can set it after creating the storage policy.", + "endpoint": "EndPoint", + "ossLANEndpointDes": "Leave blank means not use it. If your Cloudreve is deployed in Alibaba Cloud compute related services which are under the same availability zone as the OSS bucket, you can additionally specify a intranet endpoint, Cloudreve will try to use this endpoint on server side to reduce traffic cost.", + "intranetEndPoint": "Intranet endpoint", + "ossCDNDes": "Do you want to use Alibaba Cloud CDN to speed up file access?", + "createOSSCDNDes": "Go to <0>Alibaba Cloud CDN Dashboard to create a CDN domain, the source of the CDN should be your OSS bucket. Enter the CDN domain and select if you want to use HTTPS:", + "ossAKDes": "Obtain your AccessKey in <0>Security Information Management page. You can also create an AccessKey with <1>AliyunOSSFullAccess permission in <2>RAM Access Control.", + "shouldNotContainSpace": "This cannot contain spaces.", + "nameThePolicyFirst": "Name the storage policy:", + "chunkSizeLabelOSS": "Specify the chunk size for resumable uploads. Allowed range is 100 KB - 5 GB.", + "ossCORSDes": "This storage policy requires a CORS policy to enable uploading from browser. Cloudreve can set it up automatically for you, or you can set it up manually by following the steps in the documentation. If you have already set the CORS policy for this Bucket, this step can be skipped.", + "letCloudreveHelpMe": "Let Cloudreve set it for me", + "skip": "Skip", + "createUpyunBucketDes": "Fill in the name of the storage service you created in <0>Upyun Dashboard.", + "storageServiceName": "Service name", + "operatorName": "Operator name", + "operatorPassword": "Operator password", + "tokenStatus": "Token anti-hotlinking", + "upyunTokenDes": "It is strongly recommended to enable Token Anti-Hotlinking, go to the <0>Feature Configuration panel of the created storage service, go to the <1>Access Control tab, enable Token Anti-Hotlinking and set a secret.", + "tokenEnabled": "Enable Token Anti-Hotlinking", + "tokenDisabled": "Not use Token Anti-Hotlinking", + "upyunTokenSecretDes": "Enter the secret of the Token Anti-Hotlinking.", + "upyunTokenSecret": "Token Anti-Hotlinking secret", + "createCOSBucketDes": "Go to <0>COS Dashboard to create a storage bucket. Go to the basic configuration page of the created bucket, and copy the <1>Bucket name to above.", + "obsBucketDes": "Go to <0>OBS Dashboard to create a storage bucket. Enter the <1>Bucket name you just created. Storage class only supports <2>Standard or <3>Infrequent Access.", + "cosPrivateRW": "Private Read/Write", + "cosPublicRW": "Public Read and Private Write", + "cosAccessDomainDes": "On the overview page of the created Bucket, fill in the <1>Access Domain given under the <0>Domain Information section. You can also use your CNAME domain or CDN acceleration domain.", + "obsEndpointDes": "On the overview page of the created Bucket, fill in the <1>Endpoint given under the <0>Domain Information section.", + "accessDomain": "Access domain", + "cosCDNDomainDes": "Go to <0>Tencent Cloud CDN Management Console to create a CDN acceleration domain and set the source site to the COS bucket you just created. Fill in the CDN domain name below and select whether to use HTTPS.", + "cosCredentialDes": "Fill in the access keys obtained from the <0>Access Keys page of Tencent Cloud. Please make sure the pair of keys has access permission to COS services. You can also create a <2>sub-user with <1>Programmatic Access permission and grant it access to COS service.", + "obsCredentialDes": "Fill in the access keys obtained from the <0>Access Keys page of Huawei Cloud. You can also create a <2>IAM user with <1>Programmatic Access permission and grant it <3>OBS OperateAccess permission.", + "grantAccess": "Grant access", + "grantAccessLater": "After creating the storage policy, you need to sign in and grant access in the storage policy settings page.", + "odHttpsWarning": "You must enable HTTPS to use OneDrive/SharePoint storage policies; after enabled, make sure to change Settings - Basic - Site Information - Site URL.", + "creatAadAppDes": "Go to <0>Microsoft Entra ID Dashboard, after logging in, go to the <1>Microsoft Entra ID admin panel, you can optionally use an account different from the one used to store files to login.", + "createAadAppDes2": "Go to the <0>App Registrations menu on the left and click the <1>New registration button. Fill out the application registration form. Make sure <2>Supported account types is selected as <3>Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox); <4>Redirect URI (optional) is selected as <5>Web and fill in <6>{{url}}; For other fields, just leave it as default.", + "entraIdApp": "Entra ID app information", + "aadAppIDDes": "Go to the <0>Overview page in Application Management, the value of <1>Application (Client) ID.", + "aadAppID": "Application (Client) ID", + "addAppSecretDes": "The way to create client secret: Go to the <0>Certificates & secrets menu on the left side, click the <1>New client secret button, and select the longest time for the <2>Expires. You need to create a new client secret after the old one expires, and update the new one in the storage policy settings.", + "aadAppSecret": "Client secret", + "aadAccountCloud": "Microsoft Graph endpoint", + "aadAccountCloudDes": "Please select the endpoint according to the Microsoft 365 account type you are using.", + "multiTenant": "Worldwide public cloud", + "gallatin": "21V Chinese cloud", + "sharePointDes": "Do you want to store files in SharePoint?", + "saveToOneDrive": "Store files to default OneDrive", + "spSiteURL": "SharePoint Site URL", + "odReverseProxyURLDes": "Do you want to use custom reverse proxy server for file downloading?", + "odReverseProxyURL": "URL of reverse proxy server", + "chunkSizeDesOd": "Allowed range: 5 MB ~ 5GB, OneDrive requires it must be an integer multiple of 320 KiB (327,680 bytes).", + "limitOdTPSDes": "Limit OneDrive API request frequency", + "tps": "TPS limit", + "tpsDes": "Leave blank to indicate no limit. Limit this storage policy the maximum number of API requests sent to OneDrive per second. Requests that exceed this frequency will be rate-limited. When multiple Cloudreve nodes transferring files, they each use their own token bucket, so please scale this number down as appropriate in this condition.", + "tpsBurst": "TPS burst", + "tpsBurstDes": "When requested is idle, Cloudreve can reserve a specified number of slots for future bursts of traffic.", + "odOauthDes": "However, you will need to click the button below and authorize with Microsoft account login to complete the initialization before you can use it. You can re-authorize later on the Storage Policy List page.", + "gotoAuthPage": "Go to authorization page", + "s3BucketDes": "Go to AWS S3 dashboard to create a bucket, enter the <0>Bucket name you just created:", + "s3EndpointDes": "Specify the EndPoint (geographical node) of the storage bucket in full URL format, e.g. <0>https://bucket.region.example.com.", + "selectRegionDes": "Enter the region code of the storage bucket, e.g. <0>us-east-1. For non-AWS S3 compatible storage providers, please refer to their documentation for how to fill this field.", + "chunkSizeLabelS3": "Specify the chunk size for resumable uploads. Allowed range is 5 MB - 5 GB.", + "policyEndpoint": "Endpoint.", + "s3Region": "Region", + "s3EndpointPathStyle": "Select the format of the S3 Endpoint address. Some third-party S3-compatible storage policies may require this option to work. When turned on, we will force to use of path-like format addresses, such as <0>http://s3.amazonaws.com/BUCKET/KEY.", + "usePathEndpoint": "Force path style", + "thumbExt": "Extensions that supports thumbnails", + "thumbExtDes": "Leave blank to indicate that the storage policy predefined set is used. Not valid for local, S3 storage policies.", + "driverRoot": "Driver Root", + "driverRootDes": "Choose where to save files in your OneDrive account. Changing this option will make existing files in the storage policy inaccessible.", + "saveToDefaultOneDrive": "Save files to default OneDrive driver", + "saveToSharePoint": "Save files to SharePoint", + "sharePointUrlDes": "Enter the SharePoint site URL. After losing focus, the system will automatically convert it to the correct driver identifier.", + "ks3selectRegionDes": "Enter the region code of the storage bucket, e.g. <0>BEIJING .", + "ks3EndpointPathStyle": "Select the format of the KS3 Endpoint address.", + "ossRegionDes": "Enter the region code of the storage bucket, e.g. <0>cn-hangzhou. You can find the corresponding region in the <1>OSS regions and endpoints table and fill in the corresponding <2>Region ID." + }, + "node": { + "slave": "slave", + "master": "master", + "noCapabilities": "No capabilities enabled.", + "active": "Active", + "suspended": "Suspended", + "deleteNodeConfirmation": "Are you sure you want to delete node {{name}}?", + "editNode": "Edit node {{node}}", + "thisIsMasterNodes": "You are editing a master node, which is serving the current site.", + "enableNode": "Enable node", + "enableNodeDes": "After enabled, the node will accept and process the features that have been enabled.", + "name": "Name", + "nameNode": "Node name, also used to display to users.", + "type": "Type", + "server": "Node endpoint", + "serverDes": "Endpoint used for node communication. If you want to store files on this node, this address will also be exposed to the user side for file uploads.", + "loadBalancerRankDes": "Specify a load balancing weight for this node, the value is an integer. The higher the value, the higher the probability of being selected.", + "loadBalancerRank": "Load balancing weight", + "slaveSecret": "Slave secret", + "slaveSecretDes": "Secret used for slave node communication with master node. It needs to be consistent with <1>Secret in the <1>Slave section of the slave node configuration file.", + "testNode": "Test node communication", + "testNodeSuccess": "Node communicate successfully.", + "createArchiveDes": "Accept create archive task requests.", + "extractArchiveDes": "Accept extract archive task requests.", + "remoteDownloadDes": "Accept remote download task requests. After enabled, you also need to configure the remote download related information below.", + "downloader": "Downloader", + "aria2Des": "Start Aria2 as the same user/access level running Cloudreve on the target node server, enable the RPC service in the Aria2 config file, for more information and guidelines, refer the \"Remote download\" section of the documentation.", + "qbittorrentDes": "Start qBittorrent as the same user running Cloudreve on the target node server, enable the Web UI service in the qBittorrent settings, for more information and guidelines, refer the \"Remote download\" section of the documentation.", + "rpcServer": "RPC Server", + "rpcServerHelpDes": "RPC server address contain full port number, e.g. <0>http://127.0.0.1:6800/.", + "rpcToken": "RPC Token", + "rpcTokenDes": "Consistent with <0>rpc-secret in the Aria2 configuration file; leave blank if not set.", + "downloaderOptionDes": "Additional downloader configuration when creating a download task, written in JSON key-value format, see the <0>downloader official documentation for available parameters.", + "refreshInterval": "Status refresh interval (seconds)", + "refreshIntervalDes": "The interval at which Cloudreve requests a refresh of the task state from the downloader. The actual refresh interval also depends on the configuration of the \"Remote download\" queue and the busyness of the downloader.", + "waitForSeeding": "Wait for seeding", + "waitForSeedingDes": "After enabled, when the remote download task is completed, the node will keep the task in the seeding state until the seeding completion condition in the downloader configuration is met. This feature only takes effect after the remote download task is completed, and will not affect the user's use of the downloaded files.", + "webUIEndpoint": "Web UI endpoint", + "webUIEndpointDes": "The endpoint of the qBittorrent Web UI, e.g. <0>http://127.0.0.1:8080/.", + "tempPath": "Temporary download directory", + "tempPathDes": "The directory on the node that Aria2 uses as a temporary download directory. The Cloudreve process on the node needs read, write, and execute permissions on this directory, and the downloader also needs to be able to access this directory. Leave blank to use the default temporary file path.", + "webUIUsername": "Web UI username", + "webUIPassword": "Web UI password", + "webUICredDes": "Leave blank if authentication is not enabled.", + "downloaderTestPass": "Successfully connected to downloader, version: {{version}}", + "testDownloader": "Test downloader communication", + "addNewNode": "New node", + "nameTheNode": "Name the node:", + "copyBinary": "", + "runCrSlave": "Run Cloudreve on the node with the same version as the master, and start it with the following configuration file:", + "keepIfUpload": "If you need to use this node for storage policies in the future, please keep the following CORS configuration.", + "storeFiles": "Store files", + "storeFilesDes": "Use this node to store user files.", + "storeFilesHint": "If you want to use this node for storage policies, please create a slave storage policy and select this node.", + "runCrWithConfig": "Save the above file as <0>config.ini file, and start Cloudreve with this file: <0>./cloudreve -c config.ini. A slave Cloudreve instance can serve multiple Cloudreve master nodes; simply add this slave node to all master nodes and keep the secret same.", + "inputServer": "Enter the node endpoint:", + "testButton": "You can click the button below to test if the communication is successful.", + "hostHeaderHint": "If there is a signature error, please check if the reverse proxy in front of the node is passing the <0>Host header.", + "features": "Enabled features", + "remoteDownload": "Remote download", + "refresh": "Refresh" + }, + "group": { + "countUser": "Count", + "anonymous": "Anonymous user group", + "sysGroup": "System user group", + "adminGroup": "Admin user group", + "#": "#", + "name": "Name", + "type": "Storage policy", + "count": "Child users", + "size": "Storage quota", + "nameOfGroup": "Name", + "nameOfGroupDes": "Name of the group, used to display to users.", + "availablePolicies": "Available storage policies", + "availablePoliciesDes": "Select the storage policies that this group can use. Modifying this setting will not affect the files uploaded by users.", + "availablePolicyDesPro": "Multi-selectable, users can freely switch storage policies within the selected range.", + "initialStorageQuota": "Initial storage quota", + "initialStorageQuotaDes": "Max storage can used by single user under this group.", + "isAdmin": "Admin group", + "isAdminDes": "When enabled, users under this group will have admin permissions.", + "share": "Share", + "allowCreateShareLink": "Create share link", + "allowCreateShareLinkDes": "If disabled, users cannot create sharing links.", + "shareFree": "Free share link", + "shareFreeDes": "When enabled, users can access all paid sharing links without purchasing.", + "fileManagement": "File management", + "allowWabDAV": "WebDAV", + "allowWabDAVDes": "If disabled, users cannot connect to the storage via the WebDAV protocol", + "allowWabDAVProxy": "WebDAV Proxy", + "allowWabDAVProxyDes": "If enabled, users can configure the WebDAV to be proxied by Cloudreve when downloading files.", + "compressTask": "Compression/Decompression tasks", + "compressTaskDes": "If enabled, users can do compression/decompression for files online.", + "compressSize": "Maximum file size to be compressed", + "compressSizeDes": "The maximum total file size of compression jobs that can be created by the user, fill in 0 to indicate no limit. This limit is not checked when creating compression tasks, and if the total size of the original files exceeds this limit when executing, the task will fail.", + "decompressSize": "Maximum file size to be decompressed", + "decompressSizeDes": "The maximum total file size of decompression jobs that can be created by the user, fill in 0 to indicate no limit.", + "allowRemoteDownload": "Remote download", + "allowRemoteDownloadDes": "Whether to allow users to create remote download tasks. If you need to use remote download, you also need to have nodes with remote download enabled in the <0>Node List.", + "aria2Options": "Downloader job options", + "aria2OptionsDes": "Extra parameters for downloaders (qBittorrent or Aria2), written in JSON key-value format, see the downloader official documentation for available parameters.", + "aria2BatchSize": "Max batch size of remote download tasks", + "aria2BatchSizeDes": "Max number for submitting batched remote download tasks, fill in 0 to indicate no limit.", + "migratePolicy": "Relocate storage policy", + "migratePolicyDes": "Whether the user creates a storage policy relocation task.", + "advanceDelete": "Advanced file deletion options", + "advanceDeleteDes": "Once enabled, users can choose whether to keep physical files when deleting files. Please only enable this option for trusted user groups.", + "allowSelectNode": "Allow select node", + "allowSelectNodeDes": "When enabled, user can select preferred node before creating tasks. When disabled, the node will be load-balanced by system within the allowed nodes for the group.", + "allowedNodes": "Allowed nodes", + "allowedNodesDes": "Specify the nodes that this group can use to create tasks. Empty list means all nodes are available. Users can only select or be assigned nodes within this list by load balancer. Currently, the tasks covered are: remote download, file compression/decompression. Other tasks will be assigned to the master node.", + "allNodes": "All nodes", + "esclateAnonymity": "Escalate anonymity", + "esclateAnonymityDes": "When enabled, users can assign higher permissions for anonymous users (write/delete/create). When disabled, users can only assign read-only permission for anonymous users. Changing this setting will not affect existing sharing links or files.", + "allowDownloadShare": "Access shared links", + "allowDownloadShareDes": "When disabled, users cannot view others' shared links. This setting takes precedence over the sharing link permission settings.", + "deletedNode": "Deleted node #{{id}}", + "maxWalkedFiles": "Max walked files", + "maxWalkedFilesDes": "In some operations that require deep traversal of files, the maximum number of files allowed to be traversed.", + "trashBinDuration": "Trash bin duration (seconds)", + "trashBinDurationDes": "The retention time of files in the trash bin, files will be permanently deleted after the expiration time. Changing this setting will not affect files already in the trash bin.", + "serverSideBatchDownload": "Server-side batch download", + "serverSideBatchDownloadDes": "Whether to allow users to select multiple files to use the server-side relay batch download, after disabled, users can still use the pure browser-based batch download feature.", + "uploadDownload": "Upload and download", + "getDirectLink": "Get direct link", + "getDirectLinkDes": "Whether to allow users to get the direct link of the file.", + "bathSourceLinkLimit": "Max size of batch direct links", + "bathSourceLinkLimitDes": "The maximum number of files allowed for users to obtain direct links in a single batch, fill in 0 means no batch generation of direct links is allowed.", + "redirectedSource": "Use redirected direct link", + "redirectedSourceDes": "Recommended to enable. When enabled, the direct link to the file obtained by the user will be redirected by Cloudreve with a shorter link. When disabled, the direct link to the file obtained by the user becomes the original URL to the file, and is bound to the file version. Some policies produce non-redirected direct links that do not remain persistent; see Cloudreve documents for details.", + "reuseDirectLink": "Reuse existing direct link", + "reuseDirectLinkDes": "When enabled, multiple requests for the same file's direct link will reuse the existing redirect link.", + "downloadSpeedLimit": "Max download speed", + "downloadSpeedLimitDes": "Fill in 0 to indicate no limit. When the restriction is turned on, the maximum download speed will be limited when users download all files under the storage policy that supports the speed limit.", + "anonymousHint": "This user group corresponds to the anonymous visitor who is not signed in.", + "create": "Create", + "copyFromExisting": "Copy from existing group?", + "notCopy": "Not copy", + "confirmDelete": "Are you sure you want to delete group {{group}}?", + "new": "New group", + "editGroup": "Edit {{group}}" + }, + "user": { + "createdAt": "Created at", + "originUserGroup": "Original user group", + "originUserGroupDes": "User group that the user belongs to before purchasing the current group, the current group will revert to this group after expiration.", + "noOriginUserGroup": "No", + "groupExpired": "Group expired date", + "groupExpiredDes": "ISO8601 format group expired date, leave blank means the group is permanent.", + "openUserFiles": "Open user files", + "id": "ID", + "idValue": "{{id}} ({{hash_id}})", + "avatar": "Profile picture", + "removeAvatar": "Remove profile picture", + "userDialogTitle": "User details", + "2FAEnabled": "2FA enabled", + "qqEnabled": "QQ enabled", + "logtoEnabled": "Logto enabled", + "deleted": "User deleted.", + "new": "New user", + "filter": "Filter", + "emptyNoFilter": "Leave blank means no filter.", + "selectedObjects": "{{num}} objects selected.", + "nick": "Display name", + "email": "Email", + "group": "Group", + "status": "Status", + "usedStorage": "Used storage", + "status_active": "Active", + "status_inactive": "Inactive", + "status_manual_banned": "Manual blocked", + "status_sys_banned": "System blocked", + "toggleBan": "Block/Unblock", + "filterCondition": "Filter conditions", + "all": "All", + "userStatus": "User status", + "apply": "Apply", + "editUser": "Edit {{nick}}", + "password": "Password", + "passwordDes": "Leave blank means no modification.", + "groupDes": "Group that the user belongs to.", + "2FA": "2FA", + "notEnabled": "Not enabled", + "reset2Fa": "Disable", + "reset": "Reset", + "confirmDelete": "Are you sure you want to delete user {{user}}?", + "deleteXUsers": "Delete {{num}} users", + "confirmBatchDelete": "Are you sure you want to delete {{num}} users?", + "calibrateStorage": "Calibrate storage", + "calibrateStorageSuccess": "Storage calibrated successfully." + }, + "file": { + "deleteXFiles": "Delete {{num}} files", + "confirmBatchDelete": "Are you sure you want to delete {{num}} files?", + "confirmDelete": "Are you sure you want to delete file {{file}}?", + "haveShares": "Shared", + "haveDirectLinks": "Have redirected direct links", + "directLinkId": "Link identifier", + "directLinks": "Redirected direct links", + "noRecords": "No records", + "speed": "Speed limit", + "downloads": "Downloads", + "shareLink": "Share links", + "shareLinkNum": "{{num}} (<0>View)", + "blobType": "Type", + "noEntities": "No Blobs", + "blobs": "Blobs", + "creator": "Creator", + "source": "Source", + "key": "Key", + "value": "Value", + "isPublic": "Public", + "noMetadata": "No metadata", + "metadata": "Metadata", + "id": "ID", + "primaryStoragePolicy": "Primary storage policy", + "fileDialogTitle": "File details", + "name": "File name", + "deleteAsync": "Delete task will be executed in background.", + "forceDelete": "Force delete", + "size": "Size", + "sizeUsed": "Used storage", + "uploader": "Owner", + "createdAt": "Created at", + "uploading": "Uploading", + "unknownUploader": "Unknown", + "uploaderID": "Owner ID", + "searchFileName": "Search file name", + "storagePolicy": "Storage policy", + "selectTargetUser": "Select target user", + "importTaskCreated": "Import task created, you can view its status in the background tasks list.", + "manuallyPathOnly": "The selected storage policy only supports manually inputting path.", + "selectFolder": "Select folder", + "import": "Import", + "importExternalFolder": "Import external folders", + "importExternalFolderDes": "You can import existing files and directory structures from your storage policy into Cloudreve. The import operation will not take up additional physical storage, but will still deduct the user's used storage quota as normal.", + "storagePolicyDes": "Select the storage policy where the files to be imported are currently stored.", + "targetUser": "Target user", + "targetUserDes": "Select which user's file system you want to import the files to.", + "srcFolderPath": "Source folder path", + "select": "Select", + "selectSrcDes": "The path of the directory to be imported on the storage side.", + "dstFolderPath": "Destination folder path", + "dstFolderPathDes": "Path in the user's file system to hold all imported files.", + "recursivelyImport": "Recursively import", + "recursivelyImportDes": "Whether to import all subdirectories under the directory recursively.", + "createImportTask": "Create import task", + "unlink": "Unlink (Keep physical file)", + "searchUser": "Search user by name or email...", + "extractMediaMeta": "Extract media information", + "extractMediaMetaDes": "Whether to extract media information for each file during import.", + "importWarning": "Warning", + "importWarnings": [ + "After import, the physical file will be taken over by Cloudreve, please do not modify it externally afterwards.", + "Do not import the same file multiple times.", + "If the user's file conflicts, this file will be skipped." + ], + "otherConditions": "Other conditions", + "shareLinkExisted": "Has share link", + "directLinkExisted": "Has direct link", + "isUploading": "Is uploading" + }, + "entity": { + "refenenceCount": "Reference count", + "waitForRecycle": "Waiting for recycle", + "entityDialogTitle": "Blob details", + "uploadSessionID": "Upload session ID", + "referredFiles": "Referred files", + "confirmBatchDelete": "Are you sure you want to delete {{num}} Blobs?", + "deleteXEntities": "Delete {{num}} Blobs", + "forceDelete": "Force delete", + "forceDeleteDes": "Whether to delete the Blob record regardless of whether the physical file is deleted." + }, + "event": { + "cleanup": "Cleanup", + "cleanupAuditLog": "Event cleanup", + "cleanupAuditLogDescription": "Delete all events that meet the following conditions:", + "cleanupNotAfter": "Before this date", + "cleanupEventTypes": "Event types", + "cleanupEventTypesDes": "Select the event types to clean up. Leave blank to clean up all types.", + "allEventTypes": "All event types", + "initiator": "Initiator", + "event": "Event", + "userID": "User ID", + "ip": "IP", + "type": "Type", + "correlationId": "Correlation ID", + "fileID": "File ID", + "emailSend": "Send email \"{{title}}\" to {{email}}", + "emailFailed": "Email queue failed to start", + "signinFailed": "Sign in failed: {{reason}}", + "createDavAccount": "Create WebDAV account: {{account}}", + "updateDavAccount": "Update WebDAV account: {{account}}", + "deleteDavAccount": "Delete WebDAV account: {{account}}", + "pointsChange": "Points change: {{points}}", + "storageAdded": "Purchased {{size}} storage", + "nickChange": "Display name changed from {{old}} to {{new}}", + "eventDialogTitle": "Event details", + "userAgent": "User agent", + "linkedUser": "Linked user", + "datetime": "Time", + "linkedFile": "Linked file", + "linkedEntity": "Linked Blob", + "linkedShare": "Linked share", + "rawContent": "Raw content", + "confirmDelete": "Are you sure you want to delete this event?", + "deleteXEvents": "Delete {{num}} events", + "confirmBatchDelete": "Are you sure you want to delete {{num}} events?" + }, + "share": { + "confirmBatchDelete": "Are you sure you want to delete {{num}} shares?", + "confirmDelete": "Are you sure you want to delete this share?", + "deleteXShares": "Delete {{num}} shares", + "shareDialogTitle": "Share details", + "shareLink": "Share link", + "deleted": "File deleted", + "srcFileName": "Source file", + "views": "Views", + "downloads": "Downloads", + "price": "Price", + "autoExpire": "Auto expire", + "owner": "Owner", + "createdAt": "Created at", + "private": "Hide from profile page", + "yes": "Yes", + "no": "No", + "afterNDownloads": "After {{num}} download(s).", + "none": "None", + "srcType": "Source object type", + "folder": "Folder", + "file": "File" + }, + "task": { + "cleanupTasks": "Cleanup tasks", + "cleanupTasksDescription": "Cleanup all tasks that meet the following conditions:", + "cleanupNotAfter": "Before this date", + "cleanupTaskTypes": "Task types", + "cleanupTaskTypesDes": "Select the task types to clean up. Leave blank to clean up all types.", + "cleanupTaskStatuses": "Task statuses", + "cleanupTaskStatusesDes": "Select the task statuses to clean up. Leave blank to clean up all completed status tasks.", + "confirmDelete": "Are you sure you want to delete this task?", + "confirmBatchDelete": "Are you sure you want to delete {{num}} tasks?", + "deleteXTasks": "Delete {{num}} tasks", + "blobID": "Blob ID", + "retryIndex": "Retry index", + "entityError": "Blobs that failed to recycle", + "updatedAt": "Updated at", + "taskDialogTitle": "Task details", + "explicitEntityRecycle": "Explicitly recycle files Blobs: {{blobs}}", + "entityRecycleRoutine": "Scan and recycle files Blob", + "mediaMetadata": "Extract media meta of Blob <0>#{{entityID}}", + "uploadSentinelCheck": "Check status of upload session {{uploadSessionID}}", + "remoteDownload": "Remote download: ", + "owner": "Owner", + "content": "Content", + "status": "Status", + "create_archive": "Create archive", + "extract_archive": "Extract archive", + "relocate": "Relocate", + "remote_download": "Remote download", + "media_meta": "Media metadata", + "entity_recycle_routine": "Entity recycle routine", + "explicit_entity_recycle": "Explicit entity recycle", + "upload_sentinel_check": "Upload sentinel check", + "import": "External import", + "type": "Type", + "node": "Distributed node", + "createdBy": "Created by", + "ready": "Ready", + "downloading": "Downloading", + "paused": "Paused", + "seeding": "Seeding", + "error": "Error", + "finished": "Finished", + "canceled": "Canceled/Stopped", + "unknown": "Unknown", + "errorMsg": "Error message" + }, + "payment": { + "tradeNo": "Trade No.", + "productType": "Product type", + "providerID": "Provider ID", + "status": "Status", + "deleteXPayments": "Delete {{num}} payments" + }, + "customProps": { + "add": "Add", + "type": "Type", + "default": "Default value", + "actions": "Actions", + "text": "Text", + "number": "Number", + "boolean": "Checkbox", + "select": "Single select", + "multiSelect": "Multi select", + "user": "User", + "link": "Link", + "rating": "Rating", + "addProp": "Add property", + "editProp": "Edit property", + "icon": "Icon", + "iconDes": "<0>Iconify Icon name, leave blank to hide the icon.", + "id": "ID", + "idDes": "Property ID, ensure it is unique across all properties.", + "idPatternDes": "Only letters, numbers, underscores, and hyphens are allowed.", + "minLength": "Minimum length", + "maxLength": "Maximum length", + "emptyLimit": "Leave blank to not limit.", + "minValue": "Minimum value", + "maxValue": "Maximum value", + "options": "Options", + "optionsDes": "One option per line." + }, + "vas": { + "disableSubAddressEmail": "Disable sub-address email", + "disableSubAddressEmailDes": "After enabled, email addresses containing <0>+ cannot be used for sign-up.", + "confirmDelete": "Are you sure you want to delete these orders?", + "vas": "VAS", + "reports": "Reports", + "orders": "Payments", + "initialFiles": "Initial files", + "initialFilesDes": "Specify the files that the user initially owns after signups. Enter a file ID to search existing files.", + "filterEmailProvider": "Filter email provider", + "filterEmailProviderDisabled": "Disabled", + "filterEmailProviderWhitelist": "Whitelist", + "filterEmailProviderBlacklist": "Blacklist", + "filterEmailProviderDes": "Restrict the email provider for registration, third-party SSO login is not restricted.", + "filterEmailProviderRule": "Email domain filter rules", + "filterEmailProviderRuleDes": "Separate multiple fields with a semi-colon comma.", + "qqConnect": "QQ Connect", + "qqConnectHint": "When creating the application, please fill in the callback URL: {{url}}.", + "enableQQConnect": "Enable QQ Connect", + "enableQQConnectDes": "Whether to allow binding QQ, use QQ to login website.", + "loginWithoutBinding": "Login without registration", + "loginWithoutBindingDes": "After enabled, if a user sign-in from the 3rd-party but does not have a linked account, the system will create an account for them. Users sign-in this way will only be able to sign in using this 3rd-party in the future.", + "appid": "APP ID", + "appidDes": "The APP ID obtained from the application management page.", + "appKey": "APP KEY", + "appKeyDes": "The APP KEY obtained from the application management page.", + "overuseReminder": "Overuse reminder", + "overuseReminderDes": "Reminder email template sent to users after their capacity exceeds the limit due to expired VAS.", + "vasSetting": "VAS settings", + "storagePack": "Storage packs", + "purchasableGroups": "Memberships", + "giftCodes": "Gift codes", + "enable": "Enable", + "appID": "APP ID", + "appIDDes": "APPID of payment application.", + "rsaPrivate": "RSA application private key", + "rsaPrivateDes": "The RSA2 (SHA256) private key for the payment application, typically generated by you. For details, refer to <0>Generating RSA Keys.", + "alipayPublicKey": "Alipay public key", + "alipayPublicKeyDes": "Provided by Alipay, available in [Application Management] - [Application Information] - [API Signing Method].", + "wechatPay": "WeChat Pay", + "applicationID": "Application ID", + "applicationIDDes": "Public number or mobile application appid applied by merchants.", + "merchantID": "Merchant number", + "merchantIDDes": "The merchant number generated and issued by WeChat Pay.", + "apiV3Secret": "API v3 secret", + "apiV3SecretDes": "The merchant needs to set the secret in [Merchant Platform] - [API Security] before the request WeChat Pay. The length of the key is 32 bytes.", + "mcCertificateSerial": "Merchant certificate serial number", + "mcCertificateSerialDes": "Navigate to [API Security] - [API Certificate] - [View Certificate] to view the merchant API certificate serial number.", + "mcAPISecret": "Merchant API Secret", + "mcAPISecretDes": "Content of the secret file apiclient_key.pem.", + "payjs": "PAYJS", + "payjsWarning": "This service is provided by <0>PAYJS, a third-party platform, and any disputes arising from it are not the responsibility of Cloudreve developers.", + "mcNumber": "Merchant number", + "mcNumberDes": "Available in the PAYJS admin panel home page.", + "communicationSecret": "Communication key", + "otherSettings": "Other Settings", + "banBufferPeriod": "Suspend buffer period (seconds)", + "banBufferPeriodDes": "The maximum length of time that a user can maintain the capacity overage status, beyond which the user will be suspend by the system.", + "allowSellShares": "Allow pricing for shares", + "allowSellSharesDes": "Once enabled users can set a credit price for sharing and credit will be deducted for downloading.", + "creditPriceRatio": "Credit arrival rate (%)", + "creditPriceRatioDes": "The rate of credits actually arriving to the sharer for the purchase of a share with a set price for download.", + "creditPrice": "Credit price (penny)", + "creditPriceDes": "Price when recharging credits", + "add": "Add", + "name": "Name", + "price": "Price", + "duration": "Duration", + "size": "Size", + "actions": "Actions", + "orCredits": " Or {{num}} credits", + "highlight": "Highlight", + "yes": "Yes", + "no": "No", + "productName": "Product name", + "qyt": "Qyt.", + "code": "Code", + "status": "Status", + "invalidProduct": "Invalid product", + "used": "Used", + "notUsed": "Not used", + "generatingResult": "Result", + "addStoragePack": "Add storage pack", + "editStoragePack": "Edit storage pack", + "productNameDes": "Product display name", + "packSizeDes": "Size of storage pack", + "durationDay": "Duration (day)", + "durationDayDes": "Valid duration of each storage pack.", + "priceYuan": "Price (Yuan)", + "packPriceDes": "Price of storage pack.", + "priceCredits": "Price (Credits)", + "priceCreditsDes": "The price when using credits to buy, fill in 0 means you can't use credits to buy.", + "editMembership": "Edit membership", + "addMembership": "Add membership", + "group": "Group", + "groupDes": "User groups upgraded after purchase.", + "durationGroupDes": "The validity of the purchase time of the user group unit upgraded after the purchase.", + "groupPriceDes": "Membership price", + "productDescription": "Product description (Once per line)", + "productDescriptionDes": "Description of the product displayed on the purchase page.", + "highlightDes": "After enabled, it will be highlighted on the product selection page.", + "generateGiftCode": "Generate gift codes", + "numberOfCodes": "Number of codes", + "numberOfCodesDes": "Number of gift codes to generate.", + "linkedProduct": "Linked product", + "productQyt": "Product qyt.", + "productQytDes": "For credit products, this is the number of points and other products are multiples of durations.", + "freeDownload": "Download shared files for free", + "freeDownloadDes": "After enabled, user can download paid shares for free.", + "credits": "Credits", + "markSuccessful": "Marked successfully.", + "markAsResolved": "Mark as resolved", + "reportedContent": "Reported content", + "reason": "Reason", + "description": "Description", + "reportTime": "Reported at", + "invalid": "[Invalid]", + "deleteShare": "Delete share link", + "orderDeleted": "Order deleted.", + "orderName": "Name", + "product": "Product", + "paymentId": "Payment ID", + "orderNumber": "Trade No.", + "amount": "Amount", + "paidBy": "Paid with", + "orderOwner": "Created by", + "unpaid": "Unpaid", + "paid": "Paid", + "shareLink": "Shared link", + "mobileApp": "Mobile application", + "showAppPromotion": "Show promotion page", + "showAppPromotionDes": "After enabled, user can see the guidance page for mobile application in \"Connect & Mount\" page.", + "customPaymentName": "Payment method name", + "customPaymentNameDes": "Name of the payment method used to display to the user.", + "customPaymentSecretDes": "Secret key for signing payment requests.", + "customPaymentEndpoint": "Payment API URL", + "customPaymentEndpointDes": "URL to be requested when creating a payment order.", + "appFeedback": "Feedback URL", + "appForum": "User forum URL", + "appLinkDes": "Will be displayed in mobile client, leave empty to hide menu item. This setting will take effect only if VOL license is valid." + }, + "pro": { + "title": "Pro edition exclusive features", + "description": "The feature you are trying to access is only available in the Cloudreve Pro edition, upgrade to unlock all advanced features.", + "proInclude": "Pro edition includes:", + "shareLinkCollabration": "Collaboration via share links", + "filePermission": "File permission management", + "multipleStoragePolicy": "Multiple storage policies and directory storage policy switching", + "auditAndActivity": "File and system activity logs", + "vasService": "VAS service and credit system", + "sso": "SSO single sign-on", + "more": "......", + "later": "Maybe later", + "learnMore": "Learn more", + "promotionTitle": "Community edition upgrade special discount", + "promotion": "Use the promotion code <0>{{code}} when purchasing to apply a <1>-{{discount}}% discount." + }, + "abuseReport": { + "deleteXAbuseReports": "Delete {{num}} abuse reports", + "folderPath": "Folder path", + "reporter": "Reporter", + "shareLink": "Shared link <0>#{{id}}", + "deletedShare": "Deleted shared link", + "deletedUser": "Deleted user", + "confirmDelete": "Are you sure you want to delete this abuse report?", + "confirmBatchDelete": "Are you sure you want to delete {{num}} abuse reports?", + "reporterID": "Reporter user ID", + "reportedUserID": "Reported user ID", + "shareID": "Shared ID", + "reason": "Reason" + } +} diff --git a/public/locales/en-US/image_editor.json b/public/locales/en-US/image_editor.json new file mode 100755 index 0000000..ba29e42 --- /dev/null +++ b/public/locales/en-US/image_editor.json @@ -0,0 +1,113 @@ +{ + "name": "Name", + "save": "Save", + "saveAs": "Save as", + "back": "Back", + "loading": "Loading...", + "resetOperations": "Reset/delete all operations", + "changesLoseWarningHint": "If you press button “reset†your changes will be lost. Would you like to continue?", + "discardChangesWarningHint": "If you close modal, your last change will not be saved.", + "cancel": "Cancel", + "apply": "Apply", + "warning": "Warning", + "confirm": "Confirm", + "discardChanges": "Discard changes", + "undoTitle": "Undo last operation", + "redoTitle": "Redo last operation", + "showImageTitle": "Show original image", + "zoomInTitle": "Zoom in", + "zoomOutTitle": "Zoom out", + "toggleZoomMenuTitle": "Toggle zoom menu", + "adjustTab": "Adjust", + "finetuneTab": "Finetune", + "filtersTab": "Filters", + "watermarkTab": "Watermark", + "annotateTabLabel": "Annotate", + "resize": "Resize", + "resizeTab": "Resize", + "imageName": "Image name", + "invalidImageError": "Invalid image provided.", + "uploadImageError": "Error while uploading the image.", + "areNotImages": "are not images", + "isNotImage": "is not image", + "toBeUploaded": "to be uploaded", + "cropTool": "Crop", + "original": "Original", + "custom": "Custom", + "square": "Square", + "landscape": "Landscape", + "portrait": "Portrait", + "ellipse": "Ellipse", + "classicTv": "Classic TV", + "cinemascope": "Cinemascope", + "arrowTool": "Arrow", + "blurTool": "Blur", + "brightnessTool": "Brightness", + "contrastTool": "Contrast", + "ellipseTool": "Ellipse", + "unFlipX": "Un-Flip X", + "flipX": "Flip X", + "unFlipY": "Un-Flip Y", + "flipY": "Flip Y", + "hsvTool": "HSV", + "hue": "Hue", + "brightness": "Brightness", + "saturation": "Saturation", + "value": "Value", + "imageTool": "Image", + "importing": "Importing...", + "addImage": "+ Add image", + "uploadImage": "Upload image", + "fromGallery": "From gallery", + "lineTool": "Line", + "penTool": "Pen", + "polygonTool": "Polygon", + "sides": "Sides", + "rectangleTool": "Rectangle", + "cornerRadius": "Corner Radius", + "resizeWidthTitle": "Width in pixels", + "resizeHeightTitle": "Height in pixels", + "toggleRatioLockTitle": "Toggle ratio lock", + "resetSize": "Reset to original image size", + "rotateTool": "Rotate", + "textTool": "Text", + "textSpacings": "Text spacings", + "textAlignment": "Text alignment", + "fontFamily": "Font family", + "size": "Size", + "letterSpacing": "Letter Spacing", + "lineHeight": "Line height", + "warmthTool": "Warmth", + "addWatermark": "+ Add watermark", + "addTextWatermark": "+ Add text watermark", + "addWatermarkTitle": "Choose the watermark type", + "uploadWatermark": "Upload watermark", + "addWatermarkAsText": "Add as text", + "padding": "Padding", + "paddings": "Paddings", + "shadow": "Shadow", + "horizontal": "Horizontal", + "vertical": "Vertical", + "blur": "Blur", + "opacity": "Opacity", + "transparency": "Transparency", + "position": "Position", + "stroke": "Stroke", + "saveAsModalTitle": "Save as", + "extension": "Extension", + "format": "Format", + "nameIsRequired": "Name is required.", + "quality": "Quality", + "imageDimensionsHoverTitle": "Saved image size (width x height)", + "cropSizeLowerThanResizedWarning": "Note, the selected crop area is lower than the applied resize which might cause quality decrease", + "actualSize": "Actual size (100%)", + "fitSize": "Fit size", + "addImageTitle": "Select image to add...", + "mutualizedFailedToLoadImg": "Failed to load image.", + "tabsMenu": "Menu", + "download": "Download", + "width": "Width", + "height": "Height", + "plus": "+", + "cropItemNoEffect": "No preview available for this crop item" +} \ No newline at end of file diff --git a/public/locales/en-US/markdown_editor.json b/public/locales/en-US/markdown_editor.json new file mode 100755 index 0000000..8a901c1 --- /dev/null +++ b/public/locales/en-US/markdown_editor.json @@ -0,0 +1,114 @@ +{ + "frontmatterEditor": { + "title": "Edit document frontmatter", + "key": "Key", + "value": "Value", + "addEntry": "Add entry" + }, + "dialogControls": { + "save": "Save", + "cancel": "Cancel" + }, + "uploadImage": { + "dialogTitle": "Upload image", + "uploadInstructions": "Upload an image from your device:", + "addViaUrlInstructions": "Or add an image from an URL / relative path (relative to the current file):", + "autoCompletePlaceholder": "Select or paste an image URL", + "addViaUrlInstructionsNoUpload": "Image URL:", + "alt": "Alt:", + "title": "Title:" + }, + "imageEditor": { + "deleteImage": "Delete image", + "editImage": "Edit image" + }, + "createLink": { + "url": "URL", + "urlPlaceholder": "Select or paste an URL", + "title": "Title", + "saveTooltip": "Set URL", + "cancelTooltip": "Cancel change" + }, + "linkPreview": { + "open": "Open {{url}} in new window", + "edit": "Edit link", + "copyToClipboard": "Copy to clipboard", + "copied": "Copied!", + "remove": "Remove link" + }, + "table": { + "deleteTable": "Delete table", + "columnMenu": "Column menu", + "textAlignment": "Text alignment", + "alignLeft": "Align left", + "alignCenter": "Align center", + "alignRight": "Align right", + "insertColumnLeft": "Insert a column to the left of this one", + "insertColumnRight": "Insert a column to the right of this one", + "deleteColumn": "Delete this column", + "rowMenu": "Row menu", + "insertRowAbove": "Insert a row above this one", + "insertRowBelow": "Insert a row below this one", + "deleteRow": "Delete this row" + }, + "toolbar": { + "blockTypes": { + "paragraph": "Paragraph", + "quote": "Quote", + "heading": "Heading {{level}}" + }, + "blockTypeSelect": { + "selectBlockTypeTooltip": "Select block type", + "placeholder": "Block type" + }, + "toggleGroup": "toggle group", + "removeBold": "Remove bold", + "bold": "Bold", + "removeItalic": "Remove italic", + "italic": "Italic", + "underline": "Remove underline", + "removeUnderline": "Underline", + "removeInlineCode": "Remove code format", + "inlineCode": "Inline code format", + "link": "Create link", + "richText": "Rich text", + "diffMode": "Diff mode", + "source": "Source mode", + "admonition": "Insert Admonition", + "codeBlock": "Insert Code Block", + "editFrontmatter": "Edit frontmatter", + "insertFrontmatter": "Insert frontmatter", + "image": "Insert image", + "insertSandpack": "Insert Sandpack", + "table": "Insert Table", + "thematicBreak": "Insert thematic break", + "bulletedList": "Bulleted list", + "numberedList": "Numbered list", + "checkList": "Check list", + "deleteSandpack": "Delete this code block", + "undo": "Undo {{shortcut}}", + "redo": "Redo {{shortcut}}", + "superscript": "Superscript", + "subscript": "Subscript", + "strikethrough": "Strikethrough", + "removeSubscript": "Remove subscript", + "removeSuperscript": "Remove superscript", + "removeStrikethrough": "Remove strikethrough" + }, + "admonitions": { + "note": "Note", + "tip": "Tip", + "danger": "Danger", + "info": "Info", + "caution": "Caution", + "changeType": "Select admonition type", + "placeholder": "Admonition type" + }, + "codeBlock": { + "language": "Code block language", + "selectLanguage": "Select code block language" + }, + "contentArea": { + "editableMarkdown": "Editable markdown" + } +} \ No newline at end of file diff --git a/public/locales/es-ES/application.json b/public/locales/es-ES/application.json new file mode 100755 index 0000000..b3508a3 --- /dev/null +++ b/public/locales/es-ES/application.json @@ -0,0 +1,1109 @@ +{ + "login": { + "lastStep": "Último paso", + "siginToYourAccount": "Inicia sesión en tu cuenta", + "createNewAccount": "Crear nueva cuenta", + "enterPassword": "Ingresa tu contraseña", + "enterPasswordHint": "Por favor ingresa la contraseña para {{email}}", + "paswordlessHint": "La cuenta {{email}} no tiene contraseña, selecciona uno de los siguientes métodos de autenticación:", + "noAccountSignupNow": "¿No tienes cuenta? <0>Regístrate ahora", + "haveAccountSignInNow": "¿Ya tienes cuenta? <0>Inicia sesión ahora", + "privacyPolicy": "Política de privacidad", + "termOfUse": "Términos de uso", + "signupHint": "La cuenta {{email}} no existe, ¿quieres registrarte ahora?", + "accountNotFoundHint": "La cuenta {{email}} que ingresaste no existe.", + "or": "O", + "selectAccountToUse": "Selecciona una cuenta para usar", + "useOtherAccount": "Usar otra cuenta", + "email": "Correo electrónico", + "password": "Contraseña", + "captcha": "Código de verificación", + "captchaError": "Error al cargar el código de verificación: {{message}}", + "signIn": "Iniciar sesión", + "signUp": "Registrarse", + "signUpAccount": "Registrar cuenta", + "useFIDO2": "Usar clave de acceso", + "usePassword": "Usar contraseña", + "forgetPassword": "¿Olvidaste tu contraseña?", + "2FA": "Verificación de dos factores", + "input2FACode": "Por favor ingresa el código de verificación de 6 dígitos", + "passwordNotMatch": "Las contraseñas no coinciden", + "findMyPassword": "Recuperar contraseña", + "passwordReset": "Contraseña restablecida", + "newPassword": "Nueva contraseña", + "repeatNewPassword": "Repetir nueva contraseña", + "repeatPassword": "Repetir contraseña", + "resetPassword": "Restablecer contraseña", + "backToSingIn": "Volver al inicio de sesión", + "sendMeAnEmail": "Enviar correo de restablecimiento", + "resetEmailSent": "Se ha enviado el correo de restablecimiento, por favor revísalo", + "browserNotSupport": "No compatible con el navegador o entorno actual", + "success": "Inicio de sesión exitoso", + "signUpSuccess": "Registro exitoso", + "activateSuccess": "Activación exitosa", + "accountActivated": "Tu cuenta ha sido activada exitosamente", + "title": "Iniciar sesión en {{title}}", + "sinUpTitle": "Registrarse en {{title}}", + "activateTitle": "Activación por correo", + "activateDescription": "Se ha enviado un correo de activación a tu dirección de correo electrónico. Por favor visita el enlace en el correo para completar el registro.", + "continue": "Siguiente", + "back": "Anterior", + "logout": "Cerrar sesión", + "signingOut": "Cerrando sesión...", + "loggedOut": "Has cerrado sesión", + "clickToRefresh": "Haz clic para actualizar el código de verificación", + "switchLanguage": "Cambiar idioma" + }, + "navbar": { + "notBefore": "No antes de", + "notAfter": "No después de", + "minimum": "Mínimo", + "maximum": "Máximo", + "fileSize": "Tamaño de archivo", + "searchBase": "Buscar en", + "searchInBase": "Buscar en <0>", + "conditionDuplicate": "La condición ya existe", + "fileType": "Tipo de archivo", + "addCondition": "Agregar condición", + "notNameOpOr": "Debe contener todas las palabras clave", + "caseFolding": "Ignorar mayúsculas y minúsculas", + "keywords": "Palabras clave", + "fileNameKeywordsHelp": "Presiona Enter para agregar palabra clave", + "advancedSearch": "Búsqueda avanzada", + "searchFilesTitle": "Buscar archivos", + "searchIn": "Buscar <0>{{keywords}}", + "recentlyViewed": "Visto recientemente", + "searchFiles": "Buscar archivos...", + "showMore": "Más", + "myFiles": "Mis archivos", + "hisFiles": "Sus archivos", + "trash": "Papelera", + "sharedWithMe": "Compartido conmigo", + "myShare": "Mis compartidos", + "remoteDownload": "Descarga remota", + "connect": "Conectar y montar", + "taskQueue": "Tareas en segundo plano", + "setting": "Configuración", + "videos": "Videos", + "photos": "Fotos", + "music": "Música", + "documents": "Documentos", + "addATag": "Agregar etiqueta...", + "addTagDialog": { + "selectFolder": "Seleccionar carpeta", + "fileSelector": "Clasificador de archivos", + "folderLink": "Acceso directo de carpeta", + "tagName": "Nombre de etiqueta", + "matchPattern": "Patrón de coincidencia de nombre de archivo", + "matchPatternDescription": "Puedes usar <0>* como comodín. Por ejemplo, <1>*.png significa coincidir con imágenes en formato PNG. Las reglas de múltiples líneas operarán con una relación \"o\" entre ellas.", + "icon": "Icono:", + "color": "Color:", + "folderPath": "Ruta de la carpeta" + }, + "storage": "Espacio de almacenamiento", + "storageDetail": "{{used}} usado de {{total}}", + "notLoginIn": "Sin iniciar sesión", + "visitor": "Visitante", + "objectsSelected": "{{num}} seleccionados", + "searchPlaceholder": "Presiona <0>/ para buscar", + "backToHomepage": "Volver a la página principal", + "darkModeSwitch": "Cambiar a modo oscuro", + "toDarkMode": "Oscuro", + "toLightMode": "Claro", + "myProfile": "Mi perfil", + "dashboard": "Panel de control" + }, + "fileManager": { + "currentStoragePolicy": "Política de almacenamiento actual: {{policy}}", + "customProps": "Propiedades personalizadas", + "rating": "Calificación", + "description": "Descripción", + "add": "Agregar", + "clickToEdit": "Haz clic para editar...", + "clickToEditSelect": "Haz clic para seleccionar...", + "enterUrl": "Ingresa URL...", + "searchUser": "Buscar usuario...", + "typeToSearch": "Ingresa nombre o email...", + "searchProperty": "Buscar archivos con la misma propiedad", + "quality": "Calidad", + "audioTrack": "Audio", + "auto": "Automático", + "default": "Predeterminado", + "shareWithMeEmpty": "No se encontraron archivos compartidos", + "shareWithMeEmptyDes": "Si necesitas ver los archivos compartidos por otros aquí, por favor guarda el acceso directo en cualquier ubicación de tus archivos cuando visites un enlace compartido.", + "selectAll": "Seleccionar todo", + "selectNone": "Deseleccionar todo", + "invertSelection": "Invertir selección", + "imageSize": "Tamaño de imagen", + "focalLength": "Distancia focal", + "columnExisted": "La columna ya existe.", + "metadataColumn": "Metadatos ({{metadata}})", + "column": "Columna", + "listColumnSetting": "Configuración de columnas", + "addColumn": "Agregar columnas", + "failedLoadPreview": "Error al cargar vista previa.", + "recursiveLimitReached": "Límite de profundidad de búsqueda alcanzado.", + "recursiveLimitReachedDes": "El sistema ha dejado de buscar en carpetas más profundas, por favor intenta reducir el alcance de búsqueda.", + "searchConditions": "{{num}} condición(es)", + "createDate": "Fecha de creación", + "updatedDate": "Fecha de modificación", + "cameraMake": "Fabricante de cámara", + "cameraModel": "Modelo de cámara", + "lensModel": "Modelo de lente", + "lensMake": "Fabricante de lente", + "metadataKey": "Clave", + "metadataValue": "Valor", + "metadata": "Metadatos", + "symbolicFile": "Enlace simbólico", + "relocation": "Transferir política de almacenamiento", + "downloadingFile": "Descargando \"{{name}}\", por favor no cierres esta página...", + "mountOwner": "Solo el propietario de la carpeta actual puede montar políticas.", + "uploading": "Subiendo", + "noActionsCanBeDone": "No se pueden realizar acciones.", + "newFileName": "Nuevo archivo.{{ext}}", + "newDocumentType": "{{display_name}} (.{{ext}})", + "text": "Texto", + "diagram": "Diagrama", + "whiteboard": "Pizarra", + "selectApplications": "Seleccionar aplicaciones...", + "newlyCreatedFolder": "Nueva carpeta", + "expandAllApp": "Expandir todas las aplicaciones", + "epubViewer": "Lector ePub", + "googledocs": "Visor de Google Docs", + "m365viewer": "Visor en línea de Microsoft Office", + "pdfViewer": "Visor PDF", + "archivePreview": "Vista previa del archivo", + "extractSelected": "Extraer archivos seleccionados", + "viewerFileSizeWarning": "El tamaño del archivo abierto ({{file_size}}) excede el límite ({{max}}) de {{app}}, podría no funcionar correctamente.", + "testSubtitleStyle": "Probar estilo de subtítulos AaBbCc", + "color": "Color", + "fontSize": "Tamaño de fuente", + "disableSubtitle": "Desactivar subtítulos", + "noSubtitle": "No se encontraron archivos de subtítulos ASS/SRT/VTT en la carpeta actual.", + "subtitleStyles": "Estilos de subtítulos", + "subtitles": "Subtítulos", + "markdownEditor": "Editor Markdown", + "saveSuccess": "Guardado exitosamente a las {{time}}", + "drawioLng": "es", + "charset": "Codificación", + "textType": "Tipo de texto", + "fileSaved": "Archivo guardado.", + "failedToLoadFile": "Error al cargar archivo: {{msg}}", + "monacoEditor": "Editor de código Monaco", + "preparingOpenFile": "Preparando para abrir archivo...", + "openWithDescription": "Selecciona una aplicación para abrir el archivo .{{ext}}.", + "openWith": "Abrir con", + "readOnly": "Solo lectura", + "save": "Guardar", + "noMoreImages": "No se encontraron imágenes en la página actual.", + "imageViewer": "Visor de imágenes", + "logFileDeleteShare": "Eliminó un enlace compartido", + "logFileEditShare": "Editó un enlace compartido", + "deleteShareWarning": "¿Estás seguro de eliminar este enlace compartido?", + "edit": "Editar", + "editAndReactivate": "Editar y reactivar", + "yes": "Sí", + "no": "No", + "permanentValid": "Permanente", + "manageShares": "Administrar enlaces compartidos", + "manageDirectLinks": "Administrar enlaces directos", + "deleteLinkConfirm": "¿Estás seguro de eliminar este enlace directo?", + "directLinkNotFound": "El enlace directo que buscas no existe.", + "versionNotFound": "La versión que buscas no existe.", + "setNow": "Establecer ahora", + "permissionNotSet": "Sin permisos establecidos", + "permissionNotSetDes": "Seguirá la configuración de permisos del directorio padre o del enlace compartido.", + "permissions": "Permisos", + "logFileUpdateMetadata": "Actualizó metadatos", + "all": "Todo", + "updatesOnly": "Solo actualizaciones", + "readsOnly": "Solo lecturas", + "myActivitiesOnly": "Solo mis actividades", + "logUpdateView": "Actualizó configuración de vista", + "logDeleteDirectLink": "Eliminó enlace directo", + "logFileImported": "Importado desde archivo externo", + "logGetDirectLink": "Creó un <0>enlace directo", + "logFileMount": "Montar política de almacenamiento en \"{{name}}\"", + "lookForThisVersion": "Buscar esta versión", + "logFileThumbGenerated": "Activó generación de miniaturas", + "logFileLivePhotoUploaded": "Subió Live Photo", + "logFileCreate": "Creó este objeto", + "logFileRename": "Renombró este objeto de \"{{originalName}}\" a \"{{newName}}\"", + "logFileSetPermission": "Cambió permisos de este objeto", + "logFileEntityUpload": "Actualizó contenido del archivo", + "logFileCopyFrom": "Objeto creado copiando desde <0> a <1>", + "logFileCopyTo": "Fue copiado desde <0> a <1><1/>", + "logFileMoveTo": "Movido desde <0> a <1>", + "logFileMoveToTrash": "Movido a la papelera desde <0>", + "logFileShare": "Compartió este objeto", + "logFileSetCurrentVersion": "Revirtió versión del archivo a <0>", + "logFileDeleteVersion": "Eliminó la versión creada en <0>", + "logEntityDownloaded": "Descargó o leyó este objeto", + "logDirectLinkDownloaded": "Descargado vía <0>enlace directo", + "logRelocate": "Reubicó política de almacenamiento a {{newPolicy}}", + "logCreateArchive": "Agregado al archivo comprimido <0>", + "logExtractArchive": "Extraído a <0>", + "deleteVersionWarning": "¿Estás seguro de eliminar esta versión? Esta operación no se puede deshacer.", + "setAsCurrent": "Establecer como versión actual", + "current": "[Actual]", + "createdBy": "Creado por", + "manageVersions": "Administrar versiones", + "livePhoto": "Live Photo", + "version": "Versión", + "actions": "Acciones", + "versionEntity": "Datos del archivo y versiones", + "data": "Datos", + "owned": "Propio", + "ownedSymbolic": "Propio (Enlace simbólico)", + "expires": "Expira", + "originalLocation": "Ubicación original", + "myPermissions": "Mis permisos", + "descendant": "Descendiente", + "folderChildren": "{{files}} archivo(s), {{folders}} carpeta(s)", + "moreThan": "Más de {{text}}", + "calculate": "Calcular", + "unset": "Sin establecer", + "folder": "Carpeta", + "file": "Archivo", + "symbolicLink": "Enlace simbólico ({{srcType}})", + "type": "Tipo", + "storageUsed": "Almacenamiento usado", + "location": "Ubicación", + "basicInfo": "Información básica", + "format": "Formato", + "duration": "Duración", + "artist": "Artista", + "album": "Ãlbum", + "title": "Título", + "resolution": "Resolución", + "takenAt": "Tomado en", + "software": "Software", + "copyright": "Derechos de autor", + "exposureBias": "Compensación de exposición", + "flash": "Flash", + "copyToClipboard": "Copiar al portapapeles", + "searchSomething": "Buscar \"{{text}}\"...", + "iso": "ISO", + "exposureValue": "{{num}} s", + "exposure": "Exposición", + "aperture": "Apertura", + "address": "Dirección", + "street": "Calle", + "locality": "Localidad", + "place": "Ciudad", + "district": "Distrito", + "region": "Provincia", + "country": "País", + "mediaInfo": "Información multimedia", + "details": "Detalles", + "activity": "Actividad", + "goToSharedLink": "Ir al enlace compartido", + "saveShortcut": "Guardar enlace compartido como acceso directo", + "customizeIcon": "Personalizar icono", + "tags": "Etiquetas", + "apply": "Aplicar", + "customizeColor": "Personalizar color", + "folderColor": "Color de carpeta", + "restore": "Restaurar", + "unpin": "Desanclar", + "youDontHaveReadPermissionToThisFile": "No tienes permisos de acceso.", + "anonymousAccessDenied": "No tienes permisos de acceso, por favor intenta iniciar sesión.", + "sharedWithOthers": "Compartido con otros", + "new": "Nuevo", + "open": "Abrir", + "openParentFolder": "Ir a carpeta padre", + "download": "Descargar", + "batchDownload": "Descarga por lotes", + "share": "Compartir", + "rename": "Renombrar", + "organize": "Organizar", + "pin": "Anclar a la barra lateral", + "pinAlias": "Nombre para mostrar", + "optional": "Opcional", + "move": "Mover", + "delete": "Eliminar", + "moreActions": "Más acciones", + "refresh": "Actualizar", + "createArchive": "Crear archivo comprimido", + "resetThumbnail": "Restablecer miniatura dañada", + "resetThumbnailRequested": "Se solicitó restablecer la miniatura.", + "noFileCanResetThumbnail": "No hay archivos disponibles para restablecer la miniatura.", + "newFolder": "Nueva carpeta", + "newFile": "Nuevo archivo", + "showFullPath": "Mostrar ruta completa", + "listView": "Lista", + "gridView": "Cuadrícula", + "galleryView": "Galería", + "paginationSize": "Paginación", + "paginationOption": "{{option}} / página", + "noPagination": "Sin paginación", + "sortMethod": "Ordenar", + "sortMethods": { + "A-Z": "A-Z", + "Z-A": "Z-A", + "oldestUploaded": "Más antiguo subido", + "newestUploaded": "Más reciente subido", + "oldestModified": "Más antiguo modificado", + "newestModified": "Más reciente modificado", + "smallest": "Más pequeño", + "largest": "Más grande" + }, + "shareCreateBy": "Creado por {{nick}}", + "name": "Nombre", + "size": "Tamaño", + "lastModified": "Última modificación", + "currentFolder": "Carpeta actual", + "backToParentFolder": "Volver a la carpeta padre", + "folders": "Carpetas", + "files": "Archivos", + "listError": "Error al listar archivos", + "dropFileHere": "Arrastra y suelta el archivo aquí", + "orClickUploadButton": "O haz clic en el botón \"Nuevo\" en la parte superior izquierda para agregar un archivo", + "nothingFound": "No se encontró nada", + "uploadFiles": "Subir archivos", + "uploadFolder": "Subir carpeta", + "newRemoteDownloads": "Nueva descarga remota", + "enter": "Entrar", + "getSourceLink": "Obtener enlace directo", + "createRemoteDownloadForTorrent": "Nueva descarga remota", + "extractArchive": "Extraer archivo", + "createShareLink": "Compartir", + "viewDetails": "Ver detalles", + "copy": "Copiar", + "bytes": " ({{bytes}} Bytes)", + "storagePolicy": "Política de almacenamiento", + "inheritedFromParent": "(Heredado del padre)", + "childFolders": "Carpetas hijas", + "childFiles": "Archivos hijos", + "childCount": "{{num}}", + "parentFolder": "Carpeta padre", + "rootFolder": "Carpeta raíz", + "modifiedAt": "Modificado en", + "createdAt": "Creado en", + "statisticAt": "Estadística en", + "musicPlayer": "Reproductor de música", + "closeAndStop": "Cerrar y detener", + "playInBackground": "Reproducir en segundo plano", + "copyTo": "Copiar a", + "copyToDst": "Copiar a <0>", + "moveTo": "Mover a", + "moveToDst": "Mover a <0>", + "errorReadFileContent": "Error al leer contenido del archivo: {{msg}}", + "wordWrap": "Ajuste de línea", + "pdfLoadingError": "Error al cargar PDF: {{msg}}", + "subtitleSwitchTo": "Subtítulos cambiados a: {{subtitle}}", + "noSubtitleAvailable": "No hay archivos de subtítulos disponibles en la carpeta del video (soportados: ASS/SRT/VTT)", + "subtitle": "Subtítulos", + "playlist": "Lista de reproducción", + "openInExternalPlayer": "Abrir en reproductor externo", + "repeatMode": "Modo de repetición", + "listRepeat": "Repetir lista", + "singleRepeat": "Repetir uno", + "shuffle": "Aleatorio", + "playbackSpeed": "Velocidad de reproducción", + "searchResult": "Resultados de búsqueda", + "preparingBathDownload": "Preparando descarga por lotes...", + "preparingDownload": "Preparando descarga...", + "browserDownload": "Descarga del lado del navegador a carpeta local", + "browserDownloadDescription": "Tu navegador descarga archivos uno por uno y mantiene la estructura de carpetas en el directorio local que especifiques.", + "browserBatchDownload": "Archivado del lado del navegador", + "browserBatchDownloadDescription": "Descargado y empaquetado a un archivo Zip por el navegador en tiempo real, no puede manejar datos de más de 4GB.", + "serverBatchDownload": "Archivado del lado del servidor", + "serverBatchDownloadDescription": "Archivado por el servidor a un archivo Zip y enviado al cliente para descarga al vuelo, no se admiten accesos directos de enlaces compartidos.", + "selectArchiveMethod": "Seleccionar método de archivo", + "batchDownloadStarted": "La descarga por lotes ha comenzado, por favor no cierres esta pestaña...", + "batchDownloadError": "Error al archivar: {{msg}}", + "userDenied": "Usuario denegado.", + "directoryDownloadReplace": "Reemplazar", + "directoryDownloadReplaceDescription": "El archivo local \"{{name}}\" será reemplazado por el archivo descargado.", + "directoryDownloadSkip": "Omitir", + "directoryDownloadSkipDescription": "\"{{name}}\" será omitido.", + "selectDirectoryDuplicationMethod": "Archivo duplicado", + "directoryDownloadReplaceAll": "Reemplazar todo", + "directoryDownloadReplaceAllDescription": "Todos los archivos con el mismo nombre serán reemplazados por los archivos descargados.", + "directoryDownloadSkipAll": "Omitir todo", + "directoryDownloadSkipAllDescription": "Todos los archivos con el mismo nombre serán omitidos.", + "directoryDownloadStarted": "Descarga iniciada, por favor no cierres esta pestaña.", + "directoryDownloadFinished": "Descarga finalizada, sin objetos fallidos.", + "directoryDownloadFinishedWithError": "Descarga finalizada, {{failed}} objeto(s) fallaron.", + "directoryDownloadPermissionError": "Permiso denegado, por favor permite leer y escribir archivos locales.", + "back": "Atrás", + "view": "Vista", + "layout": "Diseño", + "thumbnails": "Miniaturas", + "on": "Activado", + "off": "Desactivado", + "viewSetting": "Configuración de vista", + "saved": "Guardado", + "notSet": "Sin establecer", + "deleteViewSetting": "Eliminar configuración de vista" + }, + "modals": { + "includePasswordInShareLink": "Incluir contraseña en enlace compartido", + "includePasswordInShareLinkDes": "Si se selecciona, la contraseña se incluirá en el enlace compartido, y no se requerirá contraseña al acceder al enlace compartido.", + "showFileName": "Mostrar nombre de archivo", + "forceDownload": "Forzar descarga", + "archiveFile": "Archivar archivo", + "cancelDownload": "Cancelar descarga", + "always": "Siempre", + "justOnce": "Solo una vez", + "quality": "Calidad", + "saveAsOtherFormat": "Guardar como otro formato", + "conflictDes1": "Conflicto de versión de archivo, posibles razones son:", + "conflictDes2": "<0>El archivo fue actualizado a una nueva versión desde otro lugar después de que lo abrieras.<1>Si lo guardaste con un nuevo nombre o nueva ubicación, el nombre de archivo ya existe.", + "saveAs": "Guardar como", + "versionConflict": "Conflicto de versión", + "overwrite": "Sobrescribir", + "editShareLink": "Editar enlace compartido", + "clearPermissions": "Limpiar configuración de permisos", + "shortcutCreated": "Acceso directo creado.", + "createShortcut": "Crear acceso directo", + "createShortcutTo": "Crear acceso directo en <0>", + "read": "Leer", + "readDes": "Para archivos, puedes ver su contenido, metadatos, etc.; para carpetas, puedes ver la lista de archivos hijos y sus metadatos.", + "createDes": "Solo válido para carpetas, puedes crear o subir nuevos archivos bajo ella, y mover o copiar archivos a ella.", + "update": "Actualizar", + "updateDes": "Puedes modificar metadatos, renombrar objetos, y ver registros de actividad; para archivos, puedes actualizar su contenido.", + "delete": "Eliminar", + "deleteDes": "Puedes eliminar objetos o moverlos a otros lugares.", + "noAccess": "Sin acceso", + "targetExisted": "El destino ya existe.", + "explicitAccess": "Acceso explícito", + "generalAccess": "Acceso general", + "users": "Usuarios", + "groups": "Grupos", + "builtinCollections": "Colecciones integradas", + "everyone": "Todos los demás", + "otherGroup": "Otros grupos", + "sameGroup": "Mismo grupo que yo", + "anonymous": "Visitantes anónimos", + "noResults": "Sin resultados", + "searchGroupUser": "Buscar emails o grupos...", + "resetToDefault": "Restablecer a predeterminado", + "duplicateTag": "La etiqueta \"{{tag}}\" ya existe.", + "colorForTag": "Personalizar color para nuevas etiquetas", + "enterForNewTag": "Presiona enter para agregar nueva etiqueta.", + "manageTags": "Administrar etiquetas", + "onlyOwner": "Solo el propietario de este archivo puede forzar el desbloqueo.", + "forceUnlock": "Forzar desbloqueo", + "forceUnlockAll": "Forzar desbloqueo de todo", + "forceUnlockDes": "Forzar el desbloqueo puede corromper el estado del archivo, recomendamos esperar a que el archivo sea liberado activamente, ¿estás seguro de continuar desbloqueando?", + "webdav": "WebDAV", + "soft-delete": "Mover a papelera", + "updateMetadata": "Actualizar metadatos", + "upload": "Subir", + "moveCopy": "Mover o copiar", + "view": "Ver", + "cannotPerformAction": "No se admite mover o copiar archivos aquí.", + "cannotMoveCopyToChild": "No se puede mover o copiar a carpeta descendiente.", + "copySuccess": "{{num}} archivo(s) copiado(s) exitosamente.", + "moveSuccess": "{{num}} archivo(s) movido(s) exitosamente.", + "setPermission": "Establecer permisos", + "unknownParent": "Padre desconocido", + "unknownParentDes": "La carpeta ocupada es la carpeta padre de una carpeta compartida, y no es de tu propiedad.", + "lockConflictTitle": "Archivo ocupado", + "lockConflictDescription": "Esta operación no se puede completar porque el(los) siguiente(s) archivo(s) está(n) siendo usado(s) por otros actualmente, por favor intenta de nuevo más tarde. Si eres el propietario del archivo y estás seguro de que el archivo no está en uso, puedes forzar el desbloqueo del archivo y reintentar.", + "usedBy": "Usado por", + "application": "Aplicación", + "errorDetailsTitle": "Detalles del error", + "processingMoving": "Moviendo archivos...", + "processingCopying": "Copiando archivos...", + "processingRestoring": "Restaurando archivos...", + "fileRestored": "{{num}} archivo(s) restaurado(s) a su ubicación original.", + "duplicatedObjectName": "Nombre de archivo duplicado.", + "newNameLengthError": "La longitud del nombre de archivo debe estar en el rango de 1 a 255.", + "newNameCharacterError": "El nombre no debe contener ninguno de estos caracteres: \\ / : * ? \" < > |", + "newNameDotError": "El nombre no puede ser \".\" o \"..\"", + "taskCreated": "Tarea creada.", + "taskCreateFailed": "{{failed}} tarea(s) fallaron al crearse: {{details}}.", + "linkCopied": "Enlace copiado.", + "getSourceLinkTitle": "Obtener enlace directo", + "sourceLink": "Enlace directo", + "folderName": "Nombre de carpeta", + "create": "Crear", + "fileName": "Nombre de archivo", + "renameDescription": "Ingresa el nuevo nombre para <0>{{name}}:", + "newName": "Nuevo nombre", + "moveToDescription": "Mover a <0>{{name}}", + "saveToTitle": "Guardar en", + "saveToTitleDescription": "Guardar en <0>{{name}}", + "deleteTitle": "Eliminar objetos", + "deleteOneDescription": "¿Estás seguro de mover <0>{{name}} a la papelera?", + "deleteMultipleDescription": "¿Estás seguro de mover esos {{num}} objetos a la papelera?", + "deleteOneDescriptionHard": "¿Estás seguro de eliminar permanentemente <0>{{name}}?", + "trashRetention": "Los archivos en la papelera serán eliminados después de <0>{{num}}.", + "deleteMultipleDescriptionHard": "¿Estás seguro de eliminar permanentemente esos {{num}} objetos?", + "newRemoteDownloadTitle": "Nueva tarea de descarga remota", + "remoteDownloadURL": "URL de destino de descarga", + "remoteDownloadURLDescription": "Pega la URL de descarga, una URL por línea", + "remoteDownloadDst": "Descargar a", + "processNode": "Nodo de destino", + "remoteDownloadNodeAuto": "Despacho automático", + "createTask": "Crear tarea", + "downloadToDst": "Descargar a <0>{{name}}", + "downloadTo": "Descargar a", + "decompressTo": "Extraer a", + "decompressToDst": "Extraer a <0>{{name}}", + "defaultEncoding": "Predeterminado", + "chineseMajorEncoding": "", + "selectEncoding": "Codificación del archivo ZIP", + "password": "Contraseña", + "passwordDescription": "Si el archivo comprimido no está encriptado, por favor deja este campo vacío.", + "noEncodingSelected": "No se seleccionó método de codificación", + "listingFiles": "Listando archivos...", + "listingFileError": "Error al listar archivos: {{message}}", + "generatingSourceLinks": "Generando enlaces fuente...", + "noFileCanGenerateSourceLink": "No hay archivo que pueda usarse para generar enlace fuente", + "sourceBatchSizeExceeded": "El grupo de usuarios actual puede generar enlaces fuente para un máximo de {{limit}} archivos al mismo tiempo.", + "zipFileName": "Nombre del archivo comprimido", + "shareLinkShareContent": "Compartí contigo: {{name}} Enlace: {{link}}", + "shareLinkPasswordInfo": "Contraseña: {{password}}", + "createShareLink": "Crear enlace compartido", + "privateShare": "Proteger con contraseña", + "privateShareDes": "Si se selecciona, se requiere contraseña para acceder al enlace compartido.", + "useCustomPassword": "Contraseña personalizada del enlace compartido", + "shareView": "Configuración de vista compartida", + "shareViewDes": "Si se selecciona, otros usuarios pueden ver tu configuración de vista (diseño, ordenamiento, etc.) guardada en el servidor cuando accedan a esta carpeta compartida.", + "showReadme": "Mostrar archivo README", + "showReadmeDes": "Si se selecciona, el archivo <0>README.md (sensible a mayúsculas) en el directorio se mostrará automáticamente para los visitantes.", + "expireAfterDownload": "Expirar después de ser descargado", + "sharePassword": "Contraseña de compartir", + "randomlyGenerate": "Aleatorio", + "expireAutomatically": "Expiración automática", + "downloadLimitOptions": "{{num}} descargas", + "or": "O después de", + "5minutes": "5 minutos", + "1hour": "1 hora", + "1day": "1 día", + "7days": "7 días", + "30days": "30 días", + "custom": "Personalizado", + "minutes": "minutos", + "downloads": "descargas", + "expireSuffix": "", + "expirePrefix": "Expirar después de", + "allowPreview": "Habilitar vista previa", + "allowPreviewDescription": "Si permitir vista previa del contenido del archivo desde el enlace compartido", + "shareLink": "Enlace compartido", + "sendLink": "Enviar el enlace", + "directoryDownloadReplaceNotifiction": "Sobrescribir {{name}}", + "directoryDownloadSkipNotifiction": "Omitido {{name}}", + "directoryDownloadTitle": "Registros de descarga por lotes", + "directoryDownloadStarted": "Comenzar descarga \"{{name}}\"", + "directoryDownloadFinished": "Descarga finalizada \"{{name}}\"", + "directoryDownloadError": "Error: {{msg}}", + "directoryDownloadErrorNotification": "Ocurrió error al descargar {{name}}: {{msg}}", + "directoryDownloadAutoscroll": "Desplazamiento automático", + "directoryDownloadCancelled": "Descarga cancelada", + "advanceOptions": "Opciones avanzadas", + "skipSoftDelete": "Eliminar permanentemente", + "skipSoftDeleteDes": "Omitir mover a papelera, eliminar permanentemente", + "unlinkOnly": "Mantener archivos físicos", + "unlinkOnlyDes": "Eliminar solo registros de archivos, los archivos físicos no serán eliminados." + }, + "uploader": { + "fileCopyName": "Copia de ", + "overwriteTooltip": "Sobrescribir archivo existente si hay conflicto, solo funciona para tareas recién agregadas.", + "rename": "Reintentar con nuevo nombre", + "overwrite": "Sobrescribir archivo existente", + "pasteFilesHere": "Pegar archivos aquí", + "clipboardDefaultFileName": "Portapapeles {{date}}.png", + "uploadFromClipboard": "Subir desde portapapeles", + "uploadList": "Tareas de subida", + "fileNotMatchError": "El archivo seleccionado no coincide con el archivo original.", + "unknownError": "Ocurrió error desconocido: {{msg}}", + "taskListEmpty": "Sin tareas de subida.", + "hideTaskList": "Ocultar la lista", + "uploadTasks": "Tareas de subida", + "moreActions": "Más acciones", + "addNewFiles": "Agregar nuevos archivos", + "toggleTaskList": "Expandir/Contraer la lista", + "pendingInQueue": "Pendiente en cola...", + "preparing": "Preparando...", + "processing": "Procesando...", + "progressDescription": "{{uploaded}} subido, {{total}} total - {{percentage}}%", + "progressDescriptionFull": "{{uploaded}} subido, {{total}} total - {{percentage}}% ({{speed}})", + "progressDescriptionPlaceHolder": " - subido", + "uploaded": "Subido", + "rootFolder": "Carpeta raíz", + "unknownStatus": "Desconocido", + "resumed": "Reanudado", + "resumable": "Reanudable", + "retry": "Reintentar", + "deleteTask": "Eliminar tarea", + "cancelAndDelete": "Cancelar y eliminar", + "selectAndResume": "Seleccionar el mismo archivo y reanudar subida", + "fileName": "Nombre: ", + "fileSize": "Tamaño: ", + "sessionExpiredIn": "Expira <0>", + "chunkDescription": "({{total}} fragmentos, {{size}} cada uno)", + "noChunks": "(Sin fragmentos)", + "destination": "Destino: ", + "uploadSession": "Sesión de subida: ", + "storagePolicy": "Política de almacenamiento: ", + "errorDetails": "Detalles del error: ", + "uploadSessionCleaned": "Todas las sesiones de subida limpiadas.", + "hideCompletedTooltip": "Ocultar tareas completadas, fallidas y canceladas.", + "hideCompleted": "Ocultar tareas completadas", + "addTimeAscTooltip": "Las tareas agregadas primero se clasifican primero.", + "addTimeAsc": "Más antiguo a más reciente", + "addTimeDescTooltip": "Las últimas agregadas se clasifican primero.", + "addTimeDesc": "Más reciente a más antiguo", + "showInstantSpeedTooltip": "Las velocidades de subida de tareas se muestran como velocidad instantánea.", + "showInstantSpeed": "Velocidad instantánea", + "showAvgSpeedTooltip": "Las velocidades de subida de tareas se muestran como velocidades promedio.", + "showAvgSpeed": "Velocidad promedio", + "cleanAllSessionTooltip": "Limpiar todas las sesiones de subida pendientes en el lado del servidor.", + "cleanAllSession": "Limpiar todas las sesiones de subida", + "cleanCompletedTooltip": "Limpiar tareas completadas, fallidas y canceladas", + "cleanCompleted": "Limpiar tareas completadas", + "retryFailedTasks": "Reintentar todas las tareas fallidas", + "retryFailedTasksTooltip": "Reintentar todas las tareas fallidas en la cola actual", + "setConcurrentTooltip": "Establecer el número máximo de tareas que pueden subirse simultáneamente.", + "setConcurrent": "Establecer límite de tareas concurrentes", + "sizeExceedLimitError": "El tamaño del archivo excede los límites de la política de almacenamiento. (Máximo: {{max}})", + "suffixNotAllowedError": "La política de almacenamiento no admite subir archivos con esta extensión.", + "regexpNotAllowedError": "La política de almacenamiento no admite subir archivos con este nombre.", + "suffixAllowed": " (Soportados:{{supported}})", + "suffixDenied": " (Denegados:{{denied}})", + "createUploadSessionError": "No se puede crear sesión de subida", + "deleteUploadSessionError": "No se puede eliminar sesión de subida", + "requestError": "Solicitud fallida: {{msg}} ({{url}}).", + "chunkUploadError": "Error al subir fragmento [{{index}}].", + "conflictError": "La tarea de subida para archivos con el mismo nombre ya se está procesando.", + "chunkUploadErrorWithMsg": "Error al subir fragmento: {{msg}}", + "chunkUploadErrorWithRetryAfter": "(Por favor reintenta después de {{retryAfter}}s)", + "emptyFileError": "No se admite subir archivos vacíos a OneDrive, por favor crea archivos vacíos vía el botón Crear Archivo.", + "finishUploadError": "No se puede completar la subida del archivo.", + "finishUploadErrorWithMsg": "No se puede completar la subida del archivo: {{msg}}", + "ossFinishUploadError": "No se puede completar la subida del archivo: {{msg}} ({{code}})", + "cosUploadFailed": "Subida fallida: {{msg}} ({{code}})", + "upyunUploadFailed": "Subida fallida: {{msg}}", + "parseResponseError": "No se puede analizar respuesta: {{msg}} ({{content}})", + "concurrentTaskNumber": "Límite de tareas concurrentes", + "dropFileHere": "Soltar archivo para subir" + }, + "share": { + "free": "Gratis", + "price": "Precio", + "points": "{{num}} Puntos", + "statistics": "Estadísticas", + "expireAt": "Expira <0>", + "expireAfterDownloads": "Expira después de {{downloads}} descargas", + "somebodyShare": "Compartido por {{name}}", + "expiredLink": "Enlace expirado", + "sharedBy": "<0>{{nick}} te compartió {{num}} archivos", + "files": "1 archivo", + "files_other": "{{count}} archivos", + "statisticsViews": "{{views}} visualizaciones", + "statisticsDownloads": "{{downloads}} descargas", + "views": "{{count}} visualización", + "views_other": "{{count}} visualizaciones", + "downloads": "{{count}} descarga", + "downloads_other": "{{count}} descargas", + "privateShareTitle": "Compartido privado de {{nick}}", + "enterPassword": "Contraseña del enlace", + "continue": "Continuar", + "shareCanceled": "El enlace compartido ha sido eliminado", + "listLoadingError": "Error al cargar", + "sharedFiles": "Mis archivos compartidos", + "createdAtDesc": "Más reciente", + "createdAtAsc": "Más antiguo", + "noRecords": "No hay registros de archivos compartidos", + "sourceNotFound": "[Objeto original no existe]", + "expired": "Expirado", + "changeToPublic": "Cambiar a público", + "changeToPrivate": "Cambiar a privado", + "viewPassword": "Ver contraseña", + "disablePreview": "Desactivar vista previa", + "enablePreview": "Activar vista previa", + "cancelShare": "Cancelar compartir", + "sharePassword": "Contraseña del enlace", + "readmeError": "No se puede leer el contenido README: {{msg}}", + "enterKeywords": "Por favor ingresa palabras clave de búsqueda", + "searchResult": "Resultados de búsqueda", + "sharedAt": "Compartido en <0>", + "pleaseLogin": "Por favor inicia sesión primero", + "cannotShare": "Este archivo no se puede previsualizar", + "preview": "Vista previa", + "incorrectPassword": "Contraseña incorrecta", + "shareNotExist": "El enlace compartido no existe o ha expirado", + "copyLinkToClipboard": "Copiar enlace al portapapeles" + }, + "download": { + "noFilesFound": "No se encontraron archivos", + "filterByName": "Filtrar por nombre", + "selectAll": "Seleccionar todo", + "reverseSelect": "Selección inversa", + "cancelTaskConfirm": "¿Estás seguro de que quieres cancelar esta tarea?", + "saveChanges": "Guardar cambios", + "failedToLoad": "Error al cargar", + "active": "En progreso", + "finished": "Completado", + "activeEmpty": "No hay tareas de descarga en progreso", + "finishedEmpty": "No hay tareas de descarga completadas", + "loadMore": "Cargar más", + "taskFileDeleted": "Archivo eliminado", + "unknownTaskName": "[Desconocido]", + "taskCanceled": "Tarea cancelada, el estado se actualizará más tarde", + "operationSubmitted": "Operación enviada exitosamente, el estado se actualizará más tarde", + "deleteThisFile": "Eliminar este archivo", + "openDstFolder": "Abrir carpeta de destino", + "selectDownloadingFile": "Seleccionar archivos para descargar", + "cancelTask": "Cancelar tarea", + "updatedAt": "Actualizado en:", + "uploaded": "Tamaño de subida", + "uploadSpeed": "Velocidad de subida", + "InfoHash": "InfoHash", + "seederCount": "Sembradores:", + "seeding": "Sembrando:", + "downloadNode": "Nodo:", + "isSeeding": "Sí", + "notSeeding": "No", + "chunkSize": "Tamaño de fragmento:", + "chunkNumbers": "Número de fragmentos", + "taskDeleted": "Eliminación exitosa", + "transferFailed": "Error al transferir archivo", + "downloadFailed": "Error de descarga: {{msg}}", + "canceledStatus": "Cancelado", + "finishedStatus": "Completado", + "pending": "Completado, transferencia en cola", + "transferring": "Transferiendo", + "deleteRecord": "Eliminar registro", + "createdAt": "Fecha de creación:", + "unknownSize": "Tamaño de archivo desconocido" + }, + "setting": { + "notifyStoragePolicyChange": "Notificarme cuando cambie la política de almacenamiento", + "notifyStoragePolicyChangeDes": "Cuando esté habilitado, se mostrará una notificación al entrar a una carpeta vinculada a una política de almacenamiento diferente.", + "treeView": "Vista de árbol", + "autoExpandTreeView": "Expandir automáticamente la vista de árbol", + "autoExpandTreeViewDes": "Cuando esté habilitado, el árbol de archivos en la barra lateral seguirá la carpeta actual y se expandirá automáticamente.", + "syncView": "Configuración de vista", + "syncViewDes": "Recordar la configuración de vista de cada carpeta y sincronizar al servidor.", + "syncViewOn": "Sincronizar al servidor", + "syncViewOff": "No sincronizar", + "reason": "Razón", + "change": "Cambio", + "success": "Éxito", + "loginWithPasskey": "Clave de acceso - {{name}}", + "loginWith": "Iniciar sesión con", + "result": "Resultado", + "device": "Dispositivo", + "ip": "IP", + "time": "Tiempo", + "recentSignIn": "Actividades de inicio de sesión recientes", + "noAuthenticator": "Agregar clave de acceso para iniciar sesión usando huella dactilar, cara o llave USB", + "neverUsed": "Nunca usado", + "usedAt": "Último uso en <0>", + "passkeyName": "{browser} en {os}", + "passwordlessHint": "Esta cuenta no tiene contraseña", + "versionRetentionMax": "Número máximo de versiones, 0 significa sin límite", + "versionRetentionEnabledExt": "Extensiones de archivo habilitadas", + "versionRetentionEnabledExtDes": "Presiona Enter para agregar, deja en blanco para habilitar en todos los archivos", + "enableVersionRetention": "Habilitar retención de versiones", + "enableVersionRetentionDes": "Si está habilitado, se retendrán las versiones históricas de archivos que cumplan las condiciones", + "versionRetention": "Retención de versiones", + "languageDes": "Configurar idioma de visualización de la aplicación e idioma preferido de correo", + "timezoneDes": "Configurar zona horaria de visualización, por defecto sigue la zona horaria del sistema", + "unlinkConfirm": "¿Estás seguro de que quieres desvincular la cuenta?", + "notLinked": "No vinculado", + "linkedAt": "Vinculado en <0>", + "accountLinking": "Vinculación de cuenta", + "nickNameDes": "Nombre para mostrar públicamente, puede usar nombre real o apodo", + "cropAvatar": "Recortar avatar", + "finance": "Finanzas", + "preference": "Preferencias", + "accountCreatedAt": "Creado en <0>", + "shoeQr": "Mostrar", + "deviceNothing": "El grupo de usuario actual no admite WebDAV", + "connectionInfo": "Información de conexión", + "proxyTooltip": "El servidor proxy maneja todas las solicitudes de descarga de archivos.", + "readonlyTooltip": "El usuario solo puede leer archivos a través de esta cuenta.", + "blockSysFilesUpload": "Bloquear carga de archivos del sistema", + "blockSysFilesUploadTooltip": "Cuando esté habilitado, los archivos que comiencen con <0>. serán bloqueados para la carga.", + "rootFolderIn": "Seleccionar <0>", + "createWebDavAccount": "Crear cuenta WebDAV", + "editWebDavAccount": "Editar {{name}}", + "seeding": "Sembrando", + "awaitSeeding": "Esperando sembrado", + "awaitSeedingDes": "Esperando que la tarea de descarga complete el sembrado.", + "downloadTransferDes": "Transferir archivos al destino.", + "downloadDes": "Descargar archivos especificados.", + "retryErrorHistory": "Historial de errores de reintento", + "retryCount": "Número de reintentos", + "resumeAt": "Próxima reanudación", + "executeDuration": "Duración neta de ejecución", + "input": "Entrada", + "output": "Salida", + "suspended": " (Suspendido)", + "updatedAt": "Actualizado en", + "taskDetails": "Detalles de la tarea", + "partialSuccessWarning": "{{num}} objetos fallaron en el procesamiento y fueron omitidos.", + "sendTask": "Enviar tarea", + "sendTaskDes": "Enviar la tarea al nodo de procesamiento.", + "downloaded": "Descargado", + "importingFiles": "Importar archivos", + "importingFilesDes": "Recuperar archivos e importarlos a la carpeta especificada.", + "importedFiles": "Archivos importados", + "indexedFiles": "Archivos indexados", + "extractedFiles": "Número de archivos extraídos", + "extractedFilesSize": "Tamaño de archivos extraídos", + "extractingFiles": "Extraer archivos", + "extractingFilesDes": "Extraer todos los archivos a la carpeta especificada.", + "downloadingZip": "Obtener archivo comprimido", + "downloadingZipDes": "Descargar archivo comprimido al espacio de trabajo temporal.", + "progressNotAvailable": "Información de progreso aún no disponible", + "uploadedSize": "Archivos transferidos", + "archivedFiles": "Número de archivos procesados", + "transferredFiles": "Número de archivos transferidos", + "archivedFilesSize": "Tamaño de archivos procesados", + "createArchiveFinishing": "Enviar cambios de nuevos archivos.", + "indexForArchiveDes": "Recuperar todos los archivos a comprimir.", + "prepare": "Preparar", + "preparingWorkspaceDes": "Preparar espacio de trabajo temporal.", + "compressFiles": "Crear archivo comprimido", + "compressFilesDes": "Comprimir archivos al espacio de trabajo temporal.", + "uploadArchiveFileDes": "Transferir archivo comprimido al destino.", + "uploadWorker": "Hilo de subida #{{num}}", + "relocatedEntities": "Entidades transferidas", + "queueToStart": "En cola para iniciar", + "indexingFiles": "Recuperar archivos", + "indexingFilesDes": "Recuperar todos los archivos a transferir y bloquearlos.", + "transferring": "Transferir", + "transferringRelocateDes": "Transferir datos a la nueva política de almacenamiento.", + "committingChanges": "Enviar cambios", + "relocateFinishing": "Actualizar entidades de archivo para apuntar a la nueva política de almacenamiento.", + "autoRefresh": "Actualización automática", + "avatarUpdated": "Avatar actualizado, la visualización del avatar más reciente puede tener retraso", + "nickChanged": "Apodo cambiado, efectivo después de actualizar", + "settingSaved": "Configuración guardada", + "themeColorChanged": "Color de tema cambiado", + "profile": "Perfil personal", + "avatar": "Avatar", + "uid": "UID", + "nickname": "Apodo", + "group": "Grupo de usuario", + "regTime": "Tiempo de registro", + "security": "Contraseña y seguridad", + "profilePage": "Página personal", + "publicShareOnly": "Solo mostrar enlaces compartidos sin contraseña", + "publicShareOnlyDes": "Solo mostrar en la página personal los enlaces compartidos que no tienen contraseña configurada.", + "allShare": "Todos los enlaces compartidos", + "allShareDes": "Mostrar todos los enlaces compartidos en la página personal (incluyendo los que tienen contraseña). Para los enlaces compartidos con contraseña, los usuarios aún necesitan ingresar la contraseña para acceder.", + "hideShare": "Ocultar todos los enlaces compartidos", + "hideShareDes": "Ocultar todos los enlaces compartidos en la página personal.", + "userHideShare": "El usuario ocultó la lista de enlaces compartidos", + "accountPassword": "Contraseña de inicio de sesión", + "2fa": "Verificación de dos factores", + "enabled": "Habilitado", + "disabled": "No habilitado", + "appearance": "Personalización", + "themeColor": "Color de tema", + "darkMode": "Modo oscuro", + "syncWithSystem": "Sistema", + "fileList": "Lista de archivos", + "timeZone": "Zona horaria", + "webdavServer": "Dirección de conexión", + "userName": "Nombre de usuario", + "manageAccount": "Administración de cuentas", + "uploadImage": "Subir desde archivo", + "useGravatar": "Usar avatar de Gravatar", + "changeNick": "Cambiar apodo", + "originalPassword": "Contraseña original", + "enable2FA": "Habilitar verificación de dos factores", + "disable2FA": "Desactivar verificación de dos factores", + "2faDescription": "Por favor usa cualquier aplicación de verificación de dos factores o software de gestión de contraseñas que soporte verificación de dos factores para escanear el código QR y agregar este sitio. Después de escanear, completa el código de verificación de 6 dígitos proporcionado por la aplicación de verificación de dos factores para habilitar la verificación de dos factores.", + "inputCurrent2FACode": "Ingresa el código de verificación de 6 dígitos actual proporcionado por la aplicación de verificación de dos factores:", + "timeZoneCode": "Identificador de nombre de zona horaria IANA", + "authenticatorRemoved": "Credencial eliminada", + "authenticatorAdded": "Verificador agregado", + "browserNotSupported": "No compatible con el navegador o entorno actual", + "removedAuthenticator": "Eliminar credencial", + "removedAuthenticatorConfirm": "¿Estás seguro de que quieres revocar esta credencial?", + "addNewAuthenticator": "Agregar nueva credencial", + "hardwareAuthenticator": "Clave de acceso", + "copied": "Copiado al portapapeles", + "pleaseManuallyCopy": "El navegador actual no es compatible, por favor copia manualmente", + "webdavAccounts": "Administración de cuentas WebDAV", + "webdavHint": "La dirección de WebDAV es: {{url}}; el nombre de usuario de inicio de sesión es unificado: {{name}}; la contraseña es la contraseña de la cuenta creada.", + "annotation": "Nombre de anotación", + "rootFolder": "Carpeta raíz relativa", + "createdAt": "Fecha de creación", + "action": "Operación", + "readonlyOn": "Solo lectura", + "readonlyOff": "Lectura y escritura", + "proxy": "Proxy inverso", + "none": "Ninguno", + "proxied": "Con proxy", + "delete": "Eliminar", + "listEmpty": "No hay registros", + "createNewAccount": "Crear nueva cuenta", + "taskType": "Tipo de tarea", + "taskStatus": "Estado", + "taskProgress": "Progreso de la tarea", + "errorDetails": "Información de error", + "queueing": "En cola", + "processing": "Procesando", + "failed": "Fallido", + "canceled": "Cancelado", + "finished": "Completado", + "fileTransfer": "Transferencia de archivos", + "fileRecycle": "Reciclaje de archivos", + "importFiles": "Importar carpeta externa", + "transferProgress": "Completados {{num}} archivos", + "waiting": "Esperando", + "compressing": "Comprimiendo", + "decompressing": "Descomprimiendo", + "downloading": "Descargando", + "indexing": "Indexando", + "listing": "Insertando", + "allShares": "Todos los enlaces compartidos", + "trendingShares": "Enlaces compartidos populares", + "totalShares": "Total de enlaces compartidos", + "fileName": "Nombre de archivo", + "shareDate": "Fecha de compartir", + "downloadNumber": "Número de descargas", + "viewNumber": "Número de visualizaciones", + "language": "Idioma", + "iOSApp": "Cliente iOS/iPadOS", + "connectByiOS": "Conectar a <0>{{title}} a través de dispositivos iOS/iPadOS", + "downloadOurApp": "Descargar e instalar nuestra aplicación:", + "fillInEndpoint": "Escanear el código QR de abajo con la aplicación (otras aplicaciones de escaneo no son válidas):", + "loginApp": "Completar la vinculación, puedes comenzar a usar el cliente. Si encuentras problemas con el escaneo del código QR, también puedes intentar ingresar manualmente el nombre de usuario y contraseña para iniciar sesión.", + "relocateFileTo": "Transferir la política de almacenamiento de <0>{{more}} a {{policy}}", + "extractFileTo": "Extraer <0>{{more}} a <1>", + "createArchiveTo": "Empaquetar <0>{{more}} a <1>", + "importFileTo": "Importar archivos de {{policy}} a <0>" + }, + "vas": { + "points": "Puntos", + "paid": "Pagado", + "fulfillFailedStatus": "Error de cumplimiento", + "unpaid": "No pagado", + "amount": "Monto", + "tradeNo": "Número de transacción", + "payments": "Pedidos", + "creditReasonShareGain": "Enlace compartido comprado", + "creditReasonSharePay": "Consumo en tienda", + "creditReasonRecharge": "Recarga", + "creditChanges": "Cambios de puntos", + "payXPoints": "Pagar <0>", + "pointsPayAvailable": "Este producto admite pago con puntos, puedes elegir usar <0> para canjear en el siguiente paso.", + "payAmount": "Pagar {{price}}", + "purchaseSomething": "Comprar {{name}}", + "redeem": "Canjear", + "shop": "Tienda", + "resumeTicket": "Ticket de reanudación", + "resumeTicketDes": "Puedes encontrarlo en el correo de confirmación de pedido enviado después del pago.", + "restorePurchase": "Restaurar compra", + "restorePurchaseDes": "Restaurar compra usando el \"ticket de reanudación\" en el correo de confirmación de pedido.", + "paymentSuccess": "Pago exitoso", + "fulfillFailed": "Error en el cumplimiento del pedido, por favor contacta al administrador del sitio.", + "paidButton": "Ya pagué", + "payInNewWindow": "Por favor completa el pago en la nueva ventana emergente. No cierres esta página antes de que se complete el pago. Si la nueva ventana no aparece, por favor <0>haz clic aquí.", + "paymentFailedTitle": "Error al procesar el pago", + "paymentEmailHelper": "Como no has iniciado sesión, necesitamos tu correo electrónico para enviar el comprobante de compra.", + "payEquivalentCash": "Pagar monto equivalente en efectivo: {{num}}", + "payWithCash": "Pagar con efectivo", + "recharge": "Recargar", + "pointsBalance": "Saldo de puntos: {{num}}", + "loginRequired": "Requiere inicio de sesión", + "payWithPoints": "Pagar con puntos", + "purchaseLogin": "Por favor <0>inicia sesión antes de continuar con la compra.", + "noAvailableSharePurchaseMethod": "No hay métodos de compra disponibles.", + "purchaseShareLink": "Comprar enlace compartido", + "loginWith": "Iniciar sesión con {{name}}", + "sso": "SSO", + "qq": "QQ", + "quota": "Cuota de capacidad", + "exceedQuota": "Tu capacidad utilizada ha excedido la cuota de capacidad, por favor elimina archivos adicionales o compra capacidad lo antes posible.", + "extendStorage": "Ampliar capacidad", + "folderPolicySwitched": "La política de almacenamiento de la carpeta actual se ha cambiado a \"{{name}}\"", + "switchFolderPolicy": "Cambiar política de almacenamiento de carpeta", + "setPolicyForFolder": "Establecer política de almacenamiento para la carpeta actual:", + "manageMount": "Administrar vinculación", + "saveToMyFiles": "Guardar en mis archivos", + "report": "Reportar abuso", + "reportTarget": "Objeto del reporte", + "reportReason": "Razón", + "reportReasonOptions": ["Infracción de derechos de autor", "Contenido dañino", "Información basura", "Otro"], + "reportDescription": "Descripción adicional", + "reportAbuseSuccess": "Reporte enviado", + "migrateStoragePolicy": "Transferir política de almacenamiento", + "fileSaved": "Archivo guardado", + "sharePurchaseTitle": "Este enlace compartido requiere pagar <0> para acceder después de la compra", + "payToDownload": "Pagar para descargar", + "creditToBePaid": "Puntos a pagar", + "creditGainPredict": "Se espera que cada compra genere {{num}} puntos", + "creditPrice": " ({{num}} puntos)", + "creditFree": " (Sin puntos)", + "cancelSubscription": "Cancelación exitosa, el cambio tendrá efecto en unos minutos u horas", + "qqUnlinked": "Desvinculado de la cuenta QQ", + "groupExpire": "(<0> expira)", + "manuallyCancelSubscription": "Cancelar manualmente el grupo de usuario actual", + "qqAccount": "Cuenta QQ", + "connect": "Vincular", + "unlink": "Desvincular", + "credits": "Puntos", + "cancelSubscriptionTitle": "Cancelar grupo de usuario", + "cancelSubscriptionWarning": "Volverás al grupo de usuario inicial y el monto pagado no será reembolsable, ¿estás seguro de que quieres continuar?", + "mountPolicy": "Vinculación de política de almacenamiento", + "mountDescription": "Después de vincular una política de almacenamiento a una carpeta, los nuevos archivos subidos a esta carpeta o subcarpetas usarán la política de almacenamiento vinculada. Copiar y mover a esta carpeta no aplicará la política de almacenamiento vinculada; cuando se especifiquen múltiples carpetas padre, se seleccionará la política de almacenamiento de la carpeta padre más cercana.", + "mountNewFolder": "Vincular nueva carpeta", + "nsfw": "Información pornográfica", + "malware": "Contiene virus", + "copyright": "Infracción de derechos de autor", + "inappropriateStatements": "Declaraciones inapropiadas", + "other": "Otro", + "groupBaseQuota": "Capacidad base del grupo de usuario - {{size}}", + "validPackQuota": "Capacidad de expansión - {{size}}", + "used": "Usado - {{size}}", + "total": "Capacidad total - {{size}}", + "validStorage": "Expansión válida", + "buyStorage": "Comprar capacidad", + "useGiftCode": "Usar código de activación para canjear", + "packName": "Nombre del paquete de capacidad", + "activationDate": "Fecha de activación", + "validDuration": "Período de validez", + "expiredAt": "Fecha de expiración", + "days": "{{num}} días", + "pleaseInputGiftCode": "Por favor ingresa el código de canje", + "pleaseSelectAStoragePack": "Por favor selecciona primero un paquete de capacidad", + "paymentMethod": "Método de pago", + "noAvailableMethod": "No hay métodos de pago disponibles", + "alipay": "Escanear código Alipay", + "wechatPay": "Escanear código WeChat", + "payByCredits": "Pago con puntos", + "purchaseDuration": "Duración de compra", + "creditsNum": "Cantidad de puntos de recarga", + "store": "Tienda", + "storageExpansion": "Expansión de almacenamiento", + "membership": "Membresía", + "buyCredits": "Recarga de puntos", + "subtotal": "Costo actual:", + "creditsTotalNum": "{{num}} puntos", + "purchaseNow": "Comprar ahora", + "recommended": "Recomendado", + "enterGiftCode": "Ingresar código de canje", + "qrcodeAlipay": "Por favor usa Alipay para escanear el código QR de abajo y completar el pago. Esta página se actualizará automáticamente después de completar el pago.", + "qrcodeWechat": "Por favor usa WeChat para escanear el código QR de abajo y completar el pago. Esta página se actualizará automáticamente después de completar el pago.", + "qrcodeCustom": "Por favor escanea el código QR de abajo para completar el pago, o <0>abre directamente el enlace de pago. Esta página se actualizará automáticamente después de completar el pago.", + "paymentCompleted": "Pago completado", + "productDelivered": "El producto que compraste ya está disponible.", + "confirmRedeem": "Confirmar canje", + "productType": "Producto", + "qyt": "Cantidad:", + "duration": "Duración:", + "subscribe": "Comprar grupo de usuario", + "selected": "Seleccionado:", + "paymentQrcode": "Código QR de pago", + "validDurationDays": "{{num}} días", + "reportSuccessful": "Reporte exitoso", + "additionalDescription": "Descripción adicional", + "announcement": "Anuncio", + "dontShowAgain": "No mostrar de nuevo", + "openPaymentLink": "Abrir enlace de pago directamente", + "creditReasonAdjust": "Ajuste manual" + } +} diff --git a/public/locales/es-ES/common.json b/public/locales/es-ES/common.json new file mode 100755 index 0000000..6013d3e --- /dev/null +++ b/public/locales/es-ES/common.json @@ -0,0 +1,120 @@ +{ + "pageNotFound": "Página no encontrada", + "unknownError": "Error desconocido", + "errLoadingSiteConfig": "No se pudo cargar la configuración del sitio: ", + "newVersionRefresh": "Hay una nueva versión de la página actual disponible.", + "update": "Actualizar", + "errorDetails": "Detalles", + "renderError": "Hay un error en el renderizado de la página, por favor intenta actualizar esta página.", + "ok": "Aceptar", + "cancel": "Cancelar", + "select": "Seleccionar", + "copyToClipboard": "Copiar", + "close": "Cerrar", + "dismiss": "Descartar", + "intlDateTime": "{{val, datetime}}", + "seconds": "s [segundos]", + "minutes": "m [minutos] s [segundos]", + "hours": "H [horas] m [minutos]", + "days": "{{d}} días", + "timeAgoLocaleCode": "es_ES", + "forEditorLocaleCode": "es", + "artPlayerLocaleCode": "es", + "requestID": "ID de solicitud: {{id}}", + "object": "Objeto", + "error": "Error", + "areYouSure": "¿Estás seguro?", + "incorrectSizeInput": "Entrada de tamaño incorrecta", + "of": "de", + "rowsPerPage": "Filas por página", + "custom": "Personalizado", + "enter": "Ingresar", + "captcha": { + "cap": { + "human": "Soy humano", + "verifying": "Verificando...", + "verified": "Eres humano" + } + }, + "errors": { + "401": "Por favor inicia sesión.", + "403": "No tienes permisos para realizar esta acción.", + "404": "Recurso no encontrado.", + "409": "Conflicto. ({{message}})", + "40001": "Parámetros de entrada inválidos ({{message}}).", + "40002": "Subida fallida.", + "40003": "Error al crear carpeta.", + "40004": "Ya existe un objeto con el mismo nombre.", + "40005": "Firma expirada.", + "40006": "Tipo de política no compatible.", + "40007": "El grupo actual no tiene permisos para realizar esta acción.", + "40011": "La sesión de subida no existe o ha expirado.", + "40012": "Ãndice de fragmento inválido. ({{message}})", + "40013": "Longitud de contenido inválida. ({{message}})", + "40014": "Se excedió el límite de lote para obtener enlaces de origen.", + "40015": "Se excedió el límite de lote de aria2.", + "40016": "Ruta no encontrada.", + "40017": "Esta cuenta ha sido bloqueada.", + "40018": "Esta cuenta no está activada.", + "40019": "Esta función no está habilitada.", + "40020": "Credencial inválida o expirada.", + "40021": "Usuario no encontrado.", + "40022": "Código de verificación incorrecto.", + "40023": "Sesión de inicio de sesión no existe.", + "40024": "No se puede inicializar WebAuthn.", + "40025": "Autenticación fallida.", + "40026": "El código CAPTCHA es incorrecto.", + "40027": "Verificación fallida, por favor actualiza la página e intenta de nuevo.", + "40028": "Error en la entrega del correo electrónico.", + "40029": "Este enlace es inválido.", + "40030": "Este enlace ha expirado.", + "40032": "Este correo electrónico ya está en uso.", + "40033": "Esta cuenta no está activada, se ha reenviado el correo de activación.", + "40034": "Este usuario no puede ser activado.", + "40035": "Política de almacenamiento no encontrada.", + "40039": "Grupo no encontrado.", + "40044": "Archivo no encontrado.", + "40045": "Error al listar objetos en la carpeta dada.", + "40047": "Error al inicializar el sistema de archivos.", + "40048": "Error al crear tarea.", + "40049": "El tamaño del archivo excede el límite.", + "40050": "Tipo de archivo no permitido.", + "40051": "Cuota de almacenamiento insuficiente.", + "40052": "Este nombre de archivo o extensión no está permitido.", + "40053": "No se puede realizar esta acción en la carpeta raíz.", + "40054": "Ya se está subiendo un archivo con el mismo nombre en esta carpeta, por favor limpia las sesiones de subida.", + "40055": "Los metadatos del archivo no coinciden.", + "40056": "Tipo de archivo comprimido no compatible.", + "40057": "La política de almacenamiento disponible ha cambiado, por favor actualiza la lista de archivos y agrega esta tarea nuevamente.", + "40058": "Este recurso compartido no existe o ya ha expirado.", + "40069": "Contraseña incorrecta.", + "40070": "Este recurso compartido no admite vista previa.", + "40071": "Firma inválida.", + "40073": "Archivo en uso.", + "40074": "Demasiados archivos seleccionados.", + "40079": "Se excedió el límite máximo de archivos recorridos, intenta reducir el alcance de la operación.", + "40081": "Operación no completamente exitosa.", + "40082": "Solo el propietario del archivo puede realizar esta acción.", + "40080": "Correo electrónico o contraseña incorrectos.", + "50001": "Operación de base de datos fallida. ({{message}})", + "50002": "Error al firmar la URL o solicitud. ({{message}})", + "50004": "Operación de E/S fallida. ({{message}})", + "50005": "Error interno. ({{message}})", + "50010": "El nodo deseado no está disponible.", + "50011": "Error al consultar metadatos del archivo." + }, + "vasErrors": { + "40031": "Este proveedor de correo electrónico está prohibido, por favor cambia a otro.", + "40059": "No puedes guardar tu propio recurso compartido.", + "40062": "Créditos insuficientes.", + "40063": "Tu membresía actual aún no ha expirado, por favor ve a la página de configuración para cancelar manualmente la membresía primero.", + "40064": "Ya estás en esta membresía.", + "40065": "Código de regalo inválido.", + "40066": "Ya tienes una identidad vinculada, por favor desvínculala primero.", + "40067": "Esta identidad ya está vinculada a otra cuenta.", + "40068": "Esta identidad no está vinculada a ninguna cuenta.", + "40072": "Eres administrador, no puedes comprar otro grupo.", + "40084": "Tu cuenta no tiene contraseña, debes mantener al menos una cuenta vinculada.", + "40085": "El monto total de esta orden es demasiado pequeño para el pago." + } +} diff --git a/public/locales/es-ES/dashboard.json b/public/locales/es-ES/dashboard.json new file mode 100755 index 0000000..05eb143 --- /dev/null +++ b/public/locales/es-ES/dashboard.json @@ -0,0 +1,1621 @@ +{ + "errors": { + "40036": "La política de almacenamiento predeterminada no se puede eliminar.", + "40037": "Algunos blob(s) de archivos están usando esta política, por favor elimine esos blobs de archivos primero.", + "40038": "{{message}} grupo(s) están usando esta política, por favor desvincula esos grupos primero.", + "40040": "No se puede realizar tal acción en el grupo del sistema.", + "40041": "{{message}} usuarios siguen en este grupo, por favor elimine o desvincula esos usuarios primero.", + "40042": "No se puede cambiar el grupo del usuario del grupo del sistema.", + "40043": "No se puede realizar tal acción en el usuario predeterminado.", + "40046": "No se puede realizar tal acción en el nodo maestro.", + "40060": "El nodo esclavo no puede enviar solicitud de callback al maestro, por favor verifique la configuración del nodo maestro: Básico - Información del Sitio - URL del Sitio, por favor asegúrese de que el nodo esclavo pueda acceder a esta URL. ({{message}})", + "40061": "Versión de Cloudreve no coincide. ({{message}})", + "40086": "El nodo está siendo usado por las siguientes políticas de almacenamiento: {{message}}.", + "50008": "Error al actualizar configuración. ({{message}})", + "50009": "Error al agregar política CORS." + }, + "nav": { + "summary": "Resumen", + "settings": "Configuración", + "basicSetting": "Básico", + "email": "Email", + "transportation": "Transmisión", + "appearance": "Apariencia", + "image": "Imágenes", + "captcha": "Captcha", + "storagePolicy": "Política de Almacenamiento", + "nodes": "Nodos", + "groups": "Grupos", + "users": "Usuarios", + "files": "Archivos", + "entities": "Blobs de Archivos", + "shares": "Compartir", + "tasks": "Tareas en Segundo Plano", + "remoteDownload": "Descarga Remota", + "generalTasks": "General", + "title": "Panel de Control", + "dashboard": "Panel de Control Cloudreve", + "userSession": "Sesión de usuario", + "fileSystem": "Sistema de archivos", + "mediaProcessing": "Procesamiento de medios", + "queue": "Cola", + "events": "Eventos", + "server": "Servidor", + "customProps": "Propiedades personalizadas", + "abuseReport": "Reporte de abuso" + }, + "summary": { + "generatedAt": "Generado en <0>", + "confirmSiteURLTitle": "Confirmar URL del sitio", + "siteURLNotMatch": "La URL del sitio que configuraste no contiene la actual ({{current}}), ¿quieres agregarla a la lista?", + "setAsPrimary": "Establecer como URL principal del sitio", + "setAsPrimaryDes": "Establecer {{current}} como la URL principal del sitio, utilizada para comunicación con servicios externos y recepción de callbacks. Por favor usa una URL que pueda ser accedida por WAN.", + "setAsSecondary": "Agregar a URLs secundarias", + "setAsSecondaryDes": "Agregar {{current}} a URLs secundarias, Cloudreve seleccionará automáticamente si usarla basándose en la URL realmente accedida por el usuario.", + "siteURLDescription": "Esta configuración es muy importante, asegúrate de que coincida con la URL real de tu sitio. Puedes cambiar esta configuración en Configuración - Básico.", + "ignore": "Ignorar", + "changeIt": "Cambiar", + "trend": "Tendencia", + "summary": "Resumen", + "totalUsers": "Usuarios", + "totalFilesAndFolders": "Archivos y carpetas", + "shareLinks": "Enlaces de compartir", + "totalBlobs": "Blobs", + "homepage": "Página principal", + "github": "GitHub", + "documents": "Documentos", + "discordCommunity": "Comunidad Discord", + "telegram": "Grupo Telegram", + "forum": "GitHub Discussions", + "buyPro": "Actualizar a Pro", + "publishedAt": "publicado en <0>", + "licenseExpireAt": "Fecha de expiración de licencia", + "permanentLicense": "Licencia permanente", + "offlineLicenseExpireAy": "Fecha de expiración de licencia offline", + "offlineLicenseDes": "Cloudreve actualizará automáticamente la licencia offline antes de que expire si tu servidor está conectado a la red.", + "licensedDomains": "Dominios licenciados", + "renew": "Refrescar licencia offline", + "manageLicense": "Administrar licencia", + "volPurchase": "La licencia VOL del cliente necesita ser comprada por separado desde el <0>Panel de Administración de Licencias. La licencia VOL permite a tus usuarios conectarse a tu sitio usando el <1>cliente iOS de Cloudreve gratuitamente, sin necesidad de que los usuarios paguen por una suscripción para la aplicación iOS en sí. Después de comprar una licencia, por favor haz clic en \"Refrescar licencia offline\" a continuación.", + "iosVol": "Licencia de volumen (VOL) del cliente iOS", + "refreshSuccessfully": "Refrescado exitosamente.", + "manualRefresh": "Refrescar manualmente licencia offline", + "manualRefreshDes": "Error al refrescar licencia offline automáticamente, por favor intenta iniciar sesión en el <0>Panel de Administración de Licencias para obtener la última licencia offline y pégala a continuación.", + "announcement": "Anuncio" + }, + "queue": { + "queueName_io_intense": "IO Intensivo", + "queueName_io_intenseDes": "Cola para manejar grandes cantidades de operaciones IO, incluyendo: transferencia de política de almacenamiento, descompresión, compresión.", + "queueName_media_meta": "Extracción de Metadatos de Medios", + "queueName_media_metaDes": "Utilizada para extraer metadatos de archivos de medios.", + "queueName_recycle": "Reciclaje de Blobs", + "queueName_recycleDes": "Utilizada para eliminar blobs de archivos expirados.", + "queueName_thumb": "Generación de Miniaturas", + "queueName_thumbDes": "Utilizada para generar miniaturas para archivos.", + "queueName_remote_download": "Descarga Remota", + "queueName_remote_downloadDes": "Utilizada para procesar tareas de descarga remota.", + "failed": "Fallido ({{count}})", + "success": "Exitoso ({{count}})", + "suspending": "Suspendido ({{count}})", + "busyWorker": "Procesando ({{count}})", + "submited": "Enviado ({{count}})", + "editQueueSettings": "Editar configuración de cola - {{name}}", + "workerNum": "Hilos de trabajo", + "workerNumDes": "Número máximo de tareas a ser ejecutadas en paralelo en la cola de tareas", + "maxExecution": "Tiempo máximo de ejecución", + "maxExecutionDes": "Tiempo máximo de ejecución (segundos) para una tarea, después del cual la tarea será terminada.", + "backoffFactor": "Factor de retroceso", + "backoffFactorDes": "Factor de crecimiento para intervalos de tiempo de reintento de tareas.", + "backoffMaxDuration": "Tiempo máximo de retroceso", + "backoffMaxDurationDes": "Tiempo máximo de retroceso (segundos) para reintentos de tareas.", + "maxRetry": "Máximo de reintentos", + "maxRetryDes": "Número máximo de reintentos después de un fallo de tarea.", + "retryDelay": "Retraso de reintento", + "retryDelayDes": "Tiempo de retraso inicial (segundos) para reintentos de tareas." + }, + "settings": { + "headlessFooter": "Pie de página de página de inicio", + "headlessFooterDes": "Contenido HTML personalizado mostrado en la parte inferior de las páginas de inicio de sesión, registro y resultado de callback.", + "headlessBottom": "Parte inferior de página de inicio", + "headlessBottomDes": "Contenido HTML personalizado mostrado en la parte inferior de las páginas de inicio de sesión, registro y resultado de callback.", + "customHTML": "HTML personalizado", + "customHTMLDes": "Insertar contenido HTML personalizado en la posición preestablecida del sitio.", + "sidebarBottom": "Parte inferior de la barra lateral", + "sidebarBottomDes": "Contenido HTML personalizado mostrado en la parte inferior de la barra lateral.", + "addNavItem": "Agregar elemento de navegación", + "customNavItems": "Elementos personalizados de la barra lateral", + "customNavItemsDes": "Puedes agregar elementos personalizados a la barra lateral, y los usuarios serán redirigidos al enlace correspondiente cuando hagan clic.", + "navItemUrl": "Enlace", + "iconifyNamePlaceholder": "Identificador de icono Iconify, ej. fluent:home-24-regular", + "imageUrl": "URL de imagen", + "iconifyName": "Nombre de icono Iconify", + "oidc": "OpenID Connect (OIDC)", + "oidcDes": "OpenID Connect (OIDC) es un protocolo de autenticación abierto para verificación de identidad entre diferentes sistemas. Después de crear una aplicación en una plataforma de identidad de terceros, por favor agrega <0>{{url}} al campo \"URI de Redirección\". Para más detalles, por favor consulta la <1>documentación.", + "clientID": "ID de Cliente", + "clientIDDes": "El ID de cliente de la aplicación creada en la plataforma de identidad de terceros.", + "clientSecret": "Secreto del cliente", + "clientSecretDes": "El secreto del cliente de la aplicación creada en la plataforma de identidad de terceros.", + "scope": "Alcance", + "scopeDes": "Alcances adicionales a solicitar, separados por comas <0>,. Por defecto, Cloudreve solicitará <0>openid, <0>email y <0>profile; no necesitas repetirlos aquí.", + "oidcWellknown": "Configuración OIDC Wellknown", + "oidcWellknownDes": "Documento wellknown de la plataforma de identidad de terceros, conteniendo la información de configuración de OpenID Connect.", + "importFromWellknown": "Importar desde URL", + "importOidc": "Importar Configuración OIDC Wellknown", + "oidcWellknownUrl": "URL Wellknown", + "oidcWellknownUrlDes": "URL del documento wellknown de la plataforma de identidad de terceros, como <0>https://accounts.google.com/.well-known/openid-configuration.", + "resetUrl": "Restablecer URL", + "exceedToleranceDays": "Días de tolerancia para prohibición", + "activateUrl": "URL de activación", + "domainNotLicensed": "Dominio no licenciado", + "domainNotLicensedDes": "La URL del sitio que configuraste contiene un dominio no autorizado, por favor agrega este subdominio en el <0>Panel de Administración de Licencias y haz clic en el botón de abajo para actualizar la licencia e inténtalo de nuevo.", + "showSettings": "Mostrar configuración", + "perPage": "{{num}} por página", + "noNodes": "No hay nodos disponibles.", + "extractMediaMeta": "Extraer metadatos de medios", + "extractMediaMetaDes": "Extraer metadatos de archivos de medios para visualización y búsqueda. Por defecto, las políticas de almacenamiento no locales solo usarán el generador \"Nativo en política de almacenamiento\". Puedes extender la capacidad de miniaturas de políticas de almacenamiento de terceros habilitando la función \"Proxy de extractor\" en la página de configuración de política de almacenamiento. Para más detalles, por favor consulta la <0>documentación.", + "exif": "EXIF", + "exifDes": "Extraer metadatos EXIF de archivos de imagen para visualización y búsqueda.", + "music": "Metadatos de música", + "musicDes": "Extraer metadatos de archivos de música, incluyendo título, artista, álbum, etc.", + "ffprobe": "FFprobe", + "ffprobeDes": "Usar FFprobe para extraer metadatos de archivos de video y audio.", + "maxSizeLocal": "Tamaño máximo de archivo (Almacenamiento local)", + "maxSizeLocalDes": "Tamaño máximo de archivo para extracción de metadatos cuando el archivo está almacenado en política de almacenamiento local, 0 significa sin límite.", + "maxSizeRemote": "Tamaño máximo de archivo (Almacenamiento remoto)", + "maxSizeRemoteDes": "Tamaño máximo de archivo para extracción de metadatos cuando el archivo está almacenado en políticas de almacenamiento de terceros, 0 significa sin límite.", + "exifBruteForce": "Usar fuerza bruta si es necesario", + "exifBruteForceDes": "Cuando está habilitado, todo el archivo será escaneado para encontrar datos EXIF si no puede ser encontrado en la ubicación estándar del encabezado. Esto puede aumentar el tiempo de procesamiento pero puede encontrar datos EXIF en ubicaciones no estándar.", + "musicCover": "Portada de música", + "musicCoverDes": "Extraer portada de álbum de archivos de música, soporta contenedor ID3 (v1, 2.2, 2.3 y 2.4). Este generador depende de cualquier otro generador de miniaturas de imagen (Cloudreve integrado o VIPS).", + "geocoding": "Geocodificación", + "geocodingDes": "Obtener información de dirección usando el servicio Mapbox basado en la información de coordenadas registrada en EXIF de medios.", + "mapboxAK": "Clave API de Mapbox", + "mapboxAKDes": "Clave API creada en la consola de Mapbox.", + "geocodingDependencyWarning": "El generador de geocodificación depende del generador EXIF, por favor habilite el generador EXIF.", + "notAppliedToNativeGenerator": "{{prefix}}No aplicable al generador nativo de políticas de almacenamiento.", + "notAppliedToOneDriveNativeGenerator": "{{prefix}}No aplicable al generador nativo de políticas de almacenamiento OneDrive o SharePoint.", + "fileBlobMargin": "Margen de Cache de URL de Blob de Archivo (segundos)", + "fileBlobMarginDes": "Cuando el mismo Blob de archivo es solicitado múltiples veces, si la URL inicial tiene un período de validez restante mayor que el margen, la misma URL será reutilizada.", + "fileBlobTimeout": "TTL de URL de Blob de Archivo (segundos)", + "fileBlobTimeoutDes": "Limitar el período de validez de la URL temporal obtenida cuando los usuarios abren o descargan archivos, solo aplicable a políticas de almacenamiento local, WebDAV, o archivos descargados a través de retransmisión de Cloudreve.", + "wopiSessionTimeout": "TTL de sesión WOPI (segundos)", + "wopiSessionTimeoutDes": "Limitar el período de validez de una sola sesión cuando los usuarios editan archivos usando WOPI. Después de la expiración, los usuarios necesitan reabrir el archivo desde Cloudreve.", + "oauthRefresh": "Intervalo de refresco para política de almacenamiento OAuth", + "oauthRefreshDes": "Establecer con qué frecuencia refrescar las credenciales OAuth para políticas de almacenamiento (ej. OneDrive) que requieren OAuth. Esto puede prevenir la expiración de credenciales debido a largos períodos de inactividad", + "transitParallelNum": "Máximo de transferencias de retransmisión en paralelo", + "transitParallelNumDes": "El número máximo de cargas en paralelo cuando una sola tarea de retransmisión de archivos del lado del servidor contiene múltiples archivos.", + "failedChunkRetry": "Número máximo de reintentos para fallos de carga de fragmentos", + "failedChunkRetryDes": "El número máximo de reintentos para fallos de carga de fragmentos, solo aplicable a cargas del lado del servidor o transferencias de retransmisión.", + "cacheChunks": "Cachear fragmentos de streaming", + "cacheChunksDes": "Si está habilitado, los datos de fragmentos serán cacheados en el directorio temporal del sistema durante la transferencia de streaming, para que puedan ser utilizados para reintentar cargas de fragmentos fallidas;\n Si está deshabilitado, las cargas de fragmentos de transferencia de streaming no ocuparán espacio extra en disco, pero toda la carga fallará inmediatamente si la carga de fragmento falla.", + "folderPropsTimeout": "TTL de cache de estadísticas de carpeta (segundos)", + "folderPropsTimeoutDes": "El período de validez del cache de resultados cuando los usuarios calculan estadísticas de carpeta (tamaño, número de archivos, etc.).", + "slaveAPIExpiration": "TTL de firma de API esclava (segundos)", + "slaveAPIExpirationDes": "El período de validez de la firma utilizada por el nodo maestro al acceder a la API del nodo esclavo.", + "uploadSessionTimeout": "TTL de sesión de carga (segundos)", + "uploadSessionDes": "En un período válido de sesión de carga, para políticas de almacenamiento soportadas, los usuarios pueden reanudar tareas no terminadas. El valor máximo que se puede establecer está limitado por las reglas de diferentes proveedores de política de almacenamiento.", + "archiveTimeout": "TTL de sesión de descarga por lotes del lado del servidor (segundos)", + "advanceOptions": "Opciones avanzadas", + "emojiOptions": "Opciones de Emoji", + "addCategorize": "Agregar una categoría", + "category": "Categoría", + "searchQuery": "Consulta de categorización de archivos", + "importWopi": "Importar configuración de aplicación WOPI", + "wopiEndpoint": "Punto Final de Descubrimiento WOPI", + "wopiDes": "Extender las capacidades de vista previa y edición en línea de Cloudreve integrándose con sistemas de procesamiento de documentos en línea que soportan el protocolo WOPI. Por favor llena la dirección de descubrimiento del servicio WOPI aquí, como <0>https://example.com/hosting/discovery. Para más detalles, por favor consulta la <1>documentación.", + "embeddedWebpageViewer": "Visor de Página Web Integrada", + "wopiViewer": "Aplicación WOPI", + "ext": "Extensión", + "invalidWopiActionMapping": "Mapeo de acción WOPI inválido", + "woapiActionMapping": "Mapeos de acción WOPI", + "drawioHost": "Instancia DrawIO", + "drawioHostDes": "Puedes usar URL para instancia auto-alojada.", + "openInNew": "Abrir en nueva ventana", + "openInNewDes": "Si está marcado, abrirá directamente una nueva pestaña para abrir esta aplicación.", + "maxSize": "Tamaño máximo de archivo", + "maxSizeDes": "El tamaño máximo de archivo soportado por esta aplicación. 0 significa sin límite. Si el archivo excede este tamaño, aún se abrirá, pero se advertirá a los usuarios.", + "srcEncodedVar": "URL de acceso temporal de Blob de archivo codificada en URL", + "srcVar": "URL de acceso temporal de blob de archivo", + "srcBase64Var": "URL de acceso temporal de Blob de archivo codificada en Base64", + "nameEncodedVar": "Nombre de archivo codificado en URL", + "versionEntityVar": "El ID de Blob de la versión de archivo abierta, vacío significa la última versión.", + "fileIdVar": "ID de Archivo", + "userIdVar": "ID de Usuario, vacío cuando no está conectado.", + "userDisplayNameVar": "Nombre para mostrar del usuario codificado en URL.", + "fileViewers": "Aplicaciones de archivos", + "addViewer": "Agregar una aplicación", + "viewerGroupTitle": "Grupo de aplicación #{{index}}", + "viewerType": "Tipo", + "viewerPlatform": "Plataforma", + "viewerPlatformDes": "Selecciona la plataforma correspondiente para mostrar la aplicación solo en esa plataforma.", + "viewerPlatformPC": "Escritorio", + "viewerPlatformMobile": "Móvil", + "viewerPlatformAll": "Todas", + "displayName": "Nombre para mostrar", + "displayNameDes": "Nombre para mostrar a los usuarios, soporta clave i18next.", + "viewerEnabled": "Habilitado", + "newFileAction": "Acciones de nuevo archivo", + "newFileActionDes": "Al agregar este mapeo, los usuarios verán esta opción de aplicación al hacer clic en el botón \"Nuevo\".", + "addNewFileAction": "Agregar un mapeo", + "builtinViewerType": "Aplicación integrada", + "wopiViewerType": "WOPI", + "customViewerType": "Personalizada", + "nMapping": "{{num}} mapeo(s)", + "editViewerTitle": "Editar {{name}}", + "builtInIconUrlDes": "Esta aplicación integrada tiene un icono predeterminado. Cuando la URL del icono se deja en blanco, se utilizará el icono predeterminado.", + "viewerUrl": "URL de aplicación", + "viewerUrlDes": "URL de aplicación personalizada, se soportan <0>variables mágicas.", + "addIcon": "Agregar un icono", + "exts": "Lista de extensiones", + "icon": "Icono", + "iconUrl": "URL de icono", + "iconColor": "Color", + "iconColorDark": "Color (Modo oscuro)", + "fileIcons": "Iconos de archivos", + "builtinIcon": "Integrado", + "mimeMapping": "Mapeo de tipo MIME", + "mimeMappingDes": "Mapeo de tipo MIME en formato JSON, donde la clave es la extensión del archivo y el valor es el tipo MIME. Cloudreve determinará el tipo MIME del archivo basándose en la extensión del archivo y esta configuración.", + "mapProvider": "Proveedor de mapas", + "mapProviderDes": "Proveedor de mapas utilizado para mostrar información de ubicación de medios.", + "mapGoogle": "Google Maps (Leaflet)", + "mapOpenStreetMap": "OpenStreetMap (Leaflet)", + "mapboxMap": "OpenStreetMap (Mapbox)", + "mapboxAccessToken": "Token de acceso de Mapbox", + "mapboxAccessTokenDes": "Token de acceso público creado en la <0>consola de Mapbox.", + "tileType": "Tipo de mosaico predeterminado", + "tileTypeDes": "Tipo de mosaico predeterminado para Google Maps.", + "tileTypeTerrain": "Terreno", + "tileTypeSatellite": "Satélite", + "tileTypeGeneral": "Regular", + "maxPageSize": "Tamaño máximo de página", + "maxPageSizeDes": "Limitar el número máximo de archivos que los usuarios pueden ajustar por página.", + "maxRecursiveSearch": "Conteo máximo de búsqueda recursiva", + "maxRecursiveSearchDes": "El número máximo de búsquedas recursivas permitidas al buscar archivos. Si el número de archivos buscados excede este límite, la búsqueda se detendrá y advertirá al usuario.", + "maxBatchSize": "Tamaño máximo de lote", + "maxBatchSizeDes": "El número máximo de archivos que los usuarios pueden operar en un lote, solo se contará el nivel superior, y el número de archivos bajo subdirectorios no se contará.", + "defaultPagination": "Método de paginación para lista de archivos", + "cursorPagination": "Paginación de cursor", + "cursorPaginationDes": "Más archivos se cargarán automáticamente cuando el usuario se desplace hacia abajo. Este método funciona mejor para listas de archivos grandes, pero no se puede ver el número total de páginas.", + "offsetPagination": "Paginación de desplazamiento", + "offsetPaginationDes": "La navegación de paginación se mostrará en la parte inferior de la página; los usuarios pueden ver el número total de páginas y saltar a una página específica. Este método funciona ligeramente peor para listas de archivos grandes.", + "defaultPaginationDes": "La paginación de cursor se forzará a usar al buscar, independientemente de la configuración anterior.", + "publicResourceMaxAge": "Edad máxima de cache de recursos estáticos (segundos)", + "publicResourceMaxAgeDes": "La edad máxima de cache para recursos estáticos públicamente accesibles (ej. archivos, miniaturas y fotos de perfil de usuario).", + "cronDes": "{{des}} Se requiere aquí una <0>sintaxis Cron correcta. Es necesario reiniciar Cloudreve para que tome efecto.", + "entityCollectInterval": "Intervalo de reciclaje de Blob de archivos", + "entityCollectIntervalDes": "Establecer con qué frecuencia escanear y eliminar blobs de archivos expirados.", + "trashBinInterval": "Intervalo de escaneo de papelera de reciclaje", + "trashBinIntervalDes": "Establecer con qué frecuencia escanear y eliminar archivos expirados en la papelera de reciclaje.", + "logtoName": "Nombre del método de inicio de sesión", + "logtoNameDes": "Nombre del método de inicio de sesión, mostrado a los usuarios. El predeterminado es \"SSO\", soporta clave i18next.", + "logtoDirectSSO": "Inicio de sesión directo", + "logtoDirectSSODes": "Si quieres saltarte la pantalla de inicio de sesión de Logto y saltar directamente al inicio de sesión de terceros o SSO, por favor llena el identificador del conector social aquí. Para detalles, por favor consulta la <0>documentación de Logto.", + "logtoEndpoint": "Punto final de Logto", + "logtoEndpointDes": "La URL del punto final de Logto obtenida del panel de administración de aplicaciones, que puede ser una instancia auto-alojada.", + "logtoKey": "Secreto de aplicación", + "logtoKeyDes": "Secreto de aplicación creado en la página de administración de aplicaciones.", + "logtoAppIDDes": "ID de aplicación creado en la página de administración de aplicaciones.", + "logto": "Logto", + "logtoDes": "Con <0>Logto, puedes lograr más inicios de sesión de plataformas de terceros, como Apple, GitHub, Microsoft Entra ID, Google, SMS, etc. Por favor crea una \"Aplicación Web Tradicional\" en el portal de administración de Logto y agrega <1>{{url}} a los \"URIs de Redirección\".", + "thirdPartySignIn": "Inicio de sesión de terceros", + "logo": "LOGO", + "logoDes": "URL del LOGO, por favor proporciona diferentes logos para modos oscuro y claro.", + "dark": "Modo oscuro", + "light": "Modo claro", + "tosUrl": "URL de términos de servicio", + "tosUrlDes": "Se mostrará en el pie de página de la página de inicio de sesión o registro, déjalo en blanco para no mostrar.", + "privacyUrl": "URL de política de privacidad", + "privacyUrlDes": "Se mostrará en el pie de página de la página de inicio de sesión o registro, déjalo en blanco para no mostrar.", + "addSecondary": "Agregar URL secundaria del sitio", + "secondarySiteURL": "Secundaria", + "secondaryDes": "También puedes agregar otras URLs secundarias, Cloudreve seleccionará automáticamente si usarla basándose en la URL realmente accedida por el usuario. Tu URL del sitio debe estar licenciada.", + "primarySiteURL": "Principal", + "primarySiteURLDes": "La URL principal del sitio se usa para comunicación con servicios externos y recepción de callbacks (ej. pago, proveedor de almacenamiento), por favor usa una URL que pueda ser accedida por WAN.", + "revert": "Revertir cambios", + "saved": "Configuración guardada.", + "save": "Guardar", + "basicInformation": "Información Básica", + "mainTitle": "Nombre del sitio", + "mainTitleDes": "Nombre de la instancia.", + "siteDescription": "Descripción del sitio", + "siteDescriptionDes": "Descripción del sitio web, que puede mostrarse en el resumen de la página compartida.", + "siteURL": "URL del Sitio", + "customFooterHTML": "HTML de pie de página personalizado", + "customFooterHTMLDes": "Código HTML personalizado insertado en la parte inferior de la página.", + "announcement": "Anuncio", + "announcementDes": "Anuncios mostrados a usuarios conectados. El valor en blanco no se mostrará. Después de que este contenido sea cambiado, todos los usuarios verán el anuncio nuevamente.", + "supportHTML": "Ingresa HTML o texto plano.", + "branding": "Marca", + "smallIcon": "Icono pequeño", + "smallIconDes": "URL del icono pequeño, formato ico o svg. Este icono también se mostrará en pestañas del navegador, marcadores y accesos directos del escritorio.", + "mediumIcon": "Icono mediano", + "mediumIconDes": "URL del icono mediano, preferir tamaño de 192x192, formato png.", + "largeIcon": "Icono grande", + "largeIconDes": "URL del icono grande, preferir tamaño de 512x512, formato png. Este icono también se mostrará al cambiar cuenta en la aplicación iOS.", + "displayMode": "Modo de visualización", + "displayModeDes": "El modo de visualización de una aplicación PWA después de que sea instalada.", + "themeColor": "Color de tema", + "themeColorDes": "Valor de color CSS que afecta el color de la barra de estado en la pantalla de inicio de PWA, la barra de estado en la página de contenido, y la barra de direcciones.", + "backgroundColor": "Color de fondo", + "backgroundColorDes": "Valor de color CSS.", + "hint": "Sugerencia", + "webauthnNoHttps": "Web Authn requiere que tu sitio web tenga HTTPS habilitado, y por favor confirma que en Configuración - Básico - URL del Sitio también use HTTPS.", + "accountManagement": "Cuentas", + "allowNewRegistrations": "Aceptar nuevos registros", + "allowNewRegistrationsDes": "Después de deshabilitado, no se pueden registrar nuevos usuarios, a menos que sean agregados manualmente por administradores.", + "emailActivation": "Activación por email", + "emailActivationDes": "Después de habilitado, los nuevos usuarios necesitan hacer clic en el enlace de activación en el email para completar los registros. Por favor asegúrate de que la <0>configuración de entrega de email sea correcta, de lo contrario el email de activación no será entregado.", + "captchaForSignup": "Captcha para registros", + "captchaForSignupDes": "Si habilitar el captcha para registros.", + "captchaForLogin": "Captcha para inicios de sesión", + "captchaForLoginDes": "Si habilitar el captcha para inicios de sesión.", + "captchaForReset": "Captcha para restablecer contraseña", + "captchaForResetDes": "Si habilitar el captcha para restablecer contraseña.", + "captchaForAbuseReport": "Captcha para reporte de abuso", + "captchaForAbuseReportDes": "Si habilitar el captcha para reporte de abuso.", + "webauthnDes": "Si permitir a los usuarios iniciar sesión con dispositivos de autenticación de hardware, como: cara, huella dactilar o llave USB; el sitio debe habilitar HTTPS.", + "webauthn": "Iniciar sesión con Llaves de Paso", + "defaultSymbolics": "Accesos directos de compartir predeterminados", + "defaultSymbolicsDes": "Accesos directos de enlaces de compartir predeterminados en el directorio raíz de nuevos usuarios. Por favor busca enlaces de compartir por ID, puedes ver el ID en el lado izquierdo de la <0>lista de compartir.", + "searchShare": "Buscar ID de compartir...", + "defaultGroup": "Grupo predeterminado", + "defaultGroupDes": "El grupo de usuario inicial después del registro de usuario.", + "testMailSent": "Email de prueba enviado.", + "testSMTPSettings": "Probar configuración SMTP", + "testSMTPTooltip": "Cloudreve usará tu configuración SMTP actual para enviar un email de prueba, no necesitas guardar configuración antes de probar.", + "recipient": "Destinatario", + "send": "Enviar", + "smtp": "SMTP", + "senderName": "Nombre del remitente", + "senderNameDes": "El nombre del remitente mostrado en el email.", + "senderAddress": "Dirección del remitente", + "senderAddressDes": "Dirección de email del remitente.", + "smtpServer": "Servidor SMTP", + "smtpServerDes": "Dirección del servidor SMTP, sin número de puerto.", + "smtpPort": "Puerto SMTP", + "smtpPortDes": "Puerto del servidor SMTP.", + "smtpUsername": "Nombre de usuario SMTP", + "smtpUsernameDes": "Nombre de usuario SMTP, generalmente igual que la dirección del remitente.", + "smtpPassword": "Contraseña SMTP", + "smtpPasswordDes": "Contraseña del buzón del remitente.", + "replyToAddress": "Dirección de respuesta", + "replyToAddressDes": "El buzón utilizado para recibir emails de respuesta cuando los usuarios responden a emails enviados por el sistema.", + "enforceSSL": "Forzar conexión SSL", + "enforceSSLDes": "Si forzar una conexión cifrada SSL. Si no puedes enviar emails, puedes desactivar esto y Cloudreve intentará usar STARTTLS y decidir si usar conexiones cifradas.", + "smtpTTL": "TTL de conexión SMTP (segundos)", + "smtpTTLDes": "Las conexiones SMTP establecidas durante el período TTL serán reutilizadas por nuevas solicitudes de entrega de correo.", + "emailTemplates": "Plantillas de Email", + "activateNewUser": "Activar nuevo usuario", + "resetPassword": "Restablecer contraseña", + "sendTestEmail": "Enviar email de prueba", + "transportation": "Transmisión", + "workerNum": "Número de trabajadores", + "workerNumDes": "El número máximo de tareas a ser ejecutadas en paralelo por la cola de tareas del nodo maestro, es necesario reiniciar Cloudreve para que tome efecto.", + "tempFolder": "Carpeta temporal", + "tempFolderDes": "Utilizada para almacenar archivos temporales generados por tareas como descompresión, compresión, etc.", + "textEditMaxSize": "Tamaño máximo de archivos de documentos editables", + "textEditMaxSizeDes": "El tamaño máximo de un archivo de documento que puede ser editado en línea, archivos más allá de este tamaño no pueden ser editados en línea. Esta configuración se aplica a editores Web en línea como texto plano, código y documentos de Office (WOPI).", + "resetConnection": "Restablecer conexión después de carga fallida", + "resetConnectionDes": "Si está habilitado, el servidor forzará a restablecer la conexión si la verificación de carga falla.", + "batchDownload": "Descarga por lotes", + "previewURL": "URL de vista previa", + "cannotDeleteDefaultTheme": "No se puede eliminar el tema predeterminado.", + "themeConfig": "Configuraciones", + "actions": "Acciones", + "wrongFormat": "Formato incorrecto.", + "avatar": "Avatar", + "gravatarServer": "Servidor Gravatar", + "gravatarServerDes": "URL del servidor espejo de Gravatar.", + "avatarFilePath": "Ruta de archivo de avatar", + "avatarFilePathDes": "Ruta para guardar archivos de avatar del usuario, relativa a la carpeta de datos de Cloudreve.", + "avatarSize": "Tamaño máximo de archivo de avatar", + "avatarSizeDes": "Tamaño máximo de archivos de avatar que los usuarios pueden cargar.", + "avatarImageSize": "Tamaño de imagen (px)", + "avatarImageSizeDes": "La imagen de perfil seleccionada será redimensionada al tamaño dado, en píxeles.", + "filePreview": "Vista Previa de Archivos", + "thumbnails": "Miniaturas", + "thumbnailDoc": "Para más información sobre miniaturas, consulta el <0>documento.", + "thumbnailDocLink": "https://docs.cloudreve.org/usage/thumbnails", + "thumbnailBasic": "Básico", + "generators": "Generadores de miniaturas", + "thumbMaxSize": "Tamaño máximo de archivo original", + "thumbMaxSizeDes": "El tamaño máximo de archivo original para el cual se pueden generar miniaturas, no se generarán miniaturas si los archivos exceden este tamaño.", + "generatorProxyWarning": "Por defecto, las políticas de almacenamiento no locales solo usarán el generador \"Nativo en política de almacenamiento\". Puedes extender la capacidad de miniaturas de políticas de almacenamiento de terceros habilitando la función \"Proxy de generador\" en la página de configuración de política de almacenamiento. Para más detalles, por favor consulta la <0>documentación.", + "policyBuiltin": "Nativo en política de almacenamiento", + "policyBuiltinDes": "Usar la API nativa del proveedor de almacenamiento para procesar miniaturas. Para política local y S3, este generador no está disponible y automáticamente recurrirá a otros generadores. Para otras políticas de almacenamiento, por favor ve a la página de configuración de política de almacenamiento para configurar este generador.", + "cloudreveBuiltin": "Cloudreve integrado", + "cloudreveBuiltinDes": "Solo imágenes en formatos PNG, JPEG, GIF son soportadas usando las capacidades de procesamiento de imágenes integradas de Cloudreve.", + "libreOffice": "LibreOffice", + "libreOfficeDes": "Usar LibreOffice para generar miniaturas para documentos de Office. Este generador depende de cualquier otro generador de miniaturas de imagen (Cloudreve integrado o VIPS).", + "libraw": "LibRaw / DCRaw", + "librawDes": "Usar el programa de muestra DCRaw de LibRaw, o el ejecutable DCRaw original para generar miniaturas para imágenes RAW.", + "vips": "VIPS", + "vipsDes": "Usar libvips para procesar imágenes en miniatura, soporta más formatos de imagen, y consume menos recursos.", + "thumbDependencyWarning": "Los generadores LibreOffice o de portada de música dependen de los generadores integrados de Cloudreve o VIPS, por favor habilita cualquiera de ellos.", + "ffmpeg": "FFmpeg", + "ffmpegDes": "Usar FFmpeg para generar miniaturas de video.", + "executable": "Ejecutable", + "executableDes": "La ruta o comando del ejecutable del generador de terceros.", + "executableTest": "Probar", + "executableTestSuccess": "El generador funciona, versión: {{version}}", + "generatorExts": "Extensiones disponibles", + "generatorExtsDes": "Lista de extensiones de archivo disponibles para este generador, por favor usa coma , para separar múltiples.", + "ffmpegSeek": "Ubicación de captura de miniatura", + "ffmpegSeekDes": "Definir el tiempo de intercepción de miniatura, se recomienda elegir un valor menor para acelerar el proceso de generación. Si se excede la longitud real del video, la generación de miniatura fallará.", + "ffmpegExtraArgs": "Argumentos de entrada adicionales", + "ffmpegExtraArgsDes": "Argumentos de entrada adicionales para llamar FFmpeg.", + "generatorProxy": "Proxy de generador", + "enableThumbProxy": "Usar proxy de generador", + "proxyPolicyList": "Política de almacenamiento habilitada", + "proxyPolicyListDes": "Multi-seleccionable. Si está habilitado, archivos cuya política de almacenamiento no soporta generación nativa, sus miniaturas serán generadas por proxy por Cloudreve.", + "thumbWidth": "Ancho máximo", + "thumbHeight": "Altura máxima", + "thumbSuffix": "Sufijo de archivo Blob", + "thumbSuffixDes": "El sufijo agregado al nombre de archivo Blob original para la miniatura generada, ", + "thumbFormat": "Formato de imagen", + "thumbFormatDes": "Formato de imagen preferido, si el generador no lo soporta, automáticamente se degradará al formato jpg.", + "thumbQuality": "Calidad", + "thumbQualityDes": "Porcentaje de calidad de compresión, válido solo para codificación jpg y webp. ", + "thumbGC": "Ejecutar GC después de generar miniatura", + "captcha": "Captcha", + "captchaType": "Tipo de captcha", + "captchaTypeDes": "Seleccionar tipo de captcha y proveedor.", + "plainCaptcha": "Gráfico plano", + "reCaptchaV2": "reCAPTCHA V2", + "turnstile": "Cloudflare Turnstile", + "turnstileSiteKey": "Clave del Sitio", + "turnstileSiteKSecret": "Secreto", + "cap": "Cap", + "capInstanceURL": "URL de Instancia", + "capInstanceURLDes": "La URL de tu servidor Cap auto-alojado. Para más detalles, consulta la <0>documentación de modo independiente.", + "capSiteKey": "Clave del Sitio", + "capSiteKeyDes": "La clave del sitio desde el panel de tu servidor Cap.", + "capSecretKey": "Clave Secreta", + "capSecretKeyDes": "La clave secreta desde el panel de tu servidor Cap.", + "capAssetServer": "Fuente del Servidor de Recursos", + "capAssetServerDes": "Elige la fuente para cargar recursos estáticos de captcha Cap. Usar servidor auto-desplegado requiere configurar variables de entorno en el lado del servidor, por favor consulta <0>habilitar servidor de recursos.", + "capAssetServerJsdelivr": "jsDelivr CDN", + "capAssetServerUnpkg": "unpkg CDN", + "capAssetServerInstance": "Servidor auto-alojado", + "captchaProvider": "Proveedor de captcha", + "captchaWidth": "Ancho", + "captchaHeight": "Altura", + "captchaLength": "Longitud", + "captchaLengthDes": "La longitud de los caracteres en el captcha.", + "captchaMode": "Modo", + "captchaModeNumber": "Números", + "captchaModeLetter": "Letras", + "captchaModeMath": "Matemáticas", + "captchaModeNumberLetter": "Números + Letras", + "captchaElement": "Elementos dentro de la imagen captcha.", + "complexOfNoiseText": "Complejidad del texto de ruido", + "complexOfNoiseDot": "Complejidad de puntos de ruido", + "showHollowLine": "Mostrar líneas huecas", + "showNoiseDot": "Mostrar puntos de ruido", + "showNoiseText": "Mostrar texto de ruido", + "showSlimeLine": "Mostrar líneas finas", + "showSineLine": "Mostrar líneas sinusoidales", + "siteKey": "Clave del Sitio", + "siteKeyDes": "Puedes encontrarla en la <0>Página de Administración de Aplicaciones.", + "siteSecret": "Secreto", + "siteSecretDes": "Puedes encontrarlo en la <0>Página de Administración de Aplicaciones.", + "secretID": "SecretId", + "secretIDDes": "Puedes encontrarlo en la <0>Página de Administración de Acceso.", + "secretKey": "SecretKey", + "secretKeyDes": "Puedes encontrarlo en la <0>Página de Administración de Acceso.", + "tCaptchaAppID": "APPID", + "tCaptchaAppIDDes": "Puedes encontrarlo en la <0>Página de Administración de Captcha.", + "tCaptchaSecretKey": "Clave Secreta de Aplicación", + "tCaptchaSecretKeyDes": "Puedes encontrarla en la <0>Página de Administración de Captcha.", + "staticResourceCache": "Cache de recursos estáticos públicos", + "staticResourceCacheDes": "Edad máxima de cache para recursos estáticos públicamente accesibles (ej. enlace de fuente de política local, enlace de descarga).", + "creditSystem": "Sistema de créditos", + "creditAndVAS": "Crédito y VAS", + "enableCredit": "Habilitar sistema de créditos", + "enableCreditDes": "Habilitar sistema de créditos para permitir a los usuarios establecer precios para sus enlaces de compartir.", + "creditPrice": "Precio de crédito", + "creditPriceDes": "Precio para recargar puntos de crédito con dinero (en unidad mínima de moneda). Llenar 0 para deshabilitar recarga de crédito.", + "shareScoreRate": "Tasa de comisión del propietario de compartir", + "shareScoreRateDes": "Porcentaje (1-100) de puntos de crédito que los propietarios de compartir reciben cuando sus enlaces de compartir son comprados.", + "cronNotifyUser": "Intervalo de escaneo para usuarios sobre el límite", + "cronNotifyUserDes": "Escanear y enviar recordatorios por email a usuarios sobre el límite, ", + "cronBanUser": "Horario de prohibición de usuarios", + "cronBanUserDes": "Escanear y prohibir usuarios que excedan los límites de almacenamiento y períodos de buffer", + "anonymousPurchase": "Compra anónima", + "anonymousPurchaseDes": "Permitir a usuarios no conectados comprar enlaces de compartir directamente", + "shopNavEnabled": "Mostrar Navegación de Tienda", + "shopNavEnabledDes": "Mostrar elementos de 'Tienda' en la navegación de la barra lateral", + "paymentSettings": "Configuración de pagos", + "currencyCode": "Código de moneda", + "currencyCodeDes": "Código de moneda de tres letras (ej., USD, CNY, EUR)", + "currencySymbol": "Símbolo de moneda", + "currencySymbolDes": "Símbolo de moneda a mostrar (ej., $, Â¥, €)", + "currencyUnit": "Unidad de moneda", + "currencyUnitDes": "Unidad mínima de moneda (ej., 100 para dólares/centavos)", + "paymentProviders": "Proveedores de pago", + "providerName": "Nombre del proveedor, utilizado para mostrar a los usuarios.", + "providerType": "Tipo de proveedor", + "providerKey": "Clave secreta", + "selectCurrency": "Seleccionar moneda común", + "addPaymentProvider": "Agregar proveedor de pago", + "stripeProvider": "Stripe", + "weixinProvider": "WeChat Pay", + "alipayProvider": "Alipay", + "customProvider": "Proveedor de pago personalizado", + "customProviderDes": "Crear un plugin para conectar a otras pasarelas de pago, consulta la <0>documentación para más detalles.", + "providerKeyDes": "Clave secreta de API de Stripe.", + "storageProductSettings": "Producto de almacenamiento", + "storageProductsDes": "Configurar productos que los usuarios pueden comprar para extender su espacio de almacenamiento.", + "addStorageProduct": "Agregar SKU", + "editStorageProduct": "Editar SKU", + "storageSize": "Tamaño de almacenamiento", + "storageSizeBytes": "Tamaño incluido en este SKU", + "duration": "Duración", + "durationSeconds": "Duración en segundos (ej. 2592000 para 30 días)", + "price": "Precio", + "priceInUnits": "Precio (en unidad mínima de moneda)", + "priceInUnitsDes": "El precio se mostrará como:", + "chipLabel": "Etiqueta (opcional)", + "chipLabelHelp": "Una etiqueta de texto corta mostrada junto al nombre del producto", + "usePoints": "Permitir pagar con puntos", + "points": "Puntos", + "pointsHelp": "Número de puntos requeridos para comprar este producto", + "pointsUnit": "puntos", + "groupProductSettings": "Producto de grupo", + "groupProductsDes": "Configurar productos que los usuarios pueden comprar para unirse a grupos de usuarios específicos.", + "addGroupProduct": "Agregar producto de grupo", + "editGroupProduct": "Editar producto de grupo", + "groupId": "ID de Grupo", + "groupIdHelp": "El grupo de usuario al que actualizar después de comprar este producto.", + "description": "Descripción", + "descriptionHelp": "Ingresa características o beneficios, uno por línea", + "receiptEmailTemplate": "Plantilla de recibo de pago", + "receiptEmailTemplateDes": "Plantilla de email enviada a los usuarios cuando se confirma un pago.", + "activationEmailTemplate": "Plantilla de activación de cuenta", + "activationEmailTemplateDes": "Plantilla de email enviada a los usuarios para activar sus cuentas.", + "quotaExceededEmailTemplate": "Plantilla de cuota de almacenamiento excedida", + "quotaExceededEmailTemplateDes": "Plantilla de email enviada a los usuarios cuando exceden su cuota de almacenamiento.", + "resetPasswordEmailTemplate": "Plantilla de restablecimiento de contraseña", + "resetPasswordEmailTemplateDes": "Plantilla de email enviada a los usuarios cuando solicitan un restablecimiento de contraseña.", + "preferredLanguage": "Idioma preferido", + "setAsPreferredLanguage": "Establecer como idioma preferido", + "setAsPreferredLanguageDes": "Si no se puede obtener la preferencia de idioma del usuario, se utilizará la plantilla de correo electrónico del idioma preferido.", + "alreadyAsPreferredLanguageDes": "El idioma actual ya está establecido como preferido. Si no se puede obtener la preferencia de idioma del usuario, se utilizará esta plantilla de correo electrónico.", + "addLanguage": "Agregar idioma", + "removeLanguage": "Eliminar idioma", + "removeLanguageBtn": "Eliminar idioma", + "cannotRemovePreferredLanguageDes": "No se puede eliminar el idioma preferido. Por favor establece otro idioma como preferido e inténtalo de nuevo.", + "languageCodeDes": "Por favor selecciona el idioma que quieres agregar.", + "emailSubject": "Asunto del email", + "emailSubjectDes": "La línea de asunto del email. Puedes usar <0>variables mágicas para personalizar el asunto del email.", + "emailBody": "Cuerpo del email", + "emailBodyDes": "Contenido HTML del email. Puedes usar <0>variables mágicas para personalizar el contenido del email.", + "orderTitle": "Título de la orden", + "themeOptions": "Opciones de tema", + "themeOptionsDes": "Configurar opciones de tema personalizadas para tu sitio. Estos temas estarán disponibles para que los usuarios seleccionen en sus preferencias.", + "primaryColor": "Color primario", + "secondaryColor": "Color secundario", + "primaryColorDark": "Color primario (Oscuro)", + "secondaryColorDark": "Color secundario (Oscuro)", + "addThemeOption": "Agregar opción de tema", + "editThemeOption": "Editar opción de tema", + "invalidThemeConfig": "Configuración de tema inválida. Por favor verifica tu sintaxis JSON.", + "themeConfiguration": "Configuración de tema", + "themePreview": "Vista previa de tema", + "lightTheme": "Tema claro", + "darkTheme": "Tema oscuro", + "previewTitle": "Título de vista previa", + "previewTextField": "Campo de entrada", + "previewPrimary": "Primario", + "invalidThemePreview": "Configuración de tema inválida para vista previa", + "duplicateThemeColor": "Ya existe un tema con este color primario. Por favor elige un color diferente.", + "themeDes": "Las configuraciones completas disponibles pueden consultarse en <0>Visor de tema predeterminado - Material-UI.", + "defaultTheme": "Predeterminado", + "auditLog": "Eventos", + "auditLogDes": "Configurar qué eventos deberían ser registrados. Algunos eventos podrían ser utilizados por el sistema para proporcionar características adicionales, ej. actividad de archivos y actividad de inicio de sesión.", + "systemEvents": "Eventos del sistema", + "systemEventsDes": "Eventos relacionados con operaciones y estado del sistema.", + "userEvents": "Eventos de usuario", + "userEventsDes": "Eventos relacionados con cuentas de usuario, autenticación, y cambios de perfil.", + "fileEvents": "Eventos de archivos", + "fileEventsDes": "Eventos relacionados con operaciones de archivos como carga, descarga, y modificación.", + "shareEvents": "Eventos de compartir", + "shareEventsDes": "Eventos relacionados con compartir archivos y acceso a enlaces.", + "versionEvents": "Eventos de versión", + "versionEventsDes": "Eventos relacionados con administración de versiones de archivos.", + "mediaEvents": "Eventos de medios", + "mediaEventsDes": "Eventos relacionados con procesamiento de medios como generación de miniaturas.", + "filesystemEvents": "Eventos del sistema de archivos", + "filesystemEventsDes": "Eventos relacionados con operaciones del sistema de archivos como montaje y manejo de archivos.", + "webdavEvents": "Eventos WebDAV", + "webdavEventsDes": "Eventos relacionados con administración de cuentas WebDAV y acceso.", + "paymentEvents": "Eventos de pago", + "paymentEventsDes": "Eventos relacionados con pagos, puntos, y administración de membresías.", + "emailEvents": "Eventos de email", + "emailEventsDes": "Eventos relacionados con envío de emails y notificaciones.", + "toggleAll": "Alternar todos", + "toggleAllDes": "Habilitar o deshabilitar todos los eventos en esta categoría.", + "event": { + "file_imported": "Archivo externo importado", + "server_start": "Inicio del servidor", + "user_signup": "Registro de usuario", + "email_sent": "Email enviado", + "user_activated": "Usuario activado", + "user_login_failed": "Inicio de sesión fallido", + "user_login": "Inicio de sesión de usuario", + "user_token_refresh": "Actualización de token", + "file_create": "Archivo creado", + "file_rename": "Archivo renombrado", + "set_file_permission": "Permiso cambiado", + "entity_uploaded": "Archivo cargado o actualizado", + "entity_downloaded": "Archivo descargado", + "copy_from": "Copiar desde", + "copy_to": "Copiar a", + "move_to": "Mover a", + "delete_file": "Archivo eliminado", + "move_to_trash": "Mover a papelera", + "share": "Compartir creado", + "share_link_viewed": "Enlace de compartir visto", + "set_current_version": "Establecer versión actual", + "delete_version": "Eliminar versión", + "thumb_generated": "Miniatura generada", + "live_photo_uploaded": "Foto en vivo cargada", + "update_metadata": "Metadatos actualizados", + "edit_share": "Compartir editado", + "delete_share": "Compartir eliminado", + "mount": "Montar", + "relocate": "Reubicar", + "create_archive": "Crear archivo", + "extract_archive": "Extraer archivo", + "webdav_login_failed": "Inicio de sesión WebDAV fallido", + "webdav_account_create": "Cuenta WebDAV creada", + "webdav_account_update": "Cuenta WebDAV actualizada", + "webdav_account_delete": "Cuenta WebDAV eliminada", + "payment_created": "Pago creado", + "points_change": "Puntos cambiados", + "payment_paid": "Pago realizado", + "payment_fulfilled": "Orden cumplida", + "payment_fulfill_failed": "Cumplimiento de orden fallido", + "storage_added": "Almacenamiento agregado", + "group_changed": "Grupo cambiado", + "user_exceed_quota_notified": "Notificación de cuota excedida", + "user_changed": "Estado de usuario cambiado", + "get_direct_link": "Obtener enlace directo", + "link_account": "Vincular cuenta externa", + "unlink_account": "Desvincular cuenta externa", + "change_nick": "Cambiar apodo", + "change_avatar": "Cambiar avatar", + "membership_unsubscribe": "Cancelar suscripción de membresía", + "change_password": "Cambiar contraseña", + "enable_2fa": "Habilitar 2FA", + "disable_2fa": "Deshabilitar 2FA", + "add_passkey": "Agregar llave de paso", + "remove_passkey": "Remover llave de paso", + "redeem_gift_code": "Canjear código de regalo", + "update_view": "Cambió configuración de vista", + "delete_direct_link": "Eliminar enlace directo", + "report_abuse": "Reportar abuso" + }, + "server": "Servidor", + "tempPath": "Ruta temporal", + "tempPathDes": "El directorio para almacenar archivos temporales, relativo al directorio de datos de Cloudreve. Por favor asegúrate de que no haya tareas de cola ejecutándose antes de modificarlo.", + "siteID": "ID del Sitio", + "siteIDDes": "Un ID único para identificar el sitio, generalmente no necesita ser modificado.", + "siteSecretKey": "Clave maestra", + "siteSecretKeyDes": "La clave maestra utilizada para cifrar tokens de usuario y firmas. Después de la rotación, todos los tokens de usuario y firmas serán inválidos. Toma efecto después de reiniciar Cloudreve.", + "rotateSecretKey": "Rotar clave maestra", + "hashidSalt": "Salt de HashID", + "hashidSaltDes": "El valor salt utilizado para generar HashID. Por favor ten cuidado al cambiarlo, ya que invalidará los enlaces directos y enlaces de compartir existentes.", + "accessTokenTTL": "TTL de token de acceso", + "accessTokenTTLDes": "El TTL de tokens de acceso, en segundos.", + "refreshTokenTTL": "TTL de token de actualización", + "refreshTokenTTLDes": "El TTL de tokens de actualización, en segundos. Afecta la duración del estado de inicio de sesión del usuario.", + "cronGarbageCollect": "Intervalo de escaneo de recolección de basura", + "cronGarbageCollectDes": "Establecer con qué frecuencia escanear y reciclar datos expirados en archivos temporales y almacenamiento KV.", + "startWithProtocol": "Debe comenzar con http:// o https://", + "tlsWarning": "El sitio actual está usando https, llenar una URL http aquí puede causar excepciones.", + "blobUrlCache": "Cache de URL de Blob", + "clearBlobUrlCache": "Limpiar cache de URL de Blob", + "clearBlobUrlCacheDes": "Para aumentar la tasa de aciertos de cache, Cloudreve cachea y reutiliza URLs de Blob. Cuando la dirección CDN u otras configuraciones cambien, por favor limpia el cache.", + "cacheCleared": "Cache limpiado." + }, + "giftCodes": { + "giftCodesSettings": "Códigos de Regalo", + "generateGiftCodes": "Generar Códigos de Regalo", + "giftCodeQuantity": "Cantidad", + "giftCodeQuantityHelp": "Número de códigos de regalo a generar", + "giftCodeProductType": "Tipo de Producto", + "giftCodeTypePoints": "Puntos", + "giftCodeTypeStorage": "Almacenamiento", + "giftCodeTypeGroup": "Grupo", + "giftCodePointsAmount": "Cantidad de Puntos", + "giftCodePointsAmountHelp": "Número de puntos a acreditar cuando se canjee el código", + "giftCodeProduct": "Producto", + "selectStorageProduct": "Seleccionar producto de almacenamiento", + "selectGroupProduct": "Seleccionar producto de grupo", + "giftCodeType": "Tipo", + "giftCodeAmount": "Cantidad", + "giftCode": "Código de Regalo", + "giftCodeStatus": "Estado", + "giftCodeUsedBy": "Usado por", + "giftCodeUsed": "Usado", + "giftCodeUnused": "Disponible", + "giftCodeDeleted": "Código de regalo eliminado exitosamente", + "giftCodesGenerated": "Códigos de regalo generados exitosamente", + "noGiftCodes": "No hay códigos de regalo disponibles", + "generatedCodesTitle": "Códigos de Regalo Generados", + "generatedCodesDescription": "Copia estos códigos de regalo para compartir con los usuarios. Cada código se puede usar una vez.", + "copyAndClose": "Copiar y Cerrar", + "duratonTimes": "Cantidad", + "duratonTimesDes": "Cuántas cantidades del producto se incluyen en cada código de regalo.", + "unknownProduct": "Producto Desconocido" + }, + "policy": { + "acceleratedDomainUpload": "Usar dominio de aceleración de transferencia para subida", + "acceleratedDomainUploadDes": "Cuando esté habilitado, el <0>dominio de aceleración de transferencia de Qiniu se usará al subir archivos.", + "compare": "Comparar", + "deletePolicyConfirmation": "¿Estás seguro de que quieres eliminar la política de almacenamiento {{name}}?", + "streamSaver": "Descargar vía navegador", + "streamSaverDes": "Cuando esté habilitado, las solicitudes de descarga de los usuarios serán manejadas por el navegador. Debido a la limitación de la política de almacenamiento OneDrive, el nombre del archivo descargado directamente por los usuarios no puede ser el mismo que el nombre del archivo en Cloudreve, usar el navegador para manejar descargas puede resolver este problema.", + "oauthCallbackFailed": "Autorización fallida", + "httpsRequired": "La aplicación Entra ID requiere URL de redirección HTTPS, pero el sitio actual está usando HTTP, lo que puede causar falla de redirección después del inicio de sesión, por favor reemplaza manualmente el HTTPS en la barra de direcciones del navegador con HTTP.", + "authorizeMicrosoft": "Iniciar sesión con Microsoft", + "redirectUrl": "URL de redirección", + "redirectUrlDes": "La visualización actual es la URL de redirección más reciente que cumple con los requisitos. Por favor confirma si la URL de redirección en la configuración de la aplicación es consistente con la actual.", + "authorizeOneDrive": "Confirmar configuración de aplicación Entra ID", + "authorizeOneDriveDes": "Por favor confirma si la siguiente información de aplicación Entra ID sigue siendo válida. Si es necesario, por favor realiza cambios.", + "authorizeNow": "Autorizar", + "authorizeAgain": "Autorizar nuevamente", + "notGranted": "No hay cuenta autorizada, la política de almacenamiento no se puede usar.", + "granted": "Cuenta autorizada, credencial actualizada en <0>{{time}}.", + "grantedNotRefresh": "Cuenta autorizada, credencial no actualizada desde el último inicio.", + "batchDeleteSize": "Tamaño máximo de eliminación en lote", + "batchDeleteSizeDes": "Limitar el número máximo de archivos que se pueden eliminar en una sola solicitud API. Esta configuración no afectará la eliminación de archivos en lote del usuario. Si no se completa, se usará el valor predeterminado <0>1000. Este es el valor máximo permitido para la API oficial de S3.", + "bucketPolicy": "Política de bucket", + "cdnOrCustomDomain": "CDN o CNAME personalizado", + "bucketDomain": "Dominio del bucket", + "bucketDomainDes": "Completa el dominio acelerado por CDN o dominio CNAME personalizado que has vinculado para el bucket de almacenamiento.", + "storageNodeInternal": "Nodo de almacenamiento (Endpoint de Intranet)", + "chunkSizeDesOssObs": "Rango permitido: 100 KB ~ 5 GB.", + "chunkSizeDesQiniuCos": "Rango permitido: 1 MB ~ 1 GB.", + "chunkSizeDesS3": "Rango permitido: 5 MB ~ 5 GB.", + "thisIsACustomDomain": "Este es un dominio personalizado", + "thisIsACustomDomainDes": "Si has vinculado un dominio personalizado al bucket de almacenamiento, y necesitas gestionar el bucket vía el dominio personalizado, por favor marca esta opción. Después de habilitado, Cloudreve no intentará añadir el nombre del Bucket en el dominio de solicitud.", + "addedManually": "Lo he configurado manualmente", + "origin": "Origen", + "allowMethods": "Métodos permitidos", + "exposeHeaders": "Encabezados expuestos", + "allowHeaders": "Encabezados permitidos", + "maxAge": "Max Age", + "accessCredential": "Credencial de acceso", + "downloadTrafficDiagram": "Demostración de ruta de tráfico de descarga", + "downloadRelay": "Relé de descarga", + "downloadRelayDes": "Cuando esté habilitado, las solicitudes de descarga de los usuarios serán proxy por Cloudreve.", + "download": "Descarga", + "downloadCdn": "CDN de descarga", + "useDownloadCdn": "Usar CDN para tráfico de descarga", + "skipSign": "Omitir firma de URL para CDN", + "skipSignDes": "Si has habilitado \"Usar autenticación de origen\" para este dominio en la configuración del bucket, por favor marca esta opción.", + "cdnHost": "Host CDN", + "downloadCdnDes": "El host, protocolo y puerto de la URL que los usuarios usan para acceder a archivos será reemplazado con el host CDN que especifiques.", + "mediaExtractorProxy": "Proxy de extracción de medios", + "mediaExtractorProxyDes": "Habilita esta función para extraer metadatos de medios de archivos que no son compatibles con los extractores nativos del proveedor de almacenamiento. Por favor configura el extractor de medios en <0>Procesamiento de medios.", + "mediaExtractorNative": "extractores nativos", + "mediaExtractorOss": "Gestión Inteligente de Medios (IMM)", + "mediaExtractorQiniu": "Qiniu DORA", + "mediaExtractorCos": "Procesamiento de Datos de Tencent Cloud", + "mediaExtractorObs": "servicio de procesamiento de imágenes", + "nativeMediaMetaExts": "Extensiones de archivo habilitadas para <0>{{name}}", + "nativeMediaMetaExtsGeneralDes": "Separadas por comas, valor vacío significa deshabilitar <0>{{name}}.", + "nativeMediaMetaExtsRemote": "Para almacenamiento esclavo, el soporte predeterminado es EXIF y metadatos de música, puedes anular esto configurando el nodo esclavo con más extractores.", + "nativeMediaMetaExtOss": " El servicio de Gestión Inteligente de Medios (IMM) admite el procesamiento de audio, video e imágenes. El procesamiento de imágenes no requiere configuración manual, pero si necesitas procesar audio o video, debes activar manualmente IMM y vincularlo al Bucket, consulta el <0>documento para la vinculación. Después de la vinculación, agrega las extensiones que quieres procesar al campo anterior.", + "nativeMediaMetaExtQiniu": "El servicio Qiniu DORA admite el procesamiento de audio, video e imágenes comunes, no se requiere configuración adicional, completa las extensiones que quieres procesar arriba.", + "nativeMediaMetaExtCos": "El servicio de Procesamiento de Datos de Tencent Cloud admite el procesamiento de audio, video e imágenes. El procesamiento de imágenes no requiere configuración manual, pero si necesitas procesar audio o video, primero ve a <0>Procesamiento de Datos de Tencent Cloud para activar y vincular el bucket de almacenamiento, luego ve a Configuración del Bucket - Procesamiento de medios para activar el servicio de procesamiento de imágenes. Después de la vinculación, agrega las extensiones que quieres procesar al campo anterior.", + "nativeMediaMetaExtObs": "El servicio de procesamiento de imágenes admite <0>extraer EXIF de imagen. No se requiere configuración manual, solo agrega las extensiones que quieres procesar arriba.", + "thumbProxy": "Proxy de generación de miniaturas", + "thumbProxyDes": "Habilita esta función para generar miniaturas para archivos que no cumplen las condiciones de miniatura nativa. Cloudreve intentará generar miniaturas y subirlas al lado del almacenamiento. Por favor configura el generador de miniaturas en <0>Procesamiento de medios.", + "nativeThumbnailMaxSize": "Tamaño máximo de miniaturas nativas", + "nativeThumbnailMaxSizeDes": "Ingresa 0 para deshabilitar el límite de tamaño, archivos más grandes que este tamaño no usarán miniaturas nativas.", + "nativeThumbNailsSupportAllExts": "Habilitar para todas las extensiones de archivo", + "nativeThumbNails": "Extensiones de archivo para miniaturas nativas", + "nativeThumbNailsGeneralDes": "Separadas por comas, valor vacío significa deshabilitar miniatura nativa, para las extensiones de archivo listadas arriba, Cloudreve usará la función de miniatura nativa del proveedor de almacenamiento para generar miniaturas.", + "nativeThumbNailsGeneralRemote": " Para almacenamiento esclavo, el soporte integrado es miniaturas simples de imagen y portada de música, puedes anular esto configurando el nodo esclavo con más generadores.", + "nativeThumbNailsGeneralOss": "Para almacenamiento Alibaba Cloud OSS, el servicio de <0>procesamiento de imágenes se usará para generar miniaturas.", + "nativeThumbNailsGeneralQiniu": "Para almacenamiento Qiniu Cloud, el servicio de <0>procesamiento básico de imágenes(imageView2) se usará para generar miniaturas.", + "nativeThumbNailsGeneralCos": "Para almacenamiento Tencent Cloud COS, el servicio de <0>Procesamiento de Datos de Tencent Cloud se usará para generar miniaturas.", + "nativeThumbNailsGeneralObs": "Para almacenamiento Huawei Cloud OBS, el servicio de <0>procesamiento de imágenes se usará para generar miniaturas.", + "nativeThumbNailsGeneralUpyun": "Para almacenamiento Upyun, el servicio de <0>procesamiento de imágenes se usará para generar miniaturas.", + "preallocate": "Pre-asignar espacio en disco", + "preallocateDes": "Cuando esté habilitado, la solicitud de subida del usuario pre-asignará espacio en disco en el nodo de almacenamiento, y también admite la carga paralela de fragmentos. Solo efectivo en Linux o Darwin.", + "chunkConcurrency": "Subidas de fragmentos concurrentes", + "chunkConcurrencyDes": "Establece el número de subidas de fragmentos concurrentes al usar subida directa web.", + "sourceWebEdit": "Edición en línea web", + "uploadRelay": "Relé de subida", + "uploadRelayDes": "Si está habilitado, las solicitudes de subida de los usuarios serán retransmitidas al nodo de almacenamiento vía Cloudreve, debido a la incapacidad de realizar subidas fragmentadas, ajusta el límite de tamaño máximo de subida del servidor web en consecuencia.", + "customProxy": "Proxy personalizado", + "storageNode": "Proveedor de almacenamiento", + "sourceWeb": "Web / Aplicación oficial", + "sourceDav": "WebDAV", + "uploadTrafficDiagram": "Demostración de ruta de tráfico de subida", + "node": "Nodo de almacenamiento", + "nodeDes": "Por favor selecciona un nodo esclavo para almacenamiento de archivos, puedes crear o gestionar nodos de almacenamiento esclavo en <0>Lista de nodos.", + "noBindedGroupWarning": "La política de almacenamiento actual no está vinculada a ningún grupo de usuarios, por favor ve a <0>Lista de grupos para vincular la política de almacenamiento actual a un grupo de usuarios.", + "nameRuleImmutable": " Modificar configuraciones no afectará archivos existentes en la política de almacenamiento. La ruta del Blob está fija después de la creación, incluso si las variables mágicas en ella cambian, la ruta no se actualizará.", + "uniqueVarRequired": "Por favor incluye al menos una variable única en la ruta del directorio o nombre del blob: {uuid}, {randomkey8}, {randomkey16}.", + "storageAndUpload": "Almacenamiento y Subida", + "blobFolderNaming": "Directorio de Almacenamiento Blob", + "blobFolderNamingDes": "El directorio donde se almacenan los Blobs de archivos, puedes usar <0>variables mágicas.", + "blobNameDes": "El nombre del Blob del archivo, puedes usar <0>variables mágicas, asegúrate de que sea absolutamente único, incluso para múltiples subidas del mismo nombre de archivo en la misma ruta en poco tiempo.", + "blobName": "Nombre del Blob", + "basicInfo": "Información básica", + "editX": "Editar {{name}}", + "noGroupBinded": "Ningún grupo vinculado", + "create": "Crear", + "addXStoragePolicy": "Agregar política de almacenamiento {{type}}", + "loadSummary": "Cargar resumen", + "policySummary": "{{count}} Blobs de archivo ({{size}})", + "sharp": "#", + "name": "Nombre", + "type": "Tipo", + "childFiles": "Archivos hijos", + "totalSize": "Tamaño total", + "actions": "Acciones", + "authSuccess": "Autorización otorgada.", + "policyDeleted": "Política eliminada.", + "newStoragePolicy": "Nueva política de almacenamiento", + "all": "Todos", + "local": "Local", + "remote": "Nodo Remoto", + "qiniu": "Qiniu", + "upyun": "Upyun", + "oss": "Alibaba Cloud OSS", + "cos": "Tencent Cloud COS", + "onedrive": "OneDrive", + "s3": "Compatible con S3", + "ks3": "Kingsoft Cloud S3", + "obs": "Huawei Cloud OBS", + "load_balance": "Balanceador de Carga", + "childPolicy": "Política de Almacenamiento Hija", + "childPolicyDes": "Selecciona las políticas de almacenamiento hijas para agregar al pool de balanceador de carga.", + "weight": "Peso", + "addTargetPolicy": "Agregar Política Hija", + "selectPolicies": "Seleccionar Políticas", + "selectPoliciesDes": "Selecciona políticas de almacenamiento para agregar al pool de balanceador de carga.", + "loadBalanceDes": "Al usar la política de almacenamiento balanceada por carga, las nuevas subidas serán distribuidas aleatoriamente a diferentes políticas de almacenamiento hijas basándose en el peso.", + "xChildPolicies": "{{count}} políticas de almacenamiento hijas", + "refresh": "Actualizar", + "delete": "Eliminar", + "edit": "Editar", + "selectAStorageProvider": "Seleccionar un proveedor de almacenamiento", + "maxSizeOfSingleFile": "Tamaño máximo de archivo único", + "maxSizeOfSingleFileDes": "Ingresa 0 para deshabilitar el límite.", + "enterFileExt": "Separado por punto y coma, deja en blanco para permitir todas las extensiones de archivo.", + "extList": "Restricciones de extensión de archivo", + "noLimit": "Sin límite", + "whitelist": "Permitir", + "blacklist": "Denegar", + "fileNameRegex": "Reglas regex de nombre de archivo", + "fileNameRegexDes": "Expresión regular para coincidir nombres de archivo, deja en blanco para ninguna restricción.", + "chunkSizeDes": "Especifica el tamaño de fragmento para subidas fragmentadas. Un valor de 0 significa que no se usan subidas fragmentadas, pero el tamaño máximo de subida puede estar limitado por el servidor web.", + "chunkSizeDesSuffix": "{{prefix}} Con subida fragmentada, los archivos subidos por usuarios serán cortados en fragmentos y subidos al lado del almacenamiento uno por uno. Después de que la subida se interrumpa, los usuarios pueden elegir continuar subiendo desde el último fragmento subido.", + "chunkSize": "Tamaño de fragmento", + "policyName": "El nombre de visualización de la política de almacenamiento, también usado para presentar a los usuarios.", + "magicVar": { + "fileNameMagicVar": "Variables mágicas de nombre de archivo", + "pathMagicVar": "Variables mágicas de ruta", + "variable": "Variable", + "description": "Descripción", + "example": "Ejemplo", + "16digitsRandomString": "Cadena aleatoria de 16 dígitos", + "8digitsRandomString": "Cadena aleatoria de 8 dígitos", + "secondTimestamp": "Marca de tiempo", + "nanoTimestamp": "Marca de tiempo nano", + "uid": "ID de usuario", + "originalFileName": "Nombre de archivo original", + "originFileNameNoext": "Nombre de archivo original sin extensión", + "extension": "Nombre de extensión de archivo", + "uuidV4": "UUID V4", + "date": "Fecha", + "dateAndTime": "Fecha y hora", + "randomNumber": "Número aleatorio dentro del rango", + "year": "Año", + "month": "Mes", + "day": "Día", + "hour": "Hora", + "minute": "Minuto", + "second": "Segundo", + "path": "La ruta inicial mientras el usuario sube el archivo" + }, + "storageBucket": "Bucket de almacenamiento", + "wanSiteURLDes": "Antes de usar esta política, asegúrate de que la dirección que ingresaste en Configuración Básica - Información del Sitio - URL del Sitio coincida con la dirección real y <0>pueda ser accedida apropiadamente por WAN.", + "enterQiniuBucket": "Ve al <0>panel de Qiniu para crear un bucket de almacenamiento. Ingresa el \"Nombre del bucket\" que acabas de crear.", + "aclType": "Tipo de control de acceso", + "accessTypePulic": "Lectura pública escritura privada", + "accessTypePrivate": "Lectura/escritura privada", + "accessType": "Tipo de acceso", + "qiniuBucketName": "Nombre del bucket", + "cosObsBucketName": "Nombre del bucket", + "bucketType": "ACL del bucket", + "bucketTypeDes": "Selecciona el tipo de ACL para el bucket que acabas de crear.", + "privateBucket": "Privado", + "privateDes": "Cloudreve firmará la URL del archivo.", + "publicBucket": "Lectura pública", + "publicStorage": "Público", + "publicDes": "No recomendado, Cloudreve devolverá directamente el enlace directo del archivo, que no puede controlar efectivamente el acceso de archivos.", + "bucketCDNDes": "Completa el nombre de dominio acelerado por CDN que has vinculado para el bucket de almacenamiento.", + "bucketCDNDomain": "Dominio CDN", + "qiniuCredentialDes": "Ve a Centro Personal - Gestión de Credenciales en el panel de Qiniu y completa el AK, SK obtenido.", + "ak": "AK", + "sk": "SK", + "cannotEnableForPrivateBucket": "Si esta función está habilitada para bucket privado, necesitas habilitar \"Usar enlace de origen redirigido\" para grupos de usuarios.", + "chunkSizeLabelQiniu": "Especifica el tamaño de fragmento para subidas reanudables. El rango permitido es 1 MB - 1 GB.", + "corsSettingStep": "Política CORS", + "corsPolicyAdded": "Política CORS agregada.", + "createOSSBucketDes": "Ve al <0>Panel OSS para crear un Bucket. Solo se admiten clases de almacenamiento <1>Estándar e <2>IA.", + "bucketName": "Nombre del bucket", + "publicReadBucket": "Lectura pública", + "ossEndpointDes": "Ve a la página de resumen del Bucket, ingresa el <2>Puerto bajo la sección <1>Acceso por Internet, en la página <0>Endpoint.", + "ossEndpointDesInternalHint": "Si necesitas configurar endpoint de Intranet o dominio personalizado, puedes configurarlo después de crear la política de almacenamiento.", + "obsEndpointCnameHint": "Si necesitas configurar endpoint de dominio personalizado, puedes configurarlo después de crear la política de almacenamiento.", + "endpoint": "EndPoint", + "ossLANEndpointDes": "Dejar en blanco significa no usarlo. Si tu Cloudreve está desplegado en servicios de cómputo de Alibaba Cloud que están bajo la misma zona de disponibilidad que el bucket OSS, puedes especificar adicionalmente un endpoint de intranet, Cloudreve intentará usar este endpoint en el lado del servidor para reducir el costo de tráfico.", + "intranetEndPoint": "Endpoint de intranet", + "ossCDNDes": "¿Quieres usar Alibaba Cloud CDN para acelerar el acceso a archivos?", + "createOSSCDNDes": "Ve al <0>Panel CDN de Alibaba Cloud para crear un dominio CDN, la fuente del CDN debe ser tu bucket OSS. Ingresa el dominio CDN y selecciona si quieres usar HTTPS:", + "ossAKDes": "Obtén tu AccessKey en la página <0>Gestión de Información de Seguridad. También puedes crear un AccessKey con permiso <1>AliyunOSSFullAccess en <2>Control de Acceso RAM.", + "shouldNotContainSpace": "Esto no puede contener espacios.", + "nameThePolicyFirst": "Nombra la política de almacenamiento:", + "chunkSizeLabelOSS": "Especifica el tamaño de fragmento para subidas reanudables. El rango permitido es 100 KB - 5 GB.", + "ossCORSDes": "Esta política de almacenamiento requiere una política CORS para habilitar la subida desde el navegador. Cloudreve puede configurarla automáticamente para ti, o puedes configurarla manualmente siguiendo los pasos en la documentación. Si ya has configurado la política CORS para este Bucket, este paso se puede omitir.", + "letCloudreveHelpMe": "Deja que Cloudreve lo configure por mí", + "skip": "Omitir", + "createUpyunBucketDes": "Completa el nombre del servicio de almacenamiento que creaste en el <0>Panel de Upyun.", + "storageServiceName": "Nombre del servicio", + "operatorName": "Nombre del operador", + "operatorPassword": "Contraseña del operador", + "tokenStatus": "Token anti-hotlinking", + "upyunTokenDes": "Se recomienda encarecidamente habilitar Token Anti-Hotlinking, ve al panel de <0>Configuración de Características del servicio de almacenamiento creado, ve a la pestaña <1>Control de Acceso, habilita Token Anti-Hotlinking y establece un secreto.", + "tokenEnabled": "Habilitar Token Anti-Hotlinking", + "tokenDisabled": "No usar Token Anti-Hotlinking", + "upyunTokenSecretDes": "Ingresa el secreto del Token Anti-Hotlinking.", + "upyunTokenSecret": "Secreto Token Anti-Hotlinking", + "createCOSBucketDes": "Ve al <0>Panel de COS para crear un bucket de almacenamiento. Ve a la página de configuración básica del bucket creado, y copia el <1>Nombre del bucket arriba.", + "obsBucketDes": "Ve al <0>Panel de OBS para crear un bucket de almacenamiento. Ingresa el <1>Nombre del bucket que acabas de crear. La clase de almacenamiento solo soporta <2>Estándar o <3>Acceso Poco Frecuente.", + "cosPrivateRW": "Lectura/Escritura Privada", + "cosPublicRW": "Lectura Pública y Escritura Privada", + "cosAccessDomainDes": "En la página de resumen del Bucket creado, completa el <1>Dominio de Acceso proporcionado bajo la sección <0>Información de Dominio. También puedes usar tu dominio CNAME o dominio de aceleración CDN.", + "obsEndpointDes": "En la página de resumen del Bucket creado, completa el <1>Endpoint proporcionado bajo la sección <0>Información de Dominio.", + "accessDomain": "Dominio de acceso", + "cosCDNDomainDes": "Ve a la <0>Consola de Gestión CDN de Tencent Cloud para crear un dominio de aceleración CDN y establecer el sitio fuente al bucket COS que acabas de crear. Completa el nombre de dominio CDN abajo y selecciona si usar HTTPS.", + "cosCredentialDes": "Completa las claves de acceso obtenidas de la página de <0>Claves de Acceso de Tencent Cloud. Por favor asegúrate de que el par de claves tenga permiso de acceso a los servicios COS. También puedes crear un <2>sub-usuario con permiso de <1>Acceso Programático y otorgarle acceso al servicio COS.", + "obsCredentialDes": "Completa las claves de acceso obtenidas de la página de <0>Claves de Acceso de Huawei Cloud. También puedes crear un <2>usuario IAM con permiso de <1>Acceso Programático y otorgarle permiso de <3>Acceso de Operación OBS.", + "grantAccess": "Otorgar acceso", + "grantAccessLater": "Después de crear la política de almacenamiento, necesitas iniciar sesión y otorgar acceso en la página de configuración de la política de almacenamiento.", + "odHttpsWarning": "Debes habilitar HTTPS para usar políticas de almacenamiento OneDrive/SharePoint; después de habilitarlo, asegúrate de cambiar Configuración - Básico - Información del Sitio - URL del Sitio.", + "creatAadAppDes": "Ve al <0>Panel de Microsoft Entra ID, después de iniciar sesión, ve al panel de administración de <1>Microsoft Entra ID, opcionalmente puedes usar una cuenta diferente de la usada para almacenar archivos para iniciar sesión.", + "createAadAppDes2": "Ve al menú <0>Registros de Aplicaciones a la izquierda y haz clic en el botón <1>Nuevo registro. Completa el formulario de registro de aplicación. Asegúrate de que <2>Tipos de cuenta admitidos esté seleccionado como <3>Cuentas en cualquier directorio organizacional (Cualquier directorio Azure AD - Multitenente) y cuentas personales de Microsoft (ej. Skype, Xbox); <4>URI de redirección (opcional) esté seleccionado como <5>Web y completa <6>{{url}}; Para otros campos, déjalos por defecto.", + "entraIdApp": "Información de la aplicación Entra ID", + "aadAppIDDes": "Ve a la página <0>Resumen en Gestión de Aplicaciones, el valor de <1>ID de Aplicación (Cliente).", + "aadAppID": "ID de Aplicación (Cliente)", + "addAppSecretDes": "La forma de crear secreto del cliente: Ve al menú <0>Certificados y secretos en el lado izquierdo, haz clic en el botón <1>Nuevo secreto de cliente, y selecciona el tiempo más largo para <2>Expira. Necesitas crear un nuevo secreto de cliente después de que expire el anterior, y actualizar el nuevo en la configuración de la política de almacenamiento.", + "aadAppSecret": "Secreto del cliente", + "aadAccountCloud": "Endpoint de Microsoft Graph", + "aadAccountCloudDes": "Por favor selecciona el endpoint según el tipo de cuenta de Microsoft 365 que estés usando.", + "multiTenant": "Nube pública mundial", + "gallatin": "Nube china 21V", + "sharePointDes": "¿Quieres almacenar archivos en SharePoint?", + "saveToOneDrive": "Almacenar archivos en OneDrive predeterminado", + "spSiteURL": "URL del Sitio SharePoint", + "odReverseProxyURLDes": "¿Quieres usar un servidor proxy inverso personalizado para la descarga de archivos?", + "odReverseProxyURL": "URL del servidor proxy inverso", + "chunkSizeDesOd": "Rango permitido: 5 MB ~ 5GB, OneDrive requiere que debe ser un múltiplo entero de 320 KiB (327,680 bytes).", + "limitOdTPSDes": "Limitar frecuencia de solicitudes API de OneDrive", + "tps": "Límite TPS", + "tpsDes": "Dejar en blanco indica sin límite. Limita esta política de almacenamiento el número máximo de solicitudes API enviadas a OneDrive por segundo. Las solicitudes que excedan esta frecuencia serán limitadas. Cuando múltiples nodos de Cloudreve transfieren archivos, cada uno usa su propio token bucket, así que por favor escala este número hacia abajo apropiadamente en esta condición.", + "tpsBurst": "Ráfaga TPS", + "tpsBurstDes": "Cuando la solicitud está inactiva, Cloudreve puede reservar un número específico de slots para futuras ráfagas de tráfico.", + "odOauthDes": "Sin embargo, necesitarás hacer clic en el botón de abajo y autorizar con el inicio de sesión de cuenta Microsoft para completar la inicialización antes de poder usarlo. Puedes reautorizar más tarde en la página de Lista de Políticas de Almacenamiento.", + "gotoAuthPage": "Ir a la página de autorización", + "s3BucketDes": "Ve al panel de AWS S3 para crear un bucket, ingresa el <0>Nombre del bucket que acabas de crear:", + "s3EndpointDes": "Especifica el EndPoint (nodo geográfico) del bucket de almacenamiento en formato URL completo, ej. <0>https://bucket.region.example.com.", + "selectRegionDes": "Ingresa el código de región del bucket de almacenamiento, ej. <0>us-east-1. Para proveedores de almacenamiento compatibles con S3 que no son AWS, por favor consulta su documentación para cómo completar este campo.", + "chunkSizeLabelS3": "Especifica el tamaño de fragmento para subidas reanudables. El rango permitido es 5 MB - 5 GB.", + "policyEndpoint": "Endpoint.", + "s3Region": "Región", + "s3EndpointPathStyle": "Selecciona el formato de la dirección del Endpoint S3. Algunas políticas de almacenamiento compatibles con S3 de terceros pueden requerir esta opción para funcionar. Cuando se activa, forzaremos el uso de direcciones de formato estilo ruta, como <0>http://s3.amazonaws.com/BUCKET/KEY.", + "usePathEndpoint": "Forzar estilo de ruta", + "thumbExt": "Extensiones que soportan miniaturas", + "thumbExtDes": "Dejar en blanco indica que se usa el conjunto predefinido de la política de almacenamiento. No válido para políticas de almacenamiento locales y S3.", + "driverRoot": "Raíz del Controlador", + "driverRootDes": "Elige dónde guardar archivos en tu cuenta OneDrive. Cambiar esta opción hará que los archivos existentes en la política de almacenamiento sean inaccesibles.", + "saveToDefaultOneDrive": "Guardar archivos en el controlador OneDrive predeterminado", + "saveToSharePoint": "Guardar archivos en SharePoint", + "sharePointUrlDes": "Ingresa la URL del sitio SharePoint. Después de perder el foco, el sistema convertirá automáticamente a la identificación correcta del controlador.", + "ks3selectRegionDes": "Ingresa el código de región del bucket de almacenamiento, ej. <0>BEIJING .", + "ks3EndpointPathStyle": "Selecciona el formato de la dirección del Endpoint KS3.", + "ossRegionDes": "Ingresa el código de región donde se encuentra el bucket, ej. <0>cn-hangzhou. Puedes encontrar la región correspondiente en la tabla <1>Regiones y endpoints de OSS y completar el <2>ID de región correspondiente." + }, + "node": { + "slave": "esclavo", + "master": "maestro", + "noCapabilities": "No hay capacidades habilitadas.", + "active": "Activo", + "suspended": "Suspendido", + "deleteNodeConfirmation": "¿Estás seguro de que quieres eliminar el nodo {{name}}?", + "editNode": "Editar nodo {{node}}", + "thisIsMasterNodes": "Estás editando un nodo maestro, que está sirviendo el sitio actual.", + "enableNode": "Habilitar nodo", + "enableNodeDes": "Después de habilitado, el nodo aceptará y procesará las características que han sido habilitadas.", + "name": "Nombre", + "nameNode": "Nombre del nodo, también usado para mostrar a los usuarios.", + "type": "Tipo", + "server": "Endpoint del nodo", + "serverDes": "Endpoint usado para comunicación del nodo. Si quieres almacenar archivos en este nodo, esta dirección también será expuesta al lado del usuario para subidas de archivos.", + "loadBalancerRankDes": "Especifica un peso de balanceador de carga para este nodo, el valor es un entero, mientras más alto el valor, mayor la probabilidad de ser seleccionado.", + "loadBalancerRank": "Peso del balanceador de carga", + "slaveSecret": "Secreto del esclavo", + "slaveSecretDes": "Secreto usado para comunicación del nodo esclavo con el nodo maestro. Necesita ser consistente con <1>Secret en la sección <1>Slave del archivo de configuración del nodo esclavo.", + "testNode": "Probar comunicación del nodo", + "testNodeSuccess": "El nodo se comunica exitosamente.", + "createArchiveDes": "Aceptar solicitudes de tareas de crear archivo.", + "extractArchiveDes": "Aceptar solicitudes de tareas de extraer archivo.", + "remoteDownloadDes": "Aceptar solicitudes de tareas de descarga remota. Después de habilitado, también necesitas configurar la información relacionada de descarga remota abajo.", + "downloader": "Descargador", + "aria2Des": "Inicia Aria2 como el mismo usuario/nivel de acceso ejecutando Cloudreve en el servidor del nodo objetivo, habilita el servicio RPC en el archivo de configuración de Aria2, para más información y guías, consulta la sección \"Descarga remota\" de la documentación.", + "qbittorrentDes": "Inicia qBittorrent como el mismo usuario ejecutando Cloudreve en el servidor del nodo objetivo, habilita el servicio Web UI en la configuración de qBittorrent, para más información y guías, consulta la sección \"Descarga remota\" de la documentación.", + "rpcServer": "Servidor RPC", + "rpcServerHelpDes": "Dirección del servidor RPC contiene el número de puerto completo, ej. <0>http://127.0.0.1:6800/.", + "rpcToken": "Token RPC", + "rpcTokenDes": "Consistente con <0>rpc-secret en el archivo de configuración de Aria2; deja en blanco si no está configurado.", + "downloaderOptionDes": "Configuración adicional del descargador al crear una tarea de descarga, escrito en formato JSON clave-valor, ve la <0>documentación oficial del descargador para parámetros disponibles.", + "refreshInterval": "Intervalo de actualización de estado (segundos)", + "refreshIntervalDes": "El intervalo en el que Cloudreve solicita una actualización del estado de la tarea del descargador. El intervalo de actualización real también depende de la configuración de la cola \"Descarga remota\" y la ocupación del descargador.", + "waitForSeeding": "Esperar por siembra", + "waitForSeedingDes": "Después de habilitado, cuando la tarea de descarga remota esté completada, el nodo mantendrá la tarea en el estado de siembra hasta que se cumpla la condición de finalización de siembra en la configuración del descargador. Esta característica solo toma efecto después de que la tarea de descarga remota esté completada, y no afectará el uso de los archivos descargados por el usuario.", + "webUIEndpoint": "Endpoint de Web UI", + "webUIEndpointDes": "El endpoint de la Web UI de qBittorrent, ej. <0>http://127.0.0.1:8080/.", + "tempPath": "Directorio de descarga temporal", + "tempPathDes": "El directorio en el nodo que Aria2 usa como directorio de descarga temporal. El proceso Cloudreve en el nodo necesita permisos de lectura, escritura y ejecución en este directorio, y el descargador también necesita poder acceder a este directorio. Deja en blanco para usar la ruta de archivo temporal por defecto.", + "webUIUsername": "Nombre de usuario Web UI", + "webUIPassword": "Contraseña Web UI", + "webUICredDes": "Deja en blanco si la autenticación no está habilitada.", + "downloaderTestPass": "Conectado exitosamente al descargador, versión: {{version}}", + "testDownloader": "Probar comunicación del descargador", + "addNewNode": "Nuevo nodo", + "nameTheNode": "Nombra el nodo:", + "copyBinary": "", + "runCrSlave": "Ejecuta Cloudreve en el nodo con la misma versión que el maestro, e inicia con el siguiente archivo de configuración:", + "keepIfUpload": "Si necesitas usar este nodo para políticas de almacenamiento en el futuro, por favor mantén la siguiente configuración CORS.", + "storeFiles": "Almacenar archivos", + "storeFilesDes": "Usa este nodo para almacenar archivos de usuario.", + "storeFilesHint": "Si quieres usar este nodo para políticas de almacenamiento, por favor crea una política de almacenamiento esclavo y selecciona este nodo.", + "runCrWithConfig": "Guarda el archivo anterior como archivo <0>config.ini, e inicia Cloudreve con este archivo: <0>./cloudreve -c config.ini. Una instancia esclava de Cloudreve puede servir múltiples nodos maestros de Cloudreve; simplemente agrega este nodo esclavo a todos los nodos maestros y mantén el secreto igual.", + "inputServer": "Ingresa el endpoint del nodo:", + "testButton": "Puedes hacer clic en el botón de abajo para probar si la comunicación es exitosa.", + "hostHeaderHint": "Si hay un error de firma, por favor verifica si el proxy inverso frente al nodo está pasando el header <0>Host.", + "features": "Características habilitadas", + "remoteDownload": "Descarga remota", + "refresh": "Actualizar" + }, + "group": { + "countUser": "Contar", + "anonymous": "Grupo de usuario anónimo", + "sysGroup": "Grupo de usuario del sistema", + "adminGroup": "Grupo de usuario administrador", + "#": "#", + "name": "Nombre", + "type": "Política de almacenamiento", + "count": "Usuarios hijos", + "size": "Cuota de almacenamiento", + "nameOfGroup": "Nombre", + "nameOfGroupDes": "Nombre del grupo, usado para mostrar a los usuarios.", + "availablePolicies": "Políticas de almacenamiento disponibles", + "availablePoliciesDes": "Selecciona las políticas de almacenamiento que este grupo puede usar. Modificar esta configuración no afectará los archivos subidos por los usuarios.", + "initialStorageQuota": "Cuota de almacenamiento inicial", + "initialStorageQuotaDes": "Almacenamiento máximo que puede usar un solo usuario bajo este grupo.", + "isAdmin": "Grupo administrador", + "isAdminDes": "Cuando está habilitado, los usuarios bajo este grupo tendrán permisos de administrador.", + "share": "Compartir", + "allowCreateShareLink": "Crear enlace de compartir", + "allowCreateShareLinkDes": "Si está deshabilitado, los usuarios no pueden crear enlaces de compartir.", + "shareFree": "Enlace de compartir gratuito", + "shareFreeDes": "Cuando está habilitado, los usuarios pueden acceder a todos los enlaces de compartir pagos sin comprar.", + "fileManagement": "Gestión de archivos", + "allowWabDAV": "WebDAV", + "allowWabDAVDes": "Si está deshabilitado, los usuarios no pueden conectarse al almacenamiento vía el protocolo WebDAV", + "allowWabDAVProxy": "Proxy WebDAV", + "allowWabDAVProxyDes": "Si está habilitado, los usuarios pueden configurar el WebDAV para ser proxy por Cloudreve cuando descargan archivos.", + "compressTask": "Tareas de Compresión/Descompresión", + "compressTaskDes": "Si está habilitado, los usuarios pueden hacer compresión/descompresión de archivos en línea.", + "compressSize": "Tamaño máximo de archivo para comprimir", + "compressSizeDes": "El tamaño total máximo de archivo de trabajos de compresión que puede crear el usuario, completa 0 para indicar sin límite. Este límite no se verifica al crear tareas de compresión, y si el tamaño total de los archivos originales excede este límite al ejecutar, la tarea fallará.", + "decompressSize": "Tamaño máximo de archivo para descomprimir", + "decompressSizeDes": "El tamaño total máximo de archivo de trabajos de descompresión que puede crear el usuario, completa 0 para indicar sin límite.", + "allowRemoteDownload": "Descarga remota", + "allowRemoteDownloadDes": "Si permitir a los usuarios crear tareas de descarga remota. Si necesitas usar descarga remota, también necesitas tener nodos con descarga remota habilitada en la <0>Lista de Nodos.", + "aria2Options": "Opciones de trabajo del descargador", + "aria2OptionsDes": "Parámetros extra para descargadores (qBittorrent o Aria2), escrito en formato JSON clave-valor, ve la documentación oficial del descargador para parámetros disponibles.", + "aria2BatchSize": "Tamaño máximo de lote de tareas de descarga remota", + "aria2BatchSizeDes": "Número máximo para enviar tareas de descarga remota en lote, completa 0 para indicar sin límite.", + "migratePolicy": "Reubicar política de almacenamiento", + "migratePolicyDes": "Si el usuario crea una tarea de reubicación de política de almacenamiento.", + "advanceDelete": "Opciones avanzadas de eliminación de archivos", + "advanceDeleteDes": "Una vez habilitado, los usuarios pueden elegir si mantener archivos físicos al eliminar archivos. Por favor solo habilita esta opción para grupos de usuarios confiables.", + "allowSelectNode": "Permitir seleccionar nodo", + "allowSelectNodeDes": "Cuando está habilitado, el usuario puede seleccionar el nodo preferido antes de crear tareas. Cuando está deshabilitado, el nodo será balanceado por el sistema dentro de los nodos permitidos para el grupo.", + "allowedNodes": "Nodos permitidos", + "allowedNodesDes": "Especifica los nodos que este grupo puede usar para crear tareas. Lista vacía significa que todos los nodos están disponibles. Los usuarios solo pueden seleccionar o ser asignados nodos dentro de esta lista por el balanceador de carga. Actualmente, las tareas cubiertas son: descarga remota, compresión/descompresión de archivos. Otras tareas serán asignadas al nodo maestro.", + "allNodes": "Todos los nodos", + "esclateAnonymity": "Escalar anonimato", + "esclateAnonymityDes": "Cuando está habilitado, los usuarios pueden asignar permisos más altos para usuarios anónimos (escribir/eliminar/crear). Cuando está deshabilitado, los usuarios solo pueden asignar permiso de solo lectura para usuarios anónimos. Cambiar esta configuración no afectará enlaces de compartir existentes o archivos.", + "allowDownloadShare": "Acceder enlaces compartidos", + "allowDownloadShareDes": "Cuando está deshabilitado, los usuarios no pueden ver enlaces compartidos de otros. Esta configuración tiene precedencia sobre la configuración de permisos del enlace de compartir.", + "deletedNode": "Nodo eliminado #{{id}}", + "maxWalkedFiles": "Archivos máximos recorridos", + "maxWalkedFilesDes": "En algunas operaciones que requieren recorrido profundo de archivos, el número máximo de archivos permitidos para ser recorridos.", + "trashBinDuration": "Duración de papelera de reciclaje (segundos)", + "trashBinDurationDes": "El tiempo de retención de archivos en la papelera de reciclaje, los archivos serán eliminados permanentemente después del tiempo de expiración. Cambiar esta configuración no afectará archivos ya en la papelera de reciclaje.", + "serverSideBatchDownload": "Descarga en lote del lado del servidor", + "serverSideBatchDownloadDes": "Si permitir a los usuarios seleccionar múltiples archivos para usar la descarga en lote de retransmisión del lado del servidor, después de deshabilitado, los usuarios aún pueden usar la característica de descarga en lote basada en navegador puro.", + "uploadDownload": "Subida y descarga", + "getDirectLink": "Obtener enlace directo", + "getDirectLinkDes": "Si permitir a los usuarios obtener el enlace directo del archivo.", + "bathSourceLinkLimit": "Tamaño máximo de enlaces directos en lote", + "bathSourceLinkLimitDes": "El número máximo de archivos permitidos para que los usuarios obtengan enlaces directos en un solo lote, completar 0 significa que no se permite la generación en lote de enlaces directos.", + "redirectedSource": "Usar enlace directo redirigido", + "redirectedSourceDes": "Recomendado habilitar. Cuando está habilitado, el enlace directo al archivo obtenido por el usuario será redirigido por Cloudreve con un enlace más corto. Cuando está deshabilitado, el enlace directo al archivo obtenido por el usuario se convierte en la URL original al archivo, y está vinculado a la versión del archivo. Algunas políticas producen enlaces directos no redirigidos que no permanecen persistentes; ve los documentos de Cloudreve para detalles.", + "reuseDirectLink": "Reusar enlace directo existente", + "reuseDirectLinkDes": "Cuando está habilitado, múltiples solicitudes para el enlace directo del mismo archivo reutilizarán el enlace de redirección existente.", + "downloadSpeedLimit": "Velocidad máxima de descarga", + "downloadSpeedLimitDes": "Completa 0 para indicar sin límite. Cuando la restricción está activada, la velocidad máxima de descarga será limitada cuando los usuarios descarguen todos los archivos bajo la política de almacenamiento que soporta el límite de velocidad.", + "anonymousHint": "Este grupo de usuario corresponde al visitante anónimo que no ha iniciado sesión.", + "create": "Crear", + "copyFromExisting": "¿Copiar de grupo existente?", + "notCopy": "No copiar", + "confirmDelete": "¿Estás seguro de que quieres eliminar el grupo {{group}}?", + "new": "Nuevo grupo", + "editGroup": "Editar {{group}}" + }, + "user": { + "createdAt": "Creado en", + "originUserGroup": "Grupo de usuario original", + "originUserGroupDes": "Grupo de usuario al que pertenece el usuario antes de comprar el grupo actual, el grupo actual volverá a este grupo después de la expiración.", + "noOriginUserGroup": "No", + "groupExpired": "Fecha de expiración del grupo", + "groupExpiredDes": "Fecha de expiración del grupo en formato ISO8601, dejar en blanco significa que el grupo es permanente.", + "openUserFiles": "Abrir archivos de usuario", + "id": "ID", + "idValue": "{{id}} ({{hash_id}})", + "avatar": "Foto de perfil", + "removeAvatar": "Remover foto de perfil", + "userDialogTitle": "Detalles del usuario", + "2FAEnabled": "2FA habilitado", + "qqEnabled": "QQ vinculado", + "logtoEnabled": "Logto vinculado", + "oidcEnabled": "OIDC vinculado", + "deleted": "Usuario eliminado.", + "new": "Nuevo usuario", + "filter": "Filtro", + "emptyNoFilter": "Dejar en blanco significa sin filtro.", + "selectedObjects": "{{num}} objetos seleccionados.", + "nick": "Nombre a mostrar", + "email": "Email", + "group": "Grupo", + "status": "Estado", + "usedStorage": "Almacenamiento usado", + "status_active": "Activo", + "status_inactive": "Inactivo", + "status_manual_banned": "Bloqueado manualmente", + "status_sys_banned": "Bloqueado por el sistema", + "toggleBan": "Bloquear/Desbloquear", + "filterCondition": "Condiciones de filtro", + "all": "Todos", + "userStatus": "Estado del usuario", + "apply": "Aplicar", + "editUser": "Editar {{nick}}", + "password": "Contraseña", + "passwordDes": "Dejar en blanco significa sin modificación.", + "groupDes": "Grupo al que pertenece el usuario.", + "2FA": "2FA", + "notEnabled": "No habilitado", + "reset2Fa": "Deshabilitar", + "reset": "Restablecer", + "confirmDelete": "¿Estás seguro de que quieres eliminar el usuario {{user}}?", + "deleteXUsers": "Eliminar {{num}} usuarios", + "confirmBatchDelete": "¿Estás seguro de que quieres eliminar {{num}} usuarios?", + "calibrateStorage": "Calibrar almacenamiento", + "calibrateStorageSuccess": "Almacenamiento calibrado exitosamente." + }, + "file": { + "deleteXFiles": "Eliminar {{num}} archivos", + "confirmBatchDelete": "¿Estás seguro de que quieres eliminar {{num}} archivos?", + "confirmDelete": "¿Estás seguro de que quieres eliminar el archivo {{file}}?", + "haveShares": "Compartido", + "haveDirectLinks": "Tiene enlaces directos redirigidos", + "directLinkId": "Identificador del enlace", + "directLinks": "Enlaces directos redirigidos", + "noRecords": "Sin registros", + "speed": "Límite de velocidad", + "downloads": "Descargas", + "shareLink": "Enlaces de compartir", + "shareLinkNum": "{{num}} (<0>Ver)", + "blobType": "Tipo", + "noEntities": "Sin Blobs", + "blobs": "Blobs", + "creator": "Creador", + "source": "Fuente", + "key": "Clave", + "value": "Valor", + "isPublic": "Público", + "noMetadata": "Sin metadatos", + "metadata": "Metadatos", + "id": "ID", + "primaryStoragePolicy": "Política de almacenamiento primaria", + "fileDialogTitle": "Detalles del archivo", + "name": "Nombre del archivo", + "deleteAsync": "La tarea de eliminación será ejecutada en segundo plano.", + "forceDelete": "Forzar eliminación", + "size": "Tamaño", + "sizeUsed": "Almacenamiento usado", + "uploader": "Propietario", + "createdAt": "Creado en", + "uploading": "Subiendo", + "unknownUploader": "Desconocido", + "uploaderID": "ID del propietario", + "searchFileName": "Buscar nombre de archivo", + "storagePolicy": "Política de almacenamiento", + "selectTargetUser": "Seleccionar usuario objetivo", + "importTaskCreated": "Tarea de importación creada, puedes ver su estado en la lista de tareas en segundo plano.", + "manuallyPathOnly": "La política de almacenamiento seleccionada solo soporta ingresar ruta manualmente.", + "selectFolder": "Seleccionar carpeta", + "import": "Importar", + "importExternalFolder": "Importar carpetas externas", + "importExternalFolderDes": "Puedes importar archivos existentes y estructuras de directorio de tu política de almacenamiento en Cloudreve. La operación de importación no ocupará almacenamiento físico adicional, pero aún deducirá la cuota de almacenamiento usado del usuario normalmente.", + "storagePolicyDes": "Selecciona la política de almacenamiento donde los archivos a importar están actualmente almacenados.", + "targetUser": "Usuario objetivo", + "targetUserDes": "Selecciona a qué sistema de archivos de usuario quieres importar los archivos.", + "srcFolderPath": "Ruta de carpeta fuente", + "select": "Seleccionar", + "selectSrcDes": "La ruta del directorio a ser importado en el lado del almacenamiento.", + "dstFolderPath": "Ruta de carpeta destino", + "dstFolderPathDes": "Ruta en el sistema de archivos del usuario para contener todos los archivos importados.", + "recursivelyImport": "Importar recursivamente", + "recursivelyImportDes": "Si importar todos los subdirectorios bajo el directorio recursivamente.", + "createImportTask": "Crear tarea de importación", + "unlink": "Desvincular (Mantener archivo físico)", + "searchUser": "Buscar usuario por nombre o email...", + "extractMediaMeta": "Extraer información de medios", + "extractMediaMetaDes": "Si extraer información de medios para cada archivo durante la importación.", + "importWarning": "Advertencia", + "importWarnings": [ + "Después de la importación, el archivo físico será tomado por Cloudreve, por favor no lo modifiques externamente después.", + "No importes el mismo archivo múltiples veces.", + "Si el archivo del usuario entra en conflicto, este archivo será omitido." + ], + "otherConditions": "Otras condiciones", + "shareLinkExisted": "Tiene enlace de compartir", + "directLinkExisted": "Tiene enlace directo", + "isUploading": "Está subiendo" + }, + "entity": { + "refenenceCount": "Conteo de referencia", + "waitForRecycle": "Esperando reciclaje", + "entityDialogTitle": "Detalles del Blob", + "uploadSessionID": "ID de sesión de subida", + "referredFiles": "Archivos referidos", + "confirmBatchDelete": "¿Estás seguro de que quieres eliminar {{num}} Blobs?", + "deleteXEntities": "Eliminar {{num}} Blobs", + "forceDelete": "Forzar eliminación", + "forceDeleteDes": "Si eliminar el registro del Blob independientemente de si el archivo físico es eliminado." + }, + "event": { + "cleanup": "Limpieza", + "cleanupAuditLog": "Limpieza de eventos", + "cleanupAuditLogDescription": "Eliminar todos los eventos que cumplan las siguientes condiciones:", + "cleanupNotAfter": "Antes de esta fecha", + "cleanupEventTypes": "Tipos de eventos", + "cleanupEventTypesDes": "Selecciona los tipos de eventos a limpiar. Dejar en blanco para limpiar todos los tipos.", + "initiator": "Iniciador", + "event": "Evento", + "userID": "ID de usuario", + "ip": "IP", + "type": "Tipo", + "correlationId": "ID de correlación", + "fileID": "ID de archivo", + "emailSend": "Enviar email \"{{title}}\" a {{email}}", + "emailFailed": "La cola de email falló al iniciar", + "signinFailed": "Fallo en el inicio de sesión: {{reason}}", + "createDavAccount": "Crear cuenta WebDAV: {{account}}", + "updateDavAccount": "Actualizar cuenta WebDAV: {{account}}", + "deleteDavAccount": "Eliminar cuenta WebDAV: {{account}}", + "pointsChange": "Cambio de puntos: {{points}}", + "storageAdded": "Comprado {{size}} almacenamiento", + "nickChange": "Nombre a mostrar cambiado de {{old}} a {{new}}", + "eventDialogTitle": "Detalles del evento", + "userAgent": "Agente de usuario", + "linkedUser": "Usuario vinculado", + "datetime": "Tiempo", + "linkedFile": "Archivo vinculado", + "linkedEntity": "Blob vinculado", + "linkedShare": "Compartir vinculado", + "rawContent": "Contenido sin procesar", + "confirmDelete": "¿Estás seguro de que quieres eliminar este evento?", + "deleteXEvents": "Eliminar {{num}} eventos", + "confirmBatchDelete": "¿Estás seguro de que quieres eliminar {{num}} eventos?" + }, + "share": { + "confirmBatchDelete": "¿Estás seguro de que quieres eliminar {{num}} compartidos?", + "confirmDelete": "¿Estás seguro de que quieres eliminar este compartido?", + "deleteXShares": "Eliminar {{num}} compartidos", + "shareDialogTitle": "Detalles del compartido", + "shareLink": "Enlace de compartir", + "deleted": "Archivo eliminado", + "srcFileName": "Archivo fuente", + "views": "Visualizaciones", + "downloads": "Descargas", + "price": "Precio", + "autoExpire": "Expiración automática", + "owner": "Propietario", + "createdAt": "Creado en", + "private": "Ocultar de la página de perfil", + "yes": "Sí", + "no": "No", + "afterNDownloads": "Después de {{num}} descarga(s).", + "none": "Ninguno", + "srcType": "Tipo de objeto fuente", + "folder": "Carpeta", + "file": "Archivo" + }, + "task": { + "cleanupTasks": "Limpiar tareas", + "cleanupTasksDescription": "Limpiar todas las tareas que cumplan las siguientes condiciones:", + "cleanupNotAfter": "Antes de esta fecha", + "cleanupTaskTypes": "Tipos de tareas", + "cleanupTaskTypesDes": "Selecciona los tipos de tareas a limpiar. Dejar en blanco para limpiar todos los tipos.", + "cleanupTaskStatuses": "Estados de tareas", + "cleanupTaskStatusesDes": "Selecciona los estados de tareas a limpiar. Dejar en blanco para limpiar todas las tareas de estado completado.", + "confirmDelete": "¿Estás seguro de que quieres eliminar esta tarea?", + "confirmBatchDelete": "¿Estás seguro de que quieres eliminar {{num}} tareas?", + "deleteXTasks": "Eliminar {{num}} tareas", + "blobID": "ID del Blob", + "retryIndex": "Ãndice de reintento", + "entityError": "Blobs que fallaron al reciclar", + "updatedAt": "Actualizado en", + "taskDialogTitle": "Detalles de la tarea", + "explicitEntityRecycle": "Reciclar explícitamente Blobs de archivos: {{blobs}}", + "entityRecycleRoutine": "Escanear y reciclar Blob de archivos", + "mediaMetadata": "Extraer meta de medios del Blob <0>#{{entityID}}", + "uploadSentinelCheck": "Verificar estado de la sesión de subida {{uploadSessionID}}", + "remoteDownload": "Descarga remota: ", + "owner": "Propietario", + "content": "Contenido", + "status": "Estado", + "create_archive": "Crear archivo", + "extract_archive": "Extraer archivo", + "relocate": "Reubicar", + "remote_download": "Descarga remota", + "media_meta": "Metadatos de medios", + "entity_recycle_routine": "Rutina de reciclaje de entidad", + "explicit_entity_recycle": "Reciclaje explícito de entidad", + "upload_sentinel_check": "Verificación de centinela de subida", + "import": "Importación externa", + "type": "Tipo", + "node": "Nodo distribuido", + "createdBy": "Creado por", + "ready": "Listo", + "downloading": "Descargando", + "paused": "Pausado", + "seeding": "Sembrando", + "error": "Error", + "finished": "Finalizado", + "canceled": "Cancelado/Detenido", + "unknown": "Desconocido", + "errorMsg": "Mensaje de error" + }, + "payment": { + "tradeNo": "No. de Transacción", + "productType": "Tipo de producto", + "providerID": "ID del proveedor", + "status": "Estado", + "deleteXPayments": "Eliminar {{num}} pagos" + }, + "customProps": { + "add": "Agregar", + "type": "Tipo", + "default": "Valor por defecto", + "actions": "Acciones", + "text": "Texto", + "number": "Número", + "boolean": "Casilla de verificación", + "select": "Selección única", + "multiSelect": "Selección múltiple", + "user": "Usuario", + "link": "Enlace", + "rating": "Calificación", + "addProp": "Agregar propiedad", + "editProp": "Editar propiedad", + "icon": "Icono", + "iconDes": "Nombre del icono de <0>Iconify, dejar en blanco para ocultar el icono.", + "id": "ID", + "idDes": "ID de la propiedad, asegúrate de que sea único entre todas las propiedades.", + "idPatternDes": "Solo se permiten letras, números, guiones bajos y guiones.", + "minLength": "Longitud mínima", + "maxLength": "Longitud máxima", + "emptyLimit": "Dejar en blanco para no limitar.", + "minValue": "Valor mínimo", + "maxValue": "Valor máximo", + "options": "Opciones", + "optionsDes": "Una opción por línea." + }, + "vas": { + "disableSubAddressEmail": "Deshabilitar email de sub-dirección", + "disableSubAddressEmailDes": "Después de habilitado, direcciones de email que contengan <0>+ no pueden ser usadas para registro.", + "confirmDelete": "¿Estás seguro de que quieres eliminar estas órdenes?", + "vas": "VAS", + "reports": "Reportes", + "orders": "Pagos", + "initialFiles": "Archivos iniciales", + "initialFilesDes": "Especifica los archivos que el usuario posee inicialmente después del registro. Ingresa un ID de archivo para buscar archivos existentes.", + "filterEmailProvider": "Filtrar proveedor de email", + "filterEmailProviderDisabled": "Deshabilitado", + "filterEmailProviderWhitelist": "Lista blanca", + "filterEmailProviderBlacklist": "Lista negra", + "filterEmailProviderDes": "Restringir el proveedor de email para registro, el inicio de sesión SSO de terceros no está restringido.", + "filterEmailProviderRule": "Reglas de filtro de dominio de email", + "filterEmailProviderRuleDes": "Separa múltiples campos con punto y coma coma.", + "qqConnect": "Conexión QQ", + "qqConnectHint": "Al crear la aplicación, por favor completa la URL de callback: {{url}}", + "enableQQConnect": "Habilitar Conexión QQ", + "enableQQConnectDes": "Si permitir vincular QQ, usar QQ para iniciar sesión en el sitio web.", + "loginWithoutBinding": "Iniciar sesión sin registro", + "loginWithoutBindingDes": "Después de habilitado, si un usuario inicia sesión desde terceros pero no tiene una cuenta vinculada, el sistema creará una cuenta para ellos. Los usuarios que inicien sesión de esta manera solo podrán iniciar sesión usando este tercero en el futuro.", + "appid": "APP ID", + "appidDes": "El APP ID obtenido de la página de gestión de aplicaciones.", + "appKey": "APP KEY", + "appKeyDes": "La APP KEY obtenida de la página de gestión de aplicaciones.", + "overuseReminder": "Recordatorio de sobreuso", + "overuseReminderDes": "Plantilla de email de recordatorio enviada a usuarios después de que su capacidad exceda el límite debido a VAS expirado.", + "vasSetting": "Configuraciones VAS", + "storagePack": "Paquetes de almacenamiento", + "purchasableGroups": "Membresías", + "giftCodes": "Códigos de regalo", + "enable": "Habilitar", + "appID": "App- ID", + "appIDDes": "APPID de la aplicación de pago.", + "rsaPrivate": "Clave privada RSA de la aplicación", + "rsaPrivateDes": "La clave privada RSA2 (SHA256) para la aplicación de pago, típicamente generada por ti. Para detalles, consulta <0>Generando Claves RSA.", + "alipayPublicKey": "Clave pública de Alipay", + "alipayPublicKeyDes": "Proporcionada por Alipay, disponible en Gestión de Aplicaciones - Información de Aplicación - Método de Firma API.", + "wechatPay": "WeChat Pay", + "applicationID": "ID de Aplicación", + "applicationIDDes": "Appid de número público o aplicación móvil solicitado por comerciantes.", + "merchantID": "Número de comerciante", + "merchantIDDes": "El número de comerciante generado y emitido por WeChat Pay.", + "apiV3Secret": "Secreto API v3", + "apiV3SecretDes": "El comerciante necesita establecer el secreto en [Plataforma de Comerciante] - [Seguridad API] antes de la solicitud de WeChat Pay. La longitud de la clave es 32 bytes.", + "mcCertificateSerial": "Número de serie del certificado de comerciante", + "mcCertificateSerialDes": "Navega a [Seguridad API] - [Certificado API] - [Ver Certificado] para ver el número de serie del certificado API del comerciante.", + "mcAPISecret": "Secreto API del Comerciante", + "mcAPISecretDes": "Contenido del archivo secreto apiclient_key.pem.", + "payjs": "PAYJS", + "payjsWarning": "Este servicio es proporcionado por <0>PAYJS, una plataforma de terceros, y cualquier disputa que surja de él no es responsabilidad de los desarrolladores de Cloudreve.", + "mcNumber": "Número de comerciante", + "mcNumberDes": "Disponible en la página de inicio del panel de administración de PAYJS.", + "communicationSecret": "Clave de comunicación", + "otherSettings": "Otras Configuraciones", + "banBufferPeriod": "Período de buffer de suspensión (segundos)", + "banBufferPeriodDes": "La duración máxima de tiempo que un usuario puede mantener el estado de exceso de capacidad, más allá del cual el usuario será suspendido por el sistema.", + "allowSellShares": "Permitir precios para compartidos", + "allowSellSharesDes": "Una vez habilitado, los usuarios pueden establecer un precio de crédito para compartir y se deducirán créditos para descargar.", + "creditPriceRatio": "Tasa de llegada de créditos (%)", + "creditPriceRatioDes": "La tasa de créditos que realmente llegan al compartidor por la compra de un compartido con un precio establecido para descarga.", + "creditPrice": "Precio de crédito (centavo)", + "creditPriceDes": "Precio al recargar créditos", + "add": "Agregar", + "name": "Nombre", + "price": "Precio", + "duration": "Duración", + "size": "Tamaño", + "actions": "Acciones", + "orCredits": " O {{num}} créditos", + "highlight": "Destacar", + "yes": "Sí", + "no": "No", + "productName": "Nombre del producto", + "qyt": "Cant.", + "code": "Código", + "status": "Estado", + "invalidProduct": "Producto inválido", + "used": "Usado", + "notUsed": "No usado", + "generatingResult": "Resultado", + "addStoragePack": "Agregar paquete de almacenamiento", + "editStoragePack": "Editar paquete de almacenamiento", + "productNameDes": "Nombre de visualización del producto", + "packSizeDes": "Tamaño del paquete de almacenamiento", + "durationDay": "Duración (día)", + "durationDayDes": "Duración válida de cada paquete de almacenamiento.", + "priceYuan": "Precio (Yuan)", + "packPriceDes": "Precio del paquete de almacenamiento.", + "priceCredits": "Precio (Créditos)", + "priceCreditsDes": "El precio al usar créditos para comprar, completar 0 significa que no puedes usar créditos para comprar.", + "editMembership": "Editar membresía", + "addMembership": "Agregar membresía", + "group": "Grupo", + "groupDes": "Grupos de usuario actualizados después de la compra.", + "durationGroupDes": "La validez del tiempo de compra de la unidad del grupo de usuario actualizada después de la compra.", + "groupPriceDes": "Precio de membresía", + "productDescription": "Descripción del producto (Una vez por línea)", + "productDescriptionDes": "Descripción del producto mostrada en la página de compra.", + "highlightDes": "Después de habilitado, será destacado en la página de selección de producto.", + "generateGiftCode": "Generar códigos de regalo", + "numberOfCodes": "Número de códigos", + "numberOfCodesDes": "Número de códigos de regalo a generar.", + "linkedProduct": "Producto vinculado", + "productQyt": "Cant. del producto", + "productQytDes": "Para productos de crédito, este es el número de puntos y otros productos son múltiplos de duraciones.", + "freeDownload": "Descargar archivos compartidos gratis", + "freeDownloadDes": "Después de habilitado, el usuario puede descargar compartidos pagos gratis.", + "credits": "Créditos", + "markSuccessful": "Marcado exitosamente.", + "markAsResolved": "Marcar como resuelto", + "reportedContent": "Contenido reportado", + "reason": "Razón", + "description": "Descripción", + "reportTime": "Reportado en", + "invalid": "[Inválido]", + "deleteShare": "Eliminar enlace de compartir", + "orderDeleted": "Orden eliminada.", + "orderName": "Nombre", + "product": "Producto", + "paymentId": "ID de pago", + "orderNumber": "No. de Transacción", + "amount": "Cantidad", + "paidBy": "Pagado con", + "orderOwner": "Creado por", + "unpaid": "No pagado", + "paid": "Pagado", + "shareLink": "Enlace compartido", + "mobileApp": "Aplicación móvil", + "showAppPromotion": "Mostrar página de promoción", + "showAppPromotionDes": "Después de habilitado, el usuario puede ver la página de guía para aplicación móvil en la página \"Conectar y Montar\".", + "customPaymentName": "Nombre del método de pago", + "customPaymentNameDes": "Nombre del método de pago usado para mostrar al usuario.", + "customPaymentSecretDes": "Clave secreta para firmar solicitudes de pago.", + "customPaymentEndpoint": "URL de API de Pago", + "customPaymentEndpointDes": "URL a ser solicitada al crear una orden de pago.", + "appFeedback": "URL de Retroalimentación", + "appForum": "URL del foro de usuarios", + "appLinkDes": "Será mostrado en el cliente móvil, dejar vacío para ocultar el elemento del menú. Esta configuración tendrá efecto solo si la licencia VOL es válida." + }, + "pro": { + "title": "Funciones exclusivas de la edición Pro", + "description": "La función que está intentando acceder solo está disponible en la edición Pro de Cloudreve, actualiza para desbloquear todas las funciones avanzadas.", + "proInclude": "La edición Pro incluye:", + "shareLinkCollabration": "Compartir enlace de colaboración", + "filePermission": "Gestión de permisos de archivos", + "multipleStoragePolicy": "Cambio de políticas de almacenamiento y políticas de almacenamiento de directorio", + "auditAndActivity": "Registro de actividades de archivos y sistema", + "vasService": "Servicios adicionales y sistema de puntos", + "sso": "SSO de inicio de sesión único", + "more": "......", + "later": "Más tarde", + "learnMore": "Más información sobre la edición Pro", + "promotionTitle": "Descuento especial de actualización de la edición de la comunidad", + "promotion": "Usa el código de descuento <0>{{code}} al comprar para obtener un <1>-{{discount}}% de descuento." + }, + "abuseReport": { + "deleteXAbuseReports": "Eliminar {{num}} reportes de abuso", + "folderPath": "Ruta de carpeta", + "reporter": "Reportador", + "shareLink": "Enlace compartido <0>#{{id}}", + "deletedShare": "Enlace compartido eliminado", + "deletedUser": "Usuario eliminado", + "confirmDelete": "¿Estás seguro de que quieres eliminar este reporte de abuso?", + "confirmBatchDelete": "¿Estás seguro de que quieres eliminar {{num}} reportes de abuso?", + "reporterID": "ID del usuario reportador", + "reportedUserID": "ID del usuario reportado", + "shareID": "ID", + "reason": "Razón" + } +} diff --git a/public/locales/es-ES/image_editor.json b/public/locales/es-ES/image_editor.json new file mode 100755 index 0000000..30cfa1e --- /dev/null +++ b/public/locales/es-ES/image_editor.json @@ -0,0 +1,113 @@ +{ + "name": "Nombre", + "save": "Guardar", + "saveAs": "Guardar como", + "back": "Atrás", + "loading": "Cargando...", + "resetOperations": "Restablecer/eliminar todas las operaciones", + "changesLoseWarningHint": "Si presionas el botón \"restablecer\" tus cambios se perderán. ¿Te gustaría continuar?", + "discardChangesWarningHint": "Si cierras la ventana modal, tu último cambio no se guardará.", + "cancel": "Cancelar", + "apply": "Aplicar", + "warning": "Advertencia", + "confirm": "Confirmar", + "discardChanges": "Descartar cambios", + "undoTitle": "Deshacer última operación", + "redoTitle": "Rehacer última operación", + "showImageTitle": "Mostrar imagen original", + "zoomInTitle": "Acercar", + "zoomOutTitle": "Alejar", + "toggleZoomMenuTitle": "Alternar menú de zoom", + "adjustTab": "Ajustar", + "finetuneTab": "Ajuste fino", + "filtersTab": "Filtros", + "watermarkTab": "Marca de agua", + "annotateTabLabel": "Anotar", + "resize": "Redimensionar", + "resizeTab": "Redimensionar", + "imageName": "Nombre de imagen", + "invalidImageError": "Imagen inválida proporcionada.", + "uploadImageError": "Error al subir la imagen.", + "areNotImages": "no son imágenes", + "isNotImage": "no es imagen", + "toBeUploaded": "por subir", + "cropTool": "Recortar", + "original": "Original", + "custom": "Personalizado", + "square": "Cuadrado", + "landscape": "Paisaje", + "portrait": "Retrato", + "ellipse": "Elipse", + "classicTv": "TV Clásica", + "cinemascope": "Cinemascope", + "arrowTool": "Flecha", + "blurTool": "Desenfoque", + "brightnessTool": "Brillo", + "contrastTool": "Contraste", + "ellipseTool": "Elipse", + "unFlipX": "Deshacer volteo X", + "flipX": "Voltear X", + "unFlipY": "Deshacer volteo Y", + "flipY": "Voltear Y", + "hsvTool": "HSV", + "hue": "Matiz", + "brightness": "Brillo", + "saturation": "Saturación", + "value": "Valor", + "imageTool": "Imagen", + "importing": "Importando...", + "addImage": "+ Agregar imagen", + "uploadImage": "Subir imagen", + "fromGallery": "Desde galería", + "lineTool": "Línea", + "penTool": "Pluma", + "polygonTool": "Polígono", + "sides": "Lados", + "rectangleTool": "Rectángulo", + "cornerRadius": "Radio de esquina", + "resizeWidthTitle": "Ancho en píxeles", + "resizeHeightTitle": "Alto en píxeles", + "toggleRatioLockTitle": "Alternar bloqueo de proporción", + "resetSize": "Restablecer al tamaño original de la imagen", + "rotateTool": "Rotar", + "textTool": "Texto", + "textSpacings": "Espaciado de texto", + "textAlignment": "Alineación de texto", + "fontFamily": "Familia de fuente", + "size": "Tamaño", + "letterSpacing": "Espaciado de letras", + "lineHeight": "Altura de línea", + "warmthTool": "Calidez", + "addWatermark": "+ Agregar marca de agua", + "addTextWatermark": "+ Agregar marca de agua de texto", + "addWatermarkTitle": "Elige el tipo de marca de agua", + "uploadWatermark": "Subir marca de agua", + "addWatermarkAsText": "Agregar como texto", + "padding": "Relleno", + "paddings": "Rellenos", + "shadow": "Sombra", + "horizontal": "Horizontal", + "vertical": "Vertical", + "blur": "Desenfoque", + "opacity": "Opacidad", + "transparency": "Transparencia", + "position": "Posición", + "stroke": "Trazo", + "saveAsModalTitle": "Guardar como", + "extension": "Extensión", + "format": "Formato", + "nameIsRequired": "El nombre es requerido.", + "quality": "Calidad", + "imageDimensionsHoverTitle": "Tamaño de imagen guardada (ancho x alto)", + "cropSizeLowerThanResizedWarning": "Nota, el área de recorte seleccionada es menor que el redimensionado aplicado lo que podría causar una disminución de calidad", + "actualSize": "Tamaño real (100%)", + "fitSize": "Ajustar tamaño", + "addImageTitle": "Selecciona imagen para agregar...", + "mutualizedFailedToLoadImg": "Error al cargar imagen.", + "tabsMenu": "Menú", + "download": "Descargar", + "width": "Ancho", + "height": "Alto", + "plus": "+", + "cropItemNoEffect": "No hay vista previa disponible para este elemento de recorte" +} diff --git a/public/locales/es-ES/markdown_editor.json b/public/locales/es-ES/markdown_editor.json new file mode 100755 index 0000000..32c05cb --- /dev/null +++ b/public/locales/es-ES/markdown_editor.json @@ -0,0 +1,114 @@ +{ + "frontmatterEditor": { + "title": "Editar metadatos del documento", + "key": "Clave", + "value": "Valor", + "addEntry": "Agregar entrada" + }, + "dialogControls": { + "save": "Guardar", + "cancel": "Cancelar" + }, + "uploadImage": { + "dialogTitle": "Subir imagen", + "uploadInstructions": "Sube una imagen desde tu dispositivo:", + "addViaUrlInstructions": "O agrega una imagen desde una URL / ruta relativa (relativa al archivo actual):", + "autoCompletePlaceholder": "Selecciona o pega una fuente de imagen", + "addViaUrlInstructionsNoUpload": "URL de imagen:", + "alt": "Texto alternativo:", + "title": "Título:" + }, + "imageEditor": { + "deleteImage": "Eliminar imagen", + "editImage": "Editar imagen" + }, + "createLink": { + "url": "URL", + "urlPlaceholder": "Selecciona o pega una URL", + "title": "Título", + "saveTooltip": "Establecer URL", + "cancelTooltip": "Cancelar cambio" + }, + "linkPreview": { + "open": "Abrir {{url}} en nueva ventana", + "edit": "Editar URL del enlace", + "copyToClipboard": "Copiar al portapapeles", + "copied": "¡Copiado!", + "remove": "Eliminar enlace" + }, + "table": { + "deleteTable": "Eliminar tabla", + "columnMenu": "Menú de columna", + "textAlignment": "Alineación de texto", + "alignLeft": "Alinear a la izquierda", + "alignCenter": "Centrar", + "alignRight": "Alinear a la derecha", + "insertColumnLeft": "Insertar una columna a la izquierda de esta", + "insertColumnRight": "Insertar una columna a la derecha de esta", + "deleteColumn": "Eliminar esta columna", + "rowMenu": "Menú de fila", + "insertRowAbove": "Insertar una fila arriba de esta", + "insertRowBelow": "Insertar una fila abajo de esta", + "deleteRow": "Eliminar esta fila" + }, + "toolbar": { + "blockTypes": { + "paragraph": "Párrafo", + "quote": "Cita", + "heading": "Encabezado {{level}}" + }, + "blockTypeSelect": { + "selectBlockTypeTooltip": "Seleccionar tipo de bloque", + "placeholder": "Tipo de bloque" + }, + "toggleGroup": "alternar grupo", + "removeBold": "Quitar negrita", + "bold": "Negrita", + "removeItalic": "Quitar cursiva", + "italic": "Cursiva", + "underline": "Quitar subrayado", + "removeUnderline": "Subrayado", + "removeInlineCode": "Quitar formato de código", + "inlineCode": "Formato de código en línea", + "link": "Crear enlace", + "richText": "Texto enriquecido", + "diffMode": "Modo diferencias", + "source": "Modo fuente", + "admonition": "Insertar Advertencia", + "codeBlock": "Insertar Bloque de Código", + "editFrontmatter": "Editar metadatos", + "insertFrontmatter": "Insertar metadatos", + "image": "Insertar imagen", + "insertSandpack": "Insertar Sandpack", + "table": "Insertar Tabla", + "thematicBreak": "Insertar separador temático", + "bulletedList": "Lista con viñetas", + "numberedList": "Lista numerada", + "checkList": "Lista de verificación", + "deleteSandpack": "Eliminar este bloque de código", + "undo": "Deshacer {{shortcut}}", + "redo": "Rehacer {{shortcut}}", + "superscript": "Superíndice", + "subscript": "Subíndice", + "strikethrough": "Tachado", + "removeSubscript": "Quitar subíndice", + "removeSuperscript": "Quitar superíndice", + "removeStrikethrough": "Quitar tachado" + }, + "admonitions": { + "note": "Nota", + "tip": "Consejo", + "danger": "Peligro", + "info": "Información", + "caution": "Precaución", + "changeType": "Seleccionar tipo de advertencia", + "placeholder": "Tipo de advertencia" + }, + "codeBlock": { + "language": "Lenguaje del bloque de código", + "selectLanguage": "Seleccionar lenguaje del bloque de código" + }, + "contentArea": { + "editableMarkdown": "markdown editable" + } +} diff --git a/public/locales/fr-FR/application.json b/public/locales/fr-FR/application.json new file mode 100755 index 0000000..ab63dad --- /dev/null +++ b/public/locales/fr-FR/application.json @@ -0,0 +1,1113 @@ +{ + "login": { + "lastStep": "Dernière étape", + "siginToYourAccount": "Connectez-vous à votre compte", + "createNewAccount": "Créer un nouveau compte", + "enterPassword": "Saisissez votre mot de passe", + "enterPasswordHint": "Veuillez saisir le mot de passe pour {{email}}", + "paswordlessHint": "Le compte {{email}} est un compte sans mot de passe, veuillez choisir l'une des méthodes d'authentification suivantes :", + "noAccountSignupNow": "Pas de compte ? <0>Inscrivez-vous maintenant", + "haveAccountSignInNow": "Vous avez déjà un compte ? <0>Connectez-vous maintenant", + "privacyPolicy": "Politique de confidentialité", + "termOfUse": "Conditions d'utilisation", + "signupHint": "Le compte {{email}} que vous avez saisi n'existe pas, voulez-vous vous inscrire maintenant ?", + "accountNotFoundHint": "Le compte \"{{email}}\" que vous avez saisi n'existe pas.", + "or": "Ou", + "selectAccountToUse": "Sélectionnez un compte à utiliser", + "useOtherAccount": "Utiliser un autre compte", + "email": "E-mail", + "password": "Mot de passe", + "captcha": "CAPTCHA", + "captchaError": "Impossible de charger le CAPTCHA : {{message}}", + "resetThumbnail": "Réinitialiser la miniature cassée", + "resetThumbnailRequested": "Réinitialisation de la miniature demandée.", + "noFileCanResetThumbnail": "Aucun fichier pouvant réinitialiser la miniature.", + "signIn": "Se connecter", + "signUp": "S'inscrire", + "signUpAccount": "S'inscrire", + "useFIDO2": "Utiliser une clé d'accès", + "usePassword": "Utiliser un mot de passe", + "forgetPassword": "Mot de passe oublié ?", + "2FA": "Vérification 2FA", + "input2FACode": "Veuillez saisir le code de vérification 2FA à six chiffres", + "passwordNotMatch": "Les mots de passe ne correspondent pas.", + "findMyPassword": "Retrouver mon mot de passe", + "passwordReset": "Le mot de passe a été réinitialisé.", + "newPassword": "Nouveau mot de passe", + "repeatNewPassword": "Répéter le nouveau mot de passe", + "repeatPassword": "Répéter le mot de passe", + "resetPassword": "Réinitialiser mon mot de passe", + "backToSingIn": "Retour à la connexion", + "sendMeAnEmail": "M'envoyer un e-mail", + "resetEmailSent": "Un e-mail a été envoyé, veuillez vérifier votre boîte de réception.", + "browserNotSupport": "Non pris en charge par le navigateur ou l'environnement actuel.", + "success": "Connexion réussie", + "signUpSuccess": "Inscription réussie", + "activateSuccess": "Inscription terminée", + "accountActivated": "Votre compte a été activé avec succès.", + "title": "Se connecter à {{title}}", + "sinUpTitle": "S'inscrire à {{title}}", + "activateTitle": "Activez votre compte", + "activateDescription": "Un e-mail d'activation a été envoyé à votre adresse e-mail, veuillez visiter le lien dans l'e-mail pour terminer votre inscription.", + "continue": "Suivant", + "back": "Retour", + "logout": "Se déconnecter", + "signingOut": "Déconnexion en cours...", + "loggedOut": "Vous êtes maintenant déconnecté.", + "clickToRefresh": "Cliquez pour actualiser", + "switchLanguage": "Changer de langue" + }, + "navbar": { + "notBefore": "Pas avant", + "notAfter": "Pas après", + "minimum": "Minimum", + "maximum": "Maximum", + "fileSize": "Taille du fichier", + "searchBase": "Rechercher dans", + "searchInBase": "Rechercher dans <0>", + "conditionDuplicate": "La condition existe déjà.", + "fileType": "Type de fichier", + "addCondition": "Ajouter des conditions", + "notNameOpOr": "Tous les mots-clés doivent être présents", + "caseFolding": "Ignorer la casse", + "keywords": "Mots-clés", + "fileNameKeywordsHelp": "Appuyez sur Entrée pour ajouter un nouveau mot-clé.", + "advancedSearch": "Recherche avancée", + "searchFilesTitle": "Rechercher des fichiers", + "searchIn": "Rechercher <0>{{keywords}}", + "recentlyViewed": "Récemment consultés", + "searchFiles": "Rechercher des fichiers...", + "showMore": "Plus", + "myFiles": "Mes fichiers", + "hisFiles": "Ses fichiers", + "trash": "Corbeille", + "sharedWithMe": "Partagé avec moi", + "myShare": "Mes partages", + "remoteDownload": "Téléchargement distant", + "connect": "Connecter et monter", + "taskQueue": "Tâches en arrière-plan", + "setting": "Paramètres", + "videos": "Vidéos", + "photos": "Photos", + "music": "Musique", + "documents": "Documents", + "addATag": "Ajouter un tag...", + "addTagDialog": { + "selectFolder": "Sélectionner un dossier", + "fileSelector": "Sélecteur de fichiers", + "folderLink": "Raccourci de dossier", + "tagName": "Nom du tag", + "matchPattern": "Modèle(s) de correspondance du nom de fichier", + "matchPatternDescription": "Vous pouvez utiliser <0>* comme caractère générique. Par exemple, <1>*.png signifie correspondre aux images au format png. Les règles multi-lignes fonctionneront dans une relation \"ou\" les unes avec les autres.", + "icon": "Icône :", + "color": "Couleur :", + "folderPath": "Chemin vers le dossier" + }, + "storage": "Stockage", + "storageDetail": "{{used}} sur {{total}} utilisés", + "notLoginIn": "Déconnecté", + "visitor": "Anonyme", + "objectsSelected": "{{num}} sélectionnés", + "searchPlaceholder": "Tapez <0>/ pour rechercher", + "backToHomepage": "Retour à l'accueil", + "darkModeSwitch": "Commutateur de thème sombre", + "toDarkMode": "Sombre", + "toLightMode": "Clair", + "myProfile": "Mon profil", + "dashboard": "Tableau de bord" + }, + "fileManager": { + "currentStoragePolicy": "Stratégie de stockage actuelle : {{policy}}", + "customProps": "Propriétés personnalisées", + "rating": "Évaluation", + "description": "Description", + "add": "Ajouter", + "clickToEdit": "Cliquez pour modifier...", + "clickToEditSelect": "Cliquez pour sélectionner...", + "enterUrl": "Saisir l'URL...", + "searchUser": "Rechercher un utilisateur...", + "typeToSearch": "Saisir le nom ou l'e-mail...", + "searchProperty": "Rechercher des fichiers avec la même propriété", + "quality": "Qualité", + "audioTrack": "Audio", + "auto": "Auto", + "default": "Par défaut", + "shareWithMeEmpty": "Aucun fichier partagé trouvé", + "shareWithMeEmptyDes": "Si vous devez voir les partages d'autres personnes ici, veuillez enregistrer le raccourci à n'importe quel endroit dans vos fichiers lorsque vous visitez un lien partagé.", + "selectAll": "Tout sélectionner", + "selectNone": "Ne rien sélectionner", + "invertSelection": "Inverser la sélection", + "imageSize": "Taille de l'image", + "focalLength": "Distance focale", + "columnExisted": "La colonne existe déjà.", + "metadataColumn": "Métadonnées ({{metadata}})", + "column": "Colonne", + "listColumnSetting": "Paramètres de colonne", + "addColumn": "Ajouter des colonnes", + "failedLoadPreview": "Échec du chargement de l'aperçu.", + "recursiveLimitReached": "Limite de profondeur de recherche atteinte.", + "recursiveLimitReachedDes": "Le système a arrêté de rechercher dans des dossiers plus profonds, veuillez essayer de réduire la portée de la recherche.", + "searchConditions": "{{num}} condition(s)", + "createDate": "Date de création", + "updatedDate": "Date de mise à jour", + "cameraMake": "Fabricant de l'appareil photo", + "cameraModel": "Modèle d'appareil photo", + "lensModel": "Modèle d'objectif", + "lensMake": "Fabricant d'objectif", + "metadataKey": "Clé", + "metadataValue": "Valeur", + "metadata": "Métadonnées", + "symbolicFile": "Lien symbolique", + "relocation": "Relocaliser la stratégie de stockage", + "downloadingFile": "Téléchargement de \"{{name}}\", veuillez ne pas fermer cette page...", + "mountOwner": "Seul le propriétaire du dossier actuel peut monter des stratégies.", + "uploading": "Téléchargement en cours", + "noActionsCanBeDone": "Aucune action ne peut être effectuée.", + "newFileName": "Nouveau fichier.{{ext}}", + "newDocumentType": "{{display_name}} (.{{ext}})", + "text": "Texte", + "diagram": "Diagramme", + "whiteboard": "Tableau blanc", + "selectApplications": "Sélectionner des applications...", + "newlyCreatedFolder": "Nouveau dossier", + "expandAllApp": "Développer toutes les applications", + "epubViewer": "Lecteur ePub", + "googledocs": "Visionneuse Google Docs", + "m365viewer": "Visionneuse Microsoft Office Online", + "pdfViewer": "Visionneuse PDF", + "archivePreview": "Aperçu de l'archive", + "extractSelected": "Extraire les fichiers sélectionnés", + "viewerFileSizeWarning": "La taille du fichier ouvert ({{file_size}}) dépasse la limite ({{max}}) de {{app}}, il pourrait ne pas fonctionner correctement.", + "testSubtitleStyle": "Tester le style de sous-titres AaBbCc", + "color": "Couleur", + "fontSize": "Taille de police", + "disableSubtitle": "Désactiver les sous-titres", + "noSubtitle": "Aucun fichier de sous-titres ASS/SRT/VTT trouvé dans le dossier actuel.", + "subtitleStyles": "Styles de sous-titres", + "subtitles": "Sous-titres", + "markdownEditor": "Éditeur Markdown", + "saveSuccess": "Enregistré avec succès à {{time}}", + "drawioLng": "fr", + "charset": "Jeu de caractères", + "textType": "Type de texte", + "fileSaved": "Fichier enregistré.", + "failedToLoadFile": "Échec du chargement du fichier : {{msg}}", + "monacoEditor": "Éditeur de code Monaco", + "preparingOpenFile": "Préparation de l'ouverture du fichier...", + "openWithDescription": "Sélectionnez une application pour ouvrir le fichier .{{ext}}.", + "openWith": "Ouvrir avec", + "readOnly": "Lecture seule", + "save": "Enregistrer", + "noMoreImages": "Aucune image trouvée dans la page actuelle.", + "imageViewer": "Visionneuse d'images", + "logFileDeleteShare": "Supprimé un lien de partage", + "logFileEditShare": "Modifié un lien de partage", + "deleteShareWarning": "Êtes-vous sûr de vouloir supprimer ce lien de partage ?", + "edit": "Modifier", + "editAndReactivate": "Modifier et réactiver", + "yes": "Oui", + "no": "Non", + "permanentValid": "Permanent", + "manageShares": "Gérer les liens de partage", + "manageDirectLinks": "Gérer les liens directs", + "deleteLinkConfirm": "Êtes-vous sûr de vouloir supprimer ce lien direct ?", + "directLinkNotFound": "Le lien direct que vous recherchez n'existe pas.", + "versionNotFound": "La version que vous recherchez n'existe pas.", + "setNow": "Définir maintenant", + "permissionNotSet": "Aucune autorisation définie", + "permissionNotSetDes": "Il suivra les paramètres d'autorisation du répertoire parent ou du lien de partage.", + "permissions": "Autorisations", + "logFileUpdateMetadata": "Métadonnées mises à jour", + "all": "Tout", + "updatesOnly": "Mises à jour uniquement", + "readsOnly": "Lectures uniquement", + "myActivitiesOnly": "Mes activités uniquement", + "logUpdateView": "Paramètres de vue mis à jour", + "logDeleteDirectLink": "Lien direct supprimé", + "logFileImported": "Importé depuis un fichier externe", + "logGetDirectLink": "Créé un <0>lien direct", + "logFileMount": "Monter la stratégie de stockage sur \"{{name}}\"", + "lookForThisVersion": "Rechercher cette version", + "logFileThumbGenerated": "Génération de miniature déclenchée", + "logFileLivePhotoUploaded": "Live Photo téléchargée", + "logFileCreate": "Créé cet objet", + "logFileRename": "Renommé cet objet de \"{{originalName}}\" à \"{{newName}}\"", + "logFileSetPermission": "Changé l'autorisation de cet objet", + "logFileEntityUpload": "Contenu du fichier mis à jour", + "logFileCopyFrom": "Objet créé en copiant de <0> vers <1>", + "logFileCopyTo": "A été copié de <0> vers <1>", + "logFileMoveTo": "Déplacé de <0> vers <1>", + "logFileMoveToTrash": "Déplacé vers la corbeille depuis <0>", + "logFileShare": "Partagé cet objet", + "logFileSetCurrentVersion": "Restaurer la version du fichier à <0>", + "logFileDeleteVersion": "Supprimé la version créée à <0>", + "logEntityDownloaded": "Téléchargé ou lu cet objet", + "logDirectLinkDownloaded": "Téléchargé via un <0>lien direct", + "logRelocate": "Stratégie de stockage relocalisée vers {{newPolicy}}", + "logCreateArchive": "Ajouté au fichier d'archive <0>", + "logExtractArchive": "Extrait vers <0>", + "deleteVersionWarning": "Êtes-vous sûr de vouloir supprimer cette version ? Cette opération ne peut pas être annulée.", + "setAsCurrent": "Définir comme version actuelle", + "current": "[Actuel]", + "createdBy": "Créé par", + "manageVersions": "Gérer les versions", + "livePhoto": "Live Photo", + "version": "Version", + "actions": "Actions", + "versionEntity": "Données et versions du fichier", + "data": "Données", + "owned": "Possédé", + "ownedSymbolic": "Possédé (Lien symbolique)", + "expires": "Expire", + "originalLocation": "Emplacement d'origine", + "myPermissions": "Mes autorisations", + "descendant": "Descendant", + "folderChildren": "{{files}} fichier(s), {{folders}} dossier(s)", + "moreThan": "Plus de {{text}}", + "calculate": "Calculer", + "unset": "Non défini", + "folder": "Dossier", + "file": "Fichier", + "symbolicLink": "Lien symbolique ({{srcType}})", + "type": "Type", + "storageUsed": "Stockage utilisé", + "location": "Emplacement", + "basicInfo": "Informations de base", + "format": "Format", + "duration": "Durée", + "artist": "Artiste", + "album": "Album", + "title": "Titre", + "resolution": "Résolution", + "takenAt": "Pris le", + "software": "Logiciel", + "copyright": "Copyright", + "exposureBias": "Correction d'exposition", + "flash": "Flash", + "copyToClipboard": "Copier dans le presse-papiers", + "searchSomething": "Rechercher \"{{text}}\"...", + "iso": "ISO", + "exposureValue": "{{num}} s", + "exposure": "Exposition", + "aperture": "Ouverture", + "address": "Adresse", + "street": "Rue", + "locality": "Localité", + "place": "Ville", + "district": "District", + "region": "Province", + "country": "Pays", + "mediaInfo": "Informations média", + "details": "Détails", + "activity": "Activité", + "goToSharedLink": "Aller au lien partagé", + "saveShortcut": "Enregistrer le lien de partage comme raccourci", + "customizeIcon": "Personnaliser l'icône", + "tags": "Tags", + "apply": "Appliquer", + "customizeColor": "Personnaliser la couleur", + "folderColor": "Couleur du dossier", + "restore": "Restaurer", + "unpin": "Détacher", + "youDontHaveReadPermissionToThisFile": "Vous n'avez pas l'autorisation d'accès.", + "anonymousAccessDenied": "Vous n'avez pas l'autorisation d'accès, veuillez essayer de vous connecter.", + "sharedWithOthers": "Partagé avec d'autres", + "new": "Nouveau", + "open": "Ouvrir", + "openParentFolder": "Aller au dossier parent", + "download": "Télécharger", + "batchDownload": "Télécharger par lot", + "share": "Partager", + "rename": "Renommer", + "organize": "Organiser", + "pin": "Épingler à la barre latérale", + "pinAlias": "Nom d'affichage", + "optional": "Optionnel", + "move": "Déplacer", + "delete": "Supprimer", + "moreActions": "Plus d'actions", + "refresh": "Actualiser", + "createArchive": "Créer un fichier d'archive", + "newFolder": "Nouveau dossier", + "newFile": "Nouveau fichier", + "showFullPath": "Afficher le chemin complet", + "listView": "Liste", + "gridView": "Grille", + "galleryView": "Galerie", + "paginationSize": "Pagination", + "paginationOption": "{{option}} / page", + "noPagination": "Pas de pagination", + "sortMethod": "Trier", + "sortMethods": { + "A-Z": "A-Z", + "Z-A": "Z-A", + "oldestUploaded": "Plus ancien téléchargé", + "newestUploaded": "Plus récent téléchargé", + "oldestModified": "Plus ancien modifié", + "newestModified": "Plus récent modifié", + "smallest": "Plus petit", + "largest": "Plus grand" + }, + "shareCreateBy": "Créé par {{nick}}", + "name": "Nom", + "size": "Taille", + "lastModified": "Dernière modification", + "currentFolder": "Dossier actuel", + "backToParentFolder": "Retour au parent", + "folders": "Dossiers", + "files": "Fichiers", + "listError": "Échec de la liste des fichiers", + "dropFileHere": "Glissez et déposez le fichier ici", + "orClickUploadButton": "Ou cliquez sur le bouton \"Nouveau\" en haut à gauche pour ajouter un fichier", + "nothingFound": "Rien n'a été trouvé", + "uploadFiles": "Télécharger des fichiers", + "uploadFolder": "Télécharger un dossier", + "newRemoteDownloads": "Nouveau téléchargement distant", + "enter": "Entrer", + "getSourceLink": "Obtenir un lien direct", + "createRemoteDownloadForTorrent": "Nouveau téléchargement distant", + "extractArchive": "Extraire l'archive", + "createShareLink": "Partager", + "viewDetails": "Voir les détails", + "copy": "Copier", + "bytes": " ({{bytes}} octets)", + "storagePolicy": "Stratégie de stockage", + "inheritedFromParent": "(Hérité du parent)", + "childFolders": "Dossiers enfants", + "childFiles": "Fichiers enfants", + "childCount": "{{num}}", + "parentFolder": "Dossier parent", + "rootFolder": "Dossier racine", + "modifiedAt": "Modifié le", + "createdAt": "Créé le", + "statisticAt": "Statistique le", + "musicPlayer": "Lecteur de musique", + "closeAndStop": "Fermer et arrêter", + "playInBackground": "Jouer en arrière-plan", + "copyTo": "Copier vers", + "copyToDst": "Copier vers <0>", + "moveTo": "Déplacer vers", + "moveToDst": "Déplacer vers <0>", + "errorReadFileContent": "Échec de la lecture du contenu du fichier : {{msg}}", + "wordWrap": "Retour à la ligne", + "pdfLoadingError": "Échec du chargement du PDF : {{msg}}", + "subtitleSwitchTo": "Sous-titres basculés vers : {{subtitle}}", + "noSubtitleAvailable": "Aucun fichier de sous-titres disponible dans le dossier vidéo (pris en charge : ASS/SRT/VTT)", + "subtitle": "Sous-titres", + "playlist": "Liste de lecture", + "openInExternalPlayer": "Ouvrir dans un lecteur externe", + "repeatMode": "Mode de répétition", + "listRepeat": "Répétition de liste", + "singleRepeat": "Répétition unique", + "shuffle": "Aléatoire", + "playbackSpeed": "Vitesse de lecture", + "searchResult": "Résultats de recherche", + "preparingBathDownload": "Préparation du téléchargement par lot...", + "preparingDownload": "Préparation du téléchargement...", + "browserDownload": "Téléchargement côté navigateur vers un dossier local", + "browserDownloadDescription": "Votre navigateur télécharge les fichiers un par un et conserve la structure des dossiers dans le répertoire local que vous avez spécifié.", + "browserBatchDownload": "Archivage côté navigateur", + "browserBatchDownloadDescription": "Téléchargé et empaqueté dans un fichier Zip par le navigateur en temps réel, il ne peut pas gérer des données de plus de 4 Go.", + "serverBatchDownload": "Archivage côté serveur", + "serverBatchDownloadDescription": "Archivé par le serveur dans un fichier Zip et envoyé au client pour téléchargement à la volée, le raccourci de lien de partage n'est pas pris en charge.", + "selectArchiveMethod": "Sélectionner la méthode d'archivage", + "batchDownloadStarted": "Le téléchargement par lot a commencé, veuillez ne pas fermer cet onglet...", + "batchDownloadError": "Échec de l'archivage : {{msg}}", + "userDenied": "Utilisateur refusé.", + "directoryDownloadReplace": "Remplacer", + "directoryDownloadReplaceDescription": "Le fichier local \"{{name}}\" sera remplacé par le fichier téléchargé.", + "directoryDownloadSkip": "Ignorer", + "directoryDownloadSkipDescription": "\"{{name}}\" sera ignoré.", + "selectDirectoryDuplicationMethod": "Fichier dupliqué", + "directoryDownloadReplaceAll": "Remplacer tout", + "directoryDownloadReplaceAllDescription": "Tous les fichiers avec le même nom seront remplacés par les fichiers téléchargés.", + "directoryDownloadSkipAll": "Ignorer tout", + "directoryDownloadSkipAllDescription": "Tous les fichiers avec le même nom seront ignorés.", + "directoryDownloadStarted": "Téléchargement commencé, veuillez ne pas fermer cet onglet.", + "directoryDownloadFinished": "Téléchargement terminé, aucun objet échoué.", + "directoryDownloadFinishedWithError": "Téléchargement terminé, {{failed}} objet échoué.", + "directoryDownloadPermissionError": "Permission refusée, veuillez autoriser la lecture et l'écriture des fichiers locaux.", + "back": "Retour", + "view": "Vue", + "layout": "Disposition", + "thumbnails": "Miniatures", + "on": "Activé", + "off": "Désactivé", + "viewSetting": "Paramètre de vue", + "saved": "Enregistré", + "notSet": "Non défini", + "deleteViewSetting": "Supprimer le paramètre de vue" + }, + "modals": { + "includePasswordInShareLink": "Inclure le mot de passe dans le lien de partage", + "includePasswordInShareLinkDes": "Si sélectionné, le mot de passe sera inclus dans le lien de partage, et aucun mot de passe n'est requis lors de l'accès au lien de partage.", + "showFileName": "Afficher le nom du fichier", + "forceDownload": "Forcer le téléchargement", + "archiveFile": "Fichier d'archive", + "cancelDownload": "Annuler le téléchargement", + "always": "Toujours", + "justOnce": "Une seule fois", + "quality": "Qualité", + "saveAsOtherFormat": "Enregistrer sous un autre format", + "conflictDes1": "Conflit de version de fichier, les raisons possibles sont :", + "conflictDes2": "<0>Le fichier a été mis à jour vers une nouvelle version depuis ailleurs après que vous l'ayez ouvert.<1>Si vous l'avez enregistré avec un nouveau nom ou un nouvel emplacement, le nom de fichier existe déjà.", + "saveAs": "Enregistrer sous", + "versionConflict": "Conflit de version", + "overwrite": "Écraser", + "editShareLink": "Modifier le lien de partage", + "clearPermissions": "Effacer les paramètres d'autorisation", + "shortcutCreated": "Raccourci créé.", + "createShortcut": "Créer un raccourci", + "createShortcutTo": "Créer un raccourci à <0>", + "read": "Lire", + "readDes": "Pour les fichiers, vous pouvez voir leur contenu, métadonnées, etc. ; pour les dossiers, vous pouvez voir la liste des fichiers enfants et leurs métadonnées.", + "createDes": "Valide uniquement pour les dossiers, vous pouvez créer ou télécharger de nouveaux fichiers en dessous, et déplacer ou copier des fichiers vers celui-ci.", + "update": "Mettre à jour", + "updateDes": "Vous pouvez modifier les métadonnées, renommer les objets et voir les journaux d'activité ; pour les fichiers, vous pouvez mettre à jour leur contenu.", + "delete": "Supprimer", + "deleteDes": "Vous pouvez supprimer des objets ou les déplacer vers d'autres endroits.", + "noAccess": "Pas d'accès", + "targetExisted": "La cible existe déjà.", + "explicitAccess": "Accès explicite", + "generalAccess": "Accès général", + "users": "Utilisateurs", + "groups": "Groupes", + "builtinCollections": "Collections intégrées", + "everyone": "Tous les autres", + "otherGroup": "Autres groupes", + "sameGroup": "Même groupe que moi", + "anonymous": "Visiteurs anonymes", + "noResults": "Aucun résultat", + "searchGroupUser": "Rechercher des e-mails ou des groupes...", + "resetToDefault": "Réinitialiser par défaut", + "duplicateTag": "Le tag \"{{tag}}\" existe déjà.", + "colorForTag": "Personnaliser la couleur pour les nouveaux tags", + "enterForNewTag": "Appuyez sur Entrée pour ajouter un nouveau tag.", + "manageTags": "Gérer les tags", + "onlyOwner": "Seul le propriétaire de ce fichier peut le déverrouiller de force.", + "forceUnlock": "Déverrouillage forcé", + "forceUnlockAll": "Déverrouiller tout de force", + "forceUnlockDes": "Le déverrouillage forcé peut corrompre l'état du fichier, nous recommandons d'attendre que le fichier soit libéré de manière proactive, êtes-vous sûr de vouloir continuer le déverrouillage ?", + "webdav": "WebDAV", + "soft-delete": "Déplacer vers la corbeille", + "updateMetadata": "Mettre à jour les métadonnées", + "upload": "Télécharger", + "moveCopy": "Déplacer ou copier", + "view": "Vue", + "cannotPerformAction": "Le déplacement ou la copie de fichiers ici n'est pas pris en charge.", + "cannotMoveCopyToChild": "Impossible de déplacer ou copier vers un dossier descendant.", + "copySuccess": "{{num}} fichier(s) copié(s) avec succès.", + "moveSuccess": "{{num}} fichier(s) déplacé(s) avec succès.", + "setPermission": "Définir l'autorisation", + "unknownParent": "Parent inconnu", + "unknownParentDes": "Le dossier occupé est le dossier parent d'un dossier partagé, et il ne vous appartient pas.", + "lockConflictTitle": "Fichier occupé", + "lockConflictDescription": "Cette opération ne peut pas se terminer car le(s) fichier(s) suivant(s) est actuellement utilisé par d'autres, veuillez réessayer plus tard. Si vous êtes le propriétaire du fichier et que vous êtes sûr que le fichier n'est pas en cours d'utilisation, vous pouvez déverrouiller le fichier de force et réessayer.", + "usedBy": "Utilisé par", + "application": "Application", + "errorDetailsTitle": "Détails de l'erreur", + "processingMoving": "Déplacement de fichiers...", + "processingCopying": "Copie de fichiers...", + "processingRestoring": "Restauration de fichiers...", + "fileRestored": "{{num}} fichier(s) restauré(s) à son emplacement d'origine.", + "duplicatedObjectName": "Nom de fichier dupliqué.", + "newNameLengthError": "La longueur du nom de fichier doit être comprise entre 1 et 255.", + "newNameCharacterError": "Le nom ne doit contenir aucun de ces caractères : \\ / : * ? \" < > |", + "newNameDotError": "Le nom ne peut pas être \".\" ou \"..\"", + "taskCreated": "Tâche créée.", + "taskCreateFailed": "{{failed}} tâche(s) ont échoué à être créées : {{details}}.", + "linkCopied": "Lien copié.", + "getSourceLinkTitle": "Obtenir un lien direct", + "sourceLink": "Lien direct", + "folderName": "Nom du dossier", + "create": "Créer", + "fileName": "Nom du fichier", + "renameDescription": "Saisissez le nouveau nom pour <0>{{name}} :", + "newName": "Nouveau nom", + "moveToDescription": "Déplacer vers <0>{{name}}", + "saveToTitle": "Enregistrer vers", + "saveToTitleDescription": "Enregistrer vers <0>{{name}}", + "deleteTitle": "Supprimer les objets", + "deleteOneDescription": "Êtes-vous sûr de vouloir déplacer <0>{{name}} vers la corbeille ?", + "deleteMultipleDescription": "Êtes-vous sûr de vouloir déplacer ces {{num}} objets vers la corbeille ?", + "deleteOneDescriptionHard": "Êtes-vous sûr de vouloir supprimer définitivement <0>{{name}} ?", + "trashRetention": "Les fichiers dans la corbeille seront supprimés après <0>{{num}}.", + "deleteMultipleDescriptionHard": "Êtes-vous sûr de vouloir supprimer définitivement ces {{num}} objets ?", + "newRemoteDownloadTitle": "Nouvelle tâche de téléchargement distant", + "remoteDownloadURL": "URL cible de téléchargement", + "remoteDownloadURLDescription": "Collez l'URL de téléchargement, une URL par ligne", + "remoteDownloadDst": "Télécharger vers", + "processNode": "NÅ“ud cible", + "remoteDownloadNodeAuto": "Distribution automatique", + "createTask": "Créer une tâche", + "downloadToDst": "Télécharger vers <0>{{name}}", + "downloadTo": "Télécharger vers", + "decompressTo": "Extraire vers", + "decompressToDst": "Extraire vers <0>{{name}}", + "defaultEncoding": "Par défaut", + "chineseMajorEncoding": "Encodages chinois courants", + "selectEncoding": "Encodage du fichier ZIP", + "password": "Mot de passe", + "passwordDescription": "Si le fichier d'archive n'est pas chiffré, veuillez laisser ce champ vide.", + "noEncodingSelected": "Aucune méthode d'encodage sélectionnée", + "listingFiles": "Liste des fichiers...", + "listingFileError": "Échec de la liste des fichiers : {{message}}", + "generatingSourceLinks": "Génération de liens sources...", + "noFileCanGenerateSourceLink": "Il n'y a aucun fichier qui peut être utilisé pour générer un lien source", + "sourceBatchSizeExceeded": "Le groupe d'utilisateurs actuel peut générer des liens sources pour un maximum de {{limit}} fichiers en même temps.", + "zipFileName": "Nom du fichier d'archive", + "shareLinkShareContent": "J'ai partagé avec vous : {{name}} Lien : {{link}}", + "shareLinkPasswordInfo": "Mot de passe : {{password}}", + "createShareLink": "Créer un lien de partage", + "privateShare": "Protéger avec un mot de passe", + "privateShareDes": "Si sélectionné, un mot de passe est requis pour accéder au lien de partage.", + "useCustomPassword": "Mot de passe personnalisé pour le lien de partage", + "expireAfterDownload": "Expirer après téléchargement", + "sharePassword": "Mot de passe de partage", + "randomlyGenerate": "Aléatoire", + "expireAutomatically": "Expiration automatique", + "downloadLimitOptions": "{{num}} téléchargements", + "or": "Ou après", + "5minutes": "5 minutes", + "1hour": "1 heure", + "1day": "1 jour", + "7days": "7 jours", + "30days": "30 jours", + "custom": "Personnalisé", + "minutes": "minutes", + "downloads": "téléchargements", + "expirePrefix": "Expire après", + "expireSuffix": "", + "allowPreview": "Activer l'aperçu", + "allowPreviewDescription": "S'il faut permettre l'aperçu du contenu du fichier à partir du lien de partage", + "shareLink": "Lien de partage", + "sendLink": "Envoyer le lien", + "directoryDownloadReplaceNotifiction": "Écraser {{name}}", + "directoryDownloadSkipNotifiction": "Ignoré {{name}}", + "directoryDownloadTitle": "Journaux de téléchargement par lot", + "directoryDownloadStarted": "Commencer le téléchargement de \"{{name}}\"", + "directoryDownloadFinished": "Téléchargement terminé \"{{name}}\"", + "directoryDownloadError": "Erreur : {{msg}}", + "directoryDownloadErrorNotification": "Une erreur s'est produite lors du téléchargement de {{name}} : {{msg}}", + "directoryDownloadAutoscroll": "Défilement automatique", + "directoryDownloadCancelled": "Téléchargement annulé", + "advanceOptions": "Options avancées", + "skipSoftDelete": "Supprimer définitivement", + "skipSoftDeleteDes": "Ignorer le déplacement vers la corbeille, supprimer définitivement", + "unlinkOnly": "Conserver les fichiers physiques", + "unlinkOnlyDes": "Supprimer uniquement les enregistrements de fichiers, les fichiers physiques ne seront pas supprimés", + "shareView": "Paramètre de vue de partage", + "shareViewDes": "Si sélectionné, les autres utilisateurs peuvent voir votre paramètre de vue (disposition, tri, etc.) enregistré sur le serveur lors de l'accès à ce dossier partagé.", + "showReadme": "Afficher le fichier README", + "showReadmeDes": "Si sélectionné, le fichier <0>README.md (sensible à la casse) dans le répertoire sera automatiquement affiché pour les visiteurs.", + "viewSetting": "Paramètre de vue", + "saved": "Enregistré", + "notSet": "Non défini", + "deleteViewSetting": "Supprimer le paramètre de vue" + }, + "uploader": { + "fileCopyName": "Copie de ", + "overwriteTooltip": "Écraser le fichier existant en cas de conflit, ne fonctionne que pour les tâches nouvellement ajoutées.", + "rename": "Réessayer avec un nouveau nom", + "overwrite": "Écraser le fichier existant", + "pasteFilesHere": "Coller les fichiers ici", + "clipboardDefaultFileName": "Presse-papiers {{date}}.png", + "uploadFromClipboard": "Télécharger depuis le presse-papiers", + "uploadList": "Tâches de téléchargement", + "fileNotMatchError": "Le fichier sélectionné ne correspond pas au fichier d'origine.", + "unknownError": "Une erreur inconnue s'est produite : {{msg}}", + "taskListEmpty": "Aucune tâche de téléchargement.", + "hideTaskList": "Masquer la liste", + "uploadTasks": "Tâches de téléchargement", + "moreActions": "Plus d'actions", + "addNewFiles": "Ajouter de nouveaux fichiers", + "toggleTaskList": "Développer/Réduire la liste", + "pendingInQueue": "En attente dans la file...", + "preparing": "Préparation...", + "processing": "Traitement...", + "progressDescription": "{{uploaded}} téléchargé, {{total}} total - {{percentage}}%", + "progressDescriptionFull": "{{uploaded}} téléchargé, {{total}} total - {{percentage}}% ({{speed}})", + "progressDescriptionPlaceHolder": " - téléchargé", + "uploaded": "Téléchargé", + "rootFolder": "Dossier racine", + "unknownStatus": "Inconnu", + "resumed": "Repris", + "resumable": "Reprise possible", + "retry": "Réessayer", + "deleteTask": "Supprimer la tâche", + "cancelAndDelete": "Annuler et supprimer", + "selectAndResume": "Sélectionner le même fichier et reprendre le téléchargement", + "fileName": "Nom : ", + "fileSize": "Taille : ", + "sessionExpiredIn": "Expire <0>", + "chunkDescription": "({{total}} fragments, {{size}} chacun)", + "noChunks": "(Aucun fragment)", + "destination": "Destination : ", + "uploadSession": "Session de téléchargement : ", + "storagePolicy": "Stratégie de stockage : ", + "errorDetails": "Détails de l'erreur : ", + "uploadSessionCleaned": "Toutes les sessions de téléchargement effacées.", + "hideCompletedTooltip": "Masquer les tâches terminées, échouées et annulées.", + "hideCompleted": "Masquer les tâches terminées", + "addTimeAscTooltip": "Les tâches ajoutées en premier sont classées en premier.", + "addTimeAsc": "Plus ancien au plus récent", + "addTimeDescTooltip": "Les dernières ajoutées sont classées en premier.", + "addTimeDesc": "Plus récent au plus ancien", + "showInstantSpeedTooltip": "Les vitesses de téléchargement des tâches sont affichées comme vitesse instantanée.", + "showInstantSpeed": "Vitesse instantanée", + "showAvgSpeedTooltip": "Les vitesses de téléchargement des tâches sont affichées comme vitesses moyennes.", + "showAvgSpeed": "Vitesse moyenne", + "cleanAllSessionTooltip": "Effacer toutes les sessions de téléchargement en attente côté serveur.", + "cleanAllSession": "Effacer toutes les sessions de téléchargement", + "cleanCompletedTooltip": "Effacer les tâches terminées, échouées et annulées", + "cleanCompleted": "Effacer les tâches terminées", + "retryFailedTasks": "Réessayer toutes les tâches échouées", + "retryFailedTasksTooltip": "Réessayer toutes les tâches échouées dans la file actuelle", + "setConcurrentTooltip": "Définir le nombre maximum de tâches qui peuvent être téléchargées simultanément.", + "setConcurrent": "Définir la limite de tâches simultanées", + "sizeExceedLimitError": "La taille du fichier dépasse les limites de la stratégie de stockage. (Maximum : {{max}})", + "suffixNotAllowedError": "La stratégie de stockage ne prend pas en charge le téléchargement de fichiers avec cette extension.", + "regexpNotAllowedError": "La stratégie de stockage ne prend pas en charge le téléchargement de fichiers avec ce nom.", + "suffixAllowed": " (Pris en charge : {{supported}})", + "suffixDenied": " (Refusé : {{denied}})", + "createUploadSessionError": "Impossible de créer une session de téléchargement", + "deleteUploadSessionError": "Impossible de supprimer une session de téléchargement", + "requestError": "Échec de la requête : {{msg}} ({{url}}).", + "chunkUploadError": "Échec du téléchargement du fragment [{{index}}].", + "conflictError": "La tâche de téléchargement pour des fichiers avec le même nom est déjà en cours de traitement.", + "chunkUploadErrorWithMsg": "Échec du téléchargement du fragment : {{msg}}", + "chunkUploadErrorWithRetryAfter": "(Veuillez réessayer après {{retryAfter}}s)", + "emptyFileError": "Le téléchargement de fichiers vides vers OneDrive n'est pas pris en charge, veuillez créer des fichiers vides via le bouton Créer un fichier.", + "finishUploadError": "Impossible de terminer le téléchargement du fichier.", + "finishUploadErrorWithMsg": "Impossible de terminer le téléchargement du fichier : {{msg}}", + "ossFinishUploadError": "Impossible de terminer le téléchargement du fichier : {{msg}} ({{code}})", + "cosUploadFailed": "Échec du téléchargement : {{msg}} ({{code}})", + "upyunUploadFailed": "Échec du téléchargement : {{msg}}", + "parseResponseError": "Impossible d'analyser la réponse : {{msg}} ({{content}})", + "concurrentTaskNumber": "Limite de tâches simultanées", + "dropFileHere": "Déposer le fichier pour télécharger" + }, + "share": { + "free": "Gratuit", + "price": "Prix", + "points": "{{num}} Points", + "statistics": "Statistiques", + "expireAt": "Expire <0>", + "expireAfterDownloads": "Expire après {{downloads}} téléchargement(s)", + "somebodyShare": "Partagé par {{name}}", + "expiredLink": "Partage expiré", + "sharedBy": "<0>{{nick}} a partagé $t(share.files, {\"count\": {{num}} }) avec vous.", + "files": "1 fichier", + "files_other": "{{count}} fichiers", + "statisticsViews": "$t(share.views, {\"count\": {{views}} })", + "statisticsDownloads": "$t(share.downloads, {\"count\": {{downloads}} })", + "views": "{{count}} vue", + "views_other": "{{count}} vues", + "downloads": "{{count}} téléchargement", + "downloads_other": "{{count}} téléchargements", + "privateShareTitle": "Partage privé de {{nick}}", + "enterPassword": "Mot de passe de partage", + "continue": "Continuer", + "shareCanceled": "Le lien de partage est supprimé.", + "listLoadingError": "Échec du chargement.", + "sharedFiles": "Fichiers partagés", + "createdAtDesc": "Plus récent", + "createdAtAsc": "Plus ancien", + "noRecords": "Aucun fichier partagé.", + "sourceNotFound": "[Source inexistante]", + "expired": "Expiré", + "changeToPublic": "Rendre public", + "changeToPrivate": "Rendre privé", + "viewPassword": "Voir le mot de passe", + "disablePreview": "Désactiver l'aperçu", + "enablePreview": "Activer l'aperçu", + "cancelShare": "Annuler le partage", + "sharePassword": "Mot de passe de partage", + "readmeError": "Impossible de charger le README : {{msg}}", + "enterKeywords": "Veuillez saisir des mots-clés de recherche.", + "searchResult": "Résultats de recherche", + "sharedAt": "Partagé le <0>", + "pleaseLogin": "Veuillez vous connecter d'abord.", + "cannotShare": "Ce fichier ne peut pas être prévisualisé.", + "preview": "Aperçu", + "incorrectPassword": "Mot de passe incorrect.", + "shareNotExist": "Le lien de partage est invalide ou expiré.", + "copyLinkToClipboard": "Copier le lien dans le presse-papiers" + }, + "download": { + "noFilesFound": "Aucun fichier trouvé", + "filterByName": "Filtrer par nom", + "selectAll": "Tout sélectionner", + "reverseSelect": "Sélection inverse", + "saveChanges": "Enregistrer les modifications", + "cancelTaskConfirm": "Êtes-vous sûr d'annuler cette tâche de téléchargement ?", + "failedToLoad": "Échec du chargement.", + "active": "Actif", + "finished": "Terminé", + "activeEmpty": "Aucune tâche de téléchargement en cours.", + "finishedEmpty": "Aucune tâche de téléchargement terminée.", + "loadMore": "Charger plus", + "taskFileDeleted": "Fichier supprimé.", + "unknownTaskName": "[Inconnu]", + "taskCanceled": "Tâche de téléchargement annulée, le statut sera mis à jour plus tard", + "operationSubmitted": "Opération soumise, le statut sera mis à jour plus tard", + "deleteThisFile": "Supprimer ce fichier", + "openDstFolder": "Ouvrir le dossier cible", + "selectDownloadingFile": "Sélectionner les fichiers à télécharger", + "cancelTask": "Annuler", + "updatedAt": "Mis à jour le : ", + "uploaded": "Téléchargé", + "uploadSpeed": "Vitesse de téléchargement", + "InfoHash": "InfoHash", + "seederCount": "Seeders :", + "seeding": "Seeding : ", + "downloadNode": "NÅ“ud : ", + "isSeeding": "Oui", + "notSeeding": "Non", + "chunkSize": "Taille du fragment :", + "chunkNumbers": "Fragments", + "taskDeleted": "Tâche supprimée.", + "transferFailed": "Échec du transfert des fichiers.", + "downloadFailed": "Échec du téléchargement : {{msg}}", + "canceledStatus": "Annulé", + "finishedStatus": "Terminé", + "pending": "Terminé, transfert en attente dans la file", + "transferring": "Terminé, transfert en cours", + "deleteRecord": "Supprimer l'enregistrement", + "createdAt": "Créé le : ", + "unknownSize": "Taille de fichier inconnue" + }, + "setting": { + "notifyStoragePolicyChange": "Me notifier en cas de changement de stratégie de stockage", + "notifyStoragePolicyChangeDes": "Lorsqu'activé, une notification sera affichée lors de l'entrée dans un répertoire lié à une stratégie de stockage différente.", + "treeView": "Vue arborescente", + "autoExpandTreeView": "Développement automatique de la vue arborescente", + "autoExpandTreeViewDes": "Lorsqu'activé, l'arbre de fichiers dans la barre latérale suivra le répertoire actuel et se développera automatiquement.", + "syncView": "Paramètres de vue", + "syncViewDes": "Se souvenir des paramètres de vue de chaque répertoire et synchroniser vers le serveur.", + "syncViewOn": "Synchroniser vers le serveur", + "syncViewOff": "Ne pas synchroniser", + "reason": "Raison", + "change": "Changement", + "success": "Succès", + "loginWithPasskey": "Clé d'accès - {{name}}", + "loginWith": "Se connecter avec", + "result": "Résultat", + "device": "Appareil", + "ip": "IP", + "time": "Heure", + "recentSignIn": "Activités de connexion récentes", + "noAuthenticator": "Ajoutez une clé d'accès pour vous connecter en utilisant l'empreinte digitale, le visage ou la clé USB.", + "neverUsed": "Jamais utilisé", + "usedAt": "Dernière utilisation le <0>", + "passkeyName": "{browser} sur {os}", + "passwordlessHint": "Ce compte est sans mot de passe.", + "versionRetentionMax": "Nombre maximum de versions, 0 signifie aucune limite.", + "versionRetentionEnabledExt": "Extensions de fichier activées", + "versionRetentionEnabledExtDes": "Appuyez sur Entrée pour ajouter, laissez vide pour activer pour tous les fichiers", + "enableVersionRetention": "Activer la rétention de version", + "enableVersionRetentionDes": "Si activé, les versions historiques des fichiers qui répondent aux conditions seront conservées.", + "versionRetention": "Rétention de version", + "languageDes": "Sélectionnez la langue d'affichage et la langue d'e-mail préférée.", + "timezoneDes": "Définir le fuseau horaire d'affichage, par défaut c'est le fuseau horaire du système", + "unlinkConfirm": "Êtes-vous sûr de vouloir dissocier ce compte ?", + "notLinked": "Non lié", + "linkedAt": "Lié le <0>", + "accountLinking": "Liaison de compte", + "nickNameDes": "C'est votre nom d'affichage public. Il peut s'agir de votre vrai nom ou d'un pseudonyme.", + "cropAvatar": "Recadrer l'avatar", + "finance": "Finance", + "preference": "Préférence", + "accountCreatedAt": "Créé le <0>", + "shoeQr": "Afficher", + "deviceNothing": "WebDAV n'est pas pris en charge dans votre groupe d'utilisateurs.", + "connectionInfo": "Détails de connexion", + "proxyTooltip": "Proxyfier toutes les demandes de téléchargement de fichiers.", + "readonlyTooltip": "L'utilisateur ne peut que lire les fichiers via ce compte.", + "blockSysFilesUpload": "Bloquer le téléversement des fichiers système", + "blockSysFilesUploadTooltip": "Lorsqu'activé, les fichiers commençant par <0>. seront bloqués lors du téléversement.", + "rootFolderIn": "Sélectionner <0>", + "createWebDavAccount": "Créer un compte WebDAV", + "editWebDavAccount": "Modifier {{name}}", + "seeding": "Seeding", + "awaitSeeding": "En attente de seeding", + "awaitSeedingDes": "En attente de la fin du seeding.", + "downloadTransferDes": "Transférer les fichiers vers la destination.", + "downloadDes": "Télécharger les fichiers souhaités.", + "retryErrorHistory": "Historique des erreurs de nouvelle tentative", + "retryCount": "Nouvelle tentative", + "resumeAt": "Reprendre à", + "executeDuration": "Durée d'exécution", + "input": "Entrée", + "output": "Sortie", + "suspended": " (Suspendu)", + "updatedAt": "Mis à jour le", + "taskDetails": "Détails de la tâche", + "partialSuccessWarning": "Échec du traitement de {{num}} objet(s), ils ont été ignorés.", + "sendTask": "Envoyer la tâche", + "sendTaskDes": "Envoyer la tâche à un nÅ“ud pour traitement.", + "downloaded": "Téléchargé", + "importingFiles": "Importer des fichiers", + "importingFilesDes": "Indexer les fichiers et les importer dans le dossier spécifié.", + "importedFiles": "Fichiers importés", + "indexedFiles": "Fichiers indexés", + "extractedFiles": "Fichiers extraits", + "extractedFilesSize": "Taille des fichiers extraits", + "extractingFiles": "Extraction de fichiers", + "extractingFilesDes": "Extraire tous les fichiers vers le dossier donné.", + "downloadingZip": "Téléchargement d'archive", + "downloadingZipDes": "Télécharger l'archive vers l'espace de travail temporaire.", + "progressNotAvailable": "Progression pas encore disponible.", + "uploadedSize": "Taille relocalisée", + "archivedFiles": "Fichiers traités", + "transferredFiles": "Fichiers relocalisés", + "archivedFilesSize": "Taille des fichiers traités", + "createArchiveFinishing": "Valider les modifications pour les nouveaux fichiers", + "indexForArchiveDes": "Indexer les fichiers à archiver.", + "prepare": "Préparer", + "preparingWorkspaceDes": "Préparer l'espace de travail temporaire.", + "compressFiles": "Créer une archive", + "compressFilesDes": "Créer une archive vers l'espace de travail temporaire.", + "uploadArchiveFileDes": "Transférer le fichier d'archive vers le dossier cible.", + "uploadWorker": "Worker de téléchargement #{{num}}", + "relocatedEntities": "Entités relocalisées", + "queueToStart": "File d'attente pour démarrer", + "indexingFiles": "Indexer les fichiers", + "indexingFilesDes": "Indexer les fichiers à relocaliser, les verrouiller.", + "transferring": "Transfert", + "transferringRelocateDes": "Transférer les données vers la nouvelle stratégie de stockage.", + "committingChanges": "Valider les modifications", + "relocateFinishing": "Mettre à jour la référence d'entité vers la nouvelle stratégie de stockage.", + "autoRefresh": "Actualisation automatique", + "avatarUpdated": "L'avatar a été mis à jour et prendra effet avec un délai.", + "nickChanged": "Pseudo modifié et prendra effet après actualisation.", + "settingSaved": "Paramètre enregistré.", + "themeColorChanged": "Couleur du thème modifiée.", + "profile": "Profil", + "avatar": "Photo de profil", + "uid": "UID", + "nickname": "Nom d'affichage", + "group": "Groupe", + "regTime": "Date d'inscription", + "security": "Mot de passe et sécurité", + "profilePage": "Profil public", + "publicShareOnly": "Partage public uniquement", + "publicShareOnlyDes": "Afficher uniquement les partages sans mot de passe sur la page de profil.", + "allShare": "Tous les partages", + "allShareDes": "Afficher tous les partages sur la page de profil (y compris les partages protégés par mot de passe). Les utilisateurs doivent toujours saisir un mot de passe pour y accéder.", + "hideShare": "Masquer tous les partages", + "hideShareDes": "Masquer tous les partages sur la page de profil.", + "userHideShare": "L'utilisateur a masqué la liste des partages", + "accountPassword": "Mot de passe", + "2fa": "Authentification 2FA", + "enabled": "Activé", + "disabled": "Désactivé", + "appearance": "Apparence", + "themeColor": "Couleur du thème", + "darkMode": "Mode sombre", + "syncWithSystem": "Système", + "fileList": "Liste de fichiers", + "timeZone": "Fuseau horaire", + "webdavServer": "Serveur", + "userName": "Nom d'utilisateur", + "manageAccount": "Gérer les comptes", + "uploadImage": "Télécharger depuis un fichier", + "useGravatar": "Utiliser Gravatar ", + "changeNick": "Changer le pseudo", + "originalPassword": "Mot de passe actuel", + "enable2FA": "Activer l'authentification 2FA", + "disable2FA": "Désactiver l'authentification 2FA", + "2faDescription": "Veuillez utiliser n'importe quelle application mobile 2FA ou logiciel de gestion de mots de passe prenant en charge 2FA pour scanner le code QR afin d'ajouter ce site. Après le scan, veuillez remplir le code de vérification à 6 chiffres donné par l'application 2FA pour activer 2FA.", + "inputCurrent2FACode": "Saisissez le code de vérification 2FA actuel.", + "timeZoneCode": "Code de fuseau horaire IANA", + "authenticatorRemoved": "Authentificateur supprimé.", + "authenticatorAdded": "Authentificateur ajouté.", + "browserNotSupported": "Non pris en charge par le navigateur ou l'environnement actuel.", + "removedAuthenticator": "Supprimer l'authentificateur", + "removedAuthenticatorConfirm": "Êtes-vous sûr de vouloir supprimer cet authentificateur ?", + "addNewAuthenticator": "Ajouter une clé d'accès", + "hardwareAuthenticator": "Authentificateur matériel", + "copied": "Copié dans le presse-papiers.", + "pleaseManuallyCopy": "Le navigateur actuel ne prend pas en charge, veuillez copier manuellement.", + "webdavAccounts": "Comptes WebDAV", + "webdavHint": "Serveur WebDAV : {{url}} ; Nom d'utilisateur : {{name}} ; Le mot de passe est le mot de passe du compte créé ci-dessous.", + "annotation": "Annotation", + "rootFolder": "Dossier racine relatif", + "createdAt": "Créé le", + "action": "Actions", + "readonlyOn": "Lecture seule", + "readonlyOff": "Lecture et écriture", + "proxy": "Proxy inverse", + "none": "Aucun", + "proxied": "Proxyfié", + "delete": "Supprimer", + "listEmpty": "Aucun enregistrement.", + "createNewAccount": "Créer un nouveau compte", + "taskType": "Type de tâche", + "taskStatus": "Statut", + "taskProgress": "Progression de la tâche", + "errorDetails": "Détails de l'erreur", + "queueing": "En file d'attente", + "processing": "Traitement", + "failed": "Échoué", + "canceled": "Annulé", + "finished": "Terminé", + "fileTransfer": "Transfert de fichier", + "fileRecycle": "Recyclage de fichier", + "importFiles": "Importer des fichiers externes", + "transferProgress": "{{num}} fichiers terminés", + "waiting": "En attente", + "compressing": "Compression", + "decompressing": "Décompression", + "downloading": "Téléchargement", + "indexing": "Indexation", + "listing": "Insertion", + "allShares": "Partagé", + "trendingShares": "Tendance", + "totalShares": "Partages créés", + "fileName": "Nom du fichier", + "shareDate": "Partagé le", + "downloadNumber": "Téléchargements", + "viewNumber": "Vues", + "language": "Langue", + "iOSApp": "Application iOS/iPadOS", + "connectByiOS": "Se connecter à <0>{{title}} via des appareils iOS/iPadOS.", + "downloadOurApp": "Téléchargez notre APP :", + "fillInEndpoint": "Scannez le code QR ci-dessous avec notre App (N'utilisez PAS d'autre app pour scanner) :", + "loginApp": "Vous pouvez maintenant commencer à utiliser l'App. Si vous rencontrez des problèmes avec le code QR, vous pouvez également essayer de saisir manuellement votre nom d'utilisateur et mot de passe pour vous connecter.", + "relocateFileTo": "Relocaliser la stratégie de stockage vers {{policy}} pour <0>{{more}}", + "extractFileTo": "Extraire <0>{{more}} vers <1>", + "createArchiveTo": "Créer un fichier d'archive vers <1> pour <0>{{more}}", + "importFileTo": "Importer des fichiers de {{policy}} vers <0>" + }, + "vas": { + "points": "Points", + "paid": "Payé", + "fulfillFailedStatus": "Échec de l'exécution", + "unpaid": "Non payé", + "amount": "Montant", + "tradeNo": "N° de transaction", + "payments": "Paiements", + "creditReasonShareGain": "Lien de partage acheté", + "creditReasonSharePay": "Achat en magasin", + "creditReasonRecharge": "Recharge", + "creditChanges": "Changements de crédit", + "payXPoints": "Payer avec <0>", + "pointsPayAvailable": "Ce produit prend en charge le paiement par points, vous pouvez choisir de payer <0> à l'étape suivante.", + "payAmount": "Payer {{price}}", + "purchaseSomething": "Acheter {{name}}", + "redeem": "Échanger", + "shop": "Boutique", + "resumeTicket": "Ticket de reprise", + "resumeTicketDes": "Vous pouvez le trouver dans l'e-mail de confirmation de commande envoyé après paiement.", + "restorePurchase": "Restaurer l'achat", + "restorePurchaseDes": "Restaurer l'achat avec le \"Ticket de reprise\" dans l'e-mail de confirmation de commande.", + "paymentSuccess": "Paiement réussi", + "fulfillFailed": "Échec de l'exécution de la commande, veuillez contacter l'administrateur du site.", + "paidButton": "Paiement terminé", + "payInNewWindow": "Veuillez terminer le paiement dans la nouvelle fenêtre. Ne fermez pas cette page avant que le paiement soit terminé. Si la nouvelle fenêtre n'apparaît pas, veuillez <0>cliquer ici.", + "paymentFailedTitle": "Échec du traitement du paiement", + "paymentEmailHelper": "Un e-mail est requis pour envoyer le reçu d'achat, car vous n'êtes pas connecté.", + "payEquivalentCash": "Payer l'équivalent en espèces : {{num}}", + "payWithCash": "Payer en espèces", + "recharge": "Recharge", + "pointsBalance": "Solde de points : {{num}}", + "loginRequired": "Connexion requise", + "payWithPoints": "Payer avec des points", + "purchaseLogin": "Veuillez vous <0>connecter avant de continuer l'achat.", + "noAvailableSharePurchaseMethod": "Aucune méthode d'achat disponible.", + "purchaseShareLink": "Acheter un lien de partage", + "loginWith": "Se connecter avec {{name}}", + "sso": "SSO", + "qq": "QQ", + "quota": "Quota", + "exceedQuota": "Votre capacité utilisée a dépassé le quota, veuillez supprimer les fichiers supplémentaires ou acheter plus de stockage dès que possible.", + "extendStorage": "Acheter du stockage", + "folderPolicySwitched": "La stratégie de stockage pour le dossier actuel est basculée vers \"{{name}}\"", + "switchFolderPolicy": "Changement des stratégies de stockage de dossier", + "setPolicyForFolder": "Définir la stratégie de stockage pour le dossier actuel : ", + "manageMount": "Gérer les montages", + "saveToMyFiles": "Enregistrer dans mes fichiers", + "report": "Signaler un abus", + "reportTarget": "Cible du signalement", + "reportReason": "Raison", + "reportReasonOptions": ["Violation de droits d'auteur", "Contenu nuisible", "Spam", "Autre"], + "reportDescription": "Description supplémentaire", + "reportAbuseSuccess": "Signalement soumis.", + "migrateStoragePolicy": "Migrer la stratégie de stockage", + "fileSaved": "Fichier(s) enregistré(s).", + "sharePurchaseTitle": "Vous devez payer <0> avant d'accéder à ce lien.", + "payToDownload": "Payer pour télécharger", + "creditToBePaid": "Points à payer", + "creditGainPredict": "Gagnez {{num}} points par achat", + "creditPrice": " (Coûte {{num}} crédits)", + "creditFree": " (Crédits gratuits)", + "cancelSubscription": "L'annulation est réussie et le changement prendra effet dans quelques minutes ou heures.", + "qqUnlinked": "Dissocié du compte QQ.", + "groupExpire": "(Expire le <0>)", + "manuallyCancelSubscription": "Désabonner le groupe d'utilisateurs actuel", + "qqAccount": "Compte QQ", + "connect": "Connecter", + "unlink": "Dissocier", + "credits": "Crédits", + "cancelSubscriptionTitle": "Se désabonner", + "cancelSubscriptionWarning": "Vous retournerez au groupe d'utilisateurs initial et les crédits payés ne sont pas remboursables, êtes-vous sûr de vouloir continuer ?", + "mountPolicy": "Monter la stratégie de stockage", + "mountDescription": "Après avoir monté une stratégie de stockage sur un dossier, les nouveaux fichiers téléchargés dans ce dossier ou ses sous-dossiers seront stockés en utilisant la stratégie de stockage montée. Copier et déplacer vers ce dossier n'appliquera pas la stratégie de stockage montée ; lorsque plusieurs dossiers parents sont spécifiés, la stratégie de stockage du dossier parent le plus proche sera sélectionnée.", + "mountNewFolder": "Monter un nouveau dossier", + "nsfw": "NSFW", + "malware": "Malware", + "copyright": "Copyright", + "inappropriateStatements": "Déclarations inappropriées", + "other": "Autre", + "groupBaseQuota": "Quota de base du groupe - {{size}}", + "validPackQuota": "Packs de stockage - {{size}}", + "used": "Utilisé - {{size}}", + "total": "Total - {{size}}", + "validStorage": "Stockage supplémentaire valide", + "buyStorage": "Acheter du stockage", + "useGiftCode": "Échanger avec un code cadeau", + "packName": "Nom du pack", + "activationDate": "Date d'activation", + "validDuration": "Durée", + "expiredAt": "Expire le", + "days": "{{num}} jours", + "pleaseInputGiftCode": "Veuillez saisir le code cadeau.", + "pleaseSelectAStoragePack": "Veuillez sélectionner un pack de stockage.", + "paymentMethod": "Payer avec", + "noAvailableMethod": "Aucune méthode de paiement disponible", + "alipay": "Alipay", + "wechatPay": "Wechat Pay", + "payByCredits": "Crédits", + "purchaseDuration": "Durée", + "creditsNum": "Qté de crédits :", + "store": "Boutique", + "storageExpansion": "Extension de stockage", + "membership": "Adhésions", + "buyCredits": "Crédits", + "subtotal": "Sous-total : ", + "creditsTotalNum": "{{num}} crédits", + "purchaseNow": "Acheter maintenant", + "recommended": "Recommandé", + "enterGiftCode": "Saisir le code cadeau", + "qrcodeAlipay": "Veuillez utiliser Alipay pour scanner le code QR ci-dessous afin de terminer le paiement, cette page sera automatiquement actualisée après la fin du paiement.", + "qrcodeWechat": "Veuillez utiliser WeChat pour scanner le code QR ci-dessous afin de terminer le paiement, cette page sera automatiquement actualisée après la fin du paiement.", + "qrcodeCustom": "Veuillez scanner le code QR ci-dessous pour terminer le paiement, ou <0>ouvrir directement le lien de paiement, cette page sera automatiquement actualisée après la fin du paiement.", + "paymentCompleted": "Paiement terminé", + "productDelivered": "Vos achats sont traités.", + "confirmRedeem": "Échanger", + "productType": "Produit :", + "qyt": "Qté : ", + "duration": "Durée : ", + "subscribe": "S'abonner", + "selected": "Sélectionné : ", + "paymentQrcode": "QRCode de paiement", + "validDurationDays": "{{num}} jours", + "reportSuccessful": "Signalement soumis.", + "additionalDescription": "Description supplémentaire", + "announcement": "Annonce", + "dontShowAgain": "Ne plus afficher", + "openPaymentLink": "Ouvrir le lien de paiement", + "creditReasonAdjust": "Ajustement manuel" + } +} diff --git a/public/locales/fr-FR/common.json b/public/locales/fr-FR/common.json new file mode 100755 index 0000000..1f5faac --- /dev/null +++ b/public/locales/fr-FR/common.json @@ -0,0 +1,120 @@ +{ + "pageNotFound": "Page non trouvée", + "unknownError": "Erreur inconnue", + "errLoadingSiteConfig": "Impossible de charger la configuration du site : ", + "newVersionRefresh": "Une nouvelle version de la page actuelle est disponible.", + "update": "Mettre à jour", + "errorDetails": "Détails", + "renderError": "Une erreur s'est produite lors du rendu de la page, veuillez essayer de rafraîchir cette page.", + "ok": "OK", + "cancel": "Annuler", + "select": "Sélectionner", + "copyToClipboard": "Copier", + "close": "Fermer", + "dismiss": "Ignorer", + "intlDateTime": "{{val, datetime}}", + "seconds": "s [secondes]", + "minutes": "m [minutes] s [secondes]", + "hours": "H [heures] m [minutes]", + "days": "{{d}} jours", + "timeAgoLocaleCode": "fr_FR", + "forEditorLocaleCode": "fr", + "artPlayerLocaleCode": "fr", + "requestID": "ID de la requête : {{id}}", + "object": "Objet", + "error": "Erreur", + "areYouSure": "Êtes-vous sûr ?", + "incorrectSizeInput": "Saisie de taille incorrecte", + "of": "de", + "rowsPerPage": "Lignes par page", + "custom": "Personnalisé", + "enter": "Entrer", + "captcha": { + "cap": { + "human": "Je suis humain", + "verifying": "Vérification...", + "verified": "Vous êtes humain" + } + }, + "errors": { + "401": "Veuillez vous connecter.", + "403": "Vous n'êtes pas autorisé à effectuer cette action.", + "404": "Ressource non trouvée.", + "409": "Conflit. ({{message}})", + "40001": "Paramètres d'entrée non valides ({{message}}).", + "40002": "Échec du téléchargement.", + "40003": "Échec de la création du dossier.", + "40004": "Un objet avec le même nom existe déjà.", + "40005": "Signature expirée.", + "40006": "Type de stratégie non supporté.", + "40007": "Le groupe actuel n'a pas la permission d'effectuer cette action.", + "40011": "La session de téléchargement n'existe pas ou a expiré.", + "40012": "Index de fragment non valide. ({{message}})", + "40013": "Longueur du contenu non valide. ({{message}})", + "40014": "Limite de taille de lot dépassée pour l'obtention du lien source.", + "40015": "Limite de taille de lot aria2 dépassée.", + "40016": "Chemin non trouvé.", + "40017": "Ce compte a été bloqué.", + "40018": "Ce compte n'est pas activé.", + "40019": "Cette fonctionnalité n'est pas activée.", + "40020": "Identifiant non valide ou expiré.", + "40021": "Utilisateur non trouvé.", + "40022": "Code de vérification incorrect.", + "40023": "Session de connexion inexistante.", + "40024": "Impossible d'initialiser WebAuthn.", + "40025": "Échec de l'authentification.", + "40026": "Le code CAPTCHA n'est pas correct.", + "40027": "Échec de la vérification, veuillez rafraîchir la page et réessayer.", + "40028": "Échec de la livraison de l'e-mail.", + "40029": "Ce lien n'est pas valide.", + "40030": "Ce lien a expiré.", + "40032": "Cette adresse e-mail est déjà utilisée.", + "40033": "Ce compte n'est pas activé, l'e-mail d'activation a été renvoyé.", + "40034": "Cet utilisateur ne peut pas être activé.", + "40035": "Stratégie de stockage non trouvée.", + "40039": "Groupe non trouvé.", + "40044": "Fichier non trouvé.", + "40045": "Échec de la liste des objets sous le dossier donné.", + "40047": "Échec de l'initialisation du système de fichiers.", + "40048": "Échec de la création de la tâche", + "40049": "La taille du fichier dépasse la limite.", + "40050": "Type de fichier non autorisé.", + "40051": "Quota de stockage insuffisant.", + "40052": "Ce nom de fichier ou cette extension n'est pas autorisé.", + "40053": "Impossible d'effectuer cette action sur le dossier racine", + "40054": "Un fichier avec le même nom est déjà en cours de téléchargement dans ce dossier, veuillez nettoyer les sessions de téléchargement.", + "40055": "Métadonnées du fichier incohérentes.", + "40056": "Type de fichier compressé non supporté.", + "40057": "La stratégie de stockage disponible a changé, veuillez rafraîchir la liste des fichiers et ajouter cette tâche à nouveau.", + "40058": "Ce partage n'existe pas ou a déjà expiré.", + "40069": "Mot de passe incorrect.", + "40070": "Ce partage ne supporte pas l'aperçu.", + "40071": "Signature non valide.", + "40073": "Fichier en cours d'utilisation.", + "40074": "Trop de fichiers sélectionnés.", + "40079": "Limite maximale de fichiers parcourus dépassée, essayez de réduire la portée de l'opération.", + "40081": "Opération pas entièrement réussie.", + "40082": "Seul le propriétaire du fichier peut effectuer cette action.", + "40080": "E-mail ou mot de passe incorrect.", + "50001": "Échec de l'opération de base de données. ({{message}})", + "50002": "Échec de la signature de l'URL ou de la requête. ({{message}})", + "50004": "Échec de l'opération I/O. ({{message}})", + "50005": "Erreur interne.", + "50010": "Le nÅ“ud désiré n'est pas disponible.", + "50011": "Échec de la requête des métadonnées du fichier." + }, + "vasErrors": { + "40031": "Ce fournisseur d'e-mail est interdit, veuillez en changer pour un autre.", + "40059": "Vous ne pouvez pas sauvegarder votre propre partage.", + "40062": "Crédits insuffisants.", + "40063": "Votre abonnement actuel n'a pas encore expiré, veuillez aller à la page des paramètres pour désabonner manuellement l'abonnement d'abord.", + "40064": "Vous êtes déjà dans cet abonnement.", + "40065": "Code cadeau non valide.", + "40066": "Vous avez déjà une identité liée, veuillez la dissocier d'abord.", + "40067": "Cette identité est déjà liée à un autre compte.", + "40068": "Cette identité n'est liée à aucun compte.", + "40072": "Vous êtes administrateur, vous ne pouvez pas acheter d'autre groupe.", + "40084": "Votre compte est sans mot de passe, vous devez conserver au moins un compte lié.", + "40085": "Le montant total de cette commande est trop petit pour le paiement." + } +} diff --git a/public/locales/fr-FR/dashboard.json b/public/locales/fr-FR/dashboard.json new file mode 100755 index 0000000..9aa9f8b --- /dev/null +++ b/public/locales/fr-FR/dashboard.json @@ -0,0 +1,1621 @@ +{ + "errors": { + "40036": "La politique de stockage par défaut ne peut pas être supprimée.", + "40037": "Certains blobs de fichiers utilisent cette politique, veuillez d'abord supprimer ces blobs de fichiers.", + "40038": "{{message}} groupe(s) utilisent cette politique, veuillez d'abord dissocier ces groupes.", + "40040": "Impossible d'effectuer cette action sur le groupe système.", + "40041": "{{message}} utilisateurs sont encore dans ce groupe, veuillez d'abord supprimer ou dissocier ces utilisateurs.", + "40042": "Impossible de changer le groupe du groupe d'utilisateurs système.", + "40043": "Impossible d'effectuer cette action sur l'utilisateur par défaut.", + "40046": "Impossible d'effectuer cette action sur le nÅ“ud maître.", + "40060": "Le nÅ“ud esclave ne peut pas envoyer de requête de rappel au maître, veuillez vérifier le paramètre du nÅ“ud maître : Basique - Informations du site - URL du site, assurez-vous que le nÅ“ud esclave peut accéder à cette URL. ({{message}})", + "40061": "Version Cloudreve incompatible. ({{message}})", + "40086": "Le nÅ“ud est utilisé par les politiques de stockage suivantes : {{message}}.", + "50008": "Échec de la mise à jour du paramètre. ({{message}})", + "50009": "Échec de l'ajout de la politique CORS." + }, + "nav": { + "summary": "Résumé", + "settings": "Paramètres", + "basicSetting": "Basique", + "email": "E-mail", + "transportation": "Transmission", + "appearance": "Apparence", + "image": "Images", + "captcha": "Captcha", + "storagePolicy": "Politique de stockage", + "nodes": "NÅ“uds", + "groups": "Groupes", + "users": "Utilisateurs", + "files": "Fichiers", + "entities": "Blobs de fichiers", + "shares": "Partages", + "tasks": "Tâches en arrière-plan", + "remoteDownload": "Téléchargement distant", + "generalTasks": "Général", + "title": "Tableau de bord", + "dashboard": "Tableau de bord Cloudreve", + "userSession": "Session utilisateur", + "fileSystem": "Système de fichiers", + "mediaProcessing": "Traitement multimédia", + "queue": "File d'attente", + "events": "Événements", + "server": "Serveur", + "customProps": "Propriétés personnalisées", + "abuseReport": "Rapport d'abus" + }, + "summary": { + "generatedAt": "Généré le <0>", + "confirmSiteURLTitle": "Confirmer l'URL du site", + "siteURLNotMatch": "L'URL du site que vous avez définie ne contient pas l'URL actuelle ({{current}}), voulez-vous l'ajouter à la liste ?", + "setAsPrimary": "Définir comme URL principale du site", + "setAsPrimaryDes": "Définir {{current}} comme URL principale du site, utilisée pour la communication avec les services externes et la réception des rappels. Veuillez utiliser une URL accessible par WAN.", + "setAsSecondary": "Ajouter aux URLs secondaires", + "setAsSecondaryDes": "Ajouter {{current}} aux URLs secondaires, Cloudreve sélectionnera automatiquement s'il faut l'utiliser en fonction de l'URL réellement accédée par l'utilisateur.", + "siteURLDescription": "Ce paramètre est très important, assurez-vous qu'il correspond à l'URL réelle de votre site. Vous pouvez modifier ce paramètre dans Paramètres - Basique.", + "ignore": "Ignorer", + "changeIt": "Le changer", + "trend": "Tendance", + "summary": "Résumé", + "totalUsers": "Utilisateurs", + "totalFilesAndFolders": "Fichiers et dossiers", + "shareLinks": "Liens de partage", + "totalBlobs": "Blobs", + "homepage": "Page d'accueil", + "github": "GitHub", + "documents": "Documents", + "discordCommunity": "Communauté Discord", + "telegram": "Groupe Telegram", + "forum": "Discussions GitHub", + "buyPro": "Passer à Pro", + "publishedAt": "publié le <0>", + "licenseExpireAt": "Date d'expiration de la licence", + "permanentLicense": "Licence permanente", + "offlineLicenseExpireAy": "Date d'expiration de la licence hors ligne", + "offlineLicenseDes": "Cloudreve mettra automatiquement à jour la licence hors ligne avant son expiration si votre serveur est connecté au réseau.", + "licensedDomains": "Domaines sous licence", + "renew": "Actualiser la licence hors ligne", + "manageLicense": "Gérer la licence", + "volPurchase": "La licence VOL client doit être achetée séparément depuis le <0>Tableau de bord de gestion des licences. La licence VOL permet à vos utilisateurs de se connecter à votre site en utilisant <1>Cloudreve iOS gratuitement, sans que les utilisateurs aient besoin de payer un abonnement pour l'application iOS elle-même. Après avoir acheté une licence, veuillez cliquer sur \"Actualiser la licence hors ligne\" ci-dessous.", + "iosVol": "Licence en volume client iOS (VOL)", + "refreshSuccessfully": "Actualisé avec succès.", + "manualRefresh": "Actualiser manuellement la licence hors ligne", + "manualRefreshDes": "Échec de l'actualisation automatique de la licence hors ligne, veuillez essayer de vous connecter au <0>Tableau de bord de gestion des licences pour obtenir la dernière licence hors ligne et la coller ci-dessous.", + "announcement": "Annonce" + }, + "queue": { + "queueName_io_intense": "IO Intensif", + "queueName_io_intenseDes": "File d'attente pour gérer de grandes quantités d'opérations IO, incluant : transfert de politique de stockage, décompression, compression.", + "queueName_media_meta": "Extraction de métadonnées multimédia", + "queueName_media_metaDes": "Utilisé pour extraire les métadonnées des fichiers multimédia.", + "queueName_recycle": "Recyclage de blobs", + "queueName_recycleDes": "Utilisé pour supprimer les blobs de fichiers expirés.", + "queueName_thumb": "Génération de miniatures", + "queueName_thumbDes": "Utilisé pour générer des miniatures pour les fichiers.", + "queueName_remote_download": "Téléchargement distant", + "queueName_remote_downloadDes": "Utilisé pour traiter les tâches de téléchargement distant.", + "failed": "Échoué ({{count}})", + "success": "Succès ({{count}})", + "suspending": "Suspendu ({{count}})", + "busyWorker": "En cours de traitement ({{count}})", + "submited": "Soumis ({{count}})", + "editQueueSettings": "Modifier les paramètres de la file d'attente - {{name}}", + "workerNum": "Threads de travail", + "workerNumDes": "Nombre maximum de tâches à exécuter en parallèle dans la file d'attente des tâches", + "maxExecution": "Temps d'exécution maximum", + "maxExecutionDes": "Temps d'exécution maximum (secondes) pour une tâche, après quoi la tâche sera terminée.", + "backoffFactor": "Facteur de recul", + "backoffFactorDes": "Facteur de croissance pour les intervalles de temps de nouvelle tentative des tâches.", + "backoffMaxDuration": "Temps de recul maximum", + "backoffMaxDurationDes": "Temps de recul maximum (secondes) pour les nouvelles tentatives de tâches.", + "maxRetry": "Nouvelles tentatives maximum", + "maxRetryDes": "Nombre maximum de nouvelles tentatives après l'échec d'une tâche.", + "retryDelay": "Délai de nouvelle tentative", + "retryDelayDes": "Délai initial (secondes) pour les nouvelles tentatives de tâches." + }, + "settings": { + "headlessFooter": "Pied de page de la page d'accueil", + "headlessFooterDes": "Contenu HTML personnalisé affiché en bas des pages de connexion, d'inscription et de résultat de rappel.", + "headlessBottom": "Bas de la page d'accueil", + "headlessBottomDes": "Contenu HTML personnalisé affiché en bas des pages de connexion, d'inscription et de résultat de rappel.", + "customHTML": "HTML personnalisé", + "customHTMLDes": "Insérer du contenu HTML personnalisé à la position prédéfinie du site.", + "sidebarBottom": "Bas de la barre latérale", + "sidebarBottomDes": "Contenu HTML personnalisé affiché en bas de la barre latérale.", + "addNavItem": "Ajouter un élément de navigation", + "customNavItems": "Éléments personnalisés de la barre latérale", + "customNavItemsDes": "Vous pouvez ajouter des éléments personnalisés à la barre latérale, et les utilisateurs seront redirigés vers le lien correspondant lorsqu'ils cliquent.", + "navItemUrl": "Lien", + "iconifyNamePlaceholder": "Identifiant d'icône Iconify, par ex. fluent:home-24-regular", + "imageUrl": "URL de l'image", + "iconifyName": "Nom de l'icône Iconify", + "oidc": "OpenID Connect (OIDC)", + "oidcDes": "OpenID Connect (OIDC) est un protocole d'authentification ouvert pour la vérification d'identité entre différents systèmes. Après avoir créé une application dans une plateforme d'identité tierce, veuillez ajouter <0>{{url}} au champ \"URI de redirection\". Pour plus de détails, veuillez consulter la <1>documentation.", + "clientID": "ID client", + "clientIDDes": "L'ID client de l'application créée dans la plateforme d'identité tierce.", + "clientSecret": "Secret client", + "clientSecretDes": "Le secret client de l'application créée dans la plateforme d'identité tierce.", + "scope": "Portée", + "scopeDes": "Portées supplémentaires à demander, séparées par des virgules <0>,. Par défaut, Cloudreve demandera <0>openid, <0>email et <0>profile ; pas besoin de répéter ici.", + "oidcWellknown": "Configuration OIDC Wellknown", + "oidcWellknownDes": "Document wellknown de la plateforme d'identité tierce, contenant les informations de configuration d'OpenID Connect.", + "importFromWellknown": "Importer depuis l'URL", + "importOidc": "Importer la configuration OIDC Wellknown", + "oidcWellknownUrl": "URL Wellknown", + "oidcWellknownUrlDes": "URL du document wellknown de la plateforme d'identité tierce, tel que <0>https://accounts.google.com/.well-known/openid-configuration.", + "resetUrl": "URL de réinitialisation", + "exceedToleranceDays": "Jours de tolérance pour l'interdiction", + "activateUrl": "URL d'activation", + "domainNotLicensed": "Domaine non sous licence", + "domainNotLicensedDes": "L'URL du site que vous avez définie contient un domaine non autorisé, veuillez ajouter ce sous-domaine dans le <0>Tableau de bord de gestion des licences et cliquez sur le bouton ci-dessous pour mettre à jour la licence et réessayer.", + "showSettings": "Afficher les paramètres", + "perPage": "{{num}} par page", + "noNodes": "Aucun nÅ“ud disponible.", + "extractMediaMeta": "Extraire les métadonnées multimédia", + "extractMediaMetaDes": "Extraire les métadonnées des fichiers multimédia pour l'affichage et la recherche. Par défaut, les politiques de stockage non locales n'utiliseront que le générateur \"Natif dans la politique de stockage\". Vous pouvez étendre la capacité de miniatures des politiques de stockage tierces en activant la fonctionnalité \"Proxy d'extracteur\" dans la page de paramètres de la politique de stockage. Pour plus de détails, veuillez consulter la <0>documentation.", + "exif": "EXIF", + "exifDes": "Extraire les métadonnées EXIF des fichiers image pour l'affichage et la recherche.", + "music": "Métadonnées musicales", + "musicDes": "Extraire les métadonnées des fichiers musicaux, incluant le titre, l'artiste, l'album, etc.", + "ffprobe": "FFprobe", + "ffprobeDes": "Utiliser FFprobe pour extraire les métadonnées des fichiers vidéo et audio.", + "maxSizeLocal": "Taille maximale du fichier (Stockage local)", + "maxSizeLocalDes": "Taille maximale du fichier pour l'extraction de métadonnées lorsque le fichier est stocké dans une politique de stockage local, 0 signifie aucune limite.", + "maxSizeRemote": "Taille maximale du fichier (Stockage distant)", + "maxSizeRemoteDes": "Taille maximale du fichier pour l'extraction de métadonnées lorsque le fichier est stocké dans des politiques de stockage tierces, 0 signifie aucune limite.", + "exifBruteForce": "Utiliser la force brute si nécessaire", + "exifBruteForceDes": "Lorsqu'activé, l'ensemble du fichier sera scanné pour trouver les données EXIF si elles ne peuvent pas être trouvées dans l'emplacement d'en-tête standard. Cela peut augmenter le temps de traitement mais peut trouver des données EXIF dans des emplacements non standard.", + "musicCover": "Pochette musicale", + "musicCoverDes": "Extraire la pochette d'album des fichiers musicaux, prend en charge le conteneur ID3 (v1, 2.2, 2.3 et 2.4). Ce générateur dépend de tout autre générateur de miniatures d'images (intégré à Cloudreve ou VIPS).", + "geocoding": "Géocodage", + "geocodingDes": "Obtenez des informations d'adresse en utilisant le service Mapbox basé sur les informations de coordonnées enregistrées dans l'EXIF des médias.", + "mapboxAK": "Clé API Mapbox", + "mapboxAKDes": "Clé API créée dans la console Mapbox.", + "geocodingDependencyWarning": "Le générateur de géocodage dépend du générateur EXIF, veuillez activer le générateur EXIF.", + "notAppliedToNativeGenerator": "{{prefix}}Non applicable au générateur natif des politiques de stockage.", + "notAppliedToOneDriveNativeGenerator": "{{prefix}}Non applicable au générateur natif des politiques de stockage OneDrive ou SharePoint.", + "fileBlobMargin": "Marge du cache d'URL Blob de fichier (secondes)", + "fileBlobMarginDes": "Lorsque le même Blob de fichier est demandé plusieurs fois, si l'URL initiale a une période de validité restante supérieure à la marge, la même URL sera réutilisée.", + "fileBlobTimeout": "TTL de l'URL Blob de fichier (secondes)", + "fileBlobTimeoutDes": "Limiter la période de validité de l'URL temporaire obtenue lorsque les utilisateurs ouvrent ou téléchargent des fichiers, applicable uniquement aux politiques de stockage local, WebDAV, ou aux fichiers téléchargés via le relais Cloudreve.", + "wopiSessionTimeout": "TTL de session WOPI (secondes)", + "wopiSessionTimeoutDes": "Limiter la période de validité d'une seule session lorsque les utilisateurs éditent des fichiers en utilisant WOPI. Après expiration, les utilisateurs doivent rouvrir le fichier depuis Cloudreve.", + "oauthRefresh": "Intervalle d'actualisation pour la politique de stockage OAuth", + "oauthRefreshDes": "Définir la fréquence d'actualisation des identifiants OAuth pour les politiques de stockage (par ex. OneDrive) qui nécessitent OAuth. Cela peut empêcher l'expiration des identifiants due à de longues périodes d'inactivité", + "transitParallelNum": "Transferts de relais parallèles maximum", + "transitParallelNumDes": "Le nombre maximum de téléversements parallèles lorsqu'une tâche de transfert de relais de fichier côté serveur contient plusieurs fichiers.", + "failedChunkRetry": "Nombre maximum de nouvelles tentatives pour les échecs de téléversement de chunks", + "failedChunkRetryDes": "Le nombre maximum de nouvelles tentatives pour les échecs de téléversement de chunks, applicable uniquement aux téléversements côté serveur ou aux transferts de relais.", + "cacheChunks": "Mettre en cache les chunks de streaming", + "cacheChunksDes": "Si activé, les données de chunk seront mises en cache dans le répertoire temporaire du système pendant le transfert de streaming, afin qu'elles puissent être utilisées pour réessayer les téléversements de chunks échoués ;\n Si désactivé, les téléversements de chunks de transfert de streaming ne prendront pas d'espace disque supplémentaire, mais tout le téléversement échouera immédiatement si le téléversement de chunk échoue.", + "folderPropsTimeout": "TTL du cache des statistiques de dossier (secondes)", + "folderPropsTimeoutDes": "La période de validité du cache de résultats lorsque les utilisateurs calculent les statistiques de dossier (taille, nombre de fichiers, etc.).", + "slaveAPIExpiration": "TTL de signature API esclave (secondes)", + "slaveAPIExpirationDes": "La période de validité de la signature utilisée par le nÅ“ud maître lors de l'accès à l'API du nÅ“ud esclave.", + "uploadSessionTimeout": "TTL de session de téléversement (secondes)", + "uploadSessionDes": "Dans une période de session de téléversement valide, pour les politiques de stockage prises en charge, les utilisateurs peuvent reprendre les tâches inachevées. La valeur maximale qui peut être définie est limitée par les règles des différents fournisseurs de politiques de stockage.", + "archiveTimeout": "TTL de session de téléchargement par lots côté serveur (secondes)", + "advanceOptions": "Options avancées", + "emojiOptions": "Options emoji", + "addCategorize": "Ajouter une catégorie", + "category": "Catégorie", + "searchQuery": "Requête de catégorisation de fichier", + "importWopi": "Importer les paramètres de l'application WOPI", + "wopiEndpoint": "Point de terminaison de découverte WOPI", + "wopiDes": "Étendre les capacités d'aperçu et d'édition en ligne de Cloudreve en s'intégrant avec des systèmes de traitement de documents en ligne qui prennent en charge le protocole WOPI. Veuillez remplir l'adresse de découverte du service WOPI ici, tel que <0>https://example.com/hosting/discovery. Pour plus de détails, veuillez consulter la <1>documentation.", + "embeddedWebpageViewer": "Visionneuse de page Web intégrée", + "wopiViewer": "Application WOPI", + "ext": "Extension", + "invalidWopiActionMapping": "Mappage d'action WOPI invalide", + "woapiActionMapping": "Mappages d'action WOPI", + "drawioHost": "Instance DrawIO", + "drawioHostDes": "Vous pouvez utiliser l'URL pour une instance auto-hébergée.", + "openInNew": "Ouvrir dans une nouvelle fenêtre", + "openInNewDes": "Si coché, cela ouvrira directement un nouvel onglet pour ouvrir cette application.", + "maxSize": "Taille maximale du fichier", + "maxSizeDes": "La taille maximale du fichier prise en charge par cette application. 0 signifie aucune limite. Si le fichier dépasse cette taille, il sera toujours ouvert, mais les utilisateurs seront avertis.", + "srcEncodedVar": "URL d'accès temporaire au Blob de fichier encodée en URL", + "srcVar": "URL d'accès temporaire au blob de fichier", + "srcBase64Var": "URL d'accès temporaire au Blob de fichier encodée en Base64", + "nameEncodedVar": "Nom de fichier encodé en URL", + "versionEntityVar": "L'ID Blob de la version de fichier ouverte, vide signifie la dernière version.", + "fileIdVar": "ID de fichier", + "userIdVar": "ID utilisateur, vide lorsque non connecté.", + "userDisplayNameVar": "Nom d'affichage utilisateur encodé en URL.", + "fileViewers": "Applications de fichier", + "addViewer": "Ajouter une application", + "viewerGroupTitle": "Groupe d'applications #{{index}}", + "viewerType": "Type", + "viewerPlatform": "Plateforme", + "viewerPlatformDes": "Sélectionnez la plateforme correspondante pour afficher l'application uniquement sur cette plateforme.", + "viewerPlatformPC": "Bureau", + "viewerPlatformMobile": "Mobile", + "viewerPlatformAll": "Toutes", + "displayName": "Nom d'affichage", + "displayNameDes": "Nom d'affichage pour les utilisateurs, prend en charge la clé i18next.", + "viewerEnabled": "Activé", + "newFileAction": "Actions de nouveau fichier", + "newFileActionDes": "En ajoutant ce mappage, les utilisateurs verront cette option d'application lorsqu'ils cliqueront sur le bouton \"Nouveau\".", + "addNewFileAction": "Ajouter un mappage", + "builtinViewerType": "Application intégrée", + "wopiViewerType": "WOPI", + "customViewerType": "Personnalisé", + "nMapping": "{{num}} mappage(s)", + "editViewerTitle": "Modifier {{name}}", + "builtInIconUrlDes": "Cette application intégrée a une icône par défaut. Lorsque l'URL de l'icône est laissée vide, l'icône par défaut sera utilisée.", + "viewerUrl": "URL de l'application", + "viewerUrlDes": "URL de l'application personnalisée, les <0>variables magiques sont prises en charge.", + "addIcon": "Ajouter une icône", + "exts": "Liste d'extensions", + "icon": "Icône", + "iconUrl": "URL de l'icône", + "iconColor": "Couleur", + "iconColorDark": "Couleur (Mode sombre)", + "fileIcons": "Icônes de fichier", + "builtinIcon": "Intégré", + "mimeMapping": "Mappage de type MIME", + "mimeMappingDes": "Mappage de type MIME au format JSON, où la clé est l'extension de fichier et la valeur est le type MIME. Cloudreve déterminera le type MIME du fichier basé sur l'extension de fichier et ce paramètre.", + "mapProvider": "Fournisseur de cartes", + "mapProviderDes": "Fournisseur de cartes utilisé pour afficher les informations de localisation des médias.", + "mapGoogle": "Google Maps (Leaflet)", + "mapOpenStreetMap": "OpenStreetMap (Leaflet)", + "mapboxMap": "OpenStreetMap (Mapbox)", + "mapboxAccessToken": "Jeton d'accès Mapbox", + "mapboxAccessTokenDes": "Jeton d'accès public créé dans la <0>console Mapbox.", + "tileType": "Type de tuile par défaut", + "tileTypeDes": "Type de tuile par défaut pour Google Maps.", + "tileTypeTerrain": "Terrain", + "tileTypeSatellite": "Satellite", + "tileTypeGeneral": "Régulier", + "maxPageSize": "Taille maximale de page", + "maxPageSizeDes": "Limiter le nombre maximum de fichiers que les utilisateurs peuvent ajuster par page.", + "maxRecursiveSearch": "Nombre maximum de recherches récursives", + "maxRecursiveSearchDes": "Le nombre maximum de recherches récursives autorisées lors de la recherche de fichiers. Si le nombre de fichiers recherchés dépasse cette limite, la recherche s'arrêtera et avertira l'utilisateur.", + "maxBatchSize": "Taille maximale de lot", + "maxBatchSizeDes": "Le nombre maximum de fichiers que les utilisateurs peuvent opérer en lot, seul le niveau supérieur sera compté, et le nombre de fichiers sous les sous-répertoires ne sera pas compté.", + "defaultPagination": "Méthode de pagination pour la liste de fichiers", + "cursorPagination": "Pagination par curseur", + "cursorPaginationDes": "Plus de fichiers seront automatiquement chargés lorsque l'utilisateur fait défiler vers le bas. Cette méthode fonctionne mieux pour les grandes listes de fichiers, mais le nombre total de pages ne peut pas être vu.", + "offsetPagination": "Pagination par décalage", + "offsetPaginationDes": "La navigation de pagination sera affichée en bas de la page ; les utilisateurs peuvent voir le nombre total de pages et aller à une page spécifique. Cette méthode fonctionne légèrement moins bien pour les grandes listes de fichiers.", + "defaultPaginationDes": "La pagination par curseur sera forcée à utiliser lors de la recherche, indépendamment des paramètres ci-dessus.", + "publicResourceMaxAge": "Âge maximum du cache des ressources statiques (secondes)", + "publicResourceMaxAgeDes": "L'âge maximum du cache pour les ressources statiques accessibles publiquement (par ex. fichiers, miniatures et photos de profil utilisateur).", + "cronDes": "{{des}} Une <0>syntaxe Cron correcte est requise ici. Le redémarrage de Cloudreve est nécessaire pour prendre effet.", + "entityCollectInterval": "Intervalle de recyclage des Blobs de fichier", + "entityCollectIntervalDes": "Définir la fréquence de scan et de suppression des blobs de fichier expirés.", + "trashBinInterval": "Intervalle de scan de la corbeille", + "trashBinIntervalDes": "Définir la fréquence de scan et de suppression des fichiers expirés dans la corbeille.", + "logtoName": "Nom de la méthode de connexion", + "logtoNameDes": "Nom de la méthode de connexion, affiché aux utilisateurs. Par défaut \"SSO\", prend en charge la clé i18next.", + "logtoDirectSSO": "Connexion directe", + "logtoDirectSSODes": "Si vous voulez ignorer l'écran de connexion Logto et aller directement à la connexion tierce ou SSO, veuillez remplir l'identifiant du connecteur social ici. Pour plus de détails, veuillez consulter la <0>documentation Logto.", + "logtoEndpoint": "Point de terminaison Logto", + "logtoEndpointDes": "L'URL du point de terminaison Logto obtenue depuis le panneau de gestion d'application, qui peut être une instance auto-hébergée.", + "logtoKey": "Secret d'application", + "logtoKeyDes": "Secret d'application créé dans la page de gestion d'application.", + "logtoAppIDDes": "ID d'application créé dans la page de gestion d'application.", + "logto": "Logto", + "logtoDes": "Avec <0>Logto, vous pouvez obtenir plus de connexions de plateformes tierces, telles qu'Apple, GitHub, Microsoft Entra ID, Google, SMS, etc. Veuillez créer une \"Application Web Traditionnelle\" dans le portail de gestion Logto et ajouter <1>{{url}} aux \"URIs de redirection\".", + "thirdPartySignIn": "Connexion tierce", + "logo": "LOGO", + "logoDes": "URL du LOGO, veuillez fournir différents logos pour les modes sombre et clair.", + "dark": "Mode sombre", + "light": "Mode clair", + "tosUrl": "URL des conditions de service", + "tosUrlDes": "Sera affiché dans le pied de page de la page de connexion ou d'inscription, laissez vide pour ne pas afficher.", + "privacyUrl": "URL de la politique de confidentialité", + "privacyUrlDes": "Sera affiché dans le pied de page de la page de connexion ou d'inscription, laissez vide pour ne pas afficher.", + "addSecondary": "Ajouter une URL de site secondaire", + "secondarySiteURL": "Secondaire", + "secondaryDes": "Vous pouvez également ajouter d'autres URLs secondaires, Cloudreve sélectionnera automatiquement s'il faut l'utiliser en fonction de l'URL réellement accédée par l'utilisateur. L'URL de votre site doit être sous licence.", + "primarySiteURL": "Principale", + "primarySiteURLDes": "L'URL principale du site est utilisée pour la communication avec les services externes et la réception des rappels (par ex. paiement, fournisseur de stockage), veuillez utiliser une URL accessible par WAN.", + "revert": "Annuler les modifications", + "saved": "Paramètres sauvegardés.", + "save": "Sauvegarder", + "basicInformation": "Informations de base", + "mainTitle": "Nom du site", + "mainTitleDes": "Nom de l'instance.", + "siteDescription": "Description du site", + "siteDescriptionDes": "Description du site Web, qui peut être affichée dans le résumé de la page partagée.", + "siteURL": "URL du site", + "customFooterHTML": "HTML de pied de page personnalisé", + "customFooterHTMLDes": "Code HTML personnalisé inséré en bas de la page.", + "announcement": "Annonce", + "announcementDes": "Annonces affichées aux utilisateurs connectés. Une valeur vide ne sera pas affichée. Après que ce contenu soit modifié, tous les utilisateurs verront l'annonce à nouveau.", + "supportHTML": "Entrez du HTML ou du texte brut.", + "branding": "Image de marque", + "smallIcon": "Petite icône", + "smallIconDes": "URL de la petite icône, format ico ou svg. Cette icône sera également affichée dans les onglets du navigateur, les signets et les raccourcis de bureau.", + "mediumIcon": "Icône moyenne", + "mediumIconDes": "URL de l'icône moyenne, taille préférée de 192x192, format png.", + "largeIcon": "Grande icône", + "largeIconDes": "URL de la grande icône, taille préférée de 512x512, format png. Cette icône sera également affichée lors du changement de compte dans l'application iOS.", + "displayMode": "Mode d'affichage", + "displayModeDes": "Le mode d'affichage d'une application PWA après son installation.", + "themeColor": "Couleur du thème", + "themeColorDes": "Valeur de couleur CSS qui affecte la couleur de la barre d'état sur l'écran de lancement PWA, la barre d'état dans la page de contenu, et la barre d'adresse.", + "backgroundColor": "Couleur d'arrière-plan", + "backgroundColorDes": "Valeur de couleur CSS.", + "hint": "Astuce", + "webauthnNoHttps": "Web Authn nécessite que votre site Web soit activé en HTTPS, et veuillez confirmer que dans Paramètres - Basique - URL du site utilise également HTTPS.", + "accountManagement": "Comptes", + "allowNewRegistrations": "Accepter les nouvelles inscriptions", + "allowNewRegistrationsDes": "Après désactivation, aucun nouvel utilisateur ne peut s'inscrire, sauf s'il est ajouté manuellement par les administrateurs.", + "emailActivation": "Activation par e-mail", + "emailActivationDes": "Après activation, les nouveaux utilisateurs doivent cliquer sur le lien d'activation dans l'e-mail pour terminer les inscriptions. Veuillez vous assurer que les <0>paramètres de livraison d'e-mail sont corrects, sinon l'e-mail d'activation ne sera pas livré.", + "captchaForSignup": "Captcha pour les inscriptions", + "captchaForSignupDes": "S'il faut activer le captcha pour les inscriptions.", + "captchaForLogin": "Captcha pour les connexions", + "captchaForLoginDes": "S'il faut activer le captcha pour les connexions.", + "captchaForReset": "Captcha pour la réinitialisation du mot de passe", + "captchaForResetDes": "S'il faut activer le captcha pour la réinitialisation du mot de passe.", + "captchaForAbuseReport": "Captcha pour le rapport d'abus", + "captchaForAbuseReportDes": "S'il faut activer le captcha pour le rapport d'abus.", + "webauthnDes": "S'il faut permettre aux utilisateurs de se connecter avec des dispositifs d'authentification matérielle, tels que : visage, empreinte digitale ou clé USB ; le site doit activer HTTPS.", + "webauthn": "Connexion avec les clés d'accès", + "defaultSymbolics": "Raccourcis de partage par défaut", + "defaultSymbolicsDes": "Raccourcis de partage par défaut dans le répertoire racine des nouveaux utilisateurs. Veuillez rechercher les liens de partage par ID, vous pouvez voir l'ID sur le côté gauche de la <0>liste de partage.", + "searchShare": "Rechercher l'ID de partage...", + "defaultGroup": "Groupe par défaut", + "defaultGroupDes": "Le groupe d'utilisateurs initial après l'inscription de l'utilisateur.", + "testMailSent": "L'e-mail de test est envoyé.", + "testSMTPSettings": "Tester les paramètres SMTP", + "testSMTPTooltip": "Cloudreve utilisera vos paramètres SMTP actuels pour envoyer un e-mail de test, pas besoin de sauvegarder les paramètres avant de tester.", + "recipient": "Destinataire", + "send": "Envoyer", + "smtp": "SMTP", + "senderName": "Nom de l'expéditeur", + "senderNameDes": "Le nom de l'expéditeur affiché dans l'e-mail.", + "senderAddress": "Adresse de l'expéditeur", + "senderAddressDes": "Adresse e-mail de l'expéditeur.", + "smtpServer": "Serveur SMTP", + "smtpServerDes": "Adresse du serveur SMTP, sans numéro de port.", + "smtpPort": "Port SMTP", + "smtpPortDes": "Port du serveur SMTP.", + "smtpUsername": "Nom d'utilisateur SMTP", + "smtpUsernameDes": "Nom d'utilisateur SMTP, généralement le même que l'adresse de l'expéditeur.", + "smtpPassword": "Mot de passe SMTP", + "smtpPasswordDes": "Mot de passe de la boîte mail de l'expéditeur.", + "replyToAddress": "Adresse de réponse", + "replyToAddressDes": "La boîte mail utilisée pour recevoir les e-mails de réponse lorsque les utilisateurs répondent aux e-mails envoyés par le système.", + "enforceSSL": "Forcer la connexion SSL", + "enforceSSLDes": "S'il faut forcer une connexion chiffrée SSL. Si vous ne pouvez pas envoyer d'e-mails, vous pouvez désactiver ceci et Cloudreve essaiera d'utiliser STARTTLS et décidera s'il faut utiliser des connexions chiffrées.", + "smtpTTL": "TTL de connexion SMTP (secondes)", + "smtpTTLDes": "Les connexions SMTP établies pendant la période TTL seront réutilisées par les nouvelles demandes de livraison d'e-mail.", + "emailTemplates": "Modèles d'e-mail", + "activateNewUser": "Activer un nouvel utilisateur", + "resetPassword": "Réinitialiser le mot de passe", + "sendTestEmail": "Envoyer un e-mail de test", + "transportation": "Transmission", + "workerNum": "Nombre de travailleurs", + "workerNumDes": "Le nombre maximum de tâches à exécuter en parallèle par la file d'attente des tâches du nÅ“ud maître, le redémarrage de Cloudreve est nécessaire pour prendre effet.", + "tempFolder": "Dossier temporaire", + "tempFolderDes": "Utilisé pour stocker les fichiers temporaires générés par des tâches telles que la décompression, la compression, etc.", + "textEditMaxSize": "Taille maximale des fichiers de document éditables", + "textEditMaxSizeDes": "La taille maximale d'un fichier de document qui peut être édité en ligne, les fichiers au-delà de cette taille ne peuvent pas être édités en ligne. Ce paramètre s'applique aux éditeurs Web en ligne tels que le texte brut, le code et les documents Office (WOPI).", + "resetConnection": "Réinitialiser la connexion après échec de téléversement", + "resetConnectionDes": "Si activé, le serveur forcera la réinitialisation de la connexion si la vérification de téléversement échoue.", + "batchDownload": "Téléchargement par lots", + "previewURL": "URL d'aperçu", + "cannotDeleteDefaultTheme": "Impossible de supprimer le thème par défaut.", + "themeConfig": "Configurations", + "actions": "Actions", + "wrongFormat": "Format incorrect.", + "avatar": "Avatar", + "gravatarServer": "Serveur Gravatar", + "gravatarServerDes": "URL du serveur miroir Gravatar.", + "avatarFilePath": "Chemin du fichier avatar", + "avatarFilePathDes": "Chemin pour sauvegarder les fichiers avatar des utilisateurs, relatif au dossier de données Cloudreve.", + "avatarSize": "Taille maximale du fichier avatar", + "avatarSizeDes": "Taille maximale des fichiers avatar que les utilisateurs peuvent téléverser.", + "avatarImageSize": "Taille de l'image (px)", + "avatarImageSizeDes": "L'image de profil sélectionnée sera redimensionnée à la taille donnée, en pixels.", + "filePreview": "Aperçu de fichier", + "thumbnails": "Miniatures", + "thumbnailDoc": "Pour plus d'informations sur les miniatures, voir le <0>document.", + "thumbnailDocLink": "https://docs.cloudreve.org/usage/thumbnails", + "thumbnailBasic": "Basique", + "generators": "Générateurs de miniatures", + "thumbMaxSize": "Taille maximale du fichier original", + "thumbMaxSizeDes": "La taille maximale du fichier original pour laquelle des miniatures peuvent être générées, les miniatures ne seront pas générées si les fichiers dépassent cette taille.", + "generatorProxyWarning": "Par défaut, les politiques de stockage non locales n'utiliseront que le générateur \"Natif dans la politique de stockage\". Vous pouvez étendre la capacité de miniatures des politiques de stockage tierces en activant la fonctionnalité \"Proxy de générateur\" dans la page de paramètres de la politique de stockage. Pour plus de détails, veuillez consulter la <0>documentation.", + "policyBuiltin": "Natif dans la politique de stockage", + "policyBuiltinDes": "Utiliser l'API native du fournisseur de stockage pour traiter les miniatures. Pour les politiques locales et S3, ce générateur n'est pas disponible et basculera automatiquement vers d'autres générateurs. Pour les autres politiques de stockage, veuillez aller à la page de paramètres de la politique de stockage pour configurer ce générateur.", + "cloudreveBuiltin": "Intégré à Cloudreve", + "cloudreveBuiltinDes": "Seules les images aux formats PNG, JPEG, GIF sont prises en charge en utilisant les capacités de traitement d'images intégrées de Cloudreve.", + "libreOffice": "LibreOffice", + "libreOfficeDes": "Utiliser LibreOffice pour générer des miniatures pour les documents Office. Ce générateur dépend de tout autre générateur de miniatures d'images (intégré à Cloudreve ou VIPS).", + "libraw": "LibRaw / DCRaw", + "librawDes": "Utiliser le programme d'exemple DCRaw de LibRaw, ou l'exécutable DCRaw original pour générer des miniatures pour les images RAW.", + "vips": "VIPS", + "vipsDes": "Utiliser libvips pour traiter les images miniatures, prend en charge plus de formats d'image, et consomme moins de ressources.", + "thumbDependencyWarning": "LibreOffice ou le générateur de pochette musicale dépendent des générateurs intégrés à Cloudreve ou VIPS, veuillez en activer un.", + "ffmpeg": "FFmpeg", + "ffmpegDes": "Utiliser FFmpeg pour générer des miniatures vidéo.", + "executable": "Exécutable", + "executableDes": "Le chemin ou la commande de l'exécutable du générateur tiers.", + "executableTest": "Test", + "executableTestSuccess": "Le générateur fonctionne, version : {{version}}", + "generatorExts": "Extensions disponibles", + "generatorExtsDes": "Liste des extensions de fichier disponibles pour ce générateur, veuillez utiliser la virgule , pour séparer plusieurs extensions.", + "ffmpegSeek": "Emplacement de capture de miniature", + "ffmpegSeekDes": "Définir le temps d'interception de miniature, il est recommandé de choisir une valeur plus petite pour accélérer le processus de génération. Si la longueur réelle de la vidéo est dépassée, la génération de miniature échouera.", + "ffmpegExtraArgs": "Arguments d'entrée supplémentaires", + "ffmpegExtraArgsDes": "Arguments d'entrée supplémentaires pour appeler FFmpeg.", + "generatorProxy": "Proxy de générateur", + "enableThumbProxy": "Utiliser le proxy de générateur", + "proxyPolicyList": "Politique de stockage activée", + "proxyPolicyListDes": "Multi-sélectionnable. Si activé, les fichiers dont la politique de stockage ne prend pas en charge la génération native, ses miniatures seront générées par proxy par Cloudreve.", + "thumbWidth": "Largeur maximale", + "thumbHeight": "Hauteur maximale", + "thumbSuffix": "Suffixe du fichier Blob", + "thumbSuffixDes": "Le suffixe ajouté au nom du fichier Blob original pour la miniature générée, ", + "thumbFormat": "Format d'image", + "thumbFormatDes": "Format d'image préféré, si le générateur ne le prend pas en charge, il se dégradrera automatiquement au format jpg.", + "thumbQuality": "Qualité", + "thumbQualityDes": "Pourcentage de qualité de compression, valide uniquement pour l'encodage jpg et webp. ", + "thumbGC": "Exécuter GC après génération de miniature", + "captcha": "Captcha", + "captchaType": "Type de captcha", + "captchaTypeDes": "Sélectionner le type de captcha et le fournisseur.", + "plainCaptcha": "Graphique simple", + "reCaptchaV2": "reCAPTCHA V2", + "turnstile": "Cloudflare Turnstile", + "turnstileSiteKey": "Clé de site", + "turnstileSiteKSecret": "Secret", + "cap": "Cap", + "capInstanceURL": "URL de l'instance", + "capInstanceURLDes": "L'URL de votre serveur Cap auto-hébergé. Pour plus de détails, voir la <0>documentation du mode autonome.", + "capSiteKey": "Clé de site", + "capSiteKeyDes": "La clé de site de votre tableau de bord du serveur Cap.", + "capSecretKey": "Clé secrète", + "capSecretKeyDes": "La clé secrète de votre tableau de bord du serveur Cap.", + "capAssetServer": "Source du serveur d'assets", + "capAssetServerDes": "Choisir la source pour charger les assets statiques du captcha Cap. L'utilisation d'un serveur auto-déployé nécessite de définir des variables d'environnement côté serveur, veuillez vous référer à <0>activer le serveur d'assets.", + "capAssetServerJsdelivr": "CDN jsDelivr", + "capAssetServerUnpkg": "CDN unpkg", + "capAssetServerInstance": "Serveur auto-hébergé", + "captchaProvider": "Fournisseur de captcha", + "captchaWidth": "Largeur", + "captchaHeight": "Hauteur", + "captchaLength": "Longueur", + "captchaLengthDes": "La longueur des caractères dans le captcha.", + "captchaMode": "Mode", + "captchaModeNumber": "Chiffres", + "captchaModeLetter": "Lettres", + "captchaModeMath": "Mathématiques", + "captchaModeNumberLetter": "Chiffres + Lettres", + "captchaElement": "Éléments à l'intérieur de l'image captcha.", + "complexOfNoiseText": "Complexité du texte de bruit", + "complexOfNoiseDot": "Complexité des points de bruit", + "showHollowLine": "Afficher les lignes creuses", + "showNoiseDot": "Afficher les points de bruit", + "showNoiseText": "Afficher le texte de bruit", + "showSlimeLine": "Afficher les lignes ondulées", + "showSineLine": "Afficher les lignes sinusoïdales", + "siteKey": "Clé de site", + "siteKeyDes": "Vous pouvez la trouver sur la <0>Page de gestion des applications.", + "siteSecret": "Secret", + "siteSecretDes": "Vous pouvez le trouver sur la <0>Page de gestion des applications.", + "secretID": "SecretId", + "secretIDDes": "Vous pouvez le trouver sur la <0>Page de gestion des accès.", + "secretKey": "SecretKey", + "secretKeyDes": "Vous pouvez la trouver sur la <0>Page de gestion des accès.", + "tCaptchaAppID": "APPID", + "tCaptchaAppIDDes": "Vous pouvez le trouver sur la <0>Page de gestion des captchas.", + "tCaptchaSecretKey": "Clé secrète de l'app", + "tCaptchaSecretKeyDes": "Vous pouvez la trouver sur la <0>Page de gestion des captchas.", + "staticResourceCache": "Cache des ressources statiques publiques", + "staticResourceCacheDes": "Âge maximum du cache pour les ressources statiques publiquement accessibles (ex. lien de source de politique locale, lien de téléchargement).", + "creditSystem": "Système de crédits", + "creditAndVAS": "Crédits et VAS", + "enableCredit": "Activer le système de crédits", + "enableCreditDes": "Activer le système de crédits pour permettre aux utilisateurs de fixer des prix pour leurs liens de partage.", + "creditPrice": "Prix des crédits", + "creditPriceDes": "Prix pour recharger les points de crédit avec de l'argent (en unité monétaire minimale). Mettre 0 pour désactiver la recharge de crédits.", + "shareScoreRate": "Taux de commission du propriétaire du partage", + "shareScoreRateDes": "Pourcentage (1-100) des points de crédit que les propriétaires de partage reçoivent lorsque leurs liens de partage sont achetés.", + "cronNotifyUser": "Intervalle de scan pour les utilisateurs dépassant les limites", + "cronNotifyUserDes": "Scanner et envoyer des rappels par e-mail aux utilisateurs dépassant les limites, ", + "cronBanUser": "Planification de bannissement des utilisateurs", + "cronBanUserDes": "Scanner et bannir les utilisateurs dépassant les limites de stockage et les périodes tampon", + "anonymousPurchase": "Achat anonyme", + "anonymousPurchaseDes": "Permettre aux utilisateurs non connectés d'acheter directement des liens de partage", + "shopNavEnabled": "Afficher la navigation Boutique", + "shopNavEnabledDes": "Afficher les éléments 'Boutique' dans la navigation de la barre latérale", + "paymentSettings": "Paramètres de paiement", + "currencyCode": "Code de devise", + "currencyCodeDes": "Code de devise à trois lettres (ex. USD, CNY, EUR)", + "currencySymbol": "Symbole de devise", + "currencySymbolDes": "Symbole de devise à afficher (ex. $, Â¥, €)", + "currencyUnit": "Unité monétaire", + "currencyUnitDes": "Unité monétaire minimale (ex. 100 pour dollars/centimes)", + "paymentProviders": "Fournisseurs de paiement", + "providerName": "Nom du fournisseur, utilisé pour l'affichage aux utilisateurs.", + "providerType": "Type de fournisseur", + "providerKey": "Clé secrète", + "selectCurrency": "Sélectionner une devise commune", + "addPaymentProvider": "Ajouter un fournisseur de paiement", + "stripeProvider": "Stripe", + "weixinProvider": "WeChat Pay", + "alipayProvider": "Alipay", + "customProvider": "Fournisseur de paiement personnalisé", + "customProviderDes": "Créer un plugin pour se connecter à d'autres passerelles de paiement, voir la <0>documentation pour plus de détails.", + "providerKeyDes": "Clé secrète API de Stripe.", + "storageProductSettings": "Produit de stockage", + "storageProductsDes": "Configurer les produits que les utilisateurs peuvent acheter pour étendre leur espace de stockage.", + "addStorageProduct": "Ajouter un SKU", + "editStorageProduct": "Modifier le SKU", + "storageSize": "Taille de stockage", + "storageSizeBytes": "Taille incluse dans ce SKU", + "duration": "Durée", + "durationSeconds": "Durée en secondes (ex. 2592000 pour 30 jours)", + "price": "Prix", + "priceInUnits": "Prix (en unité monétaire minimale)", + "priceInUnitsDes": "Le prix sera affiché comme :", + "chipLabel": "Étiquette (optionnel)", + "chipLabelHelp": "Une courte étiquette de texte affichée à côté du nom du produit", + "usePoints": "Permettre le paiement avec des points", + "points": "Points", + "pointsHelp": "Nombre de points requis pour acheter ce produit", + "pointsUnit": "points", + "groupProductSettings": "Produit de groupe", + "groupProductsDes": "Configurer les produits que les utilisateurs peuvent acheter pour rejoindre des groupes d'utilisateurs spécifiques.", + "addGroupProduct": "Ajouter un produit de groupe", + "editGroupProduct": "Modifier le produit de groupe", + "groupId": "ID du groupe", + "groupIdHelp": "Le groupe d'utilisateurs vers lequel passer après l'achat de ce produit.", + "description": "Description", + "descriptionHelp": "Entrez les fonctionnalités ou avantages, un par ligne", + "receiptEmailTemplate": "Modèle d'e-mail de reçu de paiement", + "receiptEmailTemplateDes": "Modèle d'e-mail envoyé aux utilisateurs lorsqu'un paiement est confirmé.", + "activationEmailTemplate": "Modèle d'activation de compte", + "activationEmailTemplateDes": "Modèle d'e-mail envoyé aux utilisateurs pour activer leurs comptes.", + "quotaExceededEmailTemplate": "Modèle de dépassement de quota de stockage", + "quotaExceededEmailTemplateDes": "Modèle d'e-mail envoyé aux utilisateurs lorsqu'ils dépassent leur quota de stockage.", + "resetPasswordEmailTemplate": "Modèle de réinitialisation de mot de passe", + "resetPasswordEmailTemplateDes": "Modèle d'e-mail envoyé aux utilisateurs lorsqu'ils demandent une réinitialisation de mot de passe.", + "preferredLanguage": "Langue préférée", + "setAsPreferredLanguage": "Définir comme langue préférée", + "setAsPreferredLanguageDes": "Si la préférence de langue de l'utilisateur ne peut pas être obtenue, le modèle d'e-mail de la langue préférée sera utilisé.", + "alreadyAsPreferredLanguageDes": "La langue actuelle est déjà définie comme langue préférée. Si la préférence de langue de l'utilisateur ne peut pas être obtenue, ce modèle d'e-mail sera utilisé.", + "addLanguage": "Ajouter une langue", + "removeLanguage": "Supprimer la langue", + "removeLanguageBtn": "Supprimer la langue", + "cannotRemovePreferredLanguageDes": "Impossible de supprimer la langue préférée. Veuillez définir une autre langue comme langue préférée et réessayer.", + "languageCodeDes": "Veuillez sélectionner la langue que vous souhaitez ajouter.", + "emailSubject": "Sujet de l'e-mail", + "emailSubjectDes": "La ligne d'objet de l'e-mail. Vous pouvez utiliser des <0>variables magiques pour personnaliser le sujet de l'e-mail.", + "emailBody": "Corps de l'e-mail", + "emailBodyDes": "Contenu HTML de l'e-mail. Vous pouvez utiliser des <0>variables magiques pour personnaliser le contenu de l'e-mail.", + "orderTitle": "Titre de la commande", + "themeOptions": "Options de thème", + "themeOptionsDes": "Configurer les options de thème personnalisées pour votre site. Ces thèmes seront disponibles pour les utilisateurs dans leurs préférences.", + "primaryColor": "Couleur primaire", + "secondaryColor": "Couleur secondaire", + "primaryColorDark": "Couleur primaire (Sombre)", + "secondaryColorDark": "Couleur secondaire (Sombre)", + "addThemeOption": "Ajouter une option de thème", + "editThemeOption": "Modifier l'option de thème", + "invalidThemeConfig": "Configuration de thème invalide. Veuillez vérifier votre syntaxe JSON.", + "themeConfiguration": "Configuration du thème", + "themePreview": "Aperçu du thème", + "lightTheme": "Thème clair", + "darkTheme": "Thème sombre", + "previewTitle": "Titre d'aperçu", + "previewTextField": "Champ de saisie", + "previewPrimary": "Primaire", + "invalidThemePreview": "Configuration de thème invalide pour l'aperçu", + "duplicateThemeColor": "Un thème avec cette couleur primaire existe déjà. Veuillez choisir une couleur différente.", + "themeDes": "Les configurations complètes disponibles peuvent être consultées sur <0>Visualiseur de thème par défaut - Material-UI.", + "defaultTheme": "Par défaut", + "auditLog": "Événements", + "auditLogDes": "Configurer quels événements doivent être enregistrés. Certains événements peuvent être utilisés par le système pour fournir des fonctionnalités supplémentaires, par ex. activité de fichier et activité de connexion.", + "systemEvents": "Événements système", + "systemEventsDes": "Événements liés aux opérations et au statut du système.", + "userEvents": "Événements utilisateur", + "userEventsDes": "Événements liés aux comptes utilisateur, à l'authentification et aux changements de profil.", + "fileEvents": "Événements de fichier", + "fileEventsDes": "Événements liés aux opérations de fichier telles que téléchargement, téléchargement et modification.", + "shareEvents": "Événements de partage", + "shareEventsDes": "Événements liés au partage de fichiers et à l'accès aux liens.", + "versionEvents": "Événements de version", + "versionEventsDes": "Événements liés à la gestion des versions de fichier.", + "mediaEvents": "Événements média", + "mediaEventsDes": "Événements liés au traitement des médias tels que la génération de miniatures.", + "filesystemEvents": "Événements de système de fichiers", + "filesystemEventsDes": "Événements liés aux opérations de système de fichiers telles que montage et gestion d'archives.", + "webdavEvents": "Événements WebDAV", + "webdavEventsDes": "Événements liés à la gestion et à l'accès des comptes WebDAV.", + "paymentEvents": "Événements de paiement", + "paymentEventsDes": "Événements liés aux paiements, points et gestion d'adhésion.", + "emailEvents": "Événements e-mail", + "emailEventsDes": "Événements liés à l'envoi d'e-mails et aux notifications.", + "toggleAll": "Basculer tout", + "toggleAllDes": "Activer ou désactiver tous les événements de cette catégorie.", + "event": { + "file_imported": "Fichier externe importé", + "server_start": "Démarrage du serveur", + "user_signup": "Inscription utilisateur", + "email_sent": "E-mail envoyé", + "user_activated": "Utilisateur activé", + "user_login_failed": "Échec de connexion", + "user_login": "Connexion utilisateur", + "user_token_refresh": "Actualisation du token", + "file_create": "Fichier créé", + "file_rename": "Fichier renommé", + "set_file_permission": "Permission modifiée", + "entity_uploaded": "Fichier téléchargé ou mis à jour", + "entity_downloaded": "Fichier téléchargé", + "copy_from": "Copier depuis", + "copy_to": "Copier vers", + "move_to": "Déplacer vers", + "delete_file": "Fichier supprimé", + "move_to_trash": "Déplacer vers la corbeille", + "share": "Partage créé", + "share_link_viewed": "Lien de partage consulté", + "set_current_version": "Définir la version actuelle", + "delete_version": "Supprimer la version", + "thumb_generated": "Miniature générée", + "live_photo_uploaded": "Live photo téléchargée", + "update_metadata": "Métadonnées mises à jour", + "edit_share": "Partage modifié", + "delete_share": "Partage supprimé", + "mount": "Montage", + "relocate": "Relocaliser", + "create_archive": "Créer une archive", + "extract_archive": "Extraire l'archive", + "webdav_login_failed": "Échec de connexion WebDAV", + "webdav_account_create": "Compte WebDAV créé", + "webdav_account_update": "Compte WebDAV mis à jour", + "webdav_account_delete": "Compte WebDAV supprimé", + "payment_created": "Paiement créé", + "points_change": "Points modifiés", + "payment_paid": "Paiement effectué", + "payment_fulfilled": "Commande honorée", + "payment_fulfill_failed": "Échec d'exécution de commande", + "storage_added": "Stockage ajouté", + "group_changed": "Groupe modifié", + "user_exceed_quota_notified": "Notification de dépassement de quota", + "user_changed": "Statut utilisateur modifié", + "get_direct_link": "Obtenir le lien direct", + "link_account": "Lier un compte externe", + "unlink_account": "Délier un compte externe", + "change_nick": "Changer le pseudonyme", + "change_avatar": "Changer l'avatar", + "membership_unsubscribe": "Désabonnement d'adhésion", + "change_password": "Changer le mot de passe", + "enable_2fa": "Activer 2FA", + "disable_2fa": "Désactiver 2FA", + "add_passkey": "Ajouter une clé d'accès", + "remove_passkey": "Supprimer la clé d'accès", + "redeem_gift_code": "Utiliser le code cadeau", + "update_view": "Paramètre de vue modifié", + "delete_direct_link": "Supprimer le lien direct", + "report_abuse": "Signaler un abus" + }, + "server": "Serveur", + "tempPath": "Chemin temporaire", + "tempPathDes": "Le répertoire pour stocker les fichiers temporaires, relatif au répertoire de données Cloudreve. Veuillez vous assurer qu'aucune tâche de file d'attente n'est en cours d'exécution avant de le modifier.", + "siteID": "ID du site", + "siteIDDes": "Un ID unique pour identifier le site, généralement pas besoin d'être modifié.", + "siteSecretKey": "Clé maître", + "siteSecretKeyDes": "La clé maître utilisée pour chiffrer les tokens utilisateur et les signatures. Après rotation, tous les tokens utilisateur et signatures seront invalides. Prend effet après redémarrage de Cloudreve.", + "rotateSecretKey": "Faire tourner la clé maître", + "hashidSalt": "Sel HashID", + "hashidSaltDes": "La valeur de sel utilisée pour générer HashID. Soyez prudent lors du changement, car cela invalidera les liens directs et liens de partage existants.", + "accessTokenTTL": "TTL du token d'accès", + "accessTokenTTLDes": "La TTL des tokens d'accès, en secondes.", + "refreshTokenTTL": "TTL du token de rafraîchissement", + "refreshTokenTTLDes": "La TTL des tokens de rafraîchissement, en secondes. Cela affecte la durée du statut de connexion utilisateur.", + "cronGarbageCollect": "Intervalle de scan de collecte des déchets", + "cronGarbageCollectDes": "Définir la fréquence de scan et recyclage des données expirées dans les fichiers temporaires et le stockage KV.", + "startWithProtocol": "Doit commencer par http:// ou https://", + "tlsWarning": "Le site actuel utilise https, remplir une URL http ici peut causer des exceptions.", + "blobUrlCache": "Cache d'URL Blob", + "clearBlobUrlCache": "Vider le cache d'URL Blob", + "clearBlobUrlCacheDes": "Pour augmenter le taux de réussite du cache, Cloudreve met en cache et réutilise les URL Blob. Lorsque l'adresse CDN ou d'autres paramètres changent, veuillez vider le cache.", + "cacheCleared": "Cache vidé." + }, + "giftCodes": { + "giftCodesSettings": "Codes cadeaux", + "generateGiftCodes": "Générer des codes cadeaux", + "giftCodeQuantity": "Quantité", + "giftCodeQuantityHelp": "Nombre de codes cadeaux à générer", + "giftCodeProductType": "Type de produit", + "giftCodeTypePoints": "Points", + "giftCodeTypeStorage": "Stockage", + "giftCodeTypeGroup": "Groupe", + "giftCodePointsAmount": "Montant de points", + "giftCodePointsAmountHelp": "Nombre de points à créditer lors de l'utilisation du code", + "giftCodeProduct": "Produit", + "selectStorageProduct": "Sélectionner un produit de stockage", + "selectGroupProduct": "Sélectionner un produit de groupe", + "giftCodeType": "Type", + "giftCodeAmount": "Montant", + "giftCode": "Code cadeau", + "giftCodeStatus": "Statut", + "giftCodeUsedBy": "Utilisé par", + "giftCodeUsed": "Utilisé", + "giftCodeUnused": "Disponible", + "giftCodeDeleted": "Code cadeau supprimé avec succès", + "giftCodesGenerated": "Codes cadeaux générés avec succès", + "noGiftCodes": "Aucun code cadeau disponible", + "generatedCodesTitle": "Codes cadeaux générés", + "generatedCodesDescription": "Copiez ces codes cadeaux pour les partager avec les utilisateurs. Chaque code ne peut être utilisé qu'une fois.", + "copyAndClose": "Copier et fermer", + "duratonTimes": "Quantité", + "duratonTimesDes": "Combien de quantités du produit sont incluses dans chaque code cadeau.", + "unknownProduct": "Produit inconnu" + }, + "policy": { + "acceleratedDomainUpload": "Utiliser le domaine d'accélération de transfert pour l'upload", + "acceleratedDomainUploadDes": "Lorsqu'activé, le <0>domaine d'accélération de transfert de Qiniu sera utilisé lors de l'upload de fichiers.", + "compare": "Comparer", + "deletePolicyConfirmation": "Êtes-vous sûr de vouloir supprimer la politique de stockage {{name}} ?", + "streamSaver": "Téléchargement via le navigateur", + "streamSaverDes": "Lorsqu'activé, les demandes de téléchargement des utilisateurs seront traitées par le navigateur. En raison des limitations de la politique de stockage OneDrive, le nom du fichier téléchargé directement par les utilisateurs ne peut pas être identique au nom du fichier dans Cloudreve, utiliser le navigateur pour gérer les téléchargements peut résoudre ce problème.", + "oauthCallbackFailed": "Échec de l'autorisation", + "httpsRequired": "L'application Entra ID nécessite une URL de redirection HTTPS, mais le site actuel utilise HTTP, ce qui peut causer un échec de redirection après connexion, veuillez remplacer manuellement le HTTPS par HTTP dans la barre d'adresse du navigateur.", + "authorizeMicrosoft": "Se connecter avec Microsoft", + "redirectUrl": "URL de redirection", + "redirectUrlDes": "L'affichage actuel est la dernière URL de redirection qui répond aux exigences. Veuillez confirmer si l'URL de redirection dans les paramètres de l'application est cohérente avec celle-ci.", + "authorizeOneDrive": "Confirmer les paramètres de l'application Entra ID", + "authorizeOneDriveDes": "Veuillez confirmer si les informations suivantes de l'application Entra ID sont toujours valides. Si nécessaire, veuillez apporter des modifications.", + "authorizeNow": "Autoriser", + "authorizeAgain": "Autoriser à nouveau", + "notGranted": "Aucun compte autorisé, la politique de stockage ne peut pas être utilisée.", + "granted": "Compte autorisé, informations d'identification actualisées le <0>{{time}}.", + "grantedNotRefresh": "Compte autorisé, informations d'identification non actualisées depuis le dernier démarrage.", + "batchDeleteSize": "Taille maximale de suppression en lot", + "batchDeleteSizeDes": "Limite le nombre maximal de fichiers pouvant être supprimés dans une seule requête API. Ce paramètre n'affectera pas la suppression de fichiers en lot par les utilisateurs. Si non rempli, la valeur par défaut <0>1000 sera utilisée. C'est la valeur maximale autorisée pour l'API S3 officielle.", + "bucketPolicy": "Politique de bucket", + "cdnOrCustomDomain": "CDN ou CNAME personnalisé", + "bucketDomain": "Domaine du bucket", + "bucketDomainDes": "Remplissez le domaine accéléré par CDN ou le domaine CNAME personnalisé que vous avez lié pour le bucket de stockage.", + "storageNodeInternal": "NÅ“ud de stockage (Endpoint Intranet)", + "chunkSizeDesOssObs": "Plage autorisée : 100 KB ~ 5 GB.", + "chunkSizeDesQiniuCos": "Plage autorisée : 1 MB ~ 1 GB.", + "chunkSizeDesS3": "Plage autorisée : 5 MB ~ 5 GB.", + "thisIsACustomDomain": "Ceci est un domaine personnalisé", + "thisIsACustomDomainDes": "Si vous avez lié un domaine personnalisé au bucket de stockage et devez gérer le bucket via le domaine personnalisé, veuillez cocher cette option. Une fois activé, Cloudreve ne tentera pas d'ajouter le nom du Bucket dans le domaine de la requête.", + "addedManually": "Je l'ai configuré manuellement", + "origin": "Origine", + "allowMethods": "Méthodes autorisées", + "exposeHeaders": "En-têtes exposés", + "allowHeaders": "En-têtes autorisés", + "maxAge": "Âge maximum", + "accessCredential": "Informations d'identification d'accès", + "downloadTrafficDiagram": "Démonstration du chemin de trafic de téléchargement", + "downloadRelay": "Relais de téléchargement", + "downloadRelayDes": "Lorsqu'activé, les demandes de téléchargement des utilisateurs seront proxifiées par Cloudreve.", + "download": "Téléchargement", + "downloadCdn": "CDN de téléchargement", + "useDownloadCdn": "Utiliser le CDN pour le trafic de téléchargement", + "skipSign": "Ignorer la signature d'URL pour le CDN", + "skipSignDes": "Si vous avez activé \"Utiliser l'auth source\" pour ce domaine dans les paramètres du bucket, veuillez cocher cette option.", + "cdnHost": "Hôte CDN", + "downloadCdnDes": "L'hôte, le protocole et le port de l'URL que les utilisateurs utilisent pour accéder aux fichiers seront remplacés par l'hôte CDN que vous avez spécifié.", + "mediaExtractorProxy": "Proxy d'extraction de médias", + "mediaExtractorProxyDes": "Activez cette fonctionnalité pour extraire les métadonnées de médias des fichiers qui ne sont pas pris en charge par les extracteurs natifs du fournisseur de stockage. Veuillez configurer l'extracteur de médias dans <0>Traitement de médias.", + "mediaExtractorNative": "extracteurs natifs", + "mediaExtractorOss": "Intelligent Media Management (IMM)", + "mediaExtractorQiniu": "Qiniu DORA", + "mediaExtractorCos": "Traitement de données Tencent Cloud", + "mediaExtractorObs": "service de traitement d'images", + "nativeMediaMetaExts": "Extensions de fichiers activées pour <0>{{name}}", + "nativeMediaMetaExtsGeneralDes": "Séparées par des virgules, une valeur vide signifie désactiver <0>{{name}}.", + "nativeMediaMetaExtsRemote": "Pour le stockage esclave, le support par défaut est EXIF et les métadonnées musicales, vous pouvez remplacer ceci en configurant le nÅ“ud esclave avec plus d'extracteurs.", + "nativeMediaMetaExtOss": "Le service Intelligent Media Management (IMM) prend en charge le traitement audio, vidéo et d'images. Le traitement d'images ne nécessite pas de configuration manuelle, mais si vous devez traiter de l'audio ou de la vidéo, vous devez activer manuellement IMM et le lier au Bucket, veuillez vous référer à la <0>documentation pour la liaison. Après la liaison, veuillez ajouter les extensions que vous souhaitez traiter dans le champ ci-dessus.", + "nativeMediaMetaExtQiniu": "Le service Qiniu DORA prend en charge le traitement d'audio, vidéo et d'images courants, aucune configuration supplémentaire n'est requise, veuillez remplir les extensions que vous souhaitez traiter ci-dessus.", + "nativeMediaMetaExtCos": "Le service de traitement de données Tencent Cloud prend en charge le traitement audio, vidéo et d'images. Le traitement d'images ne nécessite pas de configuration manuelle, mais si vous devez traiter de l'audio ou de la vidéo, veuillez d'abord aller sur <0>Traitement de données Tencent Cloud pour activer et lier le bucket de stockage, puis aller dans les paramètres du Bucket - Traitement de médias pour activer le service de traitement d'images. Après la liaison, veuillez ajouter les extensions que vous souhaitez traiter dans le champ ci-dessus.", + "nativeMediaMetaExtObs": "Le service de traitement d'images prend en charge <0>l'extraction EXIF d'images. Aucune configuration manuelle n'est requise, ajoutez simplement les extensions que vous souhaitez traiter ci-dessus.", + "thumbProxy": "Proxy de génération de miniatures", + "thumbProxyDes": "Activez cette fonctionnalité pour générer des miniatures pour les fichiers qui ne répondent pas aux conditions de miniatures natives. Cloudreve essaiera de générer des miniatures et de les télécharger côté stockage. Veuillez configurer le générateur de miniatures dans <0>Traitement de médias.", + "nativeThumbnailMaxSize": "Taille maximale des miniatures natives", + "nativeThumbnailMaxSizeDes": "Entrez 0 pour désactiver la limite de taille, les fichiers plus volumineux que cette taille n'utiliseront pas les miniatures natives.", + "nativeThumbNailsSupportAllExts": "Activer pour toutes les extensions de fichiers", + "nativeThumbNails": "Extensions de fichiers pour les miniatures natives", + "nativeThumbNailsGeneralDes": "Séparées par des virgules, une valeur vide signifie désactiver les miniatures natives, pour les extensions de fichiers listées ci-dessus, Cloudreve utilisera la fonctionnalité de miniatures natives du fournisseur de stockage pour générer des miniatures.", + "nativeThumbNailsGeneralRemote": "Pour le stockage esclave, le support intégré est constitué de miniatures simples d'images et de couvertures musicales, vous pouvez remplacer ceci en configurant le nÅ“ud esclave avec plus de générateurs.", + "nativeThumbNailsGeneralOss": "Pour le stockage Alibaba Cloud OSS, le service de <0>traitement d'images sera utilisé pour générer des miniatures.", + "nativeThumbNailsGeneralQiniu": "Pour le stockage Qiniu Cloud, le service de <0>traitement d'images de base (imageView2) sera utilisé pour générer des miniatures.", + "nativeThumbNailsGeneralCos": "Pour le stockage Tencent Cloud COS, le service de <0>traitement de données Tencent Cloud sera utilisé pour générer des miniatures.", + "nativeThumbNailsGeneralObs": "Pour le stockage Huawei Cloud OBS, le service de <0>traitement d'images sera utilisé pour générer des miniatures.", + "nativeThumbNailsGeneralUpyun": "Pour le stockage Upyun, le service de <0>traitement d'images sera utilisé pour générer des miniatures.", + "preallocate": "Pré-allouer l'espace disque", + "preallocateDes": "Lorsqu'activé, la demande d'upload de l'utilisateur pré-allouera l'espace disque sur le nÅ“ud de stockage, et prend également en charge le téléchargement parallèle de chunks. Efficace seulement sur Linux ou Darwin.", + "chunkConcurrency": "Uploads de chunks concurrents", + "chunkConcurrencyDes": "Définit le nombre d'uploads de chunks concurrents lors de l'utilisation de l'upload direct web.", + "sourceWebEdit": "Édition en ligne Web", + "uploadRelay": "Relais d'upload", + "uploadRelayDes": "Si activé, les demandes d'upload des utilisateurs seront relayées vers le nÅ“ud de stockage via Cloudreve, en raison de l'incapacité à effectuer des uploads par chunks, veuillez ajuster la limite de taille d'upload maximale du serveur web en conséquence.", + "customProxy": "Proxy personnalisé", + "storageNode": "Fournisseur de stockage", + "sourceWeb": "Web / Application officielle", + "sourceDav": "WebDAV", + "uploadTrafficDiagram": "Démonstration du chemin de trafic d'upload", + "node": "NÅ“ud de stockage", + "nodeDes": "Veuillez sélectionner un nÅ“ud esclave pour le stockage de fichiers, vous pouvez créer ou gérer les nÅ“uds de stockage esclaves dans <0>Liste des nÅ“uds.", + "noBindedGroupWarning": "La politique de stockage actuelle n'est liée à aucun groupe d'utilisateurs, veuillez aller dans <0>Liste des groupes pour lier la politique de stockage actuelle à un groupe d'utilisateurs.", + "nameRuleImmutable": "La modification des paramètres n'affectera pas les fichiers existants dans la politique de stockage. Le chemin Blob est fixe après la création, même si les variables magiques qu'il contient changent, le chemin ne sera pas mis à jour.", + "uniqueVarRequired": "Veuillez inclure au moins une variable unique dans le chemin du répertoire ou le nom du blob : {uuid}, {randomkey8}, {randomkey16}.", + "storageAndUpload": "Stockage et Upload", + "blobFolderNaming": "Répertoire de stockage Blob", + "blobFolderNamingDes": "Le répertoire où les Blobs de fichiers sont stockés, vous pouvez utiliser des <0>variables magiques.", + "blobNameDes": "Le nom du Blob de fichier, vous pouvez utiliser des <0>variables magiques, assurez-vous qu'il soit absolument unique, même pour plusieurs uploads du même nom de fichier dans le même chemin en peu de temps.", + "blobName": "Nom du Blob", + "basicInfo": "Informations de base", + "editX": "Modifier {{name}}", + "noGroupBinded": "Aucun groupe lié", + "create": "Créer", + "addXStoragePolicy": "Ajouter une politique de stockage {{type}}", + "loadSummary": "Charger le résumé", + "policySummary": "{{count}} Blobs de fichiers ({{size}})", + "sharp": "#", + "name": "Nom", + "type": "Type", + "childFiles": "Fichiers enfants", + "totalSize": "Taille totale", + "actions": "Actions", + "authSuccess": "Autorisation accordée.", + "policyDeleted": "Politique supprimée.", + "newStoragePolicy": "Nouvelle politique de stockage", + "all": "Tous", + "local": "Local", + "remote": "NÅ“ud distant", + "qiniu": "Qiniu", + "upyun": "Upyun", + "oss": "Alibaba Cloud OSS", + "cos": "Tencent Cloud COS", + "onedrive": "OneDrive", + "s3": "Compatible S3", + "ks3": "Kingsoft Cloud S3", + "obs": "Huawei Cloud OBS", + "load_balance": "Équilibrage de charge", + "childPolicy": "Politique de stockage enfant", + "childPolicyDes": "Sélectionnez les politiques de stockage enfants à ajouter au pool d'équilibrage de charge.", + "weight": "Poids", + "addTargetPolicy": "Ajouter une politique enfant", + "selectPolicies": "Sélectionner les politiques", + "selectPoliciesDes": "Sélectionnez les politiques de stockage à ajouter au pool d'équilibrage de charge.", + "loadBalanceDes": "Lors de l'utilisation de la politique de stockage à équilibrage de charge, les nouveaux uploads seront distribués aléatoirement vers différentes politiques de stockage enfants basées sur le poids.", + "xChildPolicies": "{{count}} politiques de stockage enfants", + "refresh": "Actualiser", + "delete": "Supprimer", + "edit": "Modifier", + "selectAStorageProvider": "Sélectionner un fournisseur de stockage", + "maxSizeOfSingleFile": "Taille maximale d'un seul fichier", + "maxSizeOfSingleFileDes": "Entrez 0 pour désactiver la limite.", + "enterFileExt": "Séparées par des virgules, laissez vide pour autoriser toutes les extensions de fichiers.", + "extList": "Restrictions d'extension de fichiers", + "noLimit": "Aucune limite", + "whitelist": "Autoriser", + "blacklist": "Refuser", + "fileNameRegex": "Règles regex de nom de fichier", + "fileNameRegexDes": "Expression régulière pour correspondre aux noms de fichiers, laissez vide pour aucune restriction.", + "chunkSizeDes": "Spécifiez la taille de chunk pour les uploads par chunks. Une valeur de 0 signifie qu'aucun upload par chunks n'est utilisé, mais la taille d'upload maximale peut être limitée par le serveur web.", + "chunkSizeDesSuffix": "{{prefix}} Avec l'upload par chunks, les fichiers uploadés par les utilisateurs seront découpés en chunks et uploadés vers le côté stockage un par un. Après l'interruption de l'upload, les utilisateurs peuvent choisir de continuer l'upload depuis le dernier chunk uploadé.", + "chunkSize": "Taille de chunk", + "policyName": "Le nom d'affichage de la politique de stockage, également utilisé pour être présenté aux utilisateurs.", + "magicVar": { + "fileNameMagicVar": "Variables magiques de nom de fichier", + "pathMagicVar": "Variables magiques de chemin", + "variable": "Variable", + "description": "Description", + "example": "Exemple", + "16digitsRandomString": "Chaîne aléatoire de 16 chiffres", + "8digitsRandomString": "Chaîne aléatoire de 8 chiffres", + "secondTimestamp": "Horodatage", + "nanoTimestamp": "Horodatage nano", + "uid": "ID utilisateur", + "originalFileName": "Nom de fichier original", + "originFileNameNoext": "Nom de fichier original sans extension", + "extension": "Nom d'extension de fichier", + "uuidV4": "UUID V4", + "date": "Date", + "dateAndTime": "Date et heure", + "randomNumber": "Nombre aléatoire dans la plage", + "year": "Année", + "month": "Mois", + "day": "Jour", + "hour": "Heure", + "minute": "Minute", + "second": "Seconde", + "path": "Le chemin initial lors de l'upload du fichier par l'utilisateur" + }, + "storageBucket": "Bucket de stockage", + "wanSiteURLDes": "Avant d'utiliser cette politique, assurez-vous que l'adresse que vous avez saisie dans Paramètres de base - Informations du site - URL du site correspond à l'adresse réelle et <0>peut être accessible correctement par WAN.", + "enterQiniuBucket": "Allez sur <0>tableau de bord Qiniu pour créer un bucket de stockage. Entrez le \"Nom du bucket\" que vous venez de créer.", + "aclType": "Type de contrôle d'accès", + "accessTypePulic": "Lecture publique écriture privée", + "accessTypePrivate": "Lecture/écriture privée", + "accessType": "Type d'accès", + "qiniuBucketName": "Nom du bucket", + "cosObsBucketName": "Nom du bucket", + "bucketType": "ACL du bucket", + "bucketTypeDes": "Sélectionnez le type d'ACL pour le bucket que vous venez de créer.", + "privateBucket": "Privé", + "privateDes": "Cloudreve signera l'URL du fichier.", + "publicBucket": "Lecture publique", + "publicStorage": "Public", + "publicDes": "Non recommandé, Cloudreve retournera directement le lien direct du fichier, ce qui ne peut pas contrôler efficacement l'accès aux fichiers.", + "bucketCDNDes": "Remplissez le nom de domaine accéléré par CDN que vous avez lié pour le bucket de stockage.", + "bucketCDNDomain": "Domaine CDN", + "qiniuCredentialDes": "Allez dans Centre personnel - Gestion des identifiants dans le tableau de bord Qiniu et remplissez les AK, SK obtenus.", + "ak": "AK", + "sk": "SK", + "cannotEnableForPrivateBucket": "Si cette fonctionnalité est activée pour un bucket privé, vous devez activer \"Utiliser le lien source redirigé\" pour les groupes d'utilisateurs.", + "chunkSizeLabelQiniu": "Spécifiez la taille de chunk pour les uploads reprenables. La plage autorisée est de 1 MB - 1 GB.", + "corsSettingStep": "Politique CORS", + "corsPolicyAdded": "La politique CORS est ajoutée.", + "createOSSBucketDes": "Allez sur <0>Tableau de bord OSS pour créer un Bucket. Seules les classes de stockage <1>Standard et <2>IA sont prises en charge.", + "bucketName": "Nom du bucket", + "publicReadBucket": "Lecture publique", + "ossEndpointDes": "Allez sur la page de résumé du Bucket, entrez le <2>Port sous la section <1>Accès via Internet, dans la page <0>Endpoint.", + "ossEndpointDesInternalHint": "Si vous devez configurer un endpoint de domaine intranet ou personnalisé, vous pouvez le définir après avoir créé la politique de stockage.", + "obsEndpointCnameHint": "Si vous devez configurer un endpoint de domaine personnalisé, vous pouvez le définir après avoir créé la politique de stockage.", + "endpoint": "EndPoint", + "ossLANEndpointDes": "Laisser vide signifie ne pas l'utiliser. Si votre Cloudreve est déployé dans les services de calcul Alibaba Cloud qui sont dans la même zone de disponibilité que le bucket OSS, vous pouvez spécifier en plus un endpoint intranet, Cloudreve essaiera d'utiliser cet endpoint côté serveur pour réduire les coûts de trafic.", + "intranetEndPoint": "Endpoint intranet", + "ossCDNDes": "Voulez-vous utiliser Alibaba Cloud CDN pour accélérer l'accès aux fichiers ?", + "createOSSCDNDes": "Allez sur <0>Tableau de bord Alibaba Cloud CDN pour créer un domaine CDN, la source du CDN doit être votre bucket OSS. Entrez le domaine CDN et sélectionnez si vous voulez utiliser HTTPS :", + "ossAKDes": "Obtenez votre AccessKey dans la page <0>Gestion des informations de sécurité. Vous pouvez aussi créer un AccessKey avec la permission <1>AliyunOSSFullAccess dans <2>Contrôle d'accès RAM.", + "shouldNotContainSpace": "Ceci ne peut pas contenir d'espaces.", + "nameThePolicyFirst": "Nommez la politique de stockage :", + "chunkSizeLabelOSS": "Spécifiez la taille de chunk pour les uploads reprenables. La plage autorisée est de 100 KB - 5 GB.", + "ossCORSDes": "Cette politique de stockage nécessite une politique CORS pour permettre l'upload depuis le navigateur. Cloudreve peut la configurer automatiquement pour vous, ou vous pouvez la configurer manuellement en suivant les étapes de la documentation. Si vous avez déjà défini la politique CORS pour ce Bucket, cette étape peut être ignorée.", + "letCloudreveHelpMe": "Laisser Cloudreve la configurer pour moi", + "skip": "Ignorer", + "createUpyunBucketDes": "Remplissez le nom du service de stockage que vous avez créé dans <0>Tableau de bord Upyun.", + "storageServiceName": "Nom du service", + "operatorName": "Nom de l'opérateur", + "operatorPassword": "Mot de passe de l'opérateur", + "tokenStatus": "Anti-hotlinking par token", + "upyunTokenDes": "Il est fortement recommandé d'activer l'Anti-hotlinking par Token, allez dans le panneau <0>Configuration des fonctionnalités du service de stockage créé, allez dans l'onglet <1>Contrôle d'accès, activez l'Anti-hotlinking par Token et définissez un secret.", + "tokenEnabled": "Activer l'Anti-hotlinking par Token", + "tokenDisabled": "Ne pas utiliser l'Anti-hotlinking par Token", + "upyunTokenSecretDes": "Entrez le secret de l'Anti-hotlinking par Token.", + "upyunTokenSecret": "Secret Anti-hotlinking par Token", + "createCOSBucketDes": "Allez sur <0>Tableau de bord COS pour créer un bucket de stockage. Allez sur la page de configuration de base du bucket créé, et copiez le <1>Nom du bucket ci-dessus.", + "obsBucketDes": "Allez sur <0>Tableau de bord OBS pour créer un bucket de stockage. Entrez le <1>Nom du bucket que vous venez de créer. La classe de stockage ne prend en charge que <2>Standard ou <3>Accès peu fréquent.", + "cosPrivateRW": "Lecture/Écriture privée", + "cosPublicRW": "Lecture publique et écriture privée", + "cosAccessDomainDes": "Sur la page de vue d'ensemble du Bucket créé, remplissez le <1>Domaine d'accès donné sous la section <0>Informations de domaine. Vous pouvez aussi utiliser votre domaine CNAME ou domaine d'accélération CDN.", + "obsEndpointDes": "Sur la page de vue d'ensemble du Bucket créé, remplissez l'<1>Endpoint donné sous la section <0>Informations de domaine.", + "accessDomain": "Domaine d'accès", + "cosCDNDomainDes": "Allez sur <0>Console de gestion CDN Tencent Cloud pour créer un domaine d'accélération CDN et définir le site source sur le bucket COS que vous venez de créer. Remplissez le nom de domaine CDN ci-dessous et sélectionnez si vous voulez utiliser HTTPS.", + "cosCredentialDes": "Remplissez les clés d'accès obtenues depuis la page <0>Clés d'accès de Tencent Cloud. Assurez-vous que la paire de clés a l'autorisation d'accès aux services COS. Vous pouvez aussi créer un <2>sous-utilisateur avec l'autorisation <1>Accès programmatique et lui accorder l'accès au service COS.", + "obsCredentialDes": "Remplissez les clés d'accès obtenues depuis la page <0>Clés d'accès de Huawei Cloud. Vous pouvez aussi créer un <2>utilisateur IAM avec l'autorisation <1>Accès programmatique et lui accorder l'autorisation <3>OBS OperateAccess.", + "grantAccess": "Accorder l'accès", + "grantAccessLater": "Après avoir créé la politique de stockage, vous devez vous connecter et accorder l'accès dans la page des paramètres de la politique de stockage.", + "odHttpsWarning": "Vous devez activer HTTPS pour utiliser les politiques de stockage OneDrive/SharePoint ; après activation, assurez-vous de changer Paramètres - Base - Informations du site - URL du site.", + "creatAadAppDes": "Allez sur <0>Tableau de bord Microsoft Entra ID, après vous être connecté, allez dans le panneau d'administration <1>Microsoft Entra ID, vous pouvez optionnellement utiliser un compte différent de celui utilisé pour stocker les fichiers pour vous connecter.", + "createAadAppDes2": "Allez dans le menu <0>Inscriptions d'applications sur la gauche et cliquez sur le bouton <1>Nouvelle inscription. Remplissez le formulaire d'inscription d'application. Assurez-vous que <2>Types de comptes pris en charge est sélectionné comme <3>Comptes dans tout répertoire organisationnel (Tout répertoire Azure AD - Multitenant) et comptes Microsoft personnels (par ex. Skype, Xbox) ; <4>URI de redirection (optionnel) est sélectionné comme <5>Web et remplissez <6>{{url}} ; Pour les autres champs, laissez-les par défaut.", + "entraIdApp": "Informations de l'application Entra ID", + "aadAppIDDes": "Allez sur la page <0>Vue d'ensemble dans Gestion d'application, la valeur de <1>ID d'application (client).", + "aadAppID": "ID d'application (client)", + "addAppSecretDes": "La façon de créer un secret client : Allez dans le menu <0>Certificats et secrets sur la gauche, cliquez sur le bouton <1>Nouveau secret client, et sélectionnez la durée la plus longue pour <2>Expire. Vous devez créer un nouveau secret client après l'expiration de l'ancien, et mettre à jour le nouveau dans les paramètres de la politique de stockage.", + "aadAppSecret": "Secret client", + "aadAccountCloud": "Endpoint Microsoft Graph", + "aadAccountCloudDes": "Veuillez sélectionner l'endpoint selon le type de compte Microsoft 365 que vous utilisez.", + "multiTenant": "Cloud public mondial", + "gallatin": "Cloud chinois 21V", + "sharePointDes": "Voulez-vous stocker les fichiers dans SharePoint ?", + "saveToOneDrive": "Stocker les fichiers dans OneDrive par défaut", + "spSiteURL": "URL du site SharePoint", + "odReverseProxyURLDes": "Voulez-vous utiliser un serveur proxy inverse personnalisé pour le téléchargement de fichiers ?", + "odReverseProxyURL": "URL du serveur proxy inverse", + "chunkSizeDesOd": "Plage autorisée : 5 MB ~ 5GB, OneDrive exige que ce soit un multiple entier de 320 KiB (327 680 octets).", + "limitOdTPSDes": "Limiter la fréquence des requêtes API OneDrive", + "tps": "Limite TPS", + "tpsDes": "Laisser vide pour indiquer aucune limite. Limite cette politique de stockage au nombre maximum de requêtes API envoyées à OneDrive par seconde. Les requêtes qui dépassent cette fréquence seront limitées. Lorsque plusieurs nÅ“uds Cloudreve transfèrent des fichiers, ils utilisent chacun leur propre seau de jetons, donc veuillez réduire ce nombre en conséquence dans cette condition.", + "tpsBurst": "Rafale TPS", + "tpsBurstDes": "Lorsque la requête est inactive, Cloudreve peut réserver un nombre spécifié d'emplacements pour de futures rafales de trafic.", + "odOauthDes": "Cependant, vous devrez cliquer sur le bouton ci-dessous et autoriser avec la connexion au compte Microsoft pour terminer l'initialisation avant de pouvoir l'utiliser. Vous pouvez re-autoriser plus tard sur la page Liste des politiques de stockage.", + "gotoAuthPage": "Aller à la page d'autorisation", + "s3BucketDes": "Allez sur le tableau de bord AWS S3 pour créer un bucket, entrez le <0>Nom du bucket que vous venez de créer :", + "s3EndpointDes": "Spécifiez l'EndPoint (nÅ“ud géographique) du bucket de stockage au format URL complet, par ex. <0>https://bucket.region.example.com.", + "selectRegionDes": "Entrez le code de région du bucket de stockage, par ex. <0>us-east-1. Pour les fournisseurs de stockage compatibles S3 non-AWS, veuillez vous référer à leur documentation pour savoir comment remplir ce champ.", + "chunkSizeLabelS3": "Spécifiez la taille de chunk pour les uploads reprenables. La plage autorisée est de 5 MB - 5 GB.", + "policyEndpoint": "Endpoint.", + "s3Region": "Région", + "s3EndpointPathStyle": "Sélectionnez le format de l'adresse Endpoint S3. Certaines politiques de stockage compatibles S3 tierces peuvent nécessiter cette option pour fonctionner. Lorsque activé, nous forcerons l'utilisation d'adresses au format chemin, comme <0>http://s3.amazonaws.com/BUCKET/KEY.", + "usePathEndpoint": "Forcer le style de chemin", + "thumbExt": "Extensions qui prennent en charge les miniatures", + "thumbExtDes": "Laisser vide pour indiquer que l'ensemble prédéfini de la politique de stockage est utilisé. Non valide pour les politiques de stockage local, S3.", + "driverRoot": "Racine du pilote", + "driverRootDes": "Choisissez où sauvegarder les fichiers dans votre compte OneDrive. Changer cette option rendra les fichiers existants dans la politique de stockage inaccessibles.", + "saveToDefaultOneDrive": "Sauvegarder les fichiers dans le pilote OneDrive par défaut", + "saveToSharePoint": "Sauvegarder les fichiers dans SharePoint", + "sharePointUrlDes": "Entrez l'URL du site SharePoint. Après avoir perdu le focus, le système convertira automatiquement vers l'identifiant de pilote correct.", + "ks3selectRegionDes": "Entrez le code de région du bucket de stockage, par ex. <0>BEIJING .", + "ks3EndpointPathStyle": "Sélectionnez le format de l'adresse Endpoint KS3.", + "ossRegionDes": "Entrez le code de région où se trouve le bucket, par ex. <0>cn-hangzhou. Vous pouvez trouver la région correspondante dans le tableau <1>Régions et points de terminaison OSS et remplir l'<2>ID de région correspondant." + }, + "node": { + "slave": "esclave", + "master": "maître", + "noCapabilities": "Aucune fonctionnalité activée.", + "active": "Actif", + "suspended": "Suspendu", + "deleteNodeConfirmation": "Êtes-vous sûr de vouloir supprimer le nÅ“ud {{name}} ?", + "editNode": "Modifier le nÅ“ud {{node}}", + "thisIsMasterNodes": "Vous modifiez un nÅ“ud maître, qui dessert le site actuel.", + "enableNode": "Activer le nÅ“ud", + "enableNodeDes": "Une fois activé, le nÅ“ud acceptera et traitera les fonctionnalités qui ont été activées.", + "name": "Nom", + "nameNode": "Nom du nÅ“ud, également utilisé pour l'affichage aux utilisateurs.", + "type": "Type", + "server": "Point de terminaison du nÅ“ud", + "serverDes": "Point de terminaison utilisé pour la communication du nÅ“ud. Si vous souhaitez stocker des fichiers sur ce nÅ“ud, cette adresse sera également exposée côté utilisateur pour les téléchargements de fichiers.", + "loadBalancerRankDes": "Spécifiez un poids d'équilibrage de charge pour ce nÅ“ud, la valeur est un entier, plus la valeur est élevée, plus la probabilité d'être sélectionné est élevée.", + "loadBalancerRank": "Poids d'équilibrage de charge", + "slaveSecret": "Secret d'esclave", + "slaveSecretDes": "Secret utilisé pour la communication du nÅ“ud esclave avec le nÅ“ud maître. Il doit être cohérent avec <1>Secret dans la section <1>Slave du fichier de configuration du nÅ“ud esclave.", + "testNode": "Tester la communication du nÅ“ud", + "testNodeSuccess": "Communication du nÅ“ud réussie.", + "createArchiveDes": "Accepter les demandes de tâches de création d'archives.", + "extractArchiveDes": "Accepter les demandes de tâches d'extraction d'archives.", + "remoteDownloadDes": "Accepter les demandes de tâches de téléchargement distant. Une fois activé, vous devez également configurer les informations relatives au téléchargement distant ci-dessous.", + "downloader": "Téléchargeur", + "aria2Des": "Démarrez Aria2 avec le même utilisateur/niveau d'accès que celui qui exécute Cloudreve sur le serveur de nÅ“ud cible, activez le service RPC dans le fichier de configuration Aria2, pour plus d'informations et de directives, consultez la section \"Téléchargement distant\" de la documentation.", + "qbittorrentDes": "Démarrez qBittorrent avec le même utilisateur qui exécute Cloudreve sur le serveur de nÅ“ud cible, activez le service Web UI dans les paramètres qBittorrent, pour plus d'informations et de directives, consultez la section \"Téléchargement distant\" de la documentation.", + "rpcServer": "Serveur RPC", + "rpcServerHelpDes": "Adresse du serveur RPC contenant le numéro de port complet, par ex. <0>http://127.0.0.1:6800/.", + "rpcToken": "Token RPC", + "rpcTokenDes": "Cohérent avec <0>rpc-secret dans le fichier de configuration Aria2 ; laissez vide si non défini.", + "downloaderOptionDes": "Configuration supplémentaire du téléchargeur lors de la création d'une tâche de téléchargement, écrite au format clé-valeur JSON, voir la <0>documentation officielle du téléchargeur pour les paramètres disponibles.", + "refreshInterval": "Intervalle de rafraîchissement du statut (secondes)", + "refreshIntervalDes": "L'intervalle auquel Cloudreve demande un rafraîchissement de l'état de la tâche au téléchargeur. L'intervalle de rafraîchissement réel dépend également de la configuration de la file d'attente \"Téléchargement distant\" et de l'occupation du téléchargeur.", + "waitForSeeding": "Attendre le partage", + "waitForSeedingDes": "Une fois activé, lorsque la tâche de téléchargement distant est terminée, le nÅ“ud maintiendra la tâche en état de partage jusqu'à ce que la condition de fin de partage dans la configuration du téléchargeur soit remplie. Cette fonctionnalité ne prend effet qu'après la fin de la tâche de téléchargement distant, et n'affectera pas l'utilisation des fichiers téléchargés par l'utilisateur.", + "webUIEndpoint": "Point de terminaison Web UI", + "webUIEndpointDes": "Le point de terminaison de l'interface Web qBittorrent, par ex. <0>http://127.0.0.1:8080/.", + "tempPath": "Répertoire de téléchargement temporaire", + "tempPathDes": "Le répertoire sur le nÅ“ud qu'Aria2 utilise comme répertoire de téléchargement temporaire. Le processus Cloudreve sur le nÅ“ud a besoin des permissions de lecture, écriture et exécution sur ce répertoire, et le téléchargeur doit également pouvoir accéder à ce répertoire. Laissez vide pour utiliser le chemin de fichier temporaire par défaut.", + "webUIUsername": "Nom d'utilisateur Web UI", + "webUIPassword": "Mot de passe Web UI", + "webUICredDes": "Laissez vide si l'authentification n'est pas activée.", + "downloaderTestPass": "Connexion réussie au téléchargeur, version : {{version}}", + "testDownloader": "Tester la communication du téléchargeur", + "addNewNode": "Nouveau nÅ“ud", + "nameTheNode": "Nommer le nÅ“ud :", + "copyBinary": "", + "runCrSlave": "Exécutez Cloudreve sur le nÅ“ud avec la même version que le maître, et démarrez-le avec le fichier de configuration suivant :", + "keepIfUpload": "Si vous devez utiliser ce nÅ“ud pour les politiques de stockage à l'avenir, veuillez conserver la configuration CORS suivante.", + "storeFiles": "Stocker les fichiers", + "storeFilesDes": "Utilisez ce nÅ“ud pour stocker les fichiers utilisateur.", + "storeFilesHint": "Si vous souhaitez utiliser ce nÅ“ud pour les politiques de stockage, veuillez créer une politique de stockage esclave et sélectionner ce nÅ“ud.", + "runCrWithConfig": "Enregistrez le fichier ci-dessus comme fichier <0>config.ini, et démarrez Cloudreve avec ce fichier : <0>./cloudreve -c config.ini. Une instance Cloudreve esclave peut servir plusieurs nÅ“uds maîtres Cloudreve ; ajoutez simplement ce nÅ“ud esclave à tous les nÅ“uds maîtres et gardez le secret identique.", + "inputServer": "Entrez le point de terminaison du nÅ“ud :", + "testButton": "Vous pouvez cliquer sur le bouton ci-dessous pour tester si la communication est réussie.", + "hostHeaderHint": "S'il y a une erreur de signature, veuillez vérifier si le proxy inverse devant le nÅ“ud transmet l'en-tête <0>Host.", + "features": "Fonctionnalités activées", + "remoteDownload": "Téléchargement distant", + "refresh": "Actualiser" + }, + "group": { + "countUser": "Nombre", + "anonymous": "Groupe d'utilisateurs anonymes", + "sysGroup": "Groupe d'utilisateurs système", + "adminGroup": "Groupe d'utilisateurs administrateur", + "#": "#", + "name": "Nom", + "type": "Politique de stockage", + "count": "Utilisateurs enfants", + "size": "Quota de stockage", + "nameOfGroup": "Nom", + "nameOfGroupDes": "Nom du groupe, utilisé pour l'affichage aux utilisateurs.", + "availablePolicies": "Politiques de stockage disponibles", + "availablePoliciesDes": "Sélectionnez les politiques de stockage que ce groupe peut utiliser. Modifier ce paramètre n'affectera pas les fichiers téléchargés par les utilisateurs.", + "initialStorageQuota": "Quota de stockage initial", + "initialStorageQuotaDes": "Stockage maximum pouvant être utilisé par un seul utilisateur sous ce groupe.", + "isAdmin": "Groupe administrateur", + "isAdminDes": "Lorsqu'activé, les utilisateurs sous ce groupe auront les permissions d'administrateur.", + "share": "Partage", + "allowCreateShareLink": "Créer un lien de partage", + "allowCreateShareLinkDes": "Si désactivé, les utilisateurs ne peuvent pas créer de liens de partage.", + "shareFree": "Lien de partage gratuit", + "shareFreeDes": "Lorsqu'activé, les utilisateurs peuvent accéder à tous les liens de partage payants sans achat.", + "fileManagement": "Gestion des fichiers", + "allowWabDAV": "WebDAV", + "allowWabDAVDes": "Si désactivé, les utilisateurs ne peuvent pas se connecter au stockage via le protocole WebDAV", + "allowWabDAVProxy": "Proxy WebDAV", + "allowWabDAVProxyDes": "Si activé, les utilisateurs peuvent configurer le WebDAV pour être proxifié par Cloudreve lors du téléchargement de fichiers.", + "compressTask": "Tâches de compression/décompression", + "compressTaskDes": "Si activé, les utilisateurs peuvent faire de la compression/décompression pour les fichiers en ligne.", + "compressSize": "Taille maximale de fichier à compresser", + "compressSizeDes": "La taille totale maximale de fichier des tâches de compression qui peuvent être créées par l'utilisateur, remplissez 0 pour indiquer aucune limite. Cette limite n'est pas vérifiée lors de la création de tâches de compression, et si la taille totale des fichiers originaux dépasse cette limite lors de l'exécution, la tâche échouera.", + "decompressSize": "Taille maximale de fichier à décompresser", + "decompressSizeDes": "La taille totale maximale de fichier des tâches de décompression qui peuvent être créées par l'utilisateur, remplissez 0 pour indiquer aucune limite.", + "allowRemoteDownload": "Téléchargement distant", + "allowRemoteDownloadDes": "Permettre aux utilisateurs de créer des tâches de téléchargement distant. Si vous devez utiliser le téléchargement distant, vous devez également avoir des nÅ“uds avec le téléchargement distant activé dans la <0>Liste des nÅ“uds.", + "aria2Options": "Options de tâche du téléchargeur", + "aria2OptionsDes": "Paramètres supplémentaires pour les téléchargeurs (qBittorrent ou Aria2), écrits au format clé-valeur JSON, voir la documentation officielle du téléchargeur pour les paramètres disponibles.", + "aria2BatchSize": "Taille maximale de lot des tâches de téléchargement distant", + "aria2BatchSizeDes": "Nombre maximum pour soumettre des tâches de téléchargement distant par lots, remplissez 0 pour indiquer aucune limite.", + "migratePolicy": "Relocaliser la politique de stockage", + "migratePolicyDes": "Permettre à l'utilisateur de créer une tâche de relocalisation de politique de stockage.", + "advanceDelete": "Options avancées de suppression de fichiers", + "advanceDeleteDes": "Une fois activé, les utilisateurs peuvent choisir de conserver les fichiers physiques lors de la suppression de fichiers. Veuillez n'activer cette option que pour les groupes d'utilisateurs de confiance.", + "allowSelectNode": "Permettre la sélection de nÅ“ud", + "allowSelectNodeDes": "Lorsqu'activé, l'utilisateur peut sélectionner le nÅ“ud préféré avant de créer des tâches. Lorsque désactivé, le nÅ“ud sera équilibré en charge par le système dans les nÅ“uds autorisés pour le groupe.", + "allowedNodes": "NÅ“uds autorisés", + "allowedNodesDes": "Spécifiez les nÅ“uds que ce groupe peut utiliser pour créer des tâches. Une liste vide signifie que tous les nÅ“uds sont disponibles. Les utilisateurs ne peuvent sélectionner ou se voir attribuer que des nÅ“uds dans cette liste par l'équilibreur de charge. Actuellement, les tâches couvertes sont : téléchargement distant, compression/décompression de fichiers. Les autres tâches seront attribuées au nÅ“ud maître.", + "allNodes": "Tous les nÅ“uds", + "esclateAnonymity": "Escalader l'anonymat", + "esclateAnonymityDes": "Lorsqu'activé, les utilisateurs peuvent attribuer des permissions plus élevées aux utilisateurs anonymes (écriture/suppression/création). Lorsque désactivé, les utilisateurs ne peuvent attribuer que des permissions en lecture seule aux utilisateurs anonymes. Changer ce paramètre n'affectera pas les liens de partage ou fichiers existants.", + "allowDownloadShare": "Accéder aux liens partagés", + "allowDownloadShareDes": "Lorsque désactivé, les utilisateurs ne peuvent pas voir les liens partagés d'autres utilisateurs. Ce paramètre a priorité sur les paramètres de permission de lien de partage.", + "deletedNode": "NÅ“ud supprimé #{{id}}", + "maxWalkedFiles": "Fichiers parcourus maximum", + "maxWalkedFilesDes": "Dans certaines opérations qui nécessitent un parcours profond des fichiers, le nombre maximum de fichiers autorisés à être parcourus.", + "trashBinDuration": "Durée de la corbeille (secondes)", + "trashBinDurationDes": "Le temps de rétention des fichiers dans la corbeille, les fichiers seront définitivement supprimés après la durée d'expiration. Changer ce paramètre n'affectera pas les fichiers déjà dans la corbeille.", + "serverSideBatchDownload": "Téléchargement par lot côté serveur", + "serverSideBatchDownloadDes": "Permettre aux utilisateurs de sélectionner plusieurs fichiers pour utiliser le téléchargement par lot relayé côté serveur, après désactivation, les utilisateurs peuvent toujours utiliser la fonctionnalité de téléchargement par lot basée sur le navigateur pur.", + "uploadDownload": "Téléchargement et téléchargement", + "getDirectLink": "Obtenir le lien direct", + "getDirectLinkDes": "Permettre aux utilisateurs d'obtenir le lien direct du fichier.", + "bathSourceLinkLimit": "Taille maximale des liens directs par lot", + "bathSourceLinkLimitDes": "Le nombre maximum de fichiers autorisés pour que les utilisateurs obtiennent des liens directs en un seul lot, remplissez 0 signifie qu'aucune génération par lot de liens directs n'est autorisée.", + "redirectedSource": "Utiliser le lien direct redirigé", + "redirectedSourceDes": "Recommandé d'activer. Lorsqu'activé, le lien direct vers le fichier obtenu par l'utilisateur sera redirigé par Cloudreve avec un lien plus court. Lorsque désactivé, le lien direct vers le fichier obtenu par l'utilisateur devient l'URL originale vers le fichier, et est lié à la version du fichier. Certaines politiques produisent des liens directs non redirigés qui ne restent pas persistants ; voir les documents Cloudreve pour plus de détails.", + "reuseDirectLink": "Réutiliser le lien direct existant", + "reuseDirectLinkDes": "Lorsqu'activé, plusieurs demandes pour le lien direct du même fichier réutiliseront le lien de redirection existant.", + "downloadSpeedLimit": "Vitesse de téléchargement maximale", + "downloadSpeedLimitDes": "Remplissez 0 pour indiquer aucune limite. Lorsque la restriction est activée, la vitesse de téléchargement maximale sera limitée lorsque les utilisateurs téléchargent tous les fichiers sous la politique de stockage qui supporte la limitation de vitesse.", + "anonymousHint": "Ce groupe d'utilisateurs correspond au visiteur anonyme qui n'est pas connecté.", + "create": "Créer", + "copyFromExisting": "Copier depuis un groupe existant ?", + "notCopy": "Ne pas copier", + "confirmDelete": "Êtes-vous sûr de vouloir supprimer le groupe {{group}} ?", + "new": "Nouveau groupe", + "editGroup": "Modifier {{group}}" + }, + "user": { + "createdAt": "Créé le", + "originUserGroup": "Groupe d'utilisateurs d'origine", + "originUserGroupDes": "Groupe d'utilisateurs auquel l'utilisateur appartient avant l'achat du groupe actuel, le groupe actuel reviendra à ce groupe après expiration.", + "noOriginUserGroup": "Non", + "groupExpired": "Date d'expiration du groupe", + "groupExpiredDes": "Date d'expiration du groupe au format ISO8601, laisser vide signifie que le groupe est permanent.", + "openUserFiles": "Ouvrir les fichiers utilisateur", + "id": "ID", + "idValue": "{{id}} ({{hash_id}})", + "avatar": "Photo de profil", + "removeAvatar": "Supprimer la photo de profil", + "userDialogTitle": "Détails de l'utilisateur", + "2FAEnabled": "2FA activé", + "qqEnabled": "QQ lié", + "logtoEnabled": "Logto lié", + "oidcEnabled": "OIDC lié", + "deleted": "Utilisateur supprimé.", + "new": "Nouvel utilisateur", + "filter": "Filtre", + "emptyNoFilter": "Laisser vide signifie aucun filtre.", + "selectedObjects": "{{num}} objets sélectionnés.", + "nick": "Nom d'affichage", + "email": "Email", + "group": "Groupe", + "status": "Statut", + "usedStorage": "Stockage utilisé", + "status_active": "Actif", + "status_inactive": "Inactif", + "status_manual_banned": "Bloqué manuellement", + "status_sys_banned": "Bloqué par le système", + "toggleBan": "Bloquer/Débloquer", + "filterCondition": "Conditions de filtre", + "all": "Tous", + "userStatus": "Statut de l'utilisateur", + "apply": "Appliquer", + "editUser": "Modifier {{nick}}", + "password": "Mot de passe", + "passwordDes": "Laisser vide signifie aucune modification.", + "groupDes": "Groupe auquel l'utilisateur appartient.", + "2FA": "2FA", + "notEnabled": "Non activé", + "reset2Fa": "Désactiver", + "reset": "Réinitialiser", + "confirmDelete": "Êtes-vous sûr de vouloir supprimer l'utilisateur {{user}} ?", + "deleteXUsers": "Supprimer {{num}} utilisateurs", + "confirmBatchDelete": "Êtes-vous sûr de vouloir supprimer {{num}} utilisateurs ?", + "calibrateStorage": "Calibrer le stockage", + "calibrateStorageSuccess": "Stockage calibré avec succès." + }, + "file": { + "deleteXFiles": "Supprimer {{num}} fichiers", + "confirmBatchDelete": "Êtes-vous sûr de vouloir supprimer {{num}} fichiers ?", + "confirmDelete": "Êtes-vous sûr de vouloir supprimer le fichier {{file}} ?", + "haveShares": "Partagé", + "haveDirectLinks": "A des liens directs redirigés", + "directLinkId": "Identifiant de lien", + "directLinks": "Liens directs redirigés", + "noRecords": "Aucun enregistrement", + "speed": "Limite de vitesse", + "downloads": "Téléchargements", + "shareLink": "Liens de partage", + "shareLinkNum": "{{num}} (<0>Voir)", + "blobType": "Type", + "noEntities": "Aucun Blob", + "blobs": "Blobs", + "creator": "Créateur", + "source": "Source", + "key": "Clé", + "value": "Valeur", + "isPublic": "Public", + "noMetadata": "Aucune métadonnée", + "metadata": "Métadonnées", + "id": "ID", + "primaryStoragePolicy": "Politique de stockage principale", + "fileDialogTitle": "Détails du fichier", + "name": "Nom du fichier", + "deleteAsync": "La tâche de suppression sera exécutée en arrière-plan.", + "forceDelete": "Forcer la suppression", + "size": "Taille", + "sizeUsed": "Stockage utilisé", + "uploader": "Propriétaire", + "createdAt": "Créé le", + "uploading": "Téléchargement en cours", + "unknownUploader": "Inconnu", + "uploaderID": "ID du propriétaire", + "searchFileName": "Rechercher le nom du fichier", + "storagePolicy": "Politique de stockage", + "selectTargetUser": "Sélectionner l'utilisateur cible", + "importTaskCreated": "Tâche d'importation créée, vous pouvez voir son statut dans la liste des tâches en arrière-plan.", + "manuallyPathOnly": "La politique de stockage sélectionnée ne supporte que la saisie manuelle du chemin.", + "selectFolder": "Sélectionner le dossier", + "import": "Importer", + "importExternalFolder": "Importer des dossiers externes", + "importExternalFolderDes": "Vous pouvez importer des fichiers et structures de répertoires existants de votre politique de stockage dans Cloudreve. L'opération d'importation ne prendra pas de stockage physique supplémentaire, mais déduira quand même le quota de stockage utilisé de l'utilisateur normalement.", + "storagePolicyDes": "Sélectionnez la politique de stockage où les fichiers à importer sont actuellement stockés.", + "targetUser": "Utilisateur cible", + "targetUserDes": "Sélectionnez dans quel système de fichiers utilisateur vous voulez importer les fichiers.", + "srcFolderPath": "Chemin du dossier source", + "select": "Sélectionner", + "selectSrcDes": "Le chemin du répertoire à importer côté stockage.", + "dstFolderPath": "Chemin du dossier de destination", + "dstFolderPathDes": "Chemin dans le système de fichiers de l'utilisateur pour contenir tous les fichiers importés.", + "recursivelyImport": "Importer récursivement", + "recursivelyImportDes": "S'il faut importer tous les sous-répertoires sous le répertoire récursivement.", + "createImportTask": "Créer une tâche d'importation", + "unlink": "Délier (Garder le fichier physique)", + "searchUser": "Rechercher un utilisateur par nom ou email...", + "extractMediaMeta": "Extraire les informations média", + "extractMediaMetaDes": "S'il faut extraire les informations média pour chaque fichier pendant l'importation.", + "importWarning": "Avertissement", + "importWarnings": [ + "Après l'importation, le fichier physique sera pris en charge par Cloudreve, veuillez ne pas le modifier externellement par la suite.", + "N'importez pas le même fichier plusieurs fois.", + "Si le fichier de l'utilisateur entre en conflit, ce fichier sera ignoré." + ], + "otherConditions": "Autres conditions", + "shareLinkExisted": "A un lien de partage", + "directLinkExisted": "A un lien direct", + "isUploading": "En cours de téléchargement" + }, + "entity": { + "refenenceCount": "Nombre de références", + "waitForRecycle": "En attente de recyclage", + "entityDialogTitle": "Détails du Blob", + "uploadSessionID": "ID de session de téléchargement", + "referredFiles": "Fichiers référencés", + "confirmBatchDelete": "Êtes-vous sûr de vouloir supprimer {{num}} Blobs ?", + "deleteXEntities": "Supprimer {{num}} Blobs", + "forceDelete": "Forcer la suppression", + "forceDeleteDes": "S'il faut supprimer l'enregistrement Blob indépendamment de la suppression du fichier physique." + }, + "event": { + "cleanup": "Nettoyage", + "cleanupAuditLog": "Nettoyage des événements", + "cleanupAuditLogDescription": "Supprimer tous les événements qui répondent aux conditions suivantes :", + "cleanupNotAfter": "Avant cette date", + "cleanupEventTypes": "Types d'événements", + "cleanupEventTypesDes": "Sélectionnez les types d'événements à nettoyer. Laisser vide pour nettoyer tous les types.", + "initiator": "Initiateur", + "event": "Événement", + "userID": "ID utilisateur", + "ip": "IP", + "type": "Type", + "correlationId": "ID de corrélation", + "fileID": "ID du fichier", + "emailSend": "Envoyer l'email \"{{title}}\" à {{email}}", + "emailFailed": "La file d'attente d'emails a échoué au démarrage", + "signinFailed": "Échec de connexion : {{reason}}", + "createDavAccount": "Créer un compte WebDAV : {{account}}", + "updateDavAccount": "Mettre à jour le compte WebDAV : {{account}}", + "deleteDavAccount": "Supprimer le compte WebDAV : {{account}}", + "pointsChange": "Changement de points : {{points}}", + "storageAdded": "{{size}} de stockage acheté", + "nickChange": "Nom d'affichage changé de {{old}} à {{new}}", + "eventDialogTitle": "Détails de l'événement", + "userAgent": "Agent utilisateur", + "linkedUser": "Utilisateur lié", + "datetime": "Heure", + "linkedFile": "Fichier lié", + "linkedEntity": "Blob lié", + "linkedShare": "Partage lié", + "rawContent": "Contenu brut", + "confirmDelete": "Êtes-vous sûr de vouloir supprimer cet événement ?", + "deleteXEvents": "Supprimer {{num}} événements", + "confirmBatchDelete": "Êtes-vous sûr de vouloir supprimer {{num}} événements ?" + }, + "share": { + "confirmBatchDelete": "Êtes-vous sûr de vouloir supprimer {{num}} partages ?", + "confirmDelete": "Êtes-vous sûr de vouloir supprimer ce partage ?", + "deleteXShares": "Supprimer {{num}} partages", + "shareDialogTitle": "Détails du partage", + "shareLink": "Lien de partage", + "deleted": "Fichier supprimé", + "srcFileName": "Fichier source", + "views": "Vues", + "downloads": "Téléchargements", + "price": "Prix", + "autoExpire": "Expiration automatique", + "owner": "Propriétaire", + "createdAt": "Créé le", + "private": "Cacher de la page de profil", + "yes": "Oui", + "no": "Non", + "afterNDownloads": "Après {{num}} téléchargement(s).", + "none": "Aucun", + "srcType": "Type d'objet source", + "folder": "Dossier", + "file": "Fichier" + }, + "task": { + "cleanupTasks": "Nettoyer les tâches", + "cleanupTasksDescription": "Nettoyer toutes les tâches qui répondent aux conditions suivantes :", + "cleanupNotAfter": "Avant cette date", + "cleanupTaskTypes": "Types de tâches", + "cleanupTaskTypesDes": "Sélectionnez les types de tâches à nettoyer. Laisser vide pour nettoyer tous les types.", + "cleanupTaskStatuses": "Statuts des tâches", + "cleanupTaskStatusesDes": "Sélectionnez les statuts des tâches à nettoyer. Laisser vide pour nettoyer toutes les tâches avec statut terminé.", + "confirmDelete": "Êtes-vous sûr de vouloir supprimer cette tâche ?", + "confirmBatchDelete": "Êtes-vous sûr de vouloir supprimer {{num}} tâches ?", + "deleteXTasks": "Supprimer {{num}} tâches", + "blobID": "ID du Blob", + "retryIndex": "Index de nouvelle tentative", + "entityError": "Blobs qui ont échoué au recyclage", + "updatedAt": "Mis à jour le", + "taskDialogTitle": "Détails de la tâche", + "explicitEntityRecycle": "Recycler explicitement les Blobs de fichiers : {{blobs}}", + "entityRecycleRoutine": "Scanner et recycler les Blobs de fichiers", + "mediaMetadata": "Extraire les métadonnées média du Blob <0>#{{entityID}}", + "uploadSentinelCheck": "Vérifier le statut de la session de téléchargement {{uploadSessionID}}", + "remoteDownload": "Téléchargement distant : ", + "owner": "Propriétaire", + "content": "Contenu", + "status": "Statut", + "create_archive": "Créer une archive", + "extract_archive": "Extraire une archive", + "relocate": "Relocaliser", + "remote_download": "Téléchargement distant", + "media_meta": "Métadonnées média", + "entity_recycle_routine": "Routine de recyclage d'entité", + "explicit_entity_recycle": "Recyclage d'entité explicite", + "upload_sentinel_check": "Vérification de sentinelle de téléchargement", + "import": "Importation externe", + "type": "Type", + "node": "NÅ“ud distribué", + "createdBy": "Créé par", + "ready": "Prêt", + "downloading": "Téléchargement en cours", + "paused": "En pause", + "seeding": "Partage", + "error": "Erreur", + "finished": "Terminé", + "canceled": "Annulé/Arrêté", + "unknown": "Inconnu", + "errorMsg": "Message d'erreur" + }, + "payment": { + "tradeNo": "N° de transaction", + "productType": "Type de produit", + "providerID": "ID du fournisseur", + "status": "Statut", + "deleteXPayments": "Supprimer {{num}} paiements" + }, + "customProps": { + "add": "Ajouter", + "type": "Type", + "default": "Valeur par défaut", + "actions": "Actions", + "text": "Texte", + "number": "Nombre", + "boolean": "Case à cocher", + "select": "Sélection unique", + "multiSelect": "Sélection multiple", + "user": "Utilisateur", + "link": "Lien", + "rating": "Évaluation", + "addProp": "Ajouter une propriété", + "editProp": "Modifier la propriété", + "icon": "Icône", + "iconDes": "Nom d'icône <0>Iconify, laisser vide pour cacher l'icône.", + "id": "ID", + "idDes": "ID de propriété, assurez-vous qu'il soit unique parmi toutes les propriétés.", + "idPatternDes": "Seules les lettres, chiffres, traits de soulignement et tirets sont autorisés.", + "minLength": "Longueur minimale", + "maxLength": "Longueur maximale", + "emptyLimit": "Laisser vide pour ne pas limiter.", + "minValue": "Valeur minimale", + "maxValue": "Valeur maximale", + "options": "Options", + "optionsDes": "Une option par ligne." + }, + "vas": { + "disableSubAddressEmail": "Désactiver les emails de sous-adresse", + "disableSubAddressEmailDes": "Une fois activé, les adresses email contenant <0>+ ne peuvent pas être utilisées pour l'inscription.", + "confirmDelete": "Êtes-vous sûr de vouloir supprimer ces commandes ?", + "vas": "VAS", + "reports": "Rapports", + "orders": "Paiements", + "initialFiles": "Fichiers initiaux", + "initialFilesDes": "Spécifiez les fichiers que l'utilisateur possède initialement après l'inscription. Entrez un ID de fichier pour rechercher les fichiers existants.", + "filterEmailProvider": "Filtrer le fournisseur d'email", + "filterEmailProviderDisabled": "Désactivé", + "filterEmailProviderWhitelist": "Liste blanche", + "filterEmailProviderBlacklist": "Liste noire", + "filterEmailProviderDes": "Restreindre le fournisseur d'email pour l'inscription, la connexion SSO tierce n'est pas restreinte.", + "filterEmailProviderRule": "Règles de filtre de domaine email", + "filterEmailProviderRuleDes": "Séparez plusieurs champs avec un point-virgule.", + "qqConnect": "QQ Connect", + "qqConnectHint": "Lors de la création de l'application, veuillez remplir l'URL de callback : {{url}}", + "enableQQConnect": "Activer QQ Connect", + "enableQQConnectDes": "Permettre de lier QQ, utiliser QQ pour se connecter au site web.", + "loginWithoutBinding": "Connexion sans inscription", + "loginWithoutBindingDes": "Une fois activé, si un utilisateur se connecte depuis un tiers mais n'a pas de compte lié, le système créera un compte pour lui. Les utilisateurs se connectant de cette façon ne pourront se connecter qu'en utilisant ce tiers à l'avenir.", + "appid": "APP ID", + "appidDes": "L'APP ID obtenu depuis la page de gestion d'application.", + "appKey": "APP KEY", + "appKeyDes": "L'APP KEY obtenu depuis la page de gestion d'application.", + "overuseReminder": "Rappel de surutilisation", + "overuseReminderDes": "Modèle d'email de rappel envoyé aux utilisateurs après que leur capacité dépasse la limite due à l'expiration VAS.", + "vasSetting": "Paramètres VAS", + "storagePack": "Packs de stockage", + "purchasableGroups": "Adhésions", + "giftCodes": "Codes cadeaux", + "enable": "Activer", + "appID": "App ID", + "appIDDes": "APPID de l'application de paiement.", + "rsaPrivate": "Clé privée RSA d'application", + "rsaPrivateDes": "La clé privée RSA2 (SHA256) pour l'application de paiement, généralement générée par vous. Pour plus de détails, consultez <0>Génération de clés RSA.", + "alipayPublicKey": "Clé publique Alipay", + "alipayPublicKeyDes": "Fournie par Alipay, disponible dans Gestion d'application - Informations d'application - Méthode de signature API.", + "wechatPay": "WeChat Pay", + "applicationID": "ID d'application", + "applicationIDDes": "Numéro public ou appid d'application mobile demandé par les marchands.", + "merchantID": "Numéro de marchand", + "merchantIDDes": "Le numéro de marchand généré et émis par WeChat Pay.", + "apiV3Secret": "Secret API v3", + "apiV3SecretDes": "Le marchand doit définir le secret dans [Plateforme Marchand] - [Sécurité API] avant de demander WeChat Pay. La longueur de la clé est de 32 octets.", + "mcCertificateSerial": "Numéro de série du certificat marchand", + "mcCertificateSerialDes": "Naviguez vers [Sécurité API] - [Certificat API] - [Voir le certificat] pour voir le numéro de série du certificat API marchand.", + "mcAPISecret": "Secret API Marchand", + "mcAPISecretDes": "Contenu du fichier secret apiclient_key.pem.", + "payjs": "PAYJS", + "payjsWarning": "Ce service est fourni par <0>PAYJS, une plateforme tierce, et tout litige en découlant n'est pas de la responsabilité des développeurs Cloudreve.", + "mcNumber": "Numéro de marchand", + "mcNumberDes": "Disponible sur la page d'accueil du panneau d'administration PAYJS.", + "communicationSecret": "Clé de communication", + "otherSettings": "Autres paramètres", + "banBufferPeriod": "Période tampon de suspension (secondes)", + "banBufferPeriodDes": "La durée maximale pendant laquelle un utilisateur peut maintenir le statut de dépassement de capacité, au-delà de laquelle l'utilisateur sera suspendu par le système.", + "allowSellShares": "Permettre la tarification des partages", + "allowSellSharesDes": "Une fois activé, les utilisateurs peuvent définir un prix de crédit pour le partage et les crédits seront déduits pour le téléchargement.", + "creditPriceRatio": "Taux d'arrivée des crédits (%)", + "creditPriceRatioDes": "Le taux de crédits arrivant réellement au partageur pour l'achat d'un partage avec un prix défini pour le téléchargement.", + "creditPrice": "Prix du crédit (centime)", + "creditPriceDes": "Prix lors de la recharge des crédits", + "add": "Ajouter", + "name": "Nom", + "price": "Prix", + "duration": "Durée", + "size": "Taille", + "actions": "Actions", + "orCredits": " Ou {{num}} crédits", + "highlight": "Mettre en évidence", + "yes": "Oui", + "no": "Non", + "productName": "Nom du produit", + "qyt": "Qté.", + "code": "Code", + "status": "Statut", + "invalidProduct": "Produit invalide", + "used": "Utilisé", + "notUsed": "Non utilisé", + "generatingResult": "Résultat", + "addStoragePack": "Ajouter un pack de stockage", + "editStoragePack": "Modifier le pack de stockage", + "productNameDes": "Nom d'affichage du produit", + "packSizeDes": "Taille du pack de stockage", + "durationDay": "Durée (jour)", + "durationDayDes": "Durée de validité de chaque pack de stockage.", + "priceYuan": "Prix (Yuan)", + "packPriceDes": "Prix du pack de stockage.", + "priceCredits": "Prix (Crédits)", + "priceCreditsDes": "Le prix lors de l'utilisation des crédits pour acheter, remplir 0 signifie que vous ne pouvez pas utiliser les crédits pour acheter.", + "editMembership": "Modifier l'adhésion", + "addMembership": "Ajouter une adhésion", + "group": "Groupe", + "groupDes": "Groupes d'utilisateurs mis à niveau après l'achat.", + "durationGroupDes": "La validité du temps d'achat de l'unité de groupe d'utilisateurs mis à niveau après l'achat.", + "groupPriceDes": "Prix d'adhésion", + "productDescription": "Description du produit (Une par ligne)", + "productDescriptionDes": "Description du produit affichée sur la page d'achat.", + "highlightDes": "Une fois activé, sera mis en évidence sur la page de sélection de produit.", + "generateGiftCode": "Générer des codes cadeaux", + "numberOfCodes": "Nombre de codes", + "numberOfCodesDes": "Nombre de codes cadeaux à générer.", + "linkedProduct": "Produit lié", + "productQyt": "Qté. du produit", + "productQytDes": "Pour les produits de crédit, c'est le nombre de points et les autres produits sont des multiples de durées.", + "freeDownload": "Télécharger les fichiers partagés gratuitement", + "freeDownloadDes": "Une fois activé, l'utilisateur peut télécharger les partages payants gratuitement.", + "credits": "Crédits", + "markSuccessful": "Marqué avec succès.", + "markAsResolved": "Marquer comme résolu", + "reportedContent": "Contenu signalé", + "reason": "Raison", + "description": "Description", + "reportTime": "Signalé le", + "invalid": "[Invalide]", + "deleteShare": "Supprimer le lien de partage", + "orderDeleted": "Commande supprimée.", + "orderName": "Nom", + "product": "Produit", + "paymentId": "ID de paiement", + "orderNumber": "N° de transaction", + "amount": "Montant", + "paidBy": "Payé avec", + "orderOwner": "Créé par", + "unpaid": "Non payé", + "paid": "Payé", + "shareLink": "Lien partagé", + "mobileApp": "Application mobile", + "showAppPromotion": "Afficher la page de promotion", + "showAppPromotionDes": "Une fois activé, l'utilisateur peut voir la page de guidage pour l'application mobile dans la page \"Connecter et monter\".", + "customPaymentName": "Nom de la méthode de paiement", + "customPaymentNameDes": "Nom de la méthode de paiement utilisée pour afficher à l'utilisateur.", + "customPaymentSecretDes": "Clé secrète pour signer les demandes de paiement.", + "customPaymentEndpoint": "URL de l'API de paiement", + "customPaymentEndpointDes": "URL à demander lors de la création d'une commande de paiement.", + "appFeedback": "URL de feedback", + "appForum": "URL du forum utilisateur", + "appLinkDes": "Sera affiché dans le client mobile, laisser vide pour cacher l'élément de menu. Ce paramètre ne prendra effet que si la licence VOL est valide." + }, + "pro": { + "title": "Fonctionnalités exclusives de la version Pro", + "description": "La fonctionnalité que vous essayez d'accéder n'est disponible que dans la version Pro de Cloudreve, mettez à niveau pour débloquer toutes les fonctionnalités avancées.", + "proInclude": "La version Pro inclut :", + "shareLinkCollabration": "Partage de liens de collaboration", + "filePermission": "Gestion des permissions des fichiers", + "multipleStoragePolicy": "Changement de politiques de stockage et de politiques de stockage de répertoire", + "auditAndActivity": "Journal d'activité des fichiers et du système", + "vasService": "Services additionnels et système de points", + "sso": "SSO de connexion unique", + "more": "......", + "later": "Plus tard", + "learnMore": "En savoir plus sur la version Pro", + "promotionTitle": "Promotion spéciale de mise à niveau de la version communautaire", + "promotion": "Utilisez le code de promotion <0>{{code}} lors de l'achat pour obtenir un <1>-{{discount}}% de réduction." + }, + "abuseReport": { + "deleteXAbuseReports": "Supprimer {{num}} signalements d'abus", + "folderPath": "Chemin du dossier", + "reporter": "Rapporteur", + "shareLink": "Lien partagé <0>#{{id}}", + "deletedShare": "Lien partagé supprimé", + "deletedUser": "Utilisateur supprimé", + "confirmDelete": "Êtes-vous sûr de vouloir supprimer ce signalement d'abus ?", + "confirmBatchDelete": "Êtes-vous sûr de vouloir supprimer {{num}} signalements d'abus ?", + "reporterID": "ID de l'utilisateur rapporteur", + "reportedUserID": "ID de l'utilisateur signalé", + "shareID": "ID partagé", + "reason": "Raison" + } +} diff --git a/public/locales/fr-FR/image_editor.json b/public/locales/fr-FR/image_editor.json new file mode 100755 index 0000000..f896c23 --- /dev/null +++ b/public/locales/fr-FR/image_editor.json @@ -0,0 +1,113 @@ +{ + "name": "Nom", + "save": "Enregistrer", + "saveAs": "Enregistrer sous", + "back": "Retour", + "loading": "Chargement...", + "resetOperations": "Réinitialiser/supprimer toutes les opérations", + "changesLoseWarningHint": "Si vous appuyez sur le bouton \"réinitialiser\", vos modifications seront perdues. Souhaitez-vous continuer ?", + "discardChangesWarningHint": "Si vous fermez la fenêtre modale, votre dernière modification ne sera pas sauvegardée.", + "cancel": "Annuler", + "apply": "Appliquer", + "warning": "Avertissement", + "confirm": "Confirmer", + "discardChanges": "Abandonner les modifications", + "undoTitle": "Annuler la dernière opération", + "redoTitle": "Refaire la dernière opération", + "showImageTitle": "Afficher l'image originale", + "zoomInTitle": "Zoomer", + "zoomOutTitle": "Dézoomer", + "toggleZoomMenuTitle": "Basculer le menu de zoom", + "adjustTab": "Ajuster", + "finetuneTab": "Affiner", + "filtersTab": "Filtres", + "watermarkTab": "Filigrane", + "annotateTabLabel": "Annoter", + "resize": "Redimensionner", + "resizeTab": "Redimensionner", + "imageName": "Nom de l'image", + "invalidImageError": "Image fournie non valide.", + "uploadImageError": "Erreur lors du téléchargement de l'image.", + "areNotImages": "ne sont pas des images", + "isNotImage": "n'est pas une image", + "toBeUploaded": "à télécharger", + "cropTool": "Recadrer", + "original": "Original", + "custom": "Personnalisé", + "square": "Carré", + "landscape": "Paysage", + "portrait": "Portrait", + "ellipse": "Ellipse", + "classicTv": "TV classique", + "cinemascope": "Cinémascope", + "arrowTool": "Flèche", + "blurTool": "Flou", + "brightnessTool": "Luminosité", + "contrastTool": "Contraste", + "ellipseTool": "Ellipse", + "unFlipX": "Annuler le retournement X", + "flipX": "Retourner X", + "unFlipY": "Annuler le retournement Y", + "flipY": "Retourner Y", + "hsvTool": "HSV", + "hue": "Teinte", + "brightness": "Luminosité", + "saturation": "Saturation", + "value": "Valeur", + "imageTool": "Image", + "importing": "Importation...", + "addImage": "+ Ajouter une image", + "uploadImage": "Télécharger une image", + "fromGallery": "Depuis la galerie", + "lineTool": "Ligne", + "penTool": "Stylo", + "polygonTool": "Polygone", + "sides": "Côtés", + "rectangleTool": "Rectangle", + "cornerRadius": "Rayon des coins", + "resizeWidthTitle": "Largeur en pixels", + "resizeHeightTitle": "Hauteur en pixels", + "toggleRatioLockTitle": "Basculer le verrouillage du ratio", + "resetSize": "Réinitialiser à la taille d'image originale", + "rotateTool": "Rotation", + "textTool": "Texte", + "textSpacings": "Espacement du texte", + "textAlignment": "Alignement du texte", + "fontFamily": "Famille de polices", + "size": "Taille", + "letterSpacing": "Espacement des lettres", + "lineHeight": "Hauteur de ligne", + "warmthTool": "Chaleur", + "addWatermark": "+ Ajouter un filigrane", + "addTextWatermark": "+ Ajouter un filigrane de texte", + "addWatermarkTitle": "Choisir le type de filigrane", + "uploadWatermark": "Télécharger un filigrane", + "addWatermarkAsText": "Ajouter comme texte", + "padding": "Remplissage", + "paddings": "Remplissages", + "shadow": "Ombre", + "horizontal": "Horizontal", + "vertical": "Vertical", + "blur": "Flou", + "opacity": "Opacité", + "transparency": "Transparence", + "position": "Position", + "stroke": "Trait", + "saveAsModalTitle": "Enregistrer sous", + "extension": "Extension", + "format": "Format", + "nameIsRequired": "Le nom est requis.", + "quality": "Qualité", + "imageDimensionsHoverTitle": "Taille de l'image sauvegardée (largeur x hauteur)", + "cropSizeLowerThanResizedWarning": "Notez que la zone de recadrage sélectionnée est inférieure au redimensionnement appliqué, ce qui pourrait causer une diminution de la qualité", + "actualSize": "Taille réelle (100%)", + "fitSize": "Ajuster la taille", + "addImageTitle": "Sélectionner l'image à ajouter...", + "mutualizedFailedToLoadImg": "Échec du chargement de l'image.", + "tabsMenu": "Menu", + "download": "Télécharger", + "width": "Largeur", + "height": "Hauteur", + "plus": "+", + "cropItemNoEffect": "Aucun aperçu disponible pour cet élément de recadrage" +} diff --git a/public/locales/fr-FR/markdown_editor.json b/public/locales/fr-FR/markdown_editor.json new file mode 100755 index 0000000..8c56c4c --- /dev/null +++ b/public/locales/fr-FR/markdown_editor.json @@ -0,0 +1,114 @@ +{ + "frontmatterEditor": { + "title": "Modifier les métadonnées du document", + "key": "Clé", + "value": "Valeur", + "addEntry": "Ajouter une entrée" + }, + "dialogControls": { + "save": "Enregistrer", + "cancel": "Annuler" + }, + "uploadImage": { + "dialogTitle": "Télécharger une image", + "uploadInstructions": "Téléchargez une image depuis votre appareil :", + "addViaUrlInstructions": "Ou ajoutez une image depuis une URL / chemin relatif (relatif au fichier actuel) :", + "autoCompletePlaceholder": "Sélectionnez ou collez une source d'image", + "addViaUrlInstructionsNoUpload": "URL de l'image :", + "alt": "Alt :", + "title": "Titre :" + }, + "imageEditor": { + "deleteImage": "Supprimer l'image", + "editImage": "Modifier l'image" + }, + "createLink": { + "url": "URL", + "urlPlaceholder": "Sélectionnez ou collez une URL", + "title": "Titre", + "saveTooltip": "Définir l'URL", + "cancelTooltip": "Annuler les modifications" + }, + "linkPreview": { + "open": "Ouvrir {{url}} dans une nouvelle fenêtre", + "edit": "Modifier l'URL du lien", + "copyToClipboard": "Copier dans le presse-papiers", + "copied": "Copié !", + "remove": "Supprimer le lien" + }, + "table": { + "deleteTable": "Supprimer le tableau", + "columnMenu": "Menu de colonne", + "textAlignment": "Alignement du texte", + "alignLeft": "Aligner à gauche", + "alignCenter": "Centrer", + "alignRight": "Aligner à droite", + "insertColumnLeft": "Insérer une colonne à gauche de celle-ci", + "insertColumnRight": "Insérer une colonne à droite de celle-ci", + "deleteColumn": "Supprimer cette colonne", + "rowMenu": "Menu de ligne", + "insertRowAbove": "Insérer une ligne au-dessus de celle-ci", + "insertRowBelow": "Insérer une ligne en dessous de celle-ci", + "deleteRow": "Supprimer cette ligne" + }, + "toolbar": { + "blockTypes": { + "paragraph": "Paragraphe", + "quote": "Citation", + "heading": "Titre {{level}}" + }, + "blockTypeSelect": { + "selectBlockTypeTooltip": "Sélectionner le type de bloc", + "placeholder": "Type de bloc" + }, + "toggleGroup": "basculer le groupe", + "removeBold": "Supprimer le gras", + "bold": "Gras", + "removeItalic": "Supprimer l'italique", + "italic": "Italique", + "underline": "Supprimer le souligné", + "removeUnderline": "Souligné", + "removeInlineCode": "Supprimer le format de code", + "inlineCode": "Format de code en ligne", + "link": "Créer un lien", + "richText": "Texte enrichi", + "diffMode": "Mode différentiel", + "source": "Mode source", + "admonition": "Insérer une admonition", + "codeBlock": "Insérer un bloc de code", + "editFrontmatter": "Modifier les métadonnées", + "insertFrontmatter": "Insérer les métadonnées", + "image": "Insérer une image", + "insertSandpack": "Insérer Sandpack", + "table": "Insérer un tableau", + "thematicBreak": "Insérer une rupture thématique", + "bulletedList": "Liste à puces", + "numberedList": "Liste numérotée", + "checkList": "Liste de vérification", + "deleteSandpack": "Supprimer ce bloc de code", + "undo": "Annuler {{shortcut}}", + "redo": "Refaire {{shortcut}}", + "superscript": "Exposant", + "subscript": "Indice", + "strikethrough": "Barré", + "removeSubscript": "Supprimer l'indice", + "removeSuperscript": "Supprimer l'exposant", + "removeStrikethrough": "Supprimer le barré" + }, + "admonitions": { + "note": "Note", + "tip": "Astuce", + "danger": "Danger", + "info": "Info", + "caution": "Attention", + "changeType": "Sélectionner le type d'admonition", + "placeholder": "Type d'admonition" + }, + "codeBlock": { + "language": "Langage du bloc de code", + "selectLanguage": "Sélectionner le langage du bloc de code" + }, + "contentArea": { + "editableMarkdown": "markdown modifiable" + } +} diff --git a/public/locales/it-IT/application.json b/public/locales/it-IT/application.json new file mode 100755 index 0000000..8a62e92 --- /dev/null +++ b/public/locales/it-IT/application.json @@ -0,0 +1,1107 @@ +{ + "login": { + "lastStep": "Ultimo passaggio", + "siginToYourAccount": "Accedi al tuo account", + "createNewAccount": "Crea un nuovo account", + "enterPassword": "Inserisci la password", + "enterPasswordHint": "Inserisci la password per {{email}}", + "paswordlessHint": "L'account {{email}} è senza password, seleziona uno dei seguenti metodi di autenticazione:", + "noAccountSignupNow": "Non hai un account? <0>Registrati subito", + "haveAccountSignInNow": "Hai già un account? <0>Accedi subito", + "privacyPolicy": "Privacy Policy", + "termOfUse": "Termini di utilizzo", + "signupHint": "L'account {{email}} che hai inserito non esiste, vuoi registrarti subito?", + "accountNotFoundHint": "L'account {{email}} che hai inserito non esiste.", + "or": "Oppure", + "selectAccountToUse": "Seleziona un account da utilizzare", + "useOtherAccount": "Usa un altro account", + "email": "Email", + "password": "Password", + "captcha": "CAPTCHA", + "captchaError": "Impossibile caricare il CAPTCHA: {{message}}", + "signIn": "Accedi", + "signUp": "Registrati", + "signUpAccount": "Registra account", + "useFIDO2": "Usa chiave d'accesso", + "usePassword": "Usa password", + "forgetPassword": "Password dimenticata?", + "2FA": "Verifica 2FA", + "input2FACode": "Inserisci il codice di verifica 2FA a 6 cifre", + "passwordNotMatch": "Le password non corrispondono", + "findMyPassword": "Recupera password", + "passwordReset": "Password reimpostata", + "newPassword": "Nuova password", + "repeatNewPassword": "Ripeti la nuova password", + "repeatPassword": "Ripeti la password", + "resetPassword": "Reimposta password", + "backToSingIn": "Torna al login", + "sendMeAnEmail": "Invia email di reset password", + "resetEmailSent": "L'email di reset password è stata inviata, controlla la tua casella di posta", + "browserNotSupport": "Browser o ambiente corrente non supportato", + "success": "Login riuscito", + "signUpSuccess": "Registrazione riuscita", + "activateSuccess": "Attivazione riuscita", + "accountActivated": "Il tuo account è stato attivato con successo", + "title": "Accedi a {{title}}", + "sinUpTitle": "Registrati a {{title}}", + "activateTitle": "Attivazione email", + "activateDescription": "Un'email di attivazione è stata inviata al tuo indirizzo email, visita il link nell'email per completare la registrazione.", + "continue": "Continua", + "back": "Indietro", + "logout": "Logout", + "signingOut": "Logout in corso...", + "loggedOut": "Sei stato disconnesso", + "clickToRefresh": "Clicca per aggiornare il CAPTCHA", + "switchLanguage": "Cambia lingua" + }, + "resetThumbnail": "Reimposta miniatura danneggiata", + "resetThumbnailRequested": "Reimpostazione della miniatura richiesta.", + "noFileCanResetThumbnail": "Nessun file può reimpostare la miniatura.", + "navbar": { + "notBefore": "Non prima di", + "notAfter": "Non dopo", + "minimum": "Minimo", + "maximum": "Massimo", + "fileSize": "Dimensione file", + "searchBase": "Cerca in", + "searchInBase": "Cerca in <0>", + "conditionDuplicate": "La condizione esiste già.", + "fileType": "Tipo di file", + "addCondition": "Aggiungi condizioni", + "notNameOpOr": "Tutte le parole chiave devono essere presenti", + "caseFolding": "Ignora maiuscole/minuscole", + "keywords": "Parole chiave", + "fileNameKeywordsHelp": "Premi invio per aggiungere una nuova parola chiave", + "advancedSearch": "Ricerca avanzata", + "searchFilesTitle": "Cerca file", + "searchIn": "Cerca <0>{{keywords}}", + "recentlyViewed": "Visualizzati di recente", + "searchFiles": "Cerca file...", + "showMore": "Altro", + "myFiles": "I miei file", + "hisFiles": "I suoi file", + "trash": "Cestino", + "sharedWithMe": "Condivisi con me", + "myShare": "Le mie condivisioni", + "remoteDownload": "Download remoto", + "connect": "Connetti e monta", + "taskQueue": "Attività in background", + "setting": "Impostazioni", + "videos": "Video", + "photos": "Foto", + "music": "Musica", + "documents": "Documenti", + "addATag": "Aggiungi un tag...", + "addTagDialog": { + "selectFolder": "Seleziona una cartella", + "fileSelector": "Selettore file", + "folderLink": "Collegamento cartella", + "tagName": "Nome tag", + "matchPattern": "Pattern di corrispondenza del nome file", + "matchPatternDescription": "Puoi usare <0>* come jolly. Ad esempio, <1>*.png significa corrispondere alle immagini in formato png. Le regole multilinea opereranno in una relazione \"o\" tra loro.", + "icon": "Icona:", + "color": "Colore:", + "folderPath": "Percorso della cartella" + }, + "storage": "Archiviazione", + "storageDetail": "{{used}} di {{total}} utilizzati", + "notLoginIn": "Non connesso", + "visitor": "Visitatore", + "objectsSelected": "{{num}} selezionati", + "searchPlaceholder": "Digita <0>/ per cercare", + "backToHomepage": "Torna alla homepage", + "darkModeSwitch": "Interruttore tema scuro", + "toDarkMode": "Scuro", + "toLightMode": "Chiaro", + "myProfile": "Il mio profilo", + "dashboard": "Dashboard" + }, + "fileManager": { + "currentStoragePolicy": "Policy di archiviazione corrente: {{policy}}", + "customProps": "Proprietà personalizzate", + "rating": "Valutazione", + "description": "Descrizione", + "add": "Aggiungi", + "clickToEdit": "Clicca per modificare...", + "clickToEditSelect": "Clicca per selezionare...", + "enterUrl": "Inserisci URL...", + "searchUser": "Cerca utente...", + "typeToSearch": "Inserisci nome o email...", + "searchProperty": "Cerca file con la stessa proprietà", + "quality": "Qualità", + "audioTrack": "Audio", + "auto": "Automatico", + "default": "Predefinito", + "shareWithMeEmpty": "Nessun file condiviso trovato", + "shareWithMeEmptyDes": "Se hai bisogno di vedere le condivisioni di altri qui, salva il collegamento in qualsiasi posizione nei tuoi file quando visiti un link condiviso.", + "selectAll": "Seleziona tutto", + "selectNone": "Deseleziona tutto", + "invertSelection": "Inverti selezione", + "imageSize": "Dimensione immagine", + "focalLength": "Lunghezza focale", + "columnExisted": "La colonna esiste già.", + "metadataColumn": "Metadati ({{metadata}})", + "column": "Colonna", + "listColumnSetting": "Impostazioni colonna", + "addColumn": "Aggiungi colonne", + "failedLoadPreview": "Impossibile caricare l'anteprima.", + "recursiveLimitReached": "Raggiunto il limite di profondità di ricerca.", + "recursiveLimitReachedDes": "Il sistema ha smesso di cercare cartelle più profonde, prova a restringere l'ambito di ricerca.", + "searchConditions": "{{num}} condizione/i", + "createDate": "Data di creazione", + "updatedDate": "Data di aggiornamento", + "cameraMake": "Produttore fotocamera", + "cameraModel": "Modello fotocamera", + "lensModel": "Modello obiettivo", + "lensMake": "Produttore obiettivo", + "metadataKey": "Chiave", + "metadataValue": "Valore", + "metadata": "Metadati", + "symbolicFile": "Link simbolico", + "relocation": "Riposiziona policy di archiviazione", + "downloadingFile": "Download di \"{{name}}\", non chiudere questa pagina...", + "mountOwner": "Solo il proprietario della cartella corrente può montare le policy.", + "uploading": "Caricamento in corso", + "noActionsCanBeDone": "Nessuna azione può essere eseguita.", + "newFileName": "Nuovo file.{{ext}}", + "newDocumentType": "{{display_name}} (.{{ext}})", + "text": "Testo", + "diagram": "Diagramma", + "whiteboard": "Lavagna", + "selectApplications": "Seleziona applicazioni...", + "newlyCreatedFolder": "Nuova cartella", + "expandAllApp": "Espandi tutte le applicazioni", + "epubViewer": "Lettore ePub", + "googledocs": "Visualizzatore Google Docs", + "m365viewer": "Visualizzatore Microsoft Office Online", + "pdfViewer": "Visualizzatore PDF", + "archivePreview": "Anteprima archivio", + "extractSelected": "Estrai file selezionati", + "viewerFileSizeWarning": "La dimensione del file aperto ({{file_size}}) supera il limite ({{max}}) di {{app}}, potrebbe non funzionare correttamente.", + "testSubtitleStyle": "Testa stile sottotitoli AaBbCc", + "color": "Colore", + "fontSize": "Dimensione carattere", + "disableSubtitle": "Disabilita sottotitoli", + "noSubtitle": "Nessun file di sottotitoli ASS/SRT/VTT trovato nella cartella corrente.", + "subtitleStyles": "Stili sottotitoli", + "subtitles": "Sottotitoli", + "markdownEditor": "Editor Markdown", + "saveSuccess": "Salvato con successo alle {{time}}", + "drawioLng": "it", + "charset": "Set di caratteri", + "textType": "Tipo di testo", + "fileSaved": "File salvato.", + "failedToLoadFile": "Impossibile caricare il file: {{msg}}", + "monacoEditor": "Editor di codice Monaco", + "preparingOpenFile": "Preparazione all'apertura del file...", + "openWithDescription": "Seleziona un'applicazione per aprire il file .{{ext}}.", + "openWith": "Apri con", + "readOnly": "Sola lettura", + "save": "Salva", + "noMoreImages": "Nessuna immagine trovata nella pagina corrente.", + "imageViewer": "Visualizzatore immagini", + "logFileDeleteShare": "Eliminato un link di condivisione", + "logFileEditShare": "Modificato un link di condivisione", + "deleteShareWarning": "Sei sicuro di voler eliminare questo link di condivisione?", + "edit": "Modifica", + "editAndReactivate": "Modifica e riattiva", + "yes": "Sì", + "no": "No", + "permanentValid": "Permanente", + "manageShares": "Gestisci link di condivisione", + "manageDirectLinks": "Gestisci link diretti", + "deleteLinkConfirm": "Sei sicuro di voler eliminare questo link diretto?", + "directLinkNotFound": "Il link diretto che stai cercando non esiste.", + "versionNotFound": "La versione che stai cercando non esiste.", + "setNow": "Imposta ora", + "permissionNotSet": "Nessun permesso impostato", + "permissionNotSetDes": "Seguirà le impostazioni dei permessi della cartella padre o del link di condivisione.", + "permissions": "Permessi", + "logFileUpdateMetadata": "Aggiornati i metadati", + "all": "Tutto", + "updatesOnly": "Solo aggiornamenti", + "readsOnly": "Solo letture", + "myActivitiesOnly": "Solo le mie attività", + "logUpdateView": "Aggiornate le impostazioni di visualizzazione", + "logDeleteDirectLink": "Eliminato link diretto", + "logFileImported": "Importato da file esterno", + "logGetDirectLink": "Creato un <0>link diretto", + "logFileMount": "Monta policy di archiviazione su \"{{name}}\"", + "lookForThisVersion": "Cerca questa versione", + "logFileThumbGenerated": "Attivata generazione miniatura", + "logFileLivePhotoUploaded": "Caricata Live Photo", + "logFileCreate": "Creato questo oggetto", + "logFileRename": "Rinominato questo oggetto da \"{{originalName}}\" a \"{{newName}}\"", + "logFileSetPermission": "Cambiato permesso di questo oggetto", + "logFileEntityUpload": "Aggiornato contenuto del file", + "logFileCopyFrom": "Oggetto creato copiando da <0> a <1>", + "logFileCopyTo": "È stato copiato da <0> a <1>", + "logFileMoveTo": "Spostato da <0> a <1>", + "logFileMoveToTrash": "Spostato nel cestino da <0>", + "logFileShare": "Condiviso questo oggetto", + "logFileSetCurrentVersion": "Ripristinata versione del file a <0>", + "logFileDeleteVersion": "Eliminata la versione creata il <0>", + "logEntityDownloaded": "Scaricato o letto questo oggetto", + "logDirectLinkDownloaded": "Scaricato tramite un <0>link diretto", + "logRelocate": "Riposizionata policy di archiviazione a {{newPolicy}}", + "logCreateArchive": "Aggiunto al file di archivio <0>", + "logExtractArchive": "Estratto in <0>", + "deleteVersionWarning": "Sei sicuro di voler eliminare questa versione? Questa operazione non può essere annullata.", + "setAsCurrent": "Imposta come versione corrente", + "current": "[Corrente]", + "createdBy": "Creato da", + "manageVersions": "Gestisci versioni", + "livePhoto": "Live Photo", + "version": "Versione", + "actions": "Azioni", + "versionEntity": "Dati del file e versioni", + "data": "Dati", + "owned": "Posseduto", + "ownedSymbolic": "Posseduto (Link simbolico)", + "expires": "Scade", + "originalLocation": "Posizione originale", + "myPermissions": "I miei permessi", + "descendant": "Discendente", + "folderChildren": "{{files}} file, {{folders}} cartelle", + "moreThan": "Più di {{text}}", + "calculate": "Calcola", + "unset": "Non impostato", + "folder": "Cartella", + "file": "File", + "symbolicLink": "Link simbolico ({{srcType}})", + "type": "Tipo", + "storageUsed": "Archiviazione utilizzata", + "location": "Posizione", + "basicInfo": "Informazioni di base", + "format": "Formato", + "duration": "Durata", + "artist": "Artista", + "album": "Album", + "title": "Titolo", + "resolution": "Risoluzione", + "takenAt": "Scattata il", + "software": "Software", + "copyright": "Copyright", + "exposureBias": "Compensazione esposizione", + "flash": "Flash", + "copyToClipboard": "Copia negli appunti", + "searchSomething": "Cerca \"{{text}}\"...", + "iso": "ISO", + "exposureValue": "{{num}} s", + "exposure": "Esposizione", + "aperture": "Apertura", + "address": "Indirizzo", + "street": "Via", + "locality": "Località", + "place": "Città", + "district": "Distretto", + "region": "Provincia", + "country": "Paese", + "mediaInfo": "Info media", + "details": "Dettagli", + "activity": "Attività", + "goToSharedLink": "Vai al link condiviso", + "saveShortcut": "Salva link condiviso come collegamento", + "customizeIcon": "Personalizza icona", + "tags": "Tag", + "apply": "Applica", + "customizeColor": "Personalizza colore", + "folderColor": "Colore cartella", + "restore": "Ripristina", + "unpin": "Rimuovi pin", + "youDontHaveReadPermissionToThisFile": "Non hai il permesso di accesso.", + "anonymousAccessDenied": "Non hai il permesso di accesso, prova ad accedere.", + "sharedWithOthers": "Condiviso con altri", + "new": "Nuovo", + "open": "Apri", + "openParentFolder": "Vai alla cartella padre", + "download": "Scarica", + "batchDownload": "Scarica in lotti", + "share": "Condividi", + "rename": "Rinomina", + "organize": "Organizza", + "pin": "Fissa alla barra laterale", + "pinAlias": "Nome visualizzato", + "optional": "Opzionale", + "move": "Sposta", + "delete": "Elimina", + "moreActions": "Altre azioni", + "refresh": "Aggiorna", + "createArchive": "Crea file di archivio", + "newFolder": "Nuova cartella", + "newFile": "Nuovo file", + "showFullPath": "Mostra percorso completo", + "listView": "Lista", + "gridView": "Griglia", + "galleryView": "Galleria", + "paginationSize": "Paginazione", + "paginationOption": "{{option}} / pagina", + "noPagination": "Nessuna paginazione", + "sortMethod": "Ordina", + "sortMethods": { + "A-Z": "A-Z", + "Z-A": "Z-A", + "oldestUploaded": "Caricato prima", + "newestUploaded": "Caricato dopo", + "oldestModified": "Modificato prima", + "newestModified": "Modificato dopo", + "smallest": "Più piccolo", + "largest": "Più grande" + }, + "shareCreateBy": "Creato da {{nick}}", + "name": "Nome", + "size": "Dimensione", + "lastModified": "Ultima modifica", + "currentFolder": "Cartella corrente", + "backToParentFolder": "Torna alla cartella padre", + "folders": "Cartelle", + "files": "File", + "listError": "Impossibile elencare i file", + "dropFileHere": "Trascina e rilascia il file qui", + "orClickUploadButton": "Oppure clicca il pulsante \"Nuovo\" in alto a sinistra per aggiungere un file", + "nothingFound": "Nessun risultato trovato", + "uploadFiles": "Carica file", + "uploadFolder": "Carica cartella", + "newRemoteDownloads": "Nuovo download remoto", + "enter": "Entra", + "getSourceLink": "Ottieni link diretto", + "createRemoteDownloadForTorrent": "Nuovo download remoto", + "extractArchive": "Estrai archivio", + "createShareLink": "Condividi", + "viewDetails": "Visualizza dettagli", + "copy": "Copia", + "bytes": " ({{bytes}} Byte)", + "storagePolicy": "Policy di archiviazione", + "inheritedFromParent": "(Ereditato dal genitore)", + "childFolders": "Cartelle figlie", + "childFiles": "File figli", + "childCount": "{{num}}", + "parentFolder": "Cartella padre", + "rootFolder": "Cartella radice", + "modifiedAt": "Modificato il", + "createdAt": "Creato il", + "statisticAt": "Statistiche al", + "musicPlayer": "Lettore musicale", + "closeAndStop": "Chiudi e ferma", + "playInBackground": "Riproduci in background", + "copyTo": "Copia in", + "copyToDst": "Copia in <0>", + "moveTo": "Sposta in", + "moveToDst": "Sposta in <0>", + "errorReadFileContent": "Impossibile leggere il contenuto del file: {{msg}}", + "wordWrap": "A capo automatico", + "pdfLoadingError": "Impossibile caricare il PDF: {{msg}}", + "subtitleSwitchTo": "Sottotitoli cambiati a: {{subtitle}}", + "noSubtitleAvailable": "Nessun file di sottotitoli disponibile nella cartella video (supportati: ASS/SRT/VTT)", + "subtitle": "Sottotitoli", + "playlist": "Playlist", + "openInExternalPlayer": "Apri in lettore esterno", + "repeatMode": "Modalità ripetizione", + "listRepeat": "Ripetizione lista", + "singleRepeat": "Ripetizione singola", + "shuffle": "Casuale", + "playbackSpeed": "Velocità riproduzione", + "searchResult": "Risultati ricerca", + "preparingBathDownload": "Preparazione download in lotti...", + "preparingDownload": "Preparazione al download...", + "browserDownload": "Download lato browser in una cartella locale", + "browserDownloadDescription": "Il tuo browser scarica i file uno per uno mantenendo la struttura delle cartelle nella directory locale che specifichi.", + "browserBatchDownload": "Archiviazione lato browser", + "browserBatchDownloadDescription": "Scaricato e compresso in un file Zip dal browser in tempo reale, non può gestire dati superiori a 4GB.", + "serverBatchDownload": "Archiviazione lato server", + "serverBatchDownloadDescription": "Archiviato dal server in un file Zip e inviato al client per il download al volo, il collegamento di condivisione non è supportato.", + "selectArchiveMethod": "Seleziona metodo di archiviazione", + "batchDownloadStarted": "Il download in lotti è iniziato, non chiudere questa scheda...", + "batchDownloadError": "Impossibile archiviare: {{msg}}", + "userDenied": "Utente negato.", + "directoryDownloadReplace": "Sostituisci", + "directoryDownloadReplaceDescription": "Il file locale \"{{name}}\" sarà sostituito dal file scaricato.", + "directoryDownloadSkip": "Salta", + "directoryDownloadSkipDescription": "\"{{name}}\" sarà saltato.", + "selectDirectoryDuplicationMethod": "File duplicato", + "directoryDownloadReplaceAll": "Sostituisci tutto", + "directoryDownloadReplaceAllDescription": "Tutti i file con lo stesso nome saranno sostituiti dai file scaricati.", + "directoryDownloadSkipAll": "Salta tutto", + "directoryDownloadSkipAllDescription": "Tutti i file con lo stesso nome saranno saltati.", + "directoryDownloadStarted": "Download iniziato, non chiudere questa scheda.", + "directoryDownloadFinished": "Download completato, nessun oggetto fallito.", + "directoryDownloadFinishedWithError": "Download completato, {{failed}} oggetti falliti.", + "directoryDownloadPermissionError": "Permesso negato, consenti la lettura e scrittura dei file locali.", + "back": "Indietro", + "view": "Visualizza", + "layout": "Layout", + "thumbnails": "Miniature", + "on": "Attivo", + "off": "Disattivo", + "viewSetting": "Impostazioni visualizzazione", + "saved": "Salvato", + "notSet": "Non impostato", + "deleteViewSetting": "Elimina impostazioni visualizzazione" + }, + "modals": { + "includePasswordInShareLink": "Includi password nel link di condivisione", + "includePasswordInShareLinkDes": "Se selezionato, la password sarà inclusa nel link di condivisione e non sarà richiesta password quando si accede al link di condivisione.", + "showFileName": "Mostra nome file", + "forceDownload": "Forza download", + "archiveFile": "File di archivio", + "cancelDownload": "Annulla download", + "always": "Sempre", + "justOnce": "Solo una volta", + "quality": "Qualità", + "saveAsOtherFormat": "Salva in altro formato", + "conflictDes1": "Conflitto di versione del file, possibili ragioni sono:", + "conflictDes2": "<0>Il file è stato aggiornato a una nuova versione da altrove dopo che l'hai aperto.<1>Se l'hai salvato con un nuovo nome o in una nuova posizione, il nome del file esiste già.", + "saveAs": "Salva come", + "versionConflict": "Conflitto di versione", + "overwrite": "Sovrascrivi", + "editShareLink": "Modifica link di condivisione", + "clearPermissions": "Pulisci impostazioni permessi", + "shortcutCreated": "Collegamento creato.", + "createShortcut": "Crea collegamento", + "createShortcutTo": "Crea collegamento in <0>", + "read": "Leggi", + "readDes": "Per i file, puoi visualizzare il loro contenuto, metadati, ecc.; per le cartelle, puoi visualizzare l'elenco dei file figli e i loro metadati.", + "createDes": "Valido solo per le cartelle, puoi creare o caricare nuovi file al suo interno e spostare o copiare file in essa.", + "update": "Aggiorna", + "updateDes": "Puoi modificare i metadati, rinominare oggetti e visualizzare i log delle attività; per i file, puoi aggiornare il loro contenuto.", + "delete": "Elimina", + "deleteDes": "Puoi eliminare oggetti o spostarli in altri posti.", + "noAccess": "Nessun accesso", + "targetExisted": "Il target esiste già.", + "explicitAccess": "Accesso esplicito", + "generalAccess": "Accesso generale", + "users": "Utenti", + "groups": "Gruppi", + "builtinCollections": "Collezioni integrate", + "everyone": "Tutti gli altri", + "otherGroup": "Altri gruppi", + "sameGroup": "Stesso gruppo di me", + "anonymous": "Visitatori anonimi", + "noResults": "Nessun risultato", + "searchGroupUser": "Cerca email o gruppi...", + "resetToDefault": "Ripristina predefinito", + "duplicateTag": "Il tag \"{{tag}}\" esiste già.", + "colorForTag": "Personalizza colore per nuovi tag", + "enterForNewTag": "Premi invio per aggiungere nuovo tag.", + "manageTags": "Gestisci tag", + "onlyOwner": "Solo il proprietario di questo file può forzare lo sblocco.", + "forceUnlock": "Forza sblocco", + "forceUnlockAll": "Forza sblocco tutto", + "forceUnlockDes": "Forzare lo sblocco può corrompere lo stato del file, raccomandiamo di aspettare che il file venga rilasciato proattivamente, sei sicuro di continuare lo sblocco?", + "webdav": "WebDAV", + "soft-delete": "Sposta nel cestino", + "updateMetadata": "Aggiorna metadati", + "upload": "Carica", + "moveCopy": "Sposta o copia", + "view": "Visualizza", + "cannotPerformAction": "Spostare o copiare file qui non è supportato.", + "cannotMoveCopyToChild": "Impossibile spostare o copiare nella cartella discendente.", + "copySuccess": "{{num}} file copiati con successo.", + "moveSuccess": "{{num}} file spostati con successo.", + "setPermission": "Imposta permesso", + "unknownParent": "Genitore sconosciuto", + "unknownParentDes": "La cartella occupata è la cartella genitore di una cartella condivisa e non è di tua proprietà.", + "lockConflictTitle": "File occupato", + "lockConflictDescription": "Questa operazione non può essere completata perché i seguenti file sono attualmente utilizzati da altri, riprova più tardi. Se sei il proprietario del file e sei sicuro che il file non sia in uso, puoi forzare lo sblocco del file e riprovare.", + "usedBy": "Utilizzato da", + "application": "Applicazione", + "errorDetailsTitle": "Dettagli errore", + "processingMoving": "Spostamento file...", + "processingCopying": "Copia file...", + "processingRestoring": "Ripristino file...", + "fileRestored": "{{num}} file ripristinati nella loro posizione originale.", + "duplicatedObjectName": "Nome file duplicato.", + "newNameLengthError": "La lunghezza del nome file deve essere compresa tra 1 e 255.", + "newNameCharacterError": "Il nome non deve contenere nessuno di questi caratteri: \\ / : * ? \" < > |", + "newNameDotError": "Il nome non può essere \".\" o \"..\"", + "taskCreated": "Attività creata.", + "taskCreateFailed": "{{failed}} attività non sono riuscite ad essere create: {{details}}.", + "linkCopied": "Link copiato.", + "getSourceLinkTitle": "Ottieni link diretto", + "sourceLink": "Link diretto", + "folderName": "Nome cartella", + "create": "Crea", + "fileName": "Nome file", + "renameDescription": "Inserisci il nuovo nome per <0>{{name}}:", + "newName": "Nuovo nome", + "moveToDescription": "Sposta in <0>{{name}}", + "saveToTitle": "Salva in", + "saveToTitleDescription": "Salva in <0>{{name}}", + "deleteTitle": "Elimina oggetti", + "deleteOneDescription": "Sei sicuro di voler spostare <0>{{name}} nel cestino?", + "deleteMultipleDescription": "Sei sicuro di voler spostare questi {{num}} oggetti nel cestino?", + "deleteOneDescriptionHard": "Sei sicuro di voler eliminare permanentemente <0>{{name}}?", + "trashRetention": "I file nel cestino saranno eliminati dopo <0>{{num}}.", + "deleteMultipleDescriptionHard": "Sei sicuro di voler eliminare permanentemente questi {{num}} oggetti?", + "newRemoteDownloadTitle": "Nuova attività di download remoto", + "remoteDownloadURL": "URL target del download", + "remoteDownloadURLDescription": "Incolla l'URL di download, un URL per riga", + "remoteDownloadDst": "Scarica in", + "processNode": "Nodo target", + "remoteDownloadNodeAuto": "Assegnazione automatica", + "createTask": "Crea attività", + "downloadToDst": "Scarica in <0>{{name}}", + "downloadTo": "Scarica in", + "decompressTo": "Estrai in", + "decompressToDst": "Estrai in <0>{{name}}", + "defaultEncoding": "Predefinito", + "chineseMajorEncoding": "Codifiche comuni cinesi semplificate", + "selectEncoding": "Codifica file ZIP", + "password": "Password", + "passwordDescription": "Se il file di archivio non è crittografato, lascia questo campo vuoto.", + "noEncodingSelected": "Nessun metodo di codifica selezionato", + "listingFiles": "Elenco file...", + "listingFileError": "Impossibile elencare i file: {{message}}", + "generatingSourceLinks": "Generazione link sorgente...", + "noFileCanGenerateSourceLink": "Non c'è nessun file che può essere utilizzato per generare link sorgente", + "sourceBatchSizeExceeded": "Il gruppo utente corrente può generare link sorgente per un massimo di {{limit}} file contemporaneamente.", + "zipFileName": "Nome file di archivio", + "shareLinkShareContent": "Ho condiviso con te: {{name}} Link: {{link}}", + "shareLinkPasswordInfo": "Password: {{password}}", + "createShareLink": "Crea link di condivisione", + "privateShare": "Proteggi con password", + "privateShareDes": "Se selezionato, è richiesta una password per accedere al link di condivisione.", + "useCustomPassword": "Password personalizzata per link di condivisione", + "shareView": "Condividi impostazione visualizzazione", + "shareViewDes": "Se selezionato, altri utenti possono vedere le tue impostazioni di visualizzazione (layout, ordinamento, ecc.) salvate sul server quando accedono a questa cartella condivisa.", + "showReadme": "Mostra file README", + "showReadmeDes": "Se selezionato, il file <0>README.md (case-sensitive) nella directory sarà automaticamente mostrato ai visitatori.", + "expireAfterDownload": "Scade dopo essere stato scaricato", + "sharePassword": "Password condivisione", + "randomlyGenerate": "Casuale", + "expireAutomatically": "Scadenza automatica", + "downloadLimitOptions": "{{num}} download", + "or": "O dopo", + "5minutes": "5 minuti", + "1hour": "1 ora", + "1day": "1 giorno", + "7days": "7 giorni", + "30days": "30 giorni", + "custom": "Personalizzato", + "minutes": "minuti", + "downloads": "download", + "expireSuffix": "", + "expirePrefix": "Scade dopo", + "allowPreview": "Abilita anteprima", + "allowPreviewDescription": "Se consentire l'anteprima del contenuto del file dal link di condivisione", + "shareLink": "Link di condivisione", + "sendLink": "Invia il link", + "directoryDownloadReplaceNotifiction": "Sovrascrivi {{name}}", + "directoryDownloadSkipNotifiction": "Saltato {{name}}", + "directoryDownloadTitle": "Log download in lotti", + "directoryDownloadStarted": "Inizia download \"{{name}}\"", + "directoryDownloadFinished": "Download completato \"{{name}}\"", + "directoryDownloadError": "Errore: {{msg}}", + "directoryDownloadErrorNotification": "Si è verificato un errore durante il download di {{name}}: {{msg}}", + "directoryDownloadAutoscroll": "Scorrimento automatico", + "directoryDownloadCancelled": "Download annullato", + "advanceOptions": "Opzioni avanzate", + "skipSoftDelete": "Elimina permanentemente", + "skipSoftDeleteDes": "Salta lo spostamento nel cestino, elimina permanentemente", + "unlinkOnly": "Mantieni file fisici", + "unlinkOnlyDes": "Elimina solo i record dei file, i file fisici non saranno eliminati." + }, + "uploader": { + "fileCopyName": "Copia di ", + "overwriteTooltip": "Sovrascrivi il file esistente se c'è conflitto, funziona solo per le attività appena aggiunte.", + "rename": "Riprova con nuovo nome", + "overwrite": "Sovrascrivi file esistente", + "pasteFilesHere": "Incolla file qui", + "clipboardDefaultFileName": "Appunti {{date}}.png", + "uploadFromClipboard": "Carica dagli appunti", + "uploadList": "Attività di caricamento", + "fileNotMatchError": "Il file selezionato non corrisponde al file originale.", + "unknownError": "Si è verificato un errore sconosciuto: {{msg}}", + "taskListEmpty": "Nessuna attività di caricamento.", + "hideTaskList": "Nascondi l'elenco", + "uploadTasks": "Attività di caricamento", + "moreActions": "Altre azioni", + "addNewFiles": "Aggiungi nuovi file", + "toggleTaskList": "Espandi/Comprimi l'elenco", + "pendingInQueue": "In attesa in coda...", + "preparing": "Preparazione...", + "processing": "Elaborazione...", + "progressDescription": "{{uploaded}} caricati, {{total}} totale - {{percentage}}%", + "progressDescriptionFull": "{{uploaded}} caricati, {{total}} totale - {{percentage}}% ({{speed}})", + "progressDescriptionPlaceHolder": " - caricati", + "uploaded": "Caricato", + "rootFolder": "Cartella radice", + "unknownStatus": "Sconosciuto", + "resumed": "Ripreso", + "resumable": "Riprendibile", + "retry": "Riprova", + "deleteTask": "Elimina attività", + "cancelAndDelete": "Annulla ed elimina", + "selectAndResume": "Seleziona lo stesso file e riprendi il caricamento", + "fileName": "Nome: ", + "fileSize": "Dimensione: ", + "sessionExpiredIn": "Scade <0>", + "chunkDescription": "({{total}} chunk, {{size}} ciascuno)", + "noChunks": "(Nessun chunk)", + "destination": "Destinazione: ", + "uploadSession": "Sessione di caricamento: ", + "storagePolicy": "Policy di archiviazione: ", + "errorDetails": "Dettagli errore: ", + "uploadSessionCleaned": "Tutte le sessioni di caricamento pulite.", + "hideCompletedTooltip": "Nascondi attività completate, fallite e annullate.", + "hideCompleted": "Nascondi attività completate", + "addTimeAscTooltip": "Le attività aggiunte per prime sono classificate per prime.", + "addTimeAsc": "Dal più vecchio al più nuovo", + "addTimeDescTooltip": "Le ultime attività aggiunte sono classificate per prime.", + "addTimeDesc": "Dal più nuovo al più vecchio", + "showInstantSpeedTooltip": "Le velocità di caricamento delle attività sono mostrate come velocità istantanea.", + "showInstantSpeed": "Velocità istantanea", + "showAvgSpeedTooltip": "Le velocità di caricamento delle attività sono mostrate come velocità media.", + "showAvgSpeed": "Velocità media", + "cleanAllSessionTooltip": "Pulisci tutte le sessioni di caricamento in sospeso sul lato server.", + "cleanAllSession": "Pulisci tutte le sessioni di caricamento", + "cleanCompletedTooltip": "Pulisci attività completate, fallite e annullate", + "cleanCompleted": "Pulisci attività completate", + "retryFailedTasks": "Riprova tutte le attività fallite", + "retryFailedTasksTooltip": "Riprova tutte le attività fallite nella coda corrente", + "setConcurrentTooltip": "Imposta il numero massimo di attività che possono essere caricate simultaneamente.", + "setConcurrent": "Imposta limite attività concorrenti", + "sizeExceedLimitError": "La dimensione del file supera i limiti della policy di archiviazione. (Massimo: {{max}})", + "suffixNotAllowedError": "La policy di archiviazione non supporta il caricamento di file con questa estensione.", + "regexpNotAllowedError": "La policy di archiviazione non supporta il caricamento di file con questo nome.", + "suffixAllowed": " (Supportati:{{supported}})", + "suffixDenied": " (Negati:{{denied}})", + "createUploadSessionError": "Impossibile creare la sessione di caricamento", + "deleteUploadSessionError": "Impossibile eliminare la sessione di caricamento", + "requestError": "Richiesta fallita: {{msg}} ({{url}}).", + "chunkUploadError": "Impossibile caricare il chunk [{{index}}].", + "conflictError": "L'attività di caricamento per file con lo stesso nome è già in elaborazione.", + "chunkUploadErrorWithMsg": "Caricamento chunk fallito: {{msg}}", + "chunkUploadErrorWithRetryAfter": "(Riprova dopo {{retryAfter}}s)", + "emptyFileError": "Il caricamento di file vuoti su OneDrive non è supportato, crea file vuoti tramite il pulsante Crea File.", + "finishUploadError": "Impossibile completare il caricamento del file.", + "finishUploadErrorWithMsg": "Impossibile completare il caricamento del file: {{msg}}", + "ossFinishUploadError": "Impossibile completare il caricamento del file: {{msg}} ({{code}})", + "cosUploadFailed": "Caricamento fallito: {{msg}} ({{code}})", + "upyunUploadFailed": "Caricamento fallito: {{msg}}", + "parseResponseError": "Impossibile analizzare la risposta: {{msg}} ({{content}})", + "concurrentTaskNumber": "Limite attività concorrenti", + "dropFileHere": "Rilascia file per caricare" + }, + "share": { + "free": "Gratuito", + "price": "Prezzo", + "points": "{{num}} Punti", + "statistics": "Statistiche", + "expireAt": "Scade <0>", + "expireAfterDownloads": "Scade dopo {{downloads}} download", + "somebodyShare": "Condiviso da {{name}}", + "expiredLink": "Condivisione scaduta", + "sharedBy": "<0>{{nick}} ha condiviso $t(share.files, {\"count\": {{num}} }) con te.", + "files": "1 file", + "files_other": "{{count}} file", + "statisticsViews": "$t(share.views, {\"count\": {{views}} })", + "statisticsDownloads": "$t(share.downloads, {\"count\": {{downloads}} })", + "views": "{{count}} visualizzazione", + "views_other": "{{count}} visualizzazioni", + "downloads": "{{count}} download", + "downloads_other": "{{count}} download", + "privateShareTitle": "Condivisione privata da {{nick}}", + "enterPassword": "Password condivisione", + "continue": "Continua", + "shareCanceled": "Il link di condivisione è stato eliminato.", + "listLoadingError": "Impossibile caricare.", + "sharedFiles": "File condivisi", + "createdAtDesc": "Più recenti", + "createdAtAsc": "Più vecchi", + "noRecords": "Nessun file condiviso.", + "sourceNotFound": "[Sorgente non esistente]", + "expired": "Scaduto", + "changeToPublic": "Rendi pubblico", + "changeToPrivate": "Rendi privato", + "viewPassword": "Visualizza password", + "disablePreview": "Disabilita anteprima", + "enablePreview": "Abilita anteprima", + "cancelShare": "Annulla condivisione", + "sharePassword": "Password condivisione", + "readmeError": "Impossibile caricare README: {{msg}}", + "enterKeywords": "Inserisci parole chiave di ricerca.", + "searchResult": "Risultati ricerca", + "sharedAt": "Condiviso il <0>", + "pleaseLogin": "Effettua prima il login.", + "cannotShare": "Questo file non può essere visualizzato in anteprima.", + "preview": "Anteprima", + "incorrectPassword": "Password non corretta.", + "shareNotExist": "Il link di condivisione non è valido o è scaduto.", + "copyLinkToClipboard": "Copia link negli appunti" + }, + "download": { + "noFilesFound": "Nessun file trovato", + "filterByName": "Filtra per nome", + "selectAll": "Seleziona tutto", + "reverseSelect": "Selezione inversa", + "saveChanges": "Salva modifiche", + "cancelTaskConfirm": "Sei sicuro di voler annullare questa attività di download?", + "failedToLoad": "Impossibile caricare.", + "active": "Attivo", + "finished": "Completato", + "activeEmpty": "Nessuna attività di download in corso.", + "finishedEmpty": "Nessuna attività di download completata.", + "loadMore": "Carica altro", + "taskFileDeleted": "File eliminato.", + "unknownTaskName": "[Sconosciuto]", + "taskCanceled": "Attività di download annullata, lo stato sarà aggiornato più tardi", + "operationSubmitted": "Operazione inviata, lo stato sarà aggiornato più tardi", + "deleteThisFile": "Elimina questo file", + "openDstFolder": "Apri cartella target", + "selectDownloadingFile": "Seleziona file da scaricare", + "cancelTask": "Annulla", + "updatedAt": "Aggiornato il: ", + "uploaded": "Caricato", + "uploadSpeed": "Velocità caricamento", + "InfoHash": "InfoHash", + "seederCount": "Seeder:", + "seeding": "Seeding: ", + "downloadNode": "Nodo: ", + "isSeeding": "Sì", + "notSeeding": "No", + "chunkSize": "Dimensione chunk:", + "chunkNumbers": "Chunk", + "taskDeleted": "Attività eliminata.", + "transferFailed": "Impossibile trasferire i file.", + "downloadFailed": "Download fallito: {{msg}}", + "canceledStatus": "Annullato", + "finishedStatus": "Completato", + "pending": "Completato, trasferimento in coda", + "transferring": "Completato, trasferimento in corso", + "deleteRecord": "Elimina record", + "createdAt": "Creato il: ", + "unknownSize": "Dimensione file sconosciuta" + }, + "setting": { + "notifyStoragePolicyChange": "Notificami per il cambio di policy di archiviazione", + "notifyStoragePolicyChangeDes": "Quando abilitato, verrà mostrata una notifica quando si entra in una directory associata a una policy di archiviazione diversa.", + "treeView": "Vista ad albero", + "autoExpandTreeView": "Espandi automaticamente vista ad albero", + "autoExpandTreeViewDes": "Quando abilitato, l'albero dei file nella barra laterale seguirà la directory corrente e si espanderà automaticamente.", + "syncView": "Impostazioni visualizzazione", + "syncViewDes": "Ricorda le impostazioni di visualizzazione di ogni directory e sincronizza sul server.", + "syncViewOn": "Sincronizza sul server", + "syncViewOff": "Non sincronizzare", + "reason": "Motivo", + "change": "Cambiamento", + "success": "Successo", + "loginWithPasskey": "Chiave d'accesso - {{name}}", + "loginWith": "Accedi con", + "result": "Risultato", + "device": "Dispositivo", + "ip": "IP", + "time": "Ora", + "recentSignIn": "Attività di accesso recenti", + "noAuthenticator": "Aggiungi una chiave d'accesso per accedere usando impronta digitale, volto o chiave USB.", + "neverUsed": "Mai utilizzato", + "usedAt": "Ultimo utilizzo il <0>", + "passkeyName": "{browser} su {os}", + "passwordlessHint": "Questo account è senza password.", + "versionRetentionMax": "Numero massimo di versioni, 0 significa nessun limite.", + "versionRetentionEnabledExt": "Estensioni file abilitate", + "versionRetentionEnabledExtDes": "Premi invio per aggiungere, lascia vuoto per abilitare per tutti i file", + "enableVersionRetention": "Abilita conservazione versioni", + "enableVersionRetentionDes": "Se abilitato, le versioni storiche dei file che soddisfano le condizioni saranno conservate.", + "versionRetention": "Conservazione versioni", + "languageDes": "Seleziona la lingua di visualizzazione e la lingua email preferita.", + "timezoneDes": "Imposta il fuso orario di visualizzazione, predefinito è il fuso orario del sistema", + "unlinkConfirm": "Sei sicuro di voler scollegare questo account?", + "notLinked": "Non collegato", + "linkedAt": "Collegato il <0>", + "accountLinking": "Collegamento account", + "nickNameDes": "Questo è il tuo nome pubblico. Può essere il tuo vero nome o uno pseudonimo.", + "cropAvatar": "Ritaglia avatar", + "finance": "Finanze", + "preference": "Preferenze", + "accountCreatedAt": "Creato il <0>", + "shoeQr": "Mostra", + "deviceNothing": "WebDAV non è supportato nel tuo gruppo utente.", + "connectionInfo": "Dettagli connessione", + "proxyTooltip": "Proxy per tutte le richieste di download dei file.", + "readonlyTooltip": "L'utente può solo leggere i file attraverso questo account.", + "blockSysFilesUpload": "Blocca caricamento file di sistema", + "blockSysFilesUploadTooltip": "Quando abilitato, i file che iniziano con <0>. verranno bloccati dal caricamento.", + "rootFolderIn": "Seleziona <0>", + "createWebDavAccount": "Crea account WebDAV", + "editWebDavAccount": "Modifica {{name}}", + "seeding": "Seeding", + "awaitSeeding": "Aspetta seeding", + "awaitSeedingDes": "Aspetta completamento seeding.", + "downloadTransferDes": "Trasferisci file alla destinazione.", + "downloadDes": "Scarica file desiderati.", + "retryErrorHistory": "Cronologia errori tentativi", + "retryCount": "Tentato", + "resumeAt": "Riprendi a", + "executeDuration": "Durata esecuzione", + "input": "Input", + "output": "Output", + "suspended": " (Sospeso)", + "updatedAt": "Aggiornato il", + "taskDetails": "Dettagli attività", + "partialSuccessWarning": "Impossibile elaborare {{num}} oggetto/i, sono stati saltati.", + "sendTask": "Invia attività", + "sendTaskDes": "Invia l'attività a un nodo per l'elaborazione.", + "downloaded": "Scaricato", + "importingFiles": "Importa file", + "importingFilesDes": "Indicizza file e importali nella cartella specificata.", + "importedFiles": "File importati", + "indexedFiles": "File indicizzati", + "extractedFiles": "File estratti", + "extractedFilesSize": "Dimensione file estratti", + "extractingFiles": "Estrazione file", + "extractingFilesDes": "Estrai tutti i file nella cartella specificata.", + "downloadingZip": "Scarica archivio", + "downloadingZipDes": "Scarica archivio nell'area di lavoro temporanea.", + "progressNotAvailable": "Progresso non ancora disponibile.", + "uploadedSize": "Dimensione riposizionata", + "archivedFiles": "File elaborati", + "transferredFiles": "File riposizionati", + "archivedFilesSize": "Dimensione file elaborati", + "createArchiveFinishing": "Conferma modifiche per nuovi file", + "indexForArchiveDes": "Indicizza per file da archiviare.", + "prepare": "Prepara", + "preparingWorkspaceDes": "Prepara area di lavoro temporanea.", + "compressFiles": "Crea archivio", + "compressFilesDes": "Crea archivio nell'area di lavoro temporanea.", + "uploadArchiveFileDes": "Trasferisci file di archivio nella cartella target.", + "uploadWorker": "Worker caricamento #{{num}}", + "relocatedEntities": "Entità riposizionate", + "queueToStart": "In coda per iniziare", + "indexingFiles": "Indicizza file", + "indexingFilesDes": "Indicizza per file da riposizionare, bloccali.", + "transferring": "Trasferimento", + "transferringRelocateDes": "Trasferisci dati alla nuova policy di archiviazione.", + "committingChanges": "Conferma modifiche", + "relocateFinishing": "Aggiorna riferimento entità alla nuova policy di archiviazione.", + "autoRefresh": "Aggiornamento automatico", + "avatarUpdated": "L'avatar è stato aggiornato e avrà effetto con un ritardo.", + "nickChanged": "Nickname cambiato e avrà effetto dopo l'aggiornamento.", + "settingSaved": "Impostazione salvata.", + "themeColorChanged": "Colore tema cambiato.", + "profile": "Profilo", + "avatar": "Immagine profilo", + "uid": "UID", + "nickname": "Nome visualizzato", + "group": "Gruppo", + "regTime": "Data registrazione", + "security": "Password e sicurezza", + "profilePage": "Profilo pubblico", + "publicShareOnly": "Solo condivisioni pubbliche", + "publicShareOnlyDes": "Mostra solo condivisioni senza password nella pagina del profilo.", + "allShare": "Tutte le condivisioni", + "allShareDes": "Mostra tutte le condivisioni nella pagina del profilo (incluse quelle protette da password). Gli utenti devono ancora inserire una password per accedervi.", + "hideShare": "Nascondi tutte le condivisioni", + "hideShareDes": "Nascondi tutte le condivisioni nella pagina del profilo.", + "userHideShare": "L'utente ha nascosto l'elenco delle condivisioni", + "accountPassword": "Password", + "2fa": "Autenticazione 2FA", + "enabled": "Abilitato", + "disabled": "Disabilitato", + "appearance": "Aspetto", + "themeColor": "Colore tema", + "darkMode": "Modalità scura", + "syncWithSystem": "Sistema", + "fileList": "Elenco file", + "timeZone": "Fuso orario", + "webdavServer": "Server", + "userName": "Nome utente", + "manageAccount": "Gestisci account", + "uploadImage": "Carica da file", + "useGravatar": "Usa Gravatar ", + "changeNick": "Cambia nickname", + "originalPassword": "Password corrente", + "enable2FA": "Abilita autenticazione 2FA", + "disable2FA": "Disabilita autenticazione 2FA", + "2faDescription": "Utilizza qualsiasi app mobile 2FA o software di gestione password che supporta 2FA per scansionare il codice QR per aggiungere questo sito. Dopo la scansione, inserisci il codice di verifica a 6 cifre fornito dall'app 2FA per abilitare 2FA.", + "inputCurrent2FACode": "Inserisci il codice di verifica 2FA corrente.", + "timeZoneCode": "Codice fuso orario IANA", + "authenticatorRemoved": "Autenticatore rimosso.", + "authenticatorAdded": "Autenticatore aggiunto.", + "browserNotSupported": "Non supportato dal browser o ambiente corrente.", + "removedAuthenticator": "Rimuovi autenticatore", + "removedAuthenticatorConfirm": "Sei sicuro di voler rimuovere questo autenticatore?", + "addNewAuthenticator": "Aggiungi una chiave d'accesso", + "hardwareAuthenticator": "Autenticatore hardware", + "copied": "Copiato negli appunti.", + "pleaseManuallyCopy": "Il browser corrente non supporta, copia manualmente.", + "webdavAccounts": "Account WebDAV", + "webdavHint": "Server WebDAV: {{url}}; Nome utente: {{name}} ; La password è la password dell'account creato sotto.", + "annotation": "Annotazione", + "rootFolder": "Cartella radice relativa", + "createdAt": "Creato il", + "action": "Azioni", + "readonlyOn": "Sola lettura", + "readonlyOff": "Lettura e scrittura", + "proxy": "Proxy inverso", + "none": "Nessuno", + "proxied": "Con proxy", + "delete": "Elimina", + "listEmpty": "Nessun record.", + "createNewAccount": "Crea nuovo account", + "taskType": "Tipo attività", + "taskStatus": "Stato", + "taskProgress": "Progresso attività", + "errorDetails": "Dettagli errore", + "queueing": "In coda", + "processing": "Elaborazione", + "failed": "Fallito", + "canceled": "Annullato", + "finished": "Completato", + "fileTransfer": "Trasferimento file", + "fileRecycle": "Riciclo file", + "importFiles": "Importa file esterni", + "transferProgress": "{{num}} file completati", + "waiting": "In attesa", + "compressing": "Compressione", + "decompressing": "Decompressione", + "downloading": "Download", + "indexing": "Indicizzazione", + "listing": "Inserimento", + "allShares": "Condiviso", + "trendingShares": "Di tendenza", + "totalShares": "Condivisioni create", + "fileName": "Nome file", + "shareDate": "Condiviso il", + "downloadNumber": "Download", + "viewNumber": "Visualizzazioni", + "language": "Lingua", + "iOSApp": "App iOS/iPadOS", + "connectByiOS": "Connetti a <0>{{title}} tramite dispositivi iOS/iPadOS.", + "downloadOurApp": "Scarica la nostra APP:", + "fillInEndpoint": "Scansiona il codice QR sottostante con la nostra App (NON usare altre app per scansionare):", + "loginApp": "Ora puoi iniziare a usare l'App. Se incontri problemi con il codice QR, puoi anche provare a inserire manualmente nome utente e password per accedere.", + "relocateFileTo": "Riposiziona policy di archiviazione a {{policy}} per <0>{{more}}", + "extractFileTo": "Estrai <0>{{more}} in <1>", + "createArchiveTo": "Crea file di archivio in <1> per <0>{{more}}", + "importFileTo": "Importa file da {{policy}} in <0>" + }, + "vas": { + "points": "Punti", + "paid": "Pagato", + "fulfillFailedStatus": "Impossibile adempiere", + "unpaid": "Non pagato", + "amount": "Importo", + "tradeNo": "N. Transazione", + "payments": "Pagamenti", + "creditReasonShareGain": "Link di condivisione acquistato", + "creditReasonSharePay": "Acquisto nel negozio", + "creditReasonRecharge": "Ricarica", + "creditChanges": "Modifiche crediti", + "payXPoints": "Paga con <0>", + "pointsPayAvailable": "Questo prodotto supporta il pagamento con punti, puoi scegliere di pagare <0> nel prossimo passaggio.", + "payAmount": "Paga {{price}}", + "purchaseSomething": "Acquista {{name}}", + "redeem": "Riscatta", + "shop": "Negozio", + "resumeTicket": "Biglietto di ripristino", + "resumeTicketDes": "Puoi trovarlo nell'email di conferma dell'ordine inviata dopo il pagamento.", + "restorePurchase": "Ripristina acquisto", + "restorePurchaseDes": "Ripristina l'acquisto con il \"Biglietto di ripristino\" nell'email di conferma dell'ordine.", + "paymentSuccess": "Pagamento riuscito", + "fulfillFailed": "Impossibile adempiere all'ordine, contatta l'amministratore del sito.", + "paidButton": "Pagamento completato", + "payInNewWindow": "Completa il pagamento nella nuova finestra. Non chiudere questa pagina prima che il pagamento sia completato. Se la nuova finestra non si apre, <0>clicca qui.", + "paymentFailedTitle": "Impossibile elaborare il pagamento", + "paymentEmailHelper": "È richiesta un'email per inviare la ricevuta di acquisto, dato che non sei connesso.", + "payEquivalentCash": "Paga contanti equivalenti: {{num}}", + "payWithCash": "Paga con contanti", + "recharge": "Ricarica", + "pointsBalance": "Saldo punti: {{num}}", + "loginRequired": "Accesso richiesto", + "payWithPoints": "Paga con punti", + "purchaseLogin": "Per favore <0>accedi prima di continuare l'acquisto.", + "noAvailableSharePurchaseMethod": "Nessun metodo di acquisto disponibile.", + "purchaseShareLink": "Acquista link di condivisione", + "loginWith": "Accedi con {{name}}", + "sso": "SSO", + "qq": "QQ", + "quota": "Quota", + "exceedQuota": "La tua capacità utilizzata ha superato la quota, elimina i file extra o acquista più spazio di archiviazione il prima possibile.", + "extendStorage": "Acquista archiviazione", + "folderPolicySwitched": "La policy di archiviazione per la cartella corrente è cambiata a \"{{name}}\"", + "switchFolderPolicy": "Cambio policy di archiviazione cartella", + "setPolicyForFolder": "Imposta la policy di archiviazione per la cartella corrente: ", + "manageMount": "Gestisci mount", + "saveToMyFiles": "Salva nei miei file", + "report": "Segnala abuso", + "reportTarget": "Target segnalazione", + "reportReason": "Motivo", + "reportReasonOptions": ["Violazione copyright", "Contenuto dannoso", "Spam", "Altro"], + "reportDescription": "Descrizione aggiuntiva", + "reportAbuseSuccess": "Segnalazione inviata.", + "migrateStoragePolicy": "Migra policy di archiviazione", + "fileSaved": "File salvati.", + "sharePurchaseTitle": "Devi pagare <0> prima di accedere a questo link.", + "payToDownload": "Paga per scaricare", + "creditToBePaid": "Punti da pagare", + "creditGainPredict": "Guadagna {{num}} punti per ogni acquisto", + "creditPrice": " (Costo {{num}} crediti)", + "creditFree": " (Crediti gratuiti)", + "cancelSubscription": "La cancellazione è avvenuta con successo e il cambiamento avrà effetto in alcuni minuti o ore.", + "qqUnlinked": "Scollegato dall'account QQ.", + "groupExpire": "(Scade il <0>)", + "manuallyCancelSubscription": "Disiscriviti dal gruppo utente corrente", + "qqAccount": "Account QQ", + "connect": "Connetti", + "unlink": "Scollega", + "credits": "Crediti", + "cancelSubscriptionTitle": "Disiscrivi", + "cancelSubscriptionWarning": "Tornerai al gruppo utente iniziale e i crediti pagati non sono rimborsabili, sei sicuro di voler continuare?", + "mountPolicy": "Monta policy di archiviazione", + "mountDescription": "Dopo aver montato una policy di archiviazione a una cartella, i nuovi file caricati in questa cartella o sottocartelle saranno archiviati usando la policy di archiviazione montata. Copiare e spostare in questa cartella non applicherà la policy di archiviazione montata; quando sono specificate più cartelle padre, sarà selezionata la policy di archiviazione della cartella padre più vicina.", + "mountNewFolder": "Monta nuova cartella", + "nsfw": "NSFW", + "malware": "Malware", + "copyright": "Copyright", + "inappropriateStatements": "Dichiarazioni inappropriate", + "other": "Altro", + "groupBaseQuota": "Quota base gruppo - {{size}}", + "validPackQuota": "Pacchetti archiviazione - {{size}}", + "used": "Utilizzato - {{size}}", + "total": "Totale - {{size}}", + "validStorage": "Archiviazione extra valida", + "buyStorage": "Acquista archiviazione", + "useGiftCode": "Riscatta con codice regalo", + "packName": "Nome pacchetto", + "activationDate": "Data attivazione", + "validDuration": "Durata", + "expiredAt": "Scade il", + "days": "{{num}} giorni", + "pleaseInputGiftCode": "Inserisci il codice regalo.", + "pleaseSelectAStoragePack": "Seleziona un pacchetto di archiviazione.", + "paymentMethod": "Paga con", + "noAvailableMethod": "Nessun metodo di pagamento disponibile", + "alipay": "Alipay", + "wechatPay": "Wechat Pay", + "payByCredits": "Crediti", + "purchaseDuration": "Durata", + "creditsNum": "Qtà crediti:", + "store": "Negozio", + "storageExpansion": "Espansione archiviazione", + "membership": "Iscrizioni", + "buyCredits": "Crediti", + "subtotal": "Subtotale: ", + "creditsTotalNum": "{{num}} crediti", + "purchaseNow": "Acquista ora", + "recommended": "Raccomandato", + "enterGiftCode": "Inserisci codice regalo", + "qrcodeCustom": "Scansiona il codice QR sottostante per completare il pagamento, o <0>apri direttamente il link di pagamento, questa pagina sarà automaticamente aggiornata dopo il completamento del pagamento.", + "paymentCompleted": "Pagamento completato", + "productDelivered": "I tuoi acquisti sono stati elaborati.", + "confirmRedeem": "Riscatta", + "productType": "Prodotto:", + "qyt": "Qtà: ", + "duration": "Durata: ", + "subscribe": "Iscriviti", + "selected": "Selezionato: ", + "paymentQrcode": "QRCode pagamento", + "validDurationDays": "{{num}} giorni", + "reportSuccessful": "Segnalazione inviata.", + "additionalDescription": "Descrizione aggiuntiva", + "announcement": "Annuncio", + "dontShowAgain": "Non mostrare più", + "openPaymentLink": "Apri link di pagamento", + "creditReasonAdjust": "Aggiustamento manuale" + } +} diff --git a/public/locales/it-IT/common.json b/public/locales/it-IT/common.json new file mode 100755 index 0000000..e564ee7 --- /dev/null +++ b/public/locales/it-IT/common.json @@ -0,0 +1,120 @@ +{ + "pageNotFound": "Pagina non trovata", + "unknownError": "Errore sconosciuto", + "errLoadingSiteConfig": "Impossibile caricare la configurazione del sito: ", + "newVersionRefresh": "È disponibile una nuova versione della pagina corrente.", + "update": "Aggiorna", + "errorDetails": "Dettagli", + "renderError": "Si è verificato un errore nel rendering della pagina, prova ad aggiornarla.", + "ok": "OK", + "cancel": "Annulla", + "select": "Seleziona", + "copyToClipboard": "Copia", + "close": "Chiudi", + "dismiss": "Chiudi", + "intlDateTime": "{{val, datetime}}", + "seconds": "s secondi", + "minutes": "m minuti s secondi", + "hours": "H ore m minuti", + "days": "{{d}} giorni", + "timeAgoLocaleCode": "it_IT", + "forEditorLocaleCode": "it-IT", + "artPlayerLocaleCode": "it", + "requestID": "ID richiesta: {{id}}", + "object": "Oggetto", + "error": "Errore", + "areYouSure": "Sei sicuro?", + "incorrectSizeInput": "Input dimensione errato", + "of": "di", + "rowsPerPage": "Righe per pagina", + "custom": "Personalizzato", + "enter": "Invio", + "captcha": { + "cap": { + "human": "Sono umano", + "verifying": "Verifica in corso...", + "verified": "Verifica completata" + } + }, + "errors": { + "401": "Effettua prima il login.", + "403": "Non hai i permessi per eseguire questa azione.", + "404": "Risorsa non trovata.", + "409": "Conflitto. ({{message}})", + "40001": "Parametri di input non validi ({{message}}).", + "40002": "Caricamento fallito.", + "40003": "Impossibile creare la cartella.", + "40004": "Esiste già un oggetto con lo stesso nome.", + "40005": "Firma scaduta.", + "40006": "Tipo di policy non supportato.", + "40007": "Il gruppo corrente non ha i permessi per eseguire questa azione.", + "40011": "Sessione di caricamento inesistente o scaduta.", + "40012": "Indice del chunk non valido. ({{message}})", + "40013": "Lunghezza del contenuto non valida. ({{message}})", + "40014": "Superato il limite di batch per ottenere il link sorgente.", + "40015": "Superato il limite di batch per aria2.", + "40016": "Percorso non trovato.", + "40017": "Questo account è stato bloccato.", + "40018": "Questo account non è attivato.", + "40019": "Questa funzione non è abilitata.", + "40020": "Credenziali non valide o scadute.", + "40021": "Utente non trovato.", + "40022": "Codice di verifica non corretto.", + "40023": "Sessione di login inesistente.", + "40024": "Impossibile inizializzare WebAuthn.", + "40025": "Autenticazione fallita.", + "40026": "Il codice CAPTCHA non è corretto.", + "40027": "Verifica fallita, aggiorna la pagina e riprova.", + "40028": "Invio email fallito.", + "40029": "Questo link non è valido.", + "40030": "Questo link è scaduto.", + "40032": "Questa email è già in uso.", + "40033": "Questo account non è attivato, l'email di attivazione è stata rinviata.", + "40034": "Questo utente non può essere attivato.", + "40035": "Policy di archiviazione non trovata.", + "40039": "Gruppo non trovato.", + "40044": "File non trovato.", + "40045": "Impossibile elencare gli oggetti nella cartella specificata.", + "40047": "Impossibile inizializzare il filesystem.", + "40048": "Impossibile creare l'attività", + "40049": "La dimensione del file supera il limite.", + "40050": "Tipo di file non consentito.", + "40051": "Quota di archiviazione insufficiente.", + "40052": "Questo nome file o estensione non è consentito.", + "40053": "Impossibile eseguire questa azione sulla cartella radice", + "40054": "Un file con lo stesso nome è già in caricamento in questa cartella, pulisci le sessioni di caricamento.", + "40055": "Metadati del file non corrispondenti.", + "40056": "Tipo di file compresso non supportato.", + "40057": "La policy di archiviazione disponibile è cambiata, aggiorna l'elenco dei file e aggiungi nuovamente questa attività.", + "40058": "Questa condivisione non esiste o è già scaduta.", + "40069": "Password non corretta.", + "40070": "Questa condivisione non supporta l'anteprima.", + "40071": "Firma non valida.", + "40073": "File occupato.", + "40074": "Troppi file selezionati.", + "40079": "Superato il limite massimo di file attraversati, prova a restringere l'ambito dell'operazione.", + "40081": "Operazione non completamente riuscita.", + "40082": "Solo il proprietario del file può eseguire questa azione.", + "40080": "Email o password non corrette.", + "50001": "Operazione database fallita. ({{message}})", + "50002": "Impossibile firmare l'URL o la richiesta. ({{message}})", + "50004": "Operazione I/O fallita. ({{message}})", + "50005": "Errore interno.", + "50010": "Il nodo desiderato non è disponibile.", + "50011": "Impossibile interrogare i metadati del file." + }, + "vasErrors": { + "40031": "Questo provider email è vietato, cambia con un altro.", + "40059": "Non puoi salvare la tua stessa condivisione.", + "40062": "Crediti insufficienti.", + "40063": "La tua iscrizione corrente non è ancora scaduta, vai nella pagina delle impostazioni per disdire manualmente l'iscrizione prima.", + "40064": "Sei già in questa iscrizione.", + "40065": "Codice regalo non valido.", + "40066": "Hai già un'identità collegata, scollegala prima.", + "40067": "Questa identità è già collegata a un altro account.", + "40068": "Questa identità non è collegata ad alcun account.", + "40072": "Sei amministratore, non puoi acquistare altri gruppi.", + "40084": "Il tuo account è senza password, devi mantenere almeno un account collegato.", + "40085": "L'importo totale di questo ordine è troppo piccolo per il checkout." + } +} diff --git a/public/locales/it-IT/dashboard.json b/public/locales/it-IT/dashboard.json new file mode 100755 index 0000000..aeffe25 --- /dev/null +++ b/public/locales/it-IT/dashboard.json @@ -0,0 +1,1620 @@ +{ + "errors": { + "40036": "La policy di archiviazione predefinita non può essere eliminata.", + "40037": "Alcuni blob di file stanno utilizzando questa policy, elimina prima quei blob di file.", + "40038": "{{message}} gruppo/i stanno utilizzando questa policy, scollega prima quei gruppi.", + "40040": "Non è possibile eseguire tale azione sul gruppo di sistema.", + "40041": "{{message}} utenti sono ancora in questo gruppo, elimina o scollega prima quegli utenti.", + "40042": "Non è possibile cambiare il gruppo dell'utente di sistema.", + "40043": "Non è possibile eseguire tale azione sull'utente predefinito.", + "40046": "Non è possibile eseguire tale azione sul nodo master.", + "40060": "Il nodo slave non può inviare richieste di callback al master, controlla le impostazioni del nodo master: Base - Informazioni Sito - URL Sito, assicurati che il nodo slave possa accedere a questo URL. ({{message}})", + "40061": "Versione Cloudreve non corrispondente. ({{message}})", + "40086": "Il nodo è utilizzato dalle seguenti policy di archiviazione: {{message}}.", + "50008": "Aggiornamento impostazione fallito. ({{message}})", + "50009": "Aggiunta policy CORS fallita." + }, + "nav": { + "summary": "Riepilogo", + "settings": "Impostazioni", + "basicSetting": "Base", + "email": "Email", + "transportation": "Trasmissione", + "appearance": "Aspetto", + "image": "Immagini", + "captcha": "Captcha", + "storagePolicy": "Policy di Archiviazione", + "nodes": "Nodi", + "groups": "Gruppi", + "users": "Utenti", + "files": "File", + "entities": "Blob File", + "shares": "Condivisioni", + "tasks": "Attività in Background", + "remoteDownload": "Download Remoto", + "generalTasks": "Generale", + "title": "Dashboard", + "dashboard": "Dashboard Cloudreve", + "userSession": "Sessione utente", + "fileSystem": "Filesystem", + "mediaProcessing": "Elaborazione media", + "queue": "Coda", + "events": "Eventi", + "server": "Server", + "customProps": "Proprietà personalizzate", + "abuseReport": "Segnalazione abusi" + }, + "summary": { + "generatedAt": "Generato alle <0>", + "confirmSiteURLTitle": "Conferma URL sito", + "siteURLNotMatch": "L'URL del sito che hai impostato non contiene quello attuale ({{current}}), vuoi aggiungerlo alla lista?", + "setAsPrimary": "Imposta come URL sito principale", + "setAsPrimaryDes": "Imposta {{current}} come URL sito principale, utilizzato per la comunicazione con servizi esterni e ricezione callback. Utilizza un URL accessibile da WAN.", + "setAsSecondary": "Aggiungi agli URL secondari", + "setAsSecondaryDes": "Aggiungi {{current}} agli URL secondari, Cloudreve selezionerà automaticamente se usarlo basandosi sull'URL effettivamente accessibile dall'utente.", + "siteURLDescription": "Questa impostazione è molto importante, assicurati che corrisponda all'URL effettivo del tuo sito. Puoi modificare questa impostazione in Impostazioni - Base.", + "ignore": "Ignora", + "changeIt": "Modificalo", + "trend": "Tendenza", + "summary": "Riepilogo", + "totalUsers": "Utenti", + "totalFilesAndFolders": "File e Cartelle", + "shareLinks": "Link condivisioni", + "totalBlobs": "Blob", + "homepage": "Homepage", + "github": "GitHub", + "documents": "Documentazione", + "discordCommunity": "Community Discord", + "telegram": "Gruppo Telegram", + "forum": "GitHub Discussions", + "buyPro": "Aggiorna a Pro", + "publishedAt": "pubblicato alle <0>", + "licenseExpireAt": "Data scadenza licenza", + "permanentLicense": "Licenza permanente", + "offlineLicenseExpireAy": "Data scadenza licenza offline", + "offlineLicenseDes": "Cloudreve aggiornerà automaticamente la licenza offline prima della scadenza se il server è connesso alla rete.", + "licensedDomains": "Domini autorizzati", + "renew": "Aggiorna licenza offline", + "manageLicense": "Gestisci licenza", + "volPurchase": "La licenza VOL client deve essere acquistata separatamente dalla <0>Dashboard Gestione Licenze. La licenza VOL consente ai tuoi utenti di connettersi al tuo sito utilizzando <1>Cloudreve iOS gratuitamente, senza necessità che gli utenti paghino per un abbonamento all'app iOS. Dopo aver acquistato una licenza, clicca \"Aggiorna licenza offline\" qui sotto.", + "iosVol": "Licenza volume client iOS (VOL)", + "refreshSuccessfully": "Aggiornato con successo.", + "manualRefresh": "Aggiorna manualmente licenza offline", + "manualRefreshDes": "Aggiornamento automatico licenza offline fallito, prova ad accedere alla <0>Dashboard Gestione Licenze per ottenere l'ultima licenza offline e incollala qui sotto.", + "announcement": "Annuncio" + }, + "queue": { + "queueName_io_intense": "IO Intensivo", + "queueName_io_intenseDes": "Coda per gestire grandi quantità di operazioni IO, inclusi: trasferimento policy di archiviazione, decompressione, compressione.", + "queueName_media_meta": "Estrazione Metadati Media", + "queueName_media_metaDes": "Utilizzata per estrarre metadati da file media.", + "queueName_recycle": "Riciclaggio Blob", + "queueName_recycleDes": "Utilizzata per eliminare blob file scaduti.", + "queueName_thumb": "Generazione Miniature", + "queueName_thumbDes": "Utilizzata per generare miniature per i file.", + "queueName_remote_download": "Download Remoto", + "queueName_remote_downloadDes": "Utilizzata per processare attività di download remoto.", + "failed": "Fallito ({{count}})", + "success": "Successo ({{count}})", + "suspending": "Sospeso ({{count}})", + "busyWorker": "Elaborazione ({{count}})", + "submited": "Inviato ({{count}})", + "editQueueSettings": "Modifica impostazioni coda - {{name}}", + "workerNum": "Thread worker", + "workerNumDes": "Numero massimo di attività da eseguire in parallelo nella coda attività", + "maxExecution": "Tempo massimo esecuzione", + "maxExecutionDes": "Tempo massimo esecuzione (secondi) per un'attività, dopo il quale l'attività verrà terminata.", + "backoffFactor": "Fattore backoff", + "backoffFactorDes": "Fattore di crescita per gli intervalli di tempo di ripetizione attività.", + "backoffMaxDuration": "Tempo massimo backoff", + "backoffMaxDurationDes": "Tempo massimo backoff (secondi) per ripetizioni attività.", + "maxRetry": "Ripetizioni massime", + "maxRetryDes": "Numero massimo di ripetizioni dopo un fallimento attività.", + "retryDelay": "Ritardo ripetizione", + "retryDelayDes": "Tempo ritardo iniziale (secondi) per ripetizioni attività." + }, + "settings": { + "headlessFooter": "Footer pagina di accesso", + "headlessFooterDes": "Contenuto HTML personalizzato visualizzato in fondo alle pagine di login, registrazione e risultato callback.", + "headlessBottom": "Fondo pagina di accesso", + "headlessBottomDes": "Contenuto HTML personalizzato visualizzato in fondo alle pagine di login, registrazione e risultato callback.", + "customHTML": "HTML personalizzato", + "customHTMLDes": "Inserisci contenuto HTML personalizzato nella posizione preimpostata del sito.", + "sidebarBottom": "Fondo barra laterale", + "sidebarBottomDes": "Contenuto HTML personalizzato visualizzato in fondo alla barra laterale.", + "addNavItem": "Aggiungi elemento navigazione", + "customNavItems": "Elementi barra laterale personalizzati", + "customNavItemsDes": "Puoi aggiungere elementi personalizzati alla barra laterale, gli utenti verranno reindirizzati al link corrispondente quando cliccato.", + "navItemUrl": "Link", + "iconifyNamePlaceholder": "Identificatore icona Iconify, es. fluent:home-24-regular", + "imageUrl": "URL immagine", + "iconifyName": "Nome icona Iconify", + "oidc": "OpenID Connect (OIDC)", + "oidcDes": "OpenID Connect (OIDC) è un protocollo di autenticazione aperto per la verifica dell'identità tra sistemi diversi. Dopo aver creato un'applicazione in una piattaforma di identità di terze parti, aggiungi <0>{{url}} al campo \"URI di reindirizzamento\". Per maggiori dettagli, consulta la <1>documentazione.", + "clientID": "ID Client", + "clientIDDes": "L'ID client dell'applicazione creata nella piattaforma di identità di terze parti.", + "clientSecret": "Segreto client", + "clientSecretDes": "Il segreto client dell'applicazione creata nella piattaforma di identità di terze parti.", + "scope": "Scope", + "scopeDes": "Scope aggiuntivi da richiedere, separati da virgole <0>,. Per impostazione predefinita, Cloudreve richiederà <0>openid, <0>email e <0>profile; non è necessario ripeterli qui.", + "oidcWellknown": "Config OIDC Wellknown", + "oidcWellknownDes": "Documento wellknown della piattaforma di identità di terze parti, contenente le informazioni di configurazione di OpenID Connect.", + "importFromWellknown": "Importa da URL", + "importOidc": "Importa Config OIDC Wellknown", + "oidcWellknownUrl": "URL Wellknown", + "oidcWellknownUrlDes": "URL del documento wellknown della piattaforma di identità di terze parti, come <0>https://accounts.google.com/.well-known/openid-configuration.", + "resetUrl": "URL Reset", + "exceedToleranceDays": "Giorni tolleranza per ban", + "activateUrl": "URL Attivazione", + "domainNotLicensed": "Dominio non autorizzato", + "domainNotLicensedDes": "L'URL del sito che hai impostato contiene un dominio non autorizzato, aggiungi questo sottodominio nella <0>Dashboard Gestione Licenze e clicca il pulsante qui sotto per aggiornare la licenza e riprova.", + "showSettings": "Mostra impostazioni", + "perPage": "{{num}} per pagina", + "noNodes": "Nessun nodo disponibile.", + "extractMediaMeta": "Estrai metadati media", + "extractMediaMetaDes": "Estrai metadati file media per visualizzazione e ricerca. Per impostazione predefinita, le policy di archiviazione non locali utilizzeranno solo il generatore \"Nativo nella policy di archiviazione\". Puoi estendere la capacità di miniature delle policy di archiviazione di terze parti abilitando la funzione \"Proxy estrattore\" nella pagina impostazioni policy di archiviazione. Per maggiori dettagli, consulta la <0>documentazione.", + "exif": "EXIF", + "exifDes": "Estrai metadati EXIF dai file immagine per visualizzazione e ricerca.", + "music": "Metadati musicali", + "musicDes": "Estrai metadati dai file musicali, inclusi titolo, artista, album, ecc.", + "ffprobe": "FFprobe", + "ffprobeDes": "Usa FFprobe per estrarre metadati da file video e audio.", + "maxSizeLocal": "Dimensione max file (Archiviazione locale)", + "maxSizeLocalDes": "Dimensione massima file per estrazione metadati quando il file è archiviato nella policy di archiviazione locale, 0 significa nessun limite.", + "maxSizeRemote": "Dimensione max file (Archiviazione remota)", + "maxSizeRemoteDes": "Dimensione massima file per estrazione metadati quando il file è archiviato nelle policy di archiviazione di terze parti, 0 significa nessun limite.", + "exifBruteForce": "Usa forza bruta se necessario", + "exifBruteForceDes": "Quando abilitato, l'intero file verrà scansionato per trovare dati EXIF se non possono essere trovati nella posizione header standard. Questo potrebbe aumentare il tempo di elaborazione ma può trovare dati EXIF in posizioni non standard.", + "musicCover": "Copertina musicale", + "musicCoverDes": "Estrai copertina album dai file musicali, supporta container ID3 (v1, 2.2, 2.3 e 2.4). Questo generatore dipende da qualsiasi altro generatore di miniature immagini (Cloudreve integrato o VIPS).", + "geocoding": "Geocodifica", + "geocodingDes": "Ottieni informazioni sull'indirizzo utilizzando il servizio Mapbox basato sulle informazioni delle coordinate registrate nell'EXIF dei media.", + "mapboxAK": "Chiave API Mapbox", + "mapboxAKDes": "Chiave API creata nella console Mapbox.", + "geocodingDependencyWarning": "Il generatore di geocodifica dipende dal generatore EXIF, si prega di abilitare il generatore EXIF.", + "notAppliedToNativeGenerator": "{{prefix}}Non applicabile al generatore nativo delle policy di archiviazione.", + "notAppliedToOneDriveNativeGenerator": "{{prefix}}Non applicabile al generatore nativo delle policy di archiviazione OneDrive o SharePoint.", + "fileBlobMargin": "Margine Cache URL Blob File (secondi)", + "fileBlobMarginDes": "Quando lo stesso Blob file è richiesto più volte, se l'URL iniziale ha un periodo di validità rimanente maggiore del margine, lo stesso URL verrà riutilizzato.", + "fileBlobTimeout": "TTL URL Blob File (secondi)", + "fileBlobTimeoutDes": "Limita il periodo di validità dell'URL temporaneo ottenuto quando gli utenti aprono o scaricano file, applicabile solo alle policy di archiviazione locale, WebDAV, o file scaricati tramite relay Cloudreve.", + "wopiSessionTimeout": "TTL sessione WOPI (secondi)", + "wopiSessionTimeoutDes": "Limita il periodo di validità di una singola sessione quando gli utenti modificano file utilizzando WOPI. Dopo la scadenza, gli utenti devono riaprire il file da Cloudreve.", + "oauthRefresh": "Intervallo aggiornamento per policy di archiviazione OAuth", + "oauthRefreshDes": "Imposta quanto spesso aggiornare le credenziali OAuth per policy di archiviazione (es. OneDrive) che richiedono OAuth. Questo può prevenire la scadenza delle credenziali dovuta a lunghi periodi di inattività", + "transitParallelNum": "Trasferimenti relay paralleli max", + "transitParallelNumDes": "Il numero massimo di upload paralleli quando un singolo task di relay file lato server contiene più file.", + "failedChunkRetry": "Numero massimo ripetizioni per fallimenti upload chunk", + "failedChunkRetryDes": "Il numero massimo di ripetizioni per fallimenti upload chunk, applicabile solo agli upload lato server o trasferimenti relay.", + "cacheChunks": "Cache chunk streaming", + "cacheChunksDes": "Se abilitato, i dati chunk verranno memorizzati nella directory temporanea del sistema durante il trasferimento streaming, così possono essere utilizzati per ripetere upload chunk falliti;\n Se disabilitato, gli upload chunk trasferimento streaming non occuperanno spazio disco extra, ma l'intero upload fallirà immediatamente se l'upload chunk fallisce.", + "folderPropsTimeout": "TTL cache statistiche cartella (secondi)", + "folderPropsTimeoutDes": "Il periodo di validità della cache risultato quando gli utenti calcolano statistiche cartella (dimensione, numero di file, ecc.).", + "slaveAPIExpiration": "TTL firma API Slave (secondi)", + "slaveAPIExpirationDes": "Il periodo di validità della firma utilizzato dal nodo master quando accede all'API del nodo slave.", + "uploadSessionTimeout": "TTL sessione upload (secondi)", + "uploadSessionDes": "In un periodo di sessione upload valido, per policy di archiviazione supportate, gli utenti possono riprendere attività non finite. Il valore massimo che può essere impostato è limitato dalle regole dei diversi fornitori di policy di archiviazione.", + "archiveTimeout": "TTL sessione download batch lato server (secondi)", + "advanceOptions": "Opzioni avanzate", + "emojiOptions": "Opzioni Emoji", + "addCategorize": "Aggiungi una categoria", + "category": "Categoria", + "searchQuery": "Query categorizzazione file", + "importWopi": "Importa impostazioni app WOPI", + "wopiEndpoint": "Endpoint Discovery WOPI", + "wopiDes": "Estendi le capacità di anteprima e modifica online di Cloudreve integrando con sistemi di elaborazione documenti online che supportano il protocollo WOPI. Inserisci qui l'indirizzo di discovery del servizio WOPI, come <0>https://example.com/hosting/discovery. Per maggiori dettagli, consulta la <1>documentazione.", + "embeddedWebpageViewer": "Visualizzatore Pagina Web Integrato", + "wopiViewer": "Applicazione WOPI", + "ext": "Estensione", + "invalidWopiActionMapping": "Mappatura azione WOPI non valida", + "woapiActionMapping": "Mappature azioni WOPI", + "drawioHost": "Istanza DrawIO", + "drawioHostDes": "Puoi utilizzare l'URL per istanza self-hosted.", + "openInNew": "Apri in nuova finestra", + "openInNewDes": "Se selezionato, aprirà direttamente una nuova scheda per aprire questa applicazione.", + "maxSize": "Dimensione max file", + "maxSizeDes": "La dimensione massima file supportata da questa applicazione. 0 significa nessun limite. Se il file supera questa dimensione, verrà comunque aperto, ma gli utenti verranno avvisati.", + "srcEncodedVar": "URL accesso temporaneo Blob file codificato URL", + "srcVar": "URL accesso temporaneo blob file", + "srcBase64Var": "URL accesso temporaneo Blob file codificato Base64", + "nameEncodedVar": "Nome file codificato URL", + "versionEntityVar": "L'ID Blob della versione file aperta, vuoto significa la versione più recente.", + "fileIdVar": "ID File", + "userIdVar": "ID Utente, vuoto quando non loggato.", + "userDisplayNameVar": "Nome visualizzazione utente codificato URL.", + "fileViewers": "Applicazioni file", + "addViewer": "Aggiungi un'applicazione", + "viewerGroupTitle": "Gruppo applicazione #{{index}}", + "viewerType": "Tipo", + "viewerPlatform": "Piattaforma", + "viewerPlatformDes": "Seleziona la piattaforma corrispondente per visualizzare l'applicazione solo su quella piattaforma.", + "viewerPlatformPC": "Desktop", + "viewerPlatformMobile": "Mobile", + "viewerPlatformAll": "Tutte", + "displayName": "Nome visualizzazione", + "displayNameDes": "Nome visualizzazione agli utenti, supporta chiave i18next.", + "viewerEnabled": "Abilitato", + "newFileAction": "Azioni nuovo file", + "newFileActionDes": "Aggiungendo questa mappatura, gli utenti vedranno questa opzione applicazione quando cliccano il pulsante \"Nuovo\".", + "addNewFileAction": "Aggiungi una mappatura", + "builtinViewerType": "Applicazione integrata", + "wopiViewerType": "WOPI", + "customViewerType": "Personalizzato", + "nMapping": "{{num}} mappatura/e", + "editViewerTitle": "Modifica {{name}}", + "builtInIconUrlDes": "Questa applicazione integrata ha un'icona predefinita. Quando l'URL icona è lasciato vuoto, verrà utilizzata l'icona predefinita.", + "viewerUrl": "URL Applicazione", + "viewerUrlDes": "URL dell'applicazione personalizzata, <0>variabili magiche sono supportate.", + "addIcon": "Aggiungi un'icona", + "exts": "Lista estensioni", + "icon": "Icona", + "iconUrl": "URL Icona", + "iconColor": "Colore", + "iconColorDark": "Colore (Modalità scura)", + "fileIcons": "Icone file", + "builtinIcon": "Integrato", + "mimeMapping": "Mappatura tipo MIME", + "mimeMappingDes": "Mappatura tipo MIME in formato JSON, dove la chiave è l'estensione file e il valore è il tipo MIME. Cloudreve determinerà il tipo MIME del file basandosi sull'estensione file e questa impostazione.", + "mapProvider": "Fornitore mappa", + "mapProviderDes": "Fornitore mappa utilizzato per visualizzare informazioni posizione media.", + "mapGoogle": "Google Maps (Leaflet)", + "mapOpenStreetMap": "OpenStreetMap (Leaflet)", + "mapboxMap": "OpenStreetMap (Mapbox)", + "mapboxAccessToken": "Token di accesso Mapbox", + "mapboxAccessTokenDes": "Token di accesso pubblico creato nella <0>console Mapbox.", + "tileType": "Tipo tile predefinito", + "tileTypeDes": "Tipo tile predefinito per Google Maps.", + "tileTypeTerrain": "Terreno", + "tileTypeSatellite": "Satellite", + "tileTypeGeneral": "Regolare", + "maxPageSize": "Dimensione max pagina", + "maxPageSizeDes": "Limita il numero massimo di file che gli utenti possono regolare per pagina.", + "maxRecursiveSearch": "Conteggio max ricerca ricorsiva", + "maxRecursiveSearchDes": "Il numero massimo di ricerche ricorsive consentite quando si cercano file. Se il numero di file cercati supera questo limite, la ricerca si fermerà e avviserà l'utente.", + "maxBatchSize": "Dimensione max batch", + "maxBatchSizeDes": "Il numero massimo di file che gli utenti possono operare in batch, solo il livello superiore verrà contato, e il numero di file sotto sottodirectory non verrà contato.", + "defaultPagination": "Metodo paginazione per lista file", + "cursorPagination": "Paginazione cursore", + "cursorPaginationDes": "Più file verranno caricati automaticamente quando l'utente scorre fino in fondo. Questo metodo performa meglio per grandi liste file, ma il numero totale di pagine non può essere visto.", + "offsetPagination": "Paginazione offset", + "offsetPaginationDes": "La navigazione paginazione verrà visualizzata in fondo alla pagina; gli utenti possono vedere il numero totale di pagine e saltare a una pagina specifica. Questo metodo performa leggermente peggio per grandi liste file.", + "defaultPaginationDes": "La paginazione cursore verrà forzata durante la ricerca, indipendentemente dalle impostazioni sopra.", + "publicResourceMaxAge": "Età max cache risorse statiche (secondi)", + "publicResourceMaxAgeDes": "L'età max della cache per risorse statiche accessibili pubblicamente (es. file, miniature e immagini profilo utente).", + "cronDes": "{{des}} È richiesta una <0>sintassi Cron corretta qui. È necessario riavviare Cloudreve per rendere effettive le modifiche.", + "entityCollectInterval": "Intervallo riciclo Blob File", + "entityCollectIntervalDes": "Imposta quanto spesso scansionare ed eliminare blob file scaduti.", + "trashBinInterval": "Intervallo scansione cestino", + "trashBinIntervalDes": "Imposta quanto spesso scansionare ed eliminare file scaduti nel cestino.", + "logtoName": "Nome metodo accesso", + "logtoNameDes": "Nome del metodo di accesso, visualizzato agli utenti. Predefinito è \"SSO\", supporta chiave i18next.", + "logtoDirectSSO": "Accesso diretto", + "logtoDirectSSODes": "Se vuoi saltare la schermata login Logto e saltare direttamente al login di terze parti o SSO, inserisci qui l'identificatore del connettore sociale. Per dettagli, consulta la <0>documentazione Logto.", + "logtoEndpoint": "Endpoint Logto", + "logtoEndpointDes": "L'URL endpoint Logto ottenuto dal pannello gestione applicazione, che può essere un'istanza self-hosted.", + "logtoKey": "Segreto applicazione", + "logtoKeyDes": "Segreto applicazione creato nella pagina gestione applicazione.", + "logtoAppIDDes": "ID applicazione creato nella pagina gestione applicazione.", + "logto": "Logto", + "logtoDes": "Con <0>Logto, puoi ottenere più accessi piattaforma di terze parti, come Apple, GitHub, Microsoft Entra ID, Google, SMS, ecc. Crea un \"Applicazione Web Tradizionale\" nel portale gestione Logto e aggiungi <1>{{url}} agli \"URI di Reindirizzamento\".", + "thirdPartySignIn": "Accesso terze parti", + "logo": "LOGO", + "logoDes": "URL del LOGO, fornisci logo diversi per modalità scura e chiara.", + "dark": "Modalità scura", + "light": "Modalità chiara", + "tosUrl": "URL termini di servizio", + "tosUrlDes": "Verrà visualizzato nel footer della pagina login o registrazione, lascia vuoto per non visualizzare.", + "privacyUrl": "URL politica privacy", + "privacyUrlDes": "Verrà visualizzato nel footer della pagina login o registrazione, lascia vuoto per non visualizzare.", + "addSecondary": "Aggiungi URL sito secondario", + "secondarySiteURL": "Secondario", + "secondaryDes": "Puoi anche aggiungere altri URL secondari, Cloudreve selezionerà automaticamente se usarlo basandosi sull'URL effettivamente accessibile dall'utente. Il tuo URL sito deve essere autorizzato.", + "primarySiteURL": "Principale", + "primarySiteURLDes": "L'URL sito principale è utilizzato per comunicazione con servizi esterni e ricezione callback (es. pagamento, fornitore archiviazione), utilizza un URL accessibile da WAN.", + "revert": "Ripristina modifiche", + "saved": "Impostazioni salvate.", + "save": "Salva", + "basicInformation": "Informazioni Base", + "mainTitle": "Nome sito", + "mainTitleDes": "Nome dell'istanza.", + "siteDescription": "Descrizione sito", + "siteDescriptionDes": "Descrizione del sito web, che potrebbe essere visualizzata nel riepilogo pagina condivisa.", + "siteURL": "URL Sito", + "customFooterHTML": "HTML footer personalizzato", + "customFooterHTMLDes": "Codice HTML personalizzato inserito in fondo alla pagina.", + "announcement": "Annuncio", + "announcementDes": "Annunci visualizzati agli utenti loggati. Valore vuoto non verrà visualizzato. Dopo che questo contenuto viene modificato, tutti gli utenti vedranno nuovamente l'annuncio.", + "supportHTML": "Inserisci HTML o testo semplice.", + "branding": "Branding", + "smallIcon": "Icona piccola", + "smallIconDes": "URL dell'icona piccola, formato ico o svg. Questa icona verrà anche mostrata nelle schede browser, segnalibri e scorciatoie desktop.", + "mediumIcon": "Icona media", + "mediumIconDes": "URL dell'icona media, preferisci dimensione 192x192, formato png.", + "largeIcon": "Icona grande", + "largeIconDes": "URL dell'icona grande, preferisci dimensione 512x512, formato png. Questa icona verrà anche mostrata durante il cambio account nell'app iOS.", + "displayMode": "Modalità visualizzazione", + "displayModeDes": "La modalità visualizzazione di un'applicazione PWA dopo l'installazione.", + "themeColor": "Colore tema", + "themeColorDes": "Valore colore CSS che influenza il colore della barra di stato sulla schermata lancio PWA, la barra di stato nella pagina contenuto, e la barra indirizzi.", + "backgroundColor": "Colore sfondo", + "backgroundColorDes": "Valore colore CSS.", + "hint": "Suggerimento", + "webauthnNoHttps": "Web Authn richiede che il tuo sito web abbia HTTPS abilitato, e conferma che in Impostazioni - Base - URL Sito utilizzi anche HTTPS.", + "accountManagement": "Account", + "allowNewRegistrations": "Accetta nuove registrazioni", + "allowNewRegistrationsDes": "Dopo essere disabilitato, nessun nuovo utente può essere registrato, a meno che non venga aggiunto manualmente dagli admin.", + "emailActivation": "Attivazione email", + "emailActivationDes": "Dopo essere abilitato, i nuovi utenti devono cliccare il link di attivazione nell'email per completare le registrazioni. Assicurati che le <0>impostazioni consegna email siano corrette, altrimenti l'email di attivazione non verrà consegnata.", + "captchaForSignup": "Captcha per registrazioni", + "captchaForSignupDes": "Se abilitare il captcha per le registrazioni.", + "captchaForLogin": "Captcha per login", + "captchaForLoginDes": "Se abilitare il captcha per i login.", + "captchaForReset": "Captcha per reset password", + "captchaForResetDes": "Se abilitare il captcha per il reset password.", + "captchaForAbuseReport": "Captcha per segnalazione abusi", + "captchaForAbuseReportDes": "Se abilitare il captcha per la segnalazione abusi.", + "webauthnDes": "Se permettere agli utenti di accedere con dispositivi di autenticazione hardware, come: volto, impronta digitale o chiave USB; il sito deve abilitare HTTPS.", + "webauthn": "Accesso con Passkey", + "defaultSymbolics": "Scorciatoie condivisione predefinite", + "defaultSymbolicsDes": "Scorciatoie link condivisione predefinite nella directory root di nuovi utenti. Cerca link condivisioni per ID, puoi vedere l'ID sul lato sinistro della <0>lista condivisioni.", + "searchShare": "Cerca ID condivisione...", + "defaultGroup": "Gruppo predefinito", + "defaultGroupDes": "Il gruppo utente iniziale dopo la registrazione utente.", + "testMailSent": "Email di test inviata.", + "testSMTPSettings": "Testa impostazioni SMTP", + "testSMTPTooltip": "Cloudreve utilizzerà le tue attuali impostazioni SMTP per inviare un'email di test, non è necessario salvare le impostazioni prima del test.", + "recipient": "Destinatario", + "send": "Invia", + "smtp": "SMTP", + "senderName": "Nome mittente", + "senderNameDes": "Il nome del mittente visualizzato nell'email.", + "senderAddress": "Indirizzo mittente", + "senderAddressDes": "Indirizzo email del mittente.", + "smtpServer": "Server SMTP", + "smtpServerDes": "Indirizzo server SMTP, senza numero porta.", + "smtpPort": "Porta SMTP", + "smtpPortDes": "Porta del server SMTP.", + "smtpUsername": "Nome utente SMTP", + "smtpUsernameDes": "Nome utente SMTP, generalmente uguale all'indirizzo mittente.", + "smtpPassword": "Password SMTP", + "smtpPasswordDes": "Password della casella postale mittente.", + "replyToAddress": "Indirizzo risposta", + "replyToAddressDes": "La casella postale utilizzata per ricevere email di risposta quando gli utenti rispondono alle email inviate dal sistema.", + "enforceSSL": "Forza connessione SSL", + "enforceSSLDes": "Se forzare una connessione crittografata SSL. Se non puoi inviare email, puoi disattivare questo e Cloudreve proverà ad utilizzare STARTTLS e decidere se utilizzare connessioni crittografate.", + "smtpTTL": "TTL connessione SMTP (secondi)", + "smtpTTLDes": "Le connessioni SMTP stabilite durante il periodo TTL verranno riutilizzate dalle nuove richieste consegna email.", + "emailTemplates": "Template Email", + "activateNewUser": "Attiva nuovo utente", + "resetPassword": "Reset password", + "sendTestEmail": "Invia email di test", + "transportation": "Trasmissione", + "workerNum": "Numero di worker", + "workerNumDes": "Il numero massimo di attività da eseguire in parallelo dalla coda attività nodo master, è necessario riavviare Cloudreve per rendere effettive le modifiche.", + "tempFolder": "Cartella temp", + "tempFolderDes": "Utilizzata per archiviare file temporanei generati da attività come decompressione, compressione, ecc.", + "textEditMaxSize": "Dimensione max file documenti modificabili", + "textEditMaxSizeDes": "La dimensione massima di un file documento che può essere modificato online, file oltre questa dimensione non possono essere modificati online. Questa impostazione si applica agli editor Web online come testo semplice, codice e documenti Office (WOPI).", + "resetConnection": "Reset connessione dopo upload fallito", + "resetConnectionDes": "Se abilitato, il server forzerà il reset della connessione se la verifica upload fallisce.", + "batchDownload": "Download batch", + "previewURL": "URL Anteprima", + "cannotDeleteDefaultTheme": "Impossibile eliminare il tema predefinito.", + "themeConfig": "Configurazioni", + "actions": "Azioni", + "wrongFormat": "Formato non corretto.", + "avatar": "Avatar", + "gravatarServer": "Server Gravatar", + "gravatarServerDes": "URL del server mirror Gravatar.", + "avatarFilePath": "Percorso file avatar", + "avatarFilePathDes": "Percorso per salvare i file avatar degli utenti, relativo alla cartella dati di Cloudreve.", + "avatarSize": "Dimensione massima file avatar", + "avatarSizeDes": "Dimensione massima dei file avatar che gli utenti possono caricare.", + "avatarImageSize": "Dimensione immagine (px)", + "avatarImageSizeDes": "L'immagine del profilo selezionata verrà ridimensionata alla dimensione specificata, in pixel.", + "filePreview": "Anteprima File", + "thumbnails": "Miniature", + "thumbnailDoc": "Per maggiori informazioni sulle miniature, consulta la <0>documentazione.", + "thumbnailDocLink": "https://docs.cloudreve.org/usage/thumbnails", + "thumbnailBasic": "Base", + "generators": "Generatori di miniature", + "thumbMaxSize": "Dimensione massima file originale", + "thumbMaxSizeDes": "La dimensione massima del file originale per cui è possibile generare miniature, le miniature non verranno generate se i file superano questa dimensione.", + "generatorProxyWarning": "Per impostazione predefinita, le policy di archiviazione non locali utilizzeranno solo il generatore \"Nativo nella policy di archiviazione\". Puoi estendere la capacità di miniature delle policy di archiviazione di terze parti abilitando la funzione \"Proxy del generatore\" nella pagina delle impostazioni della policy di archiviazione. Per maggiori dettagli, consulta la <0>documentazione.", + "policyBuiltin": "Nativo nella policy di archiviazione", + "policyBuiltinDes": "Utilizza l'API nativa del provider di archiviazione per elaborare le miniature. Per le policy locali e S3, questo generatore non è disponibile e tornerà automaticamente ad altri generatori. Per altre policy di archiviazione, vai alla pagina delle impostazioni della policy di archiviazione per configurare questo generatore.", + "cloudreveBuiltin": "Cloudreve integrato", + "cloudreveBuiltinDes": "Sono supportate solo immagini in formato PNG, JPEG, GIF utilizzando le capacità di elaborazione immagini integrate di Cloudreve.", + "libreOffice": "LibreOffice", + "libreOfficeDes": "Utilizza LibreOffice per generare miniature per documenti Office. Questo generatore dipende da qualsiasi altro generatore di immagini (Cloudreve integrato o VIPS).", + "libraw": "LibRaw / DCRaw", + "librawDes": "Utilizza il programma di esempio DCRaw di LibRaw, o l'eseguibile DCRaw originale per generare miniature per immagini RAW.", + "vips": "VIPS", + "vipsDes": "Utilizza libvips per elaborare immagini in miniatura, supporta più formati di immagine e consume meno risorse.", + "thumbDependencyWarning": "I generatori LibreOffice o copertina musicale dipendono dai generatori Cloudreve integrato o VIPS, abilita uno dei due.", + "ffmpeg": "FFmpeg", + "ffmpegDes": "Utilizza FFmpeg per generare miniature video.", + "executable": "Eseguibile", + "executableDes": "Il percorso o comando dell'eseguibile del generatore di terze parti.", + "executableTest": "Test", + "executableTestSuccess": "Il generatore funziona, versione: {{version}}", + "generatorExts": "Estensioni disponibili", + "generatorExtsDes": "Elenco delle estensioni di file disponibili per questo generatore, utilizzare la virgola , per separare più estensioni.", + "ffmpegSeek": "Posizione cattura miniatura", + "ffmpegSeekDes": "Definisce il tempo di cattura della miniatura, si consiglia di scegliere un valore più piccolo per accelerare il processo di generazione. Se viene superata la lunghezza effettiva del video, la generazione della miniatura fallirà.", + "ffmpegExtraArgs": "Argomenti di input extra", + "ffmpegExtraArgsDes": "Argomenti di input extra per chiamare FFmpeg.", + "generatorProxy": "Proxy del generatore", + "enableThumbProxy": "Usa proxy del generatore", + "proxyPolicyList": "Policy di archiviazione abilitate", + "proxyPolicyListDes": "Multi-selezionabile. Se abilitato, i file la cui policy di archiviazione non supporta la generazione nativa, le loro miniature saranno generate tramite proxy da Cloudreve.", + "thumbWidth": "Larghezza massima", + "thumbHeight": "Altezza massima", + "thumbSuffix": "Suffisso file blob", + "thumbSuffixDes": "Il suffisso aggiunto al nome del file Blob originale per la miniatura generata, ", + "thumbFormat": "Formato immagine", + "thumbFormatDes": "Formato immagine preferito, se il generatore non lo supporta, verrà automaticamente retrocesso al formato jpg.", + "thumbQuality": "Qualità", + "thumbQualityDes": "Percentuale di qualità di compressione, valida solo per codifica jpg e webp. ", + "thumbGC": "Esegui GC dopo la generazione della miniatura", + "captcha": "Captcha", + "captchaType": "Tipo di captcha", + "captchaTypeDes": "Seleziona il tipo di captcha e il provider.", + "plainCaptcha": "Grafico semplice", + "reCaptchaV2": "reCAPTCHA V2", + "turnstile": "Cloudflare Turnstile", + "turnstileSiteKey": "Chiave del Sito", + "turnstileSiteKSecret": "Segreto", + "cap": "Cap", + "capInstanceURL": "URL dell'Istanza", + "capInstanceURLDes": "L'URL del tuo server Cap self-hosted. Per maggiori dettagli, vedi la <0>documentazione modalità standalone.", + "capSiteKey": "Chiave del Sito", + "capSiteKeyDes": "La chiave del sito dalla dashboard del tuo server Cap.", + "capSecretKey": "Chiave Segreta", + "capSecretKeyDes": "La chiave segreta dalla dashboard del tuo server Cap.", + "capAssetServer": "Sorgente Server Asset", + "capAssetServerDes": "Scegli la sorgente per caricare gli asset statici del captcha Cap. L'uso del server self-deployed richiede l'impostazione di variabili d'ambiente lato server, consulta <0>abilita server asset.", + "capAssetServerJsdelivr": "jsDelivr CDN", + "capAssetServerUnpkg": "unpkg CDN", + "capAssetServerInstance": "Server self-hosted", + "captchaProvider": "Provider captcha", + "captchaWidth": "Larghezza", + "captchaHeight": "Altezza", + "captchaLength": "Lunghezza", + "captchaLengthDes": "La lunghezza dei caratteri nel captcha.", + "captchaMode": "Modalità", + "captchaModeNumber": "Numeri", + "captchaModeLetter": "Lettere", + "captchaModeMath": "Matematica", + "captchaModeNumberLetter": "Numeri + Lettere", + "captchaElement": "Elementi all'interno dell'immagine captcha.", + "complexOfNoiseText": "Complessità del testo di disturbo", + "complexOfNoiseDot": "Complessità dei punti di disturbo", + "showHollowLine": "Mostra linee cave", + "showNoiseDot": "Mostra punti di disturbo", + "showNoiseText": "Mostra testo di disturbo", + "showSlimeLine": "Mostra linee sottili", + "showSineLine": "Mostra linee sinusoidali", + "siteKey": "Chiave del Sito", + "siteKeyDes": "Puoi trovarla nella <0>Pagina di Gestione App.", + "siteSecret": "Segreto", + "siteSecretDes": "Puoi trovarlo nella <0>Pagina di Gestione App.", + "secretID": "SecretId", + "secretIDDes": "Puoi trovarlo nella <0>Pagina di Gestione Accesso.", + "secretKey": "SecretKey", + "secretKeyDes": "Puoi trovarlo nella <0>Pagina di Gestione Accesso.", + "tCaptchaAppID": "APPID", + "tCaptchaAppIDDes": "Puoi trovarlo nella <0>Pagina di Gestione Captcha.", + "tCaptchaSecretKey": "Chiave Segreta App", + "tCaptchaSecretKeyDes": "Puoi trovarla nella <0>Pagina di Gestione Captcha.", + "staticResourceCache": "Cache risorse statiche pubbliche", + "staticResourceCacheDes": "Età massima della cache per risorse statiche accessibili pubblicamente (es. link sorgente policy locale, link download).", + "creditSystem": "Sistema di crediti", + "creditAndVAS": "Crediti e VAS", + "enableCredit": "Abilita sistema di crediti", + "enableCreditDes": "Abilita il sistema di crediti per permettere agli utenti di impostare prezzi per i loro link di condivisione.", + "creditPrice": "Prezzo crediti", + "creditPriceDes": "Prezzo per ricaricare punti credito con denaro (in unità monetaria minima). Inserisci 0 per disabilitare la ricarica crediti.", + "shareScoreRate": "Tasso di commissione del proprietario della condivisione", + "shareScoreRateDes": "Percentuale (1-100) di punti credito che i proprietari di condivisioni ricevono quando i loro link di condivisione vengono acquistati.", + "cronNotifyUser": "Intervallo di scansione per utenti oltre il limite", + "cronNotifyUserDes": "Scansiona e invia promemoria email agli utenti oltre il limite, ", + "cronBanUser": "Pianificazione ban utenti", + "cronBanUserDes": "Scansiona e banna utenti che superano i limiti di archiviazione e i periodi di buffer", + "anonymousPurchase": "Acquisto anonimo", + "anonymousPurchaseDes": "Consenti agli utenti non loggati di acquistare direttamente i link di condivisione", + "shopNavEnabled": "Mostra Navigazione Negozio", + "shopNavEnabledDes": "Visualizza gli elementi 'Negozio' nella navigazione della barra laterale", + "paymentSettings": "Impostazioni pagamento", + "currencyCode": "Codice valuta", + "currencyCodeDes": "Codice valuta a tre lettere (es. USD, CNY, EUR)", + "currencySymbol": "Simbolo valuta", + "currencySymbolDes": "Simbolo valuta da visualizzare (es. $, Â¥, €)", + "currencyUnit": "Unità valuta", + "currencyUnitDes": "Unità monetaria minima (es. 100 per dollari/centesimi)", + "paymentProviders": "Provider di pagamento", + "providerName": "Nome provider, utilizzato per visualizzare agli utenti.", + "providerType": "Tipo provider", + "providerKey": "Chiave segreta", + "selectCurrency": "Seleziona valuta comune", + "addPaymentProvider": "Aggiungi provider di pagamento", + "stripeProvider": "Stripe", + "weixinProvider": "WeChat Pay", + "alipayProvider": "Alipay", + "customProvider": "Provider di pagamento personalizzato", + "customProviderDes": "Crea un plugin per connetterti ad altri gateway di pagamento, vedi la <0>documentazione per ulteriori dettagli.", + "providerKeyDes": "Chiave segreta API da Stripe.", + "storageProductSettings": "Prodotto di archiviazione", + "storageProductsDes": "Configura i prodotti che gli utenti possono acquistare per estendere il loro spazio di archiviazione.", + "addStorageProduct": "Aggiungi SKU", + "editStorageProduct": "Modifica SKU", + "storageSize": "Dimensione archiviazione", + "storageSizeBytes": "Dimensione inclusa in questo SKU", + "duration": "Durata", + "durationSeconds": "Durata in secondi (es. 2592000 per 30 giorni)", + "price": "Prezzo", + "priceInUnits": "Prezzo (in unità monetaria minima)", + "priceInUnitsDes": "Il prezzo verrà visualizzato come:", + "chipLabel": "Etichetta (opzionale)", + "chipLabelHelp": "Una breve etichetta di testo visualizzata accanto al nome del prodotto", + "usePoints": "Consenti pagamento con punti", + "points": "Punti", + "pointsHelp": "Numero di punti necessari per acquistare questo prodotto", + "pointsUnit": "punti", + "groupProductSettings": "Prodotto gruppo", + "groupProductsDes": "Configura i prodotti che gli utenti possono acquistare per unirsi a gruppi di utenti specifici.", + "addGroupProduct": "Aggiungi prodotto gruppo", + "editGroupProduct": "Modifica prodotto gruppo", + "groupId": "ID Gruppo", + "groupIdHelp": "Il gruppo di utenti a cui essere promosso dopo l'acquisto di questo prodotto.", + "description": "Descrizione", + "descriptionHelp": "Inserisci caratteristiche o vantaggi, uno per riga", + "receiptEmailTemplate": "Template ricevuta di pagamento", + "receiptEmailTemplateDes": "Template email inviato agli utenti quando un pagamento è confermato.", + "activationEmailTemplate": "Template attivazione account", + "activationEmailTemplateDes": "Template email inviato agli utenti per attivare i loro account.", + "quotaExceededEmailTemplate": "Template quota di archiviazione superata", + "quotaExceededEmailTemplateDes": "Template email inviato agli utenti quando superano la loro quota di archiviazione.", + "resetPasswordEmailTemplate": "Template reset password", + "resetPasswordEmailTemplateDes": "Template email inviato agli utenti quando richiedono un reset della password.", + "preferredLanguage": "Lingua preferita", + "setAsPreferredLanguage": "Imposta come lingua preferita", + "setAsPreferredLanguageDes": "Se non è possibile ottenere le preferenze linguistiche dell'utente, verrà utilizzato il template email della lingua preferita.", + "alreadyAsPreferredLanguageDes": "La lingua corrente è già impostata come preferita. Se non è possibile ottenere le preferenze linguistiche dell'utente, verrà utilizzato il template email della lingua corrente.", + "addLanguage": "Aggiungi lingua", + "removeLanguage": "Rimuovi lingua", + "removeLanguageBtn": "Rimuovi lingua", + "cannotRemovePreferredLanguageDes": "Impossibile rimuovere la lingua preferita. Imposta un'altra lingua come preferita e riprova.", + "languageCodeDes": "Seleziona la lingua che vuoi aggiungere.", + "emailSubject": "Oggetto email", + "emailSubjectDes": "L'oggetto dell'email. Puoi usare <0>variabili magiche per personalizzare l'oggetto dell'email.", + "emailBody": "Corpo email", + "emailBodyDes": "Contenuto HTML dell'email. Puoi usare <0>variabili magiche per personalizzare il contenuto dell'email.", + "orderTitle": "Titolo ordine", + "themeOptions": "Opzioni tema", + "themeOptionsDes": "Configura opzioni di tema personalizzate per il tuo sito. Questi temi saranno disponibili per gli utenti da selezionare nelle loro preferenze.", + "primaryColor": "Colore primario", + "secondaryColor": "Colore secondario", + "primaryColorDark": "Colore primario (Scuro)", + "secondaryColorDark": "Colore secondario (Scuro)", + "addThemeOption": "Aggiungi opzione tema", + "editThemeOption": "Modifica opzione tema", + "invalidThemeConfig": "Configurazione tema non valida. Controlla la sintassi JSON.", + "themeConfiguration": "Configurazione tema", + "themePreview": "Anteprima tema", + "lightTheme": "Tema chiaro", + "darkTheme": "Tema scuro", + "previewTitle": "Titolo anteprima", + "previewTextField": "Campo di input", + "previewPrimary": "Primario", + "invalidThemePreview": "Configurazione tema non valida per l'anteprima", + "duplicateThemeColor": "Un tema con questo colore primario esiste già. Scegli un colore diverso.", + "themeDes": "Le configurazioni complete disponibili possono essere consultate su <0>Visualizzatore tema predefinito - Material-UI.", + "defaultTheme": "Predefinito", + "auditLog": "Eventi", + "auditLogDes": "Configura quali eventi dovrebbero essere registrati. Alcuni eventi potrebbero essere utilizzati dal sistema per fornire funzionalità aggiuntive, es. attività file e attività di accesso.", + "systemEvents": "Eventi di sistema", + "systemEventsDes": "Eventi relativi alle operazioni e stato del sistema.", + "userEvents": "Eventi utente", + "userEventsDes": "Eventi relativi agli account utente, autenticazione e modifiche al profilo.", + "fileEvents": "Eventi file", + "fileEventsDes": "Eventi relativi alle operazioni sui file come caricamento, download e modifica.", + "shareEvents": "Eventi condivisione", + "shareEventsDes": "Eventi relativi alla condivisione di file e accesso ai link.", + "versionEvents": "Eventi versione", + "versionEventsDes": "Eventi relativi alla gestione delle versioni dei file.", + "mediaEvents": "Eventi media", + "mediaEventsDes": "Eventi relativi all'elaborazione media come la generazione di miniature.", + "filesystemEvents": "Eventi filesystem", + "filesystemEventsDes": "Eventi relativi alle operazioni del filesystem come montaggio e gestione archivi.", + "webdavEvents": "Eventi WebDAV", + "webdavEventsDes": "Eventi relativi alla gestione account WebDAV e accesso.", + "paymentEvents": "Eventi pagamento", + "paymentEventsDes": "Eventi relativi a pagamenti, punti e gestione abbonamenti.", + "emailEvents": "Eventi email", + "emailEventsDes": "Eventi relativi all'invio di email e notifiche.", + "toggleAll": "Attiva/disattiva tutto", + "toggleAllDes": "Abilita o disabilita tutti gli eventi in questa categoria.", + "event": { + "file_imported": "File esterno importato", + "server_start": "Avvio server", + "user_signup": "Registrazione utente", + "email_sent": "Email inviata", + "user_activated": "Utente attivato", + "user_login_failed": "Login fallito", + "user_login": "Login utente", + "user_token_refresh": "Aggiornamento token", + "file_create": "File creato", + "file_rename": "File rinominato", + "set_file_permission": "Permesso modificato", + "entity_uploaded": "File caricato o aggiornato", + "entity_downloaded": "File scaricato", + "copy_from": "Copia da", + "copy_to": "Copia in", + "move_to": "Sposta in", + "delete_file": "File eliminato", + "move_to_trash": "Sposta nel cestino", + "share": "Condivisione creata", + "share_link_viewed": "Link condivisione visualizzato", + "set_current_version": "Imposta versione corrente", + "delete_version": "Elimina versione", + "thumb_generated": "Miniatura generata", + "live_photo_uploaded": "Live photo caricata", + "update_metadata": "Metadati aggiornati", + "edit_share": "Condivisione modificata", + "delete_share": "Condivisione eliminata", + "mount": "Monta", + "relocate": "Riposiziona", + "create_archive": "Crea archivio", + "extract_archive": "Estrai archivio", + "webdav_login_failed": "Login WebDAV fallito", + "webdav_account_create": "Account WebDAV creato", + "webdav_account_update": "Account WebDAV aggiornato", + "webdav_account_delete": "Account WebDAV eliminato", + "payment_created": "Pagamento creato", + "points_change": "Punti modificati", + "payment_paid": "Pagamento effettuato", + "payment_fulfilled": "Ordine evaso", + "payment_fulfill_failed": "Evasione ordine fallita", + "storage_added": "Archiviazione aggiunta", + "group_changed": "Gruppo modificato", + "user_exceed_quota_notified": "Notifica quota superata", + "user_changed": "Stato utente modificato", + "get_direct_link": "Ottieni link diretto", + "link_account": "Collega account esterno", + "unlink_account": "Scollega account esterno", + "change_nick": "Cambia nickname", + "change_avatar": "Cambia avatar", + "membership_unsubscribe": "Annulla abbonamento", + "change_password": "Cambia password", + "enable_2fa": "Abilita 2FA", + "disable_2fa": "Disabilita 2FA", + "add_passkey": "Aggiungi passkey", + "remove_passkey": "Rimuovi passkey", + "redeem_gift_code": "Riscatta codice regalo", + "update_view": "Impostazione vista modificata", + "delete_direct_link": "Elimina link diretto", + "report_abuse": "Segnala abuso" + }, + "server": "Server", + "tempPath": "Percorso temporaneo", + "tempPathDes": "La directory per memorizzare i file temporanei, relativa alla directory dati di Cloudreve. Assicurati che nessuna attività in coda sia in esecuzione prima di modificarla.", + "siteID": "ID Sito", + "siteIDDes": "Un ID univoco per identificare il sito, generalmente non necessita di essere modificato.", + "siteSecretKey": "Chiave master", + "siteSecretKeyDes": "La chiave master utilizzata per crittografare token utente e firme. Dopo la rotazione, tutti i token utente e le firme saranno invalidi. Ha effetto dopo il riavvio di Cloudreve.", + "rotateSecretKey": "Ruota chiave master", + "hashidSalt": "Salt HashID", + "hashidSaltDes": "Il valore salt utilizzato per generare HashID. Prestare attenzione quando lo si modifica, poiché invalidirà i link diretti e i link di condivisione esistenti.", + "accessTokenTTL": "TTL token di accesso", + "accessTokenTTLDes": "Il TTL dei token di accesso, in secondi.", + "refreshTokenTTL": "TTL token di aggiornamento", + "refreshTokenTTLDes": "Il TTL dei token di aggiornamento, in secondi. Influisce sulla durata dello stato di login dell'utente.", + "cronGarbageCollect": "Intervallo scansione garbage collection", + "cronGarbageCollectDes": "Imposta quanto spesso eseguire la scansione e riciclare i dati scaduti nei file temporanei e nell'archiviazione KV.", + "startWithProtocol": "Deve iniziare con http:// o https://", + "tlsWarning": "Il sito corrente sta utilizzando https, inserire qui un URL http potrebbe causare eccezioni.", + "blobUrlCache": "Cache URL Blob", + "clearBlobUrlCache": "Pulisci cache URL Blob", + "clearBlobUrlCacheDes": "Per aumentare il tasso di hit della cache, Cloudreve memorizza nella cache e riutilizza gli URL Blob. Quando l'indirizzo CDN o altre impostazioni cambiano, pulisci la cache.", + "cacheCleared": "Cache pulita." + }, + "giftCodes": { + "giftCodesSettings": "Codici Regalo", + "generateGiftCodes": "Genera Codici Regalo", + "giftCodeQuantity": "Quantità", + "giftCodeQuantityHelp": "Numero di codici regalo da generare", + "giftCodeProductType": "Tipo Prodotto", + "giftCodeTypePoints": "Punti", + "giftCodeTypeStorage": "Archiviazione", + "giftCodeTypeGroup": "Gruppo", + "giftCodePointsAmount": "Quantità Punti", + "giftCodePointsAmountHelp": "Numero di punti da accreditare quando il codice viene riscattato", + "giftCodeProduct": "Prodotto", + "selectStorageProduct": "Seleziona prodotto archiviazione", + "selectGroupProduct": "Seleziona prodotto gruppo", + "giftCodeType": "Tipo", + "giftCodeAmount": "Quantità", + "giftCode": "Codice Regalo", + "giftCodeStatus": "Stato", + "giftCodeUsedBy": "Utilizzato da", + "giftCodeUsed": "Utilizzato", + "giftCodeUnused": "Disponibile", + "giftCodeDeleted": "Codice regalo eliminato con successo", + "giftCodesGenerated": "Codici regalo generati con successo", + "noGiftCodes": "Nessun codice regalo disponibile", + "generatedCodesTitle": "Codici Regalo Generati", + "generatedCodesDescription": "Copia questi codici regalo per condividerli con gli utenti. Ogni codice può essere utilizzato una volta.", + "copyAndClose": "Copia e Chiudi", + "duratonTimes": "Quantità", + "duratonTimesDes": "Quante quantità del prodotto sono incluse in ogni codice regalo.", + "unknownProduct": "Prodotto Sconosciuto" + }, + "policy": { + "acceleratedDomainUpload": "Usa dominio di accelerazione trasferimento per upload", + "acceleratedDomainUploadDes": "Quando abilitato, il <0>dominio di accelerazione trasferimento di Qiniu sarà utilizzato durante il caricamento dei file.", + "compare": "Confronta", + "deletePolicyConfirmation": "Sei sicuro di voler eliminare la policy di archiviazione {{name}}?", + "streamSaver": "Download tramite browser", + "streamSaverDes": "Quando abilitato, le richieste di download degli utenti saranno gestite dal browser. A causa della limitazione della policy di archiviazione OneDrive, il nome del file scaricato direttamente dagli utenti non può essere uguale al nome del file in Cloudreve, utilizzare il browser per gestire i download può risolvere questo problema.", + "oauthCallbackFailed": "Autorizzazione fallita", + "httpsRequired": "L'applicazione Entra ID richiede un URL di reindirizzamento HTTPS, ma il sito corrente sta utilizzando HTTP, il che potrebbe causare il fallimento del reindirizzamento dopo il login, sostituisci manualmente HTTPS nella barra degli indirizzi del browser con HTTP.", + "authorizeMicrosoft": "Accedi con Microsoft", + "redirectUrl": "URL Reindirizzamento", + "redirectUrlDes": "La visualizzazione corrente è l'ultimo URL di reindirizzamento che soddisfa i requisiti. Conferma se l'URL di reindirizzamento nelle impostazioni dell'applicazione è coerente con quello corrente.", + "authorizeOneDrive": "Conferma impostazioni applicazione Entra ID", + "authorizeOneDriveDes": "Conferma se le seguenti informazioni dell'applicazione Entra ID sono ancora valide. Se necessario, apporta modifiche.", + "authorizeNow": "Autorizza", + "authorizeAgain": "Autorizza di nuovo", + "notGranted": "Nessun account autorizzato, la policy di archiviazione non può essere utilizzata.", + "granted": "Account autorizzato, credenziali aggiornate alle <0>{{time}}.", + "grantedNotRefresh": "Account autorizzato, credenziali non aggiornate dall'ultimo avvio.", + "batchDeleteSize": "Dimensione massima eliminazione batch", + "batchDeleteSizeDes": "Limita il numero massimo di file che possono essere eliminati in una singola richiesta API. Questa impostazione non influenzerà l'eliminazione batch dei file dell'utente. Se non compilato, verrà utilizzato il valore predefinito <0>1000. Questo è il valore massimo consentito per l'API S3 ufficiale.", + "bucketPolicy": "Policy bucket", + "cdnOrCustomDomain": "CDN o CNAME personalizzato", + "bucketDomain": "Dominio bucket", + "bucketDomainDes": "Inserisci il dominio accelerato CDN o il dominio CNAME personalizzato che hai associato al bucket di archiviazione.", + "storageNodeInternal": "Nodo di archiviazione (Endpoint Intranet)", + "chunkSizeDesOssObs": "Intervallo consentito: 100 KB ~ 5 GB.", + "chunkSizeDesQiniuCos": "Intervallo consentito: 1 MB ~ 1 GB.", + "chunkSizeDesS3": "Intervallo consentito: 5 MB ~ 5 GB.", + "thisIsACustomDomain": "Questo è un dominio personalizzato", + "thisIsACustomDomainDes": "Se hai associato un dominio personalizzato al bucket di archiviazione e devi gestire il bucket tramite il dominio personalizzato, seleziona questa opzione. Una volta abilitato, Cloudreve non tenterà di aggiungere il nome del Bucket nel dominio della richiesta.", + "addedManually": "L'ho impostato manualmente", + "origin": "Origine", + "allowMethods": "Metodi Consentiti", + "exposeHeaders": "Intestazioni Esposte", + "allowHeaders": "Intestazioni Consentite", + "maxAge": "Età Massima", + "accessCredential": "Credenziali di accesso", + "downloadTrafficDiagram": "Dimostrazione percorso traffico download", + "downloadRelay": "Relay download", + "downloadRelayDes": "Quando abilitato, le richieste di download degli utenti saranno proxate da Cloudreve.", + "download": "Download", + "downloadCdn": "CDN Download", + "useDownloadCdn": "Usa CDN per il traffico di download", + "skipSign": "Salta firma URL per CDN", + "skipSignDes": "Se hai abilitato \"Usa autenticazione sorgente\" per questo dominio nelle impostazioni del bucket, seleziona questa opzione.", + "cdnHost": "Host CDN", + "downloadCdnDes": "L'host, il protocollo e la porta dell'URL che gli utenti utilizzano per accedere ai file saranno sostituiti con l'host CDN che hai specificato.", + "mediaExtractorProxy": "Proxy estrazione media", + "mediaExtractorProxyDes": "Abilita questa funzione per estrarre metadati media da file che non sono supportati dagli estrattori nativi del provider di archiviazione. Configura l'estrattore media in <0>Elaborazione media.", + "mediaExtractorNative": "estrattori nativi", + "mediaExtractorOss": "Intelligent Media Management (IMM)", + "mediaExtractorQiniu": "Qiniu DORA", + "mediaExtractorCos": "Elaborazione Dati Tencent Cloud", + "mediaExtractorObs": "servizio elaborazione immagini", + "nativeMediaMetaExts": "Estensioni file abilitate per <0>{{name}}", + "nativeMediaMetaExtsGeneralDes": "Separate da virgole, valore vuoto significa disabilitare <0>{{name}}.", + "nativeMediaMetaExtsRemote": "Per l'archiviazione slave, il supporto predefinito è EXIF e metadati musicali, puoi sovrascrivere questo configurando il nodo slave con più estrattori.", + "nativeMediaMetaExtOss": "Il servizio Intelligent Media Management (IMM) supporta l'elaborazione di audio, video e immagini. L'elaborazione delle immagini non richiede configurazione manuale, ma se devi elaborare audio o video, devi attivare manualmente IMM e associarlo al Bucket, consulta la <0>documentazione per l'associazione. Dopo l'associazione, aggiungi le estensioni che vuoi elaborare nel campo sopra.", + "nativeMediaMetaExtQiniu": "Il servizio Qiniu DORA supporta l'elaborazione di audio, video e immagini comuni, non è richiesta configurazione aggiuntiva, inserisci le estensioni che vuoi elaborare sopra.", + "nativeMediaMetaExtCos": "Il servizio Elaborazione Dati Tencent Cloud supporta l'elaborazione di audio, video e immagini. L'elaborazione delle immagini non richiede configurazione manuale, ma se devi elaborare audio o video, vai prima a <0>Elaborazione Dati Tencent Cloud per attivare e associare il bucket di archiviazione, poi vai alle impostazioni Bucket - Elaborazione media per attivare il servizio di elaborazione immagini. Dopo l'associazione, aggiungi le estensioni che vuoi elaborare nel campo sopra.", + "nativeMediaMetaExtObs": "Il servizio di elaborazione immagini supporta <0>l'estrazione EXIF delle immagini. Non è richiesta configurazione manuale, aggiungi semplicemente le estensioni che vuoi elaborare sopra.", + "thumbProxy": "Proxy generazione miniature", + "thumbProxyDes": "Abilita questa funzione per generare miniature per file che non soddisfano le condizioni native per le miniature. Cloudreve proverà a generare miniature e caricarle sul lato archiviazione. Configura il generatore di miniature in <0>Elaborazione media.", + "nativeThumbnailMaxSize": "Dimensione massima miniature native", + "nativeThumbnailMaxSizeDes": "Inserisci 0 per disabilitare il limite di dimensione, i file più grandi di questa dimensione non useranno miniature native.", + "nativeThumbNailsSupportAllExts": "Abilita per tutte le estensioni file", + "nativeThumbNails": "Estensioni file per miniature native", + "nativeThumbNailsGeneralDes": "Separate da virgole, valore vuoto significa disabilitare le miniature native, per le estensioni file elencate sopra, Cloudreve userà la funzione miniature native del provider di archiviazione per generare miniature.", + "nativeThumbNailsGeneralRemote": "Per l'archiviazione slave, il supporto integrato è per miniature semplici di immagini e copertine musicali, puoi sovrascrivere questo configurando il nodo slave con più generatori.", + "nativeThumbNailsGeneralOss": "Per l'archiviazione Alibaba Cloud OSS, verrà utilizzato il servizio <0>elaborazione immagini per generare miniature.", + "nativeThumbNailsGeneralQiniu": "Per l'archiviazione Qiniu Cloud, verrà utilizzato il servizio <0>elaborazione base immagini (imageView2) per generare miniature.", + "nativeThumbNailsGeneralCos": "Per l'archiviazione Tencent Cloud COS, verrà utilizzato il servizio <0>Elaborazione Dati Tencent Cloud per generare miniature.", + "nativeThumbNailsGeneralObs": "Per l'archiviazione Huawei Cloud OBS, verrà utilizzato il servizio <0>elaborazione immagini per generare miniature.", + "nativeThumbNailsGeneralUpyun": "Per l'archiviazione Upyun, verrà utilizzato il servizio <0>elaborazione immagini per generare miniature.", + "preallocate": "Pre-alloca spazio disco", + "preallocateDes": "Quando abilitato, la richiesta di caricamento dell'utente pre-allocherà spazio disco sul nodo di archiviazione, e supporta anche il caricamento parallelo di chunk. Efficace solo su Linux o Darwin.", + "chunkConcurrency": "Caricamenti chunk concorrenti", + "chunkConcurrencyDes": "Imposta il numero di caricamenti chunk concorrenti quando si utilizza il caricamento diretto web.", + "sourceWebEdit": "Modifica online web", + "uploadRelay": "Relay caricamento", + "uploadRelayDes": "Se abilitato, le richieste di caricamento degli utenti saranno inoltrate al nodo di archiviazione tramite Cloudreve, a causa dell'impossibilità di eseguire caricamenti in chunk, regola di conseguenza il limite massimo di dimensione caricamento del server web.", + "customProxy": "Proxy personalizzato", + "storageNode": "Provider di archiviazione", + "sourceWeb": "Web / App ufficiale", + "sourceDav": "WebDAV", + "uploadTrafficDiagram": "Dimostrazione percorso traffico caricamento", + "node": "Nodo di archiviazione", + "nodeDes": "Seleziona un nodo slave per l'archiviazione file, puoi creare o gestire nodi di archiviazione slave in <0>Elenco nodi.", + "noBindedGroupWarning": "La policy di archiviazione corrente non è associata a nessun gruppo utenti, vai a <0>Elenco gruppi per associare la policy di archiviazione corrente a un gruppo utenti.", + "nameRuleImmutable": "Modificare le impostazioni non influenzerà i file esistenti nella policy di archiviazione. Il percorso Blob è fisso dopo la creazione, anche se le variabili magiche in esso cambiano, il percorso non sarà aggiornato.", + "uniqueVarRequired": "Includi almeno una variabile univoca nel percorso della directory o nel nome blob: {uuid}, {randomkey8}, {randomkey16}.", + "storageAndUpload": "Archiviazione e Caricamento", + "blobFolderNaming": "Directory Archiviazione Blob", + "blobFolderNamingDes": "La directory dove sono archiviati i Blob dei file, puoi usare <0>variabili magiche.", + "blobNameDes": "Il nome del Blob del file, puoi usare <0>variabili magiche, assicurati che sia assolutamente univoco, anche per caricamenti multipli dello stesso nome file nello stesso percorso in poco tempo.", + "blobName": "Nome Blob", + "basicInfo": "Informazioni base", + "editX": "Modifica {{name}}", + "noGroupBinded": "Nessun gruppo associato", + "create": "Crea", + "addXStoragePolicy": "Aggiungi policy di archiviazione {{type}}", + "loadSummary": "Carica riepilogo", + "policySummary": "{{count}} Blob file ({{size}})", + "sharp": "#", + "name": "Nome", + "type": "Tipo", + "childFiles": "File figli", + "totalSize": "Dimensione totale", + "actions": "Azioni", + "authSuccess": "Autorizzazione concessa.", + "policyDeleted": "Policy eliminata.", + "newStoragePolicy": "Nuova policy di archiviazione", + "all": "Tutti", + "local": "Locale", + "remote": "Nodo Remoto", + "qiniu": "Qiniu", + "upyun": "Upyun", + "oss": "Alibaba Cloud OSS", + "cos": "Tencent Cloud COS", + "onedrive": "OneDrive", + "s3": "Compatibile S3", + "ks3": "Kingsoft Cloud S3", + "obs": "Huawei Cloud OBS", + "load_balance": "Bilanciamento del Carico", + "childPolicy": "Policy di Archiviazione Figlio", + "childPolicyDes": "Seleziona le policy di archiviazione figlio da aggiungere al pool di bilanciamento del carico.", + "weight": "Peso", + "addTargetPolicy": "Aggiungi Policy Figlio", + "selectPolicies": "Seleziona Policy", + "selectPoliciesDes": "Seleziona le policy di archiviazione da aggiungere al pool di bilanciamento del carico.", + "loadBalanceDes": "Quando si utilizza la policy di archiviazione bilanciata del carico, i nuovi caricamenti saranno distribuiti casualmente a diverse policy di archiviazione figlio in base al peso.", + "xChildPolicies": "{{count}} policy di archiviazione figlio", + "refresh": "Aggiorna", + "delete": "Elimina", + "edit": "Modifica", + "selectAStorageProvider": "Seleziona un provider di archiviazione", + "maxSizeOfSingleFile": "Dimensione massima file singolo", + "maxSizeOfSingleFileDes": "Inserisci 0 per disabilitare il limite.", + "enterFileExt": "Separato da virgole punto e virgola, lascia vuoto per consentire tutte le estensioni file.", + "extList": "Restrizioni estensioni file", + "noLimit": "Nessun limite", + "whitelist": "Consenti", + "blacklist": "Nega", + "fileNameRegex": "Regole regex nome file", + "fileNameRegexDes": "Espressione regolare per abbinare i nomi dei file, lascia vuoto per nessuna restrizione.", + "chunkSizeDes": "Specifica la dimensione del chunk per caricamenti in chunk. Un valore di 0 significa che non vengono utilizzati caricamenti in chunk, ma la dimensione massima di caricamento potrebbe essere limitata dal server web.", + "chunkSizeDesSuffix": "{{prefix}} Con il caricamento in chunk, i file caricati dagli utenti saranno suddivisi in chunk e caricati sul lato archiviazione uno per uno. Dopo che il caricamento è interrotto, gli utenti possono scegliere di continuare il caricamento dall'ultimo chunk caricato.", + "chunkSize": "Dimensione chunk", + "policyName": "Il nome visualizzato della policy di archiviazione, utilizzato anche per essere presentato agli utenti.", + "magicVar": { + "fileNameMagicVar": "Variabili magiche nome file", + "pathMagicVar": "Variabili magiche percorso", + "variable": "Variabile", + "description": "Descrizione", + "example": "Esempio", + "16digitsRandomString": "Stringa casuale di 16 cifre", + "8digitsRandomString": "Stringa casuale di 8 cifre", + "secondTimestamp": "Timestamp", + "nanoTimestamp": "Nano timestamp", + "uid": "ID Utente", + "originalFileName": "Nome file originale", + "originFileNameNoext": "Nome file originale senza estensione", + "extension": "Nome estensione file", + "uuidV4": "UUID V4", + "date": "Data", + "dateAndTime": "Data e ora", + "randomNumber": "Numero casuale nell'intervallo", + "year": "Anno", + "month": "Mese", + "day": "Giorno", + "hour": "Ora", + "minute": "Minuto", + "second": "Secondo", + "path": "Il percorso iniziale durante il caricamento del file da parte dell'utente" + }, + "storageBucket": "Bucket di archiviazione", + "wanSiteURLDes": "Prima di utilizzare questa politica, assicurati che l'indirizzo inserito in Impostazioni di base - Informazioni sito - URL del sito corrisponda all'indirizzo effettivo e <0>possa essere accessibile correttamente dalla WAN.", + "enterQiniuBucket": "Vai al <0>pannello di controllo Qiniu per creare un bucket di archiviazione. Inserisci il \"Nome bucket\" che hai appena creato.", + "aclType": "Tipo di controllo accesso", + "accessTypePulic": "Lettura pubblica scrittura privata", + "accessTypePrivate": "Lettura/scrittura privata", + "accessType": "Tipo di accesso", + "qiniuBucketName": "Nome bucket", + "cosObsBucketName": "Nome bucket", + "bucketType": "ACL bucket", + "bucketTypeDes": "Seleziona il tipo di ACL per il bucket che hai appena creato.", + "privateBucket": "Privato", + "privateDes": "Cloudreve firmerà l'URL del file.", + "publicBucket": "Lettura pubblica", + "publicStorage": "Pubblico", + "publicDes": "Non raccomandato, Cloudreve restituirà direttamente il link diretto del file, che non può controllare efficacemente l'accesso ai file.", + "bucketCDNDes": "Compila il nome di dominio accelerato CDN che hai associato al bucket di archiviazione.", + "bucketCDNDomain": "Dominio CDN", + "qiniuCredentialDes": "Vai al Centro personale - Gestione credenziali nel pannello di controllo Qiniu e compila AK, SK ottenuti.", + "ak": "AK", + "sk": "SK", + "cannotEnableForPrivateBucket": "Se questa funzione è abilitata per bucket privati, devi abilitare \"Usa link sorgente reindirizzato\" per i gruppi utente.", + "chunkSizeLabelQiniu": "Specifica la dimensione del chunk per i caricamenti ripristinabili. L'intervallo consentito è 1 MB - 1 GB.", + "corsSettingStep": "Politica CORS", + "corsPolicyAdded": "La politica CORS è stata aggiunta.", + "createOSSBucketDes": "Vai al <0>Dashboard OSS per creare un Bucket. Sono supportate solo le classi di archiviazione <1>Standard e <2>IA.", + "bucketName": "Nome bucket", + "publicReadBucket": "Lettura pubblica", + "ossEndpointDes": "Vai alla pagina di riepilogo del Bucket, inserisci la <2>Porta nella sezione <1>Accesso tramite Internet, nella pagina <0>Endpoint.", + "ossEndpointDesInternalHint": "Se devi configurare un endpoint di dominio intranet o personalizzato, puoi impostarlo dopo aver creato la politica di archiviazione.", + "obsEndpointCnameHint": "Se devi configurare un endpoint di dominio personalizzato, puoi impostarlo dopo aver creato la politica di archiviazione.", + "endpoint": "EndPoint", + "ossLANEndpointDes": "Lascia vuoto significa non usarlo. Se il tuo Cloudreve è distribuito nei servizi di calcolo Alibaba Cloud che si trovano nella stessa zona di disponibilità del bucket OSS, puoi specificare inoltre un endpoint intranet, Cloudreve cercherà di utilizzare questo endpoint lato server per ridurre i costi del traffico.", + "intranetEndPoint": "Endpoint intranet", + "ossCDNDes": "Vuoi utilizzare Alibaba Cloud CDN per accelerare l'accesso ai file?", + "createOSSCDNDes": "Vai al <0>Dashboard CDN Alibaba Cloud per creare un dominio CDN, la sorgente del CDN dovrebbe essere il tuo bucket OSS. Inserisci il dominio CDN e seleziona se vuoi utilizzare HTTPS:", + "ossAKDes": "Ottieni la tua AccessKey nella pagina <0>Gestione informazioni di sicurezza. Puoi anche creare una AccessKey con permesso <1>AliyunOSSFullAccess in <2>Controllo accesso RAM.", + "shouldNotContainSpace": "Questo non può contenere spazi.", + "nameThePolicyFirst": "Dai un nome alla politica di archiviazione:", + "chunkSizeLabelOSS": "Specifica la dimensione del chunk per i caricamenti ripristinabili. L'intervallo consentito è 100 KB - 5 GB.", + "ossCORSDes": "Questa politica di archiviazione richiede una politica CORS per abilitare il caricamento dal browser. Cloudreve può configurarla automaticamente per te, oppure puoi configurarla manualmente seguendo i passaggi nella documentazione. Se hai già impostato la politica CORS per questo Bucket, questo passaggio può essere saltato.", + "letCloudreveHelpMe": "Lascia che Cloudreve la configuri per me", + "skip": "Salta", + "createUpyunBucketDes": "Compila il nome del servizio di archiviazione che hai creato nel <0>Dashboard Upyun.", + "storageServiceName": "Nome servizio", + "operatorName": "Nome operatore", + "operatorPassword": "Password operatore", + "tokenStatus": "Token anti-hotlinking", + "upyunTokenDes": "È fortemente raccomandato abilitare il Token Anti-Hotlinking, vai al pannello <0>Configurazione funzionalità del servizio di archiviazione creato, vai alla scheda <1>Controllo accesso, abilita Token Anti-Hotlinking e imposta un segreto.", + "tokenEnabled": "Abilita Token Anti-Hotlinking", + "tokenDisabled": "Non utilizzare Token Anti-Hotlinking", + "upyunTokenSecretDes": "Inserisci il segreto del Token Anti-Hotlinking.", + "upyunTokenSecret": "Segreto Token Anti-Hotlinking", + "createCOSBucketDes": "Vai al <0>Dashboard COS per creare un bucket di archiviazione. Vai alla pagina di configurazione di base del bucket creato e copia il <1>Nome bucket sopra.", + "obsBucketDes": "Vai al <0>Dashboard OBS per creare un bucket di archiviazione. Inserisci il <1>Nome bucket che hai appena creato. La classe di archiviazione supporta solo <2>Standard o <3>Accesso infrequente.", + "cosPrivateRW": "Lettura/Scrittura privata", + "cosPublicRW": "Lettura pubblica e scrittura privata", + "cosAccessDomainDes": "Nella pagina panoramica del Bucket creato, compila il <1>Dominio di accesso fornito nella sezione <0>Informazioni dominio. Puoi anche utilizzare il tuo dominio CNAME o dominio di accelerazione CDN.", + "obsEndpointDes": "Nella pagina panoramica del Bucket creato, compila l'<1>Endpoint fornito nella sezione <0>Informazioni dominio.", + "accessDomain": "Dominio di accesso", + "cosCDNDomainDes": "Vai alla <0>Console di gestione CDN Tencent Cloud per creare un dominio di accelerazione CDN e impostare il sito sorgente al bucket COS che hai appena creato. Compila il nome di dominio CDN sotto e seleziona se utilizzare HTTPS.", + "cosCredentialDes": "Compila le chiavi di accesso ottenute dalla pagina <0>Chiavi di accesso di Tencent Cloud. Assicurati che la coppia di chiavi abbia il permesso di accesso ai servizi COS. Puoi anche creare un <2>sotto-utente con permesso <1>Accesso programmatico e concedergli l'accesso al servizio COS.", + "obsCredentialDes": "Compila le chiavi di accesso ottenute dalla pagina <0>Chiavi di accesso di Huawei Cloud. Puoi anche creare un <2>utente IAM con permesso <1>Accesso programmatico e concedergli il permesso <3>OBS OperateAccess.", + "grantAccess": "Concedi accesso", + "grantAccessLater": "Dopo aver creato la politica di archiviazione, devi accedere e concedere l'accesso nella pagina delle impostazioni della politica di archiviazione.", + "odHttpsWarning": "Devi abilitare HTTPS per utilizzare le politiche di archiviazione OneDrive/SharePoint; dopo l'abilitazione, assicurati di cambiare Impostazioni - Base - Informazioni sito - URL del sito.", + "creatAadAppDes": "Vai al <0>Dashboard Microsoft Entra ID, dopo aver effettuato l'accesso, vai al pannello di amministrazione <1>Microsoft Entra ID, puoi opzionalmente utilizzare un account diverso da quello utilizzato per archiviare i file per accedere.", + "createAadAppDes2": "Vai al menu <0>Registrazioni app a sinistra e clicca il pulsante <1>Nuova registrazione. Compila il modulo di registrazione dell'applicazione. Assicurati che <2>Tipi di account supportati sia selezionato come <3>Account in qualsiasi directory organizzativa (Qualsiasi directory Azure AD - Multitenant) e account Microsoft personali (ad esempio Skype, Xbox); <4>URI di reindirizzamento (opzionale) sia selezionato come <5>Web e compila <6>{{url}}; Per gli altri campi, lascia come predefinito.", + "entraIdApp": "Informazioni app Entra ID", + "aadAppIDDes": "Vai alla pagina <0>Panoramica in Gestione applicazioni, il valore di <1>ID applicazione (client).", + "aadAppID": "ID applicazione (client)", + "addAppSecretDes": "Il modo per creare il segreto client: Vai al menu <0>Certificati e segreti sul lato sinistro, clicca il pulsante <1>Nuovo segreto client, e seleziona il tempo più lungo per <2>Scade. Devi creare un nuovo segreto client dopo che quello vecchio scade, e aggiornare quello nuovo nelle impostazioni della politica di archiviazione.", + "aadAppSecret": "Segreto client", + "aadAccountCloud": "Endpoint Microsoft Graph", + "aadAccountCloudDes": "Seleziona l'endpoint in base al tipo di account Microsoft 365 che stai utilizzando.", + "multiTenant": "Cloud pubblico mondiale", + "gallatin": "Cloud cinese 21V", + "sharePointDes": "Vuoi archiviare i file in SharePoint?", + "saveToOneDrive": "Archivia i file su OneDrive predefinito", + "spSiteURL": "URL sito SharePoint", + "odReverseProxyURLDes": "Vuoi utilizzare un server proxy inverso personalizzato per il download dei file?", + "odReverseProxyURL": "URL del server proxy inverso", + "limitOdTPSDes": "Limita la frequenza delle richieste API OneDrive", + "tps": "Limite TPS", + "tpsDes": "Lascia vuoto per indicare nessun limite. Limita questa politica di archiviazione al numero massimo di richieste API inviate a OneDrive per secondo. Le richieste che superano questa frequenza saranno limitate. Quando più nodi Cloudreve trasferiscono file, ognuno usa il proprio token bucket, quindi ridimensiona questo numero appropriatamente in questa condizione.", + "tpsBurst": "Burst TPS", + "tpsBurstDes": "Quando la richiesta è inattiva, Cloudreve può riservare un numero specificato di slot per futuri picchi di traffico.", + "odOauthDes": "Tuttavia, dovrai cliccare il pulsante sotto e autorizzare con l'accesso all'account Microsoft per completare l'inizializzazione prima di poterlo utilizzare. Puoi ri-autorizzare successivamente nella pagina Elenco politiche di archiviazione.", + "gotoAuthPage": "Vai alla pagina di autorizzazione", + "s3BucketDes": "Vai al dashboard AWS S3 per creare un bucket, inserisci il <0>Nome bucket che hai appena creato:", + "s3EndpointDes": "Specifica l'EndPoint (nodo geografico) del bucket di archiviazione in formato URL completo, ad es. <0>https://bucket.region.example.com.", + "selectRegionDes": "Inserisci il codice regione del bucket di archiviazione, ad es. <0>us-east-1. Per fornitori di archiviazione compatibili S3 non-AWS, fai riferimento alla loro documentazione per come compilare questo campo.", + "chunkSizeLabelS3": "Specifica la dimensione del chunk per i caricamenti ripristinabili. L'intervallo consentito è 5 MB - 5 GB.", + "policyEndpoint": "Endpoint.", + "s3Region": "Regione", + "s3EndpointPathStyle": "Seleziona il formato dell'indirizzo S3 Endpoint. Alcune politiche di archiviazione compatibili S3 di terze parti potrebbero richiedere questa opzione per funzionare. Quando attivata, forzeremo l'uso di indirizzi in formato path-like, come <0>http://s3.amazonaws.com/BUCKET/KEY.", + "usePathEndpoint": "Forza stile path", + "thumbExt": "Estensioni che supportano miniature", + "thumbExtDes": "Lascia vuoto per indicare che viene utilizzato il set predefinito della politica di archiviazione. Non valido per politiche di archiviazione locali, S3.", + "driverRoot": "Root driver", + "driverRootDes": "Scegli dove salvare i file nel tuo account OneDrive. Cambiare questa opzione renderà inaccessibili i file esistenti nella politica di archiviazione.", + "saveToDefaultOneDrive": "Salva i file sul driver OneDrive predefinito", + "saveToSharePoint": "Salva i file su SharePoint", + "sharePointUrlDes": "Inserisci l'URL del sito SharePoint. Dopo aver perso il focus, il sistema lo convertirà automaticamente nell'identificatore driver corretto.", + "ks3selectRegionDes": "Inserisci il codice regione del bucket di archiviazione, ad es. <0>BEIJING.", + "ks3EndpointPathStyle": "Seleziona il formato dell'indirizzo KS3 Endpoint.", + "ossRegionDes": "Inserisci il codice regione dove si trova il bucket, ad es. <0>cn-hangzhou. Puoi trovare la regione corrispondente nella tabella <1>Regioni ed endpoint OSS e inserire il <2>ID regione corrispondente." + }, + "node": { + "slave": "slave", + "master": "master", + "noCapabilities": "Nessuna capacità abilitata.", + "active": "Attivo", + "suspended": "Sospeso", + "deleteNodeConfirmation": "Sei sicuro di voler eliminare il nodo {{name}}?", + "editNode": "Modifica nodo {{node}}", + "thisIsMasterNodes": "Stai modificando un nodo master, che sta servendo il sito corrente.", + "enableNode": "Abilita nodo", + "enableNodeDes": "Dopo l'abilitazione, il nodo accetterà ed elaborerà le funzionalità che sono state abilitate.", + "name": "Nome", + "nameNode": "Nome nodo, utilizzato anche per mostrare agli utenti.", + "type": "Tipo", + "server": "Endpoint nodo", + "serverDes": "Endpoint utilizzato per la comunicazione del nodo. Se vuoi archiviare file su questo nodo, questo indirizzo sarà anche esposto al lato utente per i caricamenti di file.", + "loadBalancerRankDes": "Specifica un peso di bilanciamento del carico per questo nodo, il valore è un intero, più alto è il valore, maggiore è la probabilità di essere selezionato.", + "loadBalancerRank": "Peso bilanciamento del carico", + "slaveSecret": "Segreto slave", + "slaveSecretDes": "Segreto utilizzato per la comunicazione del nodo slave con il nodo master. Deve essere coerente con <1>Secret nella sezione <1>Slave del file di configurazione del nodo slave.", + "testNode": "Testa comunicazione nodo", + "testNodeSuccess": "Comunicazione nodo riuscita.", + "createArchiveDes": "Accetta richieste di task di creazione archivio.", + "extractArchiveDes": "Accetta richieste di task di estrazione archivio.", + "remoteDownloadDes": "Accetta richieste di task di download remoto. Dopo l'abilitazione, devi anche configurare le informazioni relative al download remoto sotto.", + "downloader": "Downloader", + "aria2Des": "Avvia Aria2 come stesso utente/livello di accesso che esegue Cloudreve sul server del nodo target, abilita il servizio RPC nel file di configurazione Aria2, per maggiori informazioni e linee guida, fai riferimento alla sezione \"Download remoto\" della documentazione.", + "qbittorrentDes": "Avvia qBittorrent come stesso utente che esegue Cloudreve sul server del nodo target, abilita il servizio Web UI nelle impostazioni qBittorrent, per maggiori informazioni e linee guida, fai riferimento alla sezione \"Download remoto\" della documentazione.", + "rpcServer": "Server RPC", + "rpcServerHelpDes": "Indirizzo server RPC contenente numero di porta completo, ad es. <0>http://127.0.0.1:6800/.", + "rpcToken": "Token RPC", + "rpcTokenDes": "Coerente con <0>rpc-secret nel file di configurazione Aria2; lascia vuoto se non impostato.", + "downloaderOptionDes": "Configurazione downloader aggiuntiva quando si crea un task di download, scritta in formato JSON chiave-valore, vedi la <0>documentazione ufficiale downloader per i parametri disponibili.", + "refreshInterval": "Intervallo aggiornamento stato (secondi)", + "refreshIntervalDes": "L'intervallo con cui Cloudreve richiede un aggiornamento dello stato del task dal downloader. L'intervallo di aggiornamento effettivo dipende anche dalla configurazione della coda \"Download remoto\" e dalla occupazione del downloader.", + "waitForSeeding": "Aspetta il seeding", + "waitForSeedingDes": "Dopo l'abilitazione, quando il task di download remoto è completato, il nodo manterrà il task nello stato di seeding fino a quando non viene soddisfatta la condizione di completamento del seeding nella configurazione del downloader. Questa funzionalità ha effetto solo dopo il completamento del task di download remoto, e non influenzerà l'uso dei file scaricati da parte dell'utente.", + "webUIEndpoint": "Endpoint Web UI", + "webUIEndpointDes": "L'endpoint della Web UI qBittorrent, ad es. <0>http://127.0.0.1:8080/.", + "tempPath": "Directory download temporanea", + "tempPathDes": "La directory sul nodo che Aria2 utilizza come directory di download temporanea. Il processo Cloudreve sul nodo necessita di permessi di lettura, scrittura ed esecuzione su questa directory, e anche il downloader deve poter accedere a questa directory. Lascia vuoto per utilizzare il percorso file temporaneo predefinito.", + "webUIUsername": "Nome utente Web UI", + "webUIPassword": "Password Web UI", + "webUICredDes": "Lascia vuoto se l'autenticazione non è abilitata.", + "downloaderTestPass": "Connesso con successo al downloader, versione: {{version}}", + "testDownloader": "Testa comunicazione downloader", + "addNewNode": "Nuovo nodo", + "nameTheNode": "Dai un nome al nodo:", + "copyBinary": "", + "runCrSlave": "Esegui Cloudreve sul nodo con la stessa versione del master, e avvialo con il seguente file di configurazione:", + "keepIfUpload": "Se devi utilizzare questo nodo per politiche di archiviazione in futuro, mantieni la seguente configurazione CORS.", + "storeFiles": "Archivia file", + "storeFilesDes": "Usa questo nodo per archiviare file utente.", + "storeFilesHint": "Se vuoi utilizzare questo nodo per politiche di archiviazione, crea una politica di archiviazione slave e seleziona questo nodo.", + "runCrWithConfig": "Salva il file sopra come file <0>config.ini, e avvia Cloudreve con questo file: <0>./cloudreve -c config.ini. Un'istanza Cloudreve slave può servire più nodi master Cloudreve; aggiungi semplicemente questo nodo slave a tutti i nodi master e mantieni lo stesso segreto.", + "inputServer": "Inserisci l'endpoint del nodo:", + "testButton": "Puoi cliccare il pulsante sotto per testare se la comunicazione è riuscita.", + "hostHeaderHint": "Se c'è un errore di firma, controlla se il proxy inverso davanti al nodo sta passando l'header <0>Host.", + "features": "Funzionalità abilitate", + "remoteDownload": "Download remoto", + "refresh": "Aggiorna" + }, + "group": { + "countUser": "Conteggio", + "anonymous": "Gruppo utenti anonimi", + "sysGroup": "Gruppo utenti di sistema", + "adminGroup": "Gruppo utenti admin", + "#": "#", + "name": "Nome", + "type": "Politica di archiviazione", + "count": "Utenti figlio", + "size": "Quota di archiviazione", + "nameOfGroup": "Nome", + "nameOfGroupDes": "Nome del gruppo, utilizzato per mostrare agli utenti.", + "availablePolicies": "Politiche di archiviazione disponibili", + "availablePoliciesDes": "Seleziona le politiche di archiviazione che questo gruppo può utilizzare. Modificare questa impostazione non influenzerà i file caricati dagli utenti.", + "initialStorageQuota": "Quota di archiviazione iniziale", + "initialStorageQuotaDes": "Massimo spazio di archiviazione che può essere utilizzato da un singolo utente sotto questo gruppo.", + "isAdmin": "Gruppo admin", + "isAdminDes": "Quando abilitato, gli utenti sotto questo gruppo avranno permessi admin.", + "share": "Condivisione", + "allowCreateShareLink": "Crea link di condivisione", + "allowCreateShareLinkDes": "Se disabilitato, gli utenti non possono creare link di condivisione.", + "shareFree": "Link di condivisione gratuito", + "shareFreeDes": "Quando abilitato, gli utenti possono accedere a tutti i link di condivisione a pagamento senza acquistare.", + "fileManagement": "Gestione file", + "allowWabDAV": "WebDAV", + "allowWabDAVDes": "Se disabilitato, gli utenti non possono connettersi all'archiviazione tramite il protocollo WebDAV", + "allowWabDAVProxy": "Proxy WebDAV", + "allowWabDAVProxyDes": "Se abilitato, gli utenti possono configurare il WebDAV per essere proxato da Cloudreve durante il download dei file.", + "compressTask": "Task di compressione/decompressione", + "compressTaskDes": "Se abilitato, gli utenti possono fare compressione/decompressione per i file online.", + "compressSize": "Dimensione massima file da comprimere", + "compressSizeDes": "La dimensione totale massima dei file dei lavori di compressione che possono essere creati dall'utente, inserisci 0 per indicare nessun limite. Questo limite non viene controllato quando si creano task di compressione, e se la dimensione totale dei file originali supera questo limite durante l'esecuzione, il task fallirà.", + "decompressSize": "Dimensione massima file da decomprimere", + "decompressSizeDes": "La dimensione totale massima dei file dei lavori di decompressione che possono essere creati dall'utente, inserisci 0 per indicare nessun limite.", + "allowRemoteDownload": "Download remoto", + "allowRemoteDownloadDes": "Se permettere agli utenti di creare task di download remoto. Se devi utilizzare il download remoto, devi anche avere nodi con download remoto abilitato nell'<0>Elenco nodi.", + "aria2Options": "Opzioni job downloader", + "aria2OptionsDes": "Parametri extra per i downloader (qBittorrent o Aria2), scritti in formato JSON chiave-valore, vedi la documentazione ufficiale del downloader per i parametri disponibili.", + "aria2BatchSize": "Dimensione massima batch task download remoto", + "aria2BatchSizeDes": "Numero massimo per l'invio di task di download remoto in batch, inserisci 0 per indicare nessun limite.", + "migratePolicy": "Riloca politica di archiviazione", + "migratePolicyDes": "Se l'utente crea un task di rilocazione della politica di archiviazione.", + "advanceDelete": "Opzioni avanzate eliminazione file", + "advanceDeleteDes": "Una volta abilitato, gli utenti possono scegliere se mantenere i file fisici quando eliminano i file. Abilita questa opzione solo per gruppi utenti fidati.", + "allowSelectNode": "Permetti selezione nodo", + "allowSelectNodeDes": "Quando abilitato, l'utente può selezionare il nodo preferito prima di creare task. Quando disabilitato, il nodo sarà bilanciato dal sistema all'interno dei nodi consentiti per il gruppo.", + "allowedNodes": "Nodi consentiti", + "allowedNodesDes": "Specifica i nodi che questo gruppo può utilizzare per creare task. Lista vuota significa che tutti i nodi sono disponibili. Gli utenti possono solo selezionare o essere assegnati nodi all'interno di questa lista dal bilanciatore di carico. Attualmente, i task coperti sono: download remoto, compressione/decompressione file. Altri task saranno assegnati al nodo master.", + "allNodes": "Tutti i nodi", + "esclateAnonymity": "Escala anonimato", + "esclateAnonymityDes": "Quando abilitato, gli utenti possono assegnare permessi più alti per utenti anonimi (scrittura/eliminazione/creazione). Quando disabilitato, gli utenti possono assegnare solo permessi di sola lettura per utenti anonimi. Cambiare questa impostazione non influenzerà i link di condivisione o file esistenti.", + "allowDownloadShare": "Accedi ai link condivisi", + "allowDownloadShareDes": "Quando disabilitato, gli utenti non possono visualizzare i link condivisi di altri. Questa impostazione ha precedenza sulle impostazioni dei permessi del link di condivisione.", + "deletedNode": "Nodo eliminato #{{id}}", + "maxWalkedFiles": "Massimo file percorsi", + "maxWalkedFilesDes": "In alcune operazioni che richiedono attraversamento profondo dei file, il numero massimo di file consentiti da attraversare.", + "trashBinDuration": "Durata cestino (secondi)", + "trashBinDurationDes": "Il tempo di ritenzione dei file nel cestino, i file saranno eliminati permanentemente dopo il tempo di scadenza. Cambiare questa impostazione non influenzerà i file già nel cestino.", + "serverSideBatchDownload": "Download batch lato server", + "serverSideBatchDownloadDes": "Se permettere agli utenti di selezionare più file per utilizzare il download batch relay lato server, dopo la disabilitazione, gli utenti possono ancora utilizzare la funzionalità di download batch basata puramente sul browser.", + "uploadDownload": "Caricamento e download", + "getDirectLink": "Ottieni link diretto", + "getDirectLinkDes": "Se permettere agli utenti di ottenere il link diretto del file.", + "bathSourceLinkLimit": "Dimensione massima link diretti batch", + "bathSourceLinkLimitDes": "Il numero massimo di file consentiti agli utenti per ottenere link diretti in un singolo batch, inserisci 0 significa che non è consentita la generazione batch di link diretti.", + "redirectedSource": "Usa link diretto reindirizzato", + "redirectedSourceDes": "Raccomandato abilitare. Quando abilitato, il link diretto al file ottenuto dall'utente sarà reindirizzato da Cloudreve con un link più breve. Quando disabilitato, il link diretto al file ottenuto dall'utente diventa l'URL originale al file, ed è legato alla versione del file. Alcune politiche producono link diretti non reindirizzati che non rimangono persistenti; vedi i documenti Cloudreve per i dettagli.", + "reuseDirectLink": "Riutilizza link diretto esistente", + "reuseDirectLinkDes": "Quando abilitato, più richieste per il link diretto dello stesso file riutilizzeranno il link di reindirizzamento esistente.", + "downloadSpeedLimit": "Velocità massima download", + "downloadSpeedLimitDes": "Inserisci 0 per indicare nessun limite. Quando la restrizione è attivata, la velocità massima di download sarà limitata quando gli utenti scaricano tutti i file sotto la politica di archiviazione che supporta il limite di velocità.", + "anonymousHint": "Questo gruppo utenti corrisponde al visitatore anonimo che non è autenticato.", + "create": "Crea", + "copyFromExisting": "Copia da gruppo esistente?", + "notCopy": "Non copiare", + "confirmDelete": "Sei sicuro di voler eliminare il gruppo {{group}}?", + "new": "Nuovo gruppo", + "editGroup": "Modifica {{group}}" + }, + "user": { + "createdAt": "Creato il", + "originUserGroup": "Gruppo utenti originale", + "originUserGroupDes": "Gruppo utenti a cui l'utente appartiene prima di acquistare il gruppo corrente, il gruppo corrente tornerà a questo gruppo dopo la scadenza.", + "noOriginUserGroup": "No", + "groupExpired": "Data scadenza gruppo", + "groupExpiredDes": "Data scadenza gruppo in formato ISO8601, lascia vuoto significa che il gruppo è permanente.", + "openUserFiles": "Apri file utente", + "id": "ID", + "idValue": "{{id}} ({{hash_id}})", + "avatar": "Foto profilo", + "removeAvatar": "Rimuovi foto profilo", + "userDialogTitle": "Dettagli utente", + "2FAEnabled": "2FA abilitato", + "qqEnabled": "QQ collegato", + "logtoEnabled": "Logto collegato", + "oidcEnabled": "OIDC collegato", + "deleted": "Utente eliminato.", + "new": "Nuovo utente", + "filter": "Filtro", + "emptyNoFilter": "Lascia vuoto significa nessun filtro.", + "selectedObjects": "{{num}} oggetti selezionati.", + "nick": "Nome visualizzato", + "email": "Email", + "group": "Gruppo", + "status": "Stato", + "usedStorage": "Archiviazione utilizzata", + "status_active": "Attivo", + "status_inactive": "Inattivo", + "status_manual_banned": "Bloccato manualmente", + "status_sys_banned": "Bloccato dal sistema", + "toggleBan": "Blocca/Sblocca", + "filterCondition": "Condizioni filtro", + "all": "Tutti", + "userStatus": "Stato utente", + "apply": "Applica", + "editUser": "Modifica {{nick}}", + "password": "Password", + "passwordDes": "Lascia vuoto significa nessuna modifica.", + "groupDes": "Gruppo a cui appartiene l'utente.", + "2FA": "2FA", + "notEnabled": "Non abilitato", + "reset2Fa": "Disabilita", + "reset": "Ripristina", + "confirmDelete": "Sei sicuro di voler eliminare l'utente {{user}}?", + "deleteXUsers": "Elimina {{num}} utenti", + "confirmBatchDelete": "Sei sicuro di voler eliminare {{num}} utenti?", + "calibrateStorage": "Calibra archiviazione", + "calibrateStorageSuccess": "Archiviazione calibrata con successo." + }, + "file": { + "deleteXFiles": "Elimina {{num}} file", + "confirmBatchDelete": "Sei sicuro di voler eliminare {{num}} file?", + "confirmDelete": "Sei sicuro di voler eliminare il file {{file}}?", + "haveShares": "Condiviso", + "haveDirectLinks": "Ha link diretti reindirizzati", + "directLinkId": "Identificatore link", + "directLinks": "Link diretti reindirizzati", + "noRecords": "Nessun record", + "speed": "Limite velocità", + "downloads": "Download", + "shareLink": "Link condivisione", + "shareLinkNum": "{{num}} (<0>Visualizza)", + "blobType": "Tipo", + "noEntities": "Nessun Blob", + "blobs": "Blob", + "creator": "Creatore", + "source": "Origine", + "key": "Chiave", + "value": "Valore", + "isPublic": "Pubblico", + "noMetadata": "Nessun metadata", + "metadata": "Metadata", + "id": "ID", + "primaryStoragePolicy": "Politica di archiviazione primaria", + "fileDialogTitle": "Dettagli file", + "name": "Nome file", + "deleteAsync": "Il task di eliminazione sarà eseguito in background.", + "forceDelete": "Forza eliminazione", + "size": "Dimensione", + "sizeUsed": "Archiviazione utilizzata", + "uploader": "Proprietario", + "createdAt": "Creato il", + "uploading": "Caricamento", + "unknownUploader": "Sconosciuto", + "uploaderID": "ID proprietario", + "searchFileName": "Cerca nome file", + "storagePolicy": "Politica di archiviazione", + "selectTargetUser": "Seleziona utente target", + "importTaskCreated": "Task di importazione creato, puoi visualizzare il suo stato nella lista task in background.", + "manuallyPathOnly": "La politica di archiviazione selezionata supporta solo l'inserimento manuale del percorso.", + "selectFolder": "Seleziona cartella", + "import": "Importa", + "importExternalFolder": "Importa cartelle esterne", + "importExternalFolderDes": "Puoi importare file esistenti e strutture di directory dalla tua politica di archiviazione in Cloudreve. L'operazione di importazione non occuperà archiviazione fisica aggiuntiva, ma dedurrà comunque la quota di archiviazione utilizzata dall'utente normalmente.", + "storagePolicyDes": "Seleziona la politica di archiviazione dove i file da importare sono attualmente archiviati.", + "targetUser": "Utente target", + "targetUserDes": "Seleziona il file system di quale utente vuoi importare i file.", + "srcFolderPath": "Percorso cartella sorgente", + "select": "Seleziona", + "selectSrcDes": "Il percorso della directory da importare dal lato storage.", + "dstFolderPath": "Percorso cartella destinazione", + "dstFolderPathDes": "Percorso nel file system dell'utente per contenere tutti i file importati.", + "recursivelyImport": "Importa ricorsivamente", + "recursivelyImportDes": "Se importare ricorsivamente tutte le sottodirectory sotto la directory.", + "createImportTask": "Crea task di importazione", + "unlink": "Scollega (Mantieni file fisico)", + "searchUser": "Cerca utente per nome o email...", + "extractMediaMeta": "Estrai informazioni media", + "extractMediaMetaDes": "Se estrarre le informazioni multimediali per ogni file durante l'importazione.", + "importWarning": "Avviso", + "importWarnings": [ + "Dopo l'importazione, il file fisico sarà gestito da Cloudreve, non modificarlo esternamente in seguito.", + "Non importare lo stesso file più volte.", + "Se il file dell'utente è in conflitto, questo file verrà saltato." + ], + "otherConditions": "Altre condizioni", + "shareLinkExisted": "Ha link di condivisione", + "directLinkExisted": "Ha link diretto", + "isUploading": "In caricamento" + }, + "entity": { + "refenenceCount": "Conteggio riferimenti", + "waitForRecycle": "In attesa di riciclo", + "entityDialogTitle": "Dettagli Blob", + "uploadSessionID": "ID sessione caricamento", + "referredFiles": "File riferiti", + "confirmBatchDelete": "Sei sicuro di voler eliminare {{num}} Blob?", + "deleteXEntities": "Elimina {{num}} Blob", + "forceDelete": "Forza eliminazione", + "forceDeleteDes": "Se eliminare il record Blob indipendentemente dal fatto che il file fisico venga eliminato." + }, + "event": { + "cleanup": "Pulizia", + "cleanupAuditLog": "Pulizia eventi", + "cleanupAuditLogDescription": "Elimina tutti gli eventi che soddisfano le seguenti condizioni:", + "cleanupNotAfter": "Prima di questa data", + "cleanupEventTypes": "Tipi di eventi", + "cleanupEventTypesDes": "Seleziona i tipi di eventi da pulire. Lascia vuoto per pulire tutti i tipi.", + "initiator": "Iniziatore", + "event": "Evento", + "userID": "ID utente", + "ip": "IP", + "type": "Tipo", + "correlationId": "ID correlazione", + "fileID": "ID file", + "emailSend": "Invia email \"{{title}}\" a {{email}}", + "emailFailed": "Avvio coda email fallito", + "signinFailed": "Accesso fallito: {{reason}}", + "createDavAccount": "Crea account WebDAV: {{account}}", + "updateDavAccount": "Aggiorna account WebDAV: {{account}}", + "deleteDavAccount": "Elimina account WebDAV: {{account}}", + "pointsChange": "Cambio punti: {{points}}", + "storageAdded": "Acquistato {{size}} storage", + "nickChange": "Nome visualizzato cambiato da {{old}} a {{new}}", + "eventDialogTitle": "Dettagli evento", + "userAgent": "User agent", + "linkedUser": "Utente collegato", + "datetime": "Ora", + "linkedFile": "File collegato", + "linkedEntity": "Blob collegato", + "linkedShare": "Condivisione collegata", + "rawContent": "Contenuto raw", + "confirmDelete": "Sei sicuro di voler eliminare questo evento?", + "deleteXEvents": "Elimina {{num}} eventi", + "confirmBatchDelete": "Sei sicuro di voler eliminare {{num}} eventi?" + }, + "share": { + "confirmBatchDelete": "Sei sicuro di voler eliminare {{num}} condivisioni?", + "confirmDelete": "Sei sicuro di voler eliminare questa condivisione?", + "deleteXShares": "Elimina {{num}} condivisioni", + "shareDialogTitle": "Dettagli condivisione", + "shareLink": "Link di condivisione", + "deleted": "File eliminato", + "srcFileName": "File sorgente", + "views": "Visualizzazioni", + "downloads": "Download", + "price": "Prezzo", + "autoExpire": "Scadenza automatica", + "owner": "Proprietario", + "createdAt": "Creato il", + "private": "Nascondi dalla pagina profilo", + "yes": "Sì", + "no": "No", + "afterNDownloads": "Dopo {{num}} download.", + "none": "Nessuno", + "srcType": "Tipo oggetto sorgente", + "folder": "Cartella", + "file": "File" + }, + "task": { + "cleanupTasks": "Pulizia task", + "cleanupTasksDescription": "Pulisci tutti i task che soddisfano le seguenti condizioni:", + "cleanupNotAfter": "Prima di questa data", + "cleanupTaskTypes": "Tipi di task", + "cleanupTaskTypesDes": "Seleziona i tipi di task da pulire. Lascia vuoto per pulire tutti i tipi.", + "cleanupTaskStatuses": "Stati task", + "cleanupTaskStatusesDes": "Seleziona gli stati dei task da pulire. Lascia vuoto per pulire tutti i task con stato completato.", + "confirmDelete": "Sei sicuro di voler eliminare questo task?", + "confirmBatchDelete": "Sei sicuro di voler eliminare {{num}} task?", + "deleteXTasks": "Elimina {{num}} task", + "blobID": "ID Blob", + "retryIndex": "Indice retry", + "entityError": "Blob che non sono riusciti a riciclare", + "updatedAt": "Aggiornato il", + "taskDialogTitle": "Dettagli task", + "explicitEntityRecycle": "Ricicla esplicitamente Blob file: {{blobs}}", + "entityRecycleRoutine": "Scansiona e ricicla Blob file", + "mediaMetadata": "Estrai meta media di Blob <0>#{{entityID}}", + "uploadSentinelCheck": "Controlla stato sessione caricamento {{uploadSessionID}}", + "remoteDownload": "Download remoto: ", + "owner": "Proprietario", + "content": "Contenuto", + "status": "Stato", + "create_archive": "Crea archivio", + "extract_archive": "Estrai archivio", + "relocate": "Riposiziona", + "remote_download": "Download remoto", + "media_meta": "Metadata media", + "entity_recycle_routine": "Routine riciclo entity", + "explicit_entity_recycle": "Riciclo entity esplicito", + "upload_sentinel_check": "Controllo sentinel caricamento", + "import": "Importazione esterna", + "type": "Tipo", + "node": "Nodo distribuito", + "createdBy": "Creato da", + "ready": "Pronto", + "downloading": "Scaricando", + "paused": "In pausa", + "seeding": "Seeding", + "error": "Errore", + "finished": "Finito", + "canceled": "Annullato/Fermato", + "unknown": "Sconosciuto", + "errorMsg": "Messaggio errore" + }, + "payment": { + "tradeNo": "N. Transazione", + "productType": "Tipo prodotto", + "providerID": "ID Provider", + "status": "Stato", + "deleteXPayments": "Elimina {{num}} pagamenti" + }, + "customProps": { + "add": "Aggiungi", + "type": "Tipo", + "default": "Valore predefinito", + "actions": "Azioni", + "text": "Testo", + "number": "Numero", + "boolean": "Checkbox", + "select": "Selezione singola", + "multiSelect": "Selezione multipla", + "user": "Utente", + "link": "Link", + "rating": "Valutazione", + "addProp": "Aggiungi proprietà", + "editProp": "Modifica proprietà", + "icon": "Icona", + "iconDes": "Nome icona <0>Iconify, lascia vuoto per nascondere l'icona.", + "id": "ID", + "idDes": "ID proprietà, assicurati che sia unico tra tutte le proprietà.", + "idPatternDes": "Sono consentiti solo lettere, numeri, underscore e trattini.", + "minLength": "Lunghezza minima", + "maxLength": "Lunghezza massima", + "emptyLimit": "Lascia vuoto per non limitare.", + "minValue": "Valore minimo", + "maxValue": "Valore massimo", + "options": "Opzioni", + "optionsDes": "Un'opzione per riga." + }, + "vas": { + "disableSubAddressEmail": "Disabilita email sub-address", + "disableSubAddressEmailDes": "Una volta abilitato, gli indirizzi email contenenti <0>+ non possono essere utilizzati per la registrazione.", + "confirmDelete": "Sei sicuro di voler eliminare questi ordini?", + "vas": "VAS", + "reports": "Report", + "orders": "Pagamenti", + "initialFiles": "File iniziali", + "initialFilesDes": "Specifica i file che l'utente possiede inizialmente dopo la registrazione. Inserisci un ID file per cercare file esistenti.", + "filterEmailProvider": "Filtra provider email", + "filterEmailProviderDisabled": "Disabilitato", + "filterEmailProviderWhitelist": "Whitelist", + "filterEmailProviderBlacklist": "Blacklist", + "filterEmailProviderDes": "Limita il provider email per la registrazione, l'accesso SSO di terze parti non è limitato.", + "filterEmailProviderRule": "Regole filtro dominio email", + "filterEmailProviderRuleDes": "Separa più campi con punto e virgola.", + "qqConnect": "QQ Connect", + "qqConnectHint": "Durante la creazione dell'applicazione, inserisci l'URL di callback: {{url}}", + "enableQQConnect": "Abilita QQ Connect", + "enableQQConnectDes": "Se consentire il binding QQ, usa QQ per accedere al sito web.", + "loginWithoutBinding": "Accesso senza registrazione", + "loginWithoutBindingDes": "Una volta abilitato, se un utente accede da terze parti ma non ha un account collegato, il sistema creerà un account per loro. Gli utenti che accedono in questo modo potranno accedere solo utilizzando questa terza parte in futuro.", + "appid": "APP ID", + "appidDes": "L'APP ID ottenuto dalla pagina di gestione applicazioni.", + "appKey": "APP KEY", + "appKeyDes": "L'APP KEY ottenuto dalla pagina di gestione applicazioni.", + "overuseReminder": "Promemoria superamento utilizzo", + "overuseReminderDes": "Template email promemoria inviato agli utenti dopo che la loro capacità supera il limite a causa di VAS scaduto.", + "vasSetting": "Impostazioni VAS", + "storagePack": "Pacchetti storage", + "purchasableGroups": "Membership", + "giftCodes": "Codici regalo", + "enable": "Abilita", + "appID": "App ID", + "appIDDes": "APPID dell'applicazione di pagamento.", + "rsaPrivate": "Chiave privata RSA applicazione", + "rsaPrivateDes": "La chiave privata RSA2 (SHA256) per l'applicazione di pagamento, tipicamente generata da te. Per dettagli, consulta <0>Generazione Chiavi RSA.", + "alipayPublicKey": "Chiave pubblica Alipay", + "alipayPublicKeyDes": "Fornita da Alipay, disponibile in Gestione Applicazioni - Informazioni Applicazione - Metodo Firma API.", + "wechatPay": "WeChat Pay", + "applicationID": "ID Applicazione", + "applicationIDDes": "Numero pubblico o appid applicazione mobile richiesto dai commercianti.", + "merchantID": "Numero commerciante", + "merchantIDDes": "Il numero commerciante generato ed emesso da WeChat Pay.", + "apiV3Secret": "Secret API v3", + "apiV3SecretDes": "Il commerciante deve impostare il secret in [Piattaforma Commerciante] - [Sicurezza API] prima della richiesta WeChat Pay. La lunghezza della chiave è di 32 byte.", + "mcCertificateSerial": "Numero seriale certificato commerciante", + "mcCertificateSerialDes": "Naviga a [Sicurezza API] - [Certificato API] - [Visualizza Certificato] per vedere il numero seriale certificato API commerciante.", + "mcAPISecret": "Secret API Commerciante", + "mcAPISecretDes": "Contenuto del file secret apiclient_key.pem.", + "payjs": "PAYJS", + "payjsWarning": "Questo servizio è fornito da <0>PAYJS, una piattaforma di terze parti, e qualsiasi disputa derivante da esso non è responsabilità degli sviluppatori Cloudreve.", + "mcNumber": "Numero commerciante", + "mcNumberDes": "Disponibile nella home page del pannello admin PAYJS.", + "communicationSecret": "Chiave comunicazione", + "otherSettings": "Altre Impostazioni", + "banBufferPeriod": "Periodo buffer sospensione (secondi)", + "banBufferPeriodDes": "La durata massima di tempo che un utente può mantenere lo stato di superamento capacità, oltre il quale l'utente verrà sospeso dal sistema.", + "allowSellShares": "Consenti prezzi per condivisioni", + "allowSellSharesDes": "Una volta abilitato gli utenti possono impostare un prezzo crediti per la condivisione e i crediti verranno detratti per il download.", + "creditPriceRatio": "Tasso arrivo crediti (%)", + "creditPriceRatioDes": "Il tasso di crediti che arrivano effettivamente al condivisore per l'acquisto di una condivisione con un prezzo impostato per il download.", + "creditPrice": "Prezzo crediti (centesimo)", + "creditPriceDes": "Prezzo durante la ricarica crediti", + "add": "Aggiungi", + "name": "Nome", + "price": "Prezzo", + "duration": "Durata", + "size": "Dimensione", + "actions": "Azioni", + "orCredits": " O {{num}} crediti", + "highlight": "Evidenzia", + "yes": "Sì", + "no": "No", + "productName": "Nome prodotto", + "qyt": "Qtà.", + "code": "Codice", + "status": "Stato", + "invalidProduct": "Prodotto non valido", + "used": "Usato", + "notUsed": "Non usato", + "generatingResult": "Risultato", + "addStoragePack": "Aggiungi pacchetto storage", + "editStoragePack": "Modifica pacchetto storage", + "productNameDes": "Nome visualizzazione prodotto", + "packSizeDes": "Dimensione del pacchetto storage", + "durationDay": "Durata (giorni)", + "durationDayDes": "Durata validità di ogni pacchetto storage.", + "priceYuan": "Prezzo (Yuan)", + "packPriceDes": "Prezzo del pacchetto storage.", + "priceCredits": "Prezzo (Crediti)", + "priceCreditsDes": "Il prezzo quando si usano i crediti per acquistare, riempi 0 significa che non puoi usare i crediti per acquistare.", + "editMembership": "Modifica membership", + "addMembership": "Aggiungi membership", + "group": "Gruppo", + "groupDes": "Gruppi utenti aggiornati dopo l'acquisto.", + "durationGroupDes": "La validità del tempo di acquisto dell'unità gruppo utenti aggiornata dopo l'acquisto.", + "groupPriceDes": "Prezzo membership", + "productDescription": "Descrizione prodotto (Una per riga)", + "productDescriptionDes": "Descrizione del prodotto visualizzata nella pagina di acquisto.", + "highlightDes": "Una volta abilitato, verrà evidenziato nella pagina di selezione prodotto.", + "generateGiftCode": "Genera codici regalo", + "numberOfCodes": "Numero di codici", + "numberOfCodesDes": "Numero di codici regalo da generare.", + "linkedProduct": "Prodotto collegato", + "productQyt": "Qtà prodotto", + "productQytDes": "Per i prodotti crediti, questo è il numero di punti e altri prodotti sono multipli di durate.", + "freeDownload": "Download gratuito file condivisi", + "freeDownloadDes": "Una volta abilitato, l'utente può scaricare condivisioni a pagamento gratuitamente.", + "credits": "Crediti", + "markSuccessful": "Contrassegnato con successo.", + "markAsResolved": "Contrassegna come risolto", + "reportedContent": "Contenuto segnalato", + "reason": "Motivo", + "description": "Descrizione", + "reportTime": "Segnalato il", + "invalid": "[Non valido]", + "deleteShare": "Elimina link condivisione", + "orderDeleted": "Ordine eliminato.", + "orderName": "Nome", + "product": "Prodotto", + "paymentId": "ID Pagamento", + "orderNumber": "N. Transazione", + "amount": "Importo", + "paidBy": "Pagato con", + "orderOwner": "Creato da", + "unpaid": "Non pagato", + "paid": "Pagato", + "shareLink": "Link condiviso", + "mobileApp": "Applicazione mobile", + "showAppPromotion": "Mostra pagina promozione", + "showAppPromotionDes": "Una volta abilitato, l'utente può vedere la pagina guida per l'applicazione mobile nella pagina \"Connetti e Monta\".", + "customPaymentName": "Nome metodo pagamento", + "customPaymentNameDes": "Nome del metodo di pagamento utilizzato per visualizzare all'utente.", + "customPaymentSecretDes": "Chiave segreta per firmare le richieste di pagamento.", + "customPaymentEndpoint": "URL API Pagamento", + "customPaymentEndpointDes": "URL da richiedere durante la creazione di un ordine di pagamento.", + "appFeedback": "URL Feedback", + "appForum": "URL forum utenti", + "appLinkDes": "Verrà visualizzato nel client mobile, lascia vuoto per nascondere la voce menu. Questa impostazione avrà effetto solo se la licenza VOL è valida." + }, + "pro": { + "title": "Funzioni esclusive della versione Pro", + "description": "La funzione che stai cercando di accedere è disponibile solo nella versione Pro di Cloudreve, aggiorna per sbloccare tutte le funzionalità avanzate.", + "proInclude": "La versione Pro include:", + "shareLinkCollabration": "Condividi link di collaborazione", + "filePermission": "Gestione permessi file", + "multipleStoragePolicy": "Cambio politiche di storage e politiche di storage directory", + "auditAndActivity": "Registro attività file e sistema", + "vasService": "Servizi aggiuntivi e sistema di punti", + "sso": "SSO di accesso singolo", + "more": "......", + "later": "Mai", + "learnMore": "Scopri di più sulla versione Pro", + "promotionTitle": "Promozione speciale di aggiornamento della versione comunità", + "promotion": "Usa il codice di promozione <0>{{code}} al momento dell'acquisto per ottenere un <1>-{{discount}}% di sconto." + }, + "abuseReport": { + "deleteXAbuseReports": "Elimina {{num}} segnalazioni abuso", + "folderPath": "Percorso cartella", + "reporter": "Segnalatore", + "shareLink": "Link condiviso <0>#{{id}}", + "deletedShare": "Link condiviso eliminato", + "deletedUser": "Utente eliminato", + "confirmDelete": "Sei sicuro di voler eliminare questa segnalazione abuso?", + "confirmBatchDelete": "Sei sicuro di voler eliminare {{num}} segnalazioni abuso?", + "reporterID": "ID utente segnalatore", + "reportedUserID": "ID utente segnalato", + "shareID": "ID Condiviso", + "reason": "Motivo" + } +} diff --git a/public/locales/it-IT/image_editor.json b/public/locales/it-IT/image_editor.json new file mode 100755 index 0000000..30eb526 --- /dev/null +++ b/public/locales/it-IT/image_editor.json @@ -0,0 +1,113 @@ +{ + "name": "Nome", + "save": "Salva", + "saveAs": "Salva come", + "back": "Indietro", + "loading": "Caricamento...", + "resetOperations": "Ripristina/elimina tutte le operazioni", + "changesLoseWarningHint": "Se premi il pulsante \"ripristina\" le tue modifiche andranno perse. Vuoi continuare?", + "discardChangesWarningHint": "Se chiudi la finestra modale, la tua ultima modifica non sarà salvata.", + "cancel": "Annulla", + "apply": "Applica", + "warning": "Avviso", + "confirm": "Conferma", + "discardChanges": "Scarta modifiche", + "undoTitle": "Annulla ultima operazione", + "redoTitle": "Ripeti ultima operazione", + "showImageTitle": "Mostra immagine originale", + "zoomInTitle": "Ingrandisci", + "zoomOutTitle": "Rimpicciolisci", + "toggleZoomMenuTitle": "Attiva/disattiva menu zoom", + "adjustTab": "Regola", + "finetuneTab": "Regolazione fine", + "filtersTab": "Filtri", + "watermarkTab": "Filigrana", + "annotateTabLabel": "Annota", + "resize": "Ridimensiona", + "resizeTab": "Ridimensiona", + "imageName": "Nome immagine", + "invalidImageError": "Immagine fornita non valida.", + "uploadImageError": "Errore durante il caricamento dell'immagine.", + "areNotImages": "non sono immagini", + "isNotImage": "non è un'immagine", + "toBeUploaded": "da caricare", + "cropTool": "Ritaglia", + "original": "Originale", + "custom": "Personalizzato", + "square": "Quadrato", + "landscape": "Orizzontale", + "portrait": "Verticale", + "ellipse": "Ellisse", + "classicTv": "TV classica", + "cinemascope": "Cinemascope", + "arrowTool": "Freccia", + "blurTool": "Sfocatura", + "brightnessTool": "Luminosità", + "contrastTool": "Contrasto", + "ellipseTool": "Ellisse", + "unFlipX": "Annulla capovolgimento X", + "flipX": "Capovolgi X", + "unFlipY": "Annulla capovolgimento Y", + "flipY": "Capovolgi Y", + "hsvTool": "HSV", + "hue": "Tonalità", + "brightness": "Luminosità", + "saturation": "Saturazione", + "value": "Valore", + "imageTool": "Immagine", + "importing": "Importazione...", + "addImage": "+ Aggiungi immagine", + "uploadImage": "Carica immagine", + "fromGallery": "Dalla galleria", + "lineTool": "Linea", + "penTool": "Penna", + "polygonTool": "Poligono", + "sides": "Lati", + "rectangleTool": "Rettangolo", + "cornerRadius": "Raggio angolo", + "resizeWidthTitle": "Larghezza in pixel", + "resizeHeightTitle": "Altezza in pixel", + "toggleRatioLockTitle": "Attiva/disattiva blocco proporzioni", + "resetSize": "Ripristina alle dimensioni originali dell'immagine", + "rotateTool": "Ruota", + "textTool": "Testo", + "textSpacings": "Spaziature testo", + "textAlignment": "Allineamento testo", + "fontFamily": "Famiglia caratteri", + "size": "Dimensione", + "letterSpacing": "Spaziatura lettere", + "lineHeight": "Altezza riga", + "warmthTool": "Calore", + "addWatermark": "+ Aggiungi filigrana", + "addTextWatermark": "+ Aggiungi filigrana testo", + "addWatermarkTitle": "Scegli il tipo di filigrana", + "uploadWatermark": "Carica filigrana", + "addWatermarkAsText": "Aggiungi come testo", + "padding": "Riempimento", + "paddings": "Riempimenti", + "shadow": "Ombra", + "horizontal": "Orizzontale", + "vertical": "Verticale", + "blur": "Sfocatura", + "opacity": "Opacità", + "transparency": "Trasparenza", + "position": "Posizione", + "stroke": "Tratto", + "saveAsModalTitle": "Salva come", + "extension": "Estensione", + "format": "Formato", + "nameIsRequired": "Il nome è obbligatorio.", + "quality": "Qualità", + "imageDimensionsHoverTitle": "Dimensioni immagine salvata (larghezza x altezza)", + "cropSizeLowerThanResizedWarning": "Nota, l'area di ritaglio selezionata è inferiore al ridimensionamento applicato, il che potrebbe causare una diminuzione della qualità", + "actualSize": "Dimensione reale (100%)", + "fitSize": "Adatta alle dimensioni", + "addImageTitle": "Seleziona immagine da aggiungere...", + "mutualizedFailedToLoadImg": "Impossibile caricare l'immagine.", + "tabsMenu": "Menu", + "download": "Scarica", + "width": "Larghezza", + "height": "Altezza", + "plus": "+", + "cropItemNoEffect": "Nessuna anteprima disponibile per questo elemento di ritaglio" +} diff --git a/public/locales/it-IT/markdown_editor.json b/public/locales/it-IT/markdown_editor.json new file mode 100755 index 0000000..4669c81 --- /dev/null +++ b/public/locales/it-IT/markdown_editor.json @@ -0,0 +1,114 @@ +{ + "frontmatterEditor": { + "title": "Modifica metadati documento", + "key": "Chiave", + "value": "Valore", + "addEntry": "Aggiungi voce" + }, + "dialogControls": { + "save": "Salva", + "cancel": "Annulla" + }, + "uploadImage": { + "dialogTitle": "Carica immagine", + "uploadInstructions": "Carica un'immagine dal tuo dispositivo:", + "addViaUrlInstructions": "Oppure aggiungi un'immagine da un URL / percorso relativo (relativo al file corrente):", + "autoCompletePlaceholder": "Seleziona o incolla un src immagine", + "addViaUrlInstructionsNoUpload": "URL immagine:", + "alt": "Alt:", + "title": "Titolo:" + }, + "imageEditor": { + "deleteImage": "Elimina immagine", + "editImage": "Modifica immagine" + }, + "createLink": { + "url": "URL", + "urlPlaceholder": "Seleziona o incolla un URL", + "title": "Titolo", + "saveTooltip": "Imposta URL", + "cancelTooltip": "Annulla modifica" + }, + "linkPreview": { + "open": "Apri {{url}} in una nuova finestra", + "edit": "Modifica URL del link", + "copyToClipboard": "Copia negli appunti", + "copied": "Copiato!", + "remove": "Rimuovi link" + }, + "table": { + "deleteTable": "Elimina tabella", + "columnMenu": "Menu colonna", + "textAlignment": "Allineamento testo", + "alignLeft": "Allinea a sinistra", + "alignCenter": "Allinea al centro", + "alignRight": "Allinea a destra", + "insertColumnLeft": "Inserisci una colonna a sinistra di questa", + "insertColumnRight": "Inserisci una colonna a destra di questa", + "deleteColumn": "Elimina questa colonna", + "rowMenu": "Menu riga", + "insertRowAbove": "Inserisci una riga sopra questa", + "insertRowBelow": "Inserisci una riga sotto questa", + "deleteRow": "Elimina questa riga" + }, + "toolbar": { + "blockTypes": { + "paragraph": "Paragrafo", + "quote": "Citazione", + "heading": "Intestazione {{level}}" + }, + "blockTypeSelect": { + "selectBlockTypeTooltip": "Seleziona tipo di blocco", + "placeholder": "Tipo di blocco" + }, + "toggleGroup": "gruppo di commutazione", + "removeBold": "Rimuovi grassetto", + "bold": "Grassetto", + "removeItalic": "Rimuovi corsivo", + "italic": "Corsivo", + "underline": "Rimuovi sottolineatura", + "removeUnderline": "Sottolineatura", + "removeInlineCode": "Rimuovi formato codice", + "inlineCode": "Formato codice inline", + "link": "Crea link", + "richText": "Testo ricco", + "diffMode": "Modalità diff", + "source": "Modalità sorgente", + "admonition": "Inserisci ammonimento", + "codeBlock": "Inserisci blocco di codice", + "editFrontmatter": "Modifica frontmatter", + "insertFrontmatter": "Inserisci frontmatter", + "image": "Inserisci immagine", + "insertSandpack": "Inserisci Sandpack", + "table": "Inserisci tabella", + "thematicBreak": "Inserisci interruzione tematica", + "bulletedList": "Elenco puntato", + "numberedList": "Elenco numerato", + "checkList": "Elenco di controllo", + "deleteSandpack": "Elimina questo blocco di codice", + "undo": "Annulla {{shortcut}}", + "redo": "Ripeti {{shortcut}}", + "superscript": "Apice", + "subscript": "Pedice", + "strikethrough": "Barrato", + "removeSubscript": "Rimuovi pedice", + "removeSuperscript": "Rimuovi apice", + "removeStrikethrough": "Rimuovi barrato" + }, + "admonitions": { + "note": "Nota", + "tip": "Suggerimento", + "danger": "Pericolo", + "info": "Informazione", + "caution": "Attenzione", + "changeType": "Seleziona tipo di ammonimento", + "placeholder": "Tipo di ammonimento" + }, + "codeBlock": { + "language": "Linguaggio blocco di codice", + "selectLanguage": "Seleziona linguaggio blocco di codice" + }, + "contentArea": { + "editableMarkdown": "markdown modificabile" + } +} diff --git a/public/locales/ja-JP/application.json b/public/locales/ja-JP/application.json new file mode 100755 index 0000000..16e4785 --- /dev/null +++ b/public/locales/ja-JP/application.json @@ -0,0 +1,919 @@ +{ + "login": { + "lastStep": "最終ステップ", + "siginToYourAccount": "アカウントã«ãƒ­ã‚°ã‚¤ãƒ³", + "createNewAccount": "æ–°è¦ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’作æˆ", + "enterPassword": "パスワードを入力ã—ã¦ãã ã•ã„", + "enterPasswordHint": "アカウント{{email}}ã®ãƒ‘スワードを入力ã—ã¦ãã ã•ã„", + "paswordlessHint": "アカウント{{email}}ã¯ãƒ‘スワードä¸è¦ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã™ã€‚èªè¨¼æ–¹æ³•を次ã‹ã‚‰é¸æŠžã—ã¦ãã ã•ã„:", + "noAccountSignupNow": "アカウントをæŒã£ã¦ãªã„ã§ã™ã‹ï¼Ÿ<0>今ã™ã登録", + "haveAccountSignInNow": "アカウントをæŒã£ã¦ã„ã¾ã™ã‹ï¼Ÿ<0>今ã™ãログイン", + "privacyPolicy": "プライãƒã‚·ãƒ¼ãƒãƒªã‚·ãƒ¼", + "termOfUse": "利用è¦ç´„", + "signupHint": "入力ã•れãŸã‚¢ã‚«ã‚¦ãƒ³ãƒˆ{{email}}ã¯å­˜åœ¨ã—ã¾ã›ã‚“。今ã™ã登録ã—ã¾ã™ã‹ï¼Ÿ", + "accountNotFoundHint": "入力ã•れãŸã‚¢ã‚«ã‚¦ãƒ³ãƒˆ{{email}}ã¯å­˜åœ¨ã—ã¾ã›ã‚“。", + "or": "ã¾ãŸã¯", + "selectAccountToUse": "使用ã™ã‚‹ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’é¸æŠž", + "useOtherAccount": "別ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’使用", + "email": "メールアドレス", + "password": "パスワード", + "captcha": "èªè¨¼ã‚³ãƒ¼ãƒ‰", + "captchaError": "èªè¨¼ã‚³ãƒ¼ãƒ‰èª­ã¿è¾¼ã¿å¤±æ•—:{{message}}", + "signIn": "ログイン", + "signUp": "登録", + "signUpAccount": "アカウント登録", + "useFIDO2": "セキュリティキーã§ãƒ­ã‚°ã‚¤ãƒ³", + "usePassword": "パスワードã§ãƒ­ã‚°ã‚¤ãƒ³", + "forgetPassword": "パスワードをãŠå¿˜ã‚Œã§ã™ã‹ï¼Ÿ", + "2FA": "2段階èªè¨¼", + "input2FACode": "6æ¡ã®2段階èªè¨¼ã‚³ãƒ¼ãƒ‰ã‚’入力ã—ã¦ãã ã•ã„", + "passwordNotMatch": "2ã¤ã®ãƒ‘スワードãŒä¸€è‡´ã—ã¾ã›ã‚“", + "findMyPassword": "パスワードをå†è¨­å®š", + "passwordReset": "パスワードãŒãƒªã‚»ãƒƒãƒˆã•れã¾ã—ãŸ", + "newPassword": "æ–°ã—ã„パスワード", + "repeatNewPassword": "æ–°ã—ã„パスワード(確èªï¼‰", + "repeatPassword": "パスワード(確èªï¼‰", + "resetPassword": "パスワードをリセット", + "backToSingIn": "ログイン画é¢ã«æˆ»ã‚‹", + "sendMeAnEmail": "パスワードå†è¨­å®šãƒ¡ãƒ¼ãƒ«ã‚’é€ä¿¡", + "resetEmailSent": "パスワードå†è¨­å®šãƒ¡ãƒ¼ãƒ«ã‚’é€ä¿¡ã—ã¾ã—ãŸã€‚ã”確èªãã ã•ã„。", + "browserNotSupport": "ç¾åœ¨ã”利用ã®ãƒ–ラウザã¾ãŸã¯ç’°å¢ƒã¯ã‚µãƒãƒ¼ãƒˆã•れã¦ã„ã¾ã›ã‚“", + "success": "ログインæˆåŠŸ", + "signUpSuccess": "登録æˆåŠŸ", + "activateSuccess": "アクティベーションæˆåŠŸ", + "accountActivated": "ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒæ­£å¸¸ã«ã‚¢ã‚¯ãƒ†ã‚£ãƒ™ãƒ¼ãƒˆã•れã¾ã—ãŸ", + "title": "{{title}} ã«ãƒ­ã‚°ã‚¤ãƒ³", + "sinUpTitle": "{{title}} ã«ç™»éŒ²", + "activateTitle": "メールアクティベーション", + "activateDescription": "アクティベーションメールをã‚ãªãŸã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã«é€ä¿¡ã—ã¾ã—ãŸã€‚メール内ã®ãƒªãƒ³ã‚¯ã«ã‚¢ã‚¯ã‚»ã‚¹ã—ã¦ç™»éŒ²ã‚’完了ã—ã¦ãã ã•ã„。", + "continue": "次ã¸", + "back": "å‰ã¸", + "logout": "ログアウト", + "signingOut": "ログアウト中...", + "loggedOut": "ログアウトã—ã¾ã—ãŸ", + "clickToRefresh": "CAPTCHAã‚’å†èª­ã¿è¾¼ã¿", + "switchLanguage": "言語を切り替ãˆ" + }, + "resetThumbnail": "壊れãŸã‚µãƒ ãƒã‚¤ãƒ«ã‚’リセット", + "resetThumbnailRequested": "サムãƒã‚¤ãƒ«ã®ãƒªã‚»ãƒƒãƒˆã‚’リクエストã—ã¾ã—ãŸã€‚", + "noFileCanResetThumbnail": "サムãƒã‚¤ãƒ«ã‚’リセットã§ãるファイルãŒã‚りã¾ã›ã‚“。", + "navbar": { + "notBefore": "~よりå‰", + "notAfter": "~より後", + "minimum": "最å°", + "maximum": "最大", + "fileSize": "ファイルサイズ", + "searchBase": "検索パス", + "searchInBase": "検索 <0>", + "conditionDuplicate": "æ¡ä»¶ãŒæ—¢ã«å­˜åœ¨ã—ã¾ã™", + "fileType": "ファイルã®ç¨®é¡ž", + "addCondition": "æ¡ä»¶ã‚’追加", + "notNameOpOr": "å…¨ã¦ã®ã‚­ãƒ¼ãƒ¯ãƒ¼ãƒ‰ã‚’å«ã‚€", + "caseFolding": "å¤§æ–‡å­—å°æ–‡å­—を区別ã—ãªã„", + "keywords": "キーワード", + "fileNameKeywordsHelp": "キーワードを入力後ã€Enterキーを押ã—ã¦è¿½åŠ ", + "advancedSearch": "詳細検索", + "searchFilesTitle": "ファイル検索", + "searchIn": "ファイル検索 <0>{{keywords}}", + "recentlyViewed": "最近ã®é–²è¦§", + "searchFiles": "ファイル検索...", + "showMore": "ãã®ä»–", + "myFiles": "マイファイル", + "hisFiles": "å½¼ã®ãƒ•ァイル", + "trash": "ゴミ箱", + "sharedWithMe": "共有ã•れãŸãƒ•ァイル", + "myShare": "自分ã®å…±æœ‰", + "remoteDownload": "オフラインダウンロード", + "connect": "接続ã¨ãƒžã‚¦ãƒ³ãƒˆ", + "taskQueue": "ãƒãƒƒã‚¯ã‚°ãƒ©ã‚¦ãƒ³ãƒ‰ã‚¿ã‚¹ã‚¯", + "setting": "設定", + "videos": "å‹•ç”»", + "photos": "ç”»åƒ", + "music": "音楽", + "documents": "ドキュメント", + "addATag": "タグを追加...", + "addTagDialog": { + "selectFolder": "ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’é¸æŠž", + "fileSelector": "ファイル分類", + "folderLink": "ディレクトリショートカット", + "tagName": "ã‚¿ã‚°å", + "matchPattern": "ファイルåマッãƒãƒ³ã‚°ãƒ«ãƒ¼ãƒ«", + "matchPatternDescription": "ワイルドカードã¨ã—㦠<0>* を使用ã§ãã¾ã™ã€‚<1>*.png ã¯pngå½¢å¼ã®ç”»åƒã«ãƒžãƒƒãƒã—ã¾ã™ã€‚複数行ã®ãƒ«ãƒ¼ãƒ«ã¯ã€Œã¾ãŸã¯ã€ã®é–¢ä¿‚ã§æ¼”ç®—ã•れã¾ã™ã€‚", + "icon": "アイコン:", + "color": "色:", + "folderPath": "ディレクトリパス" + }, + "storage": "ストレージ容é‡", + "storageDetail": "使用済㿠{{used}}ã€åˆè¨ˆ {{total}}", + "notLoginIn": "未ログイン", + "visitor": "ゲスト", + "objectsSelected": "{{num}} 個ã®ã‚ªãƒ–ジェクト", + "searchPlaceholder": "<0>/ を押ã—ã¦æ¤œç´¢é–‹å§‹", + "backToHomepage": "ãƒ›ãƒ¼ãƒ ã«æˆ»ã‚‹", + "darkModeSwitch": "ダークモードを設定", + "toDarkMode": "ダーク", + "toLightMode": "ライト", + "myProfile": "マイページ", + "dashboard": "管ç†ãƒ‘ãƒãƒ«" + }, + "fileManager": { + "customProps": "カスタム属性", + "rating": "評価", + "description": "説明", + "add": "追加", + "clickToEdit": "編集...", + "clickToEditSelect": "é¸æŠž...", + "enterUrl": "URLを入力...", + "searchUser": "ユーザーを検索...", + "typeToSearch": "åå‰ã¾ãŸã¯ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’入力...", + "searchProperty": "åŒã˜å±žæ€§ã®ãƒ•ァイルを検索", + "permissions": "権é™", + "quality": "è§£åƒåº¦", + "audioTrack": "音声トラック", + "auto": "自動", + "default": "デフォルト", + "shareWithMeEmpty": "他ユーザーã®å…±æœ‰ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“", + "shareWithMeEmptyDes": "他ユーザーã®å…±æœ‰ã‚’ã“ã“ã«è¡¨ç¤ºã™ã‚‹ã«ã¯ã€å…±æœ‰ãƒªãƒ³ã‚¯ã«ã‚¢ã‚¯ã‚»ã‚¹ã—ãŸéš›ã«å³ä¸Šã®ã€Œã‚·ãƒ§ãƒ¼ãƒˆã‚«ãƒƒãƒˆã€ã‚’ä»»æ„ã®å ´æ‰€ã«ä¿å­˜ã—ã¦ãã ã•ã„。", + "selectAll": "ã™ã¹ã¦é¸æŠž", + "selectNone": "é¸æŠžè§£é™¤", + "invertSelection": "é¸æŠžå転", + "imageSize": "ç”»åƒã‚µã‚¤ã‚º", + "focalLength": "焦点è·é›¢", + "columnExisted": "åˆ—ã¯æ—¢ã«å­˜åœ¨ã—ã¾ã™", + "metadataColumn": "メタデータ ({{metadata}})", + "column": "列", + "listColumnSetting": "列設定", + "addColumn": "列を追加", + "failedLoadPreview": "プレビュー読ã¿è¾¼ã¿å¤±æ•—", + "recursiveLimitReached": "検索深度ã®ä¸Šé™ã«é”ã—ã¾ã—ãŸ", + "recursiveLimitReachedDes": "システムãŒã“れ以上ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®æ¤œç´¢ã‚’åœæ­¢ã—ã¾ã—ãŸã€‚検索ディレクトリã®ç¯„囲を狭ã‚ã¦ã¿ã¦ãã ã•ã„。", + "searchConditions": "{{num}} ä»¶ã®æ¡ä»¶", + "createDate": "ä½œæˆæ—¥", + "updatedDate": "æ›´æ–°æ—¥", + "cameraMake": "カメラメーカー", + "cameraModel": "カメラモデル", + "lensModel": "レンズモデル", + "lensMake": "レンズメーカー", + "metadataKey": "キー", + "metadataValue": "値", + "metadata": "メタデータ", + "symbolicFile": "ショートカット", + "relocation": "転é€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼", + "downloadingFile": "「{{name}}ã€ã‚’ダウンロード中ã§ã™ã€‚ページを閉ã˜ãªã„ã§ãã ã•ã„...", + "mountOwner": "ç¾åœ¨ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®æ‰€æœ‰è€…ã®ã¿ãŒãƒãƒªã‚·ãƒ¼ã‚’マウントã§ãã¾ã™", + "uploading": "アップロード中", + "noActionsCanBeDone": "æ“作ã§ãã¾ã›ã‚“", + "newFileName": "æ–°è¦ãƒ•ァイル.{{ext}}", + "newDocumentType": "{{display_name}} (.{{ext}})", + "text": "テキスト", + "diagram": "グラフ", + "whiteboard": "ホワイトボード", + "selectApplications": "ã‚¢ãƒ—ãƒªã‚’é¸æŠž...", + "newlyCreatedFolder": "æ–°è¦ãƒ•ォルダ", + "expandAllApp": "ã™ã¹ã¦ã®ã‚¢ãƒ—リを展開", + "epubViewer": "ePubリーダー", + "googledocs": "Googleドキュメント オンラインリーダー", + "m365viewer": "Microsoft Office オンラインリーダー", + "pdfViewer": "PDFリーダー", + "archivePreview": "アーカイブプレビュー", + "extractSelected": "é¸æŠžã—ãŸãƒ•ァイルを展開", + "viewerFileSizeWarning": "ファイルサイズ({{file_size}})ãŒ{{app}}ã®åˆ¶é™({{max}})ã‚’è¶…ãˆã¦ã„ã‚‹ãŸã‚ã€æ­£å¸¸ã«å‹•作ã—ãªã„å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚", + "testSubtitleStyle": "字幕スタイルã®ãƒ†ã‚¹ãƒˆ AaBbCc", + "color": "色", + "fontSize": "フォントサイズ", + "disableSubtitle": "字幕を無効ã«ã™ã‚‹", + "noSubtitle": "ç¾åœ¨ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«ASS/SRT/VTT字幕ファイルãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。", + "subtitleStyles": "字幕スタイル", + "subtitles": "字幕", + "markdownEditor": "Markdownエディタ", + "saveSuccess": "{{time}} ã«ä¿å­˜ã—ã¾ã—ãŸ", + "drawioLng": "ja", + "charset": "エンコーディング", + "textType": "テキストタイプ", + "fileSaved": "ファイルãŒä¿å­˜ã•れã¾ã—ãŸ", + "failedToLoadFile": "ファイル読ã¿è¾¼ã¿å¤±æ•—: {{msg}}", + "monacoEditor": "Monacoエディタ", + "preparingOpenFile": "ãƒ•ã‚¡ã‚¤ãƒ«ã‚’é–‹ãæº–備中ã§ã™...", + "openWithDescription": "{{ext}} ファイルを開ãã‚¢ãƒ—ãƒªã‚’é¸æŠžã—ã¦ãã ã•ã„。", + "openWith": "é–‹ãæ–¹æ³•", + "readOnly": "読ã¿å–り専用", + "save": "ä¿å­˜", + "noMoreImages": "ç¾åœ¨ã®ãƒšãƒ¼ã‚¸ã«ã¯é–²è¦§å¯èƒ½ãªç”»åƒãŒã‚りã¾ã›ã‚“", + "imageViewer": "ç”»åƒãƒ“ューア", + "logFileDeleteShare": "共有リンクを削除", + "logFileEditShare": "共有リンクを編集", + "deleteShareWarning": "ã“ã®å…±æœ‰ãƒªãƒ³ã‚¯ã‚’削除ã—ã¾ã™ã‹ï¼Ÿ", + "edit": "編集", + "editAndReactivate": "編集ã—ã¦å†æœ‰åŠ¹åŒ–", + "yes": "ã¯ã„", + "no": "ã„ã„ãˆ", + "permanentValid": "有効期é™ãªã—", + "manageShares": "共有リンクã®ç®¡ç†", + "manageDirectLinks": "ç›´éˆã®ç®¡ç†", + "deleteLinkConfirm": "ã“ã®ç›´éˆã‚’削除ã—ã¾ã™ã‹ï¼Ÿ", + "directLinkNotFound": "ã“ã®ç›´éˆã¯å­˜åœ¨ã—ã¾ã›ã‚“。", + "versionNotFound": "ã“ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã¯å­˜åœ¨ã—ã¾ã›ã‚“。", + "deleteVersionWarning": "ã“ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚’削除ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿã“ã®æ“作ã¯å–り消ã›ã¾ã›ã‚“。", + "setAsCurrent": "ç¾åœ¨ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«è¨­å®š", + "current": "[ç¾åœ¨ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³]", + "createdBy": "作æˆè€…", + "manageVersions": "ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®ç®¡ç†", + "livePhoto": "Live Photo", + "version": "ãƒãƒ¼ã‚¸ãƒ§ãƒ³", + "actions": "æ“作", + "versionEntity": "ファイルデータã¨ãƒãƒ¼ã‚¸ãƒ§ãƒ³å±¥æ­´", + "data": "データ", + "owned": "ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã®æ‰€æœ‰è€…", + "ownedSymbolic": "ã“ã®ã‚·ãƒ§ãƒ¼ãƒˆã‚«ãƒƒãƒˆã®æ‰€æœ‰è€…", + "expires": "有効期é™", + "originalLocation": "å…ƒã®å ´æ‰€", + "descendant": "å­ã‚ªãƒ–ジェクト", + "folderChildren": "ファイル{{files}}個ã€ãƒ•ォルダ{{folders}}個", + "moreThan": "{{text}}より大ãã„", + "calculate": "計算", + "unset": "未設定", + "folder": "フォルダ", + "file": "ファイル", + "symbolicLink": "ショートカット ({{srcType}})", + "type": "種類", + "storageUsed": "サイズ", + "location": "場所", + "basicInfo": "基本情報", + "format": "å½¢å¼", + "duration": "時間", + "artist": "アーティスト", + "album": "アルãƒãƒ ", + "title": "タイトル", + "resolution": "è§£åƒåº¦", + "takenAt": "撮影日時", + "software": "ソフトウェア", + "copyright": "作æˆè€…", + "exposureBias": "露出補正", + "flash": "フラッシュ", + "copyToClipboard": "クリップボードã«ã‚³ãƒ”ー", + "searchSomething": " \"{{text}}\" を検索...", + "iso": "ISO", + "exposureValue": "{{num}} ç§’", + "exposure": "露出", + "aperture": "絞り", + "address": "使‰€", + "street": "通り", + "locality": "地区", + "place": "都市", + "district": "区", + "region": "çœ", + "country": "国", + "mediaInfo": "メディア情報", + "details": "詳細", + "activity": "アクティビティ", + "goToSharedLink": "共有リンクã¸ç§»å‹•", + "saveShortcut": "共有をショートカットã«ä¿å­˜", + "customizeIcon": "カスタムアイコン", + "tags": "ã‚¿ã‚°", + "apply": "アプリ", + "customizeColor": "カスタムカラー", + "folderColor": "フォルダカラー", + "restore": "復元", + "unpin": "固定解除", + "youDontHaveReadPermissionToThisFile": "アクセス権é™ãŒã‚りã¾ã›ã‚“", + "anonymousAccessDenied": "アクセス権é™ãŒã‚りã¾ã›ã‚“。ログインã—ã¦ãã ã•ã„。", + "sharedWithOthers": "ä»–ã®äººã¨å…±æœ‰", + "new": "æ–°è¦ä½œæˆ", + "open": "é–‹ã", + "openParentFolder": "フォルダを開ã", + "download": "ダウンロード", + "batchDownload": "ã¾ã¨ã‚ã¦ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰", + "share": "共有", + "rename": "åå‰å¤‰æ›´", + "organize": "æ•´ç†", + "pin": "サイドãƒãƒ¼ã«å›ºå®š", + "pinAlias": "エイリアスを表示", + "optional": "オプション", + "move": "移動", + "delete": "削除", + "moreActions": "ãã®ä»–", + "refresh": "æ›´æ–°", + "createArchive": "圧縮ファイルを作æˆ", + "newFolder": "フォルダを作æˆ", + "newFile": "ファイルを作æˆ", + "showFullPath": "パス表示", + "listView": "リスト", + "gridView": "グリッド", + "galleryView": "ギャラリー", + "paginationSize": "ページサイズ", + "paginationOption": "{{option}} / ページ", + "noPagination": "ページングãªã—", + "sortMethod": "ソート", + "sortMethods": { + "A-Z": "A~Z", + "Z-A": "Z~A", + "oldestUploaded": "å¤ã„アップロード順", + "newestUploaded": "æ–°ã—ã„アップロード順", + "oldestModified": "å¤ã„æ›´æ–°é †", + "newestModified": "æ–°ã—ã„æ›´æ–°é †", + "smallest": "最å°", + "largest": "最大" + }, + "shareCreateBy": "{{nick}}ãŒä½œæˆ", + "name": "åå‰", + "size": "サイズ", + "lastModified": "æ›´æ–°æ—¥", + "currentFolder": "ç¾åœ¨ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒª", + "backToParentFolder": "上層ディレクトリ", + "folders": "フォルダ", + "files": "ファイル", + "listError": "リクエストエラーãŒç™ºç”Ÿã—ã¾ã—ãŸ", + "dropFileHere": "ファイルをã“ã“ã«ãƒ‰ãƒ©ãƒƒã‚°ï¼†ãƒ‰ãƒ­ãƒƒãƒ—", + "orClickUploadButton": "ã¾ãŸã¯å·¦ä¸Šã®ã€Œæ–°è¦ä½œæˆã€ãƒœã‚¿ãƒ³ã‚’クリックã—ã¦ãƒ•ァイルを追加", + "nothingFound": "何も見ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ", + "uploadFiles": "ファイルã®ã‚¢ãƒƒãƒ—ロード", + "uploadFolder": "ディレクトリã®ã‚¢ãƒƒãƒ—ロード", + "newRemoteDownloads": "オフラインダウンロード", + "enter": "é–‹ã", + "getSourceLink": "ダイレクトリンクをå–å¾—", + "createRemoteDownloadForTorrent": "オフラインダウンロードタスクã®ä½œæˆ", + "extractArchive": "è§£å‡", + "createShareLink": "共有リンクã®ä½œæˆ", + "viewDetails": "詳細情報", + "copy": "コピー", + "bytes": " ({{bytes}} ãƒã‚¤ãƒˆ)", + "storagePolicy": "ストレージãƒãƒªã‚·ãƒ¼", + "childFolders": "ディレクトリã®å†…容", + "childFiles": "ファイルã®å†…容", + "childCount": "{{num}} 個", + "parentFolder": "ä¿å­˜å ´æ‰€", + "rootFolder": "ルートディレクトリ", + "modifiedAt": "最終更新日", + "createdAt": "ä½œæˆæ—¥", + "statisticAt": "集計日", + "musicPlayer": "オーディオプレーヤー", + "closeAndStop": "å†ç”Ÿçµ‚了", + "playInBackground": "ãƒãƒƒã‚¯ã‚°ãƒ©ã‚¦ãƒ³ãƒ‰å†ç”Ÿ", + "copyTo": "コピー先", + "copyToDst": "コピー先 <0>", + "moveTo": "移動先", + "moveToDst": "移動先 <0>", + "errorReadFileContent": "ファイル内容ã®èª­ã¿è¾¼ã¿ã«å¤±æ•—ã—ã¾ã—ãŸï¼š{{msg}}", + "wordWrap": "自動改行", + "pdfLoadingError": "PDF読ã¿è¾¼ã¿å¤±æ•—:{{msg}}", + "subtitleSwitchTo": "字幕切替:{{subtitle}}", + "noSubtitleAvailable": "動画ディレクトリã«å­—幕ファイルãŒã‚りã¾ã›ã‚“ (対応形å¼ï¼šASS/SRT/VTT)", + "subtitle": "å­—å¹•é¸æŠž", + "playlist": "プレイリスト", + "openInExternalPlayer": "外部プレーヤーã§é–‹ã", + "repeatMode": "リピートモード", + "listRepeat": "リストリピート", + "singleRepeat": "シングルリピート", + "shuffle": "シャッフル", + "playbackSpeed": "å†ç”Ÿé€Ÿåº¦", + "searchResult": "æ¤œç´¢çµæžœ", + "preparingBathDownload": "ダウンロード準備中...", + "preparingDownload": "ダウンロード準備中...", + "browserDownload": "ブラウザダウンロード(ローカル)", + "browserDownloadDescription": "指定ã—ãŸãƒ­ãƒ¼ã‚«ãƒ«ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«ãƒ–ラウザãŒé€ä¸€ãƒ•ァイルをダウンロードã—ã¾ã™ã€‚", + "browserBatchDownload": "ブラウザ圧縮ダウンロード", + "browserBatchDownloadDescription": "ブラウザã§ãƒªã‚¢ãƒ«ã‚¿ã‚¤ãƒ ã«Zipファイルã¨ã—ã¦ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã—ã¾ã™ã€‚4GBã‚’è¶…ãˆã‚‹ãƒ‡ãƒ¼ã‚¿ã¯ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã§ãã¾ã›ã‚“。", + "serverBatchDownload": "サーãƒãƒ¼çµŒç”±åœ§ç¸®ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰", + "serverBatchDownloadDescription": "サーãƒãƒ¼ã§Zipファイルã«åœ§ç¸®ã—ã€ãƒªã‚¢ãƒ«ã‚¿ã‚¤ãƒ ã§ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆã«é€ä¿¡ã—ã¾ã™ã€‚共有ショートカットã¯ã‚µãƒãƒ¼ãƒˆã•れã¦ã„ã¾ã›ã‚“。", + "selectArchiveMethod": "ä¸€æ‹¬ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰æ–¹æ³•ã‚’é¸æŠž", + "batchDownloadStarted": "圧縮ダウンロードを開始ã—ã¾ã—ãŸã€‚ã“ã®ãƒšãƒ¼ã‚¸ã‚’é–‰ã˜ãªã„ã§ãã ã•ã„...", + "batchDownloadError": "圧縮中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸï¼š{{msg}}", + "userDenied": "ユーザー拒å¦", + "directoryDownloadReplace": "ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’ç½®ãæ›ãˆã‚‹", + "directoryDownloadReplaceDescription": "ローカル㮠\"{{name}}\" を上書ãã—ã¾ã™", + "directoryDownloadSkip": "ã“ã®ãƒ•ァイルをスキップã™ã‚‹", + "directoryDownloadSkipDescription": "\"{{name}}\" ã®ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã‚’スキップã—ã¾ã™", + "selectDirectoryDuplicationMethod": "ファイルåãŒé‡è¤‡ã—ã¦ã„ã¾ã™", + "directoryDownloadReplaceAll": "ã“ã®ãƒ•ァイルã¨ä»¥é™ã®é‡è¤‡ãƒ•ã‚¡ã‚¤ãƒ«ã‚’ç½®ãæ›ãˆã¾ã™", + "directoryDownloadReplaceAllDescription": "ローカル㮠\"{{name}}\" を上書ãã—ã€é¸æŠžã‚’記憶ã—ã¾ã™", + "directoryDownloadSkipAll": "ã“ã®ãƒ•ァイルã¨ä»¥é™ã®é‡è¤‡ãƒ•ァイルをスキップã—ã¾ã™", + "directoryDownloadSkipAllDescription": "\"{{name}}\" ã®ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã‚’スキップã—ã€é¸æŠžã‚’記憶ã—ã¾ã™", + "directoryDownloadStarted": "ダウンロードãŒé–‹å§‹ã•れã¾ã—ãŸã€‚ã“ã®ã‚¿ãƒ–ã‚’é–‰ã˜ãªã„ã§ãã ã•ã„", + "directoryDownloadFinished": "ダウンロード完了ã—ã¾ã—ãŸã€‚失敗ã—ãŸã‚ªãƒ–ジェクトã¯ã‚りã¾ã›ã‚“", + "directoryDownloadFinishedWithError": "ダウンロード完了ã—ã¾ã—ãŸã€‚{{failed}} 個ã®ã‚ªãƒ–ジェクトã§å¤±æ•—ã—ã¾ã—ãŸ", + "directoryDownloadPermissionError": "アクセス権é™ãŒã‚りã¾ã›ã‚“。ローカルファイルã®èª­ã¿æ›¸ãを許å¯ã—ã¦ãã ã•ã„", + "back": "戻る", + "view": "ビュー", + "layout": "レイアウト", + "thumbnails": "サムãƒã‚¤ãƒ«", + "on": "オン", + "off": "オフ", + "viewSetting": "ビュー設定", + "saved": "ä¿å­˜", + "notSet": "未設定", + "deleteViewSetting": "ビュー設定を削除" + }, + "modals": { + "includePasswordInShareLink": "共有リンクã«ãƒ‘スワードをå«ã‚ã‚‹", + "includePasswordInShareLinkDes": "ãƒã‚§ãƒƒã‚¯ã‚’入れるã¨ã€å…±æœ‰ãƒªãƒ³ã‚¯ã«ãƒ‘スワードãŒå«ã¾ã‚Œã¾ã™ã€‚ã“ã®ãƒªãƒ³ã‚¯ã«ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹éš›ã«ã¯ã€ãƒ‘スワードを入力ã™ã‚‹å¿…è¦ã¯ã‚りã¾ã›ã‚“。", + "showFileName": "ファイルåを表示", + "forceDownload": "強制ダウンロード", + "archiveFile": "圧縮ファイル", + "cancelDownload": "ダウンロードキャンセル", + "always": "常ã«", + "justOnce": "一度ã ã‘", + "quality": "å“質", + "saveAsOtherFormat": "ä»–ã®å½¢å¼ã§ä¿å­˜", + "conflictDes1": "ファイルã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®ç«¶åˆãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚考ãˆã‚‰ã‚Œã‚‹åŽŸå› ã¯æ¬¡ã®ã¨ãŠã‚Šã§ã™ã€‚", + "conflictDes2": "<0>ファイルを開ã„ãŸå¾Œã€åˆ¥ã®å ´æ‰€ã‹ã‚‰æ–°ã—ã„ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã§æ›´æ–°ã•れã¾ã—ãŸã€‚<1>æ–°ã—ã„ファイルåã¾ãŸã¯æ–°ã—ã„場所ã«ä¿å­˜ã—ãŸå ´åˆã€åŒåã®ãƒ•ã‚¡ã‚¤ãƒ«ãŒæ—¢ã«å­˜åœ¨ã™ã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚", + "saveAs": "åå‰ã‚’付ã‘ã¦ä¿å­˜", + "versionConflict": "ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç«¶åˆ", + "overwrite": "上書ã", + "editShareLink": "共有リンクã®ç·¨é›†", + "clearPermissions": "権é™è¨­å®šã®ã‚¯ãƒªã‚¢", + "shortcutCreated": "ショートカットãŒä½œæˆã•れã¾ã—ãŸ", + "createShortcut": "ショートカットã®ä½œæˆ", + "createShortcutTo": "<0> ã«ã‚·ãƒ§ãƒ¼ãƒˆã‚«ãƒƒãƒˆã‚’作æˆ", + "targetExisted": "ã‚¿ãƒ¼ã‚²ãƒƒãƒˆã¯æ—¢ã«å­˜åœ¨ã—ã¾ã™", + "users": "ユーザー", + "groups": "ユーザーグループ", + "noResults": "çµæžœãŒã‚りã¾ã›ã‚“", + "resetToDefault": "ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã«æˆ»ã™", + "duplicateTag": "ã‚¿ã‚° \"{{tag}}\" ã¯æ—¢ã«å­˜åœ¨ã—ã¾ã™", + "colorForTag": "æ–°ã—ã„ã‚¿ã‚°ã®è‰²ã‚’カスタマイズ", + "enterForNewTag": "Enterキーを押ã—ã¦æ–°ã—ã„タグを追加", + "manageTags": "ã‚¿ã‚°ã®ç®¡ç†", + "onlyOwner": "ファイル所有者ã®ã¿å¼·åˆ¶è§£é™¤ã§ãã¾ã™", + "forceUnlock": "強制解除", + "forceUnlockAll": "ã™ã¹ã¦å¼·åˆ¶è§£é™¤", + "forceUnlockDes": "強制解除ã™ã‚‹ã¨ãƒ•ァイルã®çŠ¶æ…‹ãŒç•°å¸¸ã«ãªã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚ファイルãŒè‡ªå‹•çš„ã«è§£æ”¾ã•れるã®ã‚’å¾…ã¤ã“ã¨ã‚’ãŠå‹§ã‚ã—ã¾ã™ã€‚解除を続行ã—ã¾ã™ã‹ï¼Ÿ", + "webdav": "WebDAV", + "soft-delete": "ゴミ箱ã¸ç§»å‹•", + "updateMetadata": "メタデータ更新", + "upload": "アップロード", + "moveCopy": "移動ã¾ãŸã¯ã‚³ãƒ”ー", + "view": "表示", + "cannotPerformAction": "ã“ã“ã«ç§»å‹•ã¾ãŸã¯ã‚³ãƒ”ーã§ãã¾ã›ã‚“", + "cannotMoveCopyToChild": "å­ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«ç§»å‹•ã¾ãŸã¯ã‚³ãƒ”ーã§ãã¾ã›ã‚“", + "copySuccess": "{{num}}個ã®ãƒ•ã‚¡ã‚¤ãƒ«ãŒæ­£å¸¸ã«ã‚³ãƒ”ーã•れã¾ã—ãŸ", + "moveSuccess": "{{num}}個ã®ãƒ•ã‚¡ã‚¤ãƒ«ãŒæ­£å¸¸ã«ç§»å‹•ã•れã¾ã—ãŸ", + "unknownParent": "è¦ªãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªä¸æ˜Ž", + "unknownParentDes": "å æœ‰ã•れã¦ã„るディレクトリã¯å…±æœ‰ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®è¦ªãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã§ã‚りã‚ãªãŸã®ã‚‚ã®ã§ã¯ã‚りã¾ã›ã‚“", + "lockConflictTitle": "ファイルãŒä½¿ç”¨ä¸­ã§ã™", + "lockConflictDescription": "次ã®ãƒ•ァイルãŒä½¿ç”¨ä¸­ã§ã‚ã‚‹ãŸã‚æ“作を完了ã§ãã¾ã›ã‚“。ã—ã°ã‚‰ãã—ã¦ã‹ã‚‰å†è©¦è¡Œã—ã¦ãã ã•ã„ã€‚ãƒ•ã‚¡ã‚¤ãƒ«ã®æ‰€æœ‰è€…ã§ã‚りファイルãŒä½¿ç”¨ã•れã¦ã„ãªã„ã“ã¨ã‚’確èªã—ã¦ã„ã‚‹å ´åˆã¯ãƒ•ァイルを強制解除ã—ã¦å†è©¦è¡Œã§ãã¾ã™ã€‚", + "application": "é©ç”¨", + "errorDetailsTitle": "エラー詳細", + "processingMoving": "ファイル移動中...", + "processingCopying": "ファイルコピー中...", + "processingRestoring": "ファイル復元中...", + "fileRestored": "{{num}}個ã®ãƒ•ァイルを元ã®å ´æ‰€ã«å¾©å…ƒã—ã¾ã—ãŸ", + "duplicatedObjectName": "æ–°ã—ã„åå‰ãŒæ—¢å­˜ã®ãƒ•ァイルã¨é‡è¤‡ã—ã¦ã„ã¾ã™", + "newNameLengthError": "ファイルåã¯1~255文字以内ã«ã—ã¦ãã ã•ã„", + "newNameCharacterError": "ファイルåã«ã¯ä»¥ä¸‹ã®æ–‡å­—を使用ã§ãã¾ã›ã‚“:\\ / : * ? \" < > |", + "newNameDotError": "ファイルåã«ã€Œ.ã€ã¾ãŸã¯ã€Œ..ã€ã¯ä½¿ç”¨ã§ãã¾ã›ã‚“", + "taskCreated": "タスクを作æˆã—ã¾ã—ãŸ", + "taskCreateFailed": "{{failed}}個ã®ã‚¿ã‚¹ã‚¯ä½œæˆã«å¤±æ•—ã—ã¾ã—ãŸï¼š{{details}}", + "linkCopied": "リンクをコピーã—ã¾ã—ãŸ", + "getSourceLinkTitle": "ファイルã®ãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆãƒªãƒ³ã‚¯ã‚’å–å¾—", + "sourceLink": "ファイルダイレクトリンク", + "folderName": "フォルダå", + "create": "作æˆ", + "fileName": "ファイルå", + "renameDescription": "<0>{{name}} ã®æ–°ã—ã„åå‰ã‚’入力ã—ã¦ãã ã•ã„:", + "newName": "æ–°ã—ã„åå‰", + "moveToDescription": "<0>{{name}} ã«ç§»å‹•", + "saveToTitle": "ä¿å­˜", + "saveToTitleDescription": "<0>{{name}} ã«ä¿å­˜", + "deleteTitle": "オブジェクトã®å‰Šé™¤", + "deleteOneDescription": "<0>{{name}} をゴミ箱ã«ç§»å‹•ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ", + "deleteMultipleDescription": "ã“れら{{num}}個ã®ã‚ªãƒ–ジェクトをゴミ箱ã«ç§»å‹•ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ", + "deleteOneDescriptionHard": "<0>{{name}} を完全ã«å‰Šé™¤ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ", + "trashRetention": "ゴミ箱内ã®ãƒ•ァイルã¯<0>{{num}}日後ã«è‡ªå‹•çš„ã«å‰Šé™¤ã•れã¾ã™ã€‚", + "deleteMultipleDescriptionHard": "ã“れら{{num}}個ã®ã‚ªãƒ–ジェクトを完全ã«å‰Šé™¤ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ", + "newRemoteDownloadTitle": "ã‚ªãƒ•ãƒ©ã‚¤ãƒ³ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã‚¿ã‚¹ã‚¯ã®æ–°è¦ä½œæˆ", + "remoteDownloadURL": "ダウンロードリンク", + "remoteDownloadURLDescription": "ファイルã®ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’1行ãšã¤å…¥åŠ›ã—ã¦ãã ã•ã„", + "remoteDownloadDst": "ダウンロード先", + "processNode": "処ç†ãƒŽãƒ¼ãƒ‰", + "remoteDownloadNodeAuto": "自動割り当ã¦", + "createTask": "タスクã®ä½œæˆ", + "downloadToDst": "<0>{{name}} ã«ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰", + "downloadTo": "ダウンロード先", + "decompressTo": "è§£å‡å…ˆ", + "decompressToDst": "<0>{{name}} ã«è§£å‡", + "defaultEncoding": "デフォルト", + "chineseMajorEncoding": "", + "selectEncoding": "ZIPファイルエンコード", + "password": "圧縮ファイルパスワード", + "passwordDescription": "åœ§ç¸®ãƒ•ã‚¡ã‚¤ãƒ«ãŒæš—å·åŒ–ã•れã¦ã„ãªã„å ´åˆã€ã“ã“ã¯ç©ºã®ã¾ã¾ã«ã—ã¦ãã ã•ã„。", + "noEncodingSelected": "エンコード方å¼ã‚’é¸æŠžã—ã¦ã„ã¾ã›ã‚“", + "listingFiles": "ファイル一覧å–得中...", + "listingFileError": "ファイル一覧å–得中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸï¼š{{message}}", + "generatingSourceLinks": "外部リンク生æˆä¸­...", + "noFileCanGenerateSourceLink": "外部リンクを生æˆã§ãるファイルãŒã‚りã¾ã›ã‚“", + "sourceBatchSizeExceeded": "ç¾åœ¨ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã¯ã€åŒæ™‚ã«æœ€å¤§{{limit}}個ã®ãƒ•ァイルã«å¯¾ã—ã¦å¤–部リンクを生æˆã§ãã¾ã™", + "zipFileName": "圧縮ファイルå", + "shareLinkShareContent": "{{name}} を共有ã—ã¾ã—ãŸã€‚リンク:{{link}}", + "shareLinkPasswordInfo": "パスワード:{{password}}", + "createShareLink": "共有リンク作æˆ", + "privateShare": "パスワードã§ä¿è­·", + "privateShareDes": "ãƒã‚§ãƒƒã‚¯ã‚’入れるã¨ã€ãƒ‘スワードãŒå¿…è¦ã§ã™ã€‚", + "useCustomPassword": "カスタムパスワードを使用", + "expireAfterDownload": "ダウンロード後ã«è‡ªå‹•çš„ã«æœŸé™åˆ‡ã‚Œ", + "sharePassword": "共有パスワード", + "randomlyGenerate": "ランダム生æˆ", + "expireAutomatically": "時間切れã§è‡ªå‹•çš„ã«æœŸé™åˆ‡ã‚Œ", + "downloadLimitOptions": "{{num}}回ダウンロード", + "or": "ã¾ãŸã¯", + "5minutes": "5 分", + "1hour": "1 時間", + "1day": "1 æ—¥", + "7days": "7 æ—¥", + "30days": "30 æ—¥", + "custom": "カスタム", + "minutes": "分", + "downloads": "ダウンロード", + "expirePrefix": "", + "expireSuffix": "期é™åˆ‡ã‚Œ", + "allowPreview": "プレビュー許å¯", + "allowPreviewDescription": "共有ページã§ãƒ•ァイル内容ã®ãƒ—レビューを許å¯ã—ã¾ã™ã‹ï¼Ÿ", + "shareLink": "共有リンク", + "sendLink": "リンクをé€ä¿¡", + "directoryDownloadReplaceNotifiction": "{{name}} を上書ãã—ã¾ã—ãŸ", + "directoryDownloadSkipNotifiction": "{{name}} をスキップã—ã¾ã—ãŸ", + "directoryDownloadTitle": "一括ダウンロードログ", + "directoryDownloadStarted": "「{{name}}ã€ã®ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã‚’é–‹å§‹ã—ã¾ã—ãŸ", + "directoryDownloadFinished": "「{{name}}ã€ã®ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ãŒå®Œäº†ã—ã¾ã—ãŸ", + "directoryDownloadError": "エラーãŒç™ºç”Ÿã—ã¾ã—ãŸï¼š{{msg}}", + "directoryDownloadErrorNotification": "{{name}} ã®ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸï¼š{{msg}}", + "directoryDownloadAutoscroll": "自動スクロール", + "directoryDownloadCancelled": "ダウンロードキャンセル済ã¿", + "advanceOptions": "詳細オプション", + "skipSoftDelete": "ファイルを完全ã«å‰Šé™¤", + "skipSoftDeleteDes": "ゴミ箱をスキップã—ã¦ãƒ•ァイルを完全ã«å‰Šé™¤ã—ã¾ã™", + "unlinkOnly": "物ç†ãƒ•ã‚¡ã‚¤ãƒ«ã‚’ä¿æŒ", + "unlinkOnlyDes": "ファイル記録ã®ã¿å‰Šé™¤ã€ç‰©ç†ãƒ•ァイルã¯å‰Šé™¤ã•れã¾ã›ã‚“", + "shareView": "共有ビュー設定", + "shareViewDes": "ãƒã‚§ãƒƒã‚¯ã‚’入れるã¨ã€ä»–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒã“ã®å…±æœ‰ãƒ•ォルダã«ã‚¢ã‚¯ã‚»ã‚¹ã—ãŸéš›ã«ã‚ãªãŸã®ãƒ“ュー設定(レイアウトã€ã‚½ãƒ¼ãƒˆãªã©ï¼‰ã‚’見るã“ã¨ãŒã§ãã¾ã™ã€‚", + "showReadme": "README ファイルを表示", + "showReadmeDes": "ãƒã‚§ãƒƒã‚¯ã‚’入れるã¨ã€ä»–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒã“ã®å…±æœ‰ãƒ•ォルダã«ã‚¢ã‚¯ã‚»ã‚¹ã—ãŸéš›ã«ã‚ãªãŸã®ãƒ“ュー設定(レイアウトã€ã‚½ãƒ¼ãƒˆãªã©ï¼‰ã‚’見るã“ã¨ãŒã§ãã¾ã™ã€‚", + "viewSetting": "表示設定", + "saved": "ä¿å­˜ã•れã¾ã—ãŸ", + "notSet": "未設定", + "deleteViewSetting": "表示設定ã®å‰Šé™¤" + }, + "uploader": { + "fileCopyName": "コピー_", + "overwriteTooltip": "ファイルåãŒé‡è¤‡ã™ã‚‹å ´åˆã€æ—¢å­˜ã®ãƒ•ァイルを上書ãã—ã¾ã™ï¼ˆæ–°è¦è¿½åŠ ã‚¿ã‚¹ã‚¯ã®ã¿æœ‰åŠ¹ï¼‰", + "rename": "æ–°ã—ã„ファイルåã§å†è©¦è¡Œ", + "overwrite": "既存ã®ãƒ•ァイルを上書ã", + "pasteFilesHere": "ファイルをã“ã“ã«è²¼ã‚Šä»˜ã‘", + "clipboardDefaultFileName": "クリップボード {{date}}.png", + "uploadFromClipboard": "クリップボードã‹ã‚‰ã‚¢ãƒƒãƒ—ロード", + "uploadList": "アップロードリスト", + "fileNotMatchError": "é¸æŠžã—ãŸãƒ•ァイルãŒå…ƒã®ãƒ•ァイルã¨ä¸€è‡´ã—ã¾ã›ã‚“", + "unknownError": "䏿˜Žãªã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸï¼š{{msg}}", + "taskListEmpty": "アップロードタスクãŒã‚りã¾ã›ã‚“", + "hideTaskList": "リストを隠ã™", + "uploadTasks": "アップロードキュー", + "moreActions": "ãã®ä»–", + "addNewFiles": "æ–°è¦ãƒ•ァイルã®è¿½åŠ ", + "toggleTaskList": "キューã®å±•é–‹/折りãŸãŸã¿", + "pendingInQueue": "キューã§ä¿ç•™ä¸­...", + "preparing": "準備中...", + "processing": "処ç†ä¸­...", + "progressDescription": "{{uploaded}} / {{total}} - {{percentage}}% アップロード済ã¿", + "progressDescriptionFull": "{{speed}}ã§{{uploaded}} / {{total}} - {{percentage}}% アップロード済ã¿", + "progressDescriptionPlaceHolder": "アップロード済㿠- ", + "uploaded": "アップロード済ã¿", + "rootFolder": "ルートディレクトリ", + "unknownStatus": "䏿˜Ž", + "resumed": "å†é–‹", + "resumable": "å†é–‹å¯èƒ½", + "retry": "å†è©¦è¡Œ", + "deleteTask": "タスク履歴ã®å‰Šé™¤", + "cancelAndDelete": "キャンセルã—ã¦å‰Šé™¤", + "selectAndResume": "åŒã˜ãƒ•ã‚¡ã‚¤ãƒ«ã‚’é¸æŠžã—ã¦ã‚¢ãƒƒãƒ—ロードをå†é–‹", + "fileName": "ファイルå:", + "fileSize": "ファイルサイズ:", + "sessionExpiredIn": "期é™åˆ‡ã‚Œ", + "chunkDescription": "({{total}} セグメント, å„セグメント {{size}})", + "noChunks": "(セグメントãªã—)", + "destination": "ä¿å­˜å ´æ‰€ï¼š", + "storagePolicy": "ストレージãƒãƒªã‚·ãƒ¼ï¼š", + "uploadSession": "アップロードセッション:", + "errorDetails": "エラーメッセージ:", + "uploadSessionCleaned": "アップロードセッションãŒã‚¯ãƒªã‚¢ã•れã¾ã—ãŸ", + "hideCompletedTooltip": "完了済ã€å¤±æ•—済ã€ã‚­ãƒ£ãƒ³ã‚»ãƒ«æ¸ˆã¿ã®ã‚¿ã‚¹ã‚¯ã¯ãƒªã‚¹ãƒˆã«è¡¨ç¤ºã•れã¾ã›ã‚“", + "hideCompleted": "完了済ã¿ã®ã‚¿ã‚¹ã‚¯ã‚’éžè¡¨ç¤º", + "addTimeAscTooltip": "最åˆã«è¿½åŠ ã—ãŸã‚¿ã‚¹ã‚¯ãŒå…ˆé ­ã«è¡¨ç¤º", + "addTimeAsc": "先入れ先出ã—", + "addTimeDescTooltip": "最後ã«è¿½åŠ ã—ãŸã‚¿ã‚¹ã‚¯ãŒå…ˆé ­ã«è¡¨ç¤º", + "addTimeDesc": "後入れ先出ã—", + "showInstantSpeedTooltip": "å˜ä¸€ã‚¿ã‚¹ã‚¯ã®ã‚¢ãƒƒãƒ—ロード速度ã¯çž¬é–“速度ã§è¡¨ç¤º", + "showInstantSpeed": "瞬間速度", + "showAvgSpeedTooltip": "å˜ä¸€ã‚¿ã‚¹ã‚¯ã®ã‚¢ãƒƒãƒ—ロード速度ã¯å¹³å‡é€Ÿåº¦ã§è¡¨ç¤º", + "showAvgSpeed": "å¹³å‡é€Ÿåº¦", + "cleanAllSessionTooltip": "サーãƒãƒ¼ä¸Šã®æœªå®Œäº†ã®ã‚¢ãƒƒãƒ—ロードセッションをã™ã¹ã¦ã‚¯ãƒªã‚¢", + "cleanAllSession": "ã™ã¹ã¦ã®ã‚¢ãƒƒãƒ—ロードセッションをクリア", + "cleanCompletedTooltip": "完了ã€å¤±æ•—ã€ã‚­ãƒ£ãƒ³ã‚»ãƒ«æ¸ˆã¿ã®ã‚¿ã‚¹ã‚¯ã‚’リストã‹ã‚‰å‰Šé™¤ã™ã‚‹", + "cleanCompleted": "完了済ã¿ã®ã‚¿ã‚¹ã‚¯ã‚’削除ã™ã‚‹", + "retryFailedTasks": "失敗ã—ãŸã™ã¹ã¦ã®ã‚¿ã‚¹ã‚¯ã‚’å†è©¦è¡Œã™ã‚‹", + "retryFailedTasksTooltip": "キュー内ã®ã™ã¹ã¦ã®å¤±æ•—ã—ãŸã‚¿ã‚¹ã‚¯ã‚’å†è©¦è¡Œã™ã‚‹", + "setConcurrentTooltip": "åŒæ™‚ã«å®Ÿè¡Œã™ã‚‹ã‚¿ã‚¹ã‚¯æ•°ã‚’設定ã™ã‚‹", + "setConcurrent": "ä¸¦åˆ—å‡¦ç†æ•°ã‚’設定ã™ã‚‹", + "sizeExceedLimitError": "ファイルサイズãŒã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã®åˆ¶é™ã‚’è¶…ãˆã¦ã„ã¾ã™ï¼ˆæœ€å¤§ï¼š{{max}})", + "suffixNotAllowedError": "ストレージãƒãƒªã‚·ãƒ¼ã¯ã“ã®æ‹¡å¼µå­ã®ãƒ•ァイルをサãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã›ã‚“", + "regexpNotAllowedError": "ストレージãƒãƒªã‚·ãƒ¼ã¯ã“ã®ãƒ•ァイルåをサãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã›ã‚“", + "suffixAllowed": "(サãƒãƒ¼ãƒˆã•れã¦ã„ã‚‹æ‹¡å¼µå­ï¼š{{supported}})", + "suffixDenied": "ï¼ˆç¦æ­¢ã•れã¦ã„ã‚‹æ‹¡å¼µå­ï¼š{{denied}})", + "createUploadSessionError": "アップロードセッションを作æˆã§ãã¾ã›ã‚“", + "deleteUploadSessionError": "アップロードセッションを削除ã§ãã¾ã›ã‚“", + "requestError": "リクエストã«å¤±æ•—ã—ã¾ã—ãŸ: {{msg}} ({{url}})", + "chunkUploadError": "ãƒãƒ£ãƒ³ã‚¯ [{{index}}] ã®ã‚¢ãƒƒãƒ—ロードã«å¤±æ•—ã—ã¾ã—ãŸ", + "conflictError": "åŒåã®ãƒ•ァイルã®ã‚¢ãƒƒãƒ—ãƒ­ãƒ¼ãƒ‰ã‚¿ã‚¹ã‚¯ãŒæ—¢ã«å‡¦ç†ä¸­ã§ã™", + "chunkUploadErrorWithMsg": "ãƒãƒ£ãƒ³ã‚¯ã‚¢ãƒƒãƒ—ロードã«å¤±æ•—ã—ã¾ã—ãŸ: {{msg}}", + "chunkUploadErrorWithRetryAfter": "({{retryAfter}} 秒後ã«å†è©¦è¡Œã—ã¦ãã ã•ã„)", + "emptyFileError": "OneDriveã¸ã®ç©ºãƒ•ァイルã®ã‚¢ãƒƒãƒ—ロードã¯ç¾åœ¨ã‚µãƒãƒ¼ãƒˆã•れã¦ã„ã¾ã›ã‚“。空ファイルを作æˆã™ã‚‹ã«ã¯ãƒ•ァイル作æˆãƒœã‚¿ãƒ³ã‚’使用ã—ã¦ãã ã•ã„。", + "finishUploadError": "ファイルã®ã‚¢ãƒƒãƒ—ロードを完了ã§ãã¾ã›ã‚“", + "finishUploadErrorWithMsg": "ファイルã®ã‚¢ãƒƒãƒ—ロードを完了ã§ãã¾ã›ã‚“: {{msg}}", + "ossFinishUploadError": "ファイルã®ã‚¢ãƒƒãƒ—ロードを完了ã§ãã¾ã›ã‚“: {{msg}} ({{code}})", + "cosUploadFailed": "アップロードã«å¤±æ•—ã—ã¾ã—ãŸ: {{msg}} ({{code}})", + "upyunUploadFailed": "アップロード失敗: {{msg}}", + "parseResponseError": "レスãƒãƒ³ã‚¹ã‚’è§£æžã§ãã¾ã›ã‚“: {{msg}} ({{content}})", + "concurrentTaskNumber": "åŒæ™‚アップロードタスク数", + "dropFileHere": "マウスボタンを離ã™ã¨ã‚¢ãƒƒãƒ—ロード開始" + }, + "share": { + "statistics": "統計", + "expireAt": "期é™åˆ‡ã‚Œ", + "expireAfterDownloads": "ダウンロード{{downloads}}回後期é™åˆ‡ã‚Œ", + "somebodyShare": "{{name}} ã•ã‚“ã®å…±æœ‰", + "expiredLink": "期é™åˆ‡ã‚Œã®å…±æœ‰", + "sharedBy": "{{nick}} ã•ã‚“ãŒ{{num}}個ã®ãƒ•ァイルを共有ã—ã¾ã—ãŸ", + "files": "ファイル1個", + "files_other": "{{count}}個ã®ãƒ•ァイル", + "statisticsViews": "閲覧{{views}}回", + "statisticsDownloads": "ダウンロード{{downloads}}回", + "views": "閲覧{{count}}回", + "views_other": "閲覧{{count}}回", + "downloads": "ダウンロード{{count}}回", + "downloads_other": "ダウンロード{{count}}回", + "privateShareTitle": "{{nick}} ã•ã‚“ã®æš—å·åŒ–ã•れãŸå…±æœ‰", + "enterPassword": "共有パスワード", + "continue": "続行", + "shareCanceled": "共有リンクを削除ã—ã¾ã—ãŸ", + "listLoadingError": "読ã¿è¾¼ã¿å¤±æ•—", + "sharedFiles": "自分ã®å…±æœ‰", + "createdAtDesc": "最新", + "createdAtAsc": "最å¤", + "noRecords": "共有履歴ãŒã‚りã¾ã›ã‚“。", + "sourceNotFound": "[å…ƒã®ã‚ªãƒ–ジェクトãŒå­˜åœ¨ã—ã¾ã›ã‚“]", + "expired": "期é™åˆ‡ã‚Œã§ã™", + "changeToPublic": "公開共有ã«å¤‰æ›´", + "changeToPrivate": "éžå…¬é–‹å…±æœ‰ã«å¤‰æ›´", + "viewPassword": "パスワード確èª", + "disablePreview": "ãƒ—ãƒ¬ãƒ“ãƒ¥ãƒ¼ç¦æ­¢", + "enablePreview": "プレビュー許å¯", + "cancelShare": "共有解除", + "sharePassword": "共有パスワード", + "readmeError": "READMEã®å†…容を読ã¿å–れã¾ã›ã‚“:{{msg}}", + "enterKeywords": "検索キーワードを入力ã—ã¦ãã ã•ã„", + "searchResult": "æ¤œç´¢çµæžœ", + "sharedAt": "共有日時 <0>", + "pleaseLogin": "ログインã—ã¦ãã ã•ã„", + "cannotShare": "ã“ã®ãƒ•ァイルã¯ãƒ—レビューã§ãã¾ã›ã‚“", + "preview": "プレビュー", + "incorrectPassword": "ãƒ‘ã‚¹ãƒ¯ãƒ¼ãƒ‰ãŒæ­£ã—ãã‚りã¾ã›ã‚“", + "shareNotExist": "共有ãŒå­˜åœ¨ã—ãªã„ã‹ã€æœŸé™åˆ‡ã‚Œã§ã™", + "copyLinkToClipboard": "クリップボードã«ãƒªãƒ³ã‚¯ã‚’コピー" + }, + "download": { + "noFilesFound": "ファイルãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“", + "filterByName": "åå‰ã§çµžã‚Šè¾¼ã¿", + "selectAll": "ã™ã¹ã¦é¸æŠž", + "reverseSelect": "é¸æŠžè§£é™¤", + "cancelTaskConfirm": "ã“ã®ã‚¿ã‚¹ã‚¯ã‚’キャンセルã—ã¾ã™ã‹ï¼Ÿ", + "saveChanges": "変更をä¿å­˜", + "failedToLoad": "読ã¿è¾¼ã¿å¤±æ•—", + "active": "処ç†ä¸­", + "finished": "完了", + "activeEmpty": "ダウンロード中ã®ã‚¿ã‚¹ã‚¯ã¯ã‚りã¾ã›ã‚“", + "finishedEmpty": "完了ã—ãŸã‚¿ã‚¹ã‚¯ã¯ã‚りã¾ã›ã‚“", + "loadMore": "ã‚‚ã£ã¨èª­ã¿è¾¼ã‚€", + "taskFileDeleted": "ファイルãŒå‰Šé™¤ã•れã¾ã—ãŸ", + "unknownTaskName": "[䏿˜Ž]", + "taskCanceled": "タスクãŒã‚­ãƒ£ãƒ³ã‚»ãƒ«ã•れã¾ã—ãŸã€‚状態ã¯å¾Œã»ã©æ›´æ–°ã•れã¾ã™ã€‚", + "operationSubmitted": "æ“ä½œãŒæˆåŠŸã—ã¾ã—ãŸã€‚状態ã¯å¾Œã»ã©æ›´æ–°ã•れã¾ã™ã€‚", + "deleteThisFile": "ã“ã®ãƒ•ァイルを削除", + "openDstFolder": "ä¿å­˜å ´æ‰€ã‚’é–‹ã", + "selectDownloadingFile": "ダウンロードã™ã‚‹ãƒ•ã‚¡ã‚¤ãƒ«ã‚’é¸æŠž", + "cancelTask": "タスクをキャンセル", + "updatedAt": "更新日時:", + "uploaded": "アップロードサイズ", + "uploadSpeed": "アップロード速度", + "InfoHash": "InfoHash", + "seederCount": "シード数:", + "seeding": "シード中:", + "downloadNode": "ピア数:", + "isSeeding": "ã¯ã„", + "notSeeding": "ã„ã„ãˆ", + "chunkSize": "ピースサイズ:", + "chunkNumbers": "ピース数", + "taskDeleted": "å‰Šé™¤ã«æˆåŠŸã—ã¾ã—ãŸ", + "transferFailed": "ファイルã®è»¢é€ã«å¤±æ•—ã—ã¾ã—ãŸ", + "downloadFailed": "ダウンロードエラー:{{msg}}", + "canceledStatus": "キャンセル済ã¿", + "finishedStatus": "完了", + "pending": "完了ã€ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–å¾…ã¡", + "transferring": "アーカイブ中", + "deleteRecord": "レコード削除", + "createdAt": "ä½œæˆæ—¥ï¼š", + "unknownSize": "未知ã®ãƒ•ァイルサイズ" + }, + "setting": { + "treeView": "ツリービュー", + "autoExpandTreeView": "ツリービューã®è‡ªå‹•展開", + "autoExpandTreeViewDes": "有効ã«ã™ã‚‹ã¨ã€ã‚µã‚¤ãƒ‰ãƒãƒ¼ã®ãƒ•ァイルツリーãŒç¾åœ¨ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«å¾“ã£ã¦è‡ªå‹•çš„ã«å±•é–‹ã•れã¾ã™ã€‚", + "syncView": "ビュー設定", + "syncViewDes": "å„ディレクトリã®ãƒ“ュー設定を記憶ã—ã€ã‚µãƒ¼ãƒãƒ¼ã«åŒæœŸã—ã¾ã™ã€‚", + "syncViewOn": "サーãƒãƒ¼ã«åŒæœŸ", + "syncViewOff": "åŒæœŸã—ãªã„", + "noAuthenticator": "é¡”èªè¨¼ã€æŒ‡ç´‹èªè¨¼ã€ã¾ãŸã¯USBキーã«ã‚ˆã‚‹ãƒ­ã‚°ã‚¤ãƒ³ã®ãŸã‚ã«é€šè¡Œã‚­ãƒ¼ã‚’追加", + "neverUsed": "未使用", + "usedAt": "最終使用日 <0>", + "passkeyName": "{os}上ã®{browser}", + "versionRetentionMax": "最大ãƒãƒ¼ã‚¸ãƒ§ãƒ³æ•°ã€0ã¯ç„¡åˆ¶é™", + "versionRetentionEnabledExt": "有効ãªãƒ•ァイル拡張å­", + "versionRetentionEnabledExtDes": "Enterキーã§è¿½åŠ ã€ç©ºæ¬„ã®å ´åˆã¯ã™ã¹ã¦ã®ãƒ•ã‚¡ã‚¤ãƒ«ãŒæœ‰åйã«ãªã‚Šã¾ã™", + "enableVersionRetention": "ãƒãƒ¼ã‚¸ãƒ§ãƒ³ä¿æŒã‚’有効化", + "enableVersionRetentionDes": "有効化ã™ã‚‹ã¨ã€æ¡ä»¶ã‚’満ãŸã™ãƒ•ァイルã®å±¥æ­´ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒä¿æŒã•れã¾ã™", + "versionRetention": "ãƒãƒ¼ã‚¸ãƒ§ãƒ³ä¿æŒ", + "languageDes": "表示言語ã¨ãƒ¡ãƒ¼ãƒ«ã®å„ªå…ˆè¨€èªžã®è¨­å®š", + "timezoneDes": "表示タイムゾーンã®è¨­å®šã€ãƒ‡ãƒ•ォルトã¯ã‚·ã‚¹ãƒ†ãƒ ã‚¿ã‚¤ãƒ ã‚¾ãƒ¼ãƒ³ã«æº–æ‹ ", + "nickNameDes": "公開表示åã€æœ¬åã¾ãŸã¯ãƒ‹ãƒƒã‚¯ãƒãƒ¼ãƒ ã‚’使用å¯èƒ½", + "cropAvatar": "ã‚¢ãƒã‚¿ãƒ¼ã®ãƒˆãƒªãƒŸãƒ³ã‚°", + "preference": "設定", + "accountCreatedAt": "ä½œæˆæ—¥ <0>", + "shoeQr": "表示", + "deviceNothing": "ç¾åœ¨ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã¯WebDAVをサãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã›ã‚“", + "connectionInfo": "接続情報", + "proxyTooltip": "サーãƒãƒ¼ãŒã™ã¹ã¦ã®ãƒ•ã‚¡ã‚¤ãƒ«ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰è¦æ±‚をプロキシã—ã¾ã™ã€‚", + "readonlyTooltip": "ユーザーã¯ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã®ã¿ãƒ•ァイルã®èª­ã¿å–りãŒå¯èƒ½ã§ã™ã€‚", + "blockSysFilesUpload": "システムファイルã®ã‚¢ãƒƒãƒ—ロードをブロック", + "blockSysFilesUploadTooltip": "有効ã«ã™ã‚‹ã¨ã€<0>. ã§å§‹ã¾ã‚‹ãƒ•ァイルã®ã‚¢ãƒƒãƒ—ロードãŒãƒ–ロックã•れã¾ã™ã€‚", + "rootFolderIn": "é¸æŠž <0>", + "createWebDavAccount": "WebDAVアカウントã®ä½œæˆ", + "editWebDavAccount": "{{name}}ã®ç·¨é›†", + "seeding": "シード中", + "awaitSeeding": "シード待ã¡", + "awaitSeedingDes": "ダウンロードタスクã®ã‚·ãƒ¼ãƒ‰å®Œäº†ã‚’待機中。", + "downloadTransferDes": "ファイルを宛先ã«è»¢é€ã—ã¾ã™ã€‚", + "downloadDes": "指定ã•れãŸãƒ•ァイルをダウンロードã—ã¾ã™ã€‚", + "retryErrorHistory": "履歴エラーã®å†è©¦è¡Œ", + "retryCount": "å†è©¦è¡Œå›žæ•°", + "resumeAt": "次回å†é–‹", + "executeDuration": "実行ã«ã‹ã‹ã£ãŸç´”粋時間", + "input": "入力", + "output": "出力", + "suspended": "(ä¿ç•™ä¸­)", + "updatedAt": "更新日時", + "taskDetails": "タスク詳細", + "partialSuccessWarning": "{{num}}個ã®ã‚ªãƒ–ジェクト処ç†ã«å¤±æ•—ã—ã€ã‚¹ã‚­ãƒƒãƒ—ã•れã¾ã—ãŸã€‚", + "sendTask": "タスクé€ä¿¡", + "sendTaskDes": "処ç†ãƒŽãƒ¼ãƒ‰ã«ã‚¿ã‚¹ã‚¯ã‚’é€ä¿¡ã—ã¾ã™ã€‚", + "downloaded": "ダウンロード済ã¿", + "importingFiles": "ファイルをインãƒãƒ¼ãƒˆ", + "importingFilesDes": "ファイルを検索ã—ã€æŒ‡å®šã•れãŸãƒ•ォルダã«ã‚¤ãƒ³ãƒãƒ¼ãƒˆã—ã¾ã™ã€‚", + "importedFiles": "インãƒãƒ¼ãƒˆæ¸ˆã¿ãƒ•ァイル", + "indexedFiles": "インデックス済ã¿ãƒ•ァイル", + "extractedFiles": "è§£å‡æ¸ˆã¿ãƒ•ァイル数", + "extractedFilesSize": "è§£å‡æ¸ˆã¿ãƒ•ァイルサイズ", + "extractingFiles": "ファイル解å‡", + "extractingFilesDes": "ã™ã¹ã¦ã®ãƒ•ァイルを指定ディレクトリã«è§£å‡ã—ã¾ã™ã€‚", + "downloadingZip": "圧縮ファイルå–å¾—", + "downloadingZipDes": "圧縮ファイルを一時作業領域ã«ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã—ã¾ã™ã€‚", + "progressNotAvailable": "é€²æ—æƒ…å ±ã¯ç¾åœ¨åˆ©ç”¨ã§ãã¾ã›ã‚“", + "uploadedSize": "ファイル転é€", + "archivedFiles": "å‡¦ç†æ¸ˆã¿ãƒ•ァイル数", + "transferredFiles": "è»¢é€æ¸ˆã¿ãƒ•ァイル数", + "archivedFilesSize": "å‡¦ç†æ¸ˆã¿ãƒ•ァイルサイズ", + "createArchiveFinishing": "æ–°è¦ãƒ•ァイル変更をコミットã—ã¾ã™ã€‚", + "indexForArchiveDes": "圧縮対象ファイル全ã¦ã‚’検索ã™ã‚‹ã€‚", + "prepare": "準備", + "preparingWorkspaceDes": "ä¸€æ™‚ä½œæ¥­é ˜åŸŸã®æº–備。", + "compressFiles": "圧縮ファイルã®ä½œæˆ", + "compressFilesDes": "ファイルを一時作業領域ã«åœ§ç¸®ã™ã‚‹ã€‚", + "uploadArchiveFileDes": "圧縮ファイルを宛先ã«è»¢é€ã™ã‚‹ã€‚", + "uploadWorker": "アップロードスレッド #{{num}}", + "queueToStart": "キューイング開始", + "indexingFiles": "ファイル検索", + "indexingFilesDes": "転é€å¯¾è±¡ãƒ•ァイル全ã¦ã‚’検索ã—ã€ãƒ­ãƒƒã‚¯ã™ã‚‹ã€‚", + "transferring": "転é€", + "committingChanges": "変更をコミットã™ã‚‹", + "autoRefresh": "自動更新", + "avatarUpdated": "ã‚¢ãƒã‚¿ãƒ¼ãŒæ›´æ–°ã•れã¾ã—ãŸã€‚最新ã®ã‚¢ãƒã‚¿ãƒ¼ãŒè¡¨ç¤ºã•れるã¾ã§å¤šå°‘ã®é…å»¶ãŒç™ºç”Ÿã™ã‚‹å ´åˆãŒã‚りã¾ã™ã€‚", + "nickChanged": "ニックãƒãƒ¼ãƒ ãŒå¤‰æ›´ã•れã¾ã—ãŸã€‚åæ˜ ã«ã¯æ›´æ–°ãŒå¿…è¦ã§ã™ã€‚", + "settingSaved": "設定をä¿å­˜ã—ã¾ã—ãŸ", + "themeColorChanged": "テーマカラーãŒå¤‰æ›´ã•れã¾ã—ãŸ", + "profile": "プロフィール", + "avatar": "ã‚¢ãƒã‚¿ãƒ¼", + "uid": "UID", + "nickname": "ニックãƒãƒ¼ãƒ ", + "group": "ユーザーグループ", + "regTime": "登録日時", + "security": "パスワードã¨ã‚»ã‚­ãƒ¥ãƒªãƒ†ã‚£", + "profilePage": "マイページ", + "publicShareOnly": "パスワードãªã—共有ã®ã¿è¡¨ç¤º", + "publicShareOnlyDes": "マイページã«ãƒ‘スワードãªã—共有ã®ã¿è¡¨ç¤ºã—ã¾ã™ã€‚", + "allShare": "ã™ã¹ã¦ã®å…±æœ‰", + "allShareDes": "マイページã«ã™ã¹ã¦ã®å…±æœ‰ã‚’表示ã—ã¾ã™ã€‚パスワードãŒè¨­å®šã•れãŸå…±æœ‰ã«ã¯ã€ãƒ‘スワードを入力ã—ã¦ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚", + "hideShare": "ã™ã¹ã¦ã®å…±æœ‰ã‚’éžè¡¨ç¤º", + "hideShareDes": "マイページã«ã™ã¹ã¦ã®å…±æœ‰ã‚’éžè¡¨ç¤ºã«ã—ã¾ã™ã€‚", + "userHideShare": "ユーザーãŒå…±æœ‰ãƒªã‚¹ãƒˆã‚’éžè¡¨ç¤ºã«ã—ã¾ã—ãŸ", + "accountPassword": "ログインパスワード", + "2fa": "2段階èªè¨¼", + "enabled": "有効", + "disabled": "無効", + "appearance": "パーソナライズ", + "themeColor": "テーマé…色", + "darkMode": "ダークモード", + "syncWithSystem": "システム", + "fileList": "ファイル一覧", + "timeZone": "タイムゾーン", + "webdavServer": "接続アドレス", + "userName": "ユーザーå", + "manageAccount": "アカウント管ç†", + "uploadImage": "ファイルã‹ã‚‰ã‚¢ãƒƒãƒ—ロード", + "useGravatar": "Gravatarã‚¢ãƒã‚¿ãƒ¼ã‚’使用", + "changeNick": "ニックãƒãƒ¼ãƒ å¤‰æ›´", + "originalPassword": "ç¾åœ¨ã®ãƒ‘スワード", + "enable2FA": "2段階èªè¨¼ã‚’有効ã«ã™ã‚‹", + "disable2FA": "2段階èªè¨¼ã‚’無効ã«ã™ã‚‹", + "2faDescription": "ä»»æ„ã®2段階èªè¨¼ã‚¢ãƒ—リã€ã¾ãŸã¯2段階èªè¨¼ã«å¯¾å¿œã—ãŸãƒ‘スワード管ç†ã‚½ãƒ•トウェアã§QRコードをスキャンã—ã¦ã€æœ¬ã‚µã‚¤ãƒˆã«è¿½åŠ ã—ã¦ãã ã•ã„。スキャン後ã€2段階èªè¨¼ã‚¢ãƒ—リãŒè¡¨ç¤ºã™ã‚‹6æ¡ã®èªè¨¼ã‚³ãƒ¼ãƒ‰ã‚’入力ã—ã¦ã€2段階èªè¨¼ã‚’有効ã«ã—ã¦ãã ã•ã„。", + "inputCurrent2FACode": "ç¾åœ¨ä½¿ç”¨ã—ã¦ã„ã‚‹2段階èªè¨¼ã‚¢ãƒ—リãŒè¡¨ç¤ºã™ã‚‹6æ¡ã®èªè¨¼ã‚³ãƒ¼ãƒ‰ã‚’入力ã—ã¦ãã ã•ã„:", + "timeZoneCode": "IANAタイムゾーンå識別å­", + "authenticatorRemoved": "èªè¨¼æƒ…å ±ãŒå‰Šé™¤ã•れã¾ã—ãŸ", + "authenticatorAdded": "検証å­ãŒè¿½åŠ ã•れã¾ã—ãŸ", + "browserNotSupported": "ç¾åœ¨ã®ãƒ–ラウザã¾ãŸã¯ç’°å¢ƒã¯ã‚µãƒãƒ¼ãƒˆã•れã¦ã„ã¾ã›ã‚“", + "removedAuthenticator": "èªè¨¼æƒ…報を削除", + "removedAuthenticatorConfirm": "ã“ã®èªè¨¼æƒ…報を本当ã«ç„¡åŠ¹åŒ–ã—ã¾ã™ã‹ï¼Ÿ", + "addNewAuthenticator": "æ–°ã—ã„èªè¨¼æƒ…報を追加", + "hardwareAuthenticator": "パスキー", + "copied": "クリップボードã«ã‚³ãƒ”ーã•れã¾ã—ãŸ", + "pleaseManuallyCopy": "ç¾åœ¨ã®ãƒ–ラウザã¯ã‚µãƒãƒ¼ãƒˆã•れã¦ã„ã¾ã›ã‚“。手動ã§ã‚³ãƒ”ーã—ã¦ãã ã•ã„", + "webdavAccounts": "WebDAVアカウント管ç†", + "webdavHint": "WebDAVã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã¯{{url}}ã§ã™ã€‚ログインユーザーåã¯{{name}}ã§ã™ã€‚パスワードã¯ä½œæˆã—ãŸã‚¢ã‚«ã‚¦ãƒ³ãƒˆã®ãƒ‘スワードã§ã™ã€‚", + "annotation": "備考", + "rootFolder": "相対ルートディレクトリ", + "createdAt": "ä½œæˆæ—¥", + "action": "æ“作", + "readonlyOn": "読ã¿å–り専用", + "readonlyOff": "èª­ã¿æ›¸ã", + "proxy": "リãƒãƒ¼ã‚¹ãƒ—ロキシ", + "none": "ãªã—", + "proxied": "プロキシ済ã¿", + "delete": "削除", + "listEmpty": "レコードãªã—", + "createNewAccount": "æ–°è¦ã‚¢ã‚«ã‚¦ãƒ³ãƒˆä½œæˆ", + "taskType": "タスクã®ç¨®é¡ž", + "taskStatus": "状態", + "taskProgress": "タスクã®é€²æ—状æ³", + "errorDetails": "エラーメッセージ", + "queueing": "キューイング中", + "processing": "処ç†ä¸­", + "failed": "失敗", + "canceled": "キャンセル", + "finished": "完了", + "fileTransfer": "ファイル転é€", + "fileRecycle": "ファイルã®å¾©å…ƒ", + "importFiles": "外部ディレクトリã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆ", + "transferProgress": "完了済㿠{{num}} ä»¶", + "waiting": "待機中", + "compressing": "圧縮中", + "decompressing": "è§£å‡ä¸­", + "downloading": "ダウンロード中", + "indexing": "インデックス作æˆä¸­", + "listing": "挿入中", + "allShares": "ã™ã¹ã¦å…±æœ‰", + "trendingShares": "人気共有", + "totalShares": "共有数", + "fileName": "ファイルå", + "shareDate": "共有日", + "downloadNumber": "ダウンロード数", + "viewNumber": "閲覧数", + "language": "言語", + "iOSApp": "iOS/iPadOS クライアント", + "connectByiOS": "iOS/iPadOSデãƒã‚¤ã‚¹ã‹ã‚‰<0>{{title}}ã«æŽ¥ç¶š", + "downloadOurApp": "アプリをダウンロードã—ã¦ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ï¼š", + "fillInEndpoint": "アプリを使用ã™ã‚‹ã«ã¯ã€ä¸‹ã®QRコードをスキャンã—ã¦ãã ã•ã„(他ã®QRコードスキャンアプリã¯ä½¿ç”¨ã§ãã¾ã›ã‚“)。", + "loginApp": "ãƒã‚¤ãƒ³ãƒ‰ãŒå®Œäº†ã—ãŸã‚‰ã€ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆã‚’使用ã§ãã¾ã™ã€‚QRコードã«ã‚ˆã‚‹ãƒã‚¤ãƒ³ãƒ‰ã«å•題ãŒã‚ã‚‹å ´åˆã¯ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼åã¨ãƒ‘スワードを手動ã§å…¥åŠ›ã—ã¦ãƒ­ã‚°ã‚¤ãƒ³ã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚", + "relocateFileTo": "<0>{{more}} ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã‚’ {{policy}} ã«å¤‰æ›´ã—ã¾ã™", + "extractFileTo": "<0>{{more}} ã‚’ <1> ã«è§£å‡ã—ã¾ã™", + "createArchiveTo": "<0>{{more}} ã‚’ <1> ã«åœ§ç¸®ã—ã¾ã™", + "importFileTo": "{{policy}} ã®ãƒ•ァイルを <1> ã«å°Žå…¥ã—ã¾ã™" + }, + "vas": { + "points": "ãƒã‚¤ãƒ³ãƒˆ", + "quota": "容é‡ã‚¯ã‚©ãƒ¼ã‚¿", + "used": "ä½¿ç”¨é‡ - {{size}}", + "total": "åˆè¨ˆå®¹é‡ - {{size}}", + "report": "䏿­£åˆ©ç”¨ã®å ±å‘Š", + "validDurationDays": "{{num}} æ—¥", + "reportTarget": "報告対象", + "reportReason": "ç†ç”±", + "reportReasonOptions": ["著作権侵害", "有害ãªã‚³ãƒ³ãƒ†ãƒ³ãƒ„", "スパム", "ãã®ä»–"], + "reportDescription": "詳細説明", + "reportAbuseSuccess": "報告をå—ã‘付ã‘ã¾ã—ãŸ" + } +} diff --git a/public/locales/ja-JP/common.json b/public/locales/ja-JP/common.json new file mode 100755 index 0000000..5151444 --- /dev/null +++ b/public/locales/ja-JP/common.json @@ -0,0 +1,106 @@ +{ + "pageNotFound": "ページãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“", + "unknownError": "䏿˜Žãªã‚¨ãƒ©ãƒ¼", + "errLoadingSiteConfig": "サイト設定を読ã¿è¾¼ã‚ã¾ã›ã‚“:", + "newVersionRefresh": "ã“ã®ãƒšãƒ¼ã‚¸ã®æ–°ã—ã„ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒã‚りã¾ã™ã€‚", + "update": "æ›´æ–°", + "errorDetails": "詳細", + "renderError": "ページã®ãƒ¬ãƒ³ãƒ€ãƒªãƒ³ã‚°ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚ページを更新ã—ã¦ã¿ã¦ãã ã•ã„。", + "ok": "OK", + "cancel": "キャンセル", + "select": "é¸æŠž", + "copyToClipboard": "コピー", + "close": "é–‰ã˜ã‚‹", + "dismiss": "é–‰ã˜ã‚‹", + "intlDateTime": "{{val, datetime}}", + "seconds": "s ç§’", + "minutes": "m 分 s ç§’", + "hours": "H 時間 m 分", + "days": "{{d}} æ—¥", + "timeAgoLocaleCode": "ja_JP", + "forEditorLocaleCode": "ja-JP", + "artPlayerLocaleCode": "ja-jp", + "requestID": "リクエストID: {{id}}", + "object": "オブジェクト", + "error": "エラー", + "areYouSure": "確èª", + "incorrectSizeInput": "サイズ制é™ã«é•åã—ã¦ã„ã¾ã™", + "of": "åˆè¨ˆ", + "rowsPerPage": "1ページã‚ãŸã‚Šã®è¡Œæ•°", + "custom": "カスタム", + "enter": "入力", + "captcha": { + "cap": { + "human": "ç§ã¯äººé–“ã§ã™", + "verifying": "検証中…", + "verified": "検証ãŒå®Œäº†ã—ã¾ã—ãŸ" + } + }, + "errors": { + "401": "ログインã—ã¦ãã ã•ã„", + "403": "ã“ã®æ“作を実行ã™ã‚‹æ¨©é™ãŒã‚りã¾ã›ã‚“", + "404": "リソースãŒå­˜åœ¨ã—ã¾ã›ã‚“", + "409": "ç«¶åˆãŒç™ºç”Ÿã—ã¾ã—㟠({{message}})", + "40001": "入力パラメータã«èª¤ã‚ŠãŒã‚りã¾ã™ ({{message}})", + "40002": "アップロード失敗", + "40003": "ディレクトリã®ä½œæˆã«å¤±æ•—ã—ã¾ã—ãŸ", + "40004": "åŒåã®ã‚ªãƒ–ã‚¸ã‚§ã‚¯ãƒˆãŒæ—¢ã«å­˜åœ¨ã—ã¾ã™", + "40005": "ç½²åæœŸé™åˆ‡ã‚Œ", + "40006": "サãƒãƒ¼ãƒˆã•れã¦ã„ãªã„ストレージãƒãƒªã‚·ãƒ¼ã‚¿ã‚¤ãƒ—ã§ã™", + "40007": "ç¾åœ¨ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã§ã¯ã“ã®æ“作を実行ã§ãã¾ã›ã‚“", + "40011": "アップロードセッションãŒå­˜åœ¨ã—ãªã„ã‹ã€æœŸé™åˆ‡ã‚Œã§ã™", + "40012": "ãƒãƒ£ãƒ³ã‚¯ç•ªå·ãŒç„¡åйã§ã™ ({{message}})", + "40013": "本文ã®é•·ã•ãŒç„¡åйã§ã™ ({{message}})", + "40014": "外部リンクã®ä¸€æ‹¬å–得制é™ã‚’è¶…ãˆã¦ã„ã¾ã™", + "40015": "ã‚ªãƒ•ãƒ©ã‚¤ãƒ³ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã‚¿ã‚¹ã‚¯æ•°ã®æœ€å¤§åˆ¶é™ã‚’è¶…ãˆã¦ã„ã¾ã™", + "40016": "パスãŒå­˜åœ¨ã—ã¾ã›ã‚“", + "40017": "ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯å‡çµã•れã¦ã„ã¾ã™", + "40018": "ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯ã‚¢ã‚¯ãƒ†ã‚£ãƒ–化ã•れã¦ã„ã¾ã›ã‚“", + "40019": "ã“ã®æ©Ÿèƒ½ã¯æœ‰åйã«ãªã£ã¦ã„ã¾ã›ã‚“", + "40020": "èªè¨¼æƒ…å ±ãŒç„¡åйã¾ãŸã¯æœŸé™åˆ‡ã‚Œã§ã™", + "40021": "ユーザーãŒå­˜åœ¨ã—ã¾ã›ã‚“", + "40022": "èªè¨¼ã‚³ãƒ¼ãƒ‰ãŒæ­£ã—ãã‚りã¾ã›ã‚“", + "40023": "ログインセッションãŒå­˜åœ¨ã—ã¾ã›ã‚“", + "40024": "WebAuthn ã‚’åˆæœŸåŒ–ã§ãã¾ã›ã‚“", + "40025": "èªè¨¼ã«å¤±æ•—ã—ã¾ã—ãŸ", + "40026": "確èªã‚³ãƒ¼ãƒ‰ãŒé–“é•ã£ã¦ã„ã¾ã™", + "40027": "èªè¨¼ã«å¤±æ•—ã—ã¾ã—ãŸã€‚ページを更新ã—ã¦å†è©¦è¡Œã—ã¦ãã ã•ã„", + "40028": "メールã®é€ä¿¡ã«å¤±æ•—ã—ã¾ã—ãŸ", + "40029": "無効ãªãƒªãƒ³ã‚¯ã§ã™", + "40030": "ã“ã®ãƒªãƒ³ã‚¯ã¯æœŸé™åˆ‡ã‚Œã§ã™", + "40032": "ã“ã®ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã¯æ—¢ã«ä½¿ç”¨ã•れã¦ã„ã¾ã™", + "40033": "ユーザーãŒã‚¢ã‚¯ãƒ†ã‚£ãƒ–化ã•れã¦ã„ã¾ã›ã‚“。アクティベーションメールをå†é€ä¿¡ã—ã¾ã—ãŸ", + "40034": "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ã‚¢ã‚¯ãƒ†ã‚£ãƒ–化ã§ãã¾ã›ã‚“", + "40035": "ストレージãƒãƒªã‚·ãƒ¼ãŒå­˜åœ¨ã—ã¾ã›ã‚“", + "40039": "ユーザーグループãŒå­˜åœ¨ã—ã¾ã›ã‚“", + "40044": "ファイルãŒå­˜åœ¨ã—ã¾ã›ã‚“", + "40045": "ディレクトリ内ã®ã‚ªãƒ–ジェクトをリストã§ãã¾ã›ã‚“", + "40047": "ãƒ•ã‚¡ã‚¤ãƒ«ã‚·ã‚¹ãƒ†ãƒ ã‚’åˆæœŸåŒ–ã§ãã¾ã›ã‚“", + "40048": "タスクã®ä½œæˆã«å¤±æ•—ã—ã¾ã—ãŸ", + "40049": "ファイルサイズãŒåˆ¶é™ã‚’è¶…ãˆã¦ã„ã¾ã™", + "40050": "ファイルã®ç¨®é¡žãŒè¨±å¯ã•れã¦ã„ã¾ã›ã‚“", + "40051": "容é‡ä¸è¶³ã§ã™", + "40052": "ã“ã®ãƒ•ァイルåã¾ãŸã¯æ‹¡å¼µå­ã¯è¨±å¯ã•れã¦ã„ã¾ã›ã‚“", + "40053": "ルートディレクトリã§ã¯ã“ã®æ“作ã¯ã‚µãƒãƒ¼ãƒˆã•れã¦ã„ã¾ã›ã‚“", + "40054": "ç¾åœ¨ã€åŒã˜åå‰ã®ãƒ•ァイルãŒã‚¢ãƒƒãƒ—ロードã•れã¦ã„ã¾ã™ã€‚アップロードセッションをクリアã—ã¦ãã ã•ã„", + "40055": "ファイル情報ãŒä¸€è‡´ã—ã¾ã›ã‚“", + "40056": "ã“ã®å½¢å¼ã®åœ§ç¸®ãƒ•ァイルã¯ã‚µãƒãƒ¼ãƒˆã•れã¦ã„ã¾ã›ã‚“", + "40057": "使用å¯èƒ½ãªã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ãŒå¤‰æ›´ã•れã¾ã—ãŸã€‚ファイルリストを更新ã—ã¦ã€ã“ã®ã‚¿ã‚¹ã‚¯ã‚’å†è¿½åŠ ã—ã¦ãã ã•ã„", + "40058": "共有ãŒå­˜åœ¨ã—ãªã„ã‹ã€æœŸé™åˆ‡ã‚Œã§ã™", + "40069": "パスワードãŒé–“é•ã£ã¦ã„ã¾ã™", + "40070": "プレビューã§ãã¾ã›ã‚“", + "40071": "ç½²åãŒä¸æ­£ã§ã™", + "40073": "ファイルãŒä½¿ç”¨ä¸­ã§ã™", + "40074": "é¸æŠžã—ãŸãƒ•ァイル数ãŒåˆ¶é™ã‚’è¶…ãˆã¦ã„ã¾ã™", + "40079": "処ç†å¯¾è±¡ãƒ•ァイル数ã®ä¸Šé™ã‚’è¶…ãˆã¾ã—ãŸã€‚æ“作範囲を狭ã‚ã¦ãã ã•ã„", + "40080": "メールアドレスã¾ãŸã¯ãƒ‘スワードãŒé–“é•ã£ã¦ã„ã¾ã™", + "40081": "æ“作ãŒå®Œå…¨ã«æˆåŠŸã—ã¾ã›ã‚“ã§ã—ãŸ", + "40082": "ファイル所有者ã®ã¿å®Ÿè¡Œå¯èƒ½ã§ã™", + "50001": "データベースæ“作ã«å¤±æ•—ã—ã¾ã—㟠({{message}})", + "50002": "URLã¾ãŸã¯ãƒªã‚¯ã‚¨ã‚¹ãƒˆç½²åã«å¤±æ•—ã—ã¾ã—㟠({{message}})", + "50004": "I/Oæ“作ã«å¤±æ•—ã—ã¾ã—㟠({{message}})", + "50005": "内部エラーãŒç™ºç”Ÿã—ã¾ã—㟠({{message}})", + "50010": "対象ノードãŒåˆ©ç”¨ã§ãã¾ã›ã‚“", + "50011": "ファイルメタデータã®å–å¾—ã«å¤±æ•—ã—ã¾ã—ãŸ" + } +} diff --git a/public/locales/ja-JP/dashboard.json b/public/locales/ja-JP/dashboard.json new file mode 100755 index 0000000..7ed65c1 --- /dev/null +++ b/public/locales/ja-JP/dashboard.json @@ -0,0 +1,1623 @@ +{ + "errors": { + "40036": "デフォルトã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã¯å‰Šé™¤ã§ãã¾ã›ã‚“", + "40037": "ファイルBlobãŒã¾ã ã“ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã‚’使用ã—ã¦ã„ã¾ã™ã€‚ã“れらã®ãƒ•ァイルBlobã‚’å…ˆã«å‰Šé™¤ã—ã¦ãã ã•ã„", + "40038": "{{message}}個ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ãŒã“ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã«ãƒã‚¤ãƒ³ãƒ‰ã•れã¦ã„ã¾ã™ã€‚å…ˆã«ãƒã‚¤ãƒ³ãƒ‰ã‚’解除ã—ã¦ãã ã•ã„", + "40040": "システムユーザーグループã«å¯¾ã—ã¦ã“ã®æ“作を実行ã§ãã¾ã›ã‚“", + "40041": "{{message}}人ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã«å±žã—ã¦ã„ã¾ã™ã€‚å…ˆã«ã“れらã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’削除ã™ã‚‹ã‹ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—を変更ã—ã¦ãã ã•ã„", + "40042": "åˆæœŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã¯å¤‰æ›´ã§ãã¾ã›ã‚“", + "40043": "åˆæœŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã«å¯¾ã—ã¦ã“ã®æ“作を実行ã§ãã¾ã›ã‚“", + "40046": "ホストノードã«å¯¾ã—ã¦ã“ã®æ“作を実行ã§ãã¾ã›ã‚“", + "40060": "従属ノードãŒãƒ›ã‚¹ãƒˆãƒŽãƒ¼ãƒ‰ã«ã‚³ãƒ¼ãƒ«ãƒãƒƒã‚¯è¦æ±‚ã‚’é€ä¿¡ã§ãã¾ã›ã‚“。ホストå´ã®ã€Œãƒ‘ラメーター設定 - サイト情報 - サイトURL設定ã€ã‚’確èªã—ã€å¾“属ノードãŒã“ã®ã‚¢ãƒ‰ãƒ¬ã‚¹({{message}})ã«æŽ¥ç¶šã§ãã‚‹ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„", + "40061": "Cloudreveã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒä¸€è‡´ã—ã¾ã›ã‚“({{message}})", + "40086": "ノードã¯ä»¥ä¸‹ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã§ä½¿ç”¨ã•れã¦ã„ã¾ã™ï¼š{{message}}", + "50008": "è¨­å®šé …ç›®ã®æ›´æ–°ã«å¤±æ•—ã—ã¾ã—ãŸ({{message}})", + "50009": "クロスオリジンãƒãƒªã‚·ãƒ¼ã®è¿½åŠ ã«å¤±æ•—ã—ã¾ã—ãŸ" + }, + "nav": { + "summary": "ダッシュボード", + "settings": "パラメーター設定", + "basicSetting": "サイト情報", + "email": "メール", + "transportation": "転é€ã¨é€šä¿¡", + "appearance": "外観", + "image": "ç”»åƒã¨ãƒ—レビュー", + "captcha": "èªè¨¼ã‚³ãƒ¼ãƒ‰", + "storagePolicy": "ストレージãƒãƒªã‚·ãƒ¼", + "nodes": "ノード", + "groups": "ユーザーグループ", + "users": "ユーザー", + "files": "ファイル", + "entities": "ファイルBlob", + "shares": "共有", + "tasks": "ãƒãƒƒã‚¯ã‚°ãƒ©ã‚¦ãƒ³ãƒ‰ã‚¿ã‚¹ã‚¯", + "remoteDownload": "オフラインダウンロード", + "generalTasks": "一般タスク", + "title": "ダッシュボード", + "dashboard": "Cloudreve ダッシュボード", + "userSession": "ユーザーセッション", + "fileSystem": "ファイルシステム", + "mediaProcessing": "メディア処ç†", + "queue": "キュー", + "events": "イベント", + "server": "サーãƒãƒ¼", + "customProps": "カスタムプロパティ", + "abuseReport": "䏿­£åˆ©ç”¨å ±å‘Š" + }, + "summary": { + "generatedAt": "ç”Ÿæˆæ—¥ <0>", + "confirmSiteURLTitle": "サイトURLã®è¨­å®š", + "siteURLNotMatch": "設定ã•れã¦ã„るサイトURLã¯ç¾åœ¨ã®{{current}}ã‚’å«ã‚“ã§ã„ã¾ã›ã‚“。設定を変更ã—ã¾ã™ã‹ï¼Ÿ", + "setAsPrimary": "メインサイトURLã¨ã—ã¦è¨­å®š", + "setAsPrimaryDes": "外部サービスã¨ã®é€šä¿¡ãŠã‚ˆã³ã‚³ãƒ¼ãƒ«ãƒãƒƒã‚¯ã®å—ä¿¡ã«ã€{{current}}をメインサイトURLã¨ã—ã¦è¨­å®šã—ã¾ã™ã€‚インターãƒãƒƒãƒˆã‹ã‚‰ã‚¢ã‚¯ã‚»ã‚¹å¯èƒ½ãªURLを使用ã—ã¦ãã ã•ã„。", + "setAsSecondary": "予備サイトURLã«è¿½åŠ ", + "setAsSecondaryDes": "{{current}}を予備サイトURLã«è¿½åŠ ã—ã¾ã™ã€‚Cloudreveã¯ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒå®Ÿéš›ã«ã‚¢ã‚¯ã‚»ã‚¹ã—ãŸURLã«åŸºã¥ã„ã¦è‡ªå‹•çš„ã«ä½¿ç”¨ã™ã‚‹ã‹æ±ºå®šã—ã¾ã™ã€‚", + "siteURLDescription": "ã“ã®è¨­å®šã¯éžå¸¸ã«é‡è¦ã§ã™ã€‚サイトã®å®Ÿéš›ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã¨ä¸€è‡´ã—ã¦ã„ã‚‹ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。「パラメーター設定 - サイト情報ã€ã§ã“ã®è¨­å®šã‚’変更ã§ãã¾ã™ã€‚", + "ignore": "無視", + "changeIt": "変更", + "trend": "傾å‘", + "summary": "åˆè¨ˆ", + "totalUsers": "登録ユーザー", + "totalFilesAndFolders": "ファイルã¨ãƒ•ォルダ", + "shareLinks": "共有リンク", + "totalBlobs": "ファイルBlob", + "homepage": "ホーム", + "github": "GitHub", + "documents": "ドキュメント", + "discordCommunity": "Discordコミュニティ", + "telegram": "Telegramグループ", + "forum": "GitHubディスカッション", + "buyPro": "Pro ã«ã‚¢ãƒƒãƒ—グレード", + "publishedAt": "公開日 <0>", + "licenseExpireAt": "ライセンス有効期é™", + "permanentLicense": "永久", + "offlineLicenseExpireAy": "オフラインライセンス有効期é™", + "offlineLicenseDes": "ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã«æŽ¥ç¶šã•れã¦ã„ã‚‹å ´åˆã€Cloudreveã¯æœŸé™å‰ã«ã‚ªãƒ•ãƒ©ã‚¤ãƒ³ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’è‡ªå‹•çš„ã«æ›´æ–°ã—ã¾ã™", + "licensedDomains": "ライセンス対象ドメイン", + "renew": "オフラインライセンス更新", + "manageLicense": "ライセンス管ç†", + "volPurchase": "クライアント VOL ライセンスã¯ã€<0>ライセンス管ç†ãƒ‘ãƒãƒ« ã§åˆ¥é€”購入ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚VOL ライセンスã«ã‚ˆã‚Šã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯<1>Cloudreve iOS クライアント を使用ã—ã¦ã‚µã‚¤ãƒˆã«ç„¡æ–™ã§æŽ¥ç¶šã§ãã€iOS ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆã®æœ‰æ–™ã‚µãƒ–スクリプションã¯ä¸è¦ã«ãªã‚Šã¾ã™ã€‚ライセンスを購入後ã€ä¸‹è¨˜ã®ã€Œæ›´æ–°ã€ãƒœã‚¿ãƒ³ã‚’クリックã—ã¦ãã ã•ã„。", + "iosVol": "iOSクライアント一括ライセンス (VOL)", + "refreshSuccessfully": "æ›´æ–°ãŒæˆåŠŸã—ã¾ã—ãŸ", + "manualRefresh": "ã‚ªãƒ•ãƒ©ã‚¤ãƒ³ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚’æ‰‹å‹•ã§æ›´æ–°", + "manualRefreshDes": "オフラインライセンスã®è‡ªå‹•æ›´æ–°ã«å¤±æ•—ã—ã¾ã—ãŸã€‚<0>ライセンス管ç†ãƒ‘ãƒãƒ« ã«ãƒ­ã‚°ã‚¤ãƒ³ã—ã¦æœ€æ–°ã®ã‚ªãƒ•ラインライセンスをå–å¾—ã—ã€ä¸‹è¨˜ã«è²¼ã‚Šä»˜ã‘ã¦ãã ã•ã„。" + }, + "queue": { + "queueName_io_intense": "IO集約型", + "queueName_io_intenseDes": "大é‡ã®IOæ“作(ストレージãƒãƒªã‚·ãƒ¼ã®è»¢é€ã€è§£å‡ã€åœ§ç¸®ãªã©ï¼‰ã‚’処ç†ã™ã‚‹ãŸã‚ã®ã‚­ãƒ¥ãƒ¼ã§ã™ã€‚", + "queueName_media_meta": "メディアメタデータ抽出", + "queueName_media_metaDes": "メディアファイルã®ãƒ¡ã‚¿ãƒ‡ãƒ¼ã‚¿æŠ½å‡ºã‚’行ã„ã¾ã™ã€‚", + "queueName_recycle": "Blob 回åŽ", + "queueName_recycleDes": "期é™åˆ‡ã‚Œã®ãƒ•ァイルBlob削除", + "queueName_thumb": "サムãƒã‚¤ãƒ«ç”Ÿæˆ", + "queueName_thumbDes": "ファイルã®ã‚µãƒ ãƒã‚¤ãƒ«ã‚’生æˆã—ã¾ã™ã€‚", + "queueName_remote_download": "オフラインダウンロード", + "queueName_remote_downloadDes": "オフラインダウンロードタスクを処ç†ã—ã¾ã™ã€‚", + "failed": "失敗 ({{count}})", + "success": "æˆåŠŸ ({{count}})", + "suspending": "ä¿ç•™ä¸­ ({{count}})", + "busyWorker": "処ç†ä¸­ ({{count}})", + "submited": "é€ä¿¡æ¸ˆã¿ ({{count}})", + "editQueueSettings": "編集キュー設定 - {{name}}", + "workerNum": "ワーカスレッド数", + "workerNumDes": "ã‚¿ã‚¹ã‚¯ã‚­ãƒ¥ãƒ¼ã®æœ€å¤§ä¸¦åˆ—実行数。", + "maxExecution": "最大実行時間", + "maxExecutionDes": "ã‚¿ã‚¹ã‚¯ã®æœ€å¤§å®Ÿè¡Œæ™‚間(秒)。ã“ã®æ™‚é–“ã‚’è¶…ãˆã‚‹ã¨ã‚¿ã‚¹ã‚¯ã¯å¼·åˆ¶çµ‚了ã•れã¾ã™ã€‚", + "backoffFactor": "ãƒãƒƒã‚¯ã‚ªãƒ•ä¿‚æ•°", + "backoffFactorDes": "タスクå†è©¦è¡Œæ™‚é–“é–“éš”ã®å¢—加係数。", + "backoffMaxDuration": "最大ãƒãƒƒã‚¯ã‚ªãƒ•時間", + "backoffMaxDurationDes": "タスクå†è©¦è¡Œã®æœ€å¤§ãƒãƒƒã‚¯ã‚ªãƒ•時間(秒)。", + "maxRetry": "最大å†è©¦è¡Œå›žæ•°", + "maxRetryDes": "ã‚¿ã‚¹ã‚¯å¤±æ•—æ™‚ã®æœ€å¤§å†è©¦è¡Œå›žæ•°", + "retryDelay": "å†è©¦è¡Œé…å»¶", + "retryDelayDes": "タスクå†è©¦è¡Œã®åˆæœŸé…延時間(秒)" + }, + "settings": { + "headlessFooter": "ログインセッションページã®ä¸‹éƒ¨", + "headlessFooterDes": "ユーザーãŒãƒ­ã‚°ã‚¤ãƒ³ã€ç™»éŒ²ã€ã‚³ãƒ¼ãƒ«ãƒãƒƒã‚¯çµæžœãªã©ã®ãƒšãƒ¼ã‚¸ã®ä¸‹éƒ¨ã«è¡¨ç¤ºã™ã‚‹ã‚«ã‚¹ã‚¿ãƒ  HTML コンテンツ。", + "headlessBottom": "ログインセッションページã®ä¸»ä½“下部", + "headlessBottomDes": "ユーザーãŒãƒ­ã‚°ã‚¤ãƒ³ã€ç™»éŒ²ã€ã‚³ãƒ¼ãƒ«ãƒãƒƒã‚¯çµæžœãªã©ã®ãƒšãƒ¼ã‚¸ã®ä¸»ä½“ボックスã®ä¸‹éƒ¨ã«è¡¨ç¤ºã™ã‚‹ã‚«ã‚¹ã‚¿ãƒ  HTML コンテンツ。", + "customHTML": "カスタム HTML", + "customHTMLDes": "ã‚µã‚¤ãƒˆã®æ—¢å®šã®ä½ç½®ã«ã‚«ã‚¹ã‚¿ãƒ  HTML コンテンツを挿入ã—ã¾ã™ã€‚", + "sidebarBottom": "サイドãƒãƒ¼ã®ä¸‹éƒ¨", + "sidebarBottomDes": "サイドãƒãƒ¼ã®ä¸‹éƒ¨ã«è¡¨ç¤ºã™ã‚‹ã‚«ã‚¹ã‚¿ãƒ  HTML コンテンツ。", + "addNavItem": "ナビゲーション項目を追加", + "customNavItems": "サイドナビゲーションãƒãƒ¼ã®ã‚«ã‚¹ã‚¿ãƒžã‚¤ã‚º", + "customNavItemsDes": "å·¦å´ã®ãƒŠãƒ“ゲーションãƒãƒ¼ã«ã‚«ã‚¹ã‚¿ãƒ é …目を追加ã§ãã¾ã™ã€‚ユーザーãŒã‚¯ãƒªãƒƒã‚¯ã™ã‚‹ã¨ã€å¯¾å¿œã™ã‚‹ãƒªãƒ³ã‚¯ã«ç§»å‹•ã—ã¾ã™ã€‚", + "navItemUrl": "リンク", + "iconifyNamePlaceholder": "Iconify アイコン識別å­ï¼ˆä¾‹ï¼šfluent:home-24-regular)", + "imageUrl": "ç”»åƒ URL", + "iconifyName": "Iconify アイコンå", + "oidc": "OpenID Connect (OIDC)", + "oidcDes": "OpenID Connect (OIDC) ã¯ã€ç•°ãªã‚‹ã‚·ã‚¹ãƒ†ãƒ é–“ã§èªè¨¼ã‚’行ã†ãŸã‚ã®ã‚ªãƒ¼ãƒ—ンãªèªè¨¼ãƒ—ロトコルã§ã™ã€‚サードパーティーã®ã‚¢ã‚¤ãƒ‡ãƒ³ãƒ†ã‚£ãƒ†ã‚£ãƒ—ラットフォームã§ã‚¢ãƒ—リケーションを作æˆã—ãŸã‚‰ã€<0>{{url}} を「リダイレクト URIã€ã«è¿½åŠ ã—ã¦ãã ã•ã„。詳細ã¯<1>å…¬å¼ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’å‚ç…§ã—ã¦ãã ã•ã„。", + "clientID": "クライアント ID", + "clientIDDes": "サードパーティーã®ã‚¢ã‚¤ãƒ‡ãƒ³ãƒ†ã‚£ãƒ†ã‚£ãƒ—ラットフォームã§ä½œæˆã•れãŸã‚¢ãƒ—リケーションã®ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆ ID。", + "clientSecret": "クライアントシークレット", + "clientSecretDes": "サードパーティーã®ã‚¢ã‚¤ãƒ‡ãƒ³ãƒ†ã‚£ãƒ†ã‚£ãƒ—ラットフォームã§ä½œæˆã•れãŸã‚¢ãƒ—リケーションã®ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆã‚·ãƒ¼ã‚¯ãƒ¬ãƒƒãƒˆã€‚", + "scope": "スコープ", + "scopeDes": "追加ã§å¿…è¦ãªã‚¹ã‚³ãƒ¼ãƒ—をカンマ <0>, ã§åŒºåˆ‡ã£ã¦å…¥åŠ›ã—ã¦ãã ã•ã„。デフォルトã§ã¯ã€Cloudreve 㯠<0>openid , <0>email ãŠã‚ˆã³ <0>profile ã‚’è¦æ±‚ã—ã¾ã™ã€‚ã“れらã¯ã“ã“ã§ç¹°ã‚Šè¿”ã—入力ã™ã‚‹å¿…è¦ã¯ã‚りã¾ã›ã‚“。", + "oidcWellknown": "Wellknown 設定", + "oidcWellknownDes": "サードパーティーã®ã‚¢ã‚¤ãƒ‡ãƒ³ãƒ†ã‚£ãƒ†ã‚£ãƒ—ラットフォームã®Wellknown設定。OpenID Connectã®è¨­å®šæƒ…å ±ãŒå«ã¾ã‚Œã¦ã„ã¾ã™ã€‚", + "importFromWellknown": "URLã‹ã‚‰ã‚¤ãƒ³ãƒãƒ¼ãƒˆ", + "importOidc": "OIDC Wellknown 設定ã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆ", + "oidcWellknownUrl": "Wellknown URL", + "oidcWellknownUrlDes": "サードパーティーã®ã‚¢ã‚¤ãƒ‡ãƒ³ãƒ†ã‚£ãƒ†ã‚£ãƒ—ラットフォームã®Wellknown URL。例: <0>https://accounts.google.com/.well-known/openid-configuration。", + "resetUrl": "リセットリンク", + "exceedToleranceDays": "設定ã•れãŸç¦æ­¢çŒ¶äºˆæ—¥æ•°", + "activateUrl": "有効化リンク", + "domainNotLicensed": "ドメイン未承èª", + "domainNotLicensedDes": "設定ã•れãŸã‚µã‚¤ãƒˆURLã«æœªæ‰¿èªã®ãƒ‰ãƒ¡ã‚¤ãƒ³ãŒå«ã¾ã‚Œã¦ã„ã¾ã™ã€‚<0>承èªç®¡ç†ãƒ‘ãƒãƒ«ã§ã“ã®ã‚µãƒ–ドメインを追加ã—ã€ä¸‹ã®ãƒœã‚¿ãƒ³ã‚’クリックã—ã¦æ‰¿èªã‚’æ›´æ–°ã—ã¦ã‹ã‚‰å†è©¦è¡Œã—ã¦ãã ã•ã„。", + "showSettings": "表示設定", + "perPage": "1ページã‚ãŸã‚Š {{num}} ä»¶", + "noNodes": "使用å¯èƒ½ãªãƒŽãƒ¼ãƒ‰ãŒã‚りã¾ã›ã‚“。", + "extractMediaMeta": "メディア情報抽出", + "extractMediaMetaDes": "è¡¨ç¤ºã¨æ¤œç´¢ã®ãŸã‚ã«ãƒ¡ãƒ‡ã‚£ã‚¢ãƒ•ァイルã®ãƒ¡ã‚¿ãƒ‡ãƒ¼ã‚¿ã‚’å–å¾—ã—ã¾ã™ã€‚デフォルトã§ã¯ã€ãƒã‚¤ãƒ†ã‚£ãƒ–ã§ãªã„ストレージãƒãƒªã‚·ãƒ¼ã¯ã€Œã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ãƒã‚¤ãƒ†ã‚£ãƒ–ã€æ–¹å¼ã®ã¿ã‚’使用ã—ã¾ã™ã€‚「ストレージãƒãƒªã‚·ãƒ¼è¨­å®šã€ãƒšãƒ¼ã‚¸ã§ã€Œã‚¨ã‚¯ã‚¹ãƒˆãƒ©ã‚¯ã‚¿ãƒ¼ã‚¨ãƒ¼ã‚¸ã‚§ãƒ³ãƒˆã€æ©Ÿèƒ½ã‚’有効ã«ã™ã‚‹ã¨ã€ã‚µãƒ¼ãƒ‰ãƒ‘ーティストレージãƒãƒªã‚·ãƒ¼ã®ã‚µãƒ ãƒã‚¤ãƒ«æ©Ÿèƒ½ã‚’æ‹¡å¼µã§ãã¾ã™ã€‚詳細ã¯<0>å…¬å¼ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’å‚ç…§ã—ã¦ãã ã•ã„。", + "exif": "EXIF", + "exifDes": "è¡¨ç¤ºã¨æ¤œç´¢ã®ãŸã‚ã«ç”»åƒãƒ•ァイルã‹ã‚‰EXIFメタデータをå–å¾—ã—ã¾ã™ã€‚", + "music": "音楽メタデータ", + "musicDes": "タイトルã€ã‚¢ãƒ¼ãƒ†ã‚£ã‚¹ãƒˆã€ã‚¢ãƒ«ãƒãƒ ãªã©ã®æƒ…報をå–å¾—ã™ã‚‹ãŸã‚ã«ã€éŸ³æ¥½ãƒ•ァイルã‹ã‚‰ãƒ¡ã‚¿ãƒ‡ãƒ¼ã‚¿ã‚’å–å¾—ã—ã¾ã™ã€‚", + "ffprobe": "FFprobe", + "ffprobeDes": "FFprobeを使用ã—ã¦ã€ãƒ“デオãŠã‚ˆã³ã‚ªãƒ¼ãƒ‡ã‚£ã‚ªãƒ•ァイルã‹ã‚‰ãƒ¡ã‚¿ãƒ‡ãƒ¼ã‚¿ã‚’å–å¾—ã—ã¾ã™ã€‚", + "maxSizeLocal": "最大ファイルサイズ(ローカルストレージ)", + "maxSizeLocalDes": "ローカルストレージãƒãƒªã‚·ãƒ¼ã§ãƒ•ァイルをä¿å­˜ã™ã‚‹å ´åˆã€ãƒ¡ã‚¿ãƒ‡ãƒ¼ã‚¿ã®æŠ½å‡ºãŒè¨±å¯ã•れる最大ファイルサイズ(0 を入力ã™ã‚‹ã¨ç„¡åˆ¶é™ï¼‰ã€‚", + "maxSizeRemote": "最大ファイルサイズ(リモートストレージ)", + "maxSizeRemoteDes": "サードパーティストレージãƒãƒªã‚·ãƒ¼ã§ãƒ•ァイルをä¿å­˜ã™ã‚‹å ´åˆã€ãƒ¡ã‚¿ãƒ‡ãƒ¼ã‚¿ã®æŠ½å‡ºãŒè¨±å¯ã•れる最大ファイルサイズ(0 を入力ã™ã‚‹ã¨ç„¡åˆ¶é™ï¼‰ã€‚", + "exifBruteForce": "å¿…è¦ã«å¿œã˜ã¦ãƒ–ルートフォースサーãƒã‚’使用ã™ã‚‹", + "exifBruteForceDes": "有効ã«ã™ã‚‹ã¨ã€æ¨™æº–ヘッダーã®ä½ç½®ã§EXIFデータãŒè¦‹ã¤ã‹ã‚‰ãªã„å ´åˆã€EXIFデータを見ã¤ã‘ã‚‹ãŸã‚ã«ãƒ•ァイル全体をスキャンã—ã¾ã™ã€‚å‡¦ç†æ™‚é–“ãŒé•·ããªã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ãŒã€éžæ¨™æº–ã®ä½ç½®ã«ã‚ã‚‹EXIFデータを見ã¤ã‘ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚", + "musicCover": "曲ã®ã‚¸ãƒ£ã‚±ãƒƒãƒˆç”»åƒ", + "musicCoverDes": "オーディオファイルã‹ã‚‰ã‚¢ãƒ«ãƒãƒ ã‚¸ãƒ£ã‚±ãƒƒãƒˆç”»åƒã‚’抽出ã—ã¾ã™ã€‚ID3(v1ã€2.2ã€2.3ã€2.4)メタデータコンテナをサãƒãƒ¼ãƒˆã—ã¾ã™ã€‚ã“ã®ã‚¸ã‚§ãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ã¯ã€ä»–ã®ç”»åƒã‚¸ã‚§ãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ï¼ˆCloudreve組ã¿è¾¼ã¿ã¾ãŸã¯VIPS)ã«ä¾å­˜ã—ã¾ã™ã€‚", + "geocoding": "ジオコーディング", + "geocodingDes": "メディアã®EXIFã«è¨˜éŒ²ã•れãŸåº§æ¨™æƒ…å ±ã«åŸºã¥ã„ã¦ã€Mapboxサービスを使用ã—ã¦ä½æ‰€æƒ…報をå–å¾—ã—ã¾ã™ã€‚", + "mapboxAK": "Mapbox APIキー", + "mapboxAKDes": "Mapboxコンソールã§ä½œæˆã•れãŸAPIキー。", + "geocodingDependencyWarning": "ジオコーディングジェãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ã¯EXIFジェãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ã«ä¾å­˜ã—ã¦ã„ã¾ã™ã€‚EXIFジェãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ã‚’有効ã«ã—ã¦ãã ã•ã„。", + "notAppliedToNativeGenerator": "{{prefix}}ã¯ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ãƒã‚¤ãƒ†ã‚£ãƒ–ジェãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ã«ã¯é©ç”¨ã•れã¾ã›ã‚“。", + "notAppliedToOneDriveNativeGenerator": "{{prefix}}ã¯OneDriveã¾ãŸã¯SharePointストレージãƒãƒªã‚·ãƒ¼ãƒã‚¤ãƒ†ã‚£ãƒ–ジェãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ã«ã¯é©ç”¨ã•れã¾ã›ã‚“。", + "fileBlobMargin": "ファイルBlob一時URLキャッシュ冗長性(秒)", + "fileBlobMarginDes": "åŒã˜ãƒ•ァイルBlobãŒè¤‡æ•°å›žãƒªã‚¯ã‚¨ã‚¹ãƒˆã•れãŸå ´åˆã€å…ƒã®URLã®æœ‰åŠ¹æœŸé™ãŒå†—長性時間よりも長ã„å ´åˆã€åŒã˜URLãŒå†åˆ©ç”¨ã•れã¾ã™ã€‚", + "fileBlobTimeout": "ファイルBlob一時URL有効期é™ï¼ˆç§’)", + "fileBlobTimeoutDes": "ユーザーãŒãƒ•ァイルを開ã„ãŸã‚Šãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã—ãŸã‚Šã™ã‚‹éš›ã«å–å¾—ã™ã‚‹ä¸€æ™‚ãƒªãƒ³ã‚¯ã®æœ‰åŠ¹æœŸé™ã‚’制é™ã—ã¾ã™ã€‚ローカルストレージãƒãƒªã‚·ãƒ¼ã€WebDAVã€ã¾ãŸã¯Cloudreveã§ãƒ—ロキシã•れãŸãƒ•ァイルã®ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã®ã¿ã«é©ç”¨ã•れã¾ã™ã€‚", + "wopiSessionTimeout": "WOPIセッション有効期é™ï¼ˆç§’)", + "wopiSessionTimeoutDes": "ユーザーãŒWOPIを使用ã—ã¦ãƒ•ァイルを編集ã™ã‚‹å ´åˆã®å˜ä¸€ã‚»ãƒƒã‚·ãƒ§ãƒ³ã®æœ‰åŠ¹æœŸé™ã‚’制é™ã—ã¾ã™ã€‚期é™åˆ‡ã‚Œã«ãªã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯Cloudreveã‹ã‚‰ãƒ•ァイルをå†åº¦é–‹ãå¿…è¦ãŒã‚りã¾ã™ã€‚", + "oauthRefresh": "OAuthストレージãƒãƒªã‚·ãƒ¼è³‡æ ¼æƒ…報更新間隔", + "oauthRefreshDes": "OAuthを使用ã™ã‚‹ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ï¼ˆOneDrive)ã®è³‡æ ¼æƒ…報をã©ã‚Œãらã„ã®é »åº¦ã§æ›´æ–°ã™ã‚‹ã‹ã‚’設定ã—ã¾ã™ã€‚ã“れã«ã‚ˆã‚Šã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã‚’長期間使用ã—ãªã‹ã£ãŸãŸã‚ã«è³‡æ ¼æƒ…å ±ãŒæœŸé™åˆ‡ã‚Œã«ãªã‚‹ã®ã‚’防ãã“ã¨ãŒã§ãã¾ã™ã€‚", + "transitParallelNum": "è»¢é€æœ€å¤§ä¸¦åˆ—転é€", + "transitParallelNumDes": "å˜ä¸€ã‚µãƒ¼ãƒãƒ¼ãƒ•ァイル転é€ã‚¿ã‚¹ã‚¯ã«è¤‡æ•°ã®ãƒ•ァイルãŒå«ã¾ã‚Œã‚‹å ´åˆã®ã€æœ€å¤§ä¸¦åˆ—アップロード数。", + "failedChunkRetry": "ãƒãƒ£ãƒ³ã‚¯ã‚¨ãƒ©ãƒ¼æœ€å¤§å†è©¦è¡Œå›žæ•°", + "failedChunkRetryDes": "ãƒãƒ£ãƒ³ã‚¯ã‚¢ãƒƒãƒ—ãƒ­ãƒ¼ãƒ‰å¤±æ•—å¾Œã®æœ€å¤§å†è©¦è¡Œå›žæ•°ã€‚サーãƒãƒ¼å´ã‚¢ãƒƒãƒ—ロードã¾ãŸã¯ä¸­ç¶™åˆ†å‰²ã‚¢ãƒƒãƒ—ロードã®ã¿ã«é©ç”¨ã•れã¾ã™ã€‚", + "cacheChunks": "キャッシュã•れãŸã‚¹ãƒˆãƒªãƒ¼ãƒ åˆ†ç‰‡ãƒ•ァイルã«ã‚ˆã‚‹å†è©¦è¡Œ", + "cacheChunksDes": "有効ã«ã™ã‚‹ã¨ã€ã‚¹ãƒˆãƒªãƒ¼ãƒ è»¢é€ã«ã‚ˆã‚‹åˆ†ç‰‡ã‚¢ãƒƒãƒ—ロード時ã«åˆ†ç‰‡ãƒ‡ãƒ¼ã‚¿ãŒã‚·ã‚¹ãƒ†ãƒ ã®ãƒ†ãƒ³ãƒãƒ©ãƒªãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«ã‚­ãƒ£ãƒƒã‚·ãƒ¥ã•れã€åˆ†ç‰‡ã‚¢ãƒƒãƒ—ロード失敗時ã®å†è©¦è¡Œã«ä½¿ç”¨ã•れã¾ã™ã€‚無効ã«ã™ã‚‹ã¨ã€ã‚¹ãƒˆãƒªãƒ¼ãƒ è»¢é€ã«ã‚ˆã‚‹åˆ†ç‰‡ã‚¢ãƒƒãƒ—ロードã¯è¿½åŠ ã®ãƒ‡ã‚£ã‚¹ã‚¯å®¹é‡ã‚’使用ã—ã¾ã›ã‚“ãŒã€åˆ†ç‰‡ã‚¢ãƒƒãƒ—ロード失敗時ã«ã¯ã‚¢ãƒƒãƒ—ロード全体ãŒç›´ã¡ã«å¤±æ•—ã—ã¾ã™ã€‚", + "folderPropsTimeout": "ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªçµ±è¨ˆæƒ…å ±ã®æœ‰åŠ¹æœŸé™ï¼ˆç§’)", + "folderPropsTimeoutDes": "ユーザーãŒãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªçµ±è¨ˆæƒ…報(サイズã€ãƒ•ァイル数ãªã©ï¼‰ã‚’計算ã™ã‚‹éš›ã€çµæžœã®ã‚­ãƒ£ãƒƒã‚·ãƒ¥ã®æœ‰åŠ¹æœŸé™ã§ã™ã€‚", + "slaveAPIExpiration": "クライアントAPIç½²åæœ‰åŠ¹æœŸé™ï¼ˆç§’)", + "slaveAPIExpirationDes": "ホストãŒã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆAPIã«ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹éš›ã«ä½¿ç”¨ã™ã‚‹ç½²åã®æœ‰åŠ¹æœŸé™ã§ã™ã€‚", + "uploadSessionTimeout": "アップロードセッション有効期é™ï¼ˆç§’)", + "uploadSessionDes": "アップロードセッション有効期é™å†…ã§ã¯ã€ã‚µãƒãƒ¼ãƒˆã•れã¦ã„るストレージãƒãƒªã‚·ãƒ¼ã«ã¤ã„ã¦ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯æœªå®Œäº†ã®ã‚¿ã‚¹ã‚¯ã‚’å†é–‹ã§ãã¾ã™ã€‚設定å¯èƒ½ãªæœ€å¤§å€¤ã¯ã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã®ãƒ—ロãƒã‚¤ãƒ€ãƒ¼ã«ã‚ˆã£ã¦ç•°ãªã‚Šã¾ã™ã€‚", + "archiveTimeout": "サーãƒãƒ¼å´ãƒ‘ッケージダウンロードセッション有効期é™ï¼ˆç§’)", + "advanceOptions": "高度ãªè¨­å®š", + "emojiOptions": "絵文字オプション", + "addCategorize": "分類ã®è¿½åŠ ", + "category": "分類", + "searchQuery": "ファイル分類検索", + "importWopi": "WOPIアプリケーション設定ã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆ", + "wopiEndpoint": "WOPI Discovery エンドãƒã‚¤ãƒ³ãƒˆ", + "wopiDes": "WOPIプロトコルã«å¯¾å¿œã—ãŸã‚ªãƒ³ãƒ©ã‚¤ãƒ³ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆå‡¦ç†ã‚·ã‚¹ãƒ†ãƒ ã¨é€£æºã™ã‚‹ã“ã¨ã§ã€Cloudreveã®ã‚ªãƒ³ãƒ©ã‚¤ãƒ³ãƒ—レビューã¨ç·¨é›†æ©Ÿèƒ½ã‚’æ‹¡å¼µã—ã¾ã™ã€‚WOPIã‚µãƒ¼ãƒ“ã‚¹ã®æ¤œå‡ºã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’ã“ã“ã«è¨˜å…¥ã—ã¦ãã ã•ã„(例: <0>https://example.com/hosting/discovery)。詳細ã¯<1>å…¬å¼ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’å‚ç…§ã—ã¦ãã ã•ã„。", + "embeddedWebpageViewer": "ウェブアプリã®åŸ‹ã‚è¾¼ã¿", + "wopiViewer": "WOPIプロトコルアプリケーション", + "ext": "æ‹¡å¼µå­", + "invalidWopiActionMapping": "WOPIアクションマッピングãŒç„¡åйã§ã™", + "woapiActionMapping": "WOPIアクションマッピング", + "drawioHost": "DrawIOインスタンス", + "drawioHostDes": "ã”è‡ªèº«ã§æ§‹ç¯‰ã•れãŸã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’入力ã—ã¦ãã ã•ã„。", + "openInNew": "æ–°ã—ã„ウィンドウã§ç›´æŽ¥é–‹ã", + "openInNewDes": "ãƒã‚§ãƒƒã‚¯ã‚’入れるã¨ã€æ–°ã—ã„タブã§ã‚¢ãƒ—リãŒç›´æŽ¥é–‹ãã¾ã™ã€‚", + "maxSize": "最大ファイルサイズ", + "maxSizeDes": "ã“ã®ã‚¢ãƒ—リãŒã‚µãƒãƒ¼ãƒˆã™ã‚‹æœ€å¤§ãƒ•ァイルサイズã§ã™ã€‚「0ã€ã¨å…¥åŠ›ã™ã‚‹ã¨åˆ¶é™ãªã—ã«ãªã‚Šã¾ã™ã€‚サイズ超éŽã®å ´åˆã‚‚ファイルを開ã“ã†ã¨ã—ã¾ã™ãŒã€è­¦å‘ŠãŒè¡¨ç¤ºã•れã¾ã™ã€‚", + "srcEncodedVar": "URLエンコード済ã¿ã®ãƒ•ァイルBlob一時アクセスアドレス", + "srcVar": "ファイルBlob一時アクセスアドレス", + "srcBase64Var": "Base64エンコード済ã¿ã®ãƒ•ァイルBlob一時アクセスアドレス", + "nameEncodedVar": "URLエンコード済ã¿ã®ãƒ•ァイルå", + "versionEntityVar": "é–‹ã„ã¦ã„るファイルãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®Blob ID(空欄ã®å ´åˆã¯æœ€æ–°ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒé–‹ã‹ã‚Œã¦ã„ã¾ã™ï¼‰ã€‚", + "fileIdVar": "ファイルID", + "userIdVar": "ユーザーID(ログインã—ã¦ã„ãªã„å ´åˆã¯ç©ºæ¬„ã§ã™ï¼‰ã€‚", + "userDisplayNameVar": "URLエンコード済ã¿ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãƒ‹ãƒƒã‚¯ãƒãƒ¼ãƒ ", + "fileViewers": "ファイル閲覧アプリ", + "addViewer": "アプリを追加", + "viewerGroupTitle": "アプリグループ #{{index}}", + "viewerType": "タイプ", + "viewerPlatform": "プラットフォーム", + "viewerPlatformDes": "対応ã™ã‚‹ãƒ—ãƒ©ãƒƒãƒˆãƒ•ã‚©ãƒ¼ãƒ ã‚’é¸æŠžã—ã€ã‚¢ãƒ—リをãã®ãƒ—ラットフォームã§ã®ã¿è¡¨ç¤ºã—ã¾ã™ã€‚", + "viewerPlatformPC": " パソコン", + "viewerPlatformMobile": "モãƒã‚¤ãƒ«", + "viewerPlatformAll": "全対応", + "displayName": "åç§°", + "displayNameDes": "表示å(i18nextキー対応)", + "viewerEnabled": "有効化", + "newFileAction": "æ–°è¦ãƒ•ァイルマッピング", + "newFileActionDes": "マッピング追加後ã€ã€Œæ–°è¦ã€ãƒœã‚¿ãƒ³ã‚’クリックã™ã‚‹ã¨ã€ã“ã®ã‚¢ãƒ—リã®é¸æŠžè‚¢ãŒè¡¨ç¤ºã•れã¾ã™ã€‚", + "addNewFileAction": "マッピングを追加", + "builtinViewerType": "ビルトインアプリ", + "wopiViewerType": "WOPI", + "customViewerType": "カスタム", + "nMapping": "{{num}}個", + "editViewerTitle": "{{name}}を編集", + "builtInIconUrlDes": "ã“ã®ãƒ“ルトインアプリã«ã¯ãƒ‡ãƒ•ォルトã®ã‚¢ã‚¤ã‚³ãƒ³ãŒã‚りã¾ã™ã€‚アイコンアドレスを空欄ã«ã™ã‚‹ã¨ã€ãƒ‡ãƒ•ォルトã®ã‚¢ã‚¤ã‚³ãƒ³ãŒä½¿ç”¨ã•れã¾ã™ã€‚", + "viewerUrl": "アプリURL", + "viewerUrlDes": "カスタムアプリã®URLアドレスã§ã™ã€‚<0>マジック変数ã®ä½¿ç”¨ãŒå¯èƒ½ã§ã™ã€‚", + "addIcon": "アイコンを追加", + "exts": "æ‹¡å¼µå­ä¸€è¦§", + "icon": "アイコン", + "iconUrl": "アイコンアドレス", + "iconColor": "アイコンカラー", + "iconColorDark": "アイコンカラー(ダークモード)", + "fileIcons": "ファイルアイコン", + "builtinIcon": "内蔵アイコン", + "mimeMapping": "MIMEタイプマッピング", + "mimeMappingDes": "JSONå½¢å¼ã®MIMEタイプマッピングテーブル。キーã¯ãƒ•ァイル拡張å­ã€å€¤ã¯MIMEタイプã§ã™ã€‚Cloudreveã¯ãƒ•ァイル拡張å­ã¨ã“ã®è¨­å®šã«åŸºã¥ã„ã¦ãƒ•ァイルã®MIMEタイプを判断ã—ã¾ã™ã€‚", + "mapProvider": "地図プロãƒã‚¤ãƒ€ãƒ¼", + "mapProviderDes": "メディアã®ä½ç½®æƒ…報を表示ã™ã‚‹éš›ã«ä½¿ç”¨ã™ã‚‹åœ°å›³ãƒ—ロãƒã‚¤ãƒ€ãƒ¼ã€‚", + "mapGoogle": "Google Maps (Leaflet)", + "mapOpenStreetMap": "OpenStreetMap (Leaflet)", + "mapboxMap": "OpenStreetMap (Mapbox)", + "mapboxAccessToken": "Mapboxアクセストークン", + "mapboxAccessTokenDes": "<0>Mapboxコンソールã§ä½œæˆã—ãŸå…¬é–‹ã‚¢ã‚¯ã‚»ã‚¹ãƒˆãƒ¼ã‚¯ãƒ³ã€‚", + "tileType": "デフォルト地図タイプ", + "tileTypeDes": "Google Mapsã®ãƒ‡ãƒ•ォルト地図タイプ。", + "tileTypeTerrain": "地形", + "tileTypeSatellite": "衛星", + "tileTypeGeneral": "標準", + "maxPageSize": "最大ページサイズ", + "maxPageSizeDes": "ユーザーãŒèª¿æ•´ã§ãã‚‹1ページã‚ãŸã‚Šã®æœ€å¤§ãƒ•ァイル数を制é™ã—ã¾ã™ã€‚", + "maxRecursiveSearch": "最大å†å¸°æ¤œç´¢æ•°", + "maxRecursiveSearchDes": "ユーザーãŒãƒ•ァイル検索を行ã†éš›ã€æ¤œç´¢æ¸ˆã¿ãƒ•ァイル数ãŒã“ã®åˆ¶é™ã‚’è¶…ãˆã‚‹ã¨ã€æ¤œç´¢ã¯åœæ­¢ã—ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«è­¦å‘ŠãŒè¡¨ç¤ºã•れã¾ã™ã€‚", + "maxBatchSize": "最大一括æ“作数", + "maxBatchSizeDes": "ユーザーãŒä¸€åº¦ã«æ“作ã§ãる最大ファイル数。最上ä½éšŽå±¤ã®æ•°ã®ã¿ã‚’カウントã—ã€ã‚µãƒ–ディレクトリ内ã®ãƒ•ァイル数ã¯ã‚«ã‚¦ãƒ³ãƒˆã•れã¾ã›ã‚“。", + "defaultPagination": "ファイル一覧ページング方å¼", + "cursorPagination": "カーソルページング", + "cursorPaginationDes": "ユーザーãŒã‚¹ã‚¯ãƒ­ãƒ¼ãƒ«ã®æœ€å¾Œã¾ã§åˆ°é”ã™ã‚‹ã¨è‡ªå‹•çš„ã«ãƒ•ァイルãŒè¿½åŠ ãƒ­ãƒ¼ãƒ‰ã•れã¾ã™ã€‚大é‡ã®ãƒ•ァイルリストã«å¯¾ã—ã¦ã¯ãƒ‘フォーマンスãŒè‰¯ã„ã§ã™ãŒã€ç·ãƒšãƒ¼ã‚¸æ•°ã¯è¡¨ç¤ºã•れã¾ã›ã‚“。", + "offsetPagination": "従æ¥ã®ãƒšãƒ¼ã‚¸ãƒ³ã‚°", + "offsetPaginationDes": "ページ下部ã«ãƒšãƒ¼ã‚¸ãƒ³ã‚°ãƒŠãƒ“ゲーションãŒè¡¨ç¤ºã•れã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ç·ãƒšãƒ¼ã‚¸æ•°ã‚’確èªã—ã€ç‰¹å®šã®ãƒšãƒ¼ã‚¸ã«ã‚¸ãƒ£ãƒ³ãƒ—ã§ãã¾ã™ã€‚大é‡ã®ãƒ•ァイルリストã«å¯¾ã—ã¦ã¯ãƒ‘フォーマンスãŒåŠ£ã‚Šã¾ã™ã€‚", + "defaultPaginationDes": "上記ã®è¨­å®šã«é–¢ã‚らãšã€æ¤œç´¢æ™‚ã¯ã‚«ãƒ¼ã‚½ãƒ«ãƒšãƒ¼ã‚¸ãƒ³ã‚°ãŒå¼·åˆ¶çš„ã«ä½¿ç”¨ã•れã¾ã™ã€‚", + "publicResourceMaxAge": "é™çš„リソースキャッシュ有効期é™ï¼ˆç§’)", + "publicResourceMaxAgeDes": "ブラウザã¾ãŸã¯CDNãŒé™çš„リソースをキャッシュã™ã‚‹æœ‰åŠ¹æœŸé™ï¼ˆç§’)を指定ã—ã¾ã™ã€‚ファイルã€ã‚µãƒ ãƒã‚¤ãƒ«ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚¢ãƒã‚¿ãƒ¼ã«å½±éŸ¿ã—ã¾ã™ã€‚", + "cronDes": "{{des}}ã€ã“ã“ã«æ­£ã—ã„<0>Cronå¼ã‚’入力ã—ã¦ãã ã•ã„。Cloudreveã®å†èµ·å‹•å¾Œã«æœ‰åйã«ãªã‚Šã¾ã™ã€‚", + "entityCollectInterval": "ファイルBlob回åŽé–“éš”", + "entityCollectIntervalDes": "期é™åˆ‡ã‚Œã®ãƒ•ァイルBlobをスキャンã—ã¦å‰Šé™¤ã™ã‚‹é »åº¦ã‚’設定ã—ã¾ã™ã€‚", + "trashBinInterval": "ゴミ箱スキャン間隔", + "trashBinIntervalDes": "ã‚´ãƒŸç®±å†…ã®æœŸé™åˆ‡ã‚Œãƒ•ァイルをスキャンã—ã¦å‰Šé™¤ã™ã‚‹é »åº¦ã‚’設定ã—ã¾ã™ã€‚", + "logtoName": "ログイン方å¼å", + "logtoNameDes": "ユーザーã«è¡¨ç¤ºã•れるログイン方å¼åã§ã™ã€‚デフォルトã¯ã€ŒSSOã€ã§ã€i18nextキー値ã«å¯¾å¿œã—ã¦ã„ã¾ã™ã€‚", + "logtoDirectSSO": "直接サードパーティログイン", + "logtoDirectSSODes": "Logtoログイン画é¢ã‚’スキップã—ã€é€£æºæ¸ˆã¿ã®ã‚µãƒ¼ãƒ‰ãƒ‘ーティログインã¾ãŸã¯SSOã«ç›´æŽ¥ãƒªãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆã—ãŸã„å ´åˆã¯ã€ã“ã“ã«ã‚µãƒ¼ãƒ‰ãƒ‘ーティログインコãƒã‚¯ã‚¿ã®è­˜åˆ¥å­ã‚’入力ã—ã¦ãã ã•ã„。詳細ã¯<0>Logtoドキュメントをå‚ç…§ã—ã¦ãã ã•ã„。", + "logtoEndpoint": "Logtoエンドãƒã‚¤ãƒ³ãƒˆ", + "logtoEndpointDes": "アプリケーション管ç†ãƒ‘ãƒãƒ«ã§å–å¾—ã—ãŸLogtoエンドãƒã‚¤ãƒ³ãƒˆã‚¢ãƒ‰ãƒ¬ã‚¹ã§ã™ã€‚自身ã§ãƒ‡ãƒ—ロイã—ãŸã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã‚’使用ã§ãã¾ã™ã€‚", + "logtoKey": "アプリケーションキー", + "logtoKeyDes": "アプリケーション管ç†ãƒšãƒ¼ã‚¸ã§ä½œæˆã—ãŸã‚¢ãƒ—リケーションキーã§ã™ã€‚", + "logtoAppIDDes": "ã‚ãªãŸãŒä½œæˆã—ãŸã‚¢ãƒ—リケーションID。", + "logto": "Logto", + "logtoDes": "Logtoを使用ã™ã‚‹ã“ã¨ã§ã€Appleã€GitHubã€Microsoft Entra IDã€Googleã€SMSãªã©ã€ã‚ˆã‚Šå¤šãã®ã‚µãƒ¼ãƒ‰ãƒ‘ーティプラットフォームã¨ã®é€£æºãƒ­ã‚°ã‚¤ãƒ³ã‚’実ç¾ã§ãã¾ã™ã€‚Logto管ç†ãƒ‘ãƒãƒ«ã§ã€Œå¾“æ¥ã®ã‚¦ã‚§ãƒ–アプリã€ã‚’作æˆã—ã€<1>{{url}}を「リダイレクトURIã€ã«è¿½åŠ ã—ã¦ãã ã•ã„。", + "thirdPartySignIn": "サードパーティログイン", + "logo": "ロゴ", + "logoDes": "ロゴ画åƒã®URL。左上ã«è¡¨ç¤ºã•れã¾ã™ã€‚ダークモードã¨ãƒ©ã‚¤ãƒˆãƒ¢ãƒ¼ãƒ‰ãれãžã‚Œã«å¯¾å¿œã—ãŸãƒ­ã‚´ã‚’ã”用æ„ãã ã•ã„。", + "dark": "ダークモード", + "light": "ライトモード", + "tosUrl": "利用è¦ç´„リンク", + "tosUrlDes": "ユーザーログイン・登録ページã®ãƒ•ッターã«è¡¨ç¤ºã•れã¾ã™ã€‚空欄ã®å ´åˆã¯è¡¨ç¤ºã•れã¾ã›ã‚“。", + "privacyUrl": "プライãƒã‚·ãƒ¼ãƒãƒªã‚·ãƒ¼ãƒªãƒ³ã‚¯", + "privacyUrlDes": "ユーザーログイン・登録ページã®ãƒ•ッターã«è¡¨ç¤ºã•れã¾ã™ã€‚空欄ã®å ´åˆã¯è¡¨ç¤ºã•れã¾ã›ã‚“。", + "addSecondary": "代替サイトURLã®è¿½åŠ ", + "secondarySiteURL": "代替", + "secondaryDes": "ä»–ã®ä»£æ›¿ã‚µã‚¤ãƒˆURLを追加ã§ãã¾ã™ã€‚Cloudreveã¯ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒå®Ÿéš›ã«ã‚¢ã‚¯ã‚»ã‚¹ã—ãŸURLã«åŸºã¥ã„ã¦ã€ä½¿ç”¨ã™ã‚‹ã‹ã©ã†ã‹ã‚’自動的ã«åˆ¤æ–­ã—ã¾ã™ã€‚", + "primarySiteURL": "主è¦", + "primarySiteURLDes": "主è¦ã‚µã‚¤ãƒˆURLã¯å¤–部サービスã¨ã®é€šä¿¡ã¨ã‚³ãƒ¼ãƒ«ãƒãƒƒã‚¯ã®å—信(例:ストレージプロãƒã‚¤ãƒ€ãƒ¼ï¼‰ã«ä½¿ç”¨ã•れã¾ã™ã€‚インターãƒãƒƒãƒˆã‹ã‚‰ã‚¢ã‚¯ã‚»ã‚¹å¯èƒ½ãªURLを使用ã—ã¦ãã ã•ã„。", + "revert": "変更をキャンセル", + "saved": "設定ãŒå¤‰æ›´ã•れã¾ã—ãŸ", + "save": "ä¿å­˜", + "basicInformation": "基本情報", + "mainTitle": "サイトå", + "mainTitleDes": "サイトåã§ã™ã€‚", + "siteDescription": "サイト説明", + "siteDescriptionDes": "サイトã®èª¬æ˜Žæƒ…å ±ã§ã™ã€‚共有ページã®ã‚µãƒžãƒªãƒ¼ã«è¡¨ç¤ºã•れる場åˆãŒã‚りã¾ã™ã€‚", + "siteURL": "サイトURL", + "customFooterHTML": "フッターコード", + "customFooterHTMLDes": "ãƒšãƒ¼ã‚¸ä¸‹éƒ¨ã«æŒ¿å…¥ã™ã‚‹ã‚«ã‚¹ã‚¿ãƒ HTMLコードã§ã™ã€‚", + "announcement": "サイトãŠçŸ¥ã‚‰ã›", + "announcementDes": "ログイン済ã¿ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«è¡¨ç¤ºã™ã‚‹ãŠçŸ¥ã‚‰ã›ã§ã™ã€‚空欄ã®å ´åˆã¯è¡¨ç¤ºã•れã¾ã›ã‚“。ã“ã®å†…容を変更ã™ã‚‹ã¨ã€å…¨ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«ãŠçŸ¥ã‚‰ã›ãŒè¡¨ç¤ºã•れã¾ã™ã€‚", + "supportHTML": "HTMLコードをサãƒãƒ¼ãƒˆ", + "branding": "アイコン", + "smallIcon": "å°ã‚¢ã‚¤ã‚³ãƒ³", + "smallIconDes": "å°ã‚¢ã‚¤ã‚³ãƒ³ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã§ã™ï¼ˆicoã¾ãŸã¯svgå½¢å¼ï¼‰ã€‚ã“ã®ã‚¢ã‚¤ã‚³ãƒ³ã¯ãƒ–ラウザã®ã‚¿ãƒ–ã€ãƒ–ックマークã€ãƒ‡ã‚¹ã‚¯ãƒˆãƒƒãƒ—ショートカットãªã©ã§è¡¨ç¤ºã•れã¾ã™ã€‚", + "mediumIcon": "中アイコン", + "mediumIconDes": "192x192 ã®ä¸­ã‚¢ã‚¤ã‚³ãƒ³ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã§ã™ï¼ˆpngå½¢å¼ï¼‰ã€‚", + "largeIcon": "大アイコン", + "largeIconDes": "512x512 ã®å¤§ã‚¢ã‚¤ã‚³ãƒ³ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã§ã™ï¼ˆpngå½¢å¼ï¼‰ã€‚ã“ã®ã‚¢ã‚¤ã‚³ãƒ³ã¯iOSクライアントã§ã‚µã‚¤ãƒˆã‚’切り替ãˆã‚‹éš›ã«ã‚‚表示ã•れã¾ã™ã€‚", + "displayMode": "表示モード", + "displayModeDes": "PWAアプリ追加後ã®è¡¨ç¤ºãƒ¢ãƒ¼ãƒ‰ã§ã™ã€‚", + "themeColor": "テーマカラー", + "themeColorDes": "CSSカラー値(PWAèµ·å‹•ç”»é¢ã€ã‚³ãƒ³ãƒ†ãƒ³ãƒ„ページã®çŠ¶æ…‹ãƒãƒ¼ã€ã‚¢ãƒ‰ãƒ¬ã‚¹ãƒãƒ¼ã®è‰²ã«å½±éŸ¿ã—ã¾ã™ï¼‰", + "backgroundColor": "背景色", + "backgroundColorDes": "CSSカラー値", + "hint": "ヒント", + "webauthnNoHttps": "WebAuthnを使用ã™ã‚‹ã«ã¯ã€ã‚µã‚¤ãƒˆã§HTTPSを有効ã«ã—ã€ã€Œãƒ‘ラメーター設定ã€-「サイト情報ã€-「サイトURLã€ã§ã‚‚HTTPSを使用ã—ã¦ã„ã‚‹ã“ã¨ã‚’確èªã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚", + "accountManagement": "登録ã¨ãƒ­ã‚°ã‚¤ãƒ³", + "allowNewRegistrations": "æ–°è¦ãƒ¦ãƒ¼ã‚¶ãƒ¼ç™»éŒ²ã‚’許å¯ã™ã‚‹", + "allowNewRegistrationsDes": "オフã«ã™ã‚‹ã¨ã€ãƒ•ロントエンドã‹ã‚‰æ–°è¦ãƒ¦ãƒ¼ã‚¶ãƒ¼ç™»éŒ²ã§ããªããªã‚Šã¾ã™ã€‚", + "emailActivation": "メールèªè¨¼", + "emailActivationDes": "オンã«ã™ã‚‹ã¨ã€æ–°è¦ãƒ¦ãƒ¼ã‚¶ãƒ¼ç™»éŒ²ã«ã¯ãƒ¡ãƒ¼ãƒ«å†…ã®ã‚¢ã‚¯ãƒ†ã‚£ãƒ™ãƒ¼ã‚·ãƒ§ãƒ³ãƒªãƒ³ã‚¯ã‚’クリックã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚<0>メールé€ä¿¡è¨­å®šãŒæ­£ã—ã設定ã•れã¦ã„ã‚‹ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。設定ãŒé–“é•ã£ã¦ã„ã‚‹ã¨ã€ã‚¢ã‚¯ãƒ†ã‚£ãƒ™ãƒ¼ã‚·ãƒ§ãƒ³ãƒ¡ãƒ¼ãƒ«ãŒé€ä¿¡ã•れã¾ã›ã‚“。", + "captchaForSignup": "登録時ã®CAPTCHA", + "captchaForSignupDes": "登録フォームã§CAPTCHAを有効ã«ã™ã‚‹ã‹ã©ã†ã‹ã€‚", + "captchaForLogin": "ログイン時ã®CAPTCHA", + "captchaForLoginDes": "ログインフォームã§CAPTCHAを有効ã«ã™ã‚‹ã‹ã©ã†ã‹ã€‚", + "captchaForReset": "パスワードå†è¨­å®šæ™‚ã®CAPTCHA", + "captchaForResetDes": "パスワードå†è¨­å®šãƒ•ォームã§CAPTCHAを有効ã«ã™ã‚‹ã‹ã©ã†ã‹ã€‚", + "captchaForAbuseReport": "䏿­£åˆ©ç”¨å ±å‘Šæ™‚ã®CAPTCHA", + "captchaForAbuseReportDes": "䏿­£åˆ©ç”¨å ±å‘Šãƒ•ォームã§CAPTCHAを有効ã«ã™ã‚‹ã‹ã©ã†ã‹ã€‚", + "webauthnDes": "ユーザーãŒç™»éŒ²æ¸ˆã¿ã®ãƒãƒ¼ãƒ‰ã‚¦ã‚§ã‚¢èªè¨¼ãƒ‡ãƒã‚¤ã‚¹ï¼ˆé¡”èªè¨¼ã€æŒ‡ç´‹èªè¨¼ã€USBキーãªã©ï¼‰ã‚’使用ã—ã¦ãƒ­ã‚°ã‚¤ãƒ³ã™ã‚‹ã“ã¨ã‚’許å¯ã™ã‚‹ã‹ã©ã†ã‹ã€‚サイトã§HTTPSを有効ã«ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚", + "webauthn": "パスキーを使用ã—ã¦ãƒ­ã‚°ã‚¤ãƒ³", + "defaultSymbolics": "デフォルトã®å…±æœ‰ãƒªãƒ³ã‚¯", + "defaultSymbolicsDes": "æ–°è¦ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒ«ãƒ¼ãƒˆãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«ãƒ‡ãƒ•ォルトã§å­˜åœ¨ã™ã‚‹å…±æœ‰ãƒªãƒ³ã‚¯ã®ã‚·ãƒ§ãƒ¼ãƒˆã‚«ãƒƒãƒˆã§ã™ã€‚共有リンクをIDã§æ¤œç´¢ã§ãã¾ã™ã€‚<0>共有リストã®å·¦å´ã«IDãŒè¡¨ç¤ºã•れã¾ã™ã€‚", + "searchShare": "共有IDを検索...", + "defaultGroup": "デフォルトユーザーグループ", + "defaultGroupDes": "ユーザー登録後ã®åˆæœŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—", + "testMailSent": "テストメールをé€ä¿¡ã—ã¾ã—ãŸ", + "testSMTPSettings": "é€ä¿¡ãƒ†ã‚¹ãƒˆ", + "testSMTPTooltip": "Cloudreveã¯ã€ç¾åœ¨ã®SMTP設定を使用ã—ã¦ãƒ†ã‚¹ãƒˆãƒ¡ãƒ¼ãƒ«ã‚’é€ä¿¡ã—ã¾ã™ã€‚テストå‰ã«è¨­å®šã‚’ä¿å­˜ã™ã‚‹å¿…è¦ã¯ã‚りã¾ã›ã‚“。", + "recipient": "å—信者アドレス", + "send": "é€ä¿¡", + "smtp": "é€ä¿¡", + "senderName": "é€ä¿¡è€…å", + "senderNameDes": "メールã«è¡¨ç¤ºã•れるé€ä¿¡è€…ã®åå‰ã§ã™ã€‚", + "senderAddress": "é€ä¿¡è€…メールアドレス", + "senderAddressDes": "é€ä¿¡ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã§ã™ã€‚", + "smtpServer": "SMTPサーãƒãƒ¼", + "smtpServerDes": "é€ä¿¡ã‚µãƒ¼ãƒãƒ¼ã‚¢ãƒ‰ãƒ¬ã‚¹ï¼ˆãƒãƒ¼ãƒˆç•ªå·ã‚’除ã)。", + "smtpPort": "SMTPãƒãƒ¼ãƒˆ", + "smtpPortDes": "é€ä¿¡ã‚µãƒ¼ãƒãƒ¼ã‚¢ãƒ‰ãƒ¬ã‚¹ã®ãƒãƒ¼ãƒˆç•ªå·ã§ã™ã€‚", + "smtpUsername": "SMTPユーザーå", + "smtpUsernameDes": "é€ä¿¡ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼åã§ã™ã€‚通常ã¯ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã¨åŒã˜ã§ã™ã€‚", + "smtpPassword": "SMTPパスワード", + "smtpPasswordDes": "é€ä¿¡ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã®ãƒ‘スワードã§ã™ã€‚", + "replyToAddress": "返信用メールアドレス", + "replyToAddressDes": "ユーザーãŒã‚·ã‚¹ãƒ†ãƒ ã‹ã‚‰é€ä¿¡ã•れãŸãƒ¡ãƒ¼ãƒ«ã«è¿”ä¿¡ã™ã‚‹å ´åˆã«ã€è¿”ä¿¡ã‚’å—ã‘å–るメールアドレスã§ã™ã€‚", + "enforceSSL": "SSL接続を強制", + "enforceSSLDes": "SSLæš—å·åŒ–接続を強制的ã«ä½¿ç”¨ã™ã‚‹ã‹è¨­å®šã—ã¾ã™ã€‚メールé€ä¿¡ã«å¤±æ•—ã™ã‚‹å ´åˆã¯ã€ã“ã®é …目をオフã«ã—ã¦ãã ã•ã„。Cloudreveã¯STARTTLSを使用ã—ã€æš—å·åŒ–接続を使用ã™ã‚‹ã‹ã©ã†ã‹ã‚’判断ã—ã¾ã™ã€‚", + "smtpTTL": "SMTP接続有効期é™(ç§’)", + "smtpTTLDes": "有効期é™å†…ã®SMTP接続ã¯ã€æ–°ã—ã„メールé€ä¿¡ãƒªã‚¯ã‚¨ã‚¹ãƒˆã§å†åˆ©ç”¨ã•れã¾ã™ã€‚", + "emailTemplates": "メールテンプレート", + "activateNewUser": "æ–°è¦ãƒ¦ãƒ¼ã‚¶ãƒ¼æœ‰åŠ¹åŒ–", + "resetPassword": "パスワードリセット", + "sendTestEmail": "テストメールé€ä¿¡", + "transportation": "転é€", + "workerNum": "ワーカー数", + "workerNumDes": "ホストノードã®ã‚¿ã‚¹ã‚¯ã‚­ãƒ¥ãƒ¼ã§ä¸¦åˆ—実行ã§ãる最大タスク数ã§ã™ã€‚ä¿å­˜å¾Œã€Cloudreveã‚’å†èµ·å‹•ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚", + "tempFolder": "テンãƒãƒ©ãƒªãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒª", + "tempFolderDes": "è§£å‡ã€åœ§ç¸®ãªã©ã®ã‚¿ã‚¹ã‚¯ã§ç”Ÿæˆã•れãŸä¸€æ™‚ファイルをä¿å­˜ã™ã‚‹ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®ãƒ‘スã§ã™ã€‚", + "textEditMaxSize": "ドキュメントオンライン編集最大サイズ", + "textEditMaxSizeDes": "オンラインã§ç·¨é›†å¯èƒ½ãªãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆãƒ•ã‚¡ã‚¤ãƒ«ã®æœ€å¤§ã‚µã‚¤ã‚ºã§ã™ã€‚ã“ã®ã‚µã‚¤ã‚ºã‚’è¶…ãˆã‚‹ãƒ•ァイルã¯ã‚ªãƒ³ãƒ©ã‚¤ãƒ³ã§ç·¨é›†ã§ãã¾ã›ã‚“。ã“ã®è¨­å®šã¯ã€ãƒ—レーンテキストファイルã€ã‚³ãƒ¼ãƒ‰ãƒ•ァイルã€Officeドキュメント(WOPI)ãªã©ã®Webオンラインエディターã«é©ç”¨ã•れã¾ã™ã€‚", + "resetConnection": "ã‚¢ãƒƒãƒ—ãƒ­ãƒ¼ãƒ‰æ¤œè¨¼å¤±æ•—æ™‚ã®æŽ¥ç¶šå¼·åˆ¶ãƒªã‚»ãƒƒãƒˆ", + "resetConnectionDes": "有効ã«ã™ã‚‹ã¨ã€ãƒãƒªã‚·ãƒ¼ã€ã‚¢ãƒã‚¿ãƒ¼ãªã©ã®ãƒ‡ãƒ¼ã‚¿ã‚¢ãƒƒãƒ—ロード検証ã«å¤±æ•—ã—ãŸå ´åˆã€ã‚µãƒ¼ãƒãƒ¼ã¯æŽ¥ç¶šã‚’強制的ã«ãƒªã‚»ãƒƒãƒˆã—ã¾ã™ã€‚", + "batchDownload": "ã¾ã¨ã‚ã¦ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰", + "previewURL": "プレビューリンク", + "cannotDeleteDefaultTheme": "デフォルトã®é…色テーマã¯å‰Šé™¤ã§ãã¾ã›ã‚“", + "themeConfig": "カラー設定", + "actions": "æ“作", + "wrongFormat": "䏿­£ãªå½¢å¼", + "avatar": "ã‚¢ãƒã‚¿ãƒ¼", + "gravatarServer": "Gravatarサーãƒãƒ¼", + "gravatarServerDes": "Gravatarサーãƒãƒ¼ã‚¢ãƒ‰ãƒ¬ã‚¹ï¼ˆå›½å†…ミラーã®ä½¿ç”¨ã‚‚å¯èƒ½ã§ã™ï¼‰ã€‚", + "avatarFilePath": "ã‚¢ãƒã‚¿ãƒ¼ä¿å­˜ãƒ‘ス", + "avatarFilePathDes": "ユーザーãŒã‚¢ãƒƒãƒ—ロードã—ãŸã‚«ã‚¹ã‚¿ãƒ ã‚¢ãƒã‚¿ãƒ¼ã®ä¿å­˜ãƒ‘ス(Cloudreveデータディレクトリã‹ã‚‰ã®ç›¸å¯¾ãƒ‘ス)", + "avatarSize": "ã‚¢ãƒã‚¿ãƒ¼ãƒ•ァイルサイズ制é™", + "avatarSizeDes": "ユーザーãŒã‚¢ãƒƒãƒ—ロードã§ãã‚‹ã‚¢ãƒã‚¿ãƒ¼ãƒ•ã‚¡ã‚¤ãƒ«ã®æœ€å¤§ã‚µã‚¤ã‚ºã€‚", + "avatarImageSize": "ç”»åƒã‚µã‚¤ã‚º (px)", + "avatarImageSizeDes": "ユーザーãŒã‚¢ãƒƒãƒ—ロードã—ãŸã‚¢ãƒã‚¿ãƒ¼ã¯æŒ‡å®šã‚µã‚¤ã‚ºã«èª¿æ•´ã•れã¾ã™ï¼ˆå˜ä½ï¼šãƒ”クセル)。", + "filePreview": "ファイルプレビュー", + "thumbnails": "サムãƒã‚¤ãƒ«", + "thumbnailDoc": "サムãƒã‚¤ãƒ«è¨­å®šã«é–¢ã™ã‚‹è©³ç´°ã¯ã€<0>å…¬å¼ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’å‚ç…§ã—ã¦ãã ã•ã„。", + "thumbnailDocLink": "https://docs.cloudreve.org/usage/thumbnails", + "thumbnailBasic": "基本設定", + "generators": "サムãƒã‚¤ãƒ«ç”Ÿæˆå™¨", + "thumbMaxSize": "最大元ファイルサイズ", + "thumbMaxSizeDes": "サムãƒã‚¤ãƒ«ã‚’生æˆã§ãる最大元ã®ãƒ•ァイルサイズ。ã“ã®ã‚µã‚¤ã‚ºã‚’è¶…ãˆã‚‹ãƒ•ァイルã¯ã‚µãƒ ãƒã‚¤ãƒ«ãŒç”Ÿæˆã•れã¾ã›ã‚“。", + "generatorProxyWarning": "デフォルトã§ã¯ã€ãƒã‚¤ãƒ†ã‚£ãƒ–以外ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã¯ã€Œã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ãƒã‚¤ãƒ†ã‚£ãƒ–ã€ã‚¸ã‚§ãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ã®ã¿ã‚’使用ã—ã¾ã™ã€‚「ジェãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ãƒ—ãƒ­ã‚­ã‚·ã€æ©Ÿèƒ½ã‚’ストレージãƒãƒªã‚·ãƒ¼è¨­å®šãƒšãƒ¼ã‚¸ã§æœ‰åйã«ã™ã‚‹ã¨ã€ã‚µãƒ¼ãƒ‰ãƒ‘ーティã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã®ã‚µãƒ ãƒã‚¤ãƒ«ç”Ÿæˆæ©Ÿèƒ½ã‚’æ‹¡å¼µã§ãã¾ã™ã€‚詳細ã¯<0>å…¬å¼ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’å‚ç…§ã—ã¦ãã ã•ã„。", + "policyBuiltin": "ストレージãƒãƒªã‚·ãƒ¼ãƒã‚¤ãƒ†ã‚£ãƒ–", + "policyBuiltinDes": "ストレージプロãƒã‚¤ãƒ€ãƒ¼ã®ãƒã‚¤ãƒ†ã‚£ãƒ–ãªç”»åƒå‡¦ç†ã‚¤ãƒ³ã‚¿ãƒ¼ãƒ•ェースを使用ã—ã¾ã™ã€‚ãƒã‚¤ãƒ†ã‚£ãƒ–ãŠã‚ˆã³S3ãƒãƒªã‚·ãƒ¼ã§ã¯ã“ã®ã‚¸ã‚§ãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ã¯ä½¿ç”¨ã§ããšã€è‡ªå‹•çš„ã«ä»–ã®ã‚¸ã‚§ãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ãŒä½¿ç”¨ã•れã¾ã™ã€‚ä»–ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã«ã¤ã„ã¦ã¯ã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼è¨­å®šãƒšãƒ¼ã‚¸ã§è¨±å¯ã™ã‚‹æ‹¡å¼µå­ã‚’設定ã—ã¦ãã ã•ã„。", + "cloudreveBuiltin": "Cloudreve内蔵", + "cloudreveBuiltinDes": "Cloudreve内蔵ã®ç”»åƒå‡¦ç†æ©Ÿèƒ½ã‚’使用ã—ã¾ã™ã€‚PNGã€JPEGã€GIFå½¢å¼ã®ç”»åƒã®ã¿ã‚µãƒãƒ¼ãƒˆã—ã¾ã™ã€‚", + "libreOffice": "LibreOffice", + "libreOfficeDes": "LibreOfficeを使用ã—ã¦Office文書ã®ã‚µãƒ ãƒã‚¤ãƒ«ã‚’生æˆã—ã¾ã™ã€‚ã“ã®ã‚¸ã‚§ãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ã¯ã€ä»–ã®ã„ãšã‚Œã‹ã®ç”»åƒã‚¸ã‚§ãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ï¼ˆCloudreve内蔵ã¾ãŸã¯VIPS)ã«ä¾å­˜ã—ã¾ã™ã€‚", + "libraw": "LibRaw / DCRaw", + "librawDes": "LibRaw 付属㮠DCRaw サンプルプログラムã€ã¾ãŸã¯å…ƒã® DCRaw プログラムを使用ã—㦠RAW ç”»åƒã®ã‚µãƒ ãƒã‚¤ãƒ«ã‚’生æˆã—ã¾ã™ã€‚", + "vips": "VIPS", + "vipsDes": "libvipsを使用ã—ã¦ã‚µãƒ ãƒã‚¤ãƒ«ç”»åƒã‚’処ç†ã—ã¾ã™ã€‚より多ãã®ç”»åƒå½¢å¼ã‚’サãƒãƒ¼ãƒˆã—ã€ãƒªã‚½ãƒ¼ã‚¹æ¶ˆè²»ãŒå°‘ãªã„ã§ã™ã€‚", + "thumbDependencyWarning": "LibreOfficeã¾ãŸã¯æ¥½æ›²ã‚«ãƒãƒ¼ç”»åƒã‚¸ã‚§ãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ã¯ã€Cloudreve内蔵ã¾ãŸã¯VIPSジェãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ã«ä¾å­˜ã—ã¾ã™ã€‚ã„ãšã‚Œã‹ã®ã‚¸ã‚§ãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ã‚’有効ã«ã—ã¦ãã ã•ã„。", + "ffmpeg": "FFmpeg", + "ffmpegDes": "FFmpegを使用ã—ã¦ãƒ“デオã®ã‚µãƒ ãƒã‚¤ãƒ«ã‚’生æˆã—ã¾ã™ã€‚", + "executable": "実行ファイル", + "executableDes": "サードパーティジェãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ã®å®Ÿè¡Œãƒ•ァイルã®ãƒ‘スã¾ãŸã¯ã‚³ãƒžãƒ³ãƒ‰ã€‚", + "executableTest": "テスト", + "executableTestSuccess": "ジェãƒãƒ¬ãƒ¼ã‚¿ãƒ¼æ­£å¸¸ã€ãƒãƒ¼ã‚¸ãƒ§ãƒ³ï¼š{{version}}", + "generatorExts": "使用å¯èƒ½ãªæ‹¡å¼µå­", + "generatorExtsDes": "ã“ã®ã‚¸ã‚§ãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ã§ä½¿ç”¨å¯èƒ½ãªãƒ•ァイル拡張å­ã®ãƒªã‚¹ãƒˆã§ã™ã€‚複数指定ã™ã‚‹å ´åˆã¯åŠè§’カンマ(,)ã§åŒºåˆ‡ã£ã¦ãã ã•ã„。", + "ffmpegSeek": "サムãƒã‚¤ãƒ«ã‚­ãƒ£ãƒ—ãƒãƒ£ä½ç½®", + "ffmpegSeekDes": "サムãƒã‚¤ãƒ«ã®ã‚­ãƒ£ãƒ—ãƒãƒ£æ™‚間を定義ã—ã¾ã™ã€‚生æˆå‡¦ç†ã‚’高速化ã™ã‚‹ãŸã‚ã«ã€å°ã•ã„å€¤ã‚’é¸æŠžã™ã‚‹ã“ã¨ã‚’ãŠå‹§ã‚ã—ã¾ã™ã€‚ビデオã®å®Ÿéš›ã®é•·ã•ã‚’è¶…ãˆã‚‹ã¨ã€ã‚µãƒ ãƒã‚¤ãƒ«ã®ã‚­ãƒ£ãƒ—ãƒãƒ£ã«å¤±æ•—ã™ã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚", + "ffmpegExtraArgs": "追加入力引数", + "ffmpegExtraArgsDes": "FFmpeg 呼ã³å‡ºã—時ã«è¿½åŠ å…¥åŠ›ã™ã‚‹å¼•数。", + "generatorProxy": "ジェãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ä»£ç†", + "enableThumbProxy": "ジェãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ä»£ç†ã®ä½¿ç”¨", + "proxyPolicyList": "代ç†ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã®èµ·å‹•", + "proxyPolicyListDes": "è¤‡æ•°é¸æŠžå¯ã€‚é¸æŠžã—ãŸå ´åˆã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ãŒãƒã‚¤ãƒ†ã‚£ãƒ–ã®ã‚µãƒ ãƒã‚¤ãƒ«ç”Ÿæˆã‚’サãƒãƒ¼ãƒˆã—ã¦ã„ãªã„タイプã¯ã€Cloudreve代ç†ã«ã‚ˆã£ã¦ç”Ÿæˆã•れã¾ã™ã€‚", + "thumbWidth": "最大幅", + "thumbHeight": "最大高ã•", + "thumbSuffix": "Blobファイル拡張å­", + "thumbSuffixDes": "生æˆã•れãŸã‚µãƒ ãƒã‚¤ãƒ«Blobã¯ã€å…ƒã®Blobã«è¿½åŠ ã•れãŸã‚µãƒ•ィックスã§ã™ã€‚", + "thumbFormat": "サムãƒã‚¤ãƒ«å½¢å¼", + "thumbFormatDes": "優先使用ã™ã‚‹ã‚µãƒ ãƒã‚¤ãƒ«å½¢å¼ã€‚ジェãƒãƒ¬ãƒ¼ã‚¿ãƒ¼ãŒã‚µãƒãƒ¼ãƒˆã—ã¦ã„ãªã„å ´åˆã€jpgå½¢å¼ã«è‡ªå‹•çš„ã«é™æ ¼ã—ã¾ã™ã€‚", + "thumbQuality": "ç”»åƒå“質", + "thumbQualityDes": "圧縮å“質パーセンテージ。jpgã¨webpエンコーディングã®ã¿ã«æœ‰åйã§ã™ã€‚", + "thumbGC": "生æˆå®Œäº†å¾Œã«ãƒ¡ãƒ¢ãƒªã‚’峿™‚解放", + "captcha": "CAPTCHA", + "captchaType": "CAPTCHAã®ç¨®é¡ž", + "captchaTypeDes": "CAPTCHAã®ç¨®é¡žã¨CAPTCHAサービスプロãƒã‚¤ãƒ€ãƒ¼ã‚’é¸æŠžã—ã¾ã™ã€‚", + "plainCaptcha": "ç”»åƒ", + "reCaptchaV2": "reCAPTCHA V2", + "turnstile": "Cloudflare Turnstile", + "turnstileSiteKey": "サイトキー", + "turnstileSiteKSecret": "キー", + "cap": "Cap", + "capInstanceURL": "インスタンス URL", + "capInstanceURLDes": "自身ã§ãƒ›ã‚¹ãƒˆã—ã¦ã„ã‚‹ Cap サーãƒãƒ¼ã® URL。詳細ã«ã¤ã„ã¦ã¯ã€<0>スタンドアロンモードドキュメント ã‚’å‚ç…§ã—ã¦ãã ã•ã„。", + "capSiteKey": "サイトキー", + "capSiteKeyDes": "Cap サーãƒãƒ¼ãƒ€ãƒƒã‚·ãƒ¥ãƒœãƒ¼ãƒ‰ã‹ã‚‰å–å¾—ã—ãŸã‚µã‚¤ãƒˆã‚­ãƒ¼ã€‚", + "capSecretKey": "シークレットキー", + "capSecretKeyDes": "Cap サーãƒãƒ¼ãƒ€ãƒƒã‚·ãƒ¥ãƒœãƒ¼ãƒ‰ã‹ã‚‰å–å¾—ã—ãŸã‚·ãƒ¼ã‚¯ãƒ¬ãƒƒãƒˆã‚­ãƒ¼ã€‚", + "capAssetServer": "é™çš„リソースソース", + "capAssetServerDes": "Capèªè¨¼ã‚³ãƒ¼ãƒ‰ã®é™çš„リソースã®èª­ã¿è¾¼ã¿ã‚½ãƒ¼ã‚¹ã‚’é¸æŠžã—ã¾ã™ã€‚自己デプロイサーãƒãƒ¼ã‚’使用ã™ã‚‹ã«ã¯ã‚µãƒ¼ãƒãƒ¼å´ã§ç’°å¢ƒå¤‰æ•°ã‚’設定ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€<0>é™çš„リソースサービスを有効ã«ã™ã‚‹ã‚’å‚ç…§ã—ã¦ãã ã•ã„。", + "capAssetServerJsdelivr": "jsDelivr CDN", + "capAssetServerUnpkg": "unpkg CDN", + "capAssetServerInstance": "自己デプロイサーãƒãƒ¼", + "captchaProvider": "èªè¨¼ã‚³ãƒ¼ãƒ‰ã‚¿ã‚¤ãƒ—", + "captchaWidth": "å¹…", + "captchaHeight": "高ã•", + "captchaLength": "é•·ã•", + "captchaLengthDes": "キャプãƒãƒ£å†…ã®æ–‡å­—ã®é•·ã•。", + "captchaMode": "モード", + "captchaModeNumber": "æ•°å­—", + "captchaModeLetter": "アルファベット", + "captchaModeMath": "ç®—æ•°", + "captchaModeNumberLetter": "æ•°å­—+アルファベット", + "captchaElement": "èªè¨¼ã‚³ãƒ¼ãƒ‰ã®å½¢å¼", + "complexOfNoiseText": "ノイズ文字ã®å¼·åŒ–", + "complexOfNoiseDot": "ノイズ点ã®å¼·åŒ–", + "showHollowLine": "空心線を使用", + "showNoiseDot": "ノイズを使用", + "showNoiseText": "ノイズ文字を使用", + "showSlimeLine": "波線を使用", + "showSineLine": "正弦波を使用", + "siteKey": "サイトキー", + "siteKeyDes": "<0>アプリケーション管ç†ç”»é¢ã§å–å¾—ã—ãŸã‚µã‚¤ãƒˆã‚­ãƒ¼ã€‚", + "siteSecret": "シークレット", + "siteSecretDes": "<0>アプリケーション管ç†ç”»é¢ã§å–å¾—ã—ãŸã‚·ãƒ¼ã‚¯ãƒ¬ãƒƒãƒˆã€‚", + "secretID": "SecretId", + "secretIDDes": "<0>アクセスキー画é¢ã§å–å¾—ã—ãŸSecretId", + "secretKey": "SecretKey", + "secretKeyDes": "<0>アクセスキー画é¢ã§å–å¾—ã—ãŸSecretKey", + "tCaptchaAppID": "APPID", + "tCaptchaAppIDDes": "<0>ç”»åƒèªè¨¼ç”»é¢ã§å–å¾—ã—ãŸAPPID", + "tCaptchaSecretKey": "App Secret Key", + "tCaptchaSecretKeyDes": "<0>ç”»åƒèªè¨¼ç”»é¢ã§å–å¾—ã—ãŸApp Secret Key", + "staticResourceCache": "é™çš„公開リソースキャッシュ", + "staticResourceCacheDes": "公開アクセスå¯èƒ½ãªé™çš„リソース(例:ローカルãƒãƒªã‚·ãƒ¼ç›´ãƒªãƒ³ã‚¯ã€ãƒ•ァイルダウンロードリンク)ã®ã‚­ãƒ£ãƒƒã‚·ãƒ¥æœ‰åŠ¹æœŸé™", + "creditSystem": "ãƒã‚¤ãƒ³ãƒˆã‚·ã‚¹ãƒ†ãƒ ", + "creditAndVAS": "ãƒã‚¤ãƒ³ãƒˆã¨ä»˜åŠ ä¾¡å€¤ã‚µãƒ¼ãƒ“ã‚¹", + "enableCredit": "ãƒã‚¤ãƒ³ãƒˆã‚·ã‚¹ãƒ†ãƒ æœ‰åŠ¹åŒ–", + "enableCreditDes": "ãƒã‚¤ãƒ³ãƒˆã‚·ã‚¹ãƒ†ãƒ ã‚’有効化ã—ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒå…±æœ‰ãƒªãƒ³ã‚¯ã«ä¾¡æ ¼ã‚’設定ã§ãるよã†ã«ã—ã¾ã™ã€‚", + "creditPrice": "ãƒã‚¤ãƒ³ãƒˆä¾¡æ ¼", + "creditPriceDes": "通貨ã§ãƒã‚¤ãƒ³ãƒˆã‚’ãƒãƒ£ãƒ¼ã‚¸ã™ã‚‹ä¾¡æ ¼ï¼ˆæœ€å°é€šè²¨å˜ä½ï¼‰ã€0を入力ã™ã‚‹ã¨ãƒã‚¤ãƒ³ãƒˆãƒãƒ£ãƒ¼ã‚¸ã‚’ç¦æ­¢ã—ã¾ã™ã€‚", + "shareScoreRate": "シェア報酬率", + "shareScoreRateDes": "共有リンクãŒè³¼å…¥ã•れãŸå ´åˆã€å…±æœ‰è€…ãŒç²å¾—ã™ã‚‹ãƒã‚¤ãƒ³ãƒˆã®å‰²åˆï¼ˆ1~100)。", + "cronNotifyUser": "è¶…éŽåˆ©ç”¨è€…ã¸ã®é€šçŸ¥ã‚¹ã‚­ãƒ£ãƒ³é–“éš”", + "cronNotifyUserDes": "è¶…éŽåˆ©ç”¨è€…をスキャンã—ã€ãƒ¡ãƒ¼ãƒ«ã§é€šçŸ¥ã™ã‚‹", + "cronBanUser": "åˆ©ç”¨è€…ã‚¢ã‚«ã‚¦ãƒ³ãƒˆåœæ­¢ã‚¹ã‚­ãƒ£ãƒ³é–“éš”", + "cronBanUserDes": "ストレージ超éŽã‹ã¤ãƒãƒƒãƒ•ァ期間超éŽã®åˆ©ç”¨è€…をスキャンã—ã¦ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’åœæ­¢ã™ã‚‹", + "anonymousPurchase": "匿å購入", + "anonymousPurchaseDes": "ログインã—ã¦ã„ãªã„ユーザーãŒå…±æœ‰ãƒªãƒ³ã‚¯ã‹ã‚‰ç›´æŽ¥è³¼å…¥ã§ãるよã†ã«ã™ã‚‹", + "shopNavEnabled": "ストアナビゲーション表示", + "shopNavEnabledDes": "サイドãƒãƒ¼ãƒŠãƒ“ゲーションã«ã€Œã‚¹ãƒˆã‚¢ã€é …目を表示ã™ã‚‹", + "paymentSettings": "決済設定", + "currencyCode": "通貨コード", + "currencyCodeDes": "3文字ã®é€šè²¨ã‚³ãƒ¼ãƒ‰ï¼ˆä¾‹ï¼šUSDã€CNYã€EUR)。", + "currencySymbol": "通貨記å·", + "currencySymbolDes": "表示ã™ã‚‹é€šè²¨è¨˜å·ï¼ˆä¾‹ï¼š$ã€Â¥ã€â‚¬ï¼‰ã€‚", + "currencyUnit": "通貨å˜ä½", + "currencyUnitDes": "最å°é€šè²¨å˜ä½ï¼ˆä¾‹ï¼š1ドル=100セント)。", + "paymentProviders": "決済プロãƒã‚¤ãƒ€ãƒ¼", + "providerName": "ユーザーã«è¡¨ç¤ºã™ã‚‹ãƒ—ロãƒã‚¤ãƒ€ãƒ¼å", + "providerType": "プロãƒã‚¤ãƒ€ãƒ¼ã®ç¨®é¡ž", + "providerKey": "APIキー", + "selectCurrency": "å¸¸ç”¨é€šè²¨ã‚’é¸æŠž", + "addPaymentProvider": "決済プロãƒã‚¤ãƒ€ãƒ¼ã®è¿½åŠ ", + "stripeProvider": "Stripe", + "weixinProvider": "WeChatpay", + "alipayProvider": "Alipay", + "customProvider": "カスタム決済ãƒãƒ£ãƒãƒ«", + "customProviderDes": "ä»–ã®ã‚µãƒ¼ãƒ‰ãƒ‘ーティ決済プラットフォームを接続ã™ã‚‹ã«ã¯ã€Cloudreve互æ›ã®æ±ºæ¸ˆã‚¤ãƒ³ã‚¿ãƒ¼ãƒ•ェースを実装ã—ã¾ã™ã€‚詳細ã¯<0>å…¬å¼ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’å‚ç…§ã—ã¦ãã ã•ã„。", + "providerKeyDes": "Stripeã®APIキーを入力ã—ã¦ãã ã•ã„。", + "storageProductSettings": "ストレージ製å“", + "storageProductsDes": "ユーザーãŒã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸å®¹é‡ã‚’増やã™ãŸã‚ã«è³¼å…¥ã§ãる製å“を設定ã—ã¾ã™ã€‚", + "addStorageProduct": "製å“ã®è¿½åŠ ", + "editStorageProduct": "製å“ã®ç·¨é›†", + "storageSize": "ストレージサイズ", + "storageSizeBytes": "ã“ã®è£½å“ã«å«ã¾ã‚Œã‚‹ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚µã‚¤ã‚ºã€‚", + "duration": "期間", + "durationSeconds": "期間(秒ã€ä¾‹ï¼š2592000 ã¯30日)。", + "price": "価格", + "priceInUnits": "価格(最å°é€šè²¨å˜ä½ï¼‰", + "priceInUnitsDes": "表示価格ã¯ï¼š", + "chipLabel": "タグ(オプション)", + "chipLabelHelp": "製å“忍ªã«è¡¨ç¤ºã•れる短ã„テキストタグ。", + "usePoints": "ãƒã‚¤ãƒ³ãƒˆã®ä½¿ç”¨ã‚’許å¯ã™ã‚‹", + "points": "ãƒã‚¤ãƒ³ãƒˆ", + "pointsHelp": "ã“ã®è£½å“を購入ã™ã‚‹ãŸã‚ã«å¿…è¦ãªãƒã‚¤ãƒ³ãƒˆæ•°ã€‚", + "pointsUnit": "ãƒã‚¤ãƒ³ãƒˆ", + "groupProductSettings": "ユーザーグループ製å“", + "groupProductsDes": "特定ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã«å‚加ã™ã‚‹ãŸã‚ã«ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒè³¼å…¥ã§ãる製å“を設定ã—ã¾ã™ã€‚", + "addGroupProduct": "ユーザーグループ製å“を追加", + "editGroupProduct": "ユーザーグループ製å“を編集", + "groupId": "ユーザーグループID", + "groupIdHelp": "ã“ã®è£½å“を購入ã™ã‚‹ã¨ã‚¢ãƒƒãƒ—グレードã•れるユーザーグループ。", + "description": "説明", + "descriptionHelp": "機能ã¾ãŸã¯åˆ©ç‚¹ã‚’1行ãšã¤å…¥åŠ›", + "receiptEmailTemplate": "支払ã„é ˜åŽæ›¸ãƒ†ãƒ³ãƒ—レート", + "receiptEmailTemplateDes": "支払ã„ãŒç¢ºèªã•れãŸéš›ã«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«é€ä¿¡ã•れるメールテンプレート。", + "activationEmailTemplate": "アカウント有効化テンプレート", + "activationEmailTemplateDes": "ユーザーãŒã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’有効化ã—ãŸéš›ã«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«é€ä¿¡ã•れるメールテンプレート。", + "quotaExceededEmailTemplate": "ストレージクォータ超éŽãƒ†ãƒ³ãƒ—レート", + "quotaExceededEmailTemplateDes": "ユーザーãŒã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚¯ã‚©ãƒ¼ã‚¿ã‚’è¶…éŽã—ãŸéš›ã«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«é€ä¿¡ã•れるメールテンプレート。", + "resetPasswordEmailTemplate": "パスワードリセットテンプレート", + "resetPasswordEmailTemplateDes": "ユーザーãŒãƒ‘スワードã®ãƒªã‚»ãƒƒãƒˆã‚’è¦æ±‚ã—ãŸéš›ã«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«é€ä¿¡ã•れるメールテンプレート。", + "preferredLanguage": "優先言語", + "setAsPreferredLanguage": "優先言語ã«è¨­å®š", + "setAsPreferredLanguageDes": "ユーザーã®è¨€èªžè¨­å®šãŒå–å¾—ã§ããªã„å ´åˆã€å„ªå…ˆè¨€èªžã®ãƒ¡ãƒ¼ãƒ«ãƒ†ãƒ³ãƒ—レートãŒä½¿ç”¨ã•れã¾ã™ã€‚", + "alreadyAsPreferredLanguageDes": "ç¾åœ¨ã®è¨€èªžã¯æ—¢ã«å„ªå…ˆè¨€èªžã¨ã—ã¦è¨­å®šã•れã¦ã„ã¾ã™ã€‚ユーザーã®è¨€èªžè¨­å®šãŒå–å¾—ã§ããªã„å ´åˆã€å„ªå…ˆè¨€èªžã®ãƒ¡ãƒ¼ãƒ«ãƒ†ãƒ³ãƒ—レートãŒä½¿ç”¨ã•れã¾ã™ã€‚", + "addLanguage": "言語を追加", + "removeLanguage": "言語を削除", + "removeLanguageBtn": "言語を削除", + "cannotRemovePreferredLanguageDes": "優先言語ã¯å‰Šé™¤ã§ãã¾ã›ã‚“。別ã®è¨€èªžã‚’優先言語ã«è¨­å®šã—ã¦ã‹ã‚‰å†è©¦è¡Œã—ã¦ãã ã•ã„。", + "languageCodeDes": "追加ã™ã‚‹è¨€èªžã‚’é¸æŠžã—ã¦ãã ã•ã„。", + "emailSubject": "メール件å", + "emailSubjectDes": "メールã®ä»¶å。<0>魔法変数 を使用ã—ã¦ä»¶åをカスタマイズã§ãã¾ã™ã€‚", + "emailBody": "メール本文", + "emailBodyDes": "ãƒ¡ãƒ¼ãƒ«ã®æœ¬æ–‡ã§ã™ã€‚<0>魔法変数 を使用ã—ã¦æœ¬æ–‡ã‚’カスタマイズã§ãã¾ã™ã€‚", + "orderTitle": "注文タイトル", + "themeOptions": "テーマオプション", + "themeOptionsDes": "サイトã«ã‚«ã‚¹ã‚¿ãƒ ãƒ†ãƒ¼ãƒžã‚ªãƒ—ションを設定ã—ã¾ã™ã€‚ã“れらã®ãƒ†ãƒ¼ãƒžã¯ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒè¨­å®šã§é¸æŠžã§ãã¾ã™ã€‚", + "primaryColor": "メインカラー", + "secondaryColor": "サブカラー", + "primaryColorDark": "メインカラー(ダークモード)", + "secondaryColorDark": "サブカラー(ダークモード)", + "addThemeOption": "テーマオプションを追加", + "editThemeOption": "テーマオプションを編集", + "invalidThemeConfig": "無効ãªãƒ†ãƒ¼ãƒžè¨­å®šã§ã™ã€‚JSON構文を確èªã—ã¦ãã ã•ã„。", + "themeConfiguration": "テーマ設定", + "themePreview": "テーマプレビュー", + "lightTheme": "ライトテーマ", + "darkTheme": "ダークテーマ", + "previewTitle": "プレビュータイトル", + "previewTextField": "入力欄", + "previewPrimary": "メインカラー", + "invalidThemePreview": "無効ãªãƒ†ãƒ¼ãƒžè¨­å®šã§ã™ã€‚プレビューã§ãã¾ã›ã‚“。", + "duplicateThemeColor": "ã“ã®ãƒ¡ã‚¤ãƒ³ã‚«ãƒ©ãƒ¼ã‚’使用ã—ã¦ã„ã‚‹ãƒ†ãƒ¼ãƒžãŒæ—¢ã«å­˜åœ¨ã—ã¾ã™ã€‚別ã®è‰²ã‚’é¸æŠžã—ã¦ãã ã•ã„。", + "themeDes": "完全ãªè¨­å®šé …ç›®ã«ã¤ã„ã¦ã¯ã€<0>Material-UI Default theme viewer ã‚’å‚ç…§ã—ã¦ãã ã•ã„。", + "defaultTheme": "デフォルト", + "auditLog": "イベント", + "auditLogDes": "記録ã™ã‚‹ã‚¤ãƒ™ãƒ³ãƒˆã‚’指定ã—ã¾ã™ã€‚一部ã®ã‚¤ãƒ™ãƒ³ãƒˆã¯ã€ãƒ•ァイルアクティビティやログインアクティビティãªã©ã€è¿½åŠ æ©Ÿèƒ½ã‚’æä¾›ã™ã‚‹ãŸã‚ã«ã‚·ã‚¹ãƒ†ãƒ ã§ä½¿ç”¨ã•れる場åˆãŒã‚りã¾ã™ã€‚", + "systemEvents": "システムイベント", + "systemEventsDes": "システムæ“作ã¨çŠ¶æ…‹ã«é–¢é€£ã™ã‚‹ã‚¤ãƒ™ãƒ³ãƒˆã§ã™ã€‚", + "userEvents": "ユーザーイベント", + "userEventsDes": "ユーザーアカウントã€èªè¨¼ã€ãƒ—ロフィール変更ã«é–¢é€£ã™ã‚‹ã‚¤ãƒ™ãƒ³ãƒˆã§ã™ã€‚", + "fileEvents": "ファイルイベント", + "fileEventsDes": "ファイルæ“作(アップロードã€ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã€å¤‰æ›´ãªã©ï¼‰ã«é–¢é€£ã™ã‚‹ã‚¤ãƒ™ãƒ³ãƒˆã§ã™ã€‚", + "shareEvents": "共有イベント", + "shareEventsDes": "ファイル共有ã¨ãƒªãƒ³ã‚¯ã‚¢ã‚¯ã‚»ã‚¹ã«é–¢é€£ã™ã‚‹ã‚¤ãƒ™ãƒ³ãƒˆã§ã™ã€‚", + "versionEvents": "ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã‚¤ãƒ™ãƒ³ãƒˆ", + "versionEventsDes": "ファイルã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ã«é–¢é€£ã™ã‚‹ã‚¤ãƒ™ãƒ³ãƒˆã§ã™ã€‚", + "mediaEvents": "メディアイベント", + "mediaEventsDes": "メディアファイル処ç†ï¼ˆã‚µãƒ ãƒã‚¤ãƒ«ç”Ÿæˆãªã©ï¼‰ã«é–¢é€£ã™ã‚‹ã‚¤ãƒ™ãƒ³ãƒˆã§ã™ã€‚", + "filesystemEvents": "ファイルシステムイベント", + "filesystemEventsDes": "ファイルシステムæ“作(マウントやアーカイブ処ç†ãªã©ï¼‰ã«é–¢ã™ã‚‹ã‚¤ãƒ™ãƒ³ãƒˆ", + "webdavEvents": "WebDAVイベント", + "webdavEventsDes": "WebDAVアカウント管ç†ã¨ã‚¢ã‚¯ã‚»ã‚¹ã«é–¢ã™ã‚‹ã‚¤ãƒ™ãƒ³ãƒˆ", + "paymentEvents": "決済イベント", + "paymentEventsDes": "決済å–引ã¨å‡¦ç†ã«é–¢ã™ã‚‹ã‚¤ãƒ™ãƒ³ãƒˆ", + "emailEvents": "メールイベント", + "emailEventsDes": "メールé€ä¿¡ã¨é€šçŸ¥ã«é–¢ã™ã‚‹ã‚¤ãƒ™ãƒ³ãƒˆ", + "toggleAll": "å…¨ã‚¤ãƒ™ãƒ³ãƒˆã®æœ‰åŠ¹åŒ–/無効化", + "toggleAllDes": "ã“ã®ã‚«ãƒ†ã‚´ãƒªå†…ã®å…¨ã‚¤ãƒ™ãƒ³ãƒˆã‚’有効化ã¾ãŸã¯ç„¡åŠ¹åŒ–ã—ã¾ã™ã€‚", + "event": { + "file_imported": "外部ファイル導入", + "server_start": "サーãƒãƒ¼èµ·å‹•", + "user_signup": "ユーザー登録", + "email_sent": "メールé€ä¿¡", + "user_activated": "ユーザー有効化", + "user_login_failed": "ログイン失敗", + "user_login": "ユーザーログイン", + "user_token_refresh": "トークン更新", + "file_create": "ファイル作æˆ", + "file_rename": "ファイルå変更", + "set_file_permission": "権é™å¤‰æ›´", + "entity_uploaded": "ファイルアップロードã¾ãŸã¯æ›´æ–°", + "entity_downloaded": "ファイルダウンロード", + "copy_from": "ソースコピー", + "copy_to": "コピー先", + "move_to": "移動", + "delete_file": "ファイル削除", + "move_to_trash": "ゴミ箱ã¸ç§»å‹•", + "share": "共有作æˆ", + "share_link_viewed": "共有リンク表示", + "set_current_version": "ç¾åœ¨ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³è¨­å®š", + "delete_version": "ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®å‰Šé™¤", + "thumb_generated": "サムãƒã‚¤ãƒ«ç”Ÿæˆ", + "live_photo_uploaded": "Live Photo アップロード", + "update_metadata": "メタデータ更新", + "edit_share": "共有編集", + "delete_share": "共有削除", + "mount": "マウント", + "relocate": "ストレージãƒãƒªã‚·ãƒ¼ã®å¤‰æ›´", + "create_archive": "アーカイブ作æˆ", + "extract_archive": "アーカイブ解å‡", + "webdav_login_failed": "WebDAVログイン失敗", + "webdav_account_create": "WebDAVアカウント作æˆ", + "webdav_account_update": "WebDAVアカウント更新", + "webdav_account_delete": "WebDAVアカウント削除", + "payment_created": "決済作æˆ", + "points_change": "ãƒã‚¤ãƒ³ãƒˆå¤‰æ›´", + "payment_paid": "決済完了", + "payment_fulfilled": "注文履行", + "payment_fulfill_failed": "注文履行失敗", + "storage_added": "ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸å®¹é‡æ‹¡å¼µ", + "group_changed": "ユーザーグループ変更", + "user_exceed_quota_notified": "容é‡è¶…éŽé€šçŸ¥", + "user_changed": "ユーザー状態変更", + "get_direct_link": "ダイレクトリンクå–å¾—", + "link_account": "外部アカウント連æº", + "unlink_account": "外部アカウント連æºè§£é™¤", + "change_nick": "ニックãƒãƒ¼ãƒ å¤‰æ›´", + "change_avatar": "ã‚¢ãƒã‚¿ãƒ¼å¤‰æ›´", + "membership_unsubscribe": "購読解除", + "change_password": "パスワード変更", + "enable_2fa": "2FA有効化", + "disable_2fa": "2FA無効化", + "add_passkey": "パスキー追加", + "remove_passkey": "パスキー削除", + "redeem_gift_code": "ギフトコード交æ›", + "update_view": "ビュー設定変更", + "delete_direct_link": "ç›´éˆå‰Šé™¤", + "report_abuse": "䏿­£åˆ©ç”¨å ±å‘Š" + }, + "server": "サーãƒãƒ¼è¨­å®š", + "tempPath": "一時パス", + "tempPathDes": "一時ファイルをä¿å­˜ã™ã‚‹ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªï¼ˆCloudreveデータディレクトリã‹ã‚‰ã®ç›¸å¯¾ãƒ‘ス)。変更ã™ã‚‹å‰ã«ã€å®Ÿè¡Œä¸­ã®ã‚­ãƒ¥ãƒ¼ã‚¿ã‚¹ã‚¯ãŒãªã„ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。", + "siteID": "サイトID", + "siteIDDes": "サイトを一æ„ã«è­˜åˆ¥ã™ã‚‹ãŸã‚ã®ID。通常ã¯å¤‰æ›´ã™ã‚‹å¿…è¦ã¯ã‚りã¾ã›ã‚“。", + "siteSecretKey": "マスターキー", + "siteSecretKeyDes": "ユーザーã®ãƒˆãƒ¼ã‚¯ãƒ³ã¨ç½²åã‚’æš—å·åŒ–ã™ã‚‹ãŸã‚ã®ãƒžã‚¹ã‚¿ãƒ¼ã‚­ãƒ¼ã€‚ローテーション後ã€ã™ã¹ã¦ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒˆãƒ¼ã‚¯ãƒ³ã¨ç½²åã¯ç„¡åйã«ãªã‚Šã¾ã™ã€‚ä¿å­˜å¾Œã«Cloudreveã‚’å†èµ·å‹•ã—ã¦æœ‰åйã«ã—ã¾ã™ã€‚", + "rotateSecretKey": "マスターキーローテーション", + "hashidSalt": "HashIDソルト値", + "hashidSaltDes": "HashIDを生æˆã™ã‚‹ãŸã‚ã®ã‚½ãƒ«ãƒˆå€¤ã€‚å¤‰æ›´ã¯æ…Žé‡ã«è¡Œã£ã¦ãã ã•ã„。変更ã™ã‚‹ã¨ã€æ—¢å­˜ã®ãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆãƒªãƒ³ã‚¯ã‚„共有リンクãªã©ãŒã™ã¹ã¦ç„¡åйã«ãªã‚Šã¾ã™ã€‚", + "accessTokenTTL": "アクセストークンTTL", + "accessTokenTTLDes": "ã‚¢ã‚¯ã‚»ã‚¹ãƒˆãƒ¼ã‚¯ãƒ³ã®æœ‰åŠ¹æœŸé™ï¼ˆç§’å˜ä½ï¼‰ã€‚", + "refreshTokenTTL": "リフレッシュトークンTTL", + "refreshTokenTTLDes": "ãƒªãƒ•ãƒ¬ãƒƒã‚·ãƒ¥ãƒˆãƒ¼ã‚¯ãƒ³ã®æœ‰åŠ¹æœŸé™ï¼ˆç§’å˜ä½ï¼‰ã€‚ユーザーã®ãƒ­ã‚°ã‚¤ãƒ³çŠ¶æ…‹ã®ä¿æŒæ™‚é–“ã«å½±éŸ¿ã—ã¾ã™ã€‚", + "cronGarbageCollect": "ガベージコレクションスキャン間隔", + "cronGarbageCollectDes": "一時ファイルã¨KVã‚¹ãƒˆã‚¢ã®æœŸé™åˆ‡ã‚Œãƒ‡ãƒ¼ã‚¿ã®å‰Šé™¤ã‚¹ã‚­ãƒ£ãƒ³é–“éš”ã®è¨­å®š", + "startWithProtocol": "http://ã¾ãŸã¯https://ã§å§‹ã‚ã‚‹å¿…è¦ãŒã‚りã¾ã™", + "tlsWarning": "ç¾åœ¨ã®ã‚µã‚¤ãƒˆã¯httpsを使用ã—ã¦ã„ã¾ã™ã€‚httpã®URLを入力ã™ã‚‹ã¨ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã™ã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚", + "blobUrlCache": "Blob URLキャッシュ", + "clearBlobUrlCache": "Blob URLキャッシュã®ã‚¯ãƒªã‚¢", + "clearBlobUrlCacheDes": "キャッシュヒット率を高ã‚ã‚‹ãŸã‚ã€Cloudreveã¯Blob URLをキャッシュã—ã¦å†åˆ©ç”¨ã—ã¾ã™ã€‚CDNアドレスãªã©ã®è¨­å®šãŒå¤‰æ›´ã•れãŸå ´åˆã¯ã€ã‚­ãƒ£ãƒƒã‚·ãƒ¥ã‚’クリアã—ã¦ãã ã•ã„。", + "cacheCleared": "キャッシュをクリアã—ã¾ã—ãŸ" + }, + "giftCodes": { + "giftCodesSettings": "ギフトコード", + "generateGiftCodes": "ギフトコードã®ç”Ÿæˆ", + "giftCodeQuantity": "æ•°é‡", + "giftCodeQuantityHelp": "生æˆã™ã‚‹ã‚®ãƒ•ãƒˆã‚³ãƒ¼ãƒ‰ã®æ•°ã€‚", + "giftCodeProductType": "製å“タイプ", + "giftCodeTypePoints": "ãƒã‚¤ãƒ³ãƒˆ", + "giftCodeTypeStorage": "ストレージ容é‡", + "giftCodeTypeGroup": "ユーザーグループ", + "giftCodePointsAmount": "ãƒã‚¤ãƒ³ãƒˆæ•°", + "giftCodePointsAmountHelp": "交æ›ã‚³ãƒ¼ãƒ‰ä½¿ç”¨æ™‚ã«ç²å¾—ã™ã‚‹ãƒã‚¤ãƒ³ãƒˆæ•°ã€‚", + "giftCodeProduct": "製å“", + "selectStorageProduct": "ストレージ製å“ã‚’é¸æŠž", + "selectGroupProduct": "ユーザーグループ製å“ã‚’é¸æŠž", + "giftCodeType": "種類", + "giftCodeAmount": "æ•°é‡", + "giftCode": "ギフトコード", + "giftCodeStatus": "状態", + "giftCodeUsedBy": "利用者", + "giftCodeUsed": "使用済ã¿", + "giftCodeUnused": "使用å¯èƒ½", + "giftCodeDeleted": "ã‚®ãƒ•ãƒˆã‚³ãƒ¼ãƒ‰ãŒæ­£å¸¸ã«å‰Šé™¤ã•れã¾ã—ãŸ", + "giftCodesGenerated": "ã‚®ãƒ•ãƒˆã‚³ãƒ¼ãƒ‰ãŒæ­£å¸¸ã«ç”Ÿæˆã•れã¾ã—ãŸ", + "noGiftCodes": "ギフトコードãŒã‚りã¾ã›ã‚“", + "generatedCodesTitle": "ç”Ÿæˆæ¸ˆã¿ã®ã‚®ãƒ•トコード", + "generatedCodesDescription": "ã“れらã®ã‚®ãƒ•トコードをコピーã—ã¦ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¨å…±æœ‰ã—ã¦ãã ã•ã„。å„ギフトコードã¯ä¸€åº¦ã®ã¿ä½¿ç”¨ã§ãã¾ã™ã€‚", + "copyAndClose": "コピーã—ã¦é–‰ã˜ã‚‹", + "duratonTimes": "æœŸé–“å€æ•°", + "duratonTimesDes": "å„ギフトコードã«ã¯ã€å¯¾å¿œã™ã‚‹å•†å“ãŒä½•個å«ã¾ã‚Œã¦ã„ã¾ã™ã‹ã€‚", + "unknownProduct": "䏿˜Žãªå•†å“" + }, + "policy": { + "acceleratedDomainUpload": "転é€åŠ é€Ÿãƒ‰ãƒ¡ã‚¤ãƒ³ã‚’ä½¿ç”¨ã—ã¦ã‚¢ãƒƒãƒ—ロード", + "acceleratedDomainUploadDes": "有効ã«ã™ã‚‹ã¨ã€ã‚¢ãƒƒãƒ—ロード時ã«ä¸ƒç‰›ã®<0>転é€åŠ é€Ÿãƒ‰ãƒ¡ã‚¤ãƒ³ã‚’ä½¿ç”¨ã—ã¾ã™ã€‚", + "compare": "ストレージãƒãƒªã‚·ãƒ¼ã®æ¯”較", + "deletePolicyConfirmation": "ストレージãƒãƒªã‚·ãƒ¼ã€Œ{{name}}ã€ã‚’削除ã—ã¾ã™ã‹ï¼Ÿ", + "streamSaver": "ブラウザã§ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰å‡¦ç†", + "streamSaverDes": "有効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒãƒ•ァイルをダウンロードã™ã‚‹éš›ã«ã€ãƒ–ラウザã§ã®å‡¦ç†ãŒå¼·åˆ¶ã•れã¾ã™ã€‚OneDriveストレージãƒãƒªã‚·ãƒ¼ã®åˆ¶é™ã«ã‚ˆã‚Šã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒãƒ•ァイルを直接ダウンロードã™ã‚‹å ´åˆã€ãƒ•ァイルåãŒCloudreve内ã®ãƒ•ァイルåã¨ä¸€è‡´ã—ãªã„å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚ブラウザã§ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰å‡¦ç†ã‚’行ã†ã“ã¨ã§ã€ã“ã®å•題を解決ã§ãã¾ã™ã€‚", + "oauthCallbackFailed": "èªè¨¼å¤±æ•—", + "httpsRequired": "Entra IDアプリã¯HTTPSリダイレクトURLを使用ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ãŒã€ç¾åœ¨ã®ã‚µã‚¤ãƒˆã§ã¯HTTPを使用ã—ã¦ã„ã¾ã™ã€‚ログイン後ã«ãƒªãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆãŒå¤±æ•—ã™ã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚ãã®å ´åˆã¯ã€ãƒ–ラウザã®ã‚¢ãƒ‰ãƒ¬ã‚¹ãƒãƒ¼ã«ã‚ã‚‹HTTPSを手動ã§HTTPã«ç½®ãæ›ãˆã¦ãã ã•ã„。", + "authorizeMicrosoft": "Microsoftアカウントã§ãƒ­ã‚°ã‚¤ãƒ³", + "redirectUrl": "リダイレクトURL", + "redirectUrlDes": "表示ã•れã¦ã„ã‚‹ã®ã¯æœ€æ–°ã®æœ‰åйãªãƒªãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆURLã§ã™ã€‚アプリã®è¨­å®šã§ãƒªãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆURLãŒä¸€è‡´ã—ã¦ã„ã‚‹ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。", + "authorizeOneDrive": "Entra IDアプリ設定ã®ç¢ºèª", + "authorizeOneDriveDes": "以下ã®Entra IDã‚¢ãƒ—ãƒªæƒ…å ±ãŒæœ‰åйã§ã‚ã‚‹ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。必è¦ã«å¿œã˜ã¦å¤‰æ›´ã—ã¦ãã ã•ã„。", + "authorizeNow": "今ã™ã承èª", + "authorizeAgain": "冿‰¿èª", + "notGranted": "æ‰¿èªæ¸ˆã¿ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒã‚りã¾ã›ã‚“。ストレージãƒãƒªã‚·ãƒ¼ã¯ä½¿ç”¨ã§ãã¾ã›ã‚“。", + "granted": "æ‰¿èªæ¸ˆã¿ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã™ã€‚資格情報ã¯<0>ã«æ›´æ–°ã•れã¾ã—ãŸã€‚", + "grantedNotRefresh": "æ‰¿èªæ¸ˆã¿ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§ã™ã€‚資格情報ã¯å‰å›žã®èµ·å‹•以陿›´æ–°ã•れã¦ã„ã¾ã›ã‚“。", + "batchDeleteSize": "一括削除ã®ä¸Šé™æ•°", + "batchDeleteSizeDes": "一度ã®APIリクエストã§å‰Šé™¤ã§ãる最大数を制é™ã—ã¾ã™ã€‚ã“ã®è¨­å®šã¯ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«ã‚ˆã‚‹ãƒ•ァイルã®ä¸€æ‹¬å‰Šé™¤ã«ã¯å½±éŸ¿ã—ã¾ã›ã‚“。何も入力ã—ãªã„å ´åˆã¯ã€ãƒ‡ãƒ•ォルト値<0>1000(公å¼S3 APIã®æœ€å¤§è¨±å®¹å€¤ï¼‰ãŒä½¿ç”¨ã•れã¾ã™ã€‚", + "bucketPolicy": "ãƒã‚±ãƒƒãƒˆãƒãƒªã‚·ãƒ¼", + "cdnOrCustomDomain": "CDNã¾ãŸã¯ã‚«ã‚¹ã‚¿ãƒ ã‚ªãƒªã‚¸ãƒ³ã‚µãƒ¼ãƒãƒ¼ã®ãƒ‰ãƒ¡ã‚¤ãƒ³å", + "bucketDomain": "ストレージスペースドメイン", + "bucketDomainDes": "ストレージスペースã«ãƒã‚¤ãƒ³ãƒ‰ã•れãŸCDN加速ドメインã¾ãŸã¯ã‚«ã‚¹ã‚¿ãƒ ã‚ªãƒªã‚¸ãƒ³ã‚µãƒ¼ãƒãƒ¼ã®ãƒ‰ãƒ¡ã‚¤ãƒ³åを入力ã—ã¦ãã ã•ã„。", + "storageNodeInternal": "ストレージノード(イントラãƒãƒƒãƒˆã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆï¼‰", + "chunkSizeDesOssObs": "許容範囲:100 KB~5 GB", + "chunkSizeDesQiniuCos": "許容範囲:1 MB~1 GB", + "chunkSizeDesS3": "許容範囲:5 MB~5 GB", + "thisIsACustomDomain": "ã“れã¯ã‚«ã‚¹ã‚¿ãƒ ãƒ‰ãƒ¡ã‚¤ãƒ³ã§ã™", + "thisIsACustomDomainDes": "ãƒã‚±ãƒƒãƒˆã«ã‚«ã‚¹ã‚¿ãƒ ãƒ‰ãƒ¡ã‚¤ãƒ³ã‚’ãƒã‚¤ãƒ³ãƒ‰ã—ã¦ãŠã‚Šã€ã‚«ã‚¹ã‚¿ãƒ ãƒ‰ãƒ¡ã‚¤ãƒ³çµŒç”±ã§ã‚¢ãƒƒãƒ—ロードãªã©ã®ç®¡ç†æ“作を行ã†å¿…è¦ãŒã‚ã‚‹å ´åˆã¯ã€ã“ã®ã‚ªãƒ—ションをオンã«ã—ã¦ãã ã•ã„。オンã«ã™ã‚‹ã¨ã€Cloudreveã¯ãƒªã‚¯ã‚¨ã‚¹ãƒˆãƒ‰ãƒ¡ã‚¤ãƒ³ã«ãƒã‚±ãƒƒãƒˆåã®è£œå®Œã‚’試ã¿ã¾ã›ã‚“。", + "addedManually": "æ—¢ã«è¨­å®šæ¸ˆã¿ã§ã™", + "origin": "ソース", + "allowMethods": "許å¯ã™ã‚‹ãƒ¡ã‚½ãƒƒãƒ‰", + "exposeHeaders": "公開ヘッダー", + "allowHeaders": "許å¯ã™ã‚‹ãƒ˜ãƒƒãƒ€ãƒ¼", + "maxAge": "キャッシュ時間", + "accessCredential": "アクセスèªè¨¼æƒ…å ±", + "downloadTrafficDiagram": "ダウンロードトラフィックパスデモ図", + "downloadRelay": "ダウンロード中継", + "downloadRelayDes": "有効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒãƒ•ァイルをダウンロードã™ã‚‹éš›ã«Cloudreveã®ãƒ—ロキシを経由ã—ã¾ã™ã€‚", + "download": "ダウンロード", + "downloadCdn": "ダウンロードCDN", + "useDownloadCdn": "CDNを使用ã—ã¦ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã‚’高速化", + "skipSign": "CDNç½²åファイルURLを生æˆã—ãªã„", + "skipSignDes": "COSドメイン設定ã§ã€Œã‚ªãƒªã‚¸ãƒ³èªè¨¼ã€ã‚’有効ã«ã—ã¦ã„ã‚‹å ´åˆã¯ã€ã“ã®é …ç›®ã‚’é¸æŠžã—ã¦ãã ã•ã„。", + "cdnHost": "CDNアドレス", + "downloadCdnDes": "ユーザーãŒãƒ•ァイルã«ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹éš›ã®URLã®ãƒ›ã‚¹ãƒˆåã€ãƒ—ロトコルãªã©ã®éƒ¨åˆ†ã¯ã€æŒ‡å®šã—ãŸCDNドメインã«ç½®ãæ›ãˆã‚‰ã‚Œã¾ã™ã€‚", + "mediaExtractorProxy": "プロキシã«ã‚ˆã‚‹ãƒ¡ãƒ‡ã‚£ã‚¢æƒ…å ±å–å¾—", + "mediaExtractorProxyDes": "有効化ã™ã‚‹ã¨ã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã®ã‚¨ã‚¯ã‚¹ãƒˆãƒ©ã‚¯ã‚¿ãŒå¯¾å¿œã—ã¦ã„ãªã„ファイルã«ã¤ã„ã¦ã€Cloudreveã¯ãƒ•ァイルã®ãƒ¡ãƒ‡ã‚£ã‚¢æƒ…å ±ã®æŠ½å‡ºã‚’è©¦ã¿ã¾ã™ã€‚<0>メディア処ç†ã§Cloudreveメディア情報エクストラクタを設定ã—ã¦ãã ã•ã„。", + "mediaExtractorNative": "ãƒã‚¤ãƒ†ã‚£ãƒ–エクストラクタ", + "mediaExtractorOss": "インテリジェントメディアマãƒã‚¸ãƒ¡ãƒ³ãƒˆï¼ˆIMM)", + "mediaExtractorQiniu": "インテリジェントマルãƒãƒ¡ãƒ‡ã‚£ã‚¢ã‚µãƒ¼ãƒ“ス", + "mediaExtractorCos": "テンセントクラウドデータ万象", + "mediaExtractorObs": "ç”»åƒå‡¦ç†ã‚µãƒ¼ãƒ“ス", + "mediaExtractorUpyun": "ç”»åƒå‡¦ç†ã‚µãƒ¼ãƒ“ス", + "nativeMediaMetaExts": "<0>{{name}}ã®ãƒ•ァイル拡張å­ã‚’使用", + "nativeMediaMetaExtsGeneralDes": "åŠè§’カンマ , ã§åŒºåˆ‡ã‚Šã€ç©ºæ¬„ã®å ´åˆã¯<0>{{name}}を使用ã—ã¾ã›ã‚“。", + "nativeMediaMetaExtsRemote": "従属ストレージã§ã¯ã€ãƒ‡ãƒ•ォルトã§EXIFã¨éŸ³æ¥½ãƒ¡ã‚¿ãƒ‡ãƒ¼ã‚¿ãŒã‚µãƒãƒ¼ãƒˆã•れã¦ã„ã¾ã™ã€‚設定ã§ä¸Šæ›¸ãã™ã‚‹ã“ã¨ã§ã€å¾“属ストレージã§ä»–ã®ã‚¸ã‚§ãƒãƒ¬ãƒ¼ã‚¿ã‚’有効化ã§ãã¾ã™ã€‚", + "nativeMediaMetaExtOss": "インテリジェントメディアマãƒã‚¸ãƒ¡ãƒ³ãƒˆï¼ˆIMM)サービスã¯ã€éŸ³å£°ã€å‹•ç”»ã€ç”»åƒã®å‡¦ç†ã‚’サãƒãƒ¼ãƒˆã—ã¾ã™ã€‚ç”»åƒã®å‡¦ç†ã¯æ‰‹å‹•設定ã¯ä¸è¦ã§ã™ãŒã€éŸ³å£°ã‚„動画を処ç†ã™ã‚‹å¿…è¦ãŒã‚ã‚‹å ´åˆã¯ã€IMMã‚’æ‰‹å‹•ã§æœ‰åŠ¹åŒ–ã—ã€ãƒã‚±ãƒƒãƒˆã«ãƒã‚¤ãƒ³ãƒ‰ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚<0>ドキュメントをå‚ç…§ã—ã¦ãƒã‚¤ãƒ³ãƒ‰ã—ã¦ãã ã•ã„。ãƒã‚¤ãƒ³ãƒ‰å¾Œã€å‡¦ç†ã—ãŸã„音声/å‹•ç”»ã®æ‹¡å¼µå­ã‚’上記ã«è¿½åŠ ã—ã¦ãã ã•ã„。", + "nativeMediaMetaExtQiniu": "インテリジェントマルãƒãƒ¡ãƒ‡ã‚£ã‚¢ã‚µãƒ¼ãƒ“スã¯ã€ä¸€èˆ¬çš„ãªéŸ³å£°ã€å‹•ç”»ã€ç”»åƒã®å‡¦ç†ã‚’サãƒãƒ¼ãƒˆã—ã¾ã™ã€‚追加設定ã¯ä¸è¦ã§ã™ã€‚処ç†ã—ãŸã„ãƒ¡ãƒ‡ã‚£ã‚¢ã®æ‹¡å¼µå­ã‚’上記ã«å…¥åŠ›ã™ã‚‹ã ã‘ã§æ¸ˆã¿ã¾ã™ã€‚", + "nativeMediaMetaExtCos": "テンセントクラウドデータ万象サービスã¯ã€éŸ³å£°ã€å‹•ç”»ã€ç”»åƒã®å‡¦ç†ã‚’サãƒãƒ¼ãƒˆã—ã¾ã™ã€‚ç”»åƒã®å‡¦ç†ã¯æ‰‹å‹•設定ã¯ä¸è¦ã§ã™ãŒã€éŸ³å£°ã‚„動画を処ç†ã™ã‚‹å¿…è¦ãŒã‚ã‚‹å ´åˆã¯ã€<0>データ万象ã§ã‚µãƒ¼ãƒ“スを有効化ã—ã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒã‚±ãƒƒãƒˆã‚’ãƒã‚¤ãƒ³ãƒ‰ã—ã¦ãã ã•ã„。ãã®å¾Œã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒã‚±ãƒƒãƒˆã®è¨­å®š - メディア処ç†ã§ç¾ŽåŒ–処ç†ã‚µãƒ¼ãƒ“スを有効化ã—ã¾ã™ã€‚ãƒã‚¤ãƒ³ãƒ‰å¾Œã€å‡¦ç†ã—ãŸã„音声/å‹•ç”»ã®æ‹¡å¼µå­ã‚’上記ã«è¿½åŠ ã—ã¦ãã ã•ã„。", + "nativeMediaMetaExtObs": "ç”»åƒå‡¦ç†ã‚µãƒ¼ãƒ“スã¯<0>ç”»åƒEXIFã®æŠ½å‡ºã‚’ã‚µãƒãƒ¼ãƒˆã—ã¾ã™ã€‚手動設定ã¯ä¸è¦ã§ã™ã€‚処ç†ã—ãŸã„ç”»åƒã®æ‹¡å¼µå­ã‚’上記ã«è¿½åŠ ã™ã‚‹ã ã‘ã§æ¸ˆã¿ã¾ã™ã€‚", + "nativeMediaMetaExtUpyun": "ç”»åƒå‡¦ç†ã‚µãƒ¼ãƒ“スã¯<0>ç”»åƒEXIFã®æŠ½å‡ºã‚’ã‚µãƒãƒ¼ãƒˆã—ã¾ã™ã€‚手動設定ã¯ä¸è¦ã§ã™ã€‚処ç†ã—ãŸã„ç”»åƒã®æ‹¡å¼µå­ã‚’上記ã«è¿½åŠ ã™ã‚‹ã ã‘ã§æ¸ˆã¿ã¾ã™ã€‚", + "thumbProxy": "プロキシã«ã‚ˆã‚‹ã‚µãƒ ãƒã‚¤ãƒ«ç”Ÿæˆ", + "thumbProxyDes": "有効化ã™ã‚‹ã¨ã€ãƒã‚¤ãƒ†ã‚£ãƒ–サムãƒã‚¤ãƒ«ã®æ¡ä»¶ã‚’満ãŸã—ã¦ã„ãªã„ファイルã«ã¤ã„ã¦ã€Cloudreveã¯ã‚µãƒ ãƒã‚¤ãƒ«ãƒ•ァイルã®ç”Ÿæˆã¨ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã¸ã®ã‚¢ãƒƒãƒ—ロードを試ã¿ã¾ã™ã€‚<0>メディア処ç†ã§Cloudreveサムãƒã‚¤ãƒ«ã‚¸ã‚§ãƒãƒ¬ãƒ¼ã‚¿ã‚’設定ã—ã¦ãã ã•ã„。", + "nativeThumbnailMaxSize": "ãƒã‚¤ãƒ†ã‚£ãƒ–サムãƒã‚¤ãƒ«ã®æœ€å¤§ãƒ•ァイルサイズ", + "nativeThumbnailMaxSizeDes": "0ã¨å…¥åŠ›ã™ã‚‹ã¨åˆ¶é™ãªã—ã¨ãªã‚Šã¾ã™ã€‚ã“ã®ã‚µã‚¤ã‚ºã‚’è¶…ãˆã‚‹ãƒ•ァイルã¯ã€ãƒã‚¤ãƒ†ã‚£ãƒ–サムãƒã‚¤ãƒ«ã‚’使用ã—ã¾ã›ã‚“。", + "nativeThumbNailsSupportAllExts": "å…¨ã¦ã®ãƒ•ァイル拡張å­ã«å¯¾ã—ã¦", + "nativeThumbNails": "ãƒã‚¤ãƒ†ã‚£ãƒ–サムãƒã‚¤ãƒ«ã®æ‹¡å¼µå­", + "nativeThumbNailsGeneralDes": "åŠè§’カンマ `,` ã§åŒºåˆ‡ã‚Šã€ç©ºæ¬„ã®å ´åˆã¯ãƒã‚¤ãƒ†ã‚£ãƒ–サムãƒã‚¤ãƒ«ã‚’使用ã—ã¾ã›ã‚“ã€‚ãƒªã‚¹ãƒˆã«æŒ™ã’られãŸãƒ•ァイル拡張å­ã«ã¤ã„ã¦ã¯ã€Cloudreveã¯ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸å´ã®ãƒã‚¤ãƒ†ã‚£ãƒ–サムãƒã‚¤ãƒ«ã‚’使用ã—ã¾ã™ã€‚", + "nativeThumbNailsGeneralRemote": "セカンダリストレージã§ã¯ã€ãƒ‡ãƒ•ォルトã§ã‚·ãƒ³ãƒ—ルãªç”»åƒã¨æ¥½æ›²ã®ã‚¸ãƒ£ã‚±ãƒƒãƒˆç”»åƒã®ã‚µãƒ ãƒã‚¤ãƒ«ã®ã¿ã‚µãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã™ã€‚設定ã§ä¸Šæ›¸ãã™ã‚‹ã“ã¨ã§ã€ã‚»ã‚«ãƒ³ãƒ€ãƒªå´ã§ä»–ã®ã‚¸ã‚§ãƒãƒ¬ãƒ¼ã‚¿ã‚’有効化ã§ãã¾ã™ã€‚", + "nativeThumbNailsGeneralOss": "Alibaba Cloud OSSストレージã®å ´åˆã€<0>ç”»åƒå‡¦ç†ã‚µãƒ¼ãƒ“スを使用ã—ã¦ã‚µãƒ ãƒã‚¤ãƒ«ã‚’生æˆã—ã¾ã™ã€‚", + "nativeThumbNailsGeneralQiniu": "Qiniu Cloud ストレージã®å ´åˆã€<0>ç”»åƒåŸºæœ¬å‡¦ç†(imageView2)サービスを使用ã—ã¦ã‚µãƒ ãƒã‚¤ãƒ«ã‚’生æˆã—ã¾ã™ã€‚", + "nativeThumbNailsGeneralCos": "Tencent Cloud COSストレージã®å ´åˆã€<0>Tencent Cloud Data Processingサービスを使用ã—ã¦ã‚µãƒ ãƒã‚¤ãƒ«ã‚’生æˆã—ã¾ã™ã€‚", + "nativeThumbNailsGeneralObs": "Huawei Cloud OBSストレージã®å ´åˆã€<0>ç”»åƒå‡¦ç†ã‚µãƒ¼ãƒ“スを使用ã—ã¦ã‚µãƒ ãƒã‚¤ãƒ«ã‚’生æˆã—ã¾ã™ã€‚", + "nativeThumbNailsGeneralUpyun": "Upyun ストレージã®å ´åˆã€<0>ç”»åƒå‡¦ç†ã‚µãƒ¼ãƒ“スを使用ã—ã¦ã‚µãƒ ãƒã‚¤ãƒ«ã‚’生æˆã—ã¾ã™ã€‚", + "preallocate": "ディスク領域ã®äº‹å‰å‰²ã‚Šå½“ã¦", + "preallocateDes": "有効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒãƒ•ァイルをアップロードã™ã‚‹éš›ã«ãƒ‡ã‚£ã‚¹ã‚¯é ˜åŸŸãŒäº‹å‰ã«å‰²ã‚Šå½“ã¦ã‚‰ã‚Œã€åŒæ™‚ã«ä¸¦è¡Œãƒãƒ£ãƒ³ã‚¯ã‚¢ãƒƒãƒ—ロードもサãƒãƒ¼ãƒˆã—ã¾ã™ã€‚Linuxã¾ãŸã¯Darwinã§ã®ã¿æœ‰åйã§ã™ã€‚", + "chunkConcurrency": "並行ãƒãƒ£ãƒ³ã‚¯ã‚¢ãƒƒãƒ—ロード数", + "chunkConcurrencyDes": "Webダイレクトアップロード時ã®ä¸¦è¡Œãƒãƒ£ãƒ³ã‚¯ã‚¢ãƒƒãƒ—ロード数を設定ã—ã¾ã™ã€‚", + "sourceWebEdit": "Webオンライン編集", + "uploadRelay": "中継アップロード", + "uploadRelayDes": "有効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ã‚¢ãƒƒãƒ—ロードリクエストã¯Cloudreveを経由ã—ã¦ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸å´ã«è»¢é€ã•れã¾ã™ã€‚ãƒãƒ£ãƒ³ã‚¯ã‚¢ãƒƒãƒ—ロードãŒã§ããªã„ãŸã‚ã€Webサーãƒãƒ¼å´ã®æœ€å¤§ã‚¢ãƒƒãƒ—ロードサイズ制é™ã‚’調整ã—ã¦ãã ã•ã„。", + "customProxy": "カスタムプロキシ", + "storageNode": "ストレージプロãƒã‚¤ãƒ€ãƒ¼", + "sourceWeb": "Web / å…¬å¼ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆ", + "sourceDav": "WebDAV", + "uploadTrafficDiagram": "アップロードトラフィック経路図", + "node": "ストレージノード", + "nodeDes": "ファイルをä¿å­˜ã™ã‚‹ã‚»ã‚«ãƒ³ãƒ€ãƒªãƒŽãƒ¼ãƒ‰ã‚’é¸æŠžã—ã¦ãã ã•ã„。<0>ストレージノード一覧ã§ã‚»ã‚«ãƒ³ãƒ€ãƒªãƒŽãƒ¼ãƒ‰ã‚’作æˆã¾ãŸã¯ç®¡ç†ã§ãã¾ã™ã€‚", + "noBindedGroupWarning": "ç¾åœ¨ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã¯ã©ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã«ã‚‚割り当ã¦ã‚‰ã‚Œã¦ã„ã¾ã›ã‚“。<0>ユーザーグループ一覧ã‹ã‚‰ç¾åœ¨ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã‚’ãƒã‚¤ãƒ³ãƒ‰ã—ã¦ãã ã•ã„。", + "nameRuleImmutable": "ã“ã®è¨­å®šã‚’変更ã—ã¦ã‚‚ã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã«æ—¢ã«å­˜åœ¨ã™ã‚‹ãƒ•ァイルã«ã¯å½±éŸ¿ã—ã¾ã›ã‚“。Blobパスã¯ä½œæˆå¾Œã«å›ºå®šã•れã€ãƒžã‚¸ãƒƒã‚¯å¤‰æ•°ãŒå¤‰æ›´ã•れã¦ã‚‚ãƒ‘ã‚¹ã¯æ›´æ–°ã•れã¾ã›ã‚“。", + "uniqueVarRequired": "ディレクトリパスã¾ãŸã¯ãƒ•ァイルåã«å°‘ãªãã¨ã‚‚1ã¤ã®ãƒ¦ãƒ‹ãƒ¼ã‚¯å¤‰æ•°ï¼ˆ{uuid}ã€{randomkey8}ã€{randomkey16})をå«ã‚ã¦ãã ã•ã„。", + "storageAndUpload": "ストレージã¨ã‚¢ãƒƒãƒ—ロード", + "blobFolderNaming": "Blobストレージディレクトリ", + "blobFolderNamingDes": "ファイルBlobã®ä¿å­˜ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã§ã™ã€‚<0>マジック変数を使用ã§ãã¾ã™ã€‚", + "blobName": "Blobå", + "blobNameDes": "ファイルBlobã®åå‰ã§ã™ã€‚<0>マジック変数を使用ã§ãã¾ã™ã€‚短時間ã«åŒã˜ãƒ•ァイルを複数回アップロードã—ãŸå ´åˆã§ã‚‚ã€çµ¶å¯¾çš„ãªä¸€æ„性を確ä¿ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚", + "basicInfo": "基本情報", + "editX": "{{name}} を編集", + "noGroupBinded": "ユーザーグループãŒãƒã‚¤ãƒ³ãƒ‰ã•れã¦ã„ã¾ã›ã‚“", + "create": "作æˆ", + "addXStoragePolicy": "{{type}} ストレージãƒãƒªã‚·ãƒ¼ã‚’追加", + "loadSummary": "統計データã®èª­ã¿è¾¼ã¿", + "policySummary": "{{count}}個ã®ãƒ•ァイルBlob ({{size}})", + "sharp": "#", + "name": "åå‰", + "type": "種類", + "childFiles": "下ä½ãƒ•ァイル数", + "totalSize": "データé‡", + "actions": "æ“作", + "authSuccess": "èªè¨¼æˆåŠŸ", + "policyDeleted": "ストレージãƒãƒªã‚·ãƒ¼ã‚’削除ã—ã¾ã—ãŸ", + "newStoragePolicy": "ストレージãƒãƒªã‚·ãƒ¼ã‚’追加", + "all": "ã™ã¹ã¦", + "local": "ローカルストレージ", + "remote": "リモートストレージ", + "qiniu": "Qiniu", + "upyun": "Upyun", + "oss": "Alibaba OSS", + "cos": "Tencent COS", + "onedrive": "OneDrive", + "s3": "S3互æ›", + "ks3": "金山雲 KS3", + "obs": "Huawei Cloud OBS", + "load_balance": "è² è·åˆ†æ•£", + "childPolicy": "å­ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼", + "childPolicyDes": "è² è·åˆ†æ•£ã«è¿½åŠ ã™ã‚‹å­ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã‚’é¸æŠžã—ã¦ãã ã•ã„。", + "weight": "Weighté‡ã¿", + "addTargetPolicy": "å­ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã‚’追加", + "selectPolicies": "ストレージãƒãƒªã‚·ãƒ¼ã‚’é¸æŠž", + "selectPoliciesDes": "è² è·åˆ†æ•£ã«è¿½åŠ ã™ã‚‹ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã‚’é¸æŠžã—ã¦ãã ã•ã„。", + "loadBalanceDes": "è² è·åˆ†æ•£ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã‚’使用ã™ã‚‹å ´åˆã€æ–°è¦ã‚¢ãƒƒãƒ—ロードã¯é‡ã¿ã«åŸºã¥ã„ã¦ãƒ©ãƒ³ãƒ€ãƒ ã«ç•°ãªã‚‹å­ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã«åˆ†æ•£ã•れã¾ã™ã€‚", + "xChildPolicies": "{{count}} å­ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼", + "refresh": "æ›´æ–°", + "delete": "削除", + "edit": "編集", + "selectAStorageProvider": "ストレージ方å¼ã‚’é¸æŠž", + "maxSizeOfSingleFile": "ファイルサイズ制é™", + "maxSizeOfSingleFileDes": "å˜ä¸€ãƒ•ã‚¡ã‚¤ãƒ«ã®æœ€å¤§ã‚µã‚¤ã‚ºã€‚0を入力ã™ã‚‹ã¨ã€å˜ä¸€ãƒ•ァイルサイズã«åˆ¶é™ã¯ã‚りã¾ã›ã‚“。", + "enterFileExt": "空欄ã¯ãƒ•ァイル拡張å­ã®åˆ¶é™ãªã—を示ã—ã¾ã™ã€‚複数指定ã™ã‚‹å ´åˆã¯åŠè§’カンマ「,ã€ã§åŒºåˆ‡ã£ã¦ãã ã•ã„。", + "extList": "ファイル拡張å­åˆ¶é™", + "noLimit": "無制é™", + "whitelist": "許å¯ãƒªã‚¹ãƒˆ", + "blacklist": "ブロックリスト", + "fileNameRegex": "ãƒ•ã‚¡ã‚¤ãƒ«åæ­£è¦è¡¨ç¾ãƒ«ãƒ¼ãƒ«", + "fileNameRegexDes": "ファイルåã«ãƒžãƒƒãƒã™ã‚‹æ­£è¦è¡¨ç¾ã€‚空白ã®å ´åˆã¯åˆ¶é™ãªã—。", + "chunkSizeDes": "ãƒãƒ£ãƒ³ã‚¯ã‚¢ãƒƒãƒ—ロード時ã®ãƒãƒ£ãƒ³ã‚¯ã‚µã‚¤ã‚ºã‚’指定ã—ã¦ãã ã•ã„。0 を入力ã™ã‚‹ã¨ãƒãƒ£ãƒ³ã‚¯ã‚¢ãƒƒãƒ—ロードを使用ã›ãšã€æœ€å¤§ã‚¢ãƒƒãƒ—ロードサイズã¯Webサーãƒãƒ¼ã«ã‚ˆã£ã¦åˆ¶é™ã•れるå¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚", + "chunkSizeDesSuffix": "{{prefix}}ãƒãƒ£ãƒ³ã‚¯ã‚¢ãƒƒãƒ—ロードを使用ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒã‚¢ãƒƒãƒ—ロードã™ã‚‹ãƒ•ァイルã¯ãƒãƒ£ãƒ³ã‚¯ã«åˆ†å‰²ã•れã€å€‹åˆ¥ã«ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã«ã‚¢ãƒƒãƒ—ロードã•れã¾ã™ã€‚アップロードãŒä¸­æ–­ã•れãŸå ´åˆã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯å‰å›žã‚¢ãƒƒãƒ—ロードã—ãŸãƒãƒ£ãƒ³ã‚¯ã®ç¶šãã‹ã‚‰ã‚¢ãƒƒãƒ—ロードをå†é–‹ã§ãã¾ã™ã€‚", + "chunkSize": "アップロードãƒãƒ£ãƒ³ã‚¯ã‚µã‚¤ã‚º", + "policyName": "ストレージãƒãƒªã‚·ãƒ¼ã®è¡¨ç¤ºå。ユーザーã«ã‚‚表示ã•れã¾ã™ã€‚", + "magicVar": { + "fileNameMagicVar": "ファイルåマジック変数", + "pathMagicVar": "パスマジック変数", + "variable": "マジック変数", + "description": "説明", + "example": "例", + "16digitsRandomString": "16ビットã®ãƒ©ãƒ³ãƒ€ãƒ æ–‡å­—列", + "8digitsRandomString": "8ビットã®ãƒ©ãƒ³ãƒ€ãƒ æ–‡å­—列", + "secondTimestamp": "ç§’å˜ä½ã®ã‚¿ã‚¤ãƒ ã‚¹ã‚¿ãƒ³ãƒ—", + "nanoTimestamp": "ナノ秒å˜ä½ã®ã‚¿ã‚¤ãƒ ã‚¹ã‚¿ãƒ³ãƒ—", + "uid": "ユーザーID", + "originalFileName": "å…ƒã®ãƒ•ァイルå", + "originFileNameNoext": "æ‹¡å¼µå­ãªã—ã®å…ƒã®ãƒ•ァイルå", + "extension": "ファイル拡張å­", + "uuidV4": "UUID V4", + "date": "日付", + "dateAndTime": "日時", + "randomNumber": "範囲内ã®ä¹±æ•°", + "year": "å¹´", + "month": "月", + "day": "æ—¥", + "hour": "時", + "minute": "分", + "second": "ç§’", + "path": "ユーザーファイルアップロード時ã®åˆæœŸãƒ‘ス" + }, + "storageBucket": "ストレージ", + "wanSiteURLDes": "ã“ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã‚’使用ã™ã‚‹å‰ã«ã€ã€Œãƒ‘ラメーター設定 - サイト情報 - サイトURLã€ã«å…¥åŠ›ã—ãŸã‚¢ãƒ‰ãƒ¬ã‚¹ãŒå®Ÿéš›ã¨ä¸€è‡´ã—ã€ã‹ã¤<0>外部ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã‹ã‚‰ã‚¢ã‚¯ã‚»ã‚¹å¯èƒ½ã§ã‚ã‚‹ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。", + "enterQiniuBucket": "<0>Qiniuクラウドコントロールパãƒãƒ«ã«ã‚¢ã‚¯ã‚»ã‚¹ã—ã¦ã‚ªãƒ–ジェクトストレージリソースを作æˆã—ã¦ãã ã•ã„。「ストレージスペースåã€ã«ã¯ã€Qiniuã§ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚¹ãƒšãƒ¼ã‚¹ã‚’作æˆã—ãŸéš›ã«æŒ‡å®šã—ãŸåå‰ã‚’入力ã—ã¾ã™ã€‚", + "qiniuBucketName": "ストレージスペースå", + "cosObsBucketName": "ãƒã‚±ãƒƒãƒˆå", + "bucketType": "ãƒã‚±ãƒƒãƒˆèª­ã¿æ›¸ã権é™", + "bucketTypeDes": "作æˆã—ãŸã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚¹ãƒšãƒ¼ã‚¹ã®èª­ã¿æ›¸ã権é™ã®ç¨®é¡žã‚’é¸æŠžã—ã¦ãã ã•ã„。", + "aclType": "アクセス制御タイプ", + "accessTypePulic": "パブリックリードプライベートライト", + "accessTypePrivate": "プライベートリードライト", + "accessType": "アクセス権é™", + "privateBucket": "プライベート", + "privateDes": "Cloudreveã¯ãƒ•ァイルURLã«ç½²åã—ã¾ã™ã€‚", + "publicBucket": "パブリック読ã¿å–り", + "publicStorage": "公開", + "publicDes": "é¸æŠžã—ãªã„ã“ã¨ã‚’推奨ã—ã¾ã™ã€‚Cloudreveã¯ãƒ•ァイルã®ç›´ãƒªãƒ³ã‚¯ã‚’直接返ã—ã€ãƒ•ァイルã®ã‚¢ã‚¯ã‚»ã‚¹æ¨©é™ã‚’効果的ã«åˆ¶å¾¡ã§ãã¾ã›ã‚“。", + "bucketCDNDes": "ストレージã«ãƒã‚¤ãƒ³ãƒ‰ã•れãŸCDN加速ドメインを入力ã—ã¦ãã ã•ã„。", + "bucketCDNDomain": "CDN加速ドメイン", + "qiniuCredentialDes": "Qiniuã®ç®¡ç†ãƒ‘ãƒãƒ«ã§ã€Œå€‹äººä¸­å¿ƒã€-「キー管ç†ã€ã«é€²ã¿ã€å–å¾—ã—ãŸAKã€SKを入力ã—ã¦ãã ã•ã„。", + "ak": "AK", + "sk": "SK", + "cannotEnableForPrivateBucket": "プライベート空間ã§å¤–リンク機能を有効ã«ã—ãŸå¾Œã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã§ã€Œãƒªãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆã«ã‚ˆã‚‹å¤–リンクã®ä½¿ç”¨ã€ã‚’有効ã«ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚有効ã«ã—ãªã„ã¨ã€å¤–リンクを正常ã«ç”Ÿæˆã§ãã¾ã›ã‚“。", + "chunkSizeLabelQiniu": "ãƒãƒ£ãƒ³ã‚¯ã‚¢ãƒƒãƒ—ロード時ã®ãƒãƒ£ãƒ³ã‚¯ã‚µã‚¤ã‚ºã‚’指定ã—ã¦ãã ã•ã„。範囲ã¯1MB~1GBã§ã™ã€‚", + "corsSettingStep": "CORSãƒãƒªã‚·ãƒ¼", + "corsPolicyAdded": "CORSãƒãƒªã‚·ãƒ¼ãŒè¿½åŠ ã•れã¾ã—ãŸã€‚", + "createOSSBucketDes": "<0>OSS管ç†ã‚³ãƒ³ã‚½ãƒ¼ãƒ« ã«ã‚¢ã‚¯ã‚»ã‚¹ã—ã¦Bucketを作æˆã§ãã¾ã™ã€‚<1>標準ストレージã¨<2>低頻度アクセスタイプã®Bucketã®ã¿ã‚µãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã™ã€‚", + "bucketName": "Bucketå", + "publicReadBucket": "パブリック読ã¿å–り", + "ossEndpointDes": "作æˆã—ãŸBucketã®æ¦‚è¦ãƒšãƒ¼ã‚¸ã«ç§»å‹•ã—ã€ã€Œ<0>ã‚¢ã‚¯ã‚»ã‚¹ãƒ‰ãƒ¡ã‚¤ãƒ³ã€æ¬„ã®ã€Œ<1>インターãƒãƒƒãƒˆã‚¢ã‚¯ã‚»ã‚¹ã€è¡Œã«ã‚る「<2>エンドãƒã‚¤ãƒ³ãƒˆï¼ˆãƒªãƒ¼ã‚¸ãƒ§ãƒ³ãƒŽãƒ¼ãƒ‰ï¼‰ã€ã‚’入力ã—ã¦ãã ã•ã„。", + "ossEndpointDesInternalHint": "イントラãƒãƒƒãƒˆã¾ãŸã¯ã‚«ã‚¹ã‚¿ãƒ ãƒ‰ãƒ¡ã‚¤ãƒ³ã®ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã‚’設定ã™ã‚‹å¿…è¦ãŒã‚ã‚‹å ´åˆã¯ã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã‚’作æˆã—ãŸå¾Œã«è¨­å®šã§ãã¾ã™ã€‚", + "obsEndpointCnameHint": "カスタムドメインエンドãƒã‚¤ãƒ³ãƒˆã®è¨­å®šã¯ã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ä½œæˆå¾Œã«è¡Œãˆã¾ã™ã€‚", + "endpoint": "エンドãƒã‚¤ãƒ³ãƒˆ", + "ossLANEndpointDes": "空欄ã®å ´åˆã¯ä½¿ç”¨ã—ã¾ã›ã‚“。CloudreveãŒAlibabaã®è¨ˆç®—サービスã«ãƒ‡ãƒ—ロイã•れã¦ãŠã‚Šã€OSSã¨åŒä¸€ã®å¯ç”¨æ€§ã‚¾ãƒ¼ãƒ³ã«ã‚ã‚‹å ´åˆã€ã‚¤ãƒ³ãƒˆãƒ©ãƒãƒƒãƒˆ エンドãƒã‚¤ãƒ³ãƒˆã‚’指定ã™ã‚‹ã“ã¨ã§ãƒˆãƒ©ãƒ•ィックコストを削減ã§ãã¾ã™ã€‚Cloudreveã¯æ¡ä»¶ã‚’満ãŸã™ã¨ã‚¤ãƒ³ãƒˆãƒ©ãƒãƒƒãƒˆã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã«åˆ‡ã‚Šæ›¿ãˆã¦ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’é€ä¿¡ã—ã¾ã™ã€‚", + "intranetEndPoint": "イントラãƒãƒƒãƒˆ エンドãƒã‚¤ãƒ³ãƒˆ", + "ossCDNDes": "Alibaba CDNを使用ã—ã¦OSSã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã‚’高速化ã—ã¾ã™ã‹ï¼Ÿ", + "createOSSCDNDes": "<0>AlibabaCDN管ç†ã‚³ãƒ³ã‚½ãƒ¼ãƒ«ã«ã‚¢ã‚¯ã‚»ã‚¹ã—ã¦CDN加速ドメインを作æˆã—ã€ã‚ªãƒªã‚¸ãƒ³ã‚µãƒ¼ãƒãƒ¼ã«ä½œæˆã—ãŸã°ã‹ã‚Šã®OSSãƒã‚±ãƒƒãƒˆã‚’設定ã—ã¾ã™ã€‚下記ã«CDN加速ドメインを入力ã—ã€HTTPSを使用ã™ã‚‹ã‹ã©ã†ã‹ã‚’é¸æŠžã—ã¾ã™ã€‚", + "ossAKDes": "Alibabaã®<0>セキュリティ情報管ç†ãƒšãƒ¼ã‚¸ã§AccessKeyã‚’å–å¾—ã—ã¾ã™ã€‚<1>RAMアクセス制御ã§<2>AliyunOSSFullAccess権é™ã‚’æŒã¤AccessKeyを作æˆã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚", + "shouldNotContainSpace": "スペースをå«ã‚ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。", + "nameThePolicyFirst": "ã“ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã«åå‰ã‚’付ã‘ã¾ã™ã€‚", + "chunkSizeLabelOSS": "ãƒãƒ£ãƒ³ã‚¯ã‚¢ãƒƒãƒ—ロード時ã®ãƒãƒ£ãƒ³ã‚¯ã‚µã‚¤ã‚ºã‚’指定ã—ã¦ãã ã•ã„。範囲ã¯100 KB~5 GBã§ã™ã€‚", + "ossCORSDes": "ã“ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã‚’使用ã™ã‚‹ã«ã¯ã€ä¸Šè¨˜ã®ã‚¯ãƒ­ã‚¹ã‚ªãƒªã‚¸ãƒ³ãƒªã‚½ãƒ¼ã‚¹å…±æœ‰ï¼ˆCORS)ãƒãƒªã‚·ãƒ¼ã‚’æ­£ã—ã設定ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚CloudreveãŒè‡ªå‹•ã§è¨­å®šã™ã‚‹ã“ã¨ã‚‚ã€æ‰‹å‹•ã§è¨­å®šã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚ã“ã®ãƒã‚±ãƒƒãƒˆã®ã‚¯ãƒ­ã‚¹ã‚ªãƒªã‚¸ãƒ³ãƒªã‚½ãƒ¼ã‚¹å…±æœ‰ï¼ˆCORS)ãƒãƒªã‚·ãƒ¼ã‚’æ—¢ã«è¨­å®šã—ã¦ã„ã‚‹å ´åˆã¯ã€ã“ã®æ‰‹é †ã‚’スキップã§ãã¾ã™ã€‚", + "letCloudreveHelpMe": "Cloudreveã«è¨­å®šã•ã›ã‚‹", + "skip": "スキップ", + "createUpyunBucketDes": "<0>Upyunパãƒãƒ«ã§ä½œæˆã—ãŸã‚¯ãƒ©ã‚¦ãƒ‰ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚µãƒ¼ãƒ“スåを入力ã—ã¾ã™ã€‚", + "storageServiceName": "サービスå", + "operatorName": "オペレーターå", + "operatorPassword": "オペレーターパスワード", + "tokenStatus": "Token盗難防止", + "upyunTokenDes": "Token盗難防止を有効ã«ã™ã‚‹ã“ã¨ã‚’å¼·ããŠå‹§ã‚ã—ã¾ã™ã€‚作æˆã—ãŸã‚¯ãƒ©ã‚¦ãƒ‰ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚µãƒ¼ãƒ“スã®<0>機能設定パãƒãƒ«ã«ã‚¢ã‚¯ã‚»ã‚¹ã—ã€<1>アクセス制御タブã§Token盗難防止を有効ã«ã—ã¦ãƒ‘スワードを設定ã—ã¦ãã ã•ã„。", + "tokenEnabled": "Tokenç›—é›£é˜²æ­¢ãŒæœ‰åйã«ãªã£ã¦ã„ã¾ã™", + "tokenDisabled": "Token盗難防止未有効化", + "upyunTokenSecretDes": "設定ã—ãŸToken盗難防止キーを入力ã—ã¦ãã ã•ã„。", + "upyunTokenSecret": "Token盗難防止キー", + "createCOSBucketDes": "<0>COS管ç†ã‚³ãƒ³ã‚½ãƒ¼ãƒ« ã«ã‚¢ã‚¯ã‚»ã‚¹ã—ã¦ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒã‚±ãƒƒãƒˆã‚’作æˆã—ã€ä½œæˆã—ãŸãƒã‚±ãƒƒãƒˆã®åŸºæœ¬è¨­å®šãƒšãƒ¼ã‚¸ã«ç§»å‹•ã—ã¦ã€<1>ãƒã‚±ãƒƒãƒˆå を上記ã«å…¥åŠ›ã—ã¦ãã ã•ã„。", + "obsBucketDes": "<0>OBS管ç†ã‚³ãƒ³ã‚½ãƒ¼ãƒ« ã«ã‚¢ã‚¯ã‚»ã‚¹ã—ã¦ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒã‚±ãƒƒãƒˆã‚’作æˆã—ã€<1>ãƒã‚±ãƒƒãƒˆå を上記ã«å…¥åŠ›ã—ã¦ãã ã•ã„。ストレージãƒã‚±ãƒƒãƒˆã®ç¨®é¡žã¯<2>標準ストレージã¾ãŸã¯<3>低頻度アクセスストレージã®ã¿ã‚µãƒãƒ¼ãƒˆã•れã¦ã„ã¾ã™ã€‚", + "cosPrivateRW": "ãƒ—ãƒ©ã‚¤ãƒ™ãƒ¼ãƒˆèª­ã¿æ›¸ã", + "cosPublicRW": "パブリック読ã¿è¾¼ã¿ã€ãƒ—ライベート書ãè¾¼ã¿", + "cosAccessDomainDes": "作æˆã—ãŸãƒã‚±ãƒƒãƒˆã®æ¦‚è¦ãƒšãƒ¼ã‚¸ã§ã€<0>ドメイン情報欄ã«è¡¨ç¤ºã•れã¦ã„ã‚‹<1>アクセスドメインを入力ã—ã¦ãã ã•ã„。自身ã§ãƒã‚¤ãƒ³ãƒ‰ã—ãŸã‚ªãƒªã‚¸ãƒ³ã‚µãƒ¼ãƒãƒ¼ã®ãƒ‰ãƒ¡ã‚¤ãƒ³åã¾ãŸã¯CDNアクセラレーションドメインを使用ã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚", + "obsEndpointDes": "作æˆã—ãŸã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒã‚±ãƒƒãƒˆã®æ¦‚è¦ãƒšãƒ¼ã‚¸ã§ã€<0>ドメイン情報欄ã«è¡¨ç¤ºã•れã¦ã„ã‚‹<1>エンドãƒã‚¤ãƒ³ãƒˆã‚’入力ã—ã¦ãã ã•ã„。", + "accessDomain": "アクセスドメイン", + "cosCDNDomainDes": "<0>テンセントクラウドCDN管ç†ã‚³ãƒ³ã‚½ãƒ¼ãƒ« ã«ã‚¢ã‚¯ã‚»ã‚¹ã—ã¦CDNアクセラレーションドメインを作æˆã—ã€ã‚ªãƒªã‚¸ãƒ³ã‚µãƒ¼ãƒãƒ¼ã¨ã—ã¦ä½œæˆã—ãŸCOSストレージãƒã‚±ãƒƒãƒˆã‚’設定ã—ã¾ã™ã€‚下記ã«CDNアクセラレーションドメインを入力ã—ã€HTTPSを使用ã™ã‚‹ã‹ã©ã†ã‹ã‚’é¸æŠžã—ã¾ã™ã€‚", + "cosCredentialDes": "テンセントクラウドã®<0>アクセスキーページã§å–å¾—ã—ãŸã‚¢ã‚¯ã‚»ã‚¹ã‚­ãƒ¼ãƒšã‚¢ã‚’入力ã—ã¦ãã ã•ã„。ã“ã®ã‚­ãƒ¼ãƒšã‚¢ã«ã¯COSサービスã¸ã®ã‚¢ã‚¯ã‚»ã‚¹æ¨©é™ãŒä»˜ä¸Žã•れã¦ã„ã‚‹ã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。<1>プログラミングアクセス機能をæŒã¤<2>å­ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’作æˆã—ã€COSサービスã¸ã®ã‚¢ã‚¯ã‚»ã‚¹æ¨©é™ã‚’付与ã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚", + "obsCredentialDes": "ファーウェイクラウドã®<0>アクセスキーページã§å–å¾—ã—ãŸã‚¢ã‚¯ã‚»ã‚¹ã‚­ãƒ¼ãƒšã‚¢ã‚’入力ã—ã¦ãã ã•ã„。<1>プログラミングアクセス機能をæŒã¤<2>IAMユーザーを作æˆã—ã€<3>OBS OperateAccess権é™ã‚’付与ã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚", + "grantAccess": "アカウントèªè¨¼", + "grantAccessLater": "下記ã®ãƒœã‚¿ãƒ³ã‚’クリックã—ã¦ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã‚’作æˆã—ãŸå¾Œã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼è¨­å®šãƒšãƒ¼ã‚¸ã§ã‚¢ã‚«ã‚¦ãƒ³ãƒˆèªè¨¼ã‚’行ã†å¿…è¦ãŒã‚りã¾ã™ã€‚", + "odHttpsWarning": "OneDrive/SharePointストレージãƒãƒªã‚·ãƒ¼ã‚’使用ã™ã‚‹ã«ã¯ã€HTTPSを有効ã«ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚有効ã«ã—ãŸå¾Œã€ãƒ‘ラメータ設定 - サイト情報 - サイトURLã‚’åŒæœŸã—ã¦å¤‰æ›´ã—ã¦ãã ã•ã„。", + "creatAadAppDes": "<0>Microsoft Entra IDコンソール ã«ã‚¢ã‚¯ã‚»ã‚¹ã—ã¦ãƒ­ã‚°ã‚¤ãƒ³ã—ã€ãƒ­ã‚°ã‚¤ãƒ³å¾Œã«<1>Microsoft Entra ID管ç†ãƒ‘ãƒãƒ«ã«ç§»å‹•ã—ã¾ã™ã€‚ã“ã“ã§ãƒ­ã‚°ã‚¤ãƒ³ã™ã‚‹ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¨æœ€çµ‚çš„ã«ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã‚’使用ã™ã‚‹OneDrive所属アカウントã¯ç•°ãªã‚‹å ´åˆãŒã‚りã¾ã™ã€‚", + "createAadAppDes2": "å·¦å´ã®<0>アプリ登録メニューã«ç§»å‹•ã—ã€<1>æ–°è¦ç™»éŒ²ãƒœã‚¿ãƒ³ã‚’クリックã—ã¾ã™ã€‚アプリ登録フォームã«å…¥åŠ›ã—ã¾ã™ã€‚åå‰ã¯ä»»æ„ã§è¨­å®šã§ãã¾ã™ã€‚<2>サãƒãƒ¼ãƒˆã•れã¦ã„るアカウントã®ç¨®é¡žã¯<3>ä»»æ„ã®çµ„織ディレクトリ(任æ„ã®Azure ADディレクトリ - 多テナント)ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¨å€‹äººMicrosoftアカウント(Skypeã€Xboxãªã©ï¼‰ã‚’é¸æŠžã—ã¦ãã ã•ã„。<4>リダイレクトURI(オプション)ã¯<5>Webã‚’é¸æŠžã—ã€<6>{{url}}を入力ã—ã¦ãã ã•ã„。ãã®ä»–ã¯ãƒ‡ãƒ•ォルトã®ã¾ã¾ã«ã—ã¦ãã ã•ã„。", + "aadAppIDDes": "アプリ管ç†ã®<0>概è¦ãƒšãƒ¼ã‚¸ã«ç§»å‹•ã—ã€è¡¨ç¤ºã•れã¦ã„ã‚‹<1>アプリケーション(クライアント)IDã®å€¤ã‚’確èªã—ã¦ãã ã•ã„。", + "entraIdApp": "Entra IDアプリ情報", + "aadAppID": "アプリケーション(クライアント)ID", + "addAppSecretDes": "クライアントシークレットã®ä½œæˆæ–¹æ³•:「<0>証明書ã¨ãƒ‘スワードã€ãƒ¡ãƒ‹ãƒ¥ãƒ¼ï¼ˆã‚¢ãƒ—リ管ç†ãƒšãƒ¼ã‚¸å·¦å´ï¼‰ã‚’é–‹ãã€ã€Œ<1>ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆã‚·ãƒ¼ã‚¯ãƒ¬ãƒƒãƒˆã®æ–°è¦ä½œæˆã€ãƒœã‚¿ãƒ³ã‚’クリックã—ã€ã€Œ<2>有効期é™ã€ã‚’最長期間ã«è¨­å®šã—ã¾ã™ã€‚ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆã‚·ãƒ¼ã‚¯ãƒ¬ãƒƒãƒˆã®æœ‰åŠ¹æœŸé™ãŒåˆ‡ã‚ŒãŸã‚‰ã€å†ä½œæˆã—ã¦ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼è¨­å®šã«å…¥åŠ›ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚", + "aadAppSecret": "クライアントシークレット", + "aadAccountCloud": "Microsoft Graph エンドãƒã‚¤ãƒ³ãƒˆ", + "aadAccountCloudDes": "ã”利用ã®Microsoft 365アカウントã®ç¨®é¡žã«å¿œã˜ã¦ã€é©åˆ‡ãªã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã‚’é¸æŠžã—ã¦ãã ã•ã„。", + "multiTenant": "パブリック(国際版)", + "gallatin": "21Vianet", + "sharePointDes": "ファイルをSharePointã«ä¿å­˜ã—ã¾ã™ã‹ï¼Ÿ", + "saveToOneDrive": "アカウントã®ãƒ‡ãƒ•ォルトã®OneDriveドライブã«ä¿å­˜", + "spSiteURL": "SharePoint サイトアドレス", + "odReverseProxyURLDes": "ファイルダウンロード時ã«ã€ç‹¬è‡ªã®ãƒªãƒãƒ¼ã‚¹ãƒ—ロキシサーãƒãƒ¼ã‚’使用ã™ã‚‹ã‚ˆã†ã«ç½®ãæ›ãˆã¾ã™ã‹ï¼Ÿ", + "odReverseProxyURL": "リãƒãƒ¼ã‚¹ãƒ—ロキシサーãƒãƒ¼ã‚¢ãƒ‰ãƒ¬ã‚¹", + "chunkSizeDesOd": "許容範囲:5 MB~5 GB。OneDriveã§ã¯ã€320 KiB(327,680ãƒã‚¤ãƒˆï¼‰ã®æ•´æ•°å€ã§ã‚ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚", + "limitOdTPSDes": "OneDrive APIリクエスト頻度ã®åˆ¶é™", + "tps": "TPS制é™", + "tpsDes": "空欄ã®å ´åˆã¯åˆ¶é™ãªã—。ã“ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ãŒOneDriveã«é€ä¿¡ã™ã‚‹APIãƒªã‚¯ã‚¨ã‚¹ãƒˆã®æ¯Žç§’最大数を制é™ã—ã¾ã™ã€‚ã“ã®é »åº¦ã‚’è¶…ãˆã‚‹ãƒªã‚¯ã‚¨ã‚¹ãƒˆã¯ãƒ¬ãƒ¼ãƒˆåˆ¶é™ã•れã¾ã™ã€‚複数ã®Cloudreveノードã§ãƒ•ァイルを転é€ã™ã‚‹å ´åˆã€ãれãžã‚ŒãŒç‹¬è‡ªã®ãƒ¬ãƒ¼ãƒˆåˆ¶é™ãƒã‚±ãƒƒãƒˆã‚’使用ã™ã‚‹ãŸã‚ã€çжæ³ã«å¿œã˜ã¦ã“ã®å€¤ã‚’比例的ã«ä¸‹ã’ã¦ãã ã•ã„。Webã‹ã‚‰ã®ã‚¢ãƒƒãƒ—ロードリクエストã¯ã€ã“ã®åˆ¶é™ã®å¯¾è±¡å¤–ã§ã™ã€‚", + "tpsBurst": "TPSãƒãƒ¼ã‚¹ãƒˆãƒªã‚¯ã‚¨ã‚¹ãƒˆ", + "tpsBurstDes": "リクエストãŒã‚¢ã‚¤ãƒ‰ãƒ«çŠ¶æ…‹ã®å ´åˆã€Cloudreveã¯æŒ‡å®šã•ã‚ŒãŸæ•°ã®æž ã‚’å°†æ¥ã®ãƒãƒ¼ã‚¹ãƒˆãƒˆãƒ©ãƒ•ィック用ã«äºˆç´„ã§ãã¾ã™ã€‚", + "odOauthDes": "ãŸã ã—ã€ä»¥ä¸‹ã®ãƒœã‚¿ãƒ³ã‚’クリックã—ã¦OneDriveã§ãƒ­ã‚°ã‚¤ãƒ³èªè¨¼ã‚’行ã„ã€åˆæœŸåŒ–を完了ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚ãã®å¾Œã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ä¸€è¦§ãƒšãƒ¼ã‚¸ã§èªè¨¼ã‚’やり直ã™ã“ã¨ãŒã§ãã¾ã™ã€‚", + "gotoAuthPage": "èªè¨¼ãƒšãƒ¼ã‚¸ã¸ç§»å‹•", + "s3BucketDes": "AWS S3コンソールã«ã‚¢ã‚¯ã‚»ã‚¹ã—ã¦ãƒã‚±ãƒƒãƒˆã‚’作æˆã—ã€ä½œæˆæ™‚ã«æŒ‡å®šã—ãŸ<0>ãƒã‚±ãƒƒãƒˆåを入力ã—ã¦ãã ã•ã„。", + "s3EndpointDes": "ãƒã‚±ãƒƒãƒˆã®ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆï¼ˆãƒªãƒ¼ã‚¸ãƒ§ãƒ³ãƒŽãƒ¼ãƒ‰ï¼‰ã‚’完全ãªURLå½¢å¼ã§æŒ‡å®šã—ã¦ãã ã•ã„(例:<0>https://bucket.region.example.com)。", + "selectRegionDes": "ãƒã‚±ãƒƒãƒˆãŒå­˜åœ¨ã™ã‚‹ãƒªãƒ¼ã‚¸ãƒ§ãƒ³ã‚³ãƒ¼ãƒ‰ã‚’入力ã—ã¦ãã ã•ã„(例:<0>us-east-1)。AWS以外ã®S3互æ›ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒ—ロãƒã‚¤ãƒ€ãƒ¼ã®å ´åˆã¯ã€ãã®ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’å‚ç…§ã—ã¦å…¥åŠ›æ–¹æ³•ã‚’ç¢ºèªã—ã¦ãã ã•ã„。", + "chunkSizeLabelS3": "分片アップロード時ã®åˆ†ç‰‡ã‚µã‚¤ã‚ºã‚’指定ã—ã¦ãã ã•ã„(5MB~5GB)。", + "policyEndpoint": "エンドãƒã‚¤ãƒ³ãƒˆ", + "s3Region": "リージョンコード", + "s3EndpointPathStyle": "パス形å¼ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã®å¼·åˆ¶ä½¿ç”¨ã‚’é¸æŠžã—ã¦ãã ã•ã„。一部ã®ã‚µãƒ¼ãƒ‰ãƒ‘ーティ製S3互æ›ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã§ã¯ã€ã“ã®ã‚ªãƒ—ã‚·ãƒ§ãƒ³ã‚’é¸æŠžã™ã‚‹å¿…è¦ãŒã‚ã‚‹å ´åˆãŒã‚りã¾ã™ã€‚有効ã«ã™ã‚‹ã¨ã€<0>http://s3.amazonaws.com/BUCKET/KEYã®ã‚ˆã†ãªãƒ‘ス形å¼ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ãŒå¼·åˆ¶çš„ã«ä½¿ç”¨ã•れã¾ã™ã€‚", + "usePathEndpoint": "パス形å¼ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆå¼·åˆ¶", + "thumbExt": "サムãƒã‚¤ãƒ«ã‚’生æˆã§ãるファイル拡張å­", + "thumbExtDes": "空欄ã®å ´åˆã¯ã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã®ãƒ—リセット集åˆã‚’使用ã—ã¾ã™ã€‚ローカルã€S3ストレージãƒãƒªã‚·ãƒ¼ã«ã¯ç„¡åйã§ã™ã€‚", + "driverRoot": "ドライブルートディレクトリ", + "driverRootDes": "OneDriveアカウント内ã§ãƒ•ァイルをä¿å­˜ã™ã‚‹å ´æ‰€ã‚’é¸æŠžã—ã¦ãã ã•ã„。ã“ã®ã‚ªãƒ—ションを変更ã™ã‚‹ã¨ã€ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã«æ—¢ã«å­˜åœ¨ã™ã‚‹ãƒ•ァイルã«ã‚¢ã‚¯ã‚»ã‚¹ã§ããªããªã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚", + "saveToDefaultOneDrive": "ファイルをデフォルトã®OneDriveドライブã«ä¿å­˜", + "saveToSharePoint": "SharePointã«ãƒ•ァイルをä¿å­˜", + "sharePointUrlDes": "SharePointサイトã®URLを入力ã—ã¦ãã ã•ã„。フォーカスãŒå¤–れるã¨ã€ã‚·ã‚¹ãƒ†ãƒ ãŒè‡ªå‹•çš„ã«æ­£ã—ã„ドライブ識別å­ã«å¤‰æ›ã—ã¾ã™ã€‚", + "ks3selectRegionDes": "ãƒã‚±ãƒƒãƒˆãŒå­˜åœ¨ã™ã‚‹ãƒªãƒ¼ã‚¸ãƒ§ãƒ³ã‚³ãƒ¼ãƒ‰ã‚’入力ã—ã¦ãã ã•ã„(例:<0>BEIJING)。", + "ks3EndpointPathStyle": "パス形å¼ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã®å¼·åˆ¶ä½¿ç”¨ã‚’é¸æŠžã—ã¦ãã ã•ã„。", + "ossRegionDes": "ãƒã‚±ãƒƒãƒˆãŒå­˜åœ¨ã™ã‚‹ãƒªãƒ¼ã‚¸ãƒ§ãƒ³ã‚³ãƒ¼ãƒ‰ã‚’入力ã—ã¦ãã ã•ã„(例:<0>cn-hangzhou)。<1>OSSリージョンã¨ã‚¨ãƒ³ãƒ‰ãƒã‚¤ãƒ³ãƒˆã®è¡¨ã§å¯¾å¿œã™ã‚‹ãƒªãƒ¼ã‚¸ãƒ§ãƒ³ã‚’見ã¤ã‘ã€å¯¾å¿œã™ã‚‹<2>リージョンIDを入力ã§ãã¾ã™ã€‚" + }, + "node": { + "slave": "スレーブ", + "master": "マスター", + "noCapabilities": "機能ã¯ç„¡åйã§ã™", + "active": "有効", + "suspended": "無効", + "deleteNodeConfirmation": "ノード「{{name}}ã€ã‚’削除ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ", + "editNode": "ノード「{{node}}ã€ã‚’編集", + "thisIsMasterNodes": "ç¾åœ¨ã“ã®ã‚µã‚¤ãƒˆã‚’サービスã—ã¦ã„ã‚‹Cloudreveインスタンスã§ã‚るマスターノードを編集ã—ã¦ã„ã¾ã™ã€‚", + "enableNode": "ノードを有効化", + "enableNodeDes": "ノードを有効化ã™ã‚‹ã¨ã€æœ‰åŠ¹åŒ–ã•ã‚ŒãŸæ©Ÿèƒ½ã®å‡¦ç†ã‚’å—ã‘付ã‘ã¾ã™ã€‚", + "name": "åå‰", + "nameNode": "ノードå。ユーザーã«ã‚‚表示ã•れã¾ã™ã€‚", + "type": "種類", + "server": "ノードアドレス", + "serverDes": "ノードã¨é€šä¿¡ã™ã‚‹ãŸã‚ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã§ã™ã€‚ã“ã®ãƒŽãƒ¼ãƒ‰ã«ãƒ•ァイルをä¿å­˜ã™ã‚‹å ´åˆã€ã“ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã¯ãƒ¦ãƒ¼ã‚¶ãƒ¼å´ã«ã‚‚ファイルアップロードã®ãŸã‚ã«å…¬é–‹ã•れã¾ã™ã€‚", + "loadBalancerRankDes": "ã“ã®ãƒŽãƒ¼ãƒ‰ã«è² è·åˆ†æ•£ã®ã‚¦ã‚§ã‚¤ãƒˆã‚’指定ã—ã¾ã™ã€‚整数値ã§ã€å€¤ãŒå¤§ãã„ã»ã©ãƒŽãƒ¼ãƒ‰ãŒé¸æŠžã•れる確率ãŒé«˜ããªã‚Šã¾ã™ã€‚", + "loadBalancerRank": "è² è·åˆ†æ•£ã‚¦ã‚§ã‚¤ãƒˆ", + "slaveSecret": "スレーブキー", + "slaveSecretDes": "スレーブノードã¨ãƒžã‚¹ã‚¿ãƒ¼ãƒŽãƒ¼ãƒ‰é–“ã®é€šä¿¡ã«ä½¿ç”¨ã™ã‚‹ã‚­ãƒ¼ã§ã™ã€‚スレーブå´ã®è¨­å®šãƒ•ァイルã®<0>Slaveタグ下ã®<1>Secretã¨ä¸€è‡´ã•ã›ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚", + "testNode": "ノード通信ã®ãƒ†ã‚¹ãƒˆ", + "testNodeSuccess": "ノード通信æˆåŠŸ", + "createArchiveDes": "圧縮ファイル作æˆã‚¿ã‚¹ã‚¯ã®è¦æ±‚ã‚’å—ã‘付ã‘ã¾ã™ã€‚", + "extractArchiveDes": "è§£å‡ãƒ•ァイルタスクã®è¦æ±‚ã‚’å—ã‘付ã‘ã¾ã™ã€‚", + "remoteDownloadDes": "オフラインダウンロードタスクã®è¦æ±‚ã‚’å—ã‘付ã‘ã¾ã™ã€‚有効化ã—ãŸå ´åˆã¯ã€ä¸‹è¨˜ã§ã‚ªãƒ•ラインダウンロードã«é–¢ã™ã‚‹æƒ…報を設定ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚", + "downloader": "ダウンローダー", + "aria2Des": "Cloudreveã¨åŒã˜ãƒ¦ãƒ¼ã‚¶ãƒ¼/権é™ã§ã€ã‚¿ãƒ¼ã‚²ãƒƒãƒˆãƒŽãƒ¼ãƒ‰ã‚µãƒ¼ãƒãƒ¼ä¸Šã«Aria2ã‚’èµ·å‹•ã—ã¦ãã ã•ã„。Aria2ã®è¨­å®šãƒ•ァイルã§RPCサービスを有効化ã—ã¦ãã ã•ã„。詳細ã¯ã€ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã®ã€Œã‚ªãƒ•ラインダウンロードã€ã‚»ã‚¯ã‚·ãƒ§ãƒ³ã‚’å‚ç…§ã—ã¦ãã ã•ã„。", + "qbittorrentDes": "Cloudreveã¨åŒã˜ãƒ¦ãƒ¼ã‚¶ãƒ¼/権é™ã§ã€ã‚¿ãƒ¼ã‚²ãƒƒãƒˆãƒŽãƒ¼ãƒ‰ã‚µãƒ¼ãƒãƒ¼ä¸Šã«qBittorrentã‚’èµ·å‹•ã—ã¦ãã ã•ã„。qBittorrentã®è¨­å®šã§ã€ŒWeb UIã€ã‚µãƒ¼ãƒ“スを有効化ã—ã¦ãã ã•ã„。詳細ã¯ã€ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã®ã€Œã‚ªãƒ•ラインダウンロードã€ã‚»ã‚¯ã‚·ãƒ§ãƒ³ã‚’å‚ç…§ã—ã¦ãã ã•ã„。", + "rpcServer": "RPCサーãƒãƒ¼ã‚¢ãƒ‰ãƒ¬ã‚¹", + "rpcServerHelpDes": "ãƒãƒ¼ãƒˆç•ªå·ã‚’å«ã‚€å®Œå…¨ãªRPCサーãƒãƒ¼ã‚¢ãƒ‰ãƒ¬ã‚¹ã€‚例:<0>http://127.0.0.1:6800/", + "rpcToken": "RPCèªè¨¼ãƒˆãƒ¼ã‚¯ãƒ³", + "rpcTokenDes": "Aria2ã®è¨­å®šãƒ•ァイルã®<0>rpc-secretã¨ä¸€è‡´ã•ã›ã¦ãã ã•ã„。設定ã—ã¦ã„ãªã„å ´åˆã¯ç©ºæ¬„ã«ã—ã¦ãã ã•ã„。", + "downloaderOptionDes": "ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã‚¿ã‚¹ã‚¯ä½œæˆæ™‚ã«è¿½åŠ ã™ã‚‹ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ãƒžãƒãƒ¼ã‚¸ãƒ£ãƒ¼ã®è¨­å®šã§ã™ã€‚JSONã®ã‚­ãƒ¼ãƒãƒªãƒ¥ãƒ¼ãƒšã‚¢å½¢å¼ã§è¨˜è¿°ã—ã¦ãã ã•ã„。詳細ã¯<0>ダウンロードマãƒãƒ¼ã‚¸ãƒ£ãƒ¼ã®å…¬å¼ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’å‚ç…§ãã ã•ã„。", + "refreshInterval": "状態更新間隔(秒)", + "refreshIntervalDes": "CloudreveãŒãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ãƒžãƒãƒ¼ã‚¸ãƒ£ãƒ¼ã«ã‚¿ã‚¹ã‚¯çŠ¶æ…‹ã®æ›´æ–°ã‚’è¦æ±‚ã™ã‚‹é–“éš”ã§ã™ã€‚å®Ÿéš›ã®æ›´æ–°é–“éš”ã¯ã€ã€Œã‚ªãƒ•ラインダウンロードã€ã‚­ãƒ¥ãƒ¼ã®è¨­å®šã¨è² è·çжæ³ã«ã‚‚ä¾å­˜ã—ã¾ã™ã€‚", + "waitForSeeding": "シード完了待ã¡", + "waitForSeedingDes": "有効ã«ã™ã‚‹ã¨ã€ã‚ªãƒ•ラインダウンロードタスク完了後ã€ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ãƒžãƒãƒ¼ã‚¸ãƒ£ãƒ¼ã®è¨­å®šã§ã‚·ãƒ¼ãƒ‰å®Œäº†æ¡ä»¶ã‚’満ãŸã™ã¾ã§ã€ã‚·ãƒ¼ãƒ‰çŠ¶æ…‹ã‚’ç¶­æŒã—ã¾ã™ã€‚シード待ã¡ã¯ã‚ªãƒ•ラインダウンロードタスク完了後ã«è¡Œã‚れã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«ã‚ˆã‚‹ãƒ•ァイルã®ä½¿ç”¨ã«ã¯å½±éŸ¿ã—ã¾ã›ã‚“。", + "webUIEndpoint": "Web UIアドレス", + "webUIEndpointDes": "qBittorrentã®Web UIアドレス。例:<0>http://127.0.0.1:8080/", + "tempPath": "一時ダウンロードディレクトリ", + "tempPathDes": "ノード上ã§ã‚ªãƒ•ラインダウンロードファイルを一時的ã«ä¿å­˜ã™ã‚‹ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã§ã™ã€‚ノード上ã®CloudreveプロセスãŒã“ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«å¯¾ã™ã‚‹èª­ã¿å–ã‚Šã€æ›¸ãè¾¼ã¿ã€å®Ÿè¡Œæ¨©é™ã‚’å¿…è¦ã¨ã—ã€ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ãƒžãƒãƒ¼ã‚¸ãƒ£ãƒ¼ã‚‚ã“ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«ã‚¢ã‚¯ã‚»ã‚¹ã§ãã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚空欄ã®å ´åˆã¯ã€ãƒ‡ãƒ•ォルトã®ä¸€æ™‚ファイルパスãŒä½¿ç”¨ã•れã¾ã™ã€‚", + "webUIUsername": "Web UIユーザーå", + "webUIPassword": "Web UIパスワード", + "webUICredDes": "èªè¨¼ã‚’有効ã«ã—ã¦ã„ãªã„å ´åˆã¯ã€ç©ºæ¬„ã«ã—ã¦ãã ã•ã„。", + "downloaderTestPass": "ダウンロードマãƒãƒ¼ã‚¸ãƒ£ãƒ¼ã¸ã®æŽ¥ç¶šã«æˆåŠŸã—ã¾ã—ãŸã€‚ãƒãƒ¼ã‚¸ãƒ§ãƒ³ï¼š{{version}}", + "testDownloader": "ダウンロードマãƒãƒ¼ã‚¸ãƒ£ãƒ¼é€šä¿¡ãƒ†ã‚¹ãƒˆ", + "addNewNode": "ノード新è¦ä½œæˆ", + "nameTheNode": "ノードã«åå‰ã‚’付ã‘る:", + "copyBinary": "", + "runCrSlave": "ノード上ã§ãƒ¡ã‚¤ãƒ³ã‚µã‚¤ãƒˆã¨åŒã˜ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®Cloudreveを実行ã—ã€ä»¥ä¸‹ã®è¨­å®šãƒ•ァイルを使ã£ã¦èµ·å‹•ã—ã¦ãã ã•ã„:", + "keepIfUpload": "å°†æ¥ã“ã®ãƒŽãƒ¼ãƒ‰ã‚’ストレージã¨ã—ã¦ä½¿ç”¨ã™ã‚‹å ´åˆã¯ã€ä»¥ä¸‹ã®ã‚¯ãƒ­ã‚¹ãƒ‰ãƒ¡ã‚¤ãƒ³è¨­å®šã‚’ä¿å­˜ã—ã¦ãŠã„ã¦ãã ã•ã„。", + "storeFiles": "ファイルä¿å­˜", + "storeFilesDes": "ã“ã®ãƒŽãƒ¼ãƒ‰ã‚’使用ã—ã¦ãƒ¦ãƒ¼ã‚¶ãƒ¼ãƒ•ァイルをä¿å­˜ã—ã¾ã™ã€‚", + "storeFilesHint": "ã“ã®ãƒŽãƒ¼ãƒ‰ã‚’使用ã—ã¦ãƒ•ァイルをä¿å­˜ã™ã‚‹å ´åˆã¯ã€<0>ストレージãƒãƒªã‚·ãƒ¼ãƒšãƒ¼ã‚¸ã§æ–°ã—ã„従属ストレージãƒãƒªã‚·ãƒ¼ã‚’作æˆã—ã€ã“ã®ãƒŽãƒ¼ãƒ‰ã‚’é¸æŠžã—ã¦ãã ã•ã„。", + "runCrWithConfig": "<0>config.iniファイルã¨ã—ã¦ä¸Šè¨˜ãƒ•ァイルをä¿å­˜ã—ã€ã“ã®ãƒ•ァイルを使ã£ã¦Cloudreveã‚’èµ·å‹•ã—ã¾ã™ï¼š<0>./cloudreve -c config.ini。従属Cloudreveインスタンスã¯è¤‡æ•°ã®Cloudreveãƒ¡ã‚¤ãƒ³ãƒŽãƒ¼ãƒ‰ã«æŽ¥ç¶šã§ãã¾ã™ã€‚å…¨ã¦ã®ãƒ¡ã‚¤ãƒ³ãƒŽãƒ¼ãƒ‰ã«ã“ã®å¾“属ノードを追加ã—ã€ã‚­ãƒ¼ã‚’一致ã•ã›ã¦ãŠãã ã‘ã§æ¸ˆã¿ã¾ã™ã€‚", + "inputServer": "ノードã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’入力ã—ã¦ãã ã•ã„:", + "testButton": "以下ã®ãƒœã‚¿ãƒ³ã‚’クリックã—ã¦é€šä¿¡ãŒæ­£å¸¸ã‹ã©ã†ã‹ãƒ†ã‚¹ãƒˆã§ãã¾ã™ã€‚", + "hostHeaderHint": "ç½²åエラーãŒã‚ã‚‹å ´åˆã¯ã€å¾“属ノードã®å‰ç½®ãƒªãƒãƒ¼ã‚¹ãƒ—ロキシãŒ<0>Hostヘッダーをé€ä¿¡ã—ã¦ã„ã‚‹ã‹ç¢ºèªã—ã¦ãã ã•ã„。", + "features": "æœ‰åŠ¹ãªæ©Ÿèƒ½", + "remoteDownload": "オフラインダウンロード", + "refresh": "æ›´æ–°" + }, + "group": { + "countUser": "統計", + "anonymous": "未ログインゲストユーザーグループ", + "sysGroup": "システムユーザーグループ", + "adminGroup": "管ç†è€…ユーザーグループ", + "#": "#", + "name": "åç§°", + "type": "ストレージãƒãƒªã‚·ãƒ¼", + "count": "所属ユーザー数", + "size": "最大容é‡", + "nameOfGroup": "ユーザーグループå", + "nameOfGroupDes": "ユーザーã«è¡¨ç¤ºã•れるユーザーグループå", + "availablePolicies": "利用å¯èƒ½ãªã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼", + "availablePoliciesDes": "指定ユーザーグループãŒåˆ©ç”¨ã§ãるストレージãƒãƒªã‚·ãƒ¼ã€‚ã“ã®è¨­å®šã‚’変更ã—ã¦ã‚‚ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒæ—¢ã«ã‚¢ãƒƒãƒ—ロードã—ãŸãƒ•ァイルã«ã¯å½±éŸ¿ã—ã¾ã›ã‚“。", + "availablePolicyDesPro": "è¤‡æ•°é¸æŠžå¯èƒ½ã€‚ユーザーã¯é¸æŠžç¯„囲内ã§ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã‚’自由ã«åˆ‡ã‚Šæ›¿ãˆã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚", + "initialStorageQuota": "åˆæœŸå®¹é‡", + "initialStorageQuotaDes": "ユーザーグループ下ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®åˆæœŸæœ€å¤§å®¹é‡ã€‚", + "isAdmin": "管ç†è€…ユーザーグループ", + "isAdminDes": "有効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—下ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ç®¡ç†è€…権é™ã‚’æŒã¤ã‚ˆã†ã«ãªã‚Šã¾ã™ã€‚", + "share": "共有", + "allowCreateShareLink": "共有リンクã®ä½œæˆ", + "allowCreateShareLinkDes": "無効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯å…±æœ‰ãƒªãƒ³ã‚¯ã‚’作æˆã§ããªããªã‚Šã¾ã™ã€‚", + "shareFree": "共有リンク購入ä¸è¦", + "shareFreeDes": "有効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ã™ã¹ã¦ã®æœ‰æ–™å…±æœ‰ãƒªãƒ³ã‚¯ã«è³¼å…¥ã›ãšã«ã‚¢ã‚¯ã‚»ã‚¹ã§ãã¾ã™ã€‚", + "fileManagement": "ファイル管ç†", + "allowWabDAV": "WebDAV", + "allowWabDAVDes": "無効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯WebDAVプロトコルã§ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ã«æŽ¥ç¶šã§ããªããªã‚Šã¾ã™ã€‚", + "allowWabDAVProxy": "WebDAVプロキシ", + "allowWabDAVProxyDes": "有効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯Cloudreveを経由ã—ãŸWebDAVダウンロードを設定ã§ãã¾ã™ã€‚", + "compressTask": "ファイルã®åœ§ç¸®/è§£å‡", + "compressTaskDes": "有効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ã‚ªãƒ³ãƒ©ã‚¤ãƒ³ã§ãƒ•ァイルã®åœ§ç¸®/è§£å‡ã‚’行ã†ã“ã¨ãŒã§ãã¾ã™ã€‚", + "compressSize": "åœ§ç¸®å¯¾è±¡ãƒ•ã‚¡ã‚¤ãƒ«ã®æœ€å¤§ã‚µã‚¤ã‚º", + "compressSizeDes": "ユーザーãŒä½œæˆã§ãる圧縮タスクã®ãƒ•ァイルç·ã‚µã‚¤ã‚ºã®ä¸Šé™ã§ã™ã€‚0 を入力ã™ã‚‹ã¨åˆ¶é™ã¯ã‚りã¾ã›ã‚“。ã“ã®åˆ¶é™ã¯åœ§ç¸®ã‚¿ã‚¹ã‚¯ä½œæˆæ™‚ã«ã¯ãƒã‚§ãƒƒã‚¯ã•れãšã€å®Ÿè¡Œæ™‚ã«å‡¦ç†å¯¾è±¡ã®ãƒ•ァイルç·ã‚µã‚¤ã‚ºãŒã“ã®åˆ¶é™ã‚’è¶…ãˆãŸå ´åˆã€ã‚¿ã‚¹ã‚¯ã¯å¤±æ•—ã—ã¾ã™ã€‚", + "decompressSize": "è§£å‡å¯¾è±¡ãƒ•ã‚¡ã‚¤ãƒ«ã®æœ€å¤§ã‚µã‚¤ã‚º", + "decompressSizeDes": "ユーザーãŒä½œæˆã§ãã‚‹è§£å‡ã‚¿ã‚¹ã‚¯ã®ãƒ•ァイルç·ã‚µã‚¤ã‚ºã®ä¸Šé™ã§ã™ã€‚0 を入力ã™ã‚‹ã¨åˆ¶é™ã¯ã‚りã¾ã›ã‚“。", + "allowRemoteDownload": "オフラインダウンロード", + "allowRemoteDownloadDes": "ユーザーãŒã‚ªãƒ•ラインダウンロードタスクを作æˆã™ã‚‹ã“ã¨ã‚’許å¯ã™ã‚‹ã‹ã©ã†ã‹ã€‚オフラインダウンロードを使用ã™ã‚‹ã«ã¯ã€<0>ノード一覧ã§ã‚ªãƒ•ãƒ©ã‚¤ãƒ³ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰æ©Ÿèƒ½ãŒæœ‰åйã«ãªã£ã¦ã„るノードãŒå¿…è¦ã§ã™ã€‚", + "aria2Options": "ダウンローダータスクパラメータ", + "aria2OptionsDes": "qBittorrentã¾ãŸã¯Aria2ダウンローダーã®ã‚¿ã‚¹ã‚¯è¿½åŠ è¨­å®šãƒ‘ãƒ©ãƒ¡ãƒ¼ã‚¿ã§ã™ã€‚JSONå½¢å¼ã®ã‚­ãƒ¼ãƒãƒªãƒ¥ãƒ¼ãƒšã‚¢ã§è¨˜è¿°ã—ã€åˆ©ç”¨å¯èƒ½ãªãƒ‘ラメータã«ã¤ã„ã¦ã¯å…¬å¼ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’å‚ç…§ã—ã¦ãã ã•ã„。", + "aria2BatchSize": "一括オフラインダウンロード最大数", + "aria2BatchSizeDes": "一括ã§ã‚ªãƒ•ラインダウンロードを作æˆã™ã‚‹éš›ã®æœ€å¤§æ•°ã§ã™ã€‚0 を入力ã™ã‚‹ã¨åˆ¶é™ã¯ã‚りã¾ã›ã‚“。", + "migratePolicy": "ストレージãƒãƒªã‚·ãƒ¼è»¢é€", + "migratePolicyDes": "ユーザーãŒã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼è»¢é€ã‚¿ã‚¹ã‚¯ã‚’作æˆã™ã‚‹ã“ã¨ã‚’許å¯ã™ã‚‹ã‹ã©ã†ã‹ã€‚", + "advanceDelete": "高度ãªãƒ•ァイル削除オプション", + "advanceDeleteDes": "有効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒãƒ•ロントエンドã§ãƒ•ァイルを削除ã™ã‚‹éš›ã«ç‰©ç†ãƒ•ァイルã®ä¿æŒã‚’é¸æŠžã§ãã¾ã™ã€‚ä¿¡é ¼ã§ãるユーザーグループã®ã¿ã«é–‹æ”¾ã—ã¦ãã ã•ã„。", + "allowSelectNode": "ãƒŽãƒ¼ãƒ‰é¸æŠžã®è¨±å¯", + "allowSelectNodeDes": "有効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ã‚¿ã‚¹ã‚¯ä½œæˆå‰ã«å‡¦ç†ãƒŽãƒ¼ãƒ‰ã‚’é¸æŠžã§ãã¾ã™ã€‚無効ã«ã™ã‚‹ã¨ã€ã‚·ã‚¹ãƒ†ãƒ ã¯ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ãŒè¨±å¯ã•れãŸãƒŽãƒ¼ãƒ‰ã«è‡ªå‹•çš„ã«ãƒŽãƒ¼ãƒ‰ã‚’割り当ã¦ã¾ã™ã€‚", + "allowedNodes": "使用å¯èƒ½ãªãƒŽãƒ¼ãƒ‰", + "allowedNodesDes": "指定ユーザーグループãŒä½¿ç”¨ã§ãるタスク処ç†ãƒŽãƒ¼ãƒ‰ã‚’指定ã—ã¾ã™ã€‚空欄ã®å ´åˆã¯ã™ã¹ã¦ã®ãƒŽãƒ¼ãƒ‰ãŒä½¿ç”¨å¯èƒ½ã§ã™ã€‚ユーザーã¯ã€ã“ã®ãƒªã‚¹ãƒˆå†…ã‹ã‚‰ãƒŽãƒ¼ãƒ‰ã‚’é¸æŠžã™ã‚‹ã‹ã€ãƒ­ãƒ¼ãƒ‰ãƒãƒ©ãƒ³ã‚·ãƒ³ã‚°ã«ã‚ˆã£ã¦ãƒŽãƒ¼ãƒ‰ãŒå‰²ã‚Šå½“ã¦ã‚‰ã‚Œã¾ã™ã€‚ç¾åœ¨ã®ã‚¿ã‚¹ã‚¯ç¯„囲ã¯ã€ã‚ªãƒ•ラインダウンロードã€ãƒ•ァイルã®åœ§ç¸®ã¾ãŸã¯è§£å‡ã§ã™ã€‚ãã®ä»–ã®ã‚¿ã‚¹ã‚¯ã¯ãƒ›ã‚¹ãƒˆã§å‡¦ç†ã•れã¾ã™ã€‚", + "allNodes": "ã™ã¹ã¦ã®ãƒŽãƒ¼ãƒ‰", + "esclateAnonymity": "匿åユーザー権é™ã®æ˜‡æ ¼", + "esclateAnonymityDes": "有効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯åŒ¿åユーザーã«é«˜ã„権é™ï¼ˆå¤‰æ›´/作æˆ/削除)を設定ã§ãã¾ã™ã€‚無効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯åŒ¿åユーザーã«èª­ã¿å–り専用権é™ã—ã‹ä»˜ä¸Žã§ãã¾ã›ã‚“。ã“ã®è¨­å®šã®å¤‰æ›´ã¯ã€æ—¢ã«è¨­å®šã•れã¦ã„る共有リンクã¾ãŸã¯ãƒ•ァイルã«ã¯å½±éŸ¿ã—ã¾ã›ã‚“。", + "allowDownloadShare": "共有リンクã¸ã®ã‚¢ã‚¯ã‚»ã‚¹", + "allowDownloadShareDes": "無効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ä»–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®å…±æœ‰ãƒªãƒ³ã‚¯ã‚’å‚ç…§ã§ããªããªã‚Šã¾ã™ã€‚ã“ã®è¨­å®šã¯ã€å…±æœ‰ãƒªãƒ³ã‚¯ã®æ¨©é™è¨­å®šã‚ˆã‚Šã‚‚優先ã•れã¾ã™ã€‚", + "deletedNode": "削除済ノード #{{id}}", + "maxWalkedFiles": "最大巡回ファイル数", + "maxWalkedFilesDes": "ãƒ•ã‚¡ã‚¤ãƒ«ã®æ·±å±¤å·¡å›žãŒå¿…è¦ãªæ“作ã«ãŠã„ã¦ã€æœ€å¤§ã§å·¡å›žã§ãるファイル数を指定ã—ã¾ã™ã€‚", + "trashBinDuration": "ã‚´ãƒŸç®±ä¿æŒæ™‚間(秒)", + "trashBinDurationDes": "ゴミ箱内ã®ãƒ•ァイルã®ä¿æŒæ™‚é–“ã§ã™ã€‚期é™åˆ‡ã‚Œå¾Œã¯ãƒ•ァイルã¯å®Œå…¨ã«å‰Šé™¤ã•れã¾ã™ã€‚ã“ã®è¨­å®šã®å¤‰æ›´ã¯ã€æ—¢ã«ã‚´ãƒŸç®±ã«å…¥ã£ã¦ã„るファイルã«ã¯å½±éŸ¿ã—ã¾ã›ã‚“。", + "serverSideBatchDownload": "サーãƒãƒ¼å´ãƒ‘ッケージダウンロード", + "serverSideBatchDownloadDes": "ユーザーãŒè¤‡æ•°ãƒ•ã‚¡ã‚¤ãƒ«ã‚’é¸æŠžã—ã¦ã‚µãƒ¼ãƒãƒ¼å´ä¸­ç¶™ã«ã‚ˆã‚‹ãƒ‘ッケージダウンロードを使用ã§ãるよã†ã«ã™ã‚‹ã‹ã©ã†ã‹ã§ã™ã€‚無効ã«ã—ã¦ã‚‚ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ç´”粋ãªWebクライアントå´ãƒ‘ッケージダウンロード機能を引ãç¶šã使用ã§ãã¾ã™ã€‚", + "uploadDownload": "アップロードã¨ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰", + "getDirectLink": "直リンクã®å–å¾—", + "getDirectLinkDes": "ユーザーãŒãƒ•ァイルã®ç›´ãƒªãƒ³ã‚¯ã‚’å–å¾—ã§ãるよã†ã«ã™ã‚‹ã‹ã©ã†ã‹ã§ã™ã€‚", + "bathSourceLinkLimit": "一括外直リンク生æˆé‡åˆ¶é™", + "bathSourceLinkLimitDes": "ユーザーãŒä¸€åº¦ã«å–å¾—ã§ãã‚‹ç›´ãƒªãƒ³ã‚¯ã®æœ€å¤§ãƒ•ァイル数を指定ã—ã¾ã™ã€‚0ã¨å…¥åŠ›ã™ã‚‹ã¨ã€ç›´ãƒªãƒ³ã‚¯ã®å–å¾—ã¯è¨±å¯ã•れã¾ã›ã‚“。", + "redirectedSource": "リダイレクトを使用ã™ã‚‹ç›´ãƒªãƒ³ã‚¯", + "redirectedSourceDes": "推奨設定ã§ã™ã€‚有効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒå–å¾—ã™ã‚‹ãƒ•ァイルã®ç›´ãƒªãƒ³ã‚¯ãŒCloudreveを経由ã™ã‚‹ã‚ˆã†ã«ãªã‚Šãƒªãƒ³ã‚¯ãŒçŸ­ããªã‚Šã¾ã™ã€‚無効ã«ã™ã‚‹ã¨ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒå–å¾—ã™ã‚‹ãƒ•ァイルã®ç›´ãƒªãƒ³ã‚¯ã¯ãƒ•ァイルã®å…ƒã®ãƒªãƒ³ã‚¯ã«ãªã‚Šã€ãƒ•ァイルãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«ç´ã¥ãã¾ã™ã€‚一部ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã§ã¯ã€ç‰¹å®šã®è¨­å®šä¸‹ã§å–å¾—ã•れãŸéžçµŒç”±ç›´ãƒªãƒ³ã‚¯ãŒæ°¸ä¹…çš„ã«æœ‰åйã«ãªã‚‰ãªã„å ´åˆãŒã‚りã¾ã™ã€‚Cloudreveã®ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’å‚ç…§ã—ã¦ãã ã•ã„。", + "reuseDirectLink": "既存ã®ç›´ãƒªãƒ³ã‚¯ã‚’å†åˆ©ç”¨", + "reuseDirectLinkDes": "有効ã«ã™ã‚‹ã¨ã€åŒã˜ãƒ•ァイルã®ç›´ãƒªãƒ³ã‚¯ã‚’è¤‡æ•°å›žè¦æ±‚ã—ãŸå ´åˆã€æ—¢å­˜ã®ä¸­ç¶™ç›´ãƒªãƒ³ã‚¯ãŒå†åˆ©ç”¨ã•れã¾ã™ã€‚", + "downloadSpeedLimit": "ダウンロード速度制é™", + "downloadSpeedLimitDes": "0ã¨å…¥åŠ›ã™ã‚‹ã¨åˆ¶é™ãªã—ã«ãªã‚Šã¾ã™ã€‚制é™ã‚’有効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒé€Ÿåº¦åˆ¶é™ã«å¯¾å¿œã—ãŸã™ã¹ã¦ã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ä¸‹ã®ãƒ•ァイルをダウンロードã™ã‚‹éš›ã®æœ€å¤§é€Ÿåº¦ãŒåˆ¶é™ã•れã¾ã™ã€‚", + "anonymousHint": "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã¯ã€ãƒ­ã‚°ã‚¤ãƒ³ã—ã¦ã„ãªã„匿åã®è¨ªå•者ã«å¯¾å¿œã—ã¾ã™ã€‚", + "create": "æ–°è¦ä½œæˆ", + "copyFromExisting": "既存ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã‹ã‚‰è¤‡è£½ã—ã¾ã™ã‹ï¼Ÿ", + "notCopy": "複製ã—ãªã„", + "confirmDelete": "ユーザーグループ「{{group}}ã€ã‚’削除ã—ã¾ã™ã‹ï¼Ÿ", + "new": "ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã®æ–°è¦ä½œæˆ", + "editGroup": "「{{group}}ã€ã®ç·¨é›†" + }, + "user": { + "createdAt": "ä½œæˆæ—¥", + "originUserGroup": "å…ƒã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—", + "originUserGroupDes": "ユーザーãŒãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—を購入ã™ã‚‹å‰ã«æ‰€å±žã—ã¦ã„ãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—。ç¾åœ¨ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ãŒæœŸé™åˆ‡ã‚Œã«ãªã‚‹ã¨ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã«æˆ»ã‚Šã¾ã™ã€‚", + "noOriginUserGroup": "ãªã—", + "groupExpired": "ユーザーグループ有効期é™", + "groupExpiredDes": "ISO8601å½¢å¼ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—有効期é™ã€‚空欄ã®å ´åˆã¯ã€ç¾åœ¨ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã¯æ°¸ä¹…ã«æœ‰åйã§ã™ã€‚", + "openUserFiles": "ユーザーファイルを開ã", + "id": "ID", + "idValue": "{{id}} ({{hash_id}})", + "avatar": "ã‚¢ãƒã‚¿ãƒ¼", + "removeAvatar": "ã‚¢ãƒã‚¿ãƒ¼å‰Šé™¤", + "userDialogTitle": "ユーザー詳細", + "2FAEnabled": "2段階èªè¨¼æœ‰åй", + "qqEnabled": "QQé€£æºæ¸ˆã¿", + "logtoEnabled": "Logtoé€£æºæ¸ˆã¿", + "deleted": "ユーザー削除済ã¿", + "new": "æ–°è¦ãƒ¦ãƒ¼ã‚¶ãƒ¼ä½œæˆ", + "filter": "フィルタ", + "emptyNoFilter": "空欄ã®å ´åˆã¯ãƒ•ィルタリングã—ã¾ã›ã‚“。", + "selectedObjects": "{{num}}ä»¶é¸æŠžæ¸ˆã¿", + "nick": "ニックãƒãƒ¼ãƒ ", + "email": "Email", + "group": "ユーザーグループ", + "status": "ステータス", + "usedStorage": "使用済ã¿å®¹é‡", + "status_active": "正常", + "status_inactive": "未アクティブ", + "status_manual_banned": "手動ã§ãƒ–ロック", + "status_sys_banned": "システムã§ãƒ–ロック", + "toggleBan": "ブロック/ブロック解除", + "filterCondition": "フィルタæ¡ä»¶", + "all": "ã™ã¹ã¦", + "userStatus": "ユーザー状態", + "apply": "アプリケーション", + "editUser": "編集 {{nick}}", + "password": "パスワード", + "passwordDes": "空欄ã¯å¤‰æ›´ãªã—", + "groupDes": "ユーザー所属ユーザーグループ", + "2FA": "2段階èªè¨¼", + "notEnabled": "éžæœ‰åй", + "reset2Fa": "無効", + "reset": "リセット", + "confirmDelete": "ユーザー「{{user}}ã€ã‚’削除ã—ã¾ã™ã‹ï¼Ÿ", + "deleteXUsers": "{{num}}ä»¶ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’削除", + "confirmBatchDelete": "{{num}}ä»¶ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’削除ã—ã¾ã™ã‹ï¼Ÿ", + "calibrateStorage": "ストレージ容é‡ã®æ ¡æ­£", + "calibrateStorageSuccess": "ストレージ容é‡ã®æ ¡æ­£ãŒå®Œäº†ã—ã¾ã—ãŸ" + }, + "file": { + "deleteXFiles": "{{num}}ä»¶ã®ãƒ•ァイルを削除", + "confirmBatchDelete": "{{num}}ä»¶ã®ãƒ•ァイルを削除ã—ã¾ã™ã‹ï¼Ÿ", + "confirmDelete": "ファイル「{{file}}ã€ã‚’削除ã—ã¾ã™ã‹ï¼Ÿ", + "haveShares": "共有リンクã‚り", + "haveDirectLinks": "直接リンクã‚り", + "directLinkId": "リンク識別å­", + "directLinks": "直接リンク", + "noRecords": "記録ãªã—", + "speed": "速度制é™", + "downloads": "ダウンロード回数", + "shareLink": "共有リンク", + "shareLinkNum": "{{num}}個 (<0>表示)", + "blobType": "タイプ", + "noEntities": "Blobãªã—", + "blobs": "Blobs", + "creator": "作æˆè€…", + "source": "ソース", + "key": "キー", + "value": "値", + "isPublic": "公開", + "noMetadata": "メタデータãªã—", + "metadata": "メタデータ", + "id": "ID", + "primaryStoragePolicy": "優先ストレージãƒãƒªã‚·ãƒ¼", + "fileDialogTitle": "ファイル詳細", + "name": "ファイルå", + "deleteAsync": "削除タスクã¯ãƒãƒƒã‚¯ã‚°ãƒ©ã‚¦ãƒ³ãƒ‰ã§å®Ÿè¡Œã•れã¾ã™", + "forceDelete": "強制削除", + "size": "サイズ", + "sizeUsed": "使用é‡", + "uploader": "所有者", + "createdAt": "ä½œæˆæ—¥æ™‚", + "uploading": "アップロード中", + "unknownUploader": "䏿˜Ž", + "uploaderID": "所有者ID", + "searchFileName": "ãƒ•ã‚¡ã‚¤ãƒ«åæ¤œç´¢", + "storagePolicy": "ストレージãƒãƒªã‚·ãƒ¼", + "selectTargetUser": "å…ˆã«ã‚¿ãƒ¼ã‚²ãƒƒãƒˆãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’é¸æŠžã—ã¦ãã ã•ã„", + "importTaskCreated": "インãƒãƒ¼ãƒˆã‚¿ã‚¹ã‚¯ãŒä½œæˆã•れã¾ã—ãŸã€‚「永続タスクã€ã§å®Ÿè¡Œçжæ³ã‚’確èªã§ãã¾ã™", + "manuallyPathOnly": "é¸æŠžã•れãŸã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã¯æ‰‹å‹•ã§ãƒ‘スを入力ã™ã‚‹ã®ã¿ã‚µãƒãƒ¼ãƒˆã—ã¦ã„ã¾ã™", + "selectFolder": "ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’é¸æŠž", + "import": "インãƒãƒ¼ãƒˆ", + "importExternalFolder": "外部ディレクトリã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆ", + "importExternalFolderDes": "ストレージãƒãƒªã‚·ãƒ¼ã«ã‚る既存ã®ãƒ•ァイルã€ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªæ§‹é€ ã‚’Cloudreveã«ã‚¤ãƒ³ãƒãƒ¼ãƒˆã§ãã¾ã™ã€‚インãƒãƒ¼ãƒˆæ“作ã¯ç‰©ç†ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸å®¹é‡ã‚’消費ã—ã¾ã›ã‚“ãŒã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ä½¿ç”¨æ¸ˆã¿å®¹é‡ã¯é€šå¸¸é€šã‚Šæ¸›ç®—ã•れã¾ã™ã€‚", + "storagePolicyDes": "インãƒãƒ¼ãƒˆå¯¾è±¡ãƒ•ァイルã®ä¿å­˜å…ˆã®ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸ãƒãƒªã‚·ãƒ¼ã‚’é¸æŠž", + "targetUser": "対象ユーザー", + "targetUserDes": "ファイルをインãƒãƒ¼ãƒˆã™ã‚‹ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚·ã‚¹ãƒ†ãƒ ã‚’é¸æŠžã—ã¦ãã ã•ã„。", + "srcFolderPath": "å…ƒã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªãƒ‘ス", + "select": "é¸æŠž", + "selectSrcDes": "ストレージ上ã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆå¯¾è±¡ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®ãƒ‘ス", + "dstFolderPath": "目的ディレクトリパス", + "dstFolderPathDes": "ユーザーã®ãƒ•ァイルシステムã«ã‚¤ãƒ³ãƒãƒ¼ãƒˆã™ã‚‹ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®ãƒ‘ス", + "recursivelyImport": "å†å¸°çš„ã«ã‚µãƒ–ディレクトリをインãƒãƒ¼ãƒˆ", + "recursivelyImportDes": "ディレクトリ内ã®ã™ã¹ã¦ã®ã‚µãƒ–ディレクトリをå†å¸°çš„ã«ã‚¤ãƒ³ãƒãƒ¼ãƒˆã—ã¾ã™ã‹ï¼Ÿ", + "createImportTask": "インãƒãƒ¼ãƒˆã‚¿ã‚¹ã‚¯ã®ä½œæˆ", + "unlink": "関連付ã‘解除(物ç†ãƒ•ァイルã¯ä¿æŒï¼‰", + "searchUser": "ニックãƒãƒ¼ãƒ ã¾ãŸã¯ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã§ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’検索...", + "extractMediaMeta": "ãƒ¡ãƒ‡ã‚£ã‚¢æƒ…å ±ã®æŠ½å‡º", + "extractMediaMetaDes": "インãƒãƒ¼ãƒˆæ™‚ã«å„ファイルã®ãƒ¡ãƒ‡ã‚£ã‚¢æƒ…報を抽出ã™ã‚‹ã‹ã©ã†ã‹ã€‚", + "importWarning": "注æ„事項", + "importWarnings": [ + "インãƒãƒ¼ãƒˆå¾Œã€ç‰©ç†ãƒ•ァイルã¯Cloudreveã«ç§»è¡Œã•れã¾ã™ã€‚外部ã§ã®å¤‰æ›´ã¯ãпާãˆãã ã•ã„。", + "åŒã˜ãƒ•ァイルを複数回インãƒãƒ¼ãƒˆã—ãªã„ã§ãã ã•ã„。", + "ユーザーã®ãƒ•ァイルãŒè¡çªã—ãŸå ´åˆã€ã“ã®ãƒ•ァイルã¯ã‚¹ã‚­ãƒƒãƒ—ã•れã¾ã™ã€‚" + ], + "otherConditions": "ãã®ä»–ã®æ¡ä»¶", + "shareLinkExisted": "共有リンクãŒå­˜åœ¨", + "directLinkExisted": "直接リンクãŒå­˜åœ¨", + "isUploading": "アップロード中" + }, + "entity": { + "refenenceCount": "å‚照回数", + "waitForRecycle": "回åŽå¾…ã¡", + "entityDialogTitle": "Blob詳細", + "uploadSessionID": "アップロードセッションID", + "referredFiles": "関連ファイル", + "confirmBatchDelete": "{{num}}個ã®Blobを削除ã—ã¾ã™ã‹ï¼Ÿ", + "deleteXEntities": "{{num}}個ã®Blobを削除", + "forceDelete": "強制削除", + "forceDeleteDes": "物ç†ãƒ•ァイルã®å‰Šé™¤æˆåŠŸå¯å¦ã«é–¢ã‚らãšã€Blobレコードã¯å‰Šé™¤ã•れã¾ã™ã€‚" + }, + "event": { + "cleanup": "イベント清ç†", + "cleanupAuditLog": "イベント清ç†", + "cleanupAuditLogDescription": "以下æ¡ä»¶ã‚’満ãŸã™ã™ã¹ã¦ã®ã‚¤ãƒ™ãƒ³ãƒˆã‚’削除ã—ã¾ã™ï¼š", + "cleanupNotAfter": "ã“ã®æ—¥ä»˜ä»¥å‰", + "cleanupEventTypes": "イベント種類", + "cleanupEventTypesDes": "ã‚¤ãƒ™ãƒ³ãƒˆç¨®é¡žã‚’é¸æŠžã—ã¦ãã ã•ã„。空ã®å ´åˆã¯ã™ã¹ã¦ã®ç¨®é¡žã‚’削除ã—ã¾ã™ã€‚", + "allEventTypes": "ã™ã¹ã¦ã®ã‚¤ãƒ™ãƒ³ãƒˆã‚¿ã‚¤ãƒ—", + "initiator": "発信者", + "event": "イベント", + "userID": "ユーザーID", + "ip": "IPアドレス", + "type": "種類", + "correlationId": "リクエストID", + "fileID": "ファイルID", + "emailSend": "メール「{{title}}ã€ã‚’{{email}}ã«é€ä¿¡", + "emailFailed": "メールキュー起動失敗", + "signinFailed": "ログイン失敗:{{reason}}", + "createDavAccount": "WebDAVアカウント作æˆï¼š{{account}}", + "updateDavAccount": "WebDAVアカウント更新:{{account}}", + "deleteDavAccount": "WebDAVアカウント削除:{{account}}", + "pointsChange": "ãƒã‚¤ãƒ³ãƒˆå¤‰æ›´ï¼š{{points}}", + "storageAdded": "容é‡{{size}}を購入", + "nickChange": "ニックãƒãƒ¼ãƒ ã‚’{{old}}ã‹ã‚‰{{new}}ã«å¤‰æ›´", + "eventDialogTitle": "イベント詳細", + "userAgent": "ユーザーエージェント", + "linkedUser": "関連ユーザー", + "datetime": "時間", + "linkedFile": "関連ファイル", + "linkedEntity": "関連Blob", + "linkedShare": "関連共有", + "rawContent": "オリジナルレコード", + "confirmDelete": "ã“ã®ã‚¤ãƒ™ãƒ³ãƒˆã‚’削除ã—ã¾ã™ã‹ï¼Ÿ", + "deleteXEvents": "{{num}}ä»¶ã®ã‚¤ãƒ™ãƒ³ãƒˆã‚’削除", + "confirmBatchDelete": "{{num}}ä»¶ã®ã‚¤ãƒ™ãƒ³ãƒˆã‚’削除ã—ã¾ã™ã‹ï¼Ÿ" + }, + "share": { + "confirmBatchDelete": "{{num}}ä»¶ã®å…±æœ‰ã‚’削除ã—ã¾ã™ã‹ï¼Ÿ", + "confirmDelete": "ã“ã®å…±æœ‰ã‚’削除ã—ã¾ã™ã‹ï¼Ÿ", + "deleteXShares": "{{num}}ä»¶ã®å…±æœ‰ã‚’削除", + "shareDialogTitle": "共有詳細", + "shareLink": "共有リンク", + "deleted": "ファイルãŒå‰Šé™¤ã•れã¾ã—ãŸ", + "srcFileName": "ソースファイル", + "views": "閲覧", + "downloads": "ダウンロード", + "price": "ãƒã‚¤ãƒ³ãƒˆ", + "autoExpire": "自動期é™åˆ‡ã‚Œ", + "owner": "共有者", + "createdAt": "共有", + "private": "マイページã‹ã‚‰éžè¡¨ç¤º", + "yes": "ã¯ã„", + "no": "ã„ã„ãˆ", + "afterNDownloads": "{{num}}回ダウンロード後", + "none": "ãªã—", + "srcType": "ソースファイルタイプ", + "folder": "ディレクトリ", + "file": "ファイル" + }, + "task": { + "cleanupTasks": "タスク清ç†", + "cleanupTasksDescription": "以下æ¡ä»¶ã‚’満ãŸã™ã™ã¹ã¦ã®ã‚¿ã‚¹ã‚¯ã‚’削除ã—ã¾ã™ï¼š", + "cleanupNotAfter": "ã“ã®æ—¥ä»˜ä»¥å‰", + "cleanupTaskTypes": "タスク種類", + "cleanupTaskTypesDes": "ã‚¿ã‚¹ã‚¯ç¨®é¡žã‚’é¸æŠžã—ã¦ãã ã•ã„。空ã®å ´åˆã¯ã™ã¹ã¦ã®ç¨®é¡žã‚’削除ã—ã¾ã™ã€‚", + "cleanupTaskStatuses": "タスクステータス", + "cleanupTaskStatusesDes": "ã‚¿ã‚¹ã‚¯ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã‚’é¸æŠžã—ã¦ãã ã•ã„。空ã®å ´åˆã¯ã™ã¹ã¦ã®å®Œäº†ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã®ã‚¿ã‚¹ã‚¯ã‚’削除ã—ã¾ã™ã€‚", + "confirmDelete": "ã“ã®ã‚¿ã‚¹ã‚¯ã‚’削除ã—ã¾ã™ã‹ï¼Ÿ", + "confirmBatchDelete": "{{num}}個ã®ã‚¿ã‚¹ã‚¯ã‚’削除ã—ã¾ã™ã‹ï¼Ÿ", + "deleteXTasks": "{{num}}個ã®ã‚¿ã‚¹ã‚¯ã‚’削除", + "blobID": "Blob ID", + "retryIndex": "å†è©¦è¡Œç•ªå·", + "entityError": "失敗ã—ãŸBlobã®å›žåŽ", + "updatedAt": "æ›´æ–°æ—¥", + "taskDialogTitle": "タスク詳細", + "explicitEntityRecycle": "ファイルBlobã®æ˜Žç¤ºçš„回åŽ: {{blobs}}", + "entityRecycleRoutine": "定期スキャンã«ã‚ˆã‚‹ãƒ•ァイルBlobã®å›žåŽ", + "mediaMetadata": "Blob <0>#{{entityID}} ã®ãƒ¡ãƒ‡ã‚£ã‚¢æƒ…å ±å–å¾—", + "uploadSentinelCheck": "アップロードセッション {{uploadSessionID}} ã®çŠ¶æ…‹ç¢ºèª", + "remoteDownload": "オフラインダウンロード:", + "owner": "所有者", + "content": "コンテンツ", + "status": "状態", + "create_archive": "圧縮ファイル作æˆ", + "extract_archive": "ファイル解å‡", + "relocate": "ストレージãƒãƒªã‚·ãƒ¼ã®ç§»è¡Œ", + "remote_download": "オフラインダウンロード", + "media_meta": "メディア情報抽出", + "entity_recycle_routine": "Blobスキャン回åŽ", + "explicit_entity_recycle": "明示的Blob回åŽ", + "upload_sentinel_check": "アップロード監視ãƒã‚§ãƒƒã‚¯", + "import": "外部インãƒãƒ¼ãƒˆ", + "type": "タイプ", + "node": "処ç†ãƒŽãƒ¼ãƒ‰", + "createdBy": "作æˆè€…", + "ready": "準備完了", + "downloading": "ダウンロード中", + "paused": "ä¸€æ™‚åœæ­¢ä¸­", + "seeding": "シード中", + "error": "エラー", + "finished": "完了", + "canceled": "キャンセル/åœæ­¢", + "unknown": "䏿˜Ž", + "errorMsg": "エラーメッセージ" + }, + "payment": { + "tradeNo": "å–引番å·", + "productType": "商å“種別", + "providerID": "決済方法", + "status": "ステータス", + "deleteXPayments": "{{num}}ä»¶ã®æ³¨æ–‡ã‚’削除" + }, + "customProps": { + "add": "追加", + "type": "種類", + "default": "デフォルト値", + "actions": "æ“作", + "text": "テキスト", + "number": "数値", + "boolean": "ãƒã‚§ãƒƒã‚¯ãƒœãƒƒã‚¯ã‚¹", + "select": "å˜ä¸€é¸æŠž", + "multiSelect": "è¤‡æ•°é¸æŠž", + "user": "ユーザー", + "link": "リンク", + "rating": "評価", + "addProp": "プロパティを追加", + "editProp": "プロパティを編集", + "icon": "アイコン", + "iconDes": "<0>Iconify アイコンåを入力ã—ã¦ãã ã•ã„。空欄ã®å ´åˆã¯ã‚¢ã‚¤ã‚³ãƒ³ã‚’表示ã—ã¾ã›ã‚“。", + "id": "ID", + "idDes": "プロパティã®IDã§ã™ã€‚ä»–ã®ãƒ—ロパティã¨é‡è¤‡ã—ãªã„よã†ã«ã—ã¦ãã ã•ã„。", + "idPatternDes": "åŠè§’英数字ã€ã‚¢ãƒ³ãƒ€ãƒ¼ã‚¹ã‚³ã‚¢ã€ãƒã‚¤ãƒ•ンã®ã¿ä½¿ç”¨ã§ãã¾ã™ã€‚", + "minLength": "æœ€å°æ–‡å­—æ•°", + "maxLength": "最大文字数", + "emptyLimit": "制é™ãªã—ã®å ´åˆã¯ç©ºæ¬„ã«ã—ã¦ãã ã•ã„。", + "minValue": "最å°å€¤", + "maxValue": "最大値", + "options": "é¸æŠžè‚¢", + "optionsDes": "1行ã«ã¤ã1ã¤ã®é¸æŠžè‚¢ã‚’入力ã—ã¦ãã ã•ã„。" + }, + "vas": { + "disableSubAddressEmail": "サブアドレスメールã®ç„¡åŠ¹åŒ–", + "disableSubAddressEmailDes": "有効化後ã€<0>+ã‚’å«ã‚€ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã¯ç™»éŒ²ä¸å¯ã«ãªã‚Šã¾ã™ã€‚", + "confirmDelete": "ã“ã‚Œã‚‰ã®æ³¨æ–‡ã‚’削除ã—ã¾ã™ã‹ï¼Ÿ", + "vas": "付加価値サービス", + "reports": "報告", + "orders": "注文", + "initialFiles": "åˆæœŸãƒ•ァイル", + "initialFilesDes": "ユーザー登録後ã«åˆæœŸçš„ã«ä¿æœ‰ã™ã‚‹ãƒ•ァイルã§ã™ã€‚ファイルIDを入力ã—ã¦ã€æ—¢å­˜ã®ãƒ•ァイルを追加ã—ã¦ãã ã•ã„。", + "filterEmailProvider": "登録メールアドレスドメインフィルタリング", + "filterEmailProviderDisabled": "無効", + "filterEmailProviderWhitelist": "ホワイトリスト", + "filterEmailProviderBlacklist": "ブラックリスト", + "filterEmailProviderDes": "特定メールアドレスã®ã¿ç™»éŒ²å¯ã€‚サードパーティSSOログインã¯åˆ¶é™å¯¾è±¡å¤–。", + "filterEmailProviderRule": "メールドメインフィルタリングルール", + "filterEmailProviderRuleDes": "複数ã®ãƒ‰ãƒ¡ã‚¤ãƒ³ã‚’指定ã™ã‚‹å ´åˆã¯ã€åŠè§’カンマã§åŒºåˆ‡ã£ã¦ãã ã•ã„。", + "qqConnect": "QQ連æº", + "qqConnectHint": "<0>QQ互æ›ã‚ªãƒ¼ãƒ—ン平å°ã§ã‚¢ãƒ—リを作æˆã™ã‚‹éš›ã€ã‚³ãƒ¼ãƒ«ãƒãƒƒã‚¯URLã«{{url}}<を入力ã—ã¦ãã ã•ã„。", + "enableQQConnect": "QQ連æºã®æœ‰åŠ¹åŒ–", + "enableQQConnectDes": "QQアカウントã®ç´ä»˜ã‘ã€QQログインを許å¯ã—ã¾ã™ã‹ï¼Ÿ", + "loginWithoutBinding": "未連æºã®å ´åˆã€ç›´æŽ¥ãƒ­ã‚°ã‚¤ãƒ³ã§ãã¾ã™ã€‚", + "loginWithoutBindingDes": "有効化後ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒã‚µãƒ¼ãƒ‰ãƒ‘ーティログインを使用ã—ã€ç´ä»˜ã‘済ã¿ã®ç™»éŒ²ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒå­˜åœ¨ã—ãªã„å ´åˆã€ã‚·ã‚¹ãƒ†ãƒ ã¯ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚’作æˆã—ã¦ãƒ­ã‚°ã‚¤ãƒ³ã•ã›ã¾ã™ã€‚ã“ã®æ–¹æ³•ã§ä½œæˆã•れãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ã€ä»¥é™ã‚µãƒ¼ãƒ‰ãƒ‘ーティログインã®ã¿ä½¿ç”¨å¯èƒ½ã§ã™ã€‚", + "appid": "APP ID", + "appidDes": "アプリ管ç†ãƒšãƒ¼ã‚¸ã§å–å¾—ã—ãŸAPP ID。", + "appKey": "APP KEY", + "appKeyDes": "アプリ管ç†ãƒšãƒ¼ã‚¸ã§å–å¾—ã—ãŸAPP KEY。", + "overuseReminder": "è¶…éŽé€šçŸ¥", + "overuseReminderDes": "増値サービス期é™åˆ‡ã‚Œã€å®¹é‡è¶…éŽã«ã‚ˆã‚‹é€šçŸ¥ãƒ¡ãƒ¼ãƒ«ãƒ†ãƒ³ãƒ—レート", + "vasSetting": "決済/ãã®ä»–設定", + "storagePack": "容é‡ãƒ‘ック", + "purchasableGroups": "購入å¯èƒ½ãªãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—", + "giftCodes": "交æ›ã‚³ãƒ¼ãƒ‰", + "enable": "有効化", + "appID": "APP ID", + "appIDDes": "当é¢ä»˜ã‚¢ãƒ—リã®APP ID。", + "rsaPrivate": "RSAアプリ秘密éµ", + "rsaPrivateDes": "当é¢ä»˜ã‚¢ãƒ—リã®RSA2(SHA256)秘密éµã€‚通常ã€è‡ªåˆ†ã§ç”Ÿæˆã—ã¾ã™ã€‚<0>RSAキーã®ç”Ÿæˆã‚’å‚ç…§ãã ã•ã„。", + "alipayPublicKey": "支付å®å…¬é–‹éµ", + "alipayPublicKeyDes": "支付å®ãŒæä¾›ã—ã¾ã™ã€‚「アプリ管ç†ã€-「アプリ情報ã€-ã€Œã‚¤ãƒ³ã‚¿ãƒ¼ãƒ•ã‚§ãƒ¼ã‚¹ç½²åæ–¹å¼ã€ã§å–å¾—ã§ãã¾ã™ã€‚", + "wechatPay": "WeChat QRコード決済", + "applicationID": "App ID", + "applicationIDDes": "ç›´çµåŠ ç›Ÿåº—ãŒç”³è«‹ã—ãŸå…¬å¼ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¾ãŸã¯ãƒ¢ãƒã‚¤ãƒ«ã‚¢ãƒ—リã®appid。", + "merchantID": "ç›´çµåŠ ç›Ÿåº—ç•ªå·", + "merchantIDDes": "ç›´çµåŠ ç›Ÿåº—ã®åŠ ç›Ÿåº—ç•ªå·ã€‚WeChatãŒç™ºè¡Œã—ã¾ã™ã€‚", + "apiV3Secret": "API v3キー", + "apiV3SecretDes": "「加盟店プラットフォームã€-「APIセキュリティã€ã§è¨­å®šãŒå¿…è¦ã§ã™ã€‚WeChatã®ç½²å検証を通éŽã™ã‚‹ãŸã‚ã«å¿…è¦ã§ã™ã€‚キーã®é•·ã•ã¯32ãƒã‚¤ãƒˆã§ã™ã€‚", + "mcCertificateSerial": "加盟店証明書シリアル番å·", + "mcCertificateSerialDes": "「加盟店プラットフォームã€-「APIセキュリティã€-「API証明書ã€-「証明書確èªã€ã§ç¢ºèªã§ãã¾ã™ã€‚", + "mcAPISecret": "加盟店API秘密éµ", + "mcAPISecretDes": "秘密éµãƒ•ァイルapiclient_key.pemã®å†…容ã§ã™ã€‚", + "payjs": "PAYJSWeChat", + "payjsWarning": "ã“ã®ã‚µãƒ¼ãƒ“スã¯ã‚µãƒ¼ãƒ‰ãƒ‘ーティプラットフォーム<0>PAYJSãŒæä¾›ã—ã¦ãŠã‚Šã€ç™ºç”Ÿã—ãŸå…¨ã¦ã®ç´›äº‰ã¯Cloudreve開発者ã¨ã¯ç„¡é–¢ä¿‚ã§ã™ã€‚", + "mcNumber": "加盟店番å·", + "mcNumberDes": "PAYJS管ç†ç”»é¢ã§ç¢ºèªã§ãã¾ã™", + "communicationSecret": "通信キー", + "otherSettings": "ãã®ä»–設定", + "banBufferPeriod": "ブロックãƒãƒƒãƒ•ァ期間(秒)", + "banBufferPeriodDes": "ユーザーãŒå®¹é‡è¶…éŽçŠ¶æ…‹ã‚’ç¶­æŒã§ãる最長時間。ã“れを超ãˆã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ã‚·ã‚¹ãƒ†ãƒ ã«ã‚ˆã£ã¦å‡çµã•れã¾ã™ã€‚", + "allowSellShares": "シェア価格設定許å¯", + "allowSellSharesDes": "有効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ã‚·ã‚§ã‚¢ã«ãƒã‚¤ãƒ³ãƒˆä¾¡æ ¼ã‚’設定ã§ãã€ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã«ã¯ãƒã‚¤ãƒ³ãƒˆã®å·®ã—引ããŒå¿…è¦ã«ãªã‚Šã¾ã™ã€‚", + "creditPriceRatio": "ãƒã‚¤ãƒ³ãƒˆå…¥é‡‘率(%)", + "creditPriceRatioDes": "ダウンロード設定価格ã®ã‚·ã‚§ã‚¢ã‚’購入ã—ãŸå ´åˆã€ã‚·ã‚§ã‚¢ä½œæˆè€…ã«å®Ÿéš›ã«å…¥é‡‘ã•れるãƒã‚¤ãƒ³ãƒˆã®å‰²åˆã§ã™ã€‚", + "creditPrice": "ãƒã‚¤ãƒ³ãƒˆä¾¡æ ¼ï¼ˆå††ï¼‰", + "creditPriceDes": "ãƒã‚¤ãƒ³ãƒˆãƒãƒ£ãƒ¼ã‚¸æ™‚ã®ä¾¡æ ¼", + "add": "追加", + "name": "åç§°", + "price": "å˜ä¾¡", + "duration": "期間", + "size": "サイズ", + "actions": "æ“作", + "orCredits": "ã¾ãŸã¯{{num}}ãƒã‚¤ãƒ³ãƒˆ", + "highlight": "強調表示", + "yes": "ã¯ã„", + "no": "ã„ã„ãˆ", + "productName": "商å“å", + "qyt": "æ•°é‡", + "code": "交æ›ã‚³ãƒ¼ãƒ‰", + "status": "状態", + "invalidProduct": "有効期é™åˆ‡ã‚Œå•†å“", + "used": "使用済ã¿", + "notUsed": "未使用", + "generatingResult": "生æˆçµæžœ", + "addStoragePack": "容é‡ãƒ‘ック追加", + "editStoragePack": "容é‡ãƒ‘ック編集", + "productNameDes": "商å“表示å", + "packSizeDes": "容é‡ãƒ‘ックã®ã‚µã‚¤ã‚º", + "durationDay": "有効期é™ï¼ˆæ—¥ï¼‰", + "durationDayDes": "å„容é‡ãƒ‘ãƒƒã‚¯ã®æœ‰åŠ¹æœŸé™", + "priceYuan": "å˜ä¾¡ï¼ˆå…ƒï¼‰", + "packPriceDes": "容é‡ãƒ‘ックã®å˜ä¾¡", + "priceCredits": "å˜ä¾¡ï¼ˆãƒã‚¤ãƒ³ãƒˆï¼‰", + "priceCreditsDes": "ãƒã‚¤ãƒ³ãƒˆè³¼å…¥æ™‚ã®ä¾¡æ ¼ï¼ˆãƒã‚¤ãƒ³ãƒˆã§è³¼å…¥ä¸å¯ã®å ´åˆã¯0を入力)", + "editMembership": "購入å¯èƒ½ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—編集", + "addMembership": "購入å¯èƒ½ãªãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã®è¿½åŠ ", + "group": "ユーザーグループ", + "groupDes": "購入後ã®ã‚¢ãƒƒãƒ—グレードユーザーグループ", + "durationGroupDes": "購入後ã®ã‚¢ãƒƒãƒ—ã‚°ãƒ¬ãƒ¼ãƒ‰ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚°ãƒ«ãƒ¼ãƒ—ã®æœ‰åŠ¹æœŸé™", + "groupPriceDes": "ユーザーグループã®å˜ä¾¡", + "productDescription": "商å“説明(一行ãšã¤ï¼‰", + "productDescriptionDes": "購入ページã«è¡¨ç¤ºã•れる商å“説明", + "highlightDes": "有効ã«ã™ã‚‹ã¨ã€å•†å“é¸æŠžãƒšãƒ¼ã‚¸ã§å¼·èª¿è¡¨ç¤ºã•れã¾ã™", + "generateGiftCode": "コードã®ç”Ÿæˆ", + "numberOfCodes": "ç”Ÿæˆæ•°", + "numberOfCodesDes": "ã‚¢ã‚¯ãƒ†ã‚£ãƒ™ãƒ¼ã‚·ãƒ§ãƒ³ã‚³ãƒ¼ãƒ‰ä¸€æ‹¬ç”Ÿæˆæ•°", + "linkedProduct": "該当商å“", + "productQyt": "商哿•°", + "productQytDes": "ãƒã‚¤ãƒ³ãƒˆå•†å“ã®å ´åˆã€ã“ã“ã«ã¯ãƒã‚¤ãƒ³ãƒˆæ•°ã‚’ã€ãれ以外ã®å•†å“ã¯å€çŽ‡ã‚’å…¥åŠ›ã—ã¦ãã ã•ã„", + "freeDownload": "ãƒã‚¤ãƒ³ãƒˆä¸è¦ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰å…±æœ‰", + "freeDownloadDes": "有効ã«ã™ã‚‹ã¨ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ãƒã‚¤ãƒ³ãƒˆãŒå¿…è¦ãªå…±æœ‰ã‚’ç„¡æ–™ã§ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã§ãã¾ã™", + "credits": "ãƒã‚¤ãƒ³ãƒˆ", + "markSuccessful": "マークæˆåŠŸ", + "markAsResolved": "å‡¦ç†æ¸ˆã¿ã¨ã—ã¦ãƒžãƒ¼ã‚¯", + "reportedContent": "通報対象", + "reason": "原因", + "description": "補足説明", + "reportTime": "通報時間", + "invalid": "[期é™åˆ‡ã‚Œ]", + "deleteShare": "シェア削除", + "orderDeleted": "注文記録ã¯å‰Šé™¤æ¸ˆã¿ã§ã™", + "orderName": "注文å", + "product": "商å“", + "paymentId": "注文ID", + "orderNumber": "注文番å·", + "paidBy": "支払方法", + "orderOwner": "作æˆè€…", + "amount": "金é¡", + "unpaid": "未払ã„", + "paid": "æ”¯æ‰•ã„æ¸ˆã¿", + "shareLink": "共有リンク", + "mobileApp": "モãƒã‚¤ãƒ«ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆ", + "showAppPromotion": "モãƒã‚¤ãƒ«ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆã‚¬ã‚¤ãƒ‰ç”»é¢è¡¨ç¤º", + "showAppPromotionDes": "有効ã«ã™ã‚‹ã¨ã€ã€ŒæŽ¥ç¶šã¨ãƒžã‚¦ãƒ³ãƒˆã€ãƒšãƒ¼ã‚¸ã«ãƒ¢ãƒã‚¤ãƒ«ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆã®ä½¿ã„方ガイドãŒè¡¨ç¤ºã•れã¾ã™ã€‚", + "customPaymentName": "支払方法å", + "customPaymentNameDes": "ユーザーã«è¡¨ç¤ºã•れる支払方法å", + "customPaymentSecretDes": "Cloudreveç½²å用秘密éµ", + "customPaymentEndpoint": "決済APIエンドãƒã‚¤ãƒ³ãƒˆ", + "customPaymentEndpointDes": "æ±ºæ¸ˆæ³¨æ–‡ä½œæˆæ™‚リクエスト用API URL", + "appFeedback": "フィードãƒãƒƒã‚¯ãƒšãƒ¼ã‚¸URL", + "appForum": "ユーザーフォーラムURL", + "appLinkDes": "アプリ設定画é¢ã«è¡¨ç¤ºã™ã‚‹URL(空欄ã®å ´åˆã¯éžè¡¨ç¤ºã€‚VOLèªè¨¼æœ‰åŠ¹æ™‚ã®ã¿æœ‰åŠ¹ï¼‰" + }, + "pro": { + "title": "Pro版é™å®šæ©Ÿèƒ½", + "description": "ã“ã®æ©Ÿèƒ½ã¯Cloudreve Pro版ã§ã®ã¿åˆ©ç”¨å¯èƒ½ã§ã™ã€‚ã™ã¹ã¦ã®é«˜åº¦ãªæ©Ÿèƒ½ã‚’アンロックã™ã‚‹ã«ã¯ã‚¢ãƒƒãƒ—グレードã—ã¦ãã ã•ã„。", + "proInclude": "Pro版ã«ã¯ä»¥ä¸‹ãŒå«ã¾ã‚Œã¾ã™ï¼š", + "shareLinkCollabration": "共有リンク共åŒç·¨é›†", + "filePermission": "ファイル権é™ç®¡ç†", + "multipleStoragePolicy": "複数ストレージ戦略ã¨ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸æˆ¦ç•¥ã®åˆ‡ã‚Šæ›¿ãˆ", + "auditAndActivity": "ファイルã¨ã‚·ã‚¹ãƒ†ãƒ ã‚¢ã‚¯ãƒ†ã‚£ãƒ“ティログ", + "vasService": "付加価値サービスã¨ãƒã‚¤ãƒ³ãƒˆã‚·ã‚¹ãƒ†ãƒ ", + "sso": "SSOシングルサインオン", + "more": "......", + "later": "後ã§", + "learnMore": "Pro版ã®è©³ç´°ã‚’見る", + "promotionTitle": "コミュニティ版アップグレード特別割引", + "promotion": "購入時ã«<0>{{code}}を使用ã™ã‚‹ã¨ã€<1>-{{discount}}%ã®å‰²å¼•ãŒé©ç”¨ã•れã¾ã™ã€‚" + }, + "abuseReport": { + "deleteXAbuseReports": "{{num}} 個ã®èˆ‰å ±ã‚’削除", + "folderPath": "ディレクトリパス", + "reporter": "舉報者", + "shareLink": "共有リンク <0>#{{id}}", + "deletedShare": "削除ã•れãŸå…±æœ‰ãƒªãƒ³ã‚¯", + "deletedUser": "削除ã•れãŸãƒ¦ãƒ¼ã‚¶ãƒ¼", + "confirmDelete": "ã“ã®èˆ‰å ±è¨˜éŒ„を削除ã—ã¾ã™ã‹ï¼Ÿ", + "confirmBatchDelete": "{{num}} 個ã®èˆ‰å ±è¨˜éŒ„を削除ã—ã¾ã™ã‹ï¼Ÿ", + "reporterID": "舉報者ユーザー ID", + "reportedUserID": "舉報対象ユーザー ID", + "shareID": "共有 ID", + "reason": "原因" + } +} diff --git a/public/locales/ja-JP/image_editor.json b/public/locales/ja-JP/image_editor.json new file mode 100755 index 0000000..6e2df4e --- /dev/null +++ b/public/locales/ja-JP/image_editor.json @@ -0,0 +1,113 @@ +{ + "name": "åç§°", + "save": "ä¿å­˜", + "saveAs": "åå‰ã‚’付ã‘ã¦ä¿å­˜", + "back": "戻る", + "loading": "読ã¿è¾¼ã¿ä¸­...", + "resetOperations": "ã™ã¹ã¦ã®ãƒªã‚»ãƒƒãƒˆ/削除", + "changesLoseWarningHint": "「リセットã€ãƒœã‚¿ãƒ³ã‚’押ã™ã¨å¤‰æ›´å†…容ãŒå¤±ã‚れã¾ã™ã€‚続行ã—ã¾ã™ã‹ï¼Ÿ", + "discardChangesWarningHint": "ウィンドウを閉ã˜ã‚‹ã¨æœ€å¾Œã®å¤‰æ›´å†…容ã¯ä¿å­˜ã•れã¾ã›ã‚“。", + "cancel": "キャンセル", + "apply": "é©ç”¨", + "warning": "警告", + "confirm": "確èª", + "discardChanges": "変更を破棄", + "undoTitle": "å…ƒã«æˆ»ã™", + "redoTitle": "やり直ã™", + "showImageTitle": "å…ƒã®ç”»åƒã‚’表示", + "zoomInTitle": "拡大", + "zoomOutTitle": "縮å°", + "toggleZoomMenuTitle": "ズームメニューã®åˆ‡ã‚Šæ›¿ãˆ", + "adjustTab": "調整", + "finetuneTab": "微調整", + "filtersTab": "フィルター", + "watermarkTab": "ウォーターマーク", + "annotateTabLabel": "注釈", + "resize": "サイズ変更", + "resizeTab": "サイズ調整", + "imageName": "ç”»åƒå", + "invalidImageError": "æä¾›ã•れãŸç”»åƒã¯ç„¡åйã§ã™ã€‚", + "uploadImageError": "ç”»åƒã®ã‚¢ãƒƒãƒ—ロード中ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚", + "areNotImages": "ç”»åƒã§ã¯ã‚りã¾ã›ã‚“", + "isNotImage": "ç”»åƒã§ã¯ã‚りã¾ã›ã‚“", + "toBeUploaded": "アップロード待ã¡", + "cropTool": "トリミング", + "original": "オリジナル", + "custom": "カスタム", + "square": "正方形", + "landscape": "横å‘", + "portrait": "縦å‘", + "ellipse": "楕円形", + "classicTv": "クラシックTV", + "cinemascope": "ワイドスクリーン", + "arrowTool": "矢å°", + "blurTool": "ã¼ã‹ã—", + "brightnessTool": "明るã•", + "contrastTool": "コントラスト", + "ellipseTool": "楕円形", + "unFlipX": "Xå転解除", + "flipX": "Xå転", + "unFlipY": "Yå転解除", + "flipY": "Yå転", + "hsvTool": "HSV", + "hue": "色相", + "brightness": "è¼åº¦", + "saturation": "彩度", + "value": "数値", + "imageTool": "ç”»åƒ", + "importing": "インãƒãƒ¼ãƒˆ...", + "addImage": "+ ç”»åƒã‚’追加", + "uploadImage": "ç”»åƒã‚’アップロード", + "fromGallery": "ギャラリーã‹ã‚‰", + "lineTool": "ç·š", + "penTool": "ペン", + "polygonTool": "多角形", + "sides": "å´é¢", + "rectangleTool": "長方形", + "cornerRadius": "角丸åŠå¾„", + "resizeWidthTitle": "幅(ピクセル)", + "resizeHeightTitle": "高ã•(ピクセル)", + "toggleRatioLockTitle": "アスペクト比ロックã®åˆ‡ã‚Šæ›¿ãˆ", + "resetSize": "å…ƒã®ç”»åƒã‚µã‚¤ã‚ºã«ãƒªã‚»ãƒƒãƒˆ", + "rotateTool": "回転", + "textTool": "テキスト", + "textSpacings": "文字間隔", + "textAlignment": "テキストé…ç½®", + "fontFamily": "フォント", + "size": "サイズ", + "letterSpacing": "文字間隔", + "lineHeight": "行間", + "warmthTool": "色温度", + "addWatermark": "+ ウォーターマークを追加", + "addTextWatermark": "文字é€ã‹ã—を追加", + "addWatermarkTitle": "é€ã‹ã—ã®ç¨®é¡žã‚’é¸æŠž", + "uploadWatermark": "é€ã‹ã—をアップロード", + "addWatermarkAsText": "テキストã¨ã—ã¦è¿½åŠ ", + "padding": "内å´ä½™ç™½", + "paddings": "内å´ä½™ç™½", + "shadow": "å½±", + "horizontal": "水平方å‘", + "vertical": "垂直方å‘", + "blur": "ã¼ã‹ã—", + "opacity": "ä¸é€æ˜Žåº¦", + "transparency": "逿˜Žåº¦", + "position": "ä½ç½®", + "stroke": "ç·š", + "saveAsModalTitle": "åå‰ã‚’付ã‘ã¦ä¿å­˜", + "extension": "æ‹¡å¼µå­", + "format": "å½¢å¼", + "nameIsRequired": "ファイルåã¯å¿…é ˆã§ã™ã€‚", + "quality": "画質", + "imageDimensionsHoverTitle": "ä¿å­˜ç”»åƒã‚µã‚¤ã‚ºï¼ˆå¹…×高ã•)", + "cropSizeLowerThanResizedWarning": "é¸æŠžã—ãŸãƒˆãƒªãƒŸãƒ³ã‚°é ˜åŸŸãŒã€é©ç”¨ã•れるサイズ変更よりå°ã•ã„ã¨ç”»è³ªãŒä½Žä¸‹ã™ã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚", + "actualSize": "実寸サイズ(100%)", + "fitSize": "最é©ã‚µã‚¤ã‚º", + "addImageTitle": "追加ã™ã‚‹ç”»åƒã‚’é¸æŠž...", + "mutualizedFailedToLoadImg": "ç”»åƒã®èª­ã¿è¾¼ã¿ã«å¤±æ•—ã—ã¾ã—ãŸã€‚", + "tabsMenu": "メニュー", + "download": "ダウンロード", + "width": "å¹…", + "height": "高ã•", + "plus": "+", + "cropItemNoEffect": "ã“ã®ãƒˆãƒªãƒŸãƒ³ã‚°é …ç›®ã«ã¯ãƒ—レビューãŒã‚りã¾ã›ã‚“。" +} \ No newline at end of file diff --git a/public/locales/ja-JP/markdown_editor.json b/public/locales/ja-JP/markdown_editor.json new file mode 100755 index 0000000..ca3acc4 --- /dev/null +++ b/public/locales/ja-JP/markdown_editor.json @@ -0,0 +1,114 @@ +{ + "frontmatterEditor": { + "title": "メタデータ編集", + "key": "キー", + "value": "値", + "addEntry": "項目を追加" + }, + "dialogControls": { + "save": "ä¿å­˜", + "cancel": "キャンセル" + }, + "uploadImage": { + "dialogTitle": "ç”»åƒã‚¢ãƒƒãƒ—ロード", + "uploadInstructions": "デãƒã‚¤ã‚¹ã‹ã‚‰ç”»åƒã‚’アップロード:", + "addViaUrlInstructions": "ã¾ãŸã¯ç”»åƒURL / 相対パス(ç¾åœ¨ã®ãƒ•ァイルã«å¯¾ã—ã¦ï¼‰ï¼š", + "autoCompletePlaceholder": "ç”»åƒURLã‚’é¸æŠžã¾ãŸã¯è²¼ã‚Šä»˜ã‘", + "addViaUrlInstructionsNoUpload": "ç”»åƒURL:", + "alt": "代替テキスト:", + "title": "タイトル:" + }, + "imageEditor": { + "deleteImage": "ç”»åƒå‰Šé™¤", + "editImage": "ç”»åƒç·¨é›†" + }, + "createLink": { + "url": "URL", + "urlPlaceholder": "URLã‚’é¸æŠžã¾ãŸã¯è²¼ã‚Šä»˜ã‘", + "title": "タイトル", + "saveTooltip": "URLを設定", + "cancelTooltip": "変更をキャンセル" + }, + "linkPreview": { + "open": "{{url}}ã‚’æ–°ã—ã„ウィンドウã§é–‹ã", + "edit": "リンク編集", + "copyToClipboard": "クリップボードã«ã‚³ãƒ”ー", + "copied": "コピーã—ã¾ã—ãŸï¼", + "remove": "リンク削除" + }, + "table": { + "deleteTable": "表削除", + "columnMenu": "列メニュー", + "textAlignment": "文字æƒãˆ", + "alignLeft": "å·¦æƒãˆ", + "alignCenter": "中央æƒãˆ", + "alignRight": "峿ƒãˆ", + "insertColumnLeft": "ç¾åœ¨ã®åˆ—ã®å·¦å´ã«åˆ—挿入", + "insertColumnRight": "ç¾åœ¨ã®åˆ—ã®å³å´ã«åˆ—挿入", + "deleteColumn": "ã“ã®åˆ—を削除", + "rowMenu": "行メニュー", + "insertRowAbove": "ç¾åœ¨ã®è¡Œã®ä¸Šã«è¡ŒæŒ¿å…¥", + "insertRowBelow": "ç¾åœ¨ã®è¡Œã®ä¸‹ã«è¡ŒæŒ¿å…¥", + "deleteRow": "ã“ã®è¡Œã‚’削除" + }, + "toolbar": { + "blockTypes": { + "paragraph": "段è½", + "quote": "引用", + "heading": "見出㗠{{level}}" + }, + "blockTypeSelect": { + "selectBlockTypeTooltip": "ブロックã®ç¨®é¡žã‚’é¸æŠž", + "placeholder": "ブロックã®ç¨®é¡ž" + }, + "toggleGroup": "グループã®åˆ‡ã‚Šæ›¿ãˆ", + "removeBold": "太字ã®è§£é™¤", + "bold": "太字", + "removeItalic": "斜体ã®è§£é™¤", + "italic": "斜体", + "underline": "下線ã®è§£é™¤", + "removeUnderline": "下線", + "removeInlineCode": "インラインコードスタイルã®è§£é™¤", + "inlineCode": "インラインコードスタイル", + "link": "リンクã®ä½œæˆ", + "richText": "リッãƒãƒ†ã‚­ã‚¹ãƒˆ", + "diffMode": "差分モード", + "source": "ソースコードモード", + "admonition": "ã‚³ãƒ¡ãƒ³ãƒˆãƒ–ãƒ­ãƒƒã‚¯ã®æŒ¿å…¥", + "codeBlock": "ã‚³ãƒ¼ãƒ‰ãƒ–ãƒ­ãƒƒã‚¯ã®æŒ¿å…¥", + "editFrontmatter": "事å‰ãƒ¡ã‚¿ãƒ‡ãƒ¼ã‚¿ã®ç·¨é›†", + "insertFrontmatter": "事å‰ãƒ¡ã‚¿ãƒ‡ãƒ¼ã‚¿ã®æŒ¿å…¥", + "image": "ç”»åƒã®æŒ¿å…¥", + "insertSandpack": "Sandpackã®æŒ¿å…¥", + "table": "ãƒ†ãƒ¼ãƒ–ãƒ«ã®æŒ¿å…¥", + "thematicBreak": "テーマ改行挿入", + "bulletedList": "ç®‡æ¡æ›¸ãリスト", + "numberedList": "番å·ä»˜ãリスト", + "checkList": "タスクリスト", + "deleteSandpack": "Sandpack削除", + "undo": "å…ƒã«æˆ»ã™ {{shortcut}}", + "redo": "やり直㗠{{shortcut}}", + "superscript": "上付ã", + "subscript": "下付ã", + "strikethrough": "å–り消ã—ç·š", + "removeSubscript": "下付ãã®è§£é™¤", + "removeSuperscript": "上付ãã®è§£é™¤", + "removeStrikethrough": "å–り消ã—ç·šã®è§£é™¤" + }, + "admonitions": { + "note": "注æ„", + "tip": "ヒント", + "danger": "å±é™º", + "info": "情報", + "caution": "警告", + "changeType": "注釈ブロックã®ç¨®é¡žã‚’é¸æŠž", + "placeholder": "注釈ブロックã®ç¨®é¡ž" + }, + "codeBlock": { + "language": "コードブロック言語", + "selectLanguage": "ã‚³ãƒ¼ãƒ‰ãƒ–ãƒ­ãƒƒã‚¯è¨€èªžã‚’é¸æŠž" + }, + "contentArea": { + "editableMarkdown": "編集å¯èƒ½ãªMarkdown" + } +} \ No newline at end of file diff --git a/public/locales/ko-KR/application.json b/public/locales/ko-KR/application.json new file mode 100755 index 0000000..72de585 --- /dev/null +++ b/public/locales/ko-KR/application.json @@ -0,0 +1,1113 @@ +{ + "login": { + "lastStep": "마지막 단계", + "siginToYourAccount": "ê³„ì •ì— ë¡œê·¸ì¸", + "createNewAccount": "새 계정 만들기", + "enterPassword": "비밀번호를 입력하세요", + "enterPasswordHint": "{{email}} ê³„ì •ì˜ ë¹„ë°€ë²ˆí˜¸ë¥¼ 입력하세요", + "paswordlessHint": "{{email}} ê³„ì •ì€ ë¹„ë°€ë²ˆí˜¸ê°€ 없는 계정입니다. ë‹¤ìŒ ë°©ë²• 중 하나를 ì„ íƒí•˜ì—¬ ì¸ì¦í•˜ì„¸ìš”:", + "noAccountSignupNow": "ê³„ì •ì´ ì—†ìœ¼ì‹ ê°€ìš”? <0>지금 가입하기", + "haveAccountSignInNow": "ì´ë¯¸ ê³„ì •ì´ ìžˆìœ¼ì‹ ê°€ìš”? <0>지금 로그ì¸", + "privacyPolicy": "ê°œì¸ì •보처리방침", + "termOfUse": "ì´ìš©ì•½ê´€", + "signupHint": "입력하신 {{email}} ê³„ì •ì´ ì¡´ìž¬í•˜ì§€ 않습니다. 지금 가입하시겠습니까?", + "accountNotFoundHint": "입력하신 {{email}} ê³„ì •ì´ ì¡´ìž¬í•˜ì§€ 않습니다.", + "or": "ë˜ëŠ”", + "selectAccountToUse": "사용할 ê³„ì •ì„ ì„ íƒí•˜ì„¸ìš”", + "useOtherAccount": "다른 계정 사용", + "email": "ì´ë©”ì¼", + "password": "비밀번호", + "captcha": "보안문ìž", + "captchaError": "ë³´ì•ˆë¬¸ìž ë¡œë“œ 실패: {{message}}", + "signIn": "로그ì¸", + "signUp": "가입", + "signUpAccount": "계정 가입", + "useFIDO2": "패스키로 로그ì¸", + "usePassword": "비밀번호로 로그ì¸", + "forgetPassword": "비밀번호를 잊으셨나요?", + "2FA": "2단계 ì¸ì¦", + "input2FACode": "6ìžë¦¬ 2단계 ì¸ì¦ 코드를 입력하세요", + "passwordNotMatch": "ë‘ ë¹„ë°€ë²ˆí˜¸ê°€ ì¼ì¹˜í•˜ì§€ 않습니다", + "findMyPassword": "비밀번호 찾기", + "passwordReset": "비밀번호가 재설정ë˜ì—ˆìŠµë‹ˆë‹¤", + "newPassword": "새 비밀번호", + "repeatNewPassword": "새 비밀번호 확ì¸", + "repeatPassword": "비밀번호 확ì¸", + "resetPassword": "비밀번호 재설정", + "backToSingIn": "로그ì¸ìœ¼ë¡œ ëŒì•„가기", + "sendMeAnEmail": "비밀번호 재설정 ì´ë©”ì¼ ì „ì†¡", + "resetEmailSent": "비밀번호 재설정 ì´ë©”ì¼ì´ 전송ë˜ì—ˆìŠµë‹ˆë‹¤. 확ì¸í•´ 주세요", + "browserNotSupport": "현재 브ë¼ìš°ì € ë˜ëŠ” 환경ì—서 ì§€ì›ë˜ì§€ 않습니다", + "success": "ë¡œê·¸ì¸ ì„±ê³µ", + "signUpSuccess": "가입 성공", + "activateSuccess": "활성화 성공", + "accountActivated": "ê³„ì •ì´ ì„±ê³µì ìœ¼ë¡œ 활성화ë˜ì—ˆìŠµë‹ˆë‹¤", + "title": "{{title}}ì— ë¡œê·¸ì¸", + "sinUpTitle": "{{title}}ì— ê°€ìž…", + "activateTitle": "ì´ë©”ì¼ í™œì„±í™”", + "activateDescription": "활성화 ì´ë©”ì¼ì´ ê·€í•˜ì˜ ì´ë©”ì¼ ì£¼ì†Œë¡œ 전송ë˜ì—ˆìŠµë‹ˆë‹¤. ì´ë©”ì¼ì˜ ë§í¬ë¥¼ 방문하여 ê°€ìž…ì„ ì™„ë£Œí•˜ì„¸ìš”.", + "continue": "다ìŒ", + "back": "ì´ì „", + "logout": "로그아웃", + "signingOut": "로그아웃 중...", + "loggedOut": "로그아웃ë˜ì—ˆìŠµë‹ˆë‹¤", + "clickToRefresh": "í´ë¦­í•˜ì—¬ ë³´ì•ˆë¬¸ìž ìƒˆë¡œê³ ì¹¨", + "switchLanguage": "언어 전환" + }, + "navbar": { + "notBefore": "ì´ì „ 아님", + "notAfter": "ì´í›„ 아님", + "minimum": "최소", + "maximum": "최대", + "fileSize": "íŒŒì¼ í¬ê¸°", + "searchBase": "검색 경로", + "searchInBase": "<0>ì—서 검색", + "conditionDuplicate": "ì¡°ê±´ì´ ì´ë¯¸ 존재합니다", + "fileType": "íŒŒì¼ í˜•ì‹", + "addCondition": "ì¡°ê±´ 추가", + "notNameOpOr": "모든 키워드 í¬í•¨ í•„ìš”", + "caseFolding": "ëŒ€ì†Œë¬¸ìž ë¬´ì‹œ", + "keywords": "키워드", + "fileNameKeywordsHelp": "ìž…ë ¥ 후 엔터키를 눌러 키워드 추가", + "advancedSearch": "고급 검색", + "searchFilesTitle": "íŒŒì¼ ê²€ìƒ‰", + "searchIn": "<0>{{keywords}} 검색", + "recentlyViewed": "최근 본 항목", + "searchFiles": "íŒŒì¼ ê²€ìƒ‰...", + "showMore": "ë”보기", + "myFiles": "ë‚´ 파ì¼", + "hisFiles": "ê·¸ì˜ íŒŒì¼", + "trash": "휴지통", + "sharedWithMe": "ê³µìœ ë°›ì€ íŒŒì¼", + "myShare": "ë‚´ 공유", + "remoteDownload": "ì›ê²© 다운로드", + "connect": "ì—°ê²° ë° ë§ˆìš´íŠ¸", + "taskQueue": "백그ë¼ìš´ë“œ 작업", + "setting": "설정", + "videos": "ë™ì˜ìƒ", + "photos": "사진", + "music": "ìŒì•…", + "documents": "문서", + "resetThumbnail": "깨진 ì¸ë„¤ì¼ 재설정", + "resetThumbnailRequested": "ì¸ë„¤ì¼ ìž¬ì„¤ì •ì´ ìš”ì²­ë˜ì—ˆìŠµë‹ˆë‹¤.", + "noFileCanResetThumbnail": "ì¸ë„¤ì¼ì„ 재설정할 수 있는 파ì¼ì´ 없습니다.", + "addATag": "태그 추가...", + "addTagDialog": { + "selectFolder": "í´ë” ì„ íƒ", + "fileSelector": "íŒŒì¼ ë¶„ë¥˜", + "folderLink": "í´ë” 바로가기", + "tagName": "태그 ì´ë¦„", + "matchPattern": "파ì¼ëª… 매칭 규칙", + "matchPatternDescription": "<0>*를 와ì¼ë“œì¹´ë“œë¡œ 사용할 수 있습니다. 예를 들어 <1>*.png는 PNG í˜•ì‹ ì´ë¯¸ì§€ë¥¼ ì˜ë¯¸í•©ë‹ˆë‹¤. 여러 줄 ê·œì¹™ì€ \"ë˜ëŠ”\" 관계로 ì—°ì‚°ë©ë‹ˆë‹¤.", + "icon": "ì•„ì´ì½˜:", + "color": "색ìƒ:", + "folderPath": "í´ë” 경로" + }, + "storage": "저장 공간", + "storageDetail": "{{used}} 사용, ì´ {{total}}", + "notLoginIn": "로그ì¸ë˜ì§€ 않ìŒ", + "visitor": "방문ìž", + "objectsSelected": "{{num}}ê°œ ê°ì²´", + "searchPlaceholder": "<0>/를 눌러 검색 시작", + "backToHomepage": "홈페ì´ì§€ë¡œ ëŒì•„가기", + "darkModeSwitch": "ë‹¤í¬ ëª¨ë“œ 설정", + "toDarkMode": "다í¬", + "toLightMode": "ë¼ì´íЏ", + "myProfile": "ê°œì¸ í”„ë¡œí•„", + "dashboard": "관리 패ë„" + }, + "fileManager": { + "currentStoragePolicy": "현재 저장 ì •ì±…: {{policy}}", + "customProps": "ì‚¬ìš©ìž ì •ì˜ ì†ì„±", + "rating": "í‰ì ", + "description": "설명", + "add": "추가", + "clickToEdit": "í´ë¦­í•˜ì—¬ 편집...", + "clickToEditSelect": "í´ë¦­í•˜ì—¬ ì„ íƒ...", + "enterUrl": "URL ìž…ë ¥...", + "searchUser": "ì‚¬ìš©ìž ê²€ìƒ‰...", + "typeToSearch": "닉네임 ë˜ëŠ” ì´ë©”ì¼ ìž…ë ¥...", + "searchProperty": "ë™ì¼í•œ ì†ì„±ì„ 가진 íŒŒì¼ ê²€ìƒ‰", + "quality": "화질", + "audioTrack": "오디오 트랙", + "auto": "ìžë™", + "default": "기본값", + "shareWithMeEmpty": "다른 ì‚¬ëžŒì˜ ê³µìœ ë¥¼ ì°¾ì„ ìˆ˜ 없습니다", + "shareWithMeEmptyDes": "다른 ì‚¬ëžŒì˜ ê³µìœ ë¥¼ 여기서 보려면, 다른 ì‚¬ëžŒì˜ ê³µìœ  ë§í¬ì— ì ‘ì†í•  때 우측 ìƒë‹¨ì—서 바로가기를 ë‚´ 파ì¼ì˜ ìž„ì˜ ìœ„ì¹˜ì— ì €ìž¥í•˜ì„¸ìš”.", + "selectAll": "ì „ì²´ ì„ íƒ", + "selectNone": "ì„ íƒ í•´ì œ", + "invertSelection": "ì„ íƒ ë°˜ì „", + "imageSize": "ì´ë¯¸ì§€ í¬ê¸°", + "focalLength": "ì´ˆì ê±°ë¦¬", + "columnExisted": "ì—´ì´ ì´ë¯¸ 존재합니다", + "metadataColumn": "메타ë°ì´í„° ({{metadata}})", + "column": "ì—´", + "listColumnSetting": "ì—´ 설정", + "addColumn": "ì—´ 추가", + "failedLoadPreview": "미리보기 로드 실패", + "recursiveLimitReached": "검색 ê¹Šì´ í•œê³„ ë„달", + "recursiveLimitReachedDes": "ì‹œìŠ¤í…œì´ ë” ê¹Šì€ í´ë” ê²€ìƒ‰ì„ ì¤‘ë‹¨í–ˆìŠµë‹ˆë‹¤. 검색 범위를 ì¢í˜€ë³´ì„¸ìš”.", + "searchConditions": "{{num}}ê°œ ì¡°ê±´", + "createDate": "ìƒì„±ì¼", + "updatedDate": "수정ì¼", + "cameraMake": "ì¹´ë©”ë¼ ì œì¡°ì‚¬", + "cameraModel": "ì¹´ë©”ë¼ ëª¨ë¸", + "lensModel": "렌즈 모ë¸", + "lensMake": "렌즈 제조사", + "metadataKey": "키", + "metadataValue": "ê°’", + "metadata": "메타ë°ì´í„°", + "symbolicFile": "바로가기", + "relocation": "저장 ì •ì±… 전환", + "downloadingFile": "\"{{name}}\" 다운로드 중, ì´ íŽ˜ì´ì§€ë¥¼ ë‹«ì§€ 마세요...", + "mountOwner": "현재 í´ë”ì˜ ì†Œìœ ìžë§Œ ì •ì±…ì„ ë§ˆìš´íŠ¸í•  수 있습니다", + "uploading": "업로드 중", + "noActionsCanBeDone": "수행할 수 있는 ìž‘ì—…ì´ ì—†ìŠµë‹ˆë‹¤", + "newFileName": "새 파ì¼.{{ext}}", + "newDocumentType": "{{display_name}} (.{{ext}})", + "text": "í…스트", + "diagram": "다ì´ì–´ê·¸ëž¨", + "whiteboard": "í™”ì´íŠ¸ë³´ë“œ", + "selectApplications": "애플리케ì´ì…˜ ì„ íƒ...", + "newlyCreatedFolder": "새 í´ë”", + "expandAllApp": "모든 애플리케ì´ì…˜ 펼치기", + "epubViewer": "ePub 리ë”", + "googledocs": "Google Docs 온ë¼ì¸ ë·°ì–´", + "m365viewer": "Microsoft Office 온ë¼ì¸ ë·°ì–´", + "pdfViewer": "PDF ë·°ì–´", + "archivePreview": "ì•„ì¹´ì´ë¸Œ 미리보기", + "extractSelected": "ì„ íƒí•œ íŒŒì¼ ì••ì¶• í•´ì œ", + "viewerFileSizeWarning": "열린 íŒŒì¼ í¬ê¸°({{file_size}})ê°€ {{app}}ì˜ ì œí•œ({{max}})ì„ ì´ˆê³¼í•˜ì—¬ ì •ìƒì ìœ¼ë¡œ ìž‘ë™í•˜ì§€ ì•Šì„ ìˆ˜ 있습니다.", + "testSubtitleStyle": "ìžë§‰ ìŠ¤íƒ€ì¼ í…ŒìŠ¤íŠ¸ AaBbCc", + "color": "색ìƒ", + "fontSize": "글꼴 í¬ê¸°", + "disableSubtitle": "ìžë§‰ 비활성화", + "noSubtitle": "현재 í´ë”ì—서 ASS/SRT/VTT ìžë§‰ 파ì¼ì„ ì°¾ì„ ìˆ˜ 없습니다.", + "subtitleStyles": "ìžë§‰ 스타ì¼", + "subtitles": "ìžë§‰", + "markdownEditor": "Markdown ì—디터", + "saveSuccess": "{{time}}ì— ì €ìž¥ 성공", + "drawioLng": "ko", + "charset": "ì¸ì½”딩", + "textType": "í…스트 형ì‹", + "fileSaved": "파ì¼ì´ 저장ë˜ì—ˆìŠµë‹ˆë‹¤", + "failedToLoadFile": "íŒŒì¼ ë¡œë“œ 실패: {{msg}}", + "monacoEditor": "Monaco 코드 ì—디터", + "preparingOpenFile": "íŒŒì¼ ì—´ê¸° 준비 중...", + "openWithDescription": ".{{ext}} 파ì¼ì„ ì—´ 애플리케ì´ì…˜ì„ ì„ íƒí•˜ì„¸ìš”.", + "openWith": "ì—°ê²° 프로그램", + "readOnly": "ì½ê¸° ì „ìš©", + "save": "저장", + "noMoreImages": "현재 페ì´ì§€ì— ë³¼ 수 있는 ì´ë¯¸ì§€ê°€ 없습니다", + "imageViewer": "ì´ë¯¸ì§€ ë·°ì–´", + "logFileDeleteShare": "공유 ë§í¬ ì‚­ì œ", + "logFileEditShare": "공유 ë§í¬ 편집", + "deleteShareWarning": "ì´ ê³µìœ  ë§í¬ë¥¼ 삭제하시겠습니까?", + "edit": "편집", + "editAndReactivate": "편집하고 재활성화", + "yes": "예", + "no": "아니오", + "permanentValid": "ì˜êµ¬ 유효", + "manageShares": "공유 ë§í¬ 관리", + "manageDirectLinks": "ì§ì ‘ ë§í¬ 관리", + "deleteLinkConfirm": "ì´ ì§ì ‘ ë§í¬ë¥¼ 삭제하시겠습니까?", + "directLinkNotFound": "찾고 있는 ì§ì ‘ ë§í¬ê°€ 존재하지 않습니다.", + "versionNotFound": "찾고 있는 ë²„ì „ì´ ì¡´ìž¬í•˜ì§€ 않습니다.", + "setNow": "지금 설정", + "permissionNotSet": "ì´ íŒŒì¼ì— ê¶Œí•œì´ ì„¤ì •ë˜ì§€ 않았습니다", + "permissionNotSetDes": "ê¶Œí•œì´ ì„¤ì •ë˜ì§€ ì•Šì€ ê²½ìš° ìƒìœ„ í´ë” ë˜ëŠ” 공유 ë§í¬ì˜ 권한 ì„¤ì •ì„ ë”°ë¦…ë‹ˆë‹¤", + "permissions": "권한", + "logFileUpdateMetadata": "íŒŒì¼ ë©”íƒ€ë°ì´í„° ì—…ë°ì´íЏ", + "all": "ì „ì²´", + "updatesOnly": "ì—…ë°ì´íЏ 활ë™ë§Œ 보기", + "readsOnly": "ì½ê¸° 활ë™ë§Œ 보기", + "myActivitiesOnly": "ë‚´ 활ë™", + "logUpdateView": "보기 설정 ì—…ë°ì´íЏ", + "logDeleteDirectLink": "ì§ì ‘ ë§í¬ ì‚­ì œ", + "logFileImported": "외부 파ì¼ì—서 가져옴", + "logGetDirectLink": "<0>ì§ì ‘ ë§í¬ë¥¼ ìƒì„±í–ˆìŠµë‹ˆë‹¤", + "logFileMount": "저장 ì •ì±… \"{{name}}\"ì— ë°”ì¸ë”©", + "lookForThisVersion": "ì´ ë²„ì „ 찾기", + "logFileThumbGenerated": "ì¸ë„¤ì¼ ìƒì„± 트리거", + "logFileLivePhotoUploaded": "Live Photo 업로드", + "logFileCreate": "ì´ ê°ì²´ ìƒì„±", + "logFileRename": "ê°ì²´ë¥¼ \"{{originalName}}\"ì—서 \"{{newName}}\"으로 ì´ë¦„ 변경", + "logFileSetPermission": "íŒŒì¼ ê¶Œí•œ 변경", + "logFileEntityUpload": "íŒŒì¼ ë‚´ìš© ì—…ë°ì´íЏ", + "logFileCopyFrom": "ì´ ê°ì²´ ìƒì„±, <0>ì—서 <1>로 복사하여 ìƒì„±", + "logFileCopyTo": "<0>ì—서 <1>로 복사ë¨", + "logFileMoveTo": "<0>ì—서 <1>로 ì´ë™", + "logFileMoveToTrash": "ì´ ê°ì²´ë¥¼ <0>ì—서 휴지통으로 ì´ë™", + "logFileShare": "ì´ ê°ì²´ë¥¼ 공유했습니다", + "logFileSetCurrentVersion": "íŒŒì¼ ë²„ì „ì„ <0>로 롤백", + "logFileDeleteVersion": "<0>ì— ìƒì„±ëœ 버전 ì‚­ì œ", + "logEntityDownloaded": "ì´ ê°ì²´ë¥¼ 다운로드하거나 ì½ì—ˆìŠµë‹ˆë‹¤", + "logDirectLinkDownloaded": "<0>ì§ì ‘ ë§í¬ë¥¼ 통해 ì´ ê°ì²´ë¥¼ ì½ì—ˆìŠµë‹ˆë‹¤", + "logRelocate": "저장 ì •ì±…ì„ {{newPolicy}}로 전환", + "logCreateArchive": "ì••ì¶• íŒŒì¼ <0>ì— ì¶”ê°€", + "logExtractArchive": "<0>로 ì••ì¶• í•´ì œ", + "deleteVersionWarning": "ì´ ë²„ì „ì„ ì‚­ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ? ì´ ìž‘ì—…ì€ ë˜ëŒë¦´ 수 없습니다.", + "setAsCurrent": "현재 버전으로 설정", + "current": "[현재 버전]", + "createdBy": "ìƒì„±ìž", + "manageVersions": "버전 관리", + "livePhoto": "Live Photo", + "version": "버전", + "actions": "작업", + "versionEntity": "íŒŒì¼ ë°ì´í„° ë° ì´ë ¥ 버전", + "data": "ë°ì´í„°", + "owned": "ì´ íŒŒì¼ ì†Œìœ ", + "ownedSymbolic": "ì´ ë°”ë¡œê°€ê¸° 소유", + "expires": "만료 시간", + "originalLocation": "ì›ë³¸ 위치", + "myPermissions": "ë‚´ 권한", + "descendant": "하위 ê°ì²´", + "folderChildren": "{{files}}ê°œ 파ì¼, {{folders}}ê°œ í´ë”", + "moreThan": "{{text}}보다 í¼", + "calculate": "계산", + "unset": "설정ë˜ì§€ 않ìŒ", + "folder": "í´ë”", + "file": "파ì¼", + "symbolicLink": "바로가기 ({{srcType}})", + "type": "형ì‹", + "storageUsed": "사용 공간", + "location": "위치", + "basicInfo": "기본 ì •ë³´", + "format": "í¬ë§·", + "duration": "ìž¬ìƒ ì‹œê°„", + "artist": "아티스트", + "album": "앨범", + "title": "제목", + "resolution": "í•´ìƒë„", + "takenAt": "ì´¬ì˜ ì‹œê°„", + "software": "소프트웨어", + "copyright": "작성ìž", + "exposureBias": "노출 ë³´ì •", + "flash": "플래시", + "copyToClipboard": "í´ë¦½ë³´ë“œì— 복사", + "searchSomething": "\"{{text}}\" 검색...", + "iso": "ISO", + "exposureValue": "{{num}}ì´ˆ", + "exposure": "노출", + "aperture": "조리개", + "address": "주소", + "street": "거리", + "locality": "지역", + "place": "ë„시", + "district": "구", + "region": "성", + "country": "êµ­ê°€", + "mediaInfo": "미디어 ì •ë³´", + "details": "ìƒì„¸ì •ë³´", + "activity": "활ë™", + "goToSharedLink": "공유 ë§í¬ë¡œ ì´ë™", + "saveShortcut": "공유를 바로가기로 저장", + "customizeIcon": "ì•„ì´ì½˜ ì‚¬ìš©ìž ì •ì˜", + "tags": "태그", + "apply": "ì ìš©", + "customizeColor": "ìƒ‰ìƒ ì‚¬ìš©ìž ì •ì˜", + "folderColor": "í´ë” 색ìƒ", + "restore": "ë³µì›", + "unpin": "ê³ ì • í•´ì œ", + "youDontHaveReadPermissionToThisFile": "ì´ ë‚´ìš©ì„ ì½ì„ ê¶Œí•œì´ ì—†ìŠµë‹ˆë‹¤", + "anonymousAccessDenied": "ì´ ë‚´ìš©ì„ ì½ì„ ê¶Œí•œì´ ì—†ìŠµë‹ˆë‹¤. 계정으로 로그ì¸í•´ 보세요.", + "sharedWithOthers": "다른 사람과 공유", + "new": "새로 만들기", + "open": "열기", + "openParentFolder": "ìƒìœ„ í´ë”로 ì´ë™", + "download": "다운로드", + "batchDownload": "ì¼ê´„ 다운로드", + "share": "공유", + "rename": "ì´ë¦„ 바꾸기", + "organize": "정리", + "pin": "사ì´ë“œë°”ì— ê³ ì •", + "pinAlias": "표시 별칭", + "optional": "ì„ íƒì‚¬í•­", + "move": "ì´ë™", + "delete": "ì‚­ì œ", + "moreActions": "ë” ë§Žì€ ìž‘ì—…", + "refresh": "새로고침", + "createArchive": "ì••ì¶• íŒŒì¼ ìƒì„±", + "newFolder": "í´ë” 만들기", + "newFile": "íŒŒì¼ ë§Œë“¤ê¸°", + "showFullPath": "경로 표시", + "listView": "목ë¡", + "gridView": "격ìž", + "galleryView": "갤러리", + "paginationSize": "페ì´ì§€ í¬ê¸°", + "paginationOption": "{{option}} / 페ì´ì§€", + "noPagination": "페ì´ì§€ 나누기 ì—†ìŒ", + "sortMethod": "ì •ë ¬", + "sortMethods": { + "A-Z": "A-Z", + "Z-A": "Z-A", + "oldestUploaded": "가장 ì˜¤ëž˜ëœ ì—…ë¡œë“œ", + "newestUploaded": "가장 최근 업로드", + "oldestModified": "가장 ì˜¤ëž˜ëœ ìˆ˜ì •", + "newestModified": "가장 최근 수정", + "smallest": "가장 ìž‘ì€", + "largest": "가장 í°" + }, + "shareCreateBy": "{{nick}}ê°€ ìƒì„±", + "name": "ì´ë¦„", + "size": "í¬ê¸°", + "lastModified": "수정ì¼", + "currentFolder": "현재 í´ë”", + "backToParentFolder": "ìƒìœ„ í´ë”", + "folders": "í´ë”", + "files": "파ì¼", + "listError": "요청 중 오류 ë°œìƒ", + "dropFileHere": "파ì¼ì„ ì—¬ê¸°ì— ë“œëž˜ê·¸", + "orClickUploadButton": "ë˜ëŠ” 좌측 ìƒë‹¨ì˜ \"새로 만들기\" ë²„íŠ¼ì„ í´ë¦­í•˜ì—¬ íŒŒì¼ ì¶”ê°€", + "nothingFound": "ì•„ë¬´ê²ƒë„ ì°¾ì„ ìˆ˜ 없습니다", + "uploadFiles": "íŒŒì¼ ì—…ë¡œë“œ", + "uploadFolder": "í´ë” 업로드", + "newRemoteDownloads": "ì›ê²© 다운로드", + "enter": "ì§„ìž…", + "getSourceLink": "ì§ì ‘ ë§í¬ 가져오기", + "createRemoteDownloadForTorrent": "ì›ê²© 다운로드 작업 ìƒì„±", + "extractArchive": "ì••ì¶• í•´ì œ", + "createShareLink": "공유 ë§í¬ ìƒì„±", + "viewDetails": "ìƒì„¸ ì •ë³´", + "copy": "복사", + "bytes": " ({{bytes}} ë°”ì´íЏ)", + "storagePolicy": "저장 ì •ì±…", + "inheritedFromParent": "(ìƒìœ„ í´ë” 따름)", + "childFolders": "í¬í•¨ í´ë”", + "childFiles": "í¬í•¨ 파ì¼", + "childCount": "{{num}}ê°œ", + "parentFolder": "ìƒìœ„ í´ë”", + "rootFolder": "루트 í´ë”", + "modifiedAt": "수정ì¼", + "createdAt": "ìƒì„±ì¼", + "statisticAt": "통계ì¼", + "musicPlayer": "ìŒì•… 플레ì´ì–´", + "closeAndStop": "ìž¬ìƒ ì¢…ë£Œ", + "playInBackground": "백그ë¼ìš´ë“œ 재ìƒ", + "copyTo": "복사 위치", + "copyToDst": "<0>로 복사", + "moveTo": "ì´ë™ 위치", + "moveToDst": "<0>로 ì´ë™", + "errorReadFileContent": "íŒŒì¼ ë‚´ìš©ì„ ì½ì„ 수 없습니다: {{msg}}", + "wordWrap": "ìžë™ 줄바꿈", + "pdfLoadingError": "PDF 로드 실패: {{msg}}", + "subtitleSwitchTo": "ìžë§‰ì„ {{subtitle}}로 전환", + "noSubtitleAvailable": "비디오 í´ë”ì— ì‚¬ìš© 가능한 ìžë§‰ 파ì¼ì´ 없습니다 (ì§€ì›: ASS/SRT/VTT)", + "subtitle": "ìžë§‰ ì„ íƒ", + "playlist": "재ìƒëª©ë¡", + "openInExternalPlayer": "외부 플레ì´ì–´ë¡œ 열기", + "repeatMode": "반복 모드", + "listRepeat": "ëª©ë¡ ë°˜ë³µ", + "singleRepeat": "한 곡 반복", + "shuffle": "무작위 재ìƒ", + "playbackSpeed": "ìž¬ìƒ ì†ë„", + "searchResult": "검색 ê²°ê³¼", + "preparingBathDownload": "ì¼ê´„ 다운로드 준비 중...", + "preparingDownload": "다운로드 준비 중...", + "browserDownload": "브ë¼ìš°ì € 측 로컬 í´ë” 다운로드", + "browserDownloadDescription": "브ë¼ìš°ì €ê°€ íŒŒì¼ êµ¬ì¡°ë¥¼ 지정한 로컬 í´ë”ì— í•˜ë‚˜ì”© 다운로드합니다.", + "browserBatchDownload": "브ë¼ìš°ì € 측 패키징", + "browserBatchDownloadDescription": "브ë¼ìš°ì €ê°€ 실시간으로 다운로드하여 Zip 파ì¼ë¡œ 패키징하며, 4GB보다 í° ë°ì´í„°ëŠ” 다운로드할 수 없습니다.", + "serverBatchDownload": "서버 측 중계 패키징", + "serverBatchDownloadDescription": "서버 측ì—서 Zip 파ì¼ë¡œ 중계 패키징하여 í´ë¼ì´ì–¸íŠ¸ë¡œ 실시간 전송하여 다운로드하며, 공유 바로가기는 ì§€ì›í•˜ì§€ 않습니다.", + "selectArchiveMethod": "ì¼ê´„ 다운로드 방법 ì„ íƒ", + "batchDownloadStarted": "ì¼ê´„ 다운로드가 시작ë˜ì—ˆìŠµë‹ˆë‹¤. ì´ íŽ˜ì´ì§€ë¥¼ ë‹«ì§€ 마세요...", + "batchDownloadError": "패키징 중 오류 ë°œìƒ: {{msg}}", + "userDenied": "ì‚¬ìš©ìž ê±°ë¶€", + "directoryDownloadReplace": "ì´ íŒŒì¼ êµì²´", + "directoryDownloadReplaceDescription": "ë¡œì»¬ì˜ \"{{name}}\"ì„ ë®ì–´ì”니다", + "directoryDownloadSkip": "ì´ íŒŒì¼ ê±´ë„ˆë›°ê¸°", + "directoryDownloadSkipDescription": "\"{{name}}\" 다운로드를 건너ëœë‹ˆë‹¤", + "selectDirectoryDuplicationMethod": "íŒŒì¼ ì¤‘ë³µ", + "directoryDownloadReplaceAll": "ì´ íŒŒì¼ê³¼ ì´í›„ 모든 중복 íŒŒì¼ êµì²´", + "directoryDownloadReplaceAllDescription": "ë¡œì»¬ì˜ \"{{name}}\"ì„ ë®ì–´ì“°ê³  ì„ íƒì„ 기억합니다", + "directoryDownloadSkipAll": "ì´ íŒŒì¼ê³¼ ì´í›„ 모든 중복 íŒŒì¼ ê±´ë„ˆë›°ê¸°", + "directoryDownloadSkipAllDescription": "\"{{name}}\" 다운로드를 건너뛰고 ì„ íƒì„ 기억합니다", + "directoryDownloadStarted": "다운로드가 시작ë˜ì—ˆìŠµë‹ˆë‹¤. ì´ íƒ­ì„ ë‹«ì§€ 마세요", + "directoryDownloadFinished": "다운로드 완료, 실패한 ê°ì²´ ì—†ìŒ", + "directoryDownloadFinishedWithError": "다운로드 완료, {{failed}}ê°œ ê°ì²´ 실패", + "directoryDownloadPermissionError": "권한 ì—†ìŒ, 로컬 íŒŒì¼ ì½ê¸°/쓰기를 허용해 주세요", + "back": "뒤로", + "view": "보기", + "layout": "ë ˆì´ì•„웃", + "thumbnails": "ì¸ë„¤ì¼", + "on": "켜기", + "off": "ë„기", + "viewSetting": "보기 설정", + "saved": "저장ë¨", + "notSet": "설정ë˜ì§€ 않ìŒ", + "deleteViewSetting": "보기 설정 ì‚­ì œ" + }, + "modals": { + "includePasswordInShareLink": "공유 ë§í¬ì— 비밀번호 í¬í•¨", + "includePasswordInShareLinkDes": "ì²´í¬í•˜ë©´ 공유 ë§í¬ì— 비밀번호가 í¬í•¨ë˜ì–´ ì´ ë§í¬ë¡œ 접근할 때 비밀번호를 다시 입력할 필요가 없습니다.", + "showFileName": "파ì¼ëª… 표시", + "forceDownload": "ê°•ì œ 다운로드", + "archiveFile": "ì••ì¶• 파ì¼", + "cancelDownload": "다운로드 취소", + "always": "í•­ìƒ", + "justOnce": "한 번만", + "quality": "품질", + "saveAsOtherFormat": "다른 형ì‹ìœ¼ë¡œ 저장", + "conflictDes1": "íŒŒì¼ ë²„ì „ ì¶©ëŒì´ ë°œìƒí–ˆìŠµë‹ˆë‹¤. 가능한 ì›ì¸:", + "conflictDes2": "<0>파ì¼ì„ ì—° 후 다른 ê³³ì—서 새 버전으로 ì—…ë°ì´íЏë˜ì—ˆìŠµë‹ˆë‹¤.<1>새 파ì¼ëª…ì´ë‚˜ 새 위치로 저장한 경우 ë™ì¼í•œ ì´ë¦„ì˜ íŒŒì¼ì´ ì´ë¯¸ 존재할 수 있습니다.", + "saveAs": "다른 ì´ë¦„으로 저장", + "versionConflict": "버전 ì¶©ëŒ", + "overwrite": "ë®ì–´ì“°ê¸°", + "editShareLink": "공유 ë§í¬ 편집", + "clearPermissions": "권한 설정 지우기", + "shortcutCreated": "바로가기가 ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤", + "createShortcut": "바로가기 ìƒì„±", + "createShortcutTo": "<0>ì— ë°”ë¡œê°€ê¸° ìƒì„±", + "read": "보기", + "readDes": "파ì¼ì˜ 경우 ë‚´ìš©, 메타ë°ì´í„° 등 ìƒì„¸ 정보를 ë³¼ 수 있으며, í´ë”ì˜ ê²½ìš° 하위 íŒŒì¼ ëª©ë¡ê³¼ 메타ë°ì´í„°ë¥¼ ë³¼ 수 있습니다.", + "createDes": "í´ë”ì—ë§Œ 유효하며, í•˜ìœ„ì— ìƒˆ 파ì¼ì„ ìƒì„±í•˜ê±°ë‚˜ 업로드할 수 있고, 파ì¼ì„ ì´ë™í•˜ê±°ë‚˜ 복사할 수 있습니다.", + "update": "수정", + "updateDes": "메타ë°ì´í„° 수정, ê°ì²´ ì´ë¦„ 바꾸기, í™œë™ ê¸°ë¡ ë³´ê¸°ê°€ 가능하며, 파ì¼ì˜ 경우 ë‚´ìš©ì„ ì—…ë°ì´íŠ¸í•  수 있습니다.", + "delete": "ì‚­ì œ", + "deleteDes": "ê°ì²´ë¥¼ 삭제하거나 다른 곳으로 ì´ë™í•  수 있습니다.", + "noAccess": "권한 ì—†ìŒ", + "targetExisted": "대ìƒì´ ì´ë¯¸ 존재합니다", + "explicitAccess": "ëª…ì‹œì  ì•¡ì„¸ìŠ¤ 권한", + "generalAccess": "ì¼ë°˜ 액세스 권한", + "users": "사용ìž", + "groups": "ì‚¬ìš©ìž ê·¸ë£¹", + "builtinCollections": "내장 컬렉션", + "everyone": "기타 모든 사람", + "otherGroup": "다른 ì‚¬ìš©ìž ê·¸ë£¹", + "sameGroup": "나와 ê°™ì€ ì‚¬ìš©ìž ê·¸ë£¹", + "anonymous": "ìµëª… 방문ìž", + "noResults": "ê²°ê³¼ ì—†ìŒ", + "searchGroupUser": "ì‚¬ìš©ìž ë˜ëŠ” ì‚¬ìš©ìž ê·¸ë£¹ 검색...", + "resetToDefault": "기본값으로 재설정", + "duplicateTag": "\"{{tag}}\" 태그가 ì´ë¯¸ 존재합니다", + "colorForTag": "새 태그 ìƒ‰ìƒ ì‚¬ìš©ìž ì •ì˜", + "enterForNewTag": "엔터키를 눌러 새 태그 추가", + "manageTags": "태그 관리", + "onlyOwner": "íŒŒì¼ ì†Œìœ ìžë§Œ ì´ íŒŒì¼ì„ 강제로 잠금 해제할 수 있습니다", + "forceUnlock": "ê°•ì œ 잠금 í•´ì œ", + "forceUnlockAll": "ëª¨ë‘ ê°•ì œ 잠금 í•´ì œ", + "forceUnlockDes": "ê°•ì œ 잠금 해제는 íŒŒì¼ ìƒíƒœ ì´ìƒì„ ì¼ìœ¼í‚¬ 수 있으므로 파ì¼ì´ 주ë™ì ìœ¼ë¡œ í•´ì œë˜ê¸°ë¥¼ 기다리는 ê²ƒì„ ê¶Œìž¥í•©ë‹ˆë‹¤. ê³„ì† ìž ê¸ˆ 해제하시겠습니까?", + "webdav": "WebDAV", + "soft-delete": "휴지통으로 ì´ë™", + "updateMetadata": "메타ë°ì´í„° ì—…ë°ì´íЏ", + "upload": "업로드", + "moveCopy": "ì´ë™ ë˜ëŠ” 복사", + "view": "보기", + "cannotPerformAction": "여기로 ì´ë™í•˜ê±°ë‚˜ 복사하는 ê²ƒì„ ì§€ì›í•˜ì§€ 않습니다", + "cannotMoveCopyToChild": "하위 í´ë”로 ì´ë™í•˜ê±°ë‚˜ 복사할 수 없습니다", + "copySuccess": "{{num}}ê°œ 파ì¼ì„ 성공ì ìœ¼ë¡œ 복사했습니다", + "moveSuccess": "{{num}}ê°œ 파ì¼ì„ 성공ì ìœ¼ë¡œ ì´ë™í–ˆìŠµë‹ˆë‹¤", + "setPermission": "권한 설정", + "unknownParent": "알 수 없는 ìƒìœ„ í´ë”", + "unknownParentDes": "ì ìœ ëœ í´ë”는 공유 í´ë”ì˜ ìƒìœ„ í´ë”ì´ë©° 귀하가 소유하지 않습니다", + "lockConflictTitle": "파ì¼ì´ ì ìœ ë¨", + "lockConflictDescription": "ë‹¤ìŒ íŒŒì¼ì´ 현재 사용 중ì´ë¯€ë¡œ ìž‘ì—…ì„ ì™„ë£Œí•  수 없습니다. ë‚˜ì¤‘ì— ë‹¤ì‹œ 시ë„하세요. íŒŒì¼ ì†Œìœ ìžì´ê³  파ì¼ì´ 사용ë˜ì§€ 않는다고 확신하는 경우 파ì¼ì„ 강제로 잠금 해제하고 다시 시ë„í•  수 있습니다.", + "usedBy": "사용ìž", + "application": "애플리케ì´ì…˜", + "errorDetailsTitle": "오류 ìƒì„¸ì •ë³´", + "processingMoving": "íŒŒì¼ ì´ë™ 중...", + "processingCopying": "íŒŒì¼ ë³µì‚¬ 중...", + "processingRestoring": "íŒŒì¼ ë³µì› ì¤‘...", + "fileRestored": "{{num}}ê°œ 파ì¼ì„ ì›ëž˜ 위치로 ë³µì›í–ˆìŠµë‹ˆë‹¤", + "duplicatedObjectName": "새 ì´ë¦„ì´ ê¸°ì¡´ 파ì¼ê³¼ 중복ë©ë‹ˆë‹¤", + "newNameLengthError": "파ì¼ëª… 길ì´ëŠ” 1~255ìž ì‚¬ì´ì—¬ì•¼ 합니다", + "newNameCharacterError": "파ì¼ëª…ì— ë‹¤ìŒ ë¬¸ìžë¥¼ í¬í•¨í•  수 없습니다: \\ / : * ? \" < > |", + "newNameDotError": "파ì¼ëª…ì€ \".\" ë˜ëŠ” \"..\"ì¼ ìˆ˜ 없습니다", + "taskCreated": "ìž‘ì—…ì´ ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤", + "taskCreateFailed": "{{failed}}ê°œ 작업 ìƒì„± 실패: {{details}}", + "linkCopied": "ë§í¬ê°€ 복사ë˜ì—ˆìŠµë‹ˆë‹¤", + "getSourceLinkTitle": "íŒŒì¼ ì§ì ‘ ë§í¬ 가져오기", + "sourceLink": "íŒŒì¼ ì§ì ‘ ë§í¬", + "folderName": "í´ë” ì´ë¦„", + "create": "ìƒì„±", + "fileName": "파ì¼ëª…", + "renameDescription": "<0>{{name}}ì˜ ìƒˆ ì´ë¦„ ìž…ë ¥:", + "newName": "새 ì´ë¦„", + "moveToDescription": "<0>{{name}}로 ì´ë™", + "saveToTitle": "저장 위치", + "saveToTitleDescription": "<0>{{name}}ì— ì €ìž¥", + "deleteTitle": "ê°ì²´ ì‚­ì œ", + "deleteOneDescription": "<0>{{name}}ì„ íœ´ì§€í†µìœ¼ë¡œ ì´ë™í•˜ì‹œê² ìŠµë‹ˆê¹Œ?", + "deleteMultipleDescription": "ì´ {{num}}ê°œ ê°ì²´ë¥¼ 휴지통으로 ì´ë™í•˜ì‹œê² ìŠµë‹ˆê¹Œ?", + "deleteOneDescriptionHard": "<0>{{name}}ì„ ì˜êµ¬ì ìœ¼ë¡œ 삭제하시겠습니까?", + "trashRetention": "íœ´ì§€í†µì˜ íŒŒì¼ì€ <0>{{num}} 후 ìžë™ìœ¼ë¡œ ì‚­ì œë©ë‹ˆë‹¤.", + "deleteMultipleDescriptionHard": "ì´ {{num}}ê°œ ê°ì²´ë¥¼ ì˜êµ¬ì ìœ¼ë¡œ 삭제하시겠습니까?", + "newRemoteDownloadTitle": "새 ì›ê²© 다운로드 작업", + "remoteDownloadURL": "다운로드 ë§í¬", + "remoteDownloadURLDescription": "íŒŒì¼ ë‹¤ìš´ë¡œë“œ 주소를 입력하세요. 한 ì¤„ì— í•˜ë‚˜ì”©", + "remoteDownloadDst": "다운로드 위치", + "processNode": "처리 노드", + "remoteDownloadNodeAuto": "ìžë™ 할당", + "createTask": "작업 ìƒì„±", + "downloadToDst": "<0>{{name}}ì— ë‹¤ìš´ë¡œë“œ", + "downloadTo": "다운로드 위치", + "decompressTo": "ì••ì¶• í•´ì œ 위치", + "decompressToDst": "<0>{{name}}로 ì••ì¶• í•´ì œ", + "defaultEncoding": "기본값", + "chineseMajorEncoding": "ê°„ì²´ 중국어 ì¼ë°˜ ì¸ì½”딩", + "selectEncoding": "ZIP íŒŒì¼ ì¸ì½”딩", + "password": "ì••ì¶• íŒŒì¼ ë¹„ë°€ë²ˆí˜¸", + "passwordDescription": "ì••ì¶• 파ì¼ì´ 암호화ë˜ì§€ ì•Šì€ ê²½ìš° 여기를 비워 ë‘세요.", + "noEncodingSelected": "ì¸ì½”딩 ë°©ì‹ì´ ì„ íƒë˜ì§€ 않았습니다", + "listingFiles": "íŒŒì¼ ë‚˜ì—´ 중...", + "listingFileError": "íŒŒì¼ ë‚˜ì—´ 중 오류 ë°œìƒ: {{message}}", + "generatingSourceLinks": "외부 ë§í¬ ìƒì„± 중...", + "noFileCanGenerateSourceLink": "외부 ë§í¬ë¥¼ ìƒì„±í•  수 있는 파ì¼ì´ 없습니다", + "sourceBatchSizeExceeded": "현재 ì‚¬ìš©ìž ê·¸ë£¹ì€ ìµœëŒ€ {{limit}}ê°œ 파ì¼ì— 대해 ë™ì‹œì— 외부 ë§í¬ë¥¼ ìƒì„±í•  수 있습니다", + "zipFileName": "ì••ì¶• 파ì¼ëª…", + "shareLinkShareContent": "{{name}}ì„ ê³µìœ í–ˆìŠµë‹ˆë‹¤. ë§í¬: {{link}}", + "shareLinkPasswordInfo": " 비밀번호: {{password}}", + "createShareLink": "공유 ë§í¬ ìƒì„±", + "privateShare": "비밀번호로 ë§í¬ 보호", + "privateShareDes": "ì²´í¬í•˜ë©´ 공유 ë§í¬ì— 접근하기 위해 비밀번호가 필요합니다.", + "useCustomPassword": "공유 비밀번호 ì‚¬ìš©ìž ì •ì˜", + "expireAfterDownload": "다운로드 후 ìžë™ 만료", + "sharePassword": "공유 비밀번호", + "randomlyGenerate": "무작위 ìƒì„±", + "expireAutomatically": "시간 초과 ìžë™ 만료", + "downloadLimitOptions": "{{num}}회 다운로드", + "or": "ë˜ëŠ”", + "5minutes": "5ë¶„", + "1hour": "1시간", + "1day": "1ì¼", + "7days": "7ì¼", + "30days": "30ì¼", + "custom": "ì‚¬ìš©ìž ì •ì˜", + "minutes": "ë¶„", + "downloads": "회 다운로드", + "expirePrefix": "", + "expireSuffix": "후 만료", + "allowPreview": "미리보기 허용", + "allowPreviewDescription": "공유 페ì´ì§€ì—서 íŒŒì¼ ë‚´ìš© 미리보기 허용 여부", + "shareLink": "공유 ë§í¬", + "sendLink": "ë§í¬ 전송", + "directoryDownloadReplaceNotifiction": "{{name}}ì„ ë®ì–´ì”€", + "directoryDownloadSkipNotifiction": "{{name}}ì„ ê±´ë„ˆëœ€", + "directoryDownloadTitle": "ì¼ê´„ 다운로드 로그", + "directoryDownloadStarted": "\"{{name}}\" 다운로드 시작", + "directoryDownloadFinished": "\"{{name}}\" 다운로드 완료", + "directoryDownloadError": "오류 ë°œìƒ: {{msg}}", + "directoryDownloadErrorNotification": "{{name}} 다운로드 중 오류 ë°œìƒ: {{msg}}", + "directoryDownloadAutoscroll": "ìžë™ 스í¬ë¡¤", + "directoryDownloadCancelled": "다운로드가 취소ë˜ì—ˆìŠµë‹ˆë‹¤", + "advanceOptions": "고급 옵션", + "skipSoftDelete": "íŒŒì¼ ì™„ì „ ì‚­ì œ", + "skipSoftDeleteDes": "íœ´ì§€í†µì„ ê±´ë„ˆë›°ê³  파ì¼ì„ ì§ì ‘ ì‚­ì œ", + "unlinkOnly": "ë¬¼ë¦¬ì  íŒŒì¼ ë³´ì¡´", + "unlinkOnlyDes": "íŒŒì¼ ê¸°ë¡ë§Œ 삭제하고 ë¬¼ë¦¬ì  íŒŒì¼ì€ 삭제하지 않ìŒ", + "shareView": "공유 보기 설정", + "shareViewDes": "ì²´í¬í•˜ë©´ 다른 사용ìžê°€ ì´ ê³µìœ  í´ë”ì— ì ‘ê·¼í•  때 ì„œë²„ì— ì €ìž¥ëœ ë³´ê¸° 설정(ë ˆì´ì•„웃, ì •ë ¬ 등)ì„ ë³¼ 수 있습니다.", + "showReadme": "README íŒŒì¼ í‘œì‹œ", + "showReadmeDes": "ì²´í¬í•˜ë©´ í´ë” ë‚´ <0>README.md (ëŒ€ì†Œë¬¸ìž êµ¬ë¶„) 파ì¼ì„ 방문ìžì—게 ìžë™ìœ¼ë¡œ 표시합니다.", + "viewSetting": "보기 설정", + "saved": "저장ë¨", + "notSet": "설정ë˜ì§€ 않ìŒ", + "deleteViewSetting": "보기 설정 ì‚­ì œ" + }, + "uploader": { + "fileCopyName": "사본_", + "overwriteTooltip": "íŒŒì¼ ì¤‘ë³µ 시 기존 íŒŒì¼ ë®ì–´ì“°ê¸°, 새로 ì¶”ê°€ëœ ìž‘ì—…ì—ë§Œ 유효", + "rename": "새 파ì¼ëª…으로 재시ë„", + "overwrite": "기존 íŒŒì¼ ë®ì–´ì“°ê¸°", + "pasteFilesHere": "파ì¼ì„ ì—¬ê¸°ì— ë¶™ì—¬ë„£ê¸°", + "clipboardDefaultFileName": "í´ë¦½ë³´ë“œ {{date}}.png", + "uploadFromClipboard": "í´ë¦½ë³´ë“œì—서 업로드", + "uploadList": "업로드 목ë¡", + "fileNotMatchError": "ì„ íƒí•œ 파ì¼ì´ ì›ë³¸ 파ì¼ê³¼ ì¼ì¹˜í•˜ì§€ 않습니다", + "unknownError": "알 수 없는 오류 ë°œìƒ: {{msg}}", + "taskListEmpty": "업로드 ìž‘ì—…ì´ ì—†ìŠµë‹ˆë‹¤", + "hideTaskList": "ëª©ë¡ ìˆ¨ê¸°ê¸°", + "uploadTasks": "업로드 대기열", + "moreActions": "ë” ë§Žì€ ìž‘ì—…", + "addNewFiles": "새 íŒŒì¼ ì¶”ê°€", + "toggleTaskList": "대기열 펼치기/접기", + "pendingInQueue": "대기 중...", + "preparing": "준비 중...", + "processing": "처리 중...", + "progressDescription": "{{uploaded}} 업로드ë¨, ì´ {{total}} - {{percentage}}%", + "progressDescriptionFull": "{{speed}} {{uploaded}} 업로드ë¨, ì´ {{total}} - {{percentage}}%", + "progressDescriptionPlaceHolder": "ì—…ë¡œë“œë¨ - ", + "uploaded": "업로드ë¨", + "rootFolder": "루트 í´ë”", + "unknownStatus": "알 수 ì—†ìŒ", + "resumed": "ì¤‘ë‹¨ì  ìž¬ê°œ", + "resumable": "진행률 ë³µì› ê°€ëŠ¥", + "retry": "재시ë„", + "deleteTask": "작업 ê¸°ë¡ ì‚­ì œ", + "cancelAndDelete": "취소 ë° ì‚­ì œ", + "selectAndResume": "ë™ì¼í•œ 파ì¼ì„ ì„ íƒí•˜ê³  업로드 재개", + "fileName": "파ì¼ëª…:", + "fileSize": "íŒŒì¼ í¬ê¸°:", + "sessionExpiredIn": "<0> 만료", + "chunkDescription": "({{total}}ê°œ ì²­í¬, ê° ì²­í¬ {{size}})", + "noChunks": "(ì²­í¬ ì—†ìŒ)", + "destination": "저장 위치:", + "storagePolicy": "저장 ì •ì±…:", + "uploadSession": "업로드 세션:", + "errorDetails": "오류 ì •ë³´:", + "uploadSessionCleaned": "업로드 ì„¸ì…˜ì´ ì§€ì›Œì¡ŒìŠµë‹ˆë‹¤", + "hideCompletedTooltip": "목ë¡ì—서 완료ëœ, 실패한, ì·¨ì†Œëœ ìž‘ì—…ì„ í‘œì‹œí•˜ì§€ 않ìŒ", + "hideCompleted": "ì™„ë£Œëœ ìž‘ì—… 숨기기", + "addTimeAscTooltip": "가장 먼저 ì¶”ê°€ëœ ìž‘ì—…ì´ ë§¨ ì•žì— ì •ë ¬", + "addTimeAsc": "가장 먼저 ì¶”ê°€ëœ ìˆœ", + "addTimeDescTooltip": "가장 ë‚˜ì¤‘ì— ì¶”ê°€ëœ ìž‘ì—…ì´ ë§¨ ì•žì— ì •ë ¬", + "addTimeDesc": "가장 ë‚˜ì¤‘ì— ì¶”ê°€ëœ ìˆœ", + "showInstantSpeedTooltip": "개별 작업 업로드 ì†ë„를 순간 ì†ë„로 표시", + "showInstantSpeed": "순간 ì†ë„", + "showAvgSpeedTooltip": "개별 작업 업로드 ì†ë„를 í‰ê·  ì†ë„로 표시", + "showAvgSpeed": "í‰ê·  ì†ë„", + "cleanAllSessionTooltip": "서버 ì¸¡ì˜ ëª¨ë“  미완료 업로드 세션 지우기", + "cleanAllSession": "모든 업로드 세션 지우기", + "cleanCompletedTooltip": "목ë¡ì—서 완료ëœ, 실패한, ì·¨ì†Œëœ ìž‘ì—… 지우기", + "cleanCompleted": "ì™„ë£Œëœ ìž‘ì—… 지우기", + "retryFailedTasks": "모든 실패한 작업 재시ë„", + "retryFailedTasksTooltip": "ëŒ€ê¸°ì—´ì˜ ëª¨ë“  실패한 작업 재시ë„", + "setConcurrentTooltip": "ë™ì‹œì— ì§„í–‰ë˜ëŠ” 작업 수 설정", + "setConcurrent": "병렬 수 설정", + "sizeExceedLimitError": "íŒŒì¼ í¬ê¸°ê°€ 저장 ì •ì±… ì œí•œì„ ì´ˆê³¼í–ˆìŠµë‹ˆë‹¤ (최대: {{max}})", + "suffixNotAllowedError": "저장 ì •ì±…ì—서 ì´ í™•ìž¥ìžì˜ íŒŒì¼ ì—…ë¡œë“œë¥¼ ì§€ì›í•˜ì§€ 않습니다", + "regexpNotAllowedError": "저장 ì •ì±…ì—서 ì´ ì´ë¦„ì˜ íŒŒì¼ ì—…ë¡œë“œë¥¼ ì§€ì›í•˜ì§€ 않습니다", + "suffixAllowed": "(ì§€ì›ë˜ëŠ” 확장ìž: {{supported}})", + "suffixDenied": "(ê¸ˆì§€ëœ í™•ìž¥ìž: {{denied}})", + "createUploadSessionError": "업로드 ì„¸ì…˜ì„ ìƒì„±í•  수 없습니다", + "deleteUploadSessionError": "업로드 ì„¸ì…˜ì„ ì‚­ì œí•  수 없습니다", + "requestError": "요청 실패: {{msg}} ({{url}})", + "chunkUploadError": "ì²­í¬ [{{index}}] 업로드 실패", + "conflictError": "ë™ì¼í•œ ì´ë¦„ì˜ íŒŒì¼ ì—…ë¡œë“œ ìž‘ì—…ì´ ì´ë¯¸ 처리 중입니다", + "chunkUploadErrorWithMsg": "ì²­í¬ ì—…ë¡œë“œ 실패: {{msg}}", + "chunkUploadErrorWithRetryAfter": "({{retryAfter}}ì´ˆ 후 재시ë„하세요)", + "emptyFileError": "OneDriveì— ë¹ˆ íŒŒì¼ ì—…ë¡œë“œëŠ” ì§€ì›ë˜ì§€ 않습니다. íŒŒì¼ ë§Œë“¤ê¸° ë²„íŠ¼ì„ í†µí•´ 빈 파ì¼ì„ 만드세요", + "finishUploadError": "íŒŒì¼ ì—…ë¡œë“œë¥¼ 완료할 수 없습니다", + "finishUploadErrorWithMsg": "íŒŒì¼ ì—…ë¡œë“œë¥¼ 완료할 수 없습니다: {{msg}}", + "ossFinishUploadError": "íŒŒì¼ ì—…ë¡œë“œë¥¼ 완료할 수 없습니다: {{msg}} ({{code}})", + "cosUploadFailed": "업로드 실패: {{msg}} ({{code}})", + "upyunUploadFailed": "업로드 실패: {{msg}}", + "parseResponseError": "ì‘ë‹µì„ íŒŒì‹±í•  수 없습니다: {{msg}} ({{content}})", + "concurrentTaskNumber": "ë™ì‹œ 업로드 작업 수", + "dropFileHere": "마우스를 놓아 업로드 시작" + }, + "share": { + "free": "무료", + "price": "가격", + "points": "{{num}} í¬ì¸íЏ", + "statistics": "통계", + "expireAt": "<0> 만료", + "expireAfterDownloads": "{{downloads}}회 다운로드 후 만료", + "somebodyShare": "{{name}}ì˜ ê³µìœ ", + "expiredLink": "ë§Œë£Œëœ ê³µìœ ", + "sharedBy": "<0>{{nick}}ê°€ {{num}}ê°œ 파ì¼ì„ 공유했습니다", + "files": "1ê°œ 파ì¼", + "files_other": "{{count}}ê°œ 파ì¼", + "statisticsViews": "{{views}}회 조회", + "statisticsDownloads": "{{downloads}}회 다운로드", + "views": "{{count}}회 조회", + "views_other": "{{count}}회 조회", + "downloads": "{{count}}회 다운로드", + "downloads_other": "{{count}}회 다운로드", + "privateShareTitle": "{{nick}}ì˜ ì•”í˜¸í™”ëœ ê³µìœ ", + "enterPassword": "공유 비밀번호", + "continue": "계ì†", + "shareCanceled": "공유 ë§í¬ê°€ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤", + "listLoadingError": "로드 실패", + "sharedFiles": "ë‚´ 공유", + "createdAtDesc": "최신", + "createdAtAsc": "가장 오래ëœ", + "noRecords": "공유 기ë¡ì´ 없습니다.", + "sourceNotFound": "[ì›ë³¸ ê°ì²´ê°€ 존재하지 않ìŒ]", + "expired": "만료ë¨", + "changeToPublic": "공개 공유로 변경", + "changeToPrivate": "비공개 공유로 변경", + "viewPassword": "비밀번호 보기", + "disablePreview": "미리보기 금지", + "enablePreview": "미리보기 허용", + "cancelShare": "공유 취소", + "sharePassword": "공유 비밀번호", + "readmeError": "README ë‚´ìš©ì„ ì½ì„ 수 없습니다: {{msg}}", + "enterKeywords": "검색 키워드를 입력하세요", + "searchResult": "검색 ê²°ê³¼", + "sharedAt": "<0>ì— ê³µìœ ë¨", + "pleaseLogin": "먼저 로그ì¸í•˜ì„¸ìš”", + "cannotShare": "ì´ íŒŒì¼ì€ 미리볼 수 없습니다", + "preview": "미리보기", + "incorrectPassword": "비밀번호가 올바르지 않습니다", + "shareNotExist": "공유가 존재하지 않거나 만료ë˜ì—ˆìŠµë‹ˆë‹¤", + "copyLinkToClipboard": "ë§í¬ë¥¼ í´ë¦½ë³´ë“œì— 복사" + }, + "download": { + "noFilesFound": "파ì¼ì„ ì°¾ì„ ìˆ˜ 없습니다", + "filterByName": "ì´ë¦„으로 í•„í„°", + "selectAll": "ì „ì²´ ì„ íƒ", + "reverseSelect": "ì„ íƒ ë°˜ì „", + "cancelTaskConfirm": "ì´ ìž‘ì—…ì„ ì·¨ì†Œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?", + "saveChanges": "변경사항 저장", + "failedToLoad": "로드 실패", + "active": "ì§„í–‰ 중", + "finished": "완료ë¨", + "activeEmpty": "다운로드 ì¤‘ì¸ ìž‘ì—…ì´ ì—†ìŠµë‹ˆë‹¤", + "finishedEmpty": "ì™„ë£Œëœ ìž‘ì—…ì´ ì—†ìŠµë‹ˆë‹¤", + "loadMore": "ë” ë¶ˆëŸ¬ì˜¤ê¸°", + "taskFileDeleted": "파ì¼ì´ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤", + "unknownTaskName": "[알 수 ì—†ìŒ]", + "taskCanceled": "ìž‘ì—…ì´ ì·¨ì†Œë˜ì—ˆìŠµë‹ˆë‹¤. ìƒíƒœëŠ” ë‚˜ì¤‘ì— ì—…ë°ì´íЏë©ë‹ˆë‹¤", + "operationSubmitted": "ìž‘ì—…ì´ ì„±ê³µí–ˆìŠµë‹ˆë‹¤. ìƒíƒœëŠ” ë‚˜ì¤‘ì— ì—…ë°ì´íЏë©ë‹ˆë‹¤", + "deleteThisFile": "ì´ íŒŒì¼ ì‚­ì œ", + "openDstFolder": "저장 í´ë” 열기", + "selectDownloadingFile": "다운로드할 íŒŒì¼ ì„ íƒ", + "cancelTask": "작업 취소", + "updatedAt": "ì—…ë°ì´íЏ:", + "uploaded": "업로드 í¬ê¸°", + "uploadSpeed": "업로드 ì†ë„", + "InfoHash": "InfoHash", + "seederCount": "시ë”:", + "seeding": "시딩 중:", + "downloadNode": "노드:", + "isSeeding": "예", + "notSeeding": "아니오", + "chunkSize": "ì²­í¬ í¬ê¸°:", + "chunkNumbers": "ì²­í¬ ìˆ˜", + "taskDeleted": "ì‚­ì œ 성공", + "transferFailed": "íŒŒì¼ ì „ì†¡ 실패", + "downloadFailed": "다운로드 오류: {{msg}}", + "canceledStatus": "취소ë¨", + "finishedStatus": "완료ë¨", + "pending": "완료ë¨, 전송 대기 중", + "transferring": "전송 중", + "deleteRecord": "ê¸°ë¡ ì‚­ì œ", + "createdAt": "ìƒì„±ì¼:", + "unknownSize": "알 수 없는 íŒŒì¼ í¬ê¸°" + }, + "setting": { + "notifyStoragePolicyChange": "다른 저장 ì •ì±…ì˜ í´ë”로 전환할 때 알림", + "notifyStoragePolicyChangeDes": "활성화하면 다른 저장 ì •ì±…ì´ ë°”ì¸ë”©ëœ í´ë”ì— ì§„ìž…í•  때 ì•Œë¦¼ì´ í‘œì‹œë©ë‹ˆë‹¤.", + "treeView": "트리 보기", + "autoExpandTreeView": "트리 보기 ìžë™ 펼치기", + "autoExpandTreeViewDes": "활성화하면 사ì´ë“œë°”ì˜ íŒŒì¼ íŠ¸ë¦¬ê°€ 현재 í´ë”를 ë”°ë¼ ìžë™ìœ¼ë¡œ 펼ì³ì§‘니다.", + "syncView": "보기 설정", + "syncViewDes": "ê° í´ë”ì˜ ë³´ê¸° ì„¤ì •ì„ ê¸°ì–µí•˜ê³  ì„œë²„ì— ë™ê¸°í™”í• ì§€ 여부", + "syncViewOn": "ì„œë²„ì— ë™ê¸°í™”", + "syncViewOff": "ë™ê¸°í™” 안 함", + "reason": "ì´ìœ ", + "change": "ë³€ë™", + "success": "성공", + "loginWithPasskey": "패스키 - {{name}}", + "loginWith": "ë¡œê·¸ì¸ ë°©ë²•", + "result": "ê²°ê³¼", + "device": "기기", + "ip": "IP", + "time": "시간", + "recentSignIn": "최근 ë¡œê·¸ì¸ í™œë™", + "noAuthenticator": "얼굴, 지문 ë˜ëŠ” USB 키로 ê³„ì •ì— ë¡œê·¸ì¸í•˜ë ¤ë©´ 패스키를 추가하세요", + "neverUsed": "사용한 ì  ì—†ìŒ", + "usedAt": "<0>ì— ë§ˆì§€ë§‰ 사용", + "passkeyName": "{os}ì˜ {browser}", + "passwordlessHint": "ì´ ê³„ì •ì€ ë¹„ë°€ë²ˆí˜¸ê°€ 없는 계정입니다", + "versionRetentionMax": "최대 버전 수, 0ì€ ë¬´ì œí•œì„ ì˜ë¯¸", + "versionRetentionEnabledExt": "í™œì„±í™”ëœ íŒŒì¼ í™•ìž¥ìž", + "versionRetentionEnabledExtDes": "엔터키를 눌러 추가, 비워ë‘ë©´ 모든 파ì¼ì— 대해 활성화", + "enableVersionRetention": "버전 ë³´ì¡´ 활성화", + "enableVersionRetentionDes": "활성화하면 ì¡°ê±´ì— ë§žëŠ” 파ì¼ì˜ ì´ë ¥ ë²„ì „ì´ ë³´ì¡´ë©ë‹ˆë‹¤", + "versionRetention": "버전 ë³´ì¡´", + "languageDes": "애플리케ì´ì…˜ 표시 언어 ë° ì„ í˜¸ ì´ë©”ì¼ ì–¸ì–´ 설정", + "timezoneDes": "표시 시간대 설정, ê¸°ë³¸ê°’ì€ ì‹œìŠ¤í…œ 시간대를 따름", + "unlinkConfirm": "계정 ì—°ê²°ì„ í•´ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?", + "notLinked": "ì—°ê²°ë˜ì§€ 않ìŒ", + "linkedAt": "<0>ì— ì—°ê²°ë¨", + "accountLinking": "계정 ì—°ê²°", + "nickNameDes": "공개 표시용 ì´ë¦„으로 실명ì´ë‚˜ ë‹‰ë„¤ìž„ì„ ì‚¬ìš©í•  수 있습니다", + "cropAvatar": "아바타 ìžë¥´ê¸°", + "finance": "재무", + "preference": "환경설정", + "accountCreatedAt": "<0>ì— ìƒì„±ë¨", + "shoeQr": "표시", + "deviceNothing": "현재 ì‚¬ìš©ìž ê·¸ë£¹ì€ WebDAV를 ì§€ì›í•˜ì§€ 않습니다", + "connectionInfo": "ì—°ê²° ì •ë³´", + "proxyTooltip": "서버 측ì—서 모든 íŒŒì¼ ë‹¤ìš´ë¡œë“œ ìš”ì²­ì„ í”„ë¡ì‹œã€‚", + "readonlyTooltip": "사용ìžëŠ” ì´ ê³„ì •ì„ í†µí•´ì„œë§Œ 파ì¼ì„ ì½ì„ 수 있습니다。", + "blockSysFilesUpload": "시스템 íŒŒì¼ ì—…ë¡œë“œ 차단", + "blockSysFilesUploadTooltip": "활성화하면 <0>.로 시작하는 파ì¼ì˜ 업로드가 차단ë©ë‹ˆë‹¤ã€‚", + "rootFolderIn": "<0> ì„ íƒ", + "createWebDavAccount": "WebDAV 계정 ìƒì„±", + "editWebDavAccount": "{{name}} 편집", + "seeding": "시딩 중", + "awaitSeeding": "시딩 대기", + "awaitSeedingDes": "다운로드 작업 시딩 완료 대기", + "downloadTransferDes": "파ì¼ì„ 목ì ì§€ë¡œ 전송", + "downloadDes": "ì§€ì •ëœ íŒŒì¼ ë‹¤ìš´ë¡œë“œ", + "retryErrorHistory": "ì´ë ¥ ìž¬ì‹œë„ ì˜¤ë¥˜", + "retryCount": "ìž¬ì‹œë„ íšŸìˆ˜", + "resumeAt": "ë‹¤ìŒ ìž¬ê°œ 실행", + "executeDuration": "실행 순시간", + "input": "ìž…ë ¥", + "output": "출력", + "suspended": " (ì¼ì‹œì¤‘단)", + "updatedAt": "ì—…ë°ì´íЏì¼", + "taskDetails": "작업 ìƒì„¸ì •ë³´", + "partialSuccessWarning": "{{num}}ê°œ ê°ì²´ 처리 실패, 건너뛰었습니다.", + "sendTask": "작업 전송", + "sendTaskDes": "ìž‘ì—…ì„ ì²˜ë¦¬ 노드로 전송", + "downloaded": "다운로드ë¨", + "importingFiles": "íŒŒì¼ ê°€ì ¸ì˜¤ê¸°", + "importingFilesDes": "파ì¼ì„ 검색하여 ì§€ì •ëœ í´ë”로 가져오기", + "importedFiles": "가져온 파ì¼", + "indexedFiles": "ì¸ë±ì‹±ëœ 파ì¼", + "extractedFiles": "ì••ì¶• í•´ì œëœ íŒŒì¼ ìˆ˜", + "extractedFilesSize": "ì••ì¶• í•´ì œëœ íŒŒì¼ í¬ê¸°", + "extractingFiles": "íŒŒì¼ ì••ì¶• í•´ì œ", + "extractingFilesDes": "모든 파ì¼ì„ ì§€ì •ëœ í´ë”로 ì••ì¶• í•´ì œ", + "downloadingZip": "ì••ì¶• íŒŒì¼ ê°€ì ¸ì˜¤ê¸°", + "downloadingZipDes": "ì••ì¶• 파ì¼ì„ 임시 작업 공간으로 다운로드", + "progressNotAvailable": "진행률 정보를 ì•„ì§ ì‚¬ìš©í•  수 없습니다", + "uploadedSize": "ì „ì†¡ëœ íŒŒì¼", + "archivedFiles": "ì²˜ë¦¬ëœ íŒŒì¼ ìˆ˜", + "transferredFiles": "ì „ì†¡ëœ íŒŒì¼ ìˆ˜", + "archivedFilesSize": "ì²˜ë¦¬ëœ íŒŒì¼ í¬ê¸°", + "createArchiveFinishing": "새 íŒŒì¼ ë³€ê²½ì‚¬í•­ 제출", + "indexForArchiveDes": "모든 ì••ì¶•í•  íŒŒì¼ ê²€ìƒ‰", + "prepare": "준비", + "preparingWorkspaceDes": "임시 작업 공간 준비", + "compressFiles": "ì••ì¶• íŒŒì¼ ìƒì„±", + "compressFilesDes": "파ì¼ì„ 임시 작업 공간으로 ì••ì¶•", + "uploadArchiveFileDes": "ì••ì¶• 파ì¼ì„ 목ì ì§€ë¡œ 전송", + "uploadWorker": "업로드 스레드 #{{num}}", + "relocatedEntities": "ì „ì†¡ëœ ì—”í‹°í‹°", + "queueToStart": "시작 대기", + "indexingFiles": "íŒŒì¼ ê²€ìƒ‰", + "indexingFilesDes": "모든 전송할 파ì¼ì„ 검색하고 잠금", + "transferring": "전송", + "transferringRelocateDes": "ë°ì´í„°ë¥¼ 새 저장 정책으로 전송", + "committingChanges": "변경사항 제출", + "relocateFinishing": "íŒŒì¼ ì—”í‹°í‹°ë¥¼ 새 저장 정책으로 ì—…ë°ì´íЏ", + "autoRefresh": "ìžë™ 새로고침", + "avatarUpdated": "아바타가 ì—…ë°ì´íЏë˜ì—ˆìŠµë‹ˆë‹¤. 최신 아바타 í‘œì‹œì— ì§€ì—°ì´ ìžˆì„ ìˆ˜ 있습니다", + "nickChanged": "ë‹‰ë„¤ìž„ì´ ë³€ê²½ë˜ì—ˆìŠµë‹ˆë‹¤. 새로고침 후 ì ìš©ë©ë‹ˆë‹¤", + "settingSaved": "ì„¤ì •ì´ ì €ìž¥ë˜ì—ˆìŠµë‹ˆë‹¤", + "themeColorChanged": "테마 색ìƒì´ 변경ë˜ì—ˆìŠµë‹ˆë‹¤", + "profile": "ê°œì¸ ì •ë³´", + "avatar": "아바타", + "uid": "UID", + "nickname": "닉네임", + "group": "ì‚¬ìš©ìž ê·¸ë£¹", + "regTime": "가입 시간", + "security": "비밀번호 ë° ë³´ì•ˆ", + "profilePage": "ê°œì¸ í™ˆíŽ˜ì´ì§€", + "publicShareOnly": "비밀번호 없는 공유 ë§í¬ë§Œ 표시", + "publicShareOnlyDes": "ê°œì¸ í™ˆíŽ˜ì´ì§€ì—서 비밀번호가 설정ë˜ì§€ ì•Šì€ ê³µìœ  ë§í¬ë§Œ 표시합니다.", + "allShare": "모든 공유", + "allShareDes": "ê°œì¸ í™ˆíŽ˜ì´ì§€ì—서 모든 공유 ë§í¬(비밀번호가 있는 공유 í¬í•¨)를 표시합니다. 비밀번호가 있는 ê³µìœ ì˜ ê²½ìš° 사용ìžê°€ 비밀번호를 입력해야 접근할 수 있습니다.", + "hideShare": "모든 공유 ë§í¬ 숨기기", + "hideShareDes": "ê°œì¸ í™ˆíŽ˜ì´ì§€ì—서 모든 공유 ë§í¬ë¥¼ 숨ê¹ë‹ˆë‹¤.", + "userHideShare": "사용ìžê°€ 공유 ë§í¬ 목ë¡ì„ 숨겼습니다", + "accountPassword": "ë¡œê·¸ì¸ ë¹„ë°€ë²ˆí˜¸", + "2fa": "2단계 ì¸ì¦", + "enabled": "활성화ë¨", + "disabled": "비활성화ë¨", + "appearance": "ê°œì¸í™”", + "themeColor": "테마 색ìƒ", + "darkMode": "ë‹¤í¬ ëª¨ë“œ", + "syncWithSystem": "시스템", + "fileList": "íŒŒì¼ ëª©ë¡", + "timeZone": "시간대", + "webdavServer": "ì—°ê²° 주소", + "userName": "사용ìžëª…", + "manageAccount": "계정 관리", + "uploadImage": "파ì¼ì—서 업로드", + "useGravatar": "Gravatar 아바타 사용 ", + "changeNick": "닉네임 수정", + "originalPassword": "기존 비밀번호", + "enable2FA": "2단계 ì¸ì¦ 활성화", + "disable2FA": "2단계 ì¸ì¦ 비활성화", + "2faDescription": "ìž„ì˜ì˜ 2단계 ì¸ì¦ 앱 ë˜ëŠ” 2단계 ì¸ì¦ì„ ì§€ì›í•˜ëŠ” 비밀번호 관리 소프트웨어를 사용하여 QR 코드를 스캔해 ì´ ì‚¬ì´íŠ¸ë¥¼ 추가하세요. 스캔 완료 후 2단계 ì¸ì¦ 앱ì—서 제공하는 6ìžë¦¬ ì¸ì¦ 코드를 입력하여 2단계 ì¸ì¦ì„ 활성화하세요.", + "inputCurrent2FACode": "현재 2단계 ì¸ì¦ 앱ì—서 제공하는 6ìžë¦¬ ì¸ì¦ 코드를 입력하세요:", + "timeZoneCode": "IANA 시간대 ì´ë¦„ ì‹ë³„ìž", + "authenticatorRemoved": "ìžê²© ì¦ëª…ì´ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤", + "authenticatorAdded": "ì¸ì¦ê¸°ê°€ 추가ë˜ì—ˆìŠµë‹ˆë‹¤", + "browserNotSupported": "현재 브ë¼ìš°ì € ë˜ëŠ” 환경ì—서 ì§€ì›ë˜ì§€ 않습니다", + "removedAuthenticator": "ìžê²© ì¦ëª… ì‚­ì œ", + "removedAuthenticatorConfirm": "ì´ ìžê²© ì¦ëª…ì„ ì·¨ì†Œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?", + "addNewAuthenticator": "새 ìžê²© ì¦ëª… 추가", + "hardwareAuthenticator": "패스키", + "copied": "í´ë¦½ë³´ë“œì— 복사ë˜ì—ˆìŠµë‹ˆë‹¤", + "pleaseManuallyCopy": "현재 브ë¼ìš°ì €ì—서 ì§€ì›í•˜ì§€ 않습니다. 수ë™ìœ¼ë¡œ 복사하세요", + "webdavAccounts": "WebDAV 계정 관리", + "webdavHint": "WebDAV 주소: {{url}}; ë¡œê·¸ì¸ ì‚¬ìš©ìžëª… 통ì¼: {{name}}; 비밀번호는 ìƒì„±ëœ ê³„ì •ì˜ ë¹„ë°€ë²ˆí˜¸ìž…ë‹ˆë‹¤.", + "annotation": "비고명", + "rootFolder": "ìƒëŒ€ 루트 í´ë”", + "createdAt": "ìƒì„±ì¼", + "action": "작업", + "readonlyOn": "ì½ê¸° ì „ìš©", + "readonlyOff": "ì½ê¸°/쓰기", + "proxy": "ì—­ë°©í–¥ 프ë¡ì‹œ", + "none": "ì—†ìŒ", + "proxied": "프ë¡ì‹œë¨", + "delete": "ì‚­ì œ", + "listEmpty": "기ë¡ì´ 없습니다", + "createNewAccount": "새 계정 ìƒì„±", + "taskType": "작업 유형", + "taskStatus": "ìƒíƒœ", + "taskProgress": "작업 진행률", + "errorDetails": "오류 ì •ë³´", + "queueing": "대기 중", + "processing": "처리 중", + "failed": "실패", + "canceled": "취소", + "finished": "완료ë¨", + "fileTransfer": "íŒŒì¼ ì¤‘ê³„", + "fileRecycle": "íŒŒì¼ ìž¬í™œìš©", + "importFiles": "외부 í´ë” 가져오기", + "transferProgress": "{{num}}ê°œ íŒŒì¼ ì™„ë£Œ", + "waiting": "대기 중", + "compressing": "ì••ì¶• 중", + "decompressing": "ì••ì¶• í•´ì œ 중", + "downloading": "다운로드 중", + "indexing": "ì¸ë±ì‹± 중", + "listing": "삽입 중", + "allShares": "ì „ì²´ 공유", + "trendingShares": "ì¸ê¸° 공유", + "totalShares": "공유 ì´ìˆ˜", + "fileName": "파ì¼ëª…", + "shareDate": "공유ì¼", + "downloadNumber": "다운로드 횟수", + "viewNumber": "조회 횟수", + "language": "언어", + "iOSApp": "iOS/iPadOS í´ë¼ì´ì–¸íЏ", + "connectByiOS": "iOS/iPadOS 기기를 통해 <0>{{title}}ì— ì—°ê²°", + "downloadOurApp": "ì•±ì„ ë‹¤ìš´ë¡œë“œí•˜ê³  설치하세요:", + "fillInEndpoint": "앱으로 아래 QR 코드를 스캔하세요 (다른 스캔 ì•±ì€ ë¬´íš¨):", + "loginApp": "ë°”ì¸ë”© 완료, í´ë¼ì´ì–¸íЏ ì‚¬ìš©ì„ ì‹œìž‘í•  수 있습니다. QR 코드 ë°”ì¸ë”©ì— 문제가 있는 경우 수ë™ìœ¼ë¡œ 사용ìžëª…ê³¼ 비밀번호를 입력하여 로그ì¸í•  ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤.", + "relocateFileTo": "<0>{{more}}ì˜ ì €ìž¥ ì •ì±…ì„ {{policy}}로 전송", + "extractFileTo": "<0>{{more}}ì„ <1>로 ì••ì¶• í•´ì œ", + "createArchiveTo": "<0>{{more}}ì„ <1>로 패키징", + "importFileTo": "{{policy}}ì˜ íŒŒì¼ì„ <0>로 가져오기" + }, + "vas": { + "points": "í¬ì¸íЏ", + "paid": "ê²°ì œë¨", + "fulfillFailedStatus": "ì´í–‰ 실패", + "unpaid": "미결제", + "amount": "금액", + "tradeNo": "거래번호", + "payments": "주문", + "creditReasonShareGain": "공유 구매ë¨", + "creditReasonSharePay": "ìƒì  소비", + "creditReasonRecharge": "ì¶©ì „", + "creditChanges": "í¬ì¸íЏ ë³€ë™", + "payXPoints": "<0> ê²°ì œ", + "pointsPayAvailable": "ì´ ìƒí’ˆì€ í¬ì¸íЏ 결제를 ì§€ì›í•©ë‹ˆë‹¤. ë‹¤ìŒ ë‹¨ê³„ì—서 <0>를 사용하여 êµí™˜í•  수 있습니다.", + "payAmount": "{{price}} ê²°ì œ", + "purchaseSomething": "{{name}} 구매", + "redeem": "êµí™˜", + "shop": "ìƒì ", + "resumeTicket": "복구 티켓", + "resumeTicketDes": "ê²°ì œ 후 ì „ì†¡ëœ ì£¼ë¬¸ í™•ì¸ ì´ë©”ì¼ì—서 ì°¾ì„ ìˆ˜ 있습니다", + "restorePurchase": "구매 복구", + "restorePurchaseDes": "주문 í™•ì¸ ì´ë©”ì¼ì˜ \"복구 티켓\"ì„ í†µí•´ 구매 복구", + "paymentSuccess": "ê²°ì œ 성공", + "fulfillFailed": "주문 ì´í–‰ 실패, 사ì´íЏ 관리ìžì—게 ì—°ë½í•˜ì„¸ìš”.", + "paidButton": "ê²°ì œ 완료", + "payInNewWindow": "새 ì°½ì—서 결제를 완료하세요. ê²°ì œ 완료 전까지 ì´ íŽ˜ì´ì§€ë¥¼ ë‹«ì§€ 마세요. 새 ì°½ì´ ë‚˜íƒ€ë‚˜ì§€ 않으면 <0>여기를 í´ë¦­í•˜ì„¸ìš”.", + "paymentFailedTitle": "ê²°ì œ 처리 실패", + "paymentEmailHelper": "로그ì¸í•˜ì§€ 않아 구매 ì¦ëª…서 ì „ì†¡ì„ ìœ„í•´ ì´ë©”ì¼ì´ 필요합니다", + "payEquivalentCash": "등가 금액 ê²°ì œ: {{num}}", + "payWithCash": "현금으로 ê²°ì œ", + "recharge": "ì¶©ì „", + "pointsBalance": "í¬ì¸íЏ 잔액: {{num}}", + "loginRequired": "ë¡œê·¸ì¸ í•„ìš”", + "payWithPoints": "í¬ì¸íŠ¸ë¡œ ê²°ì œ", + "purchaseLogin": "구매를 계ì†í•˜ë ¤ë©´ 먼저 <0>로그ì¸í•˜ì„¸ìš”", + "noAvailableSharePurchaseMethod": "사용 가능한 구매 ë°©ë²•ì´ ì—†ìŠµë‹ˆë‹¤", + "purchaseShareLink": "공유 ë§í¬ 구매", + "loginWith": "{{name}}으로 로그ì¸", + "sso": "SSO", + "qq": "QQ", + "quota": "용량 할당량", + "exceedQuota": "사용 ìš©ëŸ‰ì´ ìš©ëŸ‰ í• ë‹¹ëŸ‰ì„ ì´ˆê³¼í–ˆìŠµë‹ˆë‹¤. 가능한 한 빨리 불필요한 파ì¼ì„ 삭제하거나 ìš©ëŸ‰ì„ êµ¬ë§¤í•˜ì„¸ìš”", + "extendStorage": "확장", + "folderPolicySwitched": "현재 í´ë”ì˜ ì €ìž¥ ì •ì±…ì´ \"{{name}}\"으로 전환ë˜ì—ˆìŠµë‹ˆë‹¤", + "switchFolderPolicy": "í´ë” 저장 ì •ì±… 전환", + "setPolicyForFolder": "현재 í´ë”ì˜ ì €ìž¥ ì •ì±… 설정: ", + "manageMount": "ë°”ì¸ë”© 관리", + "saveToMyFiles": "ë‚´ 파ì¼ì— 저장", + "report": "남용 ì‹ ê³ ", + "reportTarget": "ì‹ ê³  대ìƒ", + "reportReason": "ì´ìœ ", + "reportReasonOptions": ["침해", "유해 콘í…츠", "스팸 ì •ë³´", "기타"], + "reportDescription": "추가 설명", + "reportAbuseSuccess": "ì‹ ê³ ê°€ 제출ë˜ì—ˆìŠµë‹ˆë‹¤", + "migrateStoragePolicy": "저장 ì •ì±… 전송", + "fileSaved": "파ì¼ì´ 저장ë˜ì—ˆìŠµë‹ˆë‹¤", + "sharePurchaseTitle": "ì´ ê³µìœ  ë§í¬ëŠ” <0> ê²°ì œ 후 접근할 수 있습니다", + "payToDownload": "유료 다운로드", + "creditToBePaid": "결제할 í¬ì¸íЏ", + "creditGainPredict": "구매당 ì˜ˆìƒ {{num}} í¬ì¸íЏ ì ë¦½", + "creditPrice": " ({{num}} í¬ì¸íЏ)", + "creditFree": " (무료 í¬ì¸íЏ)", + "cancelSubscription": "해약 성공, ë³€ê²½ì‚¬í•­ì€ ëª‡ ë¶„ì—서 몇 시간 í›„ì— ì ìš©ë©ë‹ˆë‹¤", + "qqUnlinked": "QQ ê³„ì •ê³¼ì˜ ì—°ê²°ì´ í•´ì œë˜ì—ˆìŠµë‹ˆë‹¤", + "groupExpire": "(<0> 만료)", + "manuallyCancelSubscription": "현재 ì‚¬ìš©ìž ê·¸ë£¹ ìˆ˜ë™ í•´ì•½", + "qqAccount": "QQ 계정", + "connect": "ë°”ì¸ë”©", + "unlink": "ë°”ì¸ë”© í•´ì œ", + "credits": "í¬ì¸íЏ", + "cancelSubscriptionTitle": "ì‚¬ìš©ìž ê·¸ë£¹ 해약", + "cancelSubscriptionWarning": "초기 ì‚¬ìš©ìž ê·¸ë£¹ìœ¼ë¡œ ëŒì•„가며 지불한 ê¸ˆì•¡ì€ í™˜ë¶ˆë˜ì§€ 않습니다. 계ì†í•˜ì‹œê² ìŠµë‹ˆê¹Œ?", + "mountPolicy": "저장 ì •ì±… ë°”ì¸ë”©", + "mountDescription": "í´ë”ì— ì €ìž¥ ì •ì±…ì„ ë°”ì¸ë”©í•œ 후 ì´ í´ë”나 ì´ í´ë”ì˜ í•˜ìœ„ í´ë”ì— ì—…ë¡œë“œë˜ëŠ” 새 파ì¼ì€ ë°”ì¸ë”©ëœ 저장 ì •ì±…ì„ ì‚¬ìš©í•˜ì—¬ 저장ë©ë‹ˆë‹¤. ì´ í´ë”로 복사, ì´ë™í•˜ëŠ” ê²ƒì€ ë°”ì¸ë”©ëœ 저장 ì •ì±…ì„ ì ìš©í•˜ì§€ 않으며, 여러 ìƒìœ„ í´ë”ê°€ 저장 ì •ì±…ì„ ì§€ì •í•  때는 가장 가까운 ìƒìœ„ í´ë”ì˜ ì €ìž¥ ì •ì±…ì„ ì„ íƒí•©ë‹ˆë‹¤.", + "mountNewFolder": "새 í´ë” ë°”ì¸ë”©", + "nsfw": "ì„±ì¸ ì •ë³´", + "malware": "ë°”ì´ëŸ¬ìФ í¬í•¨", + "copyright": "침해", + "inappropriateStatements": "ë¶€ì ì ˆí•œ 발언", + "other": "기타", + "groupBaseQuota": "ì‚¬ìš©ìž ê·¸ë£¹ 기본 용량 - {{size}}", + "validPackQuota": "확장 용량 - {{size}}", + "used": "ì‚¬ìš©ë¨ - {{size}}", + "total": "ì´ ìš©ëŸ‰ - {{size}}", + "validStorage": "유효 확장", + "buyStorage": "용량 구매", + "useGiftCode": "활성화 코드로 êµí™˜", + "packName": "용량 팩 ì´ë¦„", + "activationDate": "활성화 ë‚ ì§œ", + "validDuration": "유효 기간", + "expiredAt": "만료ì¼", + "days": "{{num}}ì¼", + "pleaseInputGiftCode": "êµí™˜ 코드를 입력하세요", + "pleaseSelectAStoragePack": "먼저 용량 íŒ©ì„ ì„ íƒí•˜ì„¸ìš”", + "paymentMethod": "ê²°ì œ 방법", + "noAvailableMethod": "사용 가능한 ê²°ì œ 방법 ì—†ìŒ", + "alipay": "ì•Œë¦¬íŽ˜ì´ ìŠ¤ìº”", + "wechatPay": "위챗 스캔", + "payByCredits": "í¬ì¸íЏ ê²°ì œ", + "purchaseDuration": "구매 기간", + "creditsNum": "ì¶©ì „ í¬ì¸íЏ 수량", + "store": "ìƒì ", + "storageExpansion": "저장 확장", + "membership": "회ì›", + "buyCredits": "í¬ì¸íЏ ì¶©ì „", + "subtotal": "현재 비용:", + "creditsTotalNum": "{{num}} í¬ì¸íЏ", + "purchaseNow": "지금 구매", + "recommended": "추천", + "enterGiftCode": "êµí™˜ 코드 ìž…ë ¥", + "qrcodeAlipay": "알리페ì´ë¡œ 아래 QR 코드를 스캔하여 결제를 완료하세요. ê²°ì œ 완료 후 ì´ íŽ˜ì´ì§€ê°€ ìžë™ìœ¼ë¡œ 새로고침ë©ë‹ˆë‹¤.", + "qrcodeWechat": "위챗으로 아래 QR 코드를 스캔하여 결제를 완료하세요. ê²°ì œ 완료 후 ì´ íŽ˜ì´ì§€ê°€ ìžë™ìœ¼ë¡œ 새로고침ë©ë‹ˆë‹¤.", + "qrcodeCustom": "아래 QR 코드를 스캔하여 결제를 완료하거나 <0>ê²°ì œ ë§í¬ë¥¼ ì§ì ‘ 열어주세요. ê²°ì œ 완료 후 ì´ íŽ˜ì´ì§€ê°€ ìžë™ìœ¼ë¡œ 새로고침ë©ë‹ˆë‹¤.", + "paymentCompleted": "ê²°ì œ 완료", + "productDelivered": "구매하신 ìƒí’ˆì´ 지급ë˜ì—ˆìŠµë‹ˆë‹¤.", + "confirmRedeem": "êµí™˜ 확ì¸", + "productType": "ìƒí’ˆ", + "qyt": "수량:", + "duration": "기간:", + "subscribe": "ì‚¬ìš©ìž ê·¸ë£¹ 구매", + "selected": "ì„ íƒë¨:", + "paymentQrcode": "ê²°ì œ QR 코드", + "validDurationDays": "{{num}}ì¼", + "reportSuccessful": "ì‹ ê³  성공", + "additionalDescription": "추가 설명", + "announcement": "공지", + "dontShowAgain": "다시 표시하지 않ìŒ", + "openPaymentLink": "ê²°ì œ ë§í¬ ì§ì ‘ 열기", + "creditReasonAdjust": "ìˆ˜ë™ ì¡°ì •" + } +} diff --git a/public/locales/ko-KR/common.json b/public/locales/ko-KR/common.json new file mode 100755 index 0000000..98a1c39 --- /dev/null +++ b/public/locales/ko-KR/common.json @@ -0,0 +1,120 @@ +{ + "pageNotFound": "페ì´ì§€ë¥¼ ì°¾ì„ ìˆ˜ 없습니다", + "unknownError": "알 수 없는 오류", + "errLoadingSiteConfig": "사ì´íЏ êµ¬ì„±ì„ ë¡œë“œí•  수 없습니다:", + "newVersionRefresh": "현재 페ì´ì§€ì— 새 ë²„ì „ì´ ìžˆìŠµë‹ˆë‹¤.", + "update": "ì—…ë°ì´íЏ", + "errorDetails": "ìƒì„¸ì •ë³´", + "renderError": "페ì´ì§€ ë Œë”ë§ì— 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. ì´ íŽ˜ì´ì§€ë¥¼ 새로고침해 보세요.", + "ok": "확ì¸", + "cancel": "취소", + "select": "ì„ íƒ", + "copyToClipboard": "복사", + "close": "닫기", + "dismiss": "닫기", + "intlDateTime": "{{val, datetime}}", + "seconds": "sì´ˆ", + "minutes": "më¶„ sì´ˆ", + "hours": "H시간 më¶„", + "days": "{{d}}ì¼", + "timeAgoLocaleCode": "ko_KR", + "forEditorLocaleCode": "ko-KR", + "artPlayerLocaleCode": "ko", + "requestID": "요청 ID: {{id}}", + "object": "ê°ì²´", + "error": "오류", + "areYouSure": "확ì¸", + "incorrectSizeInput": "í¬ê¸° ì œí•œì— ë§žì§€ 않습니다", + "of": "ì´", + "rowsPerPage": "페ì´ì§€ë‹¹ í–‰ 수", + "custom": "ì‚¬ìš©ìž ì •ì˜", + "enter": "ìž…ë ¥", + "captcha": { + "cap": { + "human": "저는 사람입니다", + "verifying": "í™•ì¸ ì¤‘â€¦", + "verified": "ì¸ì¦ì„ 통과했습니다" + } + }, + "errors": { + "401": "먼저 로그ì¸í•˜ì„¸ìš”", + "403": "ì´ ìž‘ì—…ì„ ìˆ˜í–‰í•  ê¶Œí•œì´ ì—†ìŠµë‹ˆë‹¤", + "404": "리소스가 존재하지 않습니다", + "409": "ì¶©ëŒì´ ë°œìƒí–ˆìŠµë‹ˆë‹¤ ({{message}})", + "40001": "ìž…ë ¥ 매개변수가 잘못ë˜ì—ˆìŠµë‹ˆë‹¤ ({{message}})", + "40002": "업로드 실패", + "40003": "í´ë” ìƒì„± 실패", + "40004": "ë™ì¼í•œ ì´ë¦„ì˜ ê°ì²´ê°€ ì´ë¯¸ 존재합니다", + "40005": "ì„œëª…ì´ ë§Œë£Œë˜ì—ˆìŠµë‹ˆë‹¤", + "40006": "ì§€ì›ë˜ì§€ 않는 저장 ì •ì±… 유형", + "40007": "현재 ì‚¬ìš©ìž ê·¸ë£¹ì€ ì´ ìž‘ì—…ì„ ìˆ˜í–‰í•  수 없습니다", + "40011": "업로드 ì„¸ì…˜ì´ ì¡´ìž¬í•˜ì§€ 않거나 만료ë˜ì—ˆìŠµë‹ˆë‹¤", + "40012": "ì²­í¬ ì‹œí€€ìŠ¤ 번호가 잘못ë˜ì—ˆìŠµë‹ˆë‹¤ ({{message}})", + "40013": "본문 길ì´ê°€ 잘못ë˜ì—ˆìŠµë‹ˆë‹¤ ({{message}})", + "40014": "ì¼ê´„ 외부 ë§í¬ 가져오기 ì œí•œì„ ì´ˆê³¼í–ˆìŠµë‹ˆë‹¤", + "40015": "최대 오프ë¼ì¸ 다운로드 작업 수 ì œí•œì„ ì´ˆê³¼í–ˆìŠµë‹ˆë‹¤", + "40016": "경로가 존재하지 않습니다", + "40017": "ì´ ê³„ì •ì´ ì°¨ë‹¨ë˜ì—ˆìŠµë‹ˆë‹¤", + "40018": "ì´ ê³„ì •ì´ í™œì„±í™”ë˜ì§€ 않았습니다", + "40019": "ì´ ê¸°ëŠ¥ì´ í™œì„±í™”ë˜ì§€ 않았습니다", + "40020": "ìžê²© ì¦ëª…ì´ ìž˜ëª»ë˜ì—ˆê±°ë‚˜ 만료ë˜ì—ˆìŠµë‹ˆë‹¤", + "40021": "사용ìžê°€ 존재하지 않습니다", + "40022": "ì¸ì¦ 코드가 올바르지 않습니다", + "40023": "ë¡œê·¸ì¸ ì„¸ì…˜ì´ ì¡´ìž¬í•˜ì§€ 않습니다", + "40024": "WebAuthnì„ ì´ˆê¸°í™”í•  수 없습니다", + "40025": "ì¸ì¦ 실패", + "40026": "보안문ìžê°€ 잘못ë˜ì—ˆìŠµë‹ˆë‹¤", + "40027": "ì¸ì¦ 실패, 웹페ì´ì§€ë¥¼ 새로고침하고 다시 시ë„하세요", + "40028": "ì´ë©”ì¼ ì „ì†¡ 실패", + "40029": "ìž˜ëª»ëœ ë§í¬ìž…니다", + "40030": "ì´ ë§í¬ê°€ 만료ë˜ì—ˆìŠµë‹ˆë‹¤", + "40032": "ì´ ì´ë©”ì¼ì´ ì´ë¯¸ 사용 중입니다", + "40033": "사용ìžê°€ 활성화ë˜ì§€ 않았습니다. 활성화 ì´ë©”ì¼ì„ 다시 보냈습니다", + "40034": "ì´ ì‚¬ìš©ìžëŠ” 활성화할 수 없습니다", + "40035": "저장 ì •ì±…ì´ ì¡´ìž¬í•˜ì§€ 않습니다", + "40039": "ì‚¬ìš©ìž ê·¸ë£¹ì´ ì¡´ìž¬í•˜ì§€ 않습니다", + "40044": "파ì¼ì´ 존재하지 않습니다", + "40045": "í´ë” 하위 ê°ì²´ë¥¼ 나열할 수 없습니다", + "40047": "íŒŒì¼ ì‹œìŠ¤í…œì„ ì´ˆê¸°í™”í•  수 없습니다", + "40048": "작업 ìƒì„± 오류", + "40049": "íŒŒì¼ í¬ê¸°ê°€ ì œí•œì„ ì´ˆê³¼í–ˆìŠµë‹ˆë‹¤", + "40050": "íŒŒì¼ ìœ í˜•ì´ í—ˆìš©ë˜ì§€ 않습니다", + "40051": "용량 ê³µê°„ì´ ë¶€ì¡±í•©ë‹ˆë‹¤", + "40052": "ì´ íŒŒì¼ëª… ë˜ëŠ” 확장ìžëŠ” 허용ë˜ì§€ 않습니다", + "40053": "루트 í´ë”ì—서 ì´ ìž‘ì—…ì„ ìˆ˜í–‰í•˜ëŠ” ê²ƒì€ ì§€ì›ë˜ì§€ 않습니다", + "40054": "현재 í´ë”ì— ë™ì¼í•œ ì´ë¦„ì˜ íŒŒì¼ì´ 업로드 중입니다. 업로드 ì„¸ì…˜ì„ ì§€ì›Œë³´ì„¸ìš”", + "40055": "íŒŒì¼ ì •ë³´ê°€ ì¼ì¹˜í•˜ì§€ 않습니다", + "40056": "ì§€ì›ë˜ì§€ 않는 ì••ì¶• íŒŒì¼ í˜•ì‹ìž…니다", + "40057": "사용 가능한 저장 ì •ì±…ì´ ë³€ê²½ë˜ì—ˆìŠµë‹ˆë‹¤. íŒŒì¼ ëª©ë¡ì„ 새로고침하고 ì´ ìž‘ì—…ì„ ë‹¤ì‹œ 추가하세요", + "40058": "공유가 존재하지 않거나 만료ë˜ì—ˆìŠµë‹ˆë‹¤", + "40069": "비밀번호가 올바르지 않습니다", + "40070": "ì´ ê³µìœ ëŠ” 미리볼 수 없습니다", + "40071": "ì„œëª…ì´ ìž˜ëª»ë˜ì—ˆìŠµë‹ˆë‹¤", + "40073": "파ì¼ì´ ì ìœ ë˜ì—ˆìŠµë‹ˆë‹¤", + "40074": "ì„ íƒí•œ íŒŒì¼ ìˆ˜ê°€ ì œí•œì„ ì´ˆê³¼í–ˆìŠµë‹ˆë‹¤", + "40079": "최대 íƒìƒ‰ íŒŒì¼ ìˆ˜ ì œí•œì„ ì´ˆê³¼í–ˆìŠµë‹ˆë‹¤. 작업 범위를 줄여주세요", + "40081": "ìž‘ì—…ì´ ì™„ì „ížˆ 성공하지 않았습니다", + "40082": "íŒŒì¼ ì†Œìœ ìžë§Œ ì´ ìž‘ì—…ì„ ìˆ˜í–‰í•  수 있습니다", + "40080": "ì‚¬ìš©ìž ì´ë©”ì¼ ë˜ëŠ” 비밀번호가 잘못ë˜ì—ˆìŠµë‹ˆë‹¤", + "50001": "ë°ì´í„°ë² ì´ìФ 작업 실패 ({{message}})", + "50002": "URL ë˜ëŠ” 요청 서명 실패 ({{message}})", + "50004": "I/O 작업 실패 ({{message}})", + "50005": "ë‚´ë¶€ 오류 ({{message}})", + "50010": "ëŒ€ìƒ ë…¸ë“œë¥¼ 사용할 수 없습니다", + "50011": "íŒŒì¼ ë©”íƒ€ì •ë³´ 조회 실패" + }, + "vasErrors": { + "40031": "ì´ ì´ë©”ì¼ ì„œë¹„ìŠ¤ 제공업체는 사용할 수 없습니다. 다른 ì´ë©”ì¼ ì£¼ì†Œë¡œ 변경하세요", + "40059": "ìžì‹ ì˜ 공유는 저장할 수 없습니다", + "40062": "í¬ì¸íŠ¸ê°€ 부족합니다", + "40063": "현재 ì‚¬ìš©ìž ê·¸ë£¹ì´ ì•„ì§ ë§Œë£Œë˜ì§€ 않았습니다. ê°œì¸ ì„¤ì •ì—서 수ë™ìœ¼ë¡œ 해약한 후 계ì†í•˜ì„¸ìš”", + "40064": "ì´ë¯¸ ì´ ì‚¬ìš©ìž ê·¸ë£¹ì— ì†í•´ 있습니다", + "40065": "êµí™˜ 코드가 잘못ë˜ì—ˆìŠµë‹ˆë‹¤", + "40066": "ì´ë¯¸ 다른 ì‹ ì›ì´ ì—°ê²°ë˜ì–´ 있습니다. 먼저 ì—°ê²°ì„ í•´ì œí•˜ì„¸ìš”", + "40067": "ì´ ì‹ ì› ë²ˆí˜¸ëŠ” ì´ë¯¸ 다른 ê³„ì •ì— ì—°ê²°ë˜ì–´ 있습니다", + "40068": "ì´ ì‹ ì›ì€ ì–´ë–¤ 계정ì—ë„ ì—°ê²°ë˜ì–´ 있지 않습니다", + "40072": "관리ìžëŠ” 다른 ì‚¬ìš©ìž ê·¸ë£¹ìœ¼ë¡œ 업그레ì´ë“œí•  수 없습니다", + "40084": "ê·€í•˜ì˜ ê³„ì •ì€ ë¹„ë°€ë²ˆí˜¸ê°€ 없는 계정ì´ë¯€ë¡œ 최소 í•˜ë‚˜ì˜ ì—°ê²°ëœ ê³„ì •ì„ ìœ ì§€í•´ì•¼ 합니다", + "40085": "주문 ê¸ˆì•¡ì´ ë„ˆë¬´ 작아 결제를 계ì†í•  수 없습니다" + } +} diff --git a/public/locales/ko-KR/dashboard.json b/public/locales/ko-KR/dashboard.json new file mode 100755 index 0000000..19b4f2c --- /dev/null +++ b/public/locales/ko-KR/dashboard.json @@ -0,0 +1,1620 @@ +{ + "errors": { + "40036": "기본 저장소 ì •ì±…ì€ ì‚­ì œí•  수 없습니다.", + "40037": "ì´ ì •ì±…ì„ ì‚¬ìš©í•˜ëŠ” íŒŒì¼ Blobì´ ìžˆìŠµë‹ˆë‹¤. 먼저 해당 íŒŒì¼ Blobì„ ì‚­ì œí•´ 주세요.", + "40038": "{{message}}ê°œì˜ ê·¸ë£¹ì´ ì´ ì •ì±…ì„ ì‚¬ìš©í•˜ê³  있습니다. 먼저 해당 ê·¸ë£¹ì˜ ì—°ê²°ì„ í•´ì œí•´ 주세요.", + "40040": "시스템 그룹ì—서는 ì´ëŸ¬í•œ ìž‘ì—…ì„ ìˆ˜í–‰í•  수 없습니다.", + "40041": "{{message}}ëª…ì˜ ì‚¬ìš©ìžê°€ 여전히 ì´ ê·¸ë£¹ì— ì†í•´ 있습니다. 먼저 해당 사용ìžë¥¼ 삭제하거나 ê·¸ë£¹ì„ ë³€ê²½í•´ 주세요.", + "40042": "초기 사용ìžì˜ ì‚¬ìš©ìž ê·¸ë£¹ì€ ë³€ê²½í•  수 없습니다.", + "40043": "초기 사용ìžì— 대해서는 ì´ëŸ¬í•œ ìž‘ì—…ì„ ìˆ˜í–‰í•  수 없습니다.", + "40046": "마스터 노드ì—서는 ì´ëŸ¬í•œ ìž‘ì—…ì„ ìˆ˜í–‰í•  수 없습니다.", + "40060": "슬레ì´ë¸Œ 노드가 마스터ì—게 콜백 ìš”ì²­ì„ ë³´ë‚¼ 수 없습니다. 마스터 노드 ì„¤ì •ì„ í™•ì¸í•´ 주세요: 매개변수 설정 - 사ì´íЏ ì •ë³´ - 사ì´íЏ URL. 슬레ì´ë¸Œ 노드가 ì´ ì£¼ì†Œì— ì—°ê²°í•  수 있는지 확ì¸í•´ 주세요. ({{message}})", + "40061": "Cloudreve ë²„ì „ì´ ì¼ì¹˜í•˜ì§€ 않습니다. ({{message}})", + "40086": "노드가 ë‹¤ìŒ ì €ìž¥ì†Œ ì •ì±…ì—서 사용 중입니다: {{message}}", + "50008": "설정 ì—…ë°ì´íŠ¸ì— ì‹¤íŒ¨í–ˆìŠµë‹ˆë‹¤. ({{message}})", + "50009": "CORS ì •ì±… ì¶”ê°€ì— ì‹¤íŒ¨í–ˆìŠµë‹ˆë‹¤." + }, + "nav": { + "summary": "대시보드 홈", + "settings": "매개변수 설정", + "basicSetting": "사ì´íЏ ì •ë³´", + "email": "ì´ë©”ì¼", + "transportation": "전송 ë° í†µì‹ ", + "appearance": "외관", + "image": "ì´ë¯¸ì§€ ë° ë¯¸ë¦¬ë³´ê¸°", + "captcha": "ì¸ì¦ 코드", + "storagePolicy": "저장소 ì •ì±…", + "nodes": "노드", + "groups": "ì‚¬ìš©ìž ê·¸ë£¹", + "users": "사용ìž", + "files": "파ì¼", + "entities": "íŒŒì¼ Blob", + "shares": "공유", + "tasks": "백그ë¼ìš´ë“œ 작업", + "remoteDownload": "오프ë¼ì¸ 다운로드", + "generalTasks": "ì¼ë°˜ 작업", + "title": "대시보드", + "dashboard": "Cloudreve 대시보드", + "userSession": "ì‚¬ìš©ìž ì„¸ì…˜", + "fileSystem": "íŒŒì¼ ì‹œìŠ¤í…œ", + "mediaProcessing": "미디어 처리", + "queue": "í", + "events": "ì´ë²¤íЏ", + "server": "서버", + "customProps": "ì‚¬ìš©ìž ì •ì˜ ì†ì„±", + "abuseReport": "남용 ì‹ ê³ " + }, + "summary": { + "generatedAt": "<0>ì— ìƒì„±ë¨", + "confirmSiteURLTitle": "사ì´íЏ URL 설정 확ì¸", + "siteURLNotMatch": "설정한 사ì´íЏ URLì— í˜„ìž¬ {{current}}ê°€ í¬í•¨ë˜ì–´ 있지 않습니다. ì„¤ì •ì„ ë³€ê²½í•˜ì‹œê² ìŠµë‹ˆê¹Œ?", + "setAsPrimary": "기본 사ì´íЏ URL로 설정", + "setAsPrimaryDes": "{{current}}를 기본 사ì´íЏ URL로 설정하여 외부 ì„œë¹„ìŠ¤ì™€ì˜ í†µì‹  ë° ì½œë°± ìˆ˜ì‹ ì— ì‚¬ìš©í•©ë‹ˆë‹¤. ê³µì¸ ë„¤íŠ¸ì›Œí¬ì—서 ì ‘ê·¼ 가능한 URLì„ ì‚¬ìš©í•´ 주세요.", + "setAsSecondary": "ë³´ì¡° 사ì´íЏ URLì— ì¶”ê°€", + "setAsSecondaryDes": "{{current}}를 ë³´ì¡° 사ì´íЏ URLì— ì¶”ê°€í•©ë‹ˆë‹¤. Cloudreve는 사용ìžê°€ 실제로 접근하는 URLì— ë”°ë¼ ìžë™ìœ¼ë¡œ 사용 여부를 ì„ íƒí•©ë‹ˆë‹¤.", + "siteURLDescription": "ì´ ì„¤ì •ì€ ë§¤ìš° 중요합니다. 사ì´íŠ¸ì˜ ì‹¤ì œ 주소와 ì¼ì¹˜í•˜ëŠ”ì§€ 확ì¸í•´ 주세요. 매개변수 설정 - 사ì´íЏ ì •ë³´ì—서 ì´ ì„¤ì •ì„ ë³€ê²½í•  수 있습니다.", + "ignore": "무시", + "changeIt": "변경", + "trend": "추세", + "summary": "ì´ê³„", + "totalUsers": "ë“±ë¡ ì‚¬ìš©ìž", + "totalFilesAndFolders": "íŒŒì¼ ë° í´ë”", + "shareLinks": "공유 ë§í¬", + "totalBlobs": "íŒŒì¼ Blob", + "homepage": "홈페ì´ì§€", + "github": "GitHub", + "documents": "문서", + "discordCommunity": "Discord 커뮤니티", + "telegram": "Telegram 커뮤니티", + "forum": "커뮤니티 í¬ëŸ¼", + "buyPro": "Pro로 업그레ì´ë“œ", + "publishedAt": "<0>ì— ê²Œì‹œë¨", + "licenseExpireAt": "ë¼ì´ì„ ìФ 만료ì¼", + "permanentLicense": "ì˜êµ¬ ë¼ì´ì„ ìФ", + "offlineLicenseExpireAy": "오프ë¼ì¸ ë¼ì´ì„ ìФ 만료ì¼", + "offlineLicenseDes": "네트워í¬ì— ì—°ê²°ëœ ìƒíƒœì—서 Cloudreve는 만료 ì „ì— ìžë™ìœ¼ë¡œ 오프ë¼ì¸ ë¼ì´ì„ ìŠ¤ë¥¼ ì—…ë°ì´íŠ¸í•©ë‹ˆë‹¤.", + "licensedDomains": "ë¼ì´ì„ ìŠ¤ê°€ ë¶€ì—¬ëœ ë„ë©”ì¸", + "renew": "오프ë¼ì¸ ë¼ì´ì„ ìФ ì—…ë°ì´íЏ", + "manageLicense": "ë¼ì´ì„ ìФ 관리", + "volPurchase": "í´ë¼ì´ì–¸íЏ VOL ë¼ì´ì„ ìŠ¤ëŠ” <0>ë¼ì´ì„ ìФ 관리 패ë„ì—서 별ë„로 구매해야 합니다. VOL ë¼ì´ì„ ìŠ¤ë¥¼ 통해 사용ìžëŠ” <1>Cloudreve iOS í´ë¼ì´ì–¸íŠ¸ë¥¼ 무료로 사용하여 사ì´íŠ¸ì— ì—°ê²°í•  수 있으며, 사용ìžê°€ iOS í´ë¼ì´ì–¸íŠ¸ë¥¼ 위해 별ë„로 결제할 필요가 없습니다. ë¼ì´ì„ ìФ 구매 후 ì•„ëž˜ì˜ ë¼ì´ì„ ìФ ì—…ë°ì´íЏ ë²„íŠ¼ì„ í´ë¦­í•´ 주세요.", + "iosVol": "iOS í´ë¼ì´ì–¸íЏ 대량 ë¼ì´ì„ ìФ (VOL)", + "refreshSuccessfully": "새로고침 성공", + "manualRefresh": "수ë™ìœ¼ë¡œ 오프ë¼ì¸ ë¼ì´ì„ ìФ 새로고침", + "manualRefreshDes": "ìžë™ 오프ë¼ì¸ ë¼ì´ì„ ìФ ìƒˆë¡œê³ ì¹¨ì— ì‹¤íŒ¨í–ˆìŠµë‹ˆë‹¤. <0>ë¼ì´ì„ ìФ 관리 패ë„ì— ë¡œê·¸ì¸í•˜ì—¬ 최신 오프ë¼ì¸ ë¼ì´ì„ ìŠ¤ë¥¼ 가져와서 ì•„ëž˜ì— ë¶™ì—¬ë„£ì–´ 주세요.", + "announcement": "공지사항" + }, + "queue": { + "queueName_io_intense": "IO 집약ì ", + "queueName_io_intenseDes": "ëŒ€ëŸ‰ì˜ IO ìž‘ì—…ì„ ì²˜ë¦¬í•˜ëŠ” í입니다. 저장소 ì •ì±… 전송, ì••ì¶• í•´ì œ, ì••ì¶• í¬í•¨", + "queueName_media_meta": "미디어 메타ë°ì´í„° 추출", + "queueName_media_metaDes": "미디어 파ì¼ì˜ 메타ë°ì´í„°ë¥¼ 추출하는 ë° ì‚¬ìš©ë©ë‹ˆë‹¤.", + "queueName_recycle": "Blob 재활용", + "queueName_recycleDes": "ë§Œë£Œëœ íŒŒì¼ Blobì„ ì‚­ì œí•˜ëŠ” ë° ì‚¬ìš©ë©ë‹ˆë‹¤.", + "queueName_thumb": "ì¸ë„¤ì¼ ìƒì„±", + "queueName_thumbDes": "파ì¼ì˜ ì¸ë„¤ì¼ì„ ìƒì„±í•˜ëŠ” ë° ì‚¬ìš©ë©ë‹ˆë‹¤.", + "queueName_remote_download": "오프ë¼ì¸ 다운로드", + "queueName_remote_downloadDes": "오프ë¼ì¸ 다운로드 ìž‘ì—…ì„ ì²˜ë¦¬í•˜ëŠ” ë° ì‚¬ìš©ë©ë‹ˆë‹¤.", + "failed": "실패 ({{count}})", + "success": "성공 ({{count}})", + "suspending": "ì¼ì‹œ 중단 ({{count}})", + "busyWorker": "처리 중 ({{count}})", + "submited": "ì œì¶œë¨ ({{count}})", + "editQueueSettings": "í 설정 편집 - {{name}}", + "workerNum": "작업 스레드 수", + "workerNumDes": "작업 íì—서 ë™ì‹œì— 실행ë˜ëŠ” 최대 작업 수", + "maxExecution": "최대 실행 시간", + "maxExecutionDes": "ìž‘ì—…ì˜ ìµœëŒ€ 실행 시간(ì´ˆ)입니다. ì´ ì‹œê°„ì„ ì´ˆê³¼í•˜ë©´ ìž‘ì—…ì´ ì¢…ë£Œë©ë‹ˆë‹¤.", + "backoffFactor": "백오프 계수", + "backoffFactorDes": "작업 ìž¬ì‹œë„ ì‹œê°„ ê°„ê²©ì˜ ì¦ê°€ 계수", + "backoffMaxDuration": "최대 백오프 시간", + "backoffMaxDurationDes": "작업 재시ë„ì˜ ìµœëŒ€ 백오프 시간(ì´ˆ)", + "maxRetry": "최대 ìž¬ì‹œë„ íšŸìˆ˜", + "maxRetryDes": "작업 실패 후 최대 ìž¬ì‹œë„ íšŸìˆ˜", + "retryDelay": "ìž¬ì‹œë„ ì§€ì—°", + "retryDelayDes": "작업 재시ë„ì˜ ì´ˆê¸° 지연 시간(ì´ˆ)" + }, + "settings": { + "headlessFooter": "ë¡œê·¸ì¸ ì„¸ì…˜ 페ì´ì§€ 하단", + "headlessFooterDes": "ì‚¬ìš©ìž ë¡œê·¸ì¸, 등ë¡, 콜백 ê²°ê³¼ 등 페ì´ì§€ í•˜ë‹¨ì— í‘œì‹œë˜ëŠ” ì‚¬ìš©ìž ì •ì˜ HTML 콘í…츠", + "headlessBottom": "ë¡œê·¸ì¸ ì„¸ì…˜ 페ì´ì§€ 본문 하단", + "headlessBottomDes": "ì‚¬ìš©ìž ë¡œê·¸ì¸, 등ë¡, 콜백 ê²°ê³¼ 등 페ì´ì§€ 본문 프레임 í•˜ë‹¨ì— í‘œì‹œë˜ëŠ” ì‚¬ìš©ìž ì •ì˜ HTML 콘í…츠", + "customHTML": "ì‚¬ìš©ìž ì •ì˜ HTML", + "customHTMLDes": "사ì´íŠ¸ì˜ ì‚¬ì „ ì„¤ì •ëœ ìœ„ì¹˜ì— ì‚¬ìš©ìž ì •ì˜ HTML 콘í…츠를 삽입하여 표시합니다.", + "sidebarBottom": "사ì´ë“œë°” 하단", + "sidebarBottomDes": "사ì´ë“œë°” í•˜ë‹¨ì— í‘œì‹œë˜ëŠ” ì‚¬ìš©ìž ì •ì˜ HTML 콘í…츠", + "addNavItem": "네비게ì´ì…˜ 항목 추가", + "customNavItems": "ì‚¬ìš©ìž ì •ì˜ ì‚¬ì´ë“œë°” 네비게ì´ì…˜", + "customNavItemsDes": "좌측 네비게ì´ì…˜ ë°”ì— ì‚¬ìš©ìž ì •ì˜ í•­ëª©ì„ ì¶”ê°€í•  수 있으며, 사용ìžê°€ í´ë¦­í•˜ë©´ 해당 ë§í¬ë¡œ ì´ë™í•©ë‹ˆë‹¤.", + "navItemUrl": "ë§í¬", + "iconifyNamePlaceholder": "Iconify ì•„ì´ì½˜ ì‹ë³„ìž, 예: fluent:home-24-regular", + "iconifyName": "Iconify ì•„ì´ì½˜ ì´ë¦„", + "oidc": "OpenID Connect (OIDC)", + "oidcDes": "OpenID Connect (OIDC)는 서로 다른 시스템 ê°„ì˜ ì‹ ì› ì¸ì¦ì„ 위한 개방형 ì¸ì¦ 프로토콜입니다. ì œ3ìž ì‹ ì› í”Œëž«í¼ì—서 애플리케ì´ì…˜ì„ ìƒì„±í•œ 후, <0>{{url}}ì„ \"리다ì´ë ‰íЏ URI\"ì— ì¶”ê°€í•´ 주세요. ìžì„¸í•œ ë‚´ìš©ì€ <1>ê³µì‹ ë¬¸ì„œë¥¼ 참조해 주세요.", + "clientID": "í´ë¼ì´ì–¸íЏ ID", + "clientIDDes": "ì œ3ìž ì‹ ì› í”Œëž«í¼ì—서 ìƒì„±í•œ 애플리케ì´ì…˜ì˜ í´ë¼ì´ì–¸íЏ ID", + "clientSecret": "í´ë¼ì´ì–¸íЏ 시í¬ë¦¿", + "clientSecretDes": "ì œ3ìž ì‹ ì› í”Œëž«í¼ì—서 ìƒì„±í•œ 애플리케ì´ì…˜ì˜ í´ë¼ì´ì–¸íЏ 시í¬ë¦¿", + "scope": "Scope", + "scopeDes": "추가로 요청할 Scope를 쉼표 <0>,로 구분하여 입력합니다. 기본ì ìœ¼ë¡œ Cloudreve는 <0>openid, <0>email, <0>profileì„ ìš”ì²­í•˜ë¯€ë¡œ ì—¬ê¸°ì— ì¤‘ë³µ 입력할 필요가 없습니다.", + "oidcWellknown": "발견 문서 (Wellknown)", + "oidcWellknownDes": "ì œ3ìž ì‹ ì› í”Œëž«í¼ì˜ 발견 문서로, OpenID Connectì˜ êµ¬ì„± 정보를 í¬í•¨í•©ë‹ˆë‹¤.", + "importFromWellknown": "URLì—서 가져오기", + "importOidc": "OIDC 발견 문서 가져오기", + "oidcWellknownUrl": "발견 문서 URL", + "oidcWellknownUrlDes": "ì œ3ìž ì‹ ì› í”Œëž«í¼ ë°œê²¬ ë¬¸ì„œì˜ URL 주소, 예: <0>https://accounts.google.com/.well-known/openid-configuration", + "resetUrl": "재설정 ë§í¬", + "exceedToleranceDays": "ì„¤ì •ëœ ì°¨ë‹¨ 관용 ì¼ìˆ˜", + "activateUrl": "활성화 ë§í¬", + "domainNotLicensed": "ë„ë©”ì¸ì´ ë¼ì´ì„ ìФë˜ì§€ 않ìŒ", + "domainNotLicensedDes": "설정한 사ì´íЏ URLì— ë¼ì´ì„ ìФë˜ì§€ ì•Šì€ ë„ë©”ì¸ì´ í¬í•¨ë˜ì–´ 있습니다. <0>ë¼ì´ì„ ìФ 관리 패ë„ì—서 ì´ ì„œë¸Œë„ë©”ì¸ì„ 추가한 후 아래 ë²„íŠ¼ì„ í´ë¦­í•˜ì—¬ ë¼ì´ì„ ìŠ¤ë¥¼ ì—…ë°ì´íŠ¸í•˜ê³  다시 시ë„í•´ 주세요.", + "showSettings": "설정 표시", + "perPage": "페ì´ì§€ë‹¹ {{num}}ê°œ", + "noNodes": "사용 가능한 노드가 없습니다.", + "extractMediaMeta": "미디어 ì •ë³´ 추출", + "extractMediaMetaDes": "미디어 파ì¼ì˜ 메타ë°ì´í„°ë¥¼ 추출하여 표시 ë° ê²€ìƒ‰ì— ì‚¬ìš©í•©ë‹ˆë‹¤. 기본ì ìœ¼ë¡œ 비로컬 저장소 ì •ì±…ì€ \"저장소 ì •ì±… 네ì´í‹°ë¸Œ\" ë°©ì‹ë§Œ 사용합니다. 저장소 ì •ì±… 설정 페ì´ì§€ì—서 \"추출기 프ë¡ì‹œ\" ê¸°ëŠ¥ì„ í™œì„±í™”í•˜ì—¬ ì œ3ìž ì €ìž¥ì†Œ ì •ì±…ì˜ ì¸ë„¤ì¼ ê¸°ëŠ¥ì„ í™•ìž¥í•  수 있습니다. ìžì„¸í•œ ë‚´ìš©ì€ <0>ê³µì‹ ë¬¸ì„œë¥¼ 참조해 주세요.", + "exif": "EXIF", + "exifDes": "ì´ë¯¸ì§€ 파ì¼ì—서 EXIF 메타ë°ì´í„°ë¥¼ 추출하여 표시 ë° ê²€ìƒ‰ì— ì‚¬ìš©í•©ë‹ˆë‹¤.", + "music": "ìŒì•… 메타ë°ì´í„°", + "musicDes": "ìŒì•… 파ì¼ì—서 제목, 아티스트, 앨범 ë“±ì˜ ì •ë³´ë¥¼ í¬í•¨í•œ 메타ë°ì´í„°ë¥¼ 추출합니다.", + "ffprobe": "FFprobe", + "ffprobeDes": "FFprobe를 사용하여 비디오 ë° ì˜¤ë””ì˜¤ 파ì¼ì—서 메타ë°ì´í„°ë¥¼ 추출합니다.", + "maxSizeLocal": "최대 íŒŒì¼ í¬ê¸° (로컬 저장소)", + "maxSizeLocalDes": "파ì¼ì´ 로컬 저장소 ì •ì±…ì— ì €ìž¥ë  ë•Œ 메타ë°ì´í„° ì¶”ì¶œì„ í—ˆìš©í•˜ëŠ” 최대 íŒŒì¼ í¬ê¸°ìž…니다. 0으로 설정하면 ì œí•œì´ ì—†ìŠµë‹ˆë‹¤.", + "maxSizeRemote": "최대 íŒŒì¼ í¬ê¸° (ì›ê²© 저장소)", + "maxSizeRemoteDes": "파ì¼ì´ ì œ3ìž ì €ìž¥ì†Œ ì •ì±…ì— ì €ìž¥ë  ë•Œ 메타ë°ì´í„° ì¶”ì¶œì„ í—ˆìš©í•˜ëŠ” 최대 íŒŒì¼ í¬ê¸°ìž…니다. 0으로 설정하면 ì œí•œì´ ì—†ìŠµë‹ˆë‹¤.", + "exifBruteForce": "í•„ìš” 시 무차별 검색 사용", + "exifBruteForceDes": "활성화하면 표준 í—¤ë” ìœ„ì¹˜ì—서 EXIF ë°ì´í„°ë¥¼ ì°¾ì„ ìˆ˜ ì—†ì„ ë•Œ ì „ì²´ 파ì¼ì„ 스캔하여 EXIF ë°ì´í„°ë¥¼ 찾습니다. 처리 ì‹œê°„ì´ ì¦ê°€í•  수 있지만 비표준 ìœ„ì¹˜ì˜ EXIF ë°ì´í„°ë¥¼ ì°¾ì„ ìˆ˜ 있습니다.", + "musicCover": "앨범 커버", + "musicCoverDes": "오디오 파ì¼ì—서 앨범 커버를 추출하며, ID3 (v1, 2.2, 2.3, 2.4) 메타ë°ì´í„° 컨테ì´ë„ˆë¥¼ ì§€ì›í•©ë‹ˆë‹¤. ì´ ìƒì„±ê¸°ëŠ” 다른 ì´ë¯¸ì§€ ìƒì„±ê¸°(Cloudreve 내장 ë˜ëŠ” VIPS) 중 í•˜ë‚˜ì— ì˜ì¡´í•©ë‹ˆë‹¤.", + "geocoding": "지오코딩", + "geocodingDes": "미디어 EXIFì— ê¸°ë¡ëœ 좌표 정보를 기반으로 Mapbox 서비스를 사용하여 주소 정보를 가져옵니다.", + "mapboxAK": "Mapbox API 키", + "mapboxAKDes": "Mapbox 콘솔ì—서 ìƒì„±ëœ API 키입니다.", + "geocodingDependencyWarning": "지오코딩 ìƒì„±ê¸°ëŠ” EXIF ìƒì„±ê¸°ì— ì˜ì¡´í•©ë‹ˆë‹¤. EXIF ìƒì„±ê¸°ë¥¼ 활성화하십시오.", + "notAppliedToNativeGenerator": "{{prefix}}저장소 ì •ì±… 네ì´í‹°ë¸Œ ìƒì„±ê¸°ì—는 ì ìš©ë˜ì§€ 않습니다.", + "notAppliedToOneDriveNativeGenerator": "{{prefix}}OneDrive ë˜ëŠ” SharePoint 저장소 ì •ì±… 네ì´í‹°ë¸Œ ìƒì„±ê¸°ì—는 ì ìš©ë˜ì§€ 않습니다.", + "fileBlobMargin": "íŒŒì¼ Blob 임시 URL ìºì‹œ 여유분(ì´ˆ)", + "fileBlobMarginDes": "ë™ì¼í•œ íŒŒì¼ Blobì´ ì—¬ëŸ¬ 번 ìš”ì²­ë  ë•Œ, 초기 URLì˜ ë‚¨ì€ ìœ íš¨ ê¸°ê°„ì´ ì—¬ìœ ë¶„ë³´ë‹¤ í¬ë©´ ë™ì¼í•œ URLì´ ìž¬ì‚¬ìš©ë©ë‹ˆë‹¤.", + "fileBlobTimeout": "íŒŒì¼ Blob 임시 URL 유효 기간", + "fileBlobTimeoutDes": "사용ìžê°€ 파ì¼ì„ 열거나 다운로드할 때 얻는 임시 ë§í¬ì˜ 유효 ê¸°ê°„ì„ ì œí•œí•©ë‹ˆë‹¤. 로컬 저장소 ì •ì±…, WebDAV ë˜ëŠ” Cloudreve 프ë¡ì‹œë¥¼ 통한 íŒŒì¼ ë‹¤ìš´ë¡œë“œì—ë§Œ ì ìš©ë©ë‹ˆë‹¤.", + "wopiSessionTimeout": "WOPI 세션 유효 기간(ì´ˆ)", + "wopiSessionTimeoutDes": "사용ìžê°€ WOPI를 사용하여 파ì¼ì„ 편집할 때 ë‹¨ì¼ ì„¸ì…˜ì˜ ìœ íš¨ ê¸°ê°„ì„ ì œí•œí•©ë‹ˆë‹¤. 만료 후 사용ìžëŠ” Cloudreveì—서 파ì¼ì„ 다시 열어야 합니다.", + "oauthRefresh": "OAuth 저장소 ì •ì±… ìžê²© ì¦ëª… 새로고침 간격", + "oauthRefreshDes": "OAuthê°€ 필요한 저장소 ì •ì±…(OneDrive)ì˜ ìžê²© ì¦ëª…ì„ ì–¼ë§ˆë‚˜ ìžì£¼ 새로고침할지 설정합니다. 저장소 ì •ì±…ì„ ìž¥ê¸°ê°„ 사용하지 않아 ìžê²© ì¦ëª…ì´ ë§Œë£Œë˜ëŠ” ê²ƒì„ ë°©ì§€í•  수 있습니다.", + "transitParallelNum": "중계 최대 병렬 전송", + "transitParallelNumDes": "ë‹¨ì¼ ì„œë²„ì¸¡ íŒŒì¼ ì¤‘ê³„ ìž‘ì—…ì— ì—¬ëŸ¬ 파ì¼ì´ í¬í•¨ë  때 최대 병렬 업로드 수", + "failedChunkRetry": "ì²­í¬ ì˜¤ë¥˜ 최대 재시ë„", + "failedChunkRetryDes": "ì²­í¬ ì—…ë¡œë“œ 실패 후 재시ë„하는 최대 횟수로, 서버측 업로드 ë˜ëŠ” 중계ì—ë§Œ ì ìš©ë©ë‹ˆë‹¤.", + "cacheChunks": "재시ë„를 위해 ìŠ¤íŠ¸ë¦¬ë° ì²­í¬ íŒŒì¼ ìºì‹œ", + "cacheChunksDes": "활성화하면 ìŠ¤íŠ¸ë¦¬ë° ì¤‘ê³„ ì²­í¬ ì—…ë¡œë“œ 시 ì²­í¬ ë°ì´í„°ë¥¼ 시스템 임시 ë””ë ‰í† ë¦¬ì— ìºì‹œí•˜ì—¬ ì²­í¬ ì—…ë¡œë“œ 실패 후 재시ë„ì— ì‚¬ìš©í•©ë‹ˆë‹¤.\n 비활성화하면 ìŠ¤íŠ¸ë¦¬ë° ì¤‘ê³„ ì²­í¬ ì—…ë¡œë“œëŠ” 추가 ë””ìŠ¤í¬ ê³µê°„ì„ ì°¨ì§€í•˜ì§€ 않지만 ì²­í¬ ì—…ë¡œë“œ 실패 시 ì „ì²´ 업로드가 즉시 실패합니다.", + "folderPropsTimeout": "디렉토리 통계 ì •ë³´ 유효 기간(ì´ˆ)", + "folderPropsTimeoutDes": "사용ìžê°€ 디렉토리 통계 ì •ë³´(í¬ê¸°, í¬í•¨ íŒŒì¼ ìˆ˜ 등)를 계산할 때 ê²°ê³¼ ìºì‹œì˜ 유효 기간", + "slaveAPIExpiration": "슬레ì´ë¸Œ API 서명 유효 기간(ì´ˆ)", + "slaveAPIExpirationDes": "마스터가 슬레ì´ë¸Œ APIì— ì ‘ê·¼í•  때 사용하는 서명 유효 기간", + "uploadSessionTimeout": "업로드 세션 유효 기간(ì´ˆ)", + "uploadSessionDes": "업로드 세션 유효 기간 ë‚´ì—서 ì§€ì›ë˜ëŠ” 저장소 ì •ì±…ì˜ ê²½ìš° 사용ìžëŠ” 완료ë˜ì§€ ì•Šì€ ìž‘ì—…ì„ ìž¬ê°œí•  수 있습니다. 설정할 수 있는 ìµœëŒ€ê°’ì€ ë‹¤ì–‘í•œ 저장소 ì •ì±… 서비스 ì œê³µì—…ì²´ì˜ ê·œì¹™ì— ì˜í•´ 제한ë©ë‹ˆë‹¤.", + "archiveTimeout": "서버측 패키지 다운로드 세션 유효 기간(ì´ˆ)", + "advanceOptions": "고급 설정", + "emojiOptions": "ì´ëª¨ì§€ 옵션", + "addCategorize": "분류 추가", + "category": "분류", + "searchQuery": "íŒŒì¼ ë¶„ë¥˜ 쿼리", + "importWopi": "WOPI 애플리케ì´ì…˜ 설정 가져오기", + "wopiEndpoint": "WOPI Discovery Endpoint", + "wopiDes": "WOPI í”„ë¡œí† ì½œì„ ì§€ì›í•˜ëŠ” 온ë¼ì¸ 문서 처리 시스템과 ì—°ë™í•˜ì—¬ Cloudreveì˜ ë¬¸ì„œ 온ë¼ì¸ 미리보기 ë° íŽ¸ì§‘ ê¸°ëŠ¥ì„ í™•ìž¥í•©ë‹ˆë‹¤. ì—¬ê¸°ì— WOPI 서비스 발견 주소를 입력해 주세요. 예: <0>https://example.com/hosting/discovery. ìžì„¸í•œ ë‚´ìš©ì€ <1>ê³µì‹ ë¬¸ì„œë¥¼ 참조해 주세요.", + "embeddedWebpageViewer": "임베디드 웹페ì´ì§€ 애플리케ì´ì…˜", + "wopiViewer": "WOPI 프로토콜 애플리케ì´ì…˜", + "ext": "확장ìž", + "invalidWopiActionMapping": "WOPI Action ë§¤í•‘ì´ ìœ íš¨í•˜ì§€ 않ìŒ", + "woapiActionMapping": "WOPI Action 매핑", + "drawioHost": "DrawIO ì¸ìŠ¤í„´ìŠ¤", + "drawioHostDes": "ìžì²´ 호스팅 ì¸ìŠ¤í„´ìŠ¤ì˜ ì£¼ì†Œë¥¼ 입력할 수 있습니다.", + "openInNew": "새 ì°½ì—서 ì§ì ‘ 열기", + "openInNewDes": "ì²´í¬í•˜ë©´ 새 탭ì—서 ì´ ì• í”Œë¦¬ì¼€ì´ì…˜ì„ ì§ì ‘ 엽니다.", + "maxSize": "최대 íŒŒì¼ í¬ê¸°", + "maxSizeDes": "ì´ ì• í”Œë¦¬ì¼€ì´ì…˜ì—서 ì§€ì›í•˜ëŠ” 최대 íŒŒì¼ í¬ê¸°ìž…니다. 0ì€ ì œí•œ ì—†ìŒì„ ì˜ë¯¸í•©ë‹ˆë‹¤. í¬ê¸°ë¥¼ ì´ˆê³¼í•´ë„ íŒŒì¼ì„ 열려고 시ë„하지만 사용ìžì—게 경고합니다.", + "srcEncodedVar": "URL ì¸ì½”ë”©ëœ íŒŒì¼ Blob 임시 ì ‘ê·¼ 주소", + "srcVar": "íŒŒì¼ Blob 임시 ì ‘ê·¼ 주소", + "srcBase64Var": "Base64 ì¸ì½”ë”©ëœ íŒŒì¼ Blob 임시 ì ‘ê·¼ 주소", + "nameEncodedVar": "URL ì¸ì½”ë”©ëœ íŒŒì¼ëª…", + "versionEntityVar": "열린 íŒŒì¼ ë²„ì „ Blob ID, 비어있으면 최신 ë²„ì „ì„ ì˜ë¯¸", + "fileIdVar": "íŒŒì¼ ID", + "userIdVar": "ì‚¬ìš©ìž ID, 로그ì¸í•˜ì§€ ì•Šì€ ê²½ìš° 비어있ìŒ", + "userDisplayNameVar": "URL ì¸ì½”ë”©ëœ ì‚¬ìš©ìž ë‹‰ë„¤ìž„", + "fileViewers": "íŒŒì¼ ë¸Œë¼ìš°ì € 애플리케ì´ì…˜", + "addViewer": "애플리케ì´ì…˜ 추가", + "viewerGroupTitle": "애플리케ì´ì…˜ 그룹 #{{index}}", + "viewerType": "유형", + "viewerPlatform": "플랫í¼", + "viewerPlatformDes": "해당 플랫í¼ì„ ì„ íƒí•˜ì—¬ 해당 플랫í¼ì—서만 애플리케ì´ì…˜ì„ 표시합니다.", + "viewerPlatformPC": "PC", + "viewerPlatformMobile": "모바ì¼", + "viewerPlatformAll": "모든 플랫í¼", + "displayName": "ì´ë¦„", + "displayNameDes": "표시 ì´ë¦„으로 i18next 키 ê°’ì„ ì§€ì›í•©ë‹ˆë‹¤.", + "viewerEnabled": "활성화", + "newFileAction": "새 íŒŒì¼ ë§¤í•‘", + "newFileActionDes": "ë§¤í•‘ì„ ì¶”ê°€í•˜ë©´ 사용ìžê°€ \"새로 만들기\" ë²„íŠ¼ì„ í´ë¦­í•  때 ì´ ì• í”Œë¦¬ì¼€ì´ì…˜ì˜ ì˜µì…˜ì´ ë‚˜íƒ€ë‚©ë‹ˆë‹¤.", + "addNewFileAction": "매핑 추가", + "builtinViewerType": "내장 애플리케ì´ì…˜", + "wopiViewerType": "WOPI", + "customViewerType": "ì‚¬ìš©ìž ì •ì˜", + "nMapping": "{{num}}ê°œ", + "editViewerTitle": "{{name}} 편집", + "builtInIconUrlDes": "ì´ ë‚´ìž¥ 애플리케ì´ì…˜ì—는 기본 ì•„ì´ì½˜ì´ 있습니다. ì•„ì´ì½˜ 주소가 비어있으면 기본 ì•„ì´ì½˜ì„ 사용합니다.", + "viewerUrl": "애플리케ì´ì…˜ URL", + "viewerUrlDes": "ì‚¬ìš©ìž ì •ì˜ ì• í”Œë¦¬ì¼€ì´ì…˜ì˜ URL 주소로 <0>ë§¤ì§ ë³€ìˆ˜ë¥¼ 사용할 수 있습니다.", + "addIcon": "ì•„ì´ì½˜ 추가", + "exts": "í™•ìž¥ìž ëª©ë¡", + "icon": "ì•„ì´ì½˜", + "iconUrl": "ì•„ì´ì½˜ 주소", + "iconColor": "ì•„ì´ì½˜ 색ìƒ", + "iconColorDark": "ì•„ì´ì½˜ ìƒ‰ìƒ (ë‹¤í¬ ëª¨ë“œ)", + "fileIcons": "íŒŒì¼ ì•„ì´ì½˜", + "builtinIcon": "내장 ì•„ì´ì½˜", + "mimeMapping": "MIME 유형 매핑", + "mimeMappingDes": "JSON 형ì‹ì˜ MIME 유형 매핑 í…Œì´ë¸”로, 키는 íŒŒì¼ í™•ìž¥ìž, ê°’ì€ MIME 유형입니다. Cloudreve는 íŒŒì¼ í™•ìž¥ìžì™€ ì´ ì„¤ì •ì— ë”°ë¼ íŒŒì¼ MIME ìœ í˜•ì„ íŒë‹¨í•©ë‹ˆë‹¤.", + "mapProvider": "ì§€ë„ ì œê³µì—…ì²´", + "mapProviderDes": "미디어 위치 정보를 표시할 때 사용하는 ì§€ë„ ì œê³µì—…ì²´", + "mapGoogle": "Google Maps (Leaflet)", + "mapOpenStreetMap": "OpenStreetMap (Leaflet)", + "mapboxMap": "OpenStreetMap (Mapbox)", + "mapboxAccessToken": "Mapbox 액세스 토í°", + "mapboxAccessTokenDes": "<0>Mapbox 콘솔ì—서 ìƒì„±í•œ 공개 액세스 토í°ìž…니다.", + "tileType": "기본 ì§€ë„ ìœ í˜•", + "tileTypeDes": "Google Maps 기본 ì§€ë„ ìœ í˜•", + "tileTypeTerrain": "지형", + "tileTypeSatellite": "위성", + "tileTypeGeneral": "ì¼ë°˜", + "maxPageSize": "최대 페ì´ì§€ í¬ê¸°", + "maxPageSizeDes": "사용ìžê°€ ì¡°ì •í•  수 있는 페ì´ì§€ë‹¹ 최대 íŒŒì¼ ìˆ˜ë¥¼ 제한합니다.", + "maxRecursiveSearch": "최대 재귀 검색 수", + "maxRecursiveSearchDes": "사용ìžê°€ 파ì¼ì„ 검색할 때 ê²€ìƒ‰ëœ íŒŒì¼ ìˆ˜ê°€ ì´ ì œí•œì„ ì´ˆê³¼í•˜ë©´ ê²€ìƒ‰ì´ ì¤‘ì§€ë˜ê³  사용ìžì—게 경고합니다.", + "maxBatchSize": "최대 ì¼ê´„ 작업 수", + "maxBatchSizeDes": "사용ìžê°€ ì¼ê´„ 작업할 수 있는 최대 íŒŒì¼ ìˆ˜ë¡œ, 최ìƒìœ„ 레벨만 계산ë˜ë©° 하위 ë””ë ‰í† ë¦¬ì˜ íŒŒì¼ ìˆ˜ëŠ” í¬í•¨ë˜ì§€ 않습니다.", + "defaultPagination": "íŒŒì¼ ëª©ë¡ íŽ˜ì´ì§€ë„¤ì´ì…˜ ë°©ì‹", + "cursorPagination": "커서 페ì´ì§€ë„¤ì´ì…˜", + "cursorPaginationDes": "사용ìžê°€ 하단으로 스í¬ë¡¤í•˜ë©´ ìžë™ìœ¼ë¡œ ë” ë§Žì€ íŒŒì¼ì„ 로드합니다. 대량 íŒŒì¼ ëª©ë¡ì— 대해 ì„±ëŠ¥ì´ ì¢‹ì§€ë§Œ ì´ íŽ˜ì´ì§€ 수를 ë³¼ 수 없습니다.", + "offsetPagination": "전통ì ì¸ 페ì´ì§€ë„¤ì´ì…˜", + "offsetPaginationDes": "페ì´ì§€ í•˜ë‹¨ì— íŽ˜ì´ì§€ë„¤ì´ì…˜ 네비게ì´ì…˜ì´ 표시ë˜ë©°, 사용ìžëŠ” ì´ íŽ˜ì´ì§€ 수를 ë³´ê³  특정 페ì´ì§€ë¡œ ì´ë™í•  수 있습니다. 대량 íŒŒì¼ ëª©ë¡ì—서는 ì„±ëŠ¥ì´ ë–¨ì–´ì§‘ë‹ˆë‹¤.", + "defaultPaginationDes": "위 설정과 ê´€ê³„ì—†ì´ ì‚¬ìš©ìžê°€ 검색할 때는 커서 페ì´ì§€ë„¤ì´ì…˜ì´ 강제로 사용ë©ë‹ˆë‹¤.", + "publicResourceMaxAge": "ì •ì  ìžì› ìºì‹œ 유효 기간(ì´ˆ)", + "publicResourceMaxAgeDes": "브ë¼ìš°ì €ë‚˜ CDNì—게 ì •ì  ìžì›ì˜ ìºì‹œ 유효 ê¸°ê°„ì„ ì•Œë ¤ì£¼ëŠ” ë° ì‚¬ìš©ë˜ë©°, 단위는 초입니다. 파ì¼, ì¸ë„¤ì¼, ì‚¬ìš©ìž í”„ë¡œí•„ ì‚¬ì§„ì„ í¬í•¨í•©ë‹ˆë‹¤.", + "cronDes": "{{des}} ì—¬ê¸°ì— ì˜¬ë°”ë¥¸ <0>Cron 표현ì‹ì„ 입력해야 합니다. Cloudreve를 재시작한 후 ì ìš©ë©ë‹ˆë‹¤.", + "entityCollectInterval": "íŒŒì¼ Blob 재활용 간격", + "entityCollectIntervalDes": "ë§Œë£Œëœ íŒŒì¼ Blobì„ ìŠ¤ìº”í•˜ê³  삭제하는 주기를 설정합니다.", + "trashBinInterval": "휴지통 스캔 간격", + "trashBinIntervalDes": "íœ´ì§€í†µì˜ ë§Œë£Œëœ íŒŒì¼ì„ 스캔하고 삭제하는 주기를 설정합니다.", + "logtoName": "ë¡œê·¸ì¸ ë°©ë²• ì´ë¦„", + "logtoNameDes": "사용ìžì—게 표시할 ë¡œê·¸ì¸ ë°©ë²• ì´ë¦„으로, ê¸°ë³¸ê°’ì€ \"SSO\"ì´ë©° i18next 키 ê°’ì„ ì§€ì›í•©ë‹ˆë‹¤.", + "logtoDirectSSO": "ì œ3ìž ë¡œê·¸ì¸ ì§ì ‘ ì—°ê²°", + "logtoDirectSSODes": "Logto ë¡œê·¸ì¸ í™”ë©´ì„ ê±´ë„ˆë›°ê³  ì—°ë™ëœ ì œ3ìž ë¡œê·¸ì¸ì´ë‚˜ SSO로 ì§ì ‘ ì´ë™í•˜ë ¤ë©´ ì—¬ê¸°ì— ì œ3ìž ë¡œê·¸ì¸ ì»¤ë„¥í„°ì˜ ì‹ë³„ìžë¥¼ 입력해 주세요. ìžì„¸í•œ ë‚´ìš©ì€ <0>Logto 문서를 참조해 주세요.", + "logtoEndpoint": "Logto 엔드í¬ì¸íЏ", + "logtoEndpointDes": "애플리케ì´ì…˜ 관리 패ë„ì—서 ì–»ì€ Logto 엔드í¬ì¸íЏ 주소로, ìžì²´ ë°°í¬í•œ ì¸ìŠ¤í„´ìŠ¤ì¼ ìˆ˜ 있습니다.", + "logtoKey": "애플리케ì´ì…˜ 시í¬ë¦¿", + "logtoKeyDes": "애플리케ì´ì…˜ 관리 페ì´ì§€ì—서 ìƒì„±í•œ 애플리케ì´ì…˜ 시í¬ë¦¿", + "logtoAppIDDes": "ìƒì„±í•œ 애플리케ì´ì…˜ ID", + "logto": "Logto", + "logtoDes": "<0>Logto를 통해 Apple, GitHub, Microsoft Entra ID, Google, SMS 등 ë” ë§Žì€ ì œ3ìž í”Œëž«í¼ì˜ ìƒí˜¸ ì—°ê²° 로그ì¸ì„ 구현할 수 있습니다. Logto 관리 패ë„ì—서 \"전통ì ì¸ 웹 애플리케ì´ì…˜\"ì„ ìƒì„±í•˜ê³  <1>{{url}}ì„ \"리다ì´ë ‰íЏ URIs\"ì— ì¶”ê°€í•´ 주세요.", + "thirdPartySignIn": "ì œ3ìž ë¡œê·¸ì¸", + "logo": "로고", + "logoDes": "로고 ì´ë¯¸ì§€ì˜ 주소로, ë‹¤í¬ ëª¨ë“œì™€ ë¼ì´íЏ 모드ì—서 ê°ê° 다른 로고를 제공해 주세요.", + "dark": "ë‹¤í¬ ëª¨ë“œ", + "light": "ë¼ì´íЏ 모드", + "tosUrl": "ì´ìš©ì•½ê´€ ë§í¬", + "tosUrlDes": "ì‚¬ìš©ìž ë¡œê·¸ì¸ì´ë‚˜ ë“±ë¡ íŽ˜ì´ì§€ í•˜ë‹¨ì— í‘œì‹œë˜ë©°, 비어있으면 표시ë˜ì§€ 않습니다.", + "privacyUrl": "ê°œì¸ì •보처리방침 ë§í¬", + "privacyUrlDes": "ì‚¬ìš©ìž ë¡œê·¸ì¸ì´ë‚˜ ë“±ë¡ íŽ˜ì´ì§€ í•˜ë‹¨ì— í‘œì‹œë˜ë©°, 비어있으면 표시ë˜ì§€ 않습니다.", + "addSecondary": "ë³´ì¡° 사ì´íЏ URL 추가", + "secondarySiteURL": "ë³´ì¡°", + "secondaryDes": "다른 ë³´ì¡° 사ì´íЏ URLì„ ì¶”ê°€í•  ìˆ˜ë„ ìžˆìœ¼ë©°, Cloudreve는 사용ìžê°€ 실제로 접근하는 URLì— ë”°ë¼ ìžë™ìœ¼ë¡œ 사용 여부를 ì„ íƒí•©ë‹ˆë‹¤. 사ì´íЏ URLì€ ë¼ì´ì„ ìŠ¤ê°€ ë¶€ì—¬ëœ ë„ë©”ì¸ì´ì–´ì•¼ 합니다.", + "primarySiteURL": "기본", + "primarySiteURLDes": "기본 사ì´íЏ URLì€ ì™¸ë¶€ ì„œë¹„ìŠ¤ì™€ì˜ í†µì‹  ë° ì½œë°± 수신(예: ê²°ì œ, 저장소 제공업체)ì— ì‚¬ìš©ë˜ë¯€ë¡œ ê³µì¸ ë„¤íŠ¸ì›Œí¬ì—서 ì ‘ê·¼ 가능한 URLì„ ì‚¬ìš©í•´ 주세요.", + "revert": "변경 취소", + "saved": "ì„¤ì •ì´ ë³€ê²½ë¨", + "save": "저장", + "basicInformation": "기본 ì •ë³´", + "mainTitle": "사ì´íЏ ì´ë¦„", + "mainTitleDes": "사ì´íŠ¸ì˜ ì´ë¦„", + "siteDescription": "사ì´íЏ 설명", + "siteDescriptionDes": "사ì´íЏ 설명 정보로, 공유 페ì´ì§€ ìš”ì•½ì— í‘œì‹œë  ìˆ˜ 있습니다.", + "siteURL": "사ì´íЏ URL", + "customFooterHTML": "푸터 코드", + "customFooterHTMLDes": "페ì´ì§€ í•˜ë‹¨ì— ì‚½ìž…ë˜ëŠ” ì‚¬ìš©ìž ì •ì˜ HTML 코드", + "announcement": "사ì´íЏ 공지사항", + "announcementDes": "로그ì¸í•œ 사용ìžì—게 표시ë˜ëŠ” 공지사항으로, 비어있으면 표시ë˜ì§€ 않습니다. ì´ ë‚´ìš©ì´ ë³€ê²½ë˜ë©´ 모든 사용ìžê°€ ê³µì§€ì‚¬í•­ì„ ë‹¤ì‹œ 보게 ë©ë‹ˆë‹¤.", + "supportHTML": "HTML 코드 ì§€ì›", + "branding": "ì•„ì´ì½˜", + "smallIcon": "ìž‘ì€ ì•„ì´ì½˜", + "smallIconDes": "ìž‘ì€ ì•„ì´ì½˜ 주소로, ico ë˜ëŠ” svg 형ì‹ìž…니다. ì´ ì•„ì´ì½˜ì€ 브ë¼ìš°ì € 탭, ë¶ë§ˆí¬, ë°ìФí¬í†± 바로가기 등ì—ë„ ì‚¬ìš©ë©ë‹ˆë‹¤.", + "mediumIcon": "중간 ì•„ì´ì½˜", + "mediumIconDes": "192x192 í¬ê¸°ì˜ 중간 ì•„ì´ì½˜ 주소로, png 형ì‹", + "largeIcon": "í° ì•„ì´ì½˜", + "largeIconDes": "512x512 í¬ê¸°ì˜ í° ì•„ì´ì½˜ 주소로, png 형ì‹ìž…니다. ì´ ì•„ì´ì½˜ì€ iOS í´ë¼ì´ì–¸íЏì—서 사ì´íŠ¸ë¥¼ 전환할 ë•Œë„ ì‚¬ìš©ë©ë‹ˆë‹¤.", + "displayMode": "표시 모드", + "displayModeDes": "PWA 애플리케ì´ì…˜ 추가 í›„ì˜ í‘œì‹œ 모드", + "themeColor": "테마 색ìƒ", + "themeColorDes": "CSS ìƒ‰ìƒ ê°’ìœ¼ë¡œ, PWA 시작 í™”ë©´ì˜ ìƒíƒœ 표시줄, 콘í…츠 페ì´ì§€ì˜ ìƒíƒœ 표시줄, 주소 í‘œì‹œì¤„ì˜ ìƒ‰ìƒì— ì˜í–¥ì„ ì¤ë‹ˆë‹¤.", + "backgroundColor": "배경색", + "backgroundColorDes": "CSS ìƒ‰ìƒ ê°’", + "hint": "íŒ", + "webauthnNoHttps": "Web Authnì€ ì‚¬ì´íЏì—서 HTTPS를 활성화해야 하며, 매개변수 설정 - 사ì´íЏ ì •ë³´ - 사ì´íЏ URLì—ì„œë„ HTTPS를 사용하는지 확ì¸í•´ 주세요.", + "accountManagement": "ë“±ë¡ ë° ë¡œê·¸ì¸", + "allowNewRegistrations": "새 ì‚¬ìš©ìž ë“±ë¡ í—ˆìš©", + "allowNewRegistrationsDes": "비활성화하면 ë” ì´ìƒ 프론트엔드를 통해 새 사용ìžë¥¼ 등ë¡í•  수 없습니다.", + "emailActivation": "ì´ë©”ì¼ í™œì„±í™”", + "emailActivationDes": "활성화하면 새 ì‚¬ìš©ìž ë“±ë¡ ì‹œ ì´ë©”ì¼ì˜ 활성화 ë§í¬ë¥¼ í´ë¦­í•´ì•¼ 완료ë©ë‹ˆë‹¤. <0>ì´ë©”ì¼ ë°œì†¡ ì„¤ì •ì´ ì˜¬ë°”ë¥¸ì§€ 확ì¸í•´ 주세요. 그렇지 않으면 활성화 ì´ë©”ì¼ì´ 전달ë˜ì§€ 않습니다.", + "captchaForSignup": "ë“±ë¡ ì¸ì¦ 코드", + "captchaForSignupDes": "ë“±ë¡ í¼ ì¸ì¦ 코드를 활성화할지 여부", + "captchaForLogin": "ë¡œê·¸ì¸ ì¸ì¦ 코드", + "captchaForLoginDes": "ë¡œê·¸ì¸ í¼ ì¸ì¦ 코드를 활성화할지 여부", + "captchaForReset": "비밀번호 찾기 ì¸ì¦ 코드", + "captchaForResetDes": "비밀번호 찾기 í¼ ì¸ì¦ 코드를 활성화할지 여부", + "captchaForAbuseReport": "남용 ì‹ ê³  ì¸ì¦ 코드", + "captchaForAbuseReportDes": "남용 ì‹ ê³  í¼ ì¸ì¦ 코드를 활성화할지 여부", + "webauthnDes": "사용ìžê°€ ë°”ì¸ë”©ëœ 하드웨어 ì¸ì¦ 장치(얼굴, 지문 ë˜ëŠ” USB 키 등)를 사용한 로그ì¸ì„ 허용할지 여부. 사ì´íЏì—서 HTTPS를 활성화해야 사용할 수 있습니다.", + "webauthn": "패스키를 사용한 로그ì¸", + "defaultSymbolics": "초기 공유 바로가기", + "defaultSymbolicsDes": "새 사용ìžì˜ 루트 ë””ë ‰í† ë¦¬ì— ê¸°ë³¸ì ìœ¼ë¡œ 존재하는 공유 ë§í¬ 바로가기입니다. ìˆ«ìž ID로 공유 ë§í¬ë¥¼ 검색해 주세요. <0>공유 ëª©ë¡ ë§¨ 왼쪽ì—서 ìˆ«ìž ID를 확ì¸í•  수 있습니다.", + "searchShare": "공유 ID 검색...", + "defaultGroup": "기본 ì‚¬ìš©ìž ê·¸ë£¹", + "defaultGroupDes": "ì‚¬ìš©ìž ë“±ë¡ í›„ì˜ ì´ˆê¸° ì‚¬ìš©ìž ê·¸ë£¹", + "testMailSent": "테스트 ì´ë©”ì¼ì´ 전송ë˜ì—ˆìŠµë‹ˆë‹¤.", + "testSMTPSettings": "발송 테스트", + "testSMTPTooltip": "Cloudreve는 현재 SMTP ì„¤ì •ì„ ì‚¬ìš©í•˜ì—¬ 테스트 ì´ë©”ì¼ì„ 보냅니다. 테스트 ì „ì— ì„¤ì •ì„ ì €ìž¥í•  필요가 없습니다.", + "recipient": "ìˆ˜ì‹ ìž ì£¼ì†Œ", + "send": "전송", + "smtp": "발송", + "senderName": "ë°œì‹ ìž ì´ë¦„", + "senderNameDes": "ì´ë©”ì¼ì— 표시ë˜ëŠ” ë°œì‹ ìž ì´ë¦„", + "senderAddress": "ë°œì‹ ìž ì´ë©”ì¼", + "senderAddressDes": "발송 ì´ë©”ì¼ì˜ 주소", + "smtpServer": "SMTP 서버", + "smtpServerDes": "í¬íЏ 번호를 í¬í•¨í•˜ì§€ 않는 발송 서버 주소", + "smtpPort": "SMTP í¬íЏ", + "smtpPortDes": "발송 서버 주소 í¬íЏ 번호", + "smtpUsername": "SMTP 사용ìžëª…", + "smtpUsernameDes": "발송 ì´ë©”ì¼ ì‚¬ìš©ìžëª…으로, ì¼ë°˜ì ìœ¼ë¡œ ì´ë©”ì¼ ì£¼ì†Œì™€ ë™ì¼í•©ë‹ˆë‹¤.", + "smtpPassword": "SMTP 비밀번호", + "smtpPasswordDes": "발송 ì´ë©”ì¼ ë¹„ë°€ë²ˆí˜¸", + "replyToAddress": "회신 ì´ë©”ì¼", + "replyToAddressDes": "사용ìžê°€ 시스템ì—서 보낸 ì´ë©”ì¼ì— 회신할 때 íšŒì‹ ì„ ë°›ì„ ì´ë©”ì¼ ì£¼ì†Œ", + "enforceSSL": "SSL ì—°ê²° ê°•ì œ 사용", + "enforceSSLDes": "SSL 암호화 ì—°ê²°ì„ ê°•ì œë¡œ 사용할지 여부입니다. ì´ë©”ì¼ì„ 보낼 수 없는 경우 ì´ ì˜µì…˜ì„ ë¹„í™œì„±í™”í•  수 있으며, Cloudreve는 STARTTLS를 시ë„하고 암호화 ì—°ê²° 사용 여부를 결정합니다.", + "smtpTTL": "SMTP ì—°ê²° 유효 기간(ì´ˆ)", + "smtpTTLDes": "유효 기간 ë‚´ì— ì„¤ì •ëœ SMTP ì—°ê²°ì€ ìƒˆë¡œìš´ ì´ë©”ì¼ ë°œì†¡ ìš”ì²­ì— ìž¬ì‚¬ìš©ë©ë‹ˆë‹¤.", + "emailTemplates": "ì´ë©”ì¼ í…œí”Œë¦¿", + "activateNewUser": "새 ì‚¬ìš©ìž í™œì„±í™”", + "resetPassword": "비밀번호 재설정", + "sendTestEmail": "테스트 ì´ë©”ì¼ ë³´ë‚´ê¸°", + "transportation": "전송", + "workerNum": "Worker 수", + "workerNumDes": "마스터 노드 작업 íì—서 ë™ì‹œì— 실행ë˜ëŠ” 최대 작업 수로, Cloudreve를 재시작한 후 ì ìš©ë©ë‹ˆë‹¤.", + "tempFolder": "임시 디렉토리", + "tempFolderDes": "ì••ì¶• í•´ì œ, ì••ì¶• ë“±ì˜ ìž‘ì—…ìœ¼ë¡œ ìƒì„±ë˜ëŠ” 임시 파ì¼ì„ 저장하는 디렉토리 경로", + "textEditMaxSize": "문서 온ë¼ì¸ 편집 최대 í¬ê¸°", + "textEditMaxSizeDes": "문서 파ì¼ì´ 온ë¼ì¸ìœ¼ë¡œ 편집 가능한 최대 í¬ê¸°ë¡œ, ì´ í¬ê¸°ë¥¼ 초과하는 파ì¼ì€ 온ë¼ì¸ìœ¼ë¡œ 편집할 수 없습니다. ì´ ì„¤ì •ì€ ìˆœìˆ˜ í…스트 파ì¼, 코드 파ì¼, Office 문서(WOPI) ë“±ì˜ ì›¹ 온ë¼ì¸ íŽ¸ì§‘ê¸°ì— ì ìš©ë©ë‹ˆë‹¤.", + "resetConnection": "업로드 ê²€ì¦ ì‹¤íŒ¨ 시 ê°•ì œ ì—°ê²° 재설정", + "resetConnectionDes": "활성화하면 ì •ì±…, 프로필 사진 ë“±ì˜ ë°ì´í„° 업로드 ê²€ì¦ì´ 실패할 때 서버가 강제로 ì—°ê²°ì„ ìž¬ì„¤ì •í•©ë‹ˆë‹¤.", + "batchDownload": "패키지 다운로드", + "previewURL": "미리보기 ë§í¬", + "cannotDeleteDefaultTheme": "기본 ìƒ‰ìƒ êµ¬ì„±ì€ ì‚­ì œí•  수 없습니다.", + "themeConfig": "ìƒ‰ìƒ êµ¬ì„±", + "actions": "작업", + "wrongFormat": "형ì‹ì´ 올바르지 않습니다.", + "avatar": "프로필 사진", + "gravatarServer": "Gravatar 서버", + "gravatarServerDes": "Gravatar 서버 주소로, êµ­ë‚´ 미러를 ì„ íƒí•  수 있습니다.", + "avatarFilePath": "프로필 사진 저장 경로", + "avatarFilePathDes": "사용ìžê°€ 업로드한 ì‚¬ìš©ìž ì •ì˜ í”„ë¡œí•„ ì‚¬ì§„ì˜ ì €ìž¥ 경로로, Cloudreve ë°ì´í„° ë””ë ‰í† ë¦¬ì— ìƒëŒ€ì ìž…니다.", + "avatarSize": "프로필 사진 íŒŒì¼ í¬ê¸° 제한", + "avatarSizeDes": "사용ìžê°€ 업로드할 수 있는 프로필 사진 파ì¼ì˜ 최대 í¬ê¸°", + "avatarImageSize": "ì´ë¯¸ì§€ í¬ê¸° (px)", + "avatarImageSizeDes": "사용ìžê°€ 업로드한 프로필 ì‚¬ì§„ì´ ì£¼ì–´ì§„ í¬ê¸°ë¡œ ì¡°ì •ë˜ë©°, 단위는 픽셀입니다.", + "filePreview": "íŒŒì¼ ë¯¸ë¦¬ë³´ê¸°", + "thumbnails": "ì¸ë„¤ì¼", + "thumbnailDoc": "ì¸ë„¤ì¼ êµ¬ì„±ì— ëŒ€í•œ ìžì„¸í•œ 정보는 <0>ê³µì‹ ë¬¸ì„œë¥¼ 참조해 주세요.", + "thumbnailDocLink": "https://docs.cloudreve.org/usage/thumbnails", + "thumbnailBasic": "기본 설정", + "generators": "ì¸ë„¤ì¼ ìƒì„±ê¸°", + "thumbMaxSize": "최대 ì›ë³¸ íŒŒì¼ í¬ê¸°", + "thumbMaxSizeDes": "ì¸ë„¤ì¼ì„ ìƒì„±í•  수 있는 최대 ì›ë³¸ íŒŒì¼ í¬ê¸°ë¡œ, ì´ í¬ê¸°ë¥¼ 초과하는 파ì¼ì€ ì¸ë„¤ì¼ì„ ìƒì„±í•˜ì§€ 않습니다.", + "generatorProxyWarning": "기본ì ìœ¼ë¡œ 비로컬 저장소 ì •ì±…ì€ \"저장소 ì •ì±… 네ì´í‹°ë¸Œ\" ìƒì„±ê¸°ë§Œ 사용합니다. 저장소 ì •ì±… 설정 페ì´ì§€ì—서 \"ìƒì„±ê¸° 프ë¡ì‹œ\" ê¸°ëŠ¥ì„ í™œì„±í™”í•˜ì—¬ ì œ3ìž ì €ìž¥ì†Œ ì •ì±…ì˜ ì¸ë„¤ì¼ ê¸°ëŠ¥ì„ í™•ìž¥í•  수 있습니다. ìžì„¸í•œ ë‚´ìš©ì€ <0>ê³µì‹ ë¬¸ì„œë¥¼ 참조해 주세요.", + "policyBuiltin": "저장소 ì •ì±… 네ì´í‹°ë¸Œ", + "policyBuiltinDes": "저장소 ì œê³µì—…ì²´ì˜ ë„¤ì´í‹°ë¸Œ ì´ë¯¸ì§€ 처리 ì¸í„°íŽ˜ì´ìŠ¤ë¥¼ 사용합니다. 로컬 ë° S3 ì •ì±…ì˜ ê²½ìš° ì´ ìƒì„±ê¸°ë¥¼ 사용할 수 없으며 다른 ìƒì„±ê¸°ë¡œ ìžë™ 대체ë©ë‹ˆë‹¤. 다른 저장소 ì •ì±…ì˜ ê²½ìš° 저장소 ì •ì±… 설정 페ì´ì§€ì—서 허용ë˜ëŠ” 확장ìžë¥¼ 설정해 주세요.", + "cloudreveBuiltin": "Cloudreve 내장", + "cloudreveBuiltinDes": "Cloudreve 내장 ì´ë¯¸ì§€ 처리 ê¸°ëŠ¥ì„ ì‚¬ìš©í•˜ë©°, PNG, JPEG, GIF 형ì‹ì˜ ì´ë¯¸ì§€ë§Œ ì§€ì›í•©ë‹ˆë‹¤.", + "libreOffice": "LibreOffice", + "libreOfficeDes": "LibreOffice를 사용하여 Office ë¬¸ì„œì˜ ì¸ë„¤ì¼ì„ ìƒì„±í•©ë‹ˆë‹¤. ì´ ìƒì„±ê¸°ëŠ” 다른 ì´ë¯¸ì§€ ìƒì„±ê¸°(Cloudreve 내장 ë˜ëŠ” VIPS) 중 í•˜ë‚˜ì— ì˜ì¡´í•©ë‹ˆë‹¤.", + "libraw": "LibRaw / DCRaw", + "librawDes": "LibRawì— í¬í•¨ëœ DCRaw ì—뮬레ì´ì…˜ 루틴 ë˜ëŠ” ì›ë³¸ DCRaw í”„ë¡œê·¸ëž¨ì„ ì‚¬ìš©í•˜ì—¬ RAW ì´ë¯¸ì§€ì˜ ì¸ë„¤ì¼ì„ ìƒì„±í•©ë‹ˆë‹¤.", + "vips": "VIPS", + "vipsDes": "libvips를 사용하여 ì¸ë„¤ì¼ ì´ë¯¸ì§€ë¥¼ 처리하며, ë” ë§Žì€ ì´ë¯¸ì§€ 형ì‹ì„ ì§€ì›í•˜ê³  리소스 소비가 ì ìŠµë‹ˆë‹¤.", + "thumbDependencyWarning": "LibreOffice ë˜ëŠ” 앨범 커버 ìƒì„±ê¸°ëŠ” Cloudreve 내장 ë˜ëŠ” VIPS ìƒì„±ê¸°ì— ì˜ì¡´í•˜ë¯€ë¡œ 둘 중 하나를 활성화해 주세요.", + "ffmpeg": "FFmpeg", + "ffmpegDes": "FFmpeg를 사용하여 비디오 ì¸ë„¤ì¼ì„ ìƒì„±í•©ë‹ˆë‹¤.", + "executable": "실행 파ì¼", + "executableDes": "ì œ3ìž ìƒì„±ê¸° 실행 파ì¼ì˜ 경로 ë˜ëŠ” 명령어", + "executableTest": "테스트", + "executableTestSuccess": "ìƒì„±ê¸°ê°€ ì •ìƒì ìœ¼ë¡œ ìž‘ë™í•©ë‹ˆë‹¤. 버전: {{version}}", + "generatorExts": "사용 가능한 확장ìž", + "generatorExtsDes": "ì´ ìƒì„±ê¸°ì—서 사용 가능한 íŒŒì¼ í™•ìž¥ìž ëª©ë¡ìœ¼ë¡œ, 여러 개는 ë°˜ê° ì‰¼í‘œ ,로 구분해 주세요.", + "ffmpegSeek": "ì¸ë„¤ì¼ 캡처 위치", + "ffmpegSeekDes": "ì¸ë„¤ì¼ 캡처 ì‹œê°„ì„ ì •ì˜í•©ë‹ˆë‹¤. ìƒì„± 프로세스를 ê°€ì†í™”하기 위해 ìž‘ì€ ê°’ì„ ì„ íƒí•˜ëŠ” ê²ƒì´ ì¢‹ìŠµë‹ˆë‹¤. ë¹„ë””ì˜¤ì˜ ì‹¤ì œ 길ì´ë¥¼ 초과하면 ì¸ë„¤ì¼ 캡처가 실패합니다.", + "ffmpegExtraArgs": "추가 ìž…ë ¥ 매개변수", + "ffmpegExtraArgsDes": "FFmpeg를 호출할 때 추가로 ìž…ë ¥ë˜ëŠ” 매개변수", + "generatorProxy": "ìƒì„±ê¸° 프ë¡ì‹œ", + "enableThumbProxy": "ìƒì„±ê¸° 프ë¡ì‹œ 사용", + "proxyPolicyList": "프ë¡ì‹œë¥¼ 시작하는 저장소 ì •ì±…", + "proxyPolicyListDes": "다중 ì„ íƒ ê°€ëŠ¥. ì„ íƒí•˜ë©´ 저장소 ì •ì±…ì—서 네ì´í‹°ë¸Œ ì¸ë„¤ì¼ ìƒì„±ì„ ì§€ì›í•˜ì§€ 않는 ìœ í˜•ì€ Cloudreveì—서 프ë¡ì‹œ ìƒì„±ë©ë‹ˆë‹¤.", + "thumbWidth": "최대 너비", + "thumbHeight": "최대 높ì´", + "thumbSuffix": "Blob íŒŒì¼ ì ‘ë¯¸ì‚¬", + "thumbSuffixDes": "ìƒì„±ëœ ì¸ë„¤ì¼ Blobì´ ì›ë³¸ Blobì— ëŒ€í•´ 추가ë˜ëŠ” 접미사", + "thumbFormat": "ì¸ë„¤ì¼ 형ì‹", + "thumbFormatDes": "ìš°ì„  사용할 ì¸ë„¤ì¼ 형ì‹ìœ¼ë¡œ, ìƒì„±ê¸°ì—서 ì§€ì›í•˜ì§€ 않으면 ìžë™ìœ¼ë¡œ jpg 형ì‹ìœ¼ë¡œ 다운그레ì´ë“œë©ë‹ˆë‹¤.", + "thumbQuality": "ì´ë¯¸ì§€ 품질", + "thumbQualityDes": "ì••ì¶• 품질 백분율로, jpg ë° webp ì¸ì½”딩ì—ë§Œ 유효합니다.", + "thumbGC": "ìƒì„± 완료 후 즉시 메모리 회수", + "captcha": "ì¸ì¦ 코드", + "captchaType": "ì¸ì¦ 코드 유형", + "captchaTypeDes": "ì¸ì¦ 코드 유형 ë° ì¸ì¦ 코드 서비스 제공업체를 ì„ íƒí•©ë‹ˆë‹¤.", + "plainCaptcha": "그래픽", + "reCaptchaV2": "reCAPTCHA V2", + "turnstile": "Cloudflare Turnstile", + "turnstileSiteKey": "사ì´íЏ 키", + "turnstileSiteKSecret": "시í¬ë¦¿", + "cap": "Cap", + "capInstanceURL": "ì¸ìŠ¤í„´ìŠ¤ URL", + "capInstanceURLDes": "ìžì²´ ë°°í¬í•œ Cap ì„œë²„ì˜ URL 주소입니다. ìžì„¸í•œ 정보는 <0>ìžì²´ ë°°í¬ ëª¨ë“œ 문서를 참조해 주세요.", + "capSiteKey": "사ì´íЏ 키", + "capSiteKeyDes": "Cap 서버 제어íŒì—서 ì–»ì€ ì‚¬ì´íЏ 키", + "capSecretKey": "비밀 키", + "capSecretKeyDes": "Cap 서버 제어íŒì—서 ì–»ì€ ë¹„ë°€ 키", + "capAssetServer": "ì •ì  ë¦¬ì†ŒìŠ¤ 서비스 소스", + "capAssetServerDes": "Cap ì¸ì¦ 코드 ì •ì  ë¦¬ì†ŒìŠ¤ì˜ ë¡œë”© 소스를 ì„ íƒí•©ë‹ˆë‹¤. ìžì²´ ë°°í¬ ì„œë²„ë¥¼ 사용하려면 서버 측ì—서 환경 변수를 설정해야 합니다. <0>ì •ì  ë¦¬ì†ŒìŠ¤ 서비스 활성화를 참조해 주세요.", + "capAssetServerJsdelivr": "jsDelivr CDN", + "capAssetServerUnpkg": "unpkg CDN", + "capAssetServerInstance": "ìžì²´ ë°°í¬ ì„œë²„", + "captchaProvider": "ì¸ì¦ 코드 유형", + "captchaWidth": "너비", + "captchaHeight": "높ì´", + "captchaLength": "길ì´", + "captchaLengthDes": "캡차 ë‚´ 문ìžì˜ 길ì´ìž…니다.", + "captchaMode": "모드", + "captchaModeNumber": "숫ìž", + "captchaModeLetter": "문ìž", + "captchaModeMath": "수학", + "captchaModeNumberLetter": "숫ìž+문ìž", + "captchaElement": "ì¸ì¦ ì½”ë“œì˜ í˜•íƒœ", + "complexOfNoiseText": "ê°„ì„­ í…스트 ê°•í™”", + "complexOfNoiseDot": "ê°„ì„­ì  ê°•í™”", + "showHollowLine": "빈 ì„  사용", + "showNoiseDot": "ë…¸ì´ì¦ˆ ì  ì‚¬ìš©", + "showNoiseText": "ê°„ì„­ í…스트 사용", + "showSlimeLine": "물결선 사용", + "showSineLine": "사ì¸íŒŒ 사용", + "siteKey": "Site KEY", + "siteKeyDes": "<0>애플리케ì´ì…˜ 관리 페ì´ì§€ì—서 ì–»ì€ ì›¹ì‚¬ì´íЏ 키", + "siteSecret": "Secret", + "siteSecretDes": "<0>애플리케ì´ì…˜ 관리 페ì´ì§€ì—서 ì–»ì€ ë¹„ë°€ 키", + "secretID": "SecretId", + "secretIDDes": "<0>액세스 키 페ì´ì§€ì—서 ì–»ì€ SecretId", + "secretKey": "SecretKey", + "secretKeyDes": "<0>액세스 키 페ì´ì§€ì—서 ì–»ì€ SecretKey", + "tCaptchaAppID": "APPID", + "tCaptchaAppIDDes": "<0>그래픽 ê²€ì¦ íŽ˜ì´ì§€ì—서 ì–»ì€ APPID", + "tCaptchaSecretKey": "App Secret Key", + "tCaptchaSecretKeyDes": "<0>그래픽 ê²€ì¦ íŽ˜ì´ì§€ì—서 ì–»ì€ App Secret Key", + "staticResourceCache": "ì •ì  ê³µìš© 리소스 ìºì‹œ", + "staticResourceCacheDes": "공개ì ìœ¼ë¡œ ì ‘ê·¼ 가능한 ì •ì  ë¦¬ì†ŒìŠ¤(예: 로컬 ì •ì±… ì§ì ‘ ë§í¬, íŒŒì¼ ë‹¤ìš´ë¡œë“œ ë§í¬)ì˜ ìºì‹œ 유효 기간", + "creditSystem": "í¬ì¸íЏ 시스템", + "creditAndVAS": "í¬ì¸íЏ ë° ë¶€ê°€ 서비스", + "enableCredit": "í¬ì¸íЏ 시스템 활성화", + "enableCreditDes": "í¬ì¸íЏ ì‹œìŠ¤í…œì„ í™œì„±í™”í•˜ì—¬ 사용ìžê°€ 공유 ë§í¬ì— ê°€ê²©ì„ ì„¤ì •í•  수 있ë„ë¡ í—ˆìš©í•©ë‹ˆë‹¤.", + "creditPrice": "í¬ì¸íЏ 가격", + "creditPriceDes": "í™”í로 í¬ì¸íŠ¸ë¥¼ 충전하는 가격(최소 í™”í 단위)으로, 0ì„ ìž…ë ¥í•˜ë©´ í¬ì¸íЏ ì¶©ì „ì„ ê¸ˆì§€í•©ë‹ˆë‹¤.", + "shareScoreRate": "ê³µìœ ìž ìˆ˜ìˆ˜ë£Œ 비율", + "shareScoreRateDes": "공유 ë§í¬ê°€ êµ¬ë§¤ë  ë•Œ 공유ìžê°€ 받는 í¬ì¸íЏ 백분율(1-100)", + "cronNotifyUser": "초과 ì‚¬ìš©ìž ì•Œë¦¼ 스캔 간격", + "cronNotifyUserDes": "초과 사용ìžë¥¼ 스캔하고 ì´ë©”ì¼ ì•Œë¦¼ì„ ë³´ë‚´ëŠ” 주기", + "cronBanUser": "ì‚¬ìš©ìž ì°¨ë‹¨ 스캔 간격", + "cronBanUserDes": "저장소를 초과하고 ë²„í¼ ê¸°ê°„ì„ ì´ˆê³¼í•œ 사용ìžë¥¼ 스캔하고 차단하는 주기", + "anonymousPurchase": "ìµëª… 구매", + "anonymousPurchaseDes": "로그ì¸í•˜ì§€ ì•Šì€ ì‚¬ìš©ìžê°€ 공유 ë§í¬ë¥¼ ì§ì ‘ 구매할 수 있ë„ë¡ í—ˆìš©í•©ë‹ˆë‹¤.", + "shopNavEnabled": "ìƒì  네비게ì´ì…˜ 표시", + "shopNavEnabledDes": "사ì´ë“œë°” 네비게ì´ì…˜ì—서 \"ìƒì \" í•­ëª©ì„ í‘œì‹œí•©ë‹ˆë‹¤.", + "paymentSettings": "ê²°ì œ 설정", + "currencyCode": "í™”í 코드", + "currencyCodeDes": "3ê¸€ìž í™”í 코드 (예: USD, CNY, EUR)", + "currencySymbol": "í™”í 기호", + "currencySymbolDes": "표시할 í™”í 기호 (예: $, Â¥, €)", + "currencyUnit": "í™”í 단위", + "currencyUnitDes": "최소 í™”í 단위 (예: 달러/ì„¼íŠ¸ì˜ ê²½ìš° 100)", + "paymentProviders": "ê²°ì œ 제공업체", + "providerName": "제공업체 ì´ë¦„으로, 사용ìžì—게 표시하는 ë° ì‚¬ìš©ë©ë‹ˆë‹¤.", + "providerType": "제공업체 유형", + "providerKey": "키", + "selectCurrency": "ì¼ë°˜ í™”í ì„ íƒ", + "addPaymentProvider": "ê²°ì œ 제공업체 추가", + "stripeProvider": "Stripe", + "weixinProvider": "위챗페ì´", + "alipayProvider": "알리페ì´", + "customProvider": "ì‚¬ìš©ìž ì •ì˜ ê²°ì œ 채ë„", + "customProviderDes": "Cloudreve 호환 ê²°ì œ ì¸í„°íŽ˜ì´ìŠ¤ë¥¼ 구현하여 다른 ì œ3ìž ê²°ì œ 플랫í¼ê³¼ ì—°ë™í•  수 있습니다. ìžì„¸í•œ ë‚´ìš©ì€ <0>ê³µì‹ ë¬¸ì„œë¥¼ 참조해 주세요.", + "providerKeyDes": "Stripeì˜ API 키를 입력해 주세요.", + "storageProductSettings": "저장소 제품", + "storageProductsDes": "사용ìžê°€ 저장 ê³µê°„ì„ í™•ìž¥í•˜ê¸° 위해 구매할 수 있는 ì œí’ˆì„ êµ¬ì„±í•©ë‹ˆë‹¤.", + "addStorageProduct": "제품 추가", + "editStorageProduct": "제품 편집", + "storageSize": "저장 í¬ê¸°", + "storageSizeBytes": "ì´ ì œí’ˆì— í¬í•¨ëœ 저장 í¬ê¸°", + "duration": "기간", + "durationSeconds": "기간(ì´ˆ, 예: 2592000ì€ 30ì¼ì„ ì˜ë¯¸)", + "price": "가격", + "priceInUnits": "가격(최소 í™”í 단위)", + "priceInUnitsDes": "ê°€ê²©ì€ ë‹¤ìŒê³¼ ê°™ì´ í‘œì‹œë©ë‹ˆë‹¤:", + "chipLabel": "ë¼ë²¨(ì„ íƒì‚¬í•­)", + "chipLabelHelp": "제품명 ì˜†ì— í‘œì‹œë˜ëŠ” ì§§ì€ í…스트 ë¼ë²¨", + "usePoints": "í¬ì¸íЏ 사용 허용", + "points": "í¬ì¸íЏ", + "pointsHelp": "ì´ ì œí’ˆì„ êµ¬ë§¤í•˜ëŠ” ë° í•„ìš”í•œ í¬ì¸íЏ 수", + "pointsUnit": "í¬ì¸íЏ", + "groupProductSettings": "ì‚¬ìš©ìž ê·¸ë£¹ 제품", + "groupProductsDes": "사용ìžê°€ 특정 ì‚¬ìš©ìž ê·¸ë£¹ì— ê°€ìž…í•˜ê¸° 위해 구매할 수 있는 ì œí’ˆì„ êµ¬ì„±í•©ë‹ˆë‹¤.", + "addGroupProduct": "ì‚¬ìš©ìž ê·¸ë£¹ 제품 추가", + "editGroupProduct": "ì‚¬ìš©ìž ê·¸ë£¹ 제품 편집", + "groupId": "ì‚¬ìš©ìž ê·¸ë£¹ ID", + "groupIdHelp": "ì´ ì œí’ˆì„ êµ¬ë§¤í•œ 후 업그레ì´ë“œí•  ì‚¬ìš©ìž ê·¸ë£¹", + "description": "설명", + "descriptionHelp": "특징ì´ë‚˜ 혜íƒì„ 입력하세요. 한 ì¤„ì— í•˜ë‚˜ì”©", + "receiptEmailTemplate": "ê²°ì œ ì˜ìˆ˜ì¦ 템플릿", + "receiptEmailTemplateDes": "결제가 확ì¸ë  때 사용ìžì—게 보내는 ì´ë©”ì¼ í…œí”Œë¦¿", + "activationEmailTemplate": "계정 활성화 템플릿", + "activationEmailTemplateDes": "사용ìžê°€ ê³„ì •ì„ í™œì„±í™”í•  때 보내는 ì´ë©”ì¼ í…œí”Œë¦¿", + "quotaExceededEmailTemplate": "저장 할당량 초과 템플릿", + "quotaExceededEmailTemplateDes": "사용ìžê°€ 저장 í• ë‹¹ëŸ‰ì„ ì´ˆê³¼í•  때 보내는 ì´ë©”ì¼ í…œí”Œë¦¿", + "resetPasswordEmailTemplate": "비밀번호 재설정 템플릿", + "resetPasswordEmailTemplateDes": "사용ìžê°€ 비밀번호 ìž¬ì„¤ì •ì„ ìš”ì²­í•  때 보내는 ì´ë©”ì¼ í…œí”Œë¦¿", + "preferredLanguage": "선호 언어", + "setAsPreferredLanguage": "선호 언어로 설정", + "setAsPreferredLanguageDes": "사용ìžì˜ 언어 선호ë„를 가져올 수 없는 경우 선호 ì–¸ì–´ì˜ ì´ë©”ì¼ í…œí”Œë¦¿ì´ ì‚¬ìš©ë©ë‹ˆë‹¤.", + "alreadyAsPreferredLanguageDes": "현재 언어가 선호 언어로 설정ë˜ì–´ 있습니다. 사용ìžì˜ 언어 선호ë„를 가져올 수 없는 경우 ì´ ì´ë©”ì¼ í…œí”Œë¦¿ì´ ì‚¬ìš©ë©ë‹ˆë‹¤.", + "addLanguage": "언어 추가", + "removeLanguage": "언어 제거", + "removeLanguageBtn": "언어 제거", + "cannotRemovePreferredLanguageDes": "선호 언어는 제거할 수 없습니다. 다른 언어를 선호 언어로 설정한 후 다시 시ë„í•´ 주세요.", + "languageCodeDes": "추가할 언어를 ì„ íƒí•´ 주세요.", + "emailSubject": "ì´ë©”ì¼ ì œëª©", + "emailSubjectDes": "ì´ë©”ì¼ì˜ 제목. <0>ë§¤ì§ ë³€ìˆ˜ë¥¼ 사용하여 ì‚¬ìš©ìž ì •ì˜í•  수 있습니다.", + "emailBody": "ì´ë©”ì¼ ë‚´ìš©", + "emailBodyDes": "ì´ë©”ì¼ì˜ 내용입니다. <0>ë§¤ì§ ë³€ìˆ˜ë¥¼ 사용하여 ì´ë©”ì¼ ë‚´ìš©ì„ ì‚¬ìš©ìž ì •ì˜í•  수 있습니다.", + "orderTitle": "주문 제목", + "themeOptions": "테마 옵션", + "themeOptionsDes": "사ì´íŠ¸ì˜ ì‚¬ìš©ìž ì •ì˜ í…Œë§ˆ ì˜µì…˜ì„ êµ¬ì„±í•©ë‹ˆë‹¤. ì´ëŸ¬í•œ 테마는 사용ìžê°€ 환경 설정ì—서 ì„ íƒí•  수 있습니다.", + "primaryColor": "기본 색ìƒ", + "secondaryColor": "ë³´ì¡° 색ìƒ", + "primaryColorDark": "기본 색ìƒ(ë‹¤í¬ ëª¨ë“œ)", + "secondaryColorDark": "ë³´ì¡° 색ìƒ(ë‹¤í¬ ëª¨ë“œ)", + "addThemeOption": "테마 옵션 추가", + "editThemeOption": "테마 옵션 편집", + "invalidThemeConfig": "유효하지 ì•Šì€ í…Œë§ˆ 구성입니다. JSON êµ¬ë¬¸ì„ í™•ì¸í•´ 주세요.", + "themeConfiguration": "테마 구성", + "themePreview": "테마 미리보기", + "lightTheme": "ë°ì€ 테마", + "darkTheme": "ì–´ë‘ìš´ 테마", + "previewTitle": "미리보기 제목", + "previewTextField": "ìž…ë ¥ 필드", + "previewPrimary": "기본 색ìƒ", + "invalidThemePreview": "유효하지 ì•Šì€ í…Œë§ˆ 구성으로 미리보기할 수 없습니다.", + "duplicateThemeColor": "ì´ ê¸°ë³¸ 색ìƒì„ 사용하는 테마가 ì´ë¯¸ 존재합니다. 다른 색ìƒì„ ì„ íƒí•´ 주세요.", + "themeDes": "ì „ì²´ 구성 가능한 í•­ëª©ì€ <0>Material-UI Default theme viewer를 참조해 주세요.", + "defaultTheme": "기본값", + "auditLog": "ì´ë²¤íЏ", + "auditLogDes": "ì–´ë–¤ ì´ë²¤íŠ¸ë¥¼ 기ë¡í• ì§€ 구성합니다. ì¼ë¶€ ì´ë²¤íŠ¸ëŠ” íŒŒì¼ í™œë™ ë° ë¡œê·¸ì¸ í™œë™ê³¼ ê°™ì€ ì¶”ê°€ ê¸°ëŠ¥ì„ ì œê³µí•˜ê¸° 위해 시스템ì—서 ì‚¬ìš©ë  ìˆ˜ 있습니다.", + "systemEvents": "시스템 ì´ë²¤íЏ", + "systemEventsDes": "시스템 작업 ë° ìƒíƒœì™€ ê´€ë ¨ëœ ì´ë²¤íЏ", + "userEvents": "ì‚¬ìš©ìž ì´ë²¤íЏ", + "userEventsDes": "ì‚¬ìš©ìž ê³„ì •, ì¸ì¦ ë° í”„ë¡œí•„ 변경과 ê´€ë ¨ëœ ì´ë²¤íЏ", + "fileEvents": "íŒŒì¼ ì´ë²¤íЏ", + "fileEventsDes": "업로드, 다운로드, 수정과 ê°™ì€ íŒŒì¼ ìž‘ì—…ê³¼ ê´€ë ¨ëœ ì´ë²¤íЏ", + "shareEvents": "공유 ì´ë²¤íЏ", + "shareEventsDes": "íŒŒì¼ ê³µìœ  ë° ë§í¬ 접근과 ê´€ë ¨ëœ ì´ë²¤íЏ", + "versionEvents": "버전 ì´ë²¤íЏ", + "versionEventsDes": "íŒŒì¼ ë²„ì „ 관리와 ê´€ë ¨ëœ ì´ë²¤íЏ", + "mediaEvents": "미디어 ì´ë²¤íЏ", + "mediaEventsDes": "ì¸ë„¤ì¼ ìƒì„±ê³¼ ê°™ì€ ë¯¸ë””ì–´ íŒŒì¼ ì²˜ë¦¬ì™€ ê´€ë ¨ëœ ì´ë²¤íЏ", + "filesystemEvents": "íŒŒì¼ ì‹œìŠ¤í…œ ì´ë²¤íЏ", + "filesystemEventsDes": "마운트 ë° ì•„ì¹´ì´ë¸Œ 처리와 ê°™ì€ íŒŒì¼ ì‹œìŠ¤í…œ 작업과 ê´€ë ¨ëœ ì´ë²¤íЏ", + "webdavEvents": "WebDAV ì´ë²¤íЏ", + "webdavEventsDes": "WebDAV 계정 관리 ë° ì ‘ê·¼ê³¼ ê´€ë ¨ëœ ì´ë²¤íЏ", + "paymentEvents": "ê²°ì œ ì´ë²¤íЏ", + "paymentEventsDes": "ê²°ì œ 거래 ë° ì²˜ë¦¬ì™€ ê´€ë ¨ëœ ì´ë²¤íЏ", + "emailEvents": "ì´ë©”ì¼ ì´ë²¤íЏ", + "emailEventsDes": "ì´ë©”ì¼ ë°œì†¡ ë° ì•Œë¦¼ê³¼ ê´€ë ¨ëœ ì´ë²¤íЏ", + "toggleAll": "모든 ì´ë²¤íЏ 활성화/비활성화", + "toggleAllDes": "ì´ ì¹´í…Œê³ ë¦¬ì˜ ëª¨ë“  ì´ë²¤íŠ¸ë¥¼ 활성화하거나 비활성화합니다.", + "event": { + "file_imported": "외부 íŒŒì¼ ê°€ì ¸ì˜¤ê¸°", + "server_start": "서버 시작", + "user_signup": "ì‚¬ìš©ìž ë“±ë¡", + "email_sent": "ì´ë©”ì¼ ë°œì†¡", + "user_activated": "ì‚¬ìš©ìž í™œì„±í™”", + "user_login_failed": "ë¡œê·¸ì¸ ì‹¤íŒ¨", + "user_login": "ì‚¬ìš©ìž ë¡œê·¸ì¸", + "user_token_refresh": "í† í° ìƒˆë¡œê³ ì¹¨", + "file_create": "íŒŒì¼ ìƒì„±", + "file_rename": "íŒŒì¼ ì´ë¦„ 변경", + "set_file_permission": "권한 변경", + "entity_uploaded": "íŒŒì¼ ì—…ë¡œë“œ ë˜ëŠ” ì—…ë°ì´íЏ", + "entity_downloaded": "íŒŒì¼ ë‹¤ìš´ë¡œë“œ", + "copy_from": "복사 ì›ë³¸", + "copy_to": "복사 대ìƒ", + "move_to": "ì´ë™ 대ìƒ", + "delete_file": "íŒŒì¼ ì‚­ì œ", + "move_to_trash": "휴지통으로 ì´ë™", + "share": "공유 ìƒì„±", + "share_link_viewed": "공유 ë§í¬ 조회", + "set_current_version": "현재 버전 설정", + "delete_version": "버전 ì‚­ì œ", + "thumb_generated": "ì¸ë„¤ì¼ ìƒì„±", + "live_photo_uploaded": "Live Photo 업로드", + "update_metadata": "메타ë°ì´í„° ì—…ë°ì´íЏ", + "edit_share": "공유 편집", + "delete_share": "공유 ì‚­ì œ", + "mount": "마운트", + "relocate": "저장소 ì •ì±… ì´ì „", + "create_archive": "ì•„ì¹´ì´ë¸Œ ìƒì„±", + "extract_archive": "ì•„ì¹´ì´ë¸Œ ì••ì¶• í•´ì œ", + "webdav_login_failed": "WebDAV ë¡œê·¸ì¸ ì‹¤íŒ¨", + "webdav_account_create": "WebDAV 계정 ìƒì„±", + "webdav_account_update": "WebDAV 계정 ì—…ë°ì´íЏ", + "webdav_account_delete": "WebDAV 계정 ì‚­ì œ", + "payment_created": "ê²°ì œ ìƒì„±", + "points_change": "í¬ì¸íЏ 변경", + "payment_paid": "ê²°ì œ 완료", + "payment_fulfilled": "주문 ì´í–‰", + "payment_fulfill_failed": "주문 ì´í–‰ 실패", + "storage_added": "저장소 확장", + "group_changed": "ì‚¬ìš©ìž ê·¸ë£¹ 변경", + "user_exceed_quota_notified": "할당량 초과 알림", + "user_changed": "ì‚¬ìš©ìž ìƒíƒœ 변경", + "get_direct_link": "ì§ì ‘ ë§í¬ 가져오기", + "link_account": "외부 계정 ì—°ê²°", + "unlink_account": "외부 계정 ì—°ê²° í•´ì œ", + "change_nick": "닉네임 변경", + "change_avatar": "프로필 사진 변경", + "membership_unsubscribe": "êµ¬ë… ì·¨ì†Œ", + "change_password": "비밀번호 변경", + "enable_2fa": "2FA 활성화", + "disable_2fa": "2FA 비활성화", + "add_passkey": "패스키 추가", + "remove_passkey": "패스키 제거", + "redeem_gift_code": "기프트 코드 사용", + "update_view": "보기 설정 변경", + "delete_direct_link": "ì§ì ‘ ë§í¬ ì‚­ì œ", + "report_abuse": "남용 ì‹ ê³ " + }, + "server": "서버 설정", + "tempPath": "임시 경로", + "tempPathDes": "임시 파ì¼ì„ 저장하는 디렉토리로, Cloudreve ë°ì´í„° ë””ë ‰í† ë¦¬ì— ìƒëŒ€ì ìž…니다. 수정하기 ì „ì— ì§„í–‰ ì¤‘ì¸ í ìž‘ì—…ì´ ì—†ëŠ”ì§€ 확ì¸í•´ 주세요.", + "siteID": "사ì´íЏ ID", + "siteIDDes": "사ì´íŠ¸ë¥¼ ì‹ë³„하는 고유 ID로, ì¼ë°˜ì ìœ¼ë¡œ 수정할 필요가 없습니다.", + "siteSecretKey": "마스터 키", + "siteSecretKeyDes": "ì‚¬ìš©ìž í† í°, 서명 ì•”í˜¸í™”ì— ì‚¬ìš©ë˜ëŠ” 마스터 키입니다. 순환 후 모든 ì‚¬ìš©ìž í† í°, ì„œëª…ì´ ë¬´íš¨í™”ë©ë‹ˆë‹¤. Cloudreve를 재시작한 후 ì ìš©ë©ë‹ˆë‹¤.", + "rotateSecretKey": "마스터 키 순환", + "hashidSalt": "HashID 솔트 ê°’", + "hashidSaltDes": "HashID ìƒì„±ì— 사용ë˜ëŠ” 솔트 값입니다. 신중하게 변경해 주세요. 변경하면 ê¸°ì¡´ì˜ ì§ì ‘ ë§í¬, 공유 ë§í¬ ë“±ì´ ëª¨ë‘ ë¬´íš¨í™”ë©ë‹ˆë‹¤.", + "accessTokenTTL": "액세스 í† í° TTL", + "accessTokenTTLDes": "액세스 토í°ì˜ 유효 기간(ì´ˆ)", + "refreshTokenTTL": "리프레시 í† í° TTL", + "refreshTokenTTLDes": "리프레시 토í°ì˜ 유효 기간(ì´ˆ)입니다. ì‚¬ìš©ìž ë¡œê·¸ì¸ ìƒíƒœ 유지 ì‹œê°„ì— ì˜í–¥ì„ ì¤ë‹ˆë‹¤.", + "cronGarbageCollect": "가비지 컬렉션 스캔 간격", + "cronGarbageCollectDes": "임시 파ì¼ê³¼ KV ì €ìž¥ì†Œì˜ ë§Œë£Œëœ ë°ì´í„°ë¥¼ 스캔하고 회수하는 주기를 설정합니다.", + "startWithProtocol": "http:// ë˜ëŠ” https://로 시작해야 합니다.", + "tlsWarning": "현재 사ì´íŠ¸ëŠ” https를 사용하고 있습니다. ì—¬ê¸°ì— http URLì„ ìž…ë ¥í•˜ë©´ 예외가 ë°œìƒí•  수 있습니다.", + "blobUrlCache": "Blob URL ìºì‹œ", + "clearBlobUrlCache": "Blob URL ìºì‹œ 지우기", + "clearBlobUrlCacheDes": "ìºì‹œ ì ì¤‘ë¥ ì„ ë†’ì´ê¸° 위해 Cloudreve는 Blob URLì„ ìºì‹œí•˜ê³  재사용합니다. CDN 주소 ë“±ì˜ ì„¤ì •ì´ ë³€ê²½ë˜ë©´ ìºì‹œë¥¼ 지워 주세요.", + "cacheCleared": "ìºì‹œê°€ 지워졌습니다." + }, + "giftCodes": { + "giftCodesSettings": "기프트 코드", + "generateGiftCodes": "기프트 코드 ìƒì„±", + "giftCodeQuantity": "수량", + "giftCodeQuantityHelp": "ìƒì„±í•  기프트 코드 수량", + "giftCodeProductType": "제품 유형", + "giftCodeTypePoints": "í¬ì¸íЏ", + "giftCodeTypeStorage": "저장 공간", + "giftCodeTypeGroup": "ì‚¬ìš©ìž ê·¸ë£¹", + "giftCodePointsAmount": "í¬ì¸íЏ 수량", + "giftCodePointsAmountHelp": "êµí™˜ 코드 사용 시 íšë“í•  í¬ì¸íЏ 수량", + "giftCodeProduct": "제품", + "selectStorageProduct": "저장 제품 ì„ íƒ", + "selectGroupProduct": "ì‚¬ìš©ìž ê·¸ë£¹ 제품 ì„ íƒ", + "giftCodeType": "유형", + "giftCodeAmount": "수량", + "giftCode": "기프트 코드", + "giftCodeStatus": "ìƒíƒœ", + "giftCodeUsedBy": "사용ìž", + "giftCodeUsed": "사용ë¨", + "giftCodeUnused": "사용 가능", + "giftCodeDeleted": "기프트 코드가 성공ì ìœ¼ë¡œ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤", + "giftCodesGenerated": "기프트 코드가 성공ì ìœ¼ë¡œ ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤", + "noGiftCodes": "기프트 코드가 없습니다", + "generatedCodesTitle": "ìƒì„±ëœ 기프트 코드", + "generatedCodesDescription": "ì´ ê¸°í”„íŠ¸ ì½”ë“œë“¤ì„ ë³µì‚¬í•˜ì—¬ 사용ìžì—게 공유하세요. ê° ì½”ë“œëŠ” 한 번만 사용할 수 있습니다.", + "copyAndClose": "복사하고 닫기", + "duratonTimes": "기간 배수", + "duratonTimesDes": "ê° ê¸°í”„íŠ¸ ì½”ë“œì— í¬í•¨ëœ 해당 ìƒí’ˆì˜ 수량", + "unknownProduct": "알 수 없는 제품" + }, + "policy": { + "acceleratedDomainUpload": "전송 ê°€ì† ë„ë©”ì¸ì„ 사용하여 업로드", + "acceleratedDomainUploadDes": "활성화하면 íŒŒì¼ ì—…ë¡œë“œ 시 Qiniuì˜ <0>전송 ê°€ì† ë„ë©”ì¸ì„ 사용합니다.", + "compare": "스토리지 ì •ì±… 비êµ", + "deletePolicyConfirmation": "스토리지 ì •ì±… {{name}}ì„(를) 삭제하시겠습니까?", + "streamSaver": "브ë¼ìš°ì €ì—서 다운로드 처리", + "streamSaverDes": "활성화하면 사용ìžê°€ 파ì¼ì„ 다운로드할 때 브ë¼ìš°ì €ì—서 강제로 처리합니다. OneDrive 스토리지 ì •ì±…ì˜ ì œí•œìœ¼ë¡œ ì¸í•´ 사용ìžê°€ ì§ì ‘ 다운로드하는 파ì¼ì˜ ì´ë¦„ì´ Cloudreve ë‚´ íŒŒì¼ ì´ë¦„ê³¼ ì¼ì¹˜í•˜ì§€ ì•Šì„ ìˆ˜ 있으며, 브ë¼ìš°ì €ì—서 다운로드를 처리하면 ì´ ë¬¸ì œë¥¼ í•´ê²°í•  수 있습니다.", + "oauthCallbackFailed": "ì¸ì¦ 실패", + "httpsRequired": "Entra ID 애플리케ì´ì…˜ì€ HTTPS 리디렉션 URLì´ í•„ìš”í•˜ì§€ë§Œ 현재 사ì´íŠ¸ëŠ” HTTP를 사용하고 있어 ë¡œê·¸ì¸ ì™„ë£Œ 후 리디렉션 실패가 ë°œìƒí•  수 있습니다. ì´ ê²½ìš° 브ë¼ìš°ì € ì£¼ì†Œì°½ì˜ HTTPS를 수ë™ìœ¼ë¡œ HTTP로 êµì²´í•´ 주세요.", + "authorizeMicrosoft": "Microsoft로 로그ì¸", + "redirectUrl": "리디렉션 URL", + "redirectUrlDes": "현재 í‘œì‹œëœ ê²ƒì€ ìš”êµ¬ì‚¬í•­ì„ ì¶©ì¡±í•˜ëŠ” 최신 리디렉션 URL입니다. 애플리케ì´ì…˜ ì„¤ì •ì˜ ë¦¬ë””ë ‰ì…˜ URLì´ ì¼ì¹˜í•˜ëŠ”ì§€ 확ì¸í•´ 주세요.", + "authorizeOneDrive": "Entra ID 애플리케ì´ì…˜ 설정 확ì¸", + "authorizeOneDriveDes": "ë‹¤ìŒ Entra ID 애플리케ì´ì…˜ ì •ë³´ê°€ 여전히 유효한지 확ì¸í•˜ê³ , 필요한 경우 변경해 주세요.", + "authorizeNow": "즉시 ì¸ì¦", + "authorizeAgain": "재ì¸ì¦", + "notGranted": "ì¸ì¦ëœ ê³„ì •ì´ ì—†ì–´ 스토리지 ì •ì±…ì„ ì‚¬ìš©í•  수 없습니다.", + "granted": "ê³„ì •ì´ ì¸ì¦ë˜ì—ˆìœ¼ë©°, ìžê²© ì¦ëª…ì´ <0>ì— ìƒˆë¡œê³ ì¹¨ë˜ì—ˆìŠµë‹ˆë‹¤.", + "grantedNotRefresh": "ê³„ì •ì´ ì¸ì¦ë˜ì—ˆìœ¼ë©°, 마지막 시작 ì´í›„ ìžê²© ì¦ëª…ì´ ìƒˆë¡œê³ ì¹¨ë˜ì§€ 않았습니다.", + "batchDeleteSize": "최대 ì¼ê´„ ì‚­ì œ 수량", + "batchDeleteSizeDes": "ë‹¨ì¼ API ìš”ì²­ì˜ ìµœëŒ€ ì‚­ì œ ìˆ˜ëŸ‰ì„ ì œí•œí•©ë‹ˆë‹¤. ì´ ì„¤ì •ì€ ì‚¬ìš©ìžì˜ ì¼ê´„ íŒŒì¼ ì‚­ì œì— ì˜í–¥ì„ 주지 않습니다. 입력하지 않으면 기본값 <0>1000ì„ ì‚¬ìš©í•˜ë©°, ì´ëŠ” ê³µì‹ S3 APIì˜ ìµœëŒ€ 허용값입니다.", + "bucketPolicy": "버킷 ì •ì±…", + "cdnOrCustomDomain": "CDN ë˜ëŠ” ì‚¬ìš©ìž ì •ì˜ ì†ŒìŠ¤ ë„ë©”ì¸", + "bucketDomain": "스토리지 공간 ë„ë©”ì¸", + "bucketDomainDes": "스토리지 ê³µê°„ì— ë°”ì¸ë”©ëœ CDN ê°€ì† ë„ë©”ì¸ ë˜ëŠ” ì‚¬ìš©ìž ì •ì˜ ì†ŒìŠ¤ ë„ë©”ì¸ì„ 입력하세요.", + "storageNodeInternal": "스토리지 노드 (ë‚´ë¶€ë§ Endpoint)", + "chunkSizeDesOssObs": "허용 범위: 100 KB ~ 5 GB,", + "chunkSizeDesQiniuCos": "허용 범위: 1 MB ~ 1 GB,", + "chunkSizeDesS3": "허용 범위: 5 MB ~ 5 GB,", + "thisIsACustomDomain": "ì´ê²ƒì€ ì‚¬ìš©ìž ì •ì˜ ë„ë©”ì¸ìž…니다", + "thisIsACustomDomainDes": "Bucketì— ì‚¬ìš©ìž ì •ì˜ ë„ë©”ì¸ì„ ë°”ì¸ë”©í–ˆê³  ì‚¬ìš©ìž ì •ì˜ ë„ë©”ì¸ì„ 통해 업로드 ë“±ì˜ ê´€ë¦¬ ìž‘ì—…ì„ ìˆ˜í–‰í•´ì•¼ 하는 경우 ì´ ì˜µì…˜ì„ ì„ íƒí•˜ì„¸ìš”. ì„ íƒí•˜ë©´ Cloudreve는 요청 ë„ë©”ì¸ì—서 Bucket ì´ë¦„ì„ ìžë™ìœ¼ë¡œ 추가하려고 시ë„하지 않습니다.", + "addedManually": "ì§ì ‘ 설정했습니다", + "origin": "소스", + "allowMethods": "허용 Methods", + "exposeHeaders": "노출 Headers", + "allowHeaders": "허용 Headers", + "maxAge": "ìºì‹œ 시간", + "accessCredential": "액세스 ìžê²© ì¦ëª…", + "downloadTrafficDiagram": "다운로드 트래픽 경로 시연ë„", + "downloadRelay": "다운로드 중계", + "downloadRelayDes": "활성화하면 사용ìžê°€ 파ì¼ì„ 다운로드할 때 Cloudreve를 통해 프ë¡ì‹œë©ë‹ˆë‹¤.", + "download": "다운로드", + "downloadCdn": "다운로드 CDN", + "useDownloadCdn": "CDNì„ ì‚¬ìš©í•˜ì—¬ 다운로드 ê°€ì†", + "skipSign": "CDN íŒŒì¼ URLì— ì„œëª…í•˜ì§€ 않ìŒ", + "skipSignDes": "COS ë„ë©”ì¸ ì„¤ì •ì—서 \"소스 ì¸ì¦\"ì„ í™œì„±í™”í•œ 경우 ì´ í•­ëª©ì„ ì„ íƒí•˜ì„¸ìš”.", + "cdnHost": "CDN 주소", + "downloadCdnDes": "사용ìžê°€ 파ì¼ì— 액세스할 때 URLì˜ í˜¸ìŠ¤íŠ¸ëª…, 프로토콜 등 ë¶€ë¶„ì´ ì§€ì •í•œ CDN ë„ë©”ì¸ìœ¼ë¡œ êµì²´ë©ë‹ˆë‹¤.", + "mediaExtractorProxy": "미디어 ì •ë³´ 추출 프ë¡ì‹œ", + "mediaExtractorProxyDes": "활성화하면 스토리지 측 추출기가 ì§€ì›í•˜ì§€ 않는 파ì¼ì— 대해 Cloudreveê°€ íŒŒì¼ ë¯¸ë””ì–´ ì •ë³´ ì¶”ì¶œì„ ì‹œë„합니다. <0>미디어 처리ì—서 Cloudreve 미디어 ì •ë³´ 추출기를 구성하세요.", + "mediaExtractorNative": "네ì´í‹°ë¸Œ 추출기", + "mediaExtractorOss": "지능형 미디어 관리 (IMM)", + "mediaExtractorQiniu": "지능형 멀티미디어 서비스", + "mediaExtractorCos": "Tencent Cloud ë°ì´í„° ë§Œìƒ", + "mediaExtractorObs": "ì´ë¯¸ì§€ 처리 서비스", + "mediaExtractorUpyun": "ì´ë¯¸ì§€ 처리 서비스", + "nativeMediaMetaExts": "<0>{{name}}ì„(를) 사용하는 íŒŒì¼ í™•ìž¥ìž", + "nativeMediaMetaExtsGeneralDes": "ë°˜ê° ì‰¼í‘œ ,로 구분하며, 비워ë‘ë©´ <0>{{name}}ì„(를) 사용하지 않습니다.", + "nativeMediaMetaExtsRemote": "슬레ì´ë¸Œ ìŠ¤í† ë¦¬ì§€ì˜ ê²½ìš° 기본ì ìœ¼ë¡œ EXIF ë° ìŒì•… 메타ë°ì´í„°ë¥¼ ì§€ì›í•˜ë©°, 슬레ì´ë¸Œ 측ì—서 다른 ìƒì„±ê¸°ë¥¼ 활성화하려면 êµ¬ì„±ì„ í†µí•´ ë®ì–´ì“¸ 수 있습니다.", + "nativeMediaMetaExtOss": "지능형 미디어 관리 (IMM) 서비스는 오디오, 비디오 ë° ì´ë¯¸ì§€ 처리를 ì§€ì›í•©ë‹ˆë‹¤. ì´ë¯¸ì§€ 처리는 ìˆ˜ë™ êµ¬ì„±ì´ í•„ìš”í•˜ì§€ 않지만 오디오나 비디오를 처리해야 하는 경우 IMMì„ ìˆ˜ë™ìœ¼ë¡œ 개통하고 Bucketì— ë°”ì¸ë”©í•´ì•¼ 합니다. <0>문서를 참고하여 ë°”ì¸ë”©í•˜ì„¸ìš”. ë°”ì¸ë”© 완료 후 ìœ„ì— ì²˜ë¦¬í•˜ë ¤ëŠ” 오디오/ë¹„ë””ì˜¤ì˜ í™•ìž¥ìžë¥¼ 추가하세요.", + "nativeMediaMetaExtQiniu": "지능형 멀티미디어 서비스는 ì¼ë°˜ì ì¸ 오디오, 비디오 ë° ì´ë¯¸ì§€ 처리를 ì§€ì›í•˜ë©° 추가 êµ¬ì„±ì´ í•„ìš”í•˜ì§€ 않습니다. ìœ„ì— ì²˜ë¦¬í•˜ë ¤ëŠ” ë¯¸ë””ì–´ì˜ í™•ìž¥ìžë¥¼ 입력하세요.", + "nativeMediaMetaExtCos": "Tencent Cloud ë°ì´í„° ë§Œìƒ ì„œë¹„ìŠ¤ëŠ” 오디오, 비디오 ë° ì´ë¯¸ì§€ 처리를 ì§€ì›í•©ë‹ˆë‹¤. ì´ë¯¸ì§€ 처리는 ìˆ˜ë™ êµ¬ì„±ì´ í•„ìš”í•˜ì§€ 않지만 오디오나 비디오를 처리해야 하는 경우 먼저 <0>ë°ì´í„° ë§Œìƒìœ¼ë¡œ ì´ë™í•˜ì—¬ 개통하고 스토리지 ë²„í‚·ì„ ë°”ì¸ë”©í•œ ë‹¤ìŒ ë²„í‚· 설정 - 미디어 처리ì—서 ì´ë¯¸ì§€ 처리 서비스를 개통하세요. ë°”ì¸ë”© 완료 후 ìœ„ì— ì²˜ë¦¬í•˜ë ¤ëŠ” 오디오/ë¹„ë””ì˜¤ì˜ í™•ìž¥ìžë¥¼ 추가하세요.", + "nativeMediaMetaExtObs": "ì´ë¯¸ì§€ 처리 서비스는 <0>ì´ë¯¸ì§€ EXIF ì¶”ì¶œì„ ì§€ì›í•©ë‹ˆë‹¤. ìˆ˜ë™ êµ¬ì„±ì´ í•„ìš”í•˜ì§€ 않으며, ìœ„ì— ì²˜ë¦¬í•˜ë ¤ëŠ” ì´ë¯¸ì§€ì˜ 확장ìžë¥¼ 추가하세요.", + "nativeMediaMetaExtUpyun": "ì´ë¯¸ì§€ 처리 서비스는 <0>ì´ë¯¸ì§€ EXIF ì¶”ì¶œì„ ì§€ì›í•©ë‹ˆë‹¤. ìˆ˜ë™ êµ¬ì„±ì´ í•„ìš”í•˜ì§€ 않으며, ìœ„ì— ì²˜ë¦¬í•˜ë ¤ëŠ” ì´ë¯¸ì§€ì˜ 확장ìžë¥¼ 추가하세요.", + "thumbProxy": "ì¸ë„¤ì¼ ìƒì„± 프ë¡ì‹œ", + "thumbProxyDes": "활성화하면 네ì´í‹°ë¸Œ ì¸ë„¤ì¼ ì¡°ê±´ì— ë§žì§€ 않는 파ì¼ì— 대해 Cloudreveê°€ ì¸ë„¤ì¼ 파ì¼ì„ ìƒì„±í•˜ì—¬ 스토리지 ì¸¡ì— ì—…ë¡œë“œë¥¼ 시ë„합니다. <0>미디어 처리ì—서 Cloudreve ì¸ë„¤ì¼ ìƒì„±ê¸°ë¥¼ 구성하세요.", + "nativeThumbnailMaxSize": "네ì´í‹°ë¸Œ ì¸ë„¤ì¼ì„ 사용하는 최대 íŒŒì¼ í¬ê¸°", + "nativeThumbnailMaxSizeDes": "0ì„ ìž…ë ¥í•˜ë©´ 제한하지 않으며, ì´ í¬ê¸°ë¥¼ 초과하는 파ì¼ì€ 네ì´í‹°ë¸Œ ì¸ë„¤ì¼ì„ 사용하지 않습니다.", + "nativeThumbNailsSupportAllExts": "모든 íŒŒì¼ í™•ìž¥ìžì— 사용", + "nativeThumbNails": "네ì´í‹°ë¸Œ ì¸ë„¤ì¼ì„ 사용하는 확장ìž", + "nativeThumbNailsGeneralDes": "ë°˜ê° ì‰¼í‘œ ,로 구분하며, 비워ë‘ë©´ 네ì´í‹°ë¸Œ ì¸ë„¤ì¼ì„ 사용하지 않습니다. 목ë¡ì— ë‚˜ì—´ëœ íŒŒì¼ í™•ìž¥ìžì— 대해 Cloudreve는 스토리지 ì¸¡ì˜ ë„¤ì´í‹°ë¸Œ ì¸ë„¤ì¼ì„ 사용합니다.", + "nativeThumbNailsGeneralRemote": "슬레ì´ë¸Œ ìŠ¤í† ë¦¬ì§€ì˜ ê²½ìš° 기본ì ìœ¼ë¡œ 단순 ì´ë¯¸ì§€ ë° ìŒì•… 커버 ì¸ë„¤ì¼ë§Œ ì§€ì›í•˜ë©°, 슬레ì´ë¸Œ 측ì—서 다른 ìƒì„±ê¸°ë¥¼ 활성화하려면 êµ¬ì„±ì„ í†µí•´ ë®ì–´ì“¸ 수 있습니다.", + "nativeThumbNailsGeneralOss": "Alibaba Cloud OSS ìŠ¤í† ë¦¬ì§€ì˜ ê²½ìš° <0>ì´ë¯¸ì§€ 처리 서비스를 사용하여 ì¸ë„¤ì¼ì„ ìƒì„±í•©ë‹ˆë‹¤.", + "nativeThumbNailsGeneralQiniu": "Qiniu Cloud ìŠ¤í† ë¦¬ì§€ì˜ ê²½ìš° <0>ì´ë¯¸ì§€ 기본 처리(imageView2) 서비스를 사용하여 ì¸ë„¤ì¼ì„ ìƒì„±í•©ë‹ˆë‹¤.", + "nativeThumbNailsGeneralCos": "Tencent Cloud COS ìŠ¤í† ë¦¬ì§€ì˜ ê²½ìš° <0>Tencent Cloud ë°ì´í„° ë§Œìƒ ì„œë¹„ìŠ¤ë¥¼ 사용하여 ì¸ë„¤ì¼ì„ ìƒì„±í•©ë‹ˆë‹¤.", + "nativeThumbNailsGeneralObs": "Huawei Cloud OBS ìŠ¤í† ë¦¬ì§€ì˜ ê²½ìš° <0>ì´ë¯¸ì§€ 처리 서비스를 사용하여 ì¸ë„¤ì¼ì„ ìƒì„±í•©ë‹ˆë‹¤.", + "nativeThumbNailsGeneralUpyun": "Upyun ìŠ¤í† ë¦¬ì§€ì˜ ê²½ìš° <0>ì´ë¯¸ì§€ 처리 서비스를 사용하여 ì¸ë„¤ì¼ì„ ìƒì„±í•©ë‹ˆë‹¤.", + "preallocate": "하드 ë””ìŠ¤í¬ ê³µê°„ 사전 할당", + "preallocateDes": "활성화하면 사용ìžê°€ 파ì¼ì„ 업로드할 때 하드 ë””ìŠ¤í¬ ê³µê°„ì„ ë¯¸ë¦¬ 할당하며, ë™ì‹œì— 병렬 ì²­í¬ ì—…ë¡œë“œë„ ì§€ì›í•©ë‹ˆë‹¤. Linux ë˜ëŠ” Darwinì—서만 유효합니다.", + "chunkConcurrency": "병렬 ì²­í¬ ì—…ë¡œë“œ 수", + "chunkConcurrencyDes": "웹 ì§ì ‘ 업로드 시 ë™ì‹œ ì§„í–‰ë˜ëŠ” ì²­í¬ ì—…ë¡œë“œ 수를 설정합니다.", + "sourceWebEdit": "웹 온ë¼ì¸ 편집", + "uploadRelay": "업로드 중계", + "uploadRelayDes": "활성화하면 사용ìžì˜ 업로드 ìš”ì²­ì´ Cloudreve를 통해 스토리지 측으로 중계ë˜ë©°, ë¶„í•  업로드를 수행할 수 없으므로 웹 ì„œë²„ì˜ ìµœëŒ€ 업로드 í¬ê¸° ì œí•œì„ ì ì ˆížˆ 조정하세요.", + "customProxy": "ì‚¬ìš©ìž ì •ì˜ í”„ë¡ì‹œ", + "storageNode": "스토리지 제공업체", + "sourceWeb": "웹 / ê³µì‹ í´ë¼ì´ì–¸íЏ", + "sourceDav": "WebDAV", + "uploadTrafficDiagram": "업로드 트래픽 경로 시연ë„", + "node": "스토리지 노드", + "nodeDes": "íŒŒì¼ ì €ìž¥ì— ì‚¬ìš©í•  슬레ì´ë¸Œ 노드를 ì„ íƒí•˜ì„¸ìš”. <0>스토리지 노드 목ë¡ì—서 슬레ì´ë¸Œ 노드를 ìƒì„±í•˜ê±°ë‚˜ 관리할 수 있습니다.", + "noBindedGroupWarning": "현재 스토리지 ì •ì±…ì´ ì–´ë–¤ ì‚¬ìš©ìž ê·¸ë£¹ì—ë„ í• ë‹¹ë˜ì§€ 않았습니다. <0>ì‚¬ìš©ìž ê·¸ë£¹ 목ë¡ìœ¼ë¡œ ì´ë™í•˜ì—¬ 현재 스토리지 ì •ì±…ì— ì‚¬ìš©ìž ê·¸ë£¹ì„ ë°”ì¸ë”©í•˜ì„¸ìš”.", + "nameRuleImmutable": "ì´ ì„¤ì •ì„ ìˆ˜ì •í•´ë„ ìŠ¤í† ë¦¬ì§€ ì •ì±… í•˜ì˜ ê¸°ì¡´ 파ì¼ì—는 ì˜í–¥ì„ 주지 않습니다. Blob 경로는 ìƒì„± 후 ê³ ì •ë˜ë©°, ê·¸ ì•ˆì˜ ë§¤ì§ ë³€ìˆ˜ê°€ 변경ë˜ì–´ë„ 경로는 ì—…ë°ì´íЏë˜ì§€ 않습니다.", + "uniqueVarRequired": "디렉터리 경로 ë˜ëŠ” 파ì¼ëª…ì— ìµœì†Œ í•˜ë‚˜ì˜ ê³ ìœ ì„± 변수를 í¬í•¨í•˜ì„¸ìš”: {uuid}, {randomkey8}, {randomkey16}.", + "storageAndUpload": "스토리지 ë° ì—…ë¡œë“œ", + "blobFolderNaming": "Blob 스토리지 디렉터리", + "blobFolderNamingDes": "íŒŒì¼ Blobì˜ ì €ìž¥ 디렉터리로, <0>ë§¤ì§ ë³€ìˆ˜ë¥¼ 사용할 수 있습니다.", + "blobName": "Blob ì´ë¦„", + "blobNameDes": "íŒŒì¼ Blobì˜ ì´ë¦„으로, <0>ë§¤ì§ ë³€ìˆ˜ë¥¼ 사용할 수 있으며, ì§§ì€ ì‹œê°„ ë‚´ì— ê°™ì€ íŒŒì¼ì„ 여러 번 업로드하는 경우ì—ë„ ì ˆëŒ€ì ìœ¼ë¡œ 고유해야 합니다.", + "basicInfo": "기본 ì •ë³´", + "editX": "{{name}} 편집", + "noGroupBinded": "ë°”ì¸ë”©ëœ ì‚¬ìš©ìž ê·¸ë£¹ì´ ì—†ìŠµë‹ˆë‹¤", + "create": "ìƒì„±", + "addXStoragePolicy": "{{type}} 스토리지 ì •ì±… 추가", + "loadSummary": "통계 ë°ì´í„° 로드", + "policySummary": "{{count}}ê°œ íŒŒì¼ Blob ({{size}})", + "sharp": "#", + "name": "ì´ë¦„", + "type": "유형", + "childFiles": "하위 íŒŒì¼ ìˆ˜", + "totalSize": "ë°ì´í„° ì–‘", + "actions": "작업", + "authSuccess": "ì¸ì¦ 성공", + "policyDeleted": "스토리지 ì •ì±…ì´ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤", + "newStoragePolicy": "스토리지 ì •ì±… 추가", + "all": "ì „ì²´", + "local": "로컬 스토리지", + "remote": "슬레ì´ë¸Œ 스토리지", + "qiniu": "Qiniu", + "upyun": "Upyun", + "oss": "Alibaba Cloud OSS", + "cos": "Tencent Cloud COS", + "onedrive": "OneDrive", + "s3": "S3 호환", + "ks3": "Kingsoft Cloud KS3", + "obs": "Huawei Cloud OBS", + "load_balance": "로드 밸런싱", + "childPolicy": "하위 스토리지 ì •ì±…", + "childPolicyDes": "로드 ë°¸ëŸ°ì‹±ì— ì¶”ê°€í•  하위 스토리지 ì •ì±…ì„ ì„ íƒí•˜ì„¸ìš”.", + "weight": "가중치", + "addTargetPolicy": "하위 스토리지 ì •ì±… 추가", + "selectPolicies": "ì •ì±… ì„ íƒ", + "selectPoliciesDes": "로드 ë°¸ëŸ°ì‹±ì— ì¶”ê°€í•  스토리지 ì •ì±…ì„ ì„ íƒí•˜ì„¸ìš”.", + "loadBalanceDes": "로드 밸런싱 스토리지 ì •ì±…ì„ ì‚¬ìš©í•  때 새로 업로드ë˜ëŠ” 파ì¼ì€ ê°€ì¤‘ì¹˜ì— ë”°ë¼ ì„œë¡œ 다른 하위 스토리지 ì •ì±…ì— ë¬´ìž‘ìœ„ë¡œ 할당ë©ë‹ˆë‹¤.", + "xChildPolicies": "{{count}}ê°œ 하위 스토리지 ì •ì±…", + "refresh": "새로고침", + "delete": "ì‚­ì œ", + "edit": "편집", + "selectAStorageProvider": "스토리지 ë°©ì‹ ì„ íƒ", + "maxSizeOfSingleFile": "íŒŒì¼ í¬ê¸° 제한", + "maxSizeOfSingleFileDes": "ë‹¨ì¼ íŒŒì¼ì˜ 최대 í¬ê¸°ìž…니다. ì œí•œì„ 0으로 입력하면 ë‹¨ì¼ íŒŒì¼ í¬ê¸°ë¥¼ 제한하지 않습니다.", + "enterFileExt": "비워ë‘ë©´ íŒŒì¼ í™•ìž¥ìžë¥¼ 제한하지 않으며, 여러 개는 ë°˜ê° ì‰¼í‘œ ,로 구분하세요.", + "extList": "íŒŒì¼ í™•ìž¥ìž ì œí•œ", + "noLimit": "제한 ì—†ìŒ", + "whitelist": "허용", + "blacklist": "ê±°ë¶€", + "fileNameRegex": "파ì¼ëª… ì •ê·œì‹ ê·œì¹™", + "fileNameRegexDes": "파ì¼ëª…ê³¼ ì¼ì¹˜í•˜ëŠ” ì •ê·œ 표현ì‹ìž…니다. 비워ë‘ë©´ ì œí•œì´ ì—†ìŠµë‹ˆë‹¤.", + "chunkSizeDes": "ë¶„í•  업로드 시 ë¶„í•  í¬ê¸°ë¥¼ 지정하세요. 0으로 입력하면 ë¶„í•  업로드를 사용하지 않지만 최대 업로드 í¬ê¸°ê°€ 웹 ì„œë²„ì˜ ì œí•œì„ ë°›ì„ ìˆ˜ 있습니다.", + "chunkSizeDesSuffix": "{{prefix}}ë¶„í•  업로드를 통해 사용ìžê°€ 업로드하는 파ì¼ì€ ë¶„í• ë˜ì–´ 스토리지 ì¸¡ì— í•˜ë‚˜ì”© 업로드ë©ë‹ˆë‹¤. 업로드가 ì¤‘ë‹¨ëœ í›„ 사용ìžëŠ” 마지막으로 ì—…ë¡œë“œëœ ë¶„í•  ì´í›„부터 ê³„ì† ì—…ë¡œë“œí•  수 있습니다.", + "chunkSize": "업로드 ë¶„í•  í¬ê¸°", + "policyName": "스토리지 ì •ì±…ì˜ í‘œì‹œëª…ìœ¼ë¡œ, 사용ìžì—ê²Œë„ í‘œì‹œë©ë‹ˆë‹¤.", + "magicVar": { + "fileNameMagicVar": "파ì¼ëª… ë§¤ì§ ë³€ìˆ˜", + "pathMagicVar": "경로 ë§¤ì§ ë³€ìˆ˜", + "variable": "ë§¤ì§ ë³€ìˆ˜", + "description": "설명", + "example": "예시", + "16digitsRandomString": "16ìžë¦¬ ëžœë¤ ë¬¸ìž", + "8digitsRandomString": "8ìžë¦¬ ëžœë¤ ë¬¸ìž", + "secondTimestamp": "ì´ˆ 단위 타임스탬프", + "nanoTimestamp": "나노초 단위 타임스탬프", + "uid": "ì‚¬ìš©ìž ID", + "originalFileName": "ì›ë³¸ 파ì¼ëª…", + "originFileNameNoext": "확장ìžê°€ 없는 ì›ë³¸ 파ì¼ëª…", + "extension": "íŒŒì¼ í™•ìž¥ìž", + "uuidV4": "UUID V4", + "date": "ë‚ ì§œ", + "dateAndTime": "ë‚ ì§œ 시간", + "randomNumber": "범위 ë‚´ ëžœë¤ ìˆ«ìž", + "year": "ë…„ë„", + "month": "ì›”", + "day": "ì¼", + "hour": "시간", + "minute": "ë¶„", + "second": "ì´ˆ", + "path": "사용ìžê°€ 파ì¼ì„ 업로드할 ë•Œì˜ ì´ˆê¸° 경로" + }, + "storageBucket": "스토리지 공간", + "wanSiteURLDes": "ì´ ìŠ¤í† ë¦¬ì§€ ì •ì±…ì„ ì‚¬ìš©í•˜ê¸° ì „ì— ë§¤ê°œë³€ìˆ˜ 설정 - 사ì´íЏ ì •ë³´ - 사ì´íЏ URLì— ìž…ë ¥í•œ 주소가 실제와 ì¼ì¹˜í•˜ê³  <0>외부 네트워í¬ì—서 ì •ìƒì ìœ¼ë¡œ 액세스할 수 있는지 확ì¸í•˜ì„¸ìš”.", + "enterQiniuBucket": "<0>Qiniu 제어íŒìœ¼ë¡œ ì´ë™í•˜ì—¬ ê°ì²´ 스토리지 리소스를 ìƒì„±í•˜ì„¸ìš”. Qiniuì—서 스토리지 ê³µê°„ì„ ìƒì„±í•  때 지정한 \"스토리지 공간 ì´ë¦„\"ì„ ìž…ë ¥í•˜ì„¸ìš”.", + "qiniuBucketName": "스토리지 공간 ì´ë¦„", + "cosObsBucketName": "스토리지 버킷 ì´ë¦„", + "bucketType": "Bucket ì½ê¸°/쓰기 권한", + "bucketTypeDes": "ìƒì„±í•œ 스토리지 ê³µê°„ì˜ ì½ê¸°/쓰기 권한 ìœ í˜•ì„ ì„ íƒí•˜ì„¸ìš”.", + "aclType": "액세스 제어 유형", + "accessTypePulic": "공개 ì½ê¸° 비공개 쓰기", + "accessTypePrivate": "비공개 ì½ê¸°/쓰기", + "accessType": "액세스 권한", + "privateBucket": "비공개", + "privateDes": "Cloudreveê°€ íŒŒì¼ URLì— ì„œëª…í•©ë‹ˆë‹¤.", + "publicBucket": "공개 ì½ê¸°", + "publicStorage": "공개", + "publicDes": "권장하지 않습니다. Cloudreveê°€ 파ì¼ì˜ ì§ì ‘ ë§í¬ë¥¼ 바로 반환하여 파ì¼ì˜ 액세스 ê¶Œí•œì„ íš¨ê³¼ì ìœ¼ë¡œ 제어할 수 없습니다.", + "bucketCDNDes": "스토리지 ê³µê°„ì— ë°”ì¸ë”©í•œ CDN ê°€ì† ë„ë©”ì¸ì„ 입력하세요.", + "bucketCDNDomain": "CDN ê°€ì† ë„ë©”ì¸", + "qiniuCredentialDes": "Qiniu 제어íŒì—서 ê°œì¸ ì„¼í„° - 키 관리로 ì´ë™í•˜ì—¬ íšë“한 AK, SK를 입력하세요.", + "ak": "AK", + "sk": "SK", + "cannotEnableForPrivateBucket": "비공개 공간ì—서 외부 ë§í¬ ê¸°ëŠ¥ì„ í™œì„±í™”í•œ 후ì—는 ì‚¬ìš©ìž ê·¸ë£¹ì—서 \"ë¦¬ë””ë ‰ì…˜ì„ ì‚¬ìš©í•˜ëŠ” 외부 ë§í¬\" 활성화를 설정해야 하며, 그렇지 않으면 ì •ìƒì ìœ¼ë¡œ 외부 ë§í¬ë¥¼ ìƒì„±í•  수 없습니다", + "chunkSizeLabelQiniu": "ë¶„í•  업로드 시 ë¶„í•  í¬ê¸°ë¥¼ 지정하세요. 허용 범위는 1 MB - 1 GB입니다.", + "corsSettingStep": "êµì°¨ 출처 ì •ì±…", + "corsPolicyAdded": "êµì°¨ 출처 ì •ì±…ì´ ì¶”ê°€ë˜ì—ˆìŠµë‹ˆë‹¤.", + "createOSSBucketDes": "<0>OSS 관리 콘솔로 ì´ë™í•˜ì—¬ Bucketì„ ìƒì„±í•  수 있습니다. <1>표준 스토리지 ë° <2>ì €ë¹ˆë„ ì•¡ì„¸ìŠ¤ ìœ í˜•ì˜ Bucketë§Œ ì§€ì›í•©ë‹ˆë‹¤.", + "bucketName": "Bucket ì´ë¦„", + "publicReadBucket": "공개 ì½ê¸°", + "ossEndpointDes": "ìƒì„±ëœ Bucketì˜ ê°œìš” 페ì´ì§€ë¡œ ì´ë™í•˜ì—¬ <0>액세스 ë„ë©”ì¸ í•­ëª© í•˜ì˜ <1>외부 ë„¤íŠ¸ì›Œí¬ ì•¡ì„¸ìŠ¤ í–‰ì˜ ì¤‘ê°„ì— ìžˆëŠ” <2>EndPoint(지역 노드)를 입력하세요.", + "ossEndpointDesInternalHint": "ë‚´ë¶€ ë„¤íŠ¸ì›Œí¬ ë˜ëŠ” ì‚¬ìš©ìž ì •ì˜ ë„ë©”ì¸ Endpoint를 구성해야 하는 경우 스토리지 ì •ì±… ìƒì„± 후 설정할 수 있습니다.", + "obsEndpointCnameHint": "ì‚¬ìš©ìž ì •ì˜ ë„ë©”ì¸ Endpoint를 구성해야 하는 경우 스토리지 ì •ì±… ìƒì„± 후 설정할 수 있습니다.", + "endpoint": "EndPoint", + "ossLANEndpointDes": "비워ë‘ë©´ 사용하지 않습니다. Cloudreveê°€ Alibaba Cloud 컴퓨팅 ì„œë¹„ìŠ¤ì— ë°°í¬ë˜ì–´ 있고 OSS와 ê°™ì€ ê°€ìš© ì˜ì—­ì— 있는 경우, 트래픽 ë¹„ìš©ì„ ì ˆì•½í•˜ê¸° 위해 ë‚´ë¶€ ë„¤íŠ¸ì›Œí¬ EndPoint를 추가로 지정할 수 있으며, Cloudreve는 ì¡°ê±´ì´ ì¶©ì¡±ë  ë•Œ ë‚´ë¶€ ë„¤íŠ¸ì›Œí¬ EndPoint로 전환하여 ìš”ì²­ì„ ë³´ëƒ…ë‹ˆë‹¤.", + "intranetEndPoint": "ë‚´ë¶€ ë„¤íŠ¸ì›Œí¬ EndPoint", + "ossCDNDes": "Alibaba Cloud CDNì„ ì‚¬ìš©í•˜ì—¬ OSS 액세스를 ê°€ì†í™”하시겠습니까?", + "createOSSCDNDes": "<0>Alibaba Cloud CDN 관리 콘솔로 ì´ë™í•˜ì—¬ CDN ê°€ì† ë„ë©”ì¸ì„ ìƒì„±í•˜ê³  소스를 방금 ìƒì„±í•œ OSS Bucket으로 설정하세요. ì•„ëž˜ì— CDN ê°€ì† ë„ë©”ì¸ì„ 입력하고 HTTPS 사용 여부를 ì„ íƒí•˜ì„¸ìš”:", + "ossAKDes": "Alibaba Cloud <0>보안 ì •ë³´ 관리 페ì´ì§€ì—서 AccessKey를 íšë“하세요. <1>RAM 액세스 제어ì—서 <2>AliyunOSSFullAccess ê¶Œí•œì„ ê°€ì§„ AccessKey를 ìƒì„±í•  ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤.", + "shouldNotContainSpace": "ê³µë°±ì„ í¬í•¨í•  수 없습니다", + "nameThePolicyFirst": "ì´ ìŠ¤í† ë¦¬ì§€ ì •ì±…ì— ì´ë¦„ì„ ì§€ì •í•˜ì„¸ìš”:", + "chunkSizeLabelOSS": "ë¶„í•  업로드 시 ë¶„í•  í¬ê¸°ë¥¼ 지정하세요. 허용 범위는 100 KB ~ 5 GB입니다.", + "ossCORSDes": "ì´ ìŠ¤í† ë¦¬ì§€ ì •ì±…ì€ ì›¹ 단ì—서 파ì¼ì„ 업로드하려면 위와 ê°™ì€ êµì°¨ 출처 ì •ì±…ì„ ì˜¬ë°”ë¥´ê²Œ 구성해야 합니다. Cloudreveê°€ ìžë™ìœ¼ë¡œ 설정하ë„ë¡ ë„울 ìˆ˜ë„ ìžˆê³  수ë™ìœ¼ë¡œ 설정할 ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤. ì´ Bucketì˜ êµì°¨ 출처 ì •ì±…ì„ ì´ë¯¸ 설정한 경우 ì´ ë‹¨ê³„ë¥¼ 건너뛸 수 있습니다.", + "letCloudreveHelpMe": "Cloudreveê°€ 설정하ë„ë¡ í•˜ê¸°", + "skip": "건너뛰기", + "createUpyunBucketDes": "<0>Upyun 패ë„ì—서 ìƒì„±í•œ í´ë¼ìš°ë“œ 스토리지 서비스 ì´ë¦„ì„ ìž…ë ¥í•˜ì„¸ìš”.", + "storageServiceName": "서비스 ì´ë¦„", + "operatorName": "ìš´ì˜ìž ì´ë¦„", + "operatorPassword": "ìš´ì˜ìž 비밀번호", + "tokenStatus": "Token ë„ìš© ë°©ì§€", + "upyunTokenDes": "Token ë„ìš© 방지를 활성화하는 ê²ƒì„ ê°•ë ¥ížˆ 권장합니다. ìƒì„±ëœ í´ë¼ìš°ë“œ 스토리지 ì„œë¹„ìŠ¤ì˜ <0>기능 구성 패ë„로 ì´ë™í•˜ì—¬ <1>액세스 제어 탭으로 전환하고 Token ë„ìš© 방지를 활성화하고 비밀번호를 설정하세요.", + "tokenEnabled": "Token ë„ìš© ë°©ì§€ 활성화ë¨", + "tokenDisabled": "Token ë„ìš© ë°©ì§€ 활성화ë˜ì§€ 않ìŒ", + "upyunTokenSecretDes": "설정한 Token ë„ìš© ë°©ì§€ 키를 입력하세요.", + "upyunTokenSecret": "Token ë„ìš© ë°©ì§€ 키", + "createCOSBucketDes": "<0>COS 관리 콘솔로 ì´ë™í•˜ì—¬ 스토리지 ë²„í‚·ì„ ìƒì„±í•˜ê³ , ìƒì„±ëœ 스토리지 ë²„í‚·ì˜ ê¸°ë³¸ 구성 페ì´ì§€ë¡œ ì´ë™í•˜ì—¬ <1>스토리지 버킷 ì´ë¦„ì„ ìœ„ì— ìž…ë ¥í•˜ì„¸ìš”.", + "obsBucketDes": "<0>OBS 관리 콘솔로 ì´ë™í•˜ì—¬ 스토리지 ë²„í‚·ì„ ìƒì„±í•˜ê³  <1>버킷 ì´ë¦„ì„ ìœ„ì— ìž…ë ¥í•˜ì„¸ìš”. 스토리지 버킷 ìœ í˜•ì€ <2>표준 스토리지 ë˜ëŠ” <3>ì €ë¹ˆë„ ì•¡ì„¸ìŠ¤ 스토리지만 ì§€ì›í•©ë‹ˆë‹¤.", + "cosPrivateRW": "비공개 ì½ê¸°/쓰기", + "cosPublicRW": "공개 ì½ê¸° 비공개 쓰기", + "cosAccessDomainDes": "ìƒì„±ëœ Bucketì˜ ê°œìš” 페ì´ì§€ì—서 <0>ë„ë©”ì¸ ì •ë³´ 항목 í•˜ì— ì œê³µëœ <1>액세스 ë„ë©”ì¸ì„ 입력하세요. ë°”ì¸ë”©í•œ 소스 ë„ë©”ì¸ ë˜ëŠ” CDN ê°€ì† ë„ë©”ì¸ì„ 사용할 ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤.", + "obsEndpointDes": "ìƒì„±ëœ 스토리지 ë²„í‚·ì˜ ê°œìš” 페ì´ì§€ì—서 <0>ë„ë©”ì¸ ì •ë³´ 항목 í•˜ì— ì œê³µëœ <1>Endpoint(엔드í¬ì¸íЏ)를 입력하세요.", + "accessDomain": "액세스 ë„ë©”ì¸", + "cosCDNDomainDes": "<0>Tencent Cloud CDN 관리 콘솔로 ì´ë™í•˜ì—¬ CDN ê°€ì† ë„ë©”ì¸ì„ ìƒì„±í•˜ê³  소스를 방금 ìƒì„±í•œ COS 스토리지 버킷으로 설정하세요. ì•„ëž˜ì— CDN ê°€ì† ë„ë©”ì¸ì„ 입력하고 HTTPS 사용 여부를 ì„ íƒí•˜ì„¸ìš”:", + "cosCredentialDes": "Tencent Cloud <0>액세스 키 페ì´ì§€ì—서 íšë“한 액세스 키 ìŒì„ 입력하세요. ì´ í‚¤ ìŒì´ COS ì„œë¹„ìŠ¤ì— ëŒ€í•œ 액세스 ê¶Œí•œì„ ê°€ì§€ê³  있는지 확ì¸í•˜ì„¸ìš”. <1>í”„ë¡œê·¸ëž˜ë° ì•¡ì„¸ìŠ¤ ê¸°ëŠ¥ì„ ê°€ì§„ <2>하위 사용ìžë¥¼ ìƒì„±í•˜ì—¬ COS ì„œë¹„ìŠ¤ì— ëŒ€í•œ 액세스 ê¶Œí•œì„ ë¶€ì—¬í•  ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤.", + "obsCredentialDes": "Huawei Cloud <0>액세스 키 페ì´ì§€ì—서 íšë“한 액세스 키 ìŒì„ 입력하세요. <1>í”„ë¡œê·¸ëž˜ë° ì•¡ì„¸ìŠ¤ ê¸°ëŠ¥ì„ ê°€ì§„ <2>IAM 사용ìžë¥¼ ìƒì„±í•˜ì—¬ <3>OBS OperateAccess ê¶Œí•œì„ ë¶€ì—¬í•  ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤.", + "grantAccess": "계정 ì¸ì¦", + "grantAccessLater": "아래 ë²„íŠ¼ì„ í´ë¦­í•˜ì—¬ 스토리지 ì •ì±…ì„ ìƒì„±í•œ 후ì—ë„ ìŠ¤í† ë¦¬ì§€ ì •ì±… 설정 페ì´ì§€ì—서 계정 ì¸ì¦ì„ 수행해야 합니다.", + "odHttpsWarning": "OneDrive/SharePoint 스토리지 ì •ì±…ì„ ì‚¬ìš©í•˜ë ¤ë©´ HTTPS를 활성화해야 합니다. 활성화 후 매개변수 설정 - 사ì´íЏ ì •ë³´ - 사ì´íЏ URLì„ ë™ì‹œì— 변경하세요.", + "creatAadAppDes": "<0>Microsoft Entra ID 콘솔로 ì´ë™í•˜ì—¬ 로그ì¸í•˜ì„¸ìš”. ë¡œê·¸ì¸ í›„ <1>Microsoft Entra ID 관리 패ë„로 들어가세요. 여기서 로그ì¸ì— 사용하는 계정과 최종 ìŠ¤í† ë¦¬ì§€ì— ì‚¬ìš©í•˜ëŠ” OneDrive ì†Œì† ê³„ì •ì€ ë‹¤ë¥¼ 수 있습니다.", + "createAadAppDes2": "왼쪽 <0>앱 ë“±ë¡ ë©”ë‰´ë¡œ 들어가서 <1>새 ë“±ë¡ ë²„íŠ¼ì„ í´ë¦­í•˜ì„¸ìš”. 애플리케ì´ì…˜ ë“±ë¡ ì–‘ì‹ì„ 작성하세요. ê·¸ 중 ì´ë¦„ì€ ìž„ì˜ë¡œ 지정할 수 있으며, <2>ì§€ì›ë˜ëŠ” 계정 ìœ í˜•ì€ <3>모든 ì¡°ì§ ë””ë ‰í„°ë¦¬(모든 Azure AD 디렉터리 - 다중 테넌트)ì˜ ê³„ì • ë° ê°œì¸ Microsoft 계정(예: Skype, Xbox)으로 ì„ íƒí•˜ê³ , <4>리디렉션 URI (ì„ íƒ ì‚¬í•­)ì€ <5>ì›¹ì„ ì„ íƒí•˜ê³  <6>{{url}}를 입력하세요. 나머지는 ê¸°ë³¸ê°’ì„ ìœ ì§€í•˜ë©´ ë©ë‹ˆë‹¤.", + "aadAppIDDes": "애플리케ì´ì…˜ ê´€ë¦¬ì˜ <0>개요 페ì´ì§€ë¡œ 들어가서 ë³´ì´ëŠ” <1>애플리케ì´ì…˜(í´ë¼ì´ì–¸íЏ) IDì˜ ê°’ìž…ë‹ˆë‹¤.", + "entraIdApp": "Entra ID 애플리케ì´ì…˜ ì •ë³´", + "aadAppID": "애플리케ì´ì…˜(í´ë¼ì´ì–¸íЏ) ID", + "addAppSecretDes": "í´ë¼ì´ì–¸íЏ 비밀번호 ìƒì„± 방법: 애플리케ì´ì…˜ 관리 페ì´ì§€ ì™¼ìª½ì˜ <0>ì¸ì¦ì„œ ë° ë¹„ë°€ë²ˆí˜¸ 메뉴로 들어가서 <1>새 í´ë¼ì´ì–¸íЏ 비밀번호 ë²„íŠ¼ì„ í´ë¦­í•˜ê³  <2>만료 ê¸°í•œì„ ìµœëŒ€ 시간으로 ì„ íƒí•˜ì„¸ìš”. í´ë¼ì´ì–¸íЏ 비밀번호가 ë§Œë£Œëœ í›„ì—는 다시 ìƒì„±í•˜ì—¬ 스토리지 ì •ì±… ì„¤ì •ì— ìž…ë ¥í•´ì•¼ 합니다.", + "aadAppSecret": "í´ë¼ì´ì–¸íЏ 비밀번호", + "aadAccountCloud": "Microsoft Graph 엔드í¬ì¸íЏ", + "aadAccountCloudDes": "사용하는 Microsoft 365 계정 ìœ í˜•ì— ë”°ë¼ í•´ë‹¹ 엔드í¬ì¸íŠ¸ë¥¼ ì„ íƒí•˜ì„¸ìš”.", + "multiTenant": "공개(국제íŒï¼‰", + "gallatin": "세기 ì¸í„°ë„·", + "sharePointDes": "파ì¼ì„ SharePointì— ì €ìž¥í•˜ì‹œê² ìŠµë‹ˆê¹Œ?", + "saveToOneDrive": "계정 기본 OneDrive 드ë¼ì´ë¸Œì— 저장", + "spSiteURL": "SharePoint 사ì´íЏ 주소", + "odReverseProxyURLDes": "íŒŒì¼ ë‹¤ìš´ë¡œë“œ 시 ìžì²´ 구축한 ì—­ë°©í–¥ 프ë¡ì‹œ 서버로 êµì²´í•˜ì—¬ 사용하시겠습니까?", + "odReverseProxyURL": "ì—­ë°©í–¥ 프ë¡ì‹œ 서버 주소", + "chunkSizeDesOd": "허용 범위: 5 MB ~ 5GB, OneDrive는 320 KiB (327,680 bytes)ì˜ ì •ìˆ˜ 배수여야 합니다.", + "limitOdTPSDes": "OneDrive API 요청 ë¹ˆë„ ì œí•œ", + "tps": "TPS 제한", + "tpsDes": "비워ë‘ë©´ 제한하지 않습니다. ì´ ìŠ¤í† ë¦¬ì§€ ì •ì±…ì´ OneDriveì— ì´ˆë‹¹ 보내는 API ìš”ì²­ì˜ ìµœëŒ€ ìˆ˜ëŸ‰ì„ ì œí•œí•©ë‹ˆë‹¤. ì´ ë¹ˆë„를 초과하는 ìš”ì²­ì€ ì†ë„ 제한ë©ë‹ˆë‹¤. 여러 Cloudreve 노드가 파ì¼ì„ 전송할 때 ê°ê° ìžì‹ ì˜ 제한 ë²„í‚·ì„ ì‚¬ìš©í•˜ë¯€ë¡œ ìƒí™©ì— ë”°ë¼ ì´ ìˆ˜ì¹˜ë¥¼ 비례ì ìœ¼ë¡œ 낮춰주세요. 웹 단 업로드 ìš”ì²­ì€ ì´ ì œí•œì„ ë°›ì§€ 않습니다.", + "tpsBurst": "TPS 버스트 요청", + "tpsBurstDes": "ìš”ì²­ì´ ìœ íœ´ ìƒíƒœì¼ 때 Cloudreve는 ì§€ì •ëœ ìˆ˜ëŸ‰ì˜ í• ë‹¹ëŸ‰ì„ ë¯¸ëž˜ì˜ ë²„ìŠ¤íŠ¸ 트래픽 ì‚¬ìš©ì„ ìœ„í•´ 예약할 수 있습니다.", + "odOauthDes": "하지만 아래 ë²„íŠ¼ì„ í´ë¦­í•˜ì—¬ OneDrive ë¡œê·¸ì¸ ì¸ì¦ì„ 사용해 초기화를 완료해야 사용할 수 있습니다. ë‚˜ì¤‘ì— ìŠ¤í† ë¦¬ì§€ ì •ì±… ëª©ë¡ íŽ˜ì´ì§€ì—서 다시 ì¸ì¦í•  수 있습니다.", + "gotoAuthPage": "ì¸ì¦ 페ì´ì§€ë¡œ ì´ë™", + "s3BucketDes": "AWS S3 콘솔로 ì´ë™í•˜ì—¬ 스토리지 ë²„í‚·ì„ ìƒì„±í•˜ê³  ì•„ëž˜ì— ìŠ¤í† ë¦¬ì§€ 버킷 ìƒì„± 시 지정한 <0>Bucket ì´ë¦„ì„ ìž…ë ¥í•˜ì„¸ìš”:", + "s3EndpointDes": "스토리지 ë²„í‚·ì˜ EndPoint(지역 노드)를 지정하고 완전한 URL 형ì‹ìœ¼ë¡œ 입력하세요. 예: <0>https://bucket.region.example.com.", + "selectRegionDes": "스토리지 ë²„í‚·ì´ ìœ„ì¹˜í•œ 지역 코드를 입력하세요. 예: <0>us-east-1. AWSê°€ 아닌 S3 호환 스토리지 ì œê³µì—…ì²´ì˜ ê²½ìš° 해당 문서ì—서 ì´ í•­ëª©ì„ ìž‘ì„±í•˜ëŠ” ë°©ë²•ì„ ì°¾ì•„ë³´ì„¸ìš”.", + "chunkSizeLabelS3": "ë¶„í•  업로드 시 ë¶„í•  í¬ê¸°ë¥¼ 지정하세요. 허용 범위는 5 MB ~ 5 GB입니다.", + "policyEndpoint": "Endpoint", + "s3Region": "지역 코드", + "s3EndpointPathStyle": "경로 í˜•ì‹ Endpoint를 강제로 사용할지 ì„ íƒí•˜ì„¸ìš”. ì¼ë¶€ 타사 S3 호환 스토리지는 ì´ ì˜µì…˜ì„ ì„ íƒí•´ì•¼ í•  수 있습니다. 활성화하면 경로 í˜•ì‹ ì£¼ì†Œë¥¼ 강제로 사용합니다. 예: <0>http://s3.amazonaws.com/BUCKET/KEY.", + "usePathEndpoint": "경로 í˜•ì‹ Endpoint ê°•ì œ 사용", + "thumbExt": "ì¸ë„¤ì¼ì„ ìƒì„±í•  수 있는 íŒŒì¼ í™•ìž¥ìž", + "thumbExtDes": "비워ë‘ë©´ 스토리지 ì •ì±… 사전 ì •ì˜ ì§‘í•©ì„ ì‚¬ìš©í•©ë‹ˆë‹¤. 로컬, S3 스토리지 ì •ì±…ì—는 유효하지 않습니다", + "driverRoot": "드ë¼ì´ë¸Œ 루트 디렉터리", + "driverRootDes": "OneDrive 계정ì—서 파ì¼ì„ 저장할 위치를 ì„ íƒí•˜ì„¸ìš”. ì´ ì˜µì…˜ì„ ë³€ê²½í•˜ë©´ 스토리지 ì •ì±…ì˜ ê¸°ì¡´ 파ì¼ì— 액세스할 수 없게 ë©ë‹ˆë‹¤.", + "saveToDefaultOneDrive": "파ì¼ì„ 기본 OneDrive 드ë¼ì´ë¸Œì— 저장", + "saveToSharePoint": "파ì¼ì„ SharePointì— ì €ìž¥", + "sharePointUrlDes": "SharePoint 사ì´íЏ URLì„ ìž…ë ¥í•˜ì„¸ìš”. í¬ì»¤ìŠ¤ë¥¼ 잃으면 ì‹œìŠ¤í…œì´ ìžë™ìœ¼ë¡œ 올바른 드ë¼ì´ë¸Œ ì‹ë³„ìžë¡œ 변환합니다.", + "ks3selectRegionDes": "스토리지 ë²„í‚·ì´ ìœ„ì¹˜í•œ 지역 코드를 입력하세요. 예: <0>BEIJING.", + "ks3EndpointPathStyle": "경로 í˜•ì‹ Endpoint를 강제로 사용할지 ì„ íƒí•˜ì„¸ìš”.", + "ossRegionDes": "ë²„í‚·ì´ ìœ„ì¹˜í•œ 지역 코드를 입력하세요. 예: <0>cn-hangzhou. <1>OSS 지역 ë° ì—”ë“œí¬ì¸íЏ 표ì—서 해당 ì§€ì—­ì„ ì°¾ì•„ 해당하는 <2>지역 ID를 입력할 수 있습니다." + }, + "node": { + "slave": "슬레ì´ë¸Œ", + "master": "마스터", + "noCapabilities": "í™œì„±í™”ëœ ê¸°ëŠ¥ì´ ì—†ìŠµë‹ˆë‹¤", + "active": "활성화ë¨", + "suspended": "비활성화ë¨", + "deleteNodeConfirmation": "노드 {{name}}ì„(를) 삭제하시겠습니까?", + "editNode": "노드 {{node}} 편집", + "thisIsMasterNodes": "현재 사ì´íŠ¸ë¥¼ 서비스하고 있는 Cloudreve ì¸ìŠ¤í„´ìŠ¤ì¸ ë§ˆìŠ¤í„° 노드를 편집하고 있습니다.", + "enableNode": "노드 활성화", + "enableNodeDes": "노드를 활성화하면 í™œì„±í™”ëœ ê¸°ëŠ¥ì˜ ì²˜ë¦¬ë¥¼ 수ë½í•©ë‹ˆë‹¤.", + "name": "ì´ë¦„", + "nameNode": "노드 ì´ë¦„으로, 사용ìžì—ê²Œë„ í‘œì‹œë©ë‹ˆë‹¤.", + "type": "유형", + "server": "노드 주소", + "serverDes": "노드와 통신하는 ë° ì‚¬ìš©ë˜ëŠ” 주소입니다. ì´ ë…¸ë“œì— íŒŒì¼ì„ 저장하려는 경우 ì´ ì£¼ì†ŒëŠ” íŒŒì¼ ì—…ë¡œë“œë¥¼ 위해 ì‚¬ìš©ìž ì¸¡ì—ë„ ë…¸ì¶œë©ë‹ˆë‹¤.", + "loadBalancerRankDes": "ì´ ë…¸ë“œì— ë¡œë“œ 밸런싱 가중치를 지정하세요. ê°’ì€ ì •ìˆ˜ì´ë©°, 가중치가 높ì„ìˆ˜ë¡ ë…¸ë“œê°€ ì„ íƒë  í™•ë¥ ì´ ë†’ì•„ì§‘ë‹ˆë‹¤.", + "loadBalancerRank": "로드 밸런싱 가중치", + "slaveSecret": "슬레ì´ë¸Œ 키", + "slaveSecretDes": "슬레ì´ë¸Œ 노드와 마스터 노드 ê°„ í†µì‹ ì— ì‚¬ìš©ë˜ëŠ” 키입니다. 슬레ì´ë¸Œ 구성 파ì¼ì˜ <0>Slave í•˜ì˜ <1>Secretê³¼ ì¼ì¹˜í•´ì•¼ 합니다.", + "testNode": "노드 통신 테스트", + "testNodeSuccess": "노드 통신 성공", + "createArchiveDes": "ì••ì¶• íŒŒì¼ ìƒì„± 작업 ìš”ì²­ì„ ìˆ˜ë½í•©ë‹ˆë‹¤.", + "extractArchiveDes": "íŒŒì¼ ì••ì¶• í•´ì œ 작업 ìš”ì²­ì„ ìˆ˜ë½í•©ë‹ˆë‹¤.", + "remoteDownloadDes": "오프ë¼ì¸ 다운로드 작업 ìš”ì²­ì„ ìˆ˜ë½í•©ë‹ˆë‹¤. 활성화 후 아래ì—서 오프ë¼ì¸ 다운로드 관련 정보를 구성해야 합니다.", + "downloader": "다운로ë”", + "aria2Des": "ëŒ€ìƒ ë…¸ë“œ 서버ì—서 Cloudreve를 실행하는 것과 ë™ì¼í•œ 사용ìž/권한으로 Aria2를 시작하고, Aria2ì˜ êµ¬ì„± 파ì¼ì—서 RPC 서비스를 활성화하세요. ìžì„¸í•œ 정보와 ê°€ì´ë“œëŠ” ë¬¸ì„œì˜ \"오프ë¼ì¸ 다운로드\" ì„¹ì…˜ì„ ì°¸ì¡°í•˜ì„¸ìš”.", + "qbittorrentDes": "ëŒ€ìƒ ë…¸ë“œ 서버ì—서 Cloudreve를 실행하는 것과 ë™ì¼í•œ 사용ìž/권한으로 qBittorrent를 시작하고, qBittorrent 설정ì—서 \"Web UI\" 서비스를 활성화하세요. ìžì„¸í•œ 정보와 ê°€ì´ë“œëŠ” ë¬¸ì„œì˜ \"오프ë¼ì¸ 다운로드\" ì„¹ì…˜ì„ ì°¸ì¡°í•˜ì„¸ìš”.", + "rpcServer": "RPC 서버 주소", + "rpcServerHelpDes": "í¬íŠ¸ë¥¼ í¬í•¨í•œ 완전한 RPC 서버 주소입니다. 예: <0>http://127.0.0.1:6800/.", + "rpcToken": "RPC ì¸ì¦ 토í°", + "rpcTokenDes": "Aria2 구성 파ì¼ì˜ <0>rpc-secretê³¼ ì¼ì¹˜í•´ì•¼ 하며, 설정하지 ì•Šì€ ê²½ìš° 비워ë‘세요.", + "downloaderOptionDes": "다운로드 작업 ìƒì„± 시 추가로 전달ë˜ëŠ” ë‹¤ìš´ë¡œë” êµ¬ì„±ìœ¼ë¡œ, JSON 키-ê°’ ìŒ í˜•ì‹ìœ¼ë¡œ 작성하며, 구체ì ì¸ ë‚´ìš©ì€ <0>ë‹¤ìš´ë¡œë” ê³µì‹ ë¬¸ì„œë¥¼ 참조하세요.", + "refreshInterval": "ìƒíƒœ 새로고침 간격 (ì´ˆ)", + "refreshIntervalDes": "Cloudreveê°€ 다운로ë”ì— ìž‘ì—… ìƒíƒœ ìƒˆë¡œê³ ì¹¨ì„ ìš”ì²­í•˜ëŠ” 간격입니다. 실제 새로고침 ê°„ê²©ì€ \"오프ë¼ì¸ 다운로드\" íì˜ êµ¬ì„±ê³¼ ë°”ìœ ì •ë„ì— ë”°ë¼ ë‹¬ë¼ì§‘니다.", + "waitForSeeding": "시딩 완료 대기", + "waitForSeedingDes": "활성화하면 오프ë¼ì¸ 다운로드 ìž‘ì—…ì´ ì™„ë£Œëœ í›„ ë‹¤ìš´ë¡œë” êµ¬ì„±ì˜ ì‹œë”© 종료 ì¡°ê±´ì´ ì¶©ì¡±ë  ë•Œê¹Œì§€ ì´ ìž‘ì—…ì„ ì‹œë”© ìƒíƒœë¡œ 유지합니다. 시딩 대기는 오프ë¼ì¸ 다운로드 작업 완료 í›„ì— ë°œìƒí•˜ë©° 사용ìžì˜ ë‹¤ìš´ë¡œë“œëœ íŒŒì¼ ì‚¬ìš©ì— ì˜í–¥ì„ 주지 않습니다.", + "webUIEndpoint": "Web UI 주소", + "webUIEndpointDes": "qBittorrentì˜ Web UI 주소입니다. 예: <0>http://127.0.0.1:8080/.", + "tempPath": "임시 다운로드 디렉터리", + "tempPathDes": "노드ì—서 오프ë¼ì¸ 다운로드 파ì¼ì„ 임시로 저장하는 ë° ì‚¬ìš©ë˜ëŠ” 디렉터리입니다. ë…¸ë“œì˜ Cloudreve 프로세스는 ì´ ë””ë ‰í„°ë¦¬ì— ëŒ€í•œ ì½ê¸°, 쓰기, 실행 ê¶Œí•œì´ í•„ìš”í•˜ë©°, 다운로ë”ë„ ì´ ë””ë ‰í„°ë¦¬ì— ì•¡ì„¸ìŠ¤í•  수 있어야 합니다. 비워ë‘ë©´ 기본 임시 íŒŒì¼ ê²½ë¡œë¥¼ 사용합니다.", + "webUIUsername": "Web UI 사용ìžëª…", + "webUIPassword": "Web UI 비밀번호", + "webUICredDes": "ì¸ì¦ì„ 활성화하지 ì•Šì€ ê²½ìš° 여기를 비워ë‘세요.", + "downloaderTestPass": "다운로ë”ì— ì„±ê³µì ìœ¼ë¡œ ì—°ê²°ë˜ì—ˆìŠµë‹ˆë‹¤. 버전: {{version}}", + "testDownloader": "ë‹¤ìš´ë¡œë” í†µì‹  테스트", + "addNewNode": "새 노드 ìƒì„±", + "nameTheNode": "노드 ì´ë¦„ 지정:", + "runCrSlave": "노드ì—서 마스터 사ì´íŠ¸ì™€ ë™ì¼í•œ ë²„ì „ì˜ Cloudreve를 실행하고 ë‹¤ìŒ êµ¬ì„± 파ì¼ë¡œ 시작하세요:", + "keepIfUpload": "향후 ì´ ë…¸ë“œë¥¼ ìŠ¤í† ë¦¬ì§€ì— ì‚¬ìš©í•´ì•¼ 하는 경우 ì•„ëž˜ì˜ êµì°¨ 출처 êµ¬ì„±ì„ ìœ ì§€í•˜ì„¸ìš”.", + "storeFiles": "íŒŒì¼ ì €ìž¥", + "storeFilesDes": "ì´ ë…¸ë“œë¥¼ 사용하여 ì‚¬ìš©ìž íŒŒì¼ì„ 저장합니다.", + "storeFilesHint": "ì´ ë…¸ë“œë¥¼ 사용하여 파ì¼ì„ 저장하려면 <0>스토리지 ì •ì±… 페ì´ì§€ë¡œ ì´ë™í•˜ì—¬ 새 슬레ì´ë¸Œ 스토리지 ì •ì±…ì„ ìƒì„±í•˜ê³  ì´ ë…¸ë“œë¥¼ ì„ íƒí•˜ì„¸ìš”.", + "runCrWithConfig": "ìœ„ì˜ íŒŒì¼ì„ <0>config.ini 파ì¼ë¡œ 저장하고 ì´ íŒŒì¼ì„ 사용하여 Cloudreve를 시작하세요: <0>./cloudreve -c config.ini. í•˜ë‚˜ì˜ ìŠ¬ë ˆì´ë¸Œ Cloudreve ì¸ìŠ¤í„´ìŠ¤ëŠ” 여러 Cloudreve 마스터 ë…¸ë“œì— ì—°ê²°í•  수 있으며, 모든 마스터 노드ì—서 ì´ ìŠ¬ë ˆì´ë¸Œ 노드를 추가하고 키를 ì¼ì¹˜ì‹œí‚¤ê¸°ë§Œ 하면 ë©ë‹ˆë‹¤.", + "inputServer": "ë…¸ë“œì˜ ì£¼ì†Œë¥¼ 입력하세요:", + "testButton": "아래 ë²„íŠ¼ì„ í´ë¦­í•˜ì—¬ í†µì‹ ì´ ì •ìƒì¸ì§€ 테스트할 수 있습니다.", + "hostHeaderHint": "서명 오류가 있는 경우 슬레ì´ë¸Œ ì•žì˜ ì—­ë°©í–¥ 프ë¡ì‹œê°€ <0>Host í—¤ë”를 전달하는지 확ì¸í•˜ì„¸ìš”.", + "features": "í™œì„±í™”ëœ ê¸°ëŠ¥", + "remoteDownload": "오프ë¼ì¸ 다운로드", + "refresh": "새로고침" + }, + "group": { + "countUser": "통계", + "anonymous": "로그ì¸í•˜ì§€ ì•Šì€ ìµëª… ì‚¬ìš©ìž ê·¸ë£¹", + "sysGroup": "시스템 ì‚¬ìš©ìž ê·¸ë£¹", + "adminGroup": "ê´€ë¦¬ìž ì‚¬ìš©ìž ê·¸ë£¹", + "#": "#", + "name": "ì´ë¦„", + "type": "스토리지 ì •ì±…", + "count": "하위 ì‚¬ìš©ìž ìˆ˜", + "size": "최대 용량", + "nameOfGroup": "ì‚¬ìš©ìž ê·¸ë£¹ëª…", + "nameOfGroupDes": "ì‚¬ìš©ìž ê·¸ë£¹ì˜ ì´ë¦„으로, 사용ìžì—게 표시하는 ë° ì‚¬ìš©ë©ë‹ˆë‹¤.", + "availablePolicies": "사용 가능한 스토리지 ì •ì±…", + "availablePoliciesDes": "ì‚¬ìš©ìž ê·¸ë£¹ì´ ì‚¬ìš©í•  수 있는 스토리지 ì •ì±…ì„ ì§€ì •í•˜ì„¸ìš”. 다중 ì„ íƒ ê°€ëŠ¥í•˜ë©°, 사용ìžëŠ” ì„ íƒëœ 범위 ë‚´ì—서 ìžìœ ë¡­ê²Œ 스토리지 ì •ì±…ì„ ì „í™˜í•  수 있습니다. ì´ ì„¤ì •ì„ ìˆ˜ì •í•´ë„ ì‚¬ìš©ìžê°€ ì´ë¯¸ 업로드한 파ì¼ì—는 ì˜í–¥ì„ 주지 않습니다.", + "initialStorageQuota": "초기 용량", + "initialStorageQuotaDes": "ì‚¬ìš©ìž ê·¸ë£¹ í•˜ì˜ ì‚¬ìš©ìž ì´ˆê¸° 사용 가능 최대 용량입니다.", + "isAdmin": "ê´€ë¦¬ìž ì‚¬ìš©ìž ê·¸ë£¹", + "isAdminDes": "활성화하면 ì‚¬ìš©ìž ê·¸ë£¹ í•˜ì˜ ì‚¬ìš©ìžê°€ ê´€ë¦¬ìž ê¶Œí•œì„ ê°–ê²Œ ë©ë‹ˆë‹¤.", + "share": "공유", + "allowCreateShareLink": "공유 ë§í¬ ìƒì„±", + "allowCreateShareLinkDes": "비활성화하면 사용ìžê°€ 공유 ë§í¬ë¥¼ ìƒì„±í•  수 없습니다.", + "shareFree": "공유 ë§í¬ 구매 불필요", + "shareFreeDes": "활성화하면 사용ìžê°€ 구매하지 ì•Šê³ ë„ ëª¨ë“  유료 공유 ë§í¬ì— 액세스할 수 있습니다.", + "fileManagement": "íŒŒì¼ ê´€ë¦¬", + "allowWabDAV": "WebDAV", + "allowWabDAVDes": "비활성화하면 사용ìžê°€ WebDAV í”„ë¡œí† ì½œì„ í†µí•´ ë„¤íŠ¸ì›Œí¬ ë“œë¼ì´ë¸Œì— ì—°ê²°í•  수 없습니다.", + "allowWabDAVProxy": "WebDAV 프ë¡ì‹œ", + "allowWabDAVProxyDes": "활성화하면 사용ìžê°€ WebDAV 다운로드를 Cloudreve를 통해 중계하ë„ë¡ êµ¬ì„±í•  수 있습니다.", + "compressTask": "ì••ì¶•/ì••ì¶• í•´ì œ 파ì¼", + "compressTaskDes": "활성화하면 사용ìžê°€ 온ë¼ì¸ìœ¼ë¡œ 파ì¼ì„ ì••ì¶•/ì••ì¶• 해제할 수 있습니다.", + "compressSize": "ì••ì¶•í•  파ì¼ì˜ 최대 í¬ê¸°", + "compressSizeDes": "사용ìžê°€ ìƒì„±í•  수 있는 ì••ì¶• ìž‘ì—…ì˜ íŒŒì¼ ìµœëŒ€ ì´ í¬ê¸°ìž…니다. 0으로 입력하면 제한하지 않습니다. ì´ ì œí•œì€ ì••ì¶• 작업 ìƒì„± 시 확ì¸í•˜ì§€ 않으며, 실행 시 ì²˜ë¦¬ëœ ì›ë³¸ íŒŒì¼ ì´ í¬ê¸°ê°€ ì´ ì œí•œì„ ì´ˆê³¼í•˜ë©´ ìž‘ì—…ì´ ì‹¤íŒ¨í•©ë‹ˆë‹¤.", + "decompressSize": "ì••ì¶• 해제할 파ì¼ì˜ 최대 í¬ê¸°", + "decompressSizeDes": "사용ìžê°€ ìƒì„±í•  수 있는 ì••ì¶• í•´ì œ ìž‘ì—…ì˜ íŒŒì¼ ìµœëŒ€ ì´ í¬ê¸°ìž…니다. 0으로 입력하면 제한하지 않습니다.", + "allowRemoteDownload": "오프ë¼ì¸ 다운로드", + "allowRemoteDownloadDes": "사용ìžê°€ 오프ë¼ì¸ 다운로드 ìž‘ì—…ì„ ìƒì„±í•  수 있는지 여부입니다. 오프ë¼ì¸ 다운로드를 사용하려면 <0>노드 목ë¡ì—서 오프ë¼ì¸ 다운로드 ê¸°ëŠ¥ì´ í™œì„±í™”ëœ ë…¸ë“œê°€ 필요합니다.", + "aria2Options": "ë‹¤ìš´ë¡œë” ìž‘ì—… 매개변수", + "aria2OptionsDes": "qBittorrent ë˜ëŠ” Aria2 다운로ë”ì˜ ìž‘ì—… 추가 구성 매개변수로, JSON으로 ì¸ì½”ë”©ëœ í‚¤-ê°’ 형ì‹ìœ¼ë¡œ 작성하며, 사용 가능한 매개변수는 ê³µì‹ ë¬¸ì„œë¥¼ 참조하세요.", + "aria2BatchSize": "ì¼ê´„ 오프ë¼ì¸ 다운로드 최대 수량", + "aria2BatchSizeDes": "ì¼ê´„ 오프ë¼ì¸ 다운로드 ìƒì„± 시 최대 수량입니다. 0으로 입력하면 제한하지 않습니다.", + "migratePolicy": "스토리지 ì •ì±… 전송", + "migratePolicyDes": "사용ìžê°€ 스토리지 ì •ì±… 전송 ìž‘ì—…ì„ ìƒì„±í•  수 있는지 여부입니다.", + "advanceDelete": "고급 íŒŒì¼ ì‚­ì œ 옵션", + "advanceDeleteDes": "활성화하면 사용ìžê°€ 프론트엔드ì—서 파ì¼ì„ 삭제할 때 ë¬¼ë¦¬ì  íŒŒì¼ì„ ë³´ì¡´í• ì§€ 여부를 ì„ íƒí•  수 있습니다. 신뢰할 수 있는 ì‚¬ìš©ìž ê·¸ë£¹ì—ë§Œ 개방하세요.", + "allowSelectNode": "노드 ì„ íƒ í—ˆìš©", + "allowSelectNodeDes": "활성화하면 사용ìžê°€ 작업 ìƒì„± ì „ì— ì²˜ë¦¬ 노드를 ì„ íƒí•  수 있습니다. 비활성화하면 ì‹œìŠ¤í…œì´ ì‚¬ìš©ìž ê·¸ë£¹ì´ í—ˆìš©í•˜ëŠ” 노드 하ì—서 ìžë™ìœ¼ë¡œ 노드를 할당합니다.", + "allowedNodes": "사용 가능한 노드", + "allowedNodesDes": "ì‚¬ìš©ìž ê·¸ë£¹ì´ ì‚¬ìš©í•  수 있는 작업 처리 노드를 지정하세요. 비워ë‘ë©´ 모든 노드를 사용할 수 있습니다. 사용ìžëŠ” ì´ ëª©ë¡ ë‚´ì—서만 ì„ íƒí•˜ê±°ë‚˜ 로드 밸런싱으로 노드를 í• ë‹¹ë°›ì„ ìˆ˜ 있습니다. 현재 ì ìš©ë˜ëŠ” 작업 범위는 오프ë¼ì¸ 다운로드, íŒŒì¼ ì••ì¶• ë˜ëŠ” ì••ì¶• 해제입니다. 다른 ìž‘ì—…ì€ ë§ˆìŠ¤í„°ì— í• ë‹¹ë˜ì–´ 처리ë©ë‹ˆë‹¤.", + "allNodes": "모든 노드", + "esclateAnonymity": "ìµëª… ì‚¬ìš©ìž ê¶Œí•œ í–¥ìƒ", + "esclateAnonymityDes": "활성화하면 사용ìžê°€ ìµëª… 사용ìžì—게 ë” ë†’ì€ ê¶Œí•œ(수정/ìƒì„±/ì‚­ì œ)ì„ ì„¤ì •í•  수 있습니다. 비활성화하면 사용ìžëŠ” 최대 ìµëª… 사용ìžì—게 ì½ê¸° ì „ìš© 권한만 부여할 수 있습니다. ì´ ì„¤ì •ì„ ë³€ê²½í•´ë„ ì´ë¯¸ ì„¤ì •ëœ ê³µìœ  ë§í¬ë‚˜ 파ì¼ì—는 ì˜í–¥ì„ 주지 않습니다.", + "allowDownloadShare": "공유 ë§í¬ 액세스", + "allowDownloadShareDes": "비활성화하면 사용ìžê°€ 다른 ì‚¬ëžŒì˜ ê³µìœ  ë§í¬ë¥¼ ë³¼ 수 없습니다. ì´ ì„¤ì •ì€ ê³µìœ  ë§í¬ì˜ 권한 설정보다 우선순위가 높습니다.", + "deletedNode": "ì‚­ì œëœ ë…¸ë“œ #{{id}}", + "maxWalkedFiles": "최대 íƒìƒ‰ íŒŒì¼ ìˆ˜", + "maxWalkedFilesDes": "파ì¼ì„ ê¹Šì´ íƒìƒ‰í•´ì•¼ 하는 ì¼ë¶€ 작업ì—서 허용ë˜ëŠ” 최대 íƒìƒ‰ íŒŒì¼ ìˆ˜ìž…ë‹ˆë‹¤.", + "trashBinDuration": "휴지통 ë³´ì¡´ 시간 (ì´ˆ)", + "trashBinDurationDes": "íœ´ì§€í†µì˜ íŒŒì¼ ë³´ì¡´ 기간으로, ê¸°í•œì´ ì§€ë‚˜ë©´ 파ì¼ì´ 완전히 ì‚­ì œë©ë‹ˆë‹¤. ì´ ì„¤ì •ì„ ë³€ê²½í•´ë„ ì´ë¯¸ íœ´ì§€í†µì— ìžˆëŠ” 파ì¼ì—는 ì˜í–¥ì„ 주지 않습니다.", + "serverSideBatchDownload": "서버 측 ì¼ê´„ 다운로드", + "serverSideBatchDownloadDes": "사용ìžê°€ 여러 파ì¼ì„ ì„ íƒí•˜ì—¬ 서버 측 중계 ì¼ê´„ 다운로드를 사용할 수 있는지 여부입니다. ë¹„í™œì„±í™”í•´ë„ ì‚¬ìš©ìžëŠ” 여전히 순수 웹 단 ì¼ê´„ 다운로드 ê¸°ëŠ¥ì„ ì‚¬ìš©í•  수 있습니다.", + "uploadDownload": "업로드 ë° ë‹¤ìš´ë¡œë“œ", + "getDirectLink": "ì§ì ‘ ë§í¬ 가져오기", + "getDirectLinkDes": "사용ìžê°€ 파ì¼ì˜ ì§ì ‘ ë§í¬ë¥¼ 가져올 수 있는지 여부입니다.", + "bathSourceLinkLimit": "ì¼ê´„ 외부 ì§ì ‘ ë§í¬ ìƒì„± 수량 제한", + "bathSourceLinkLimitDes": "사용ìžê°€ 한 ë²ˆì— ì¼ê´„로 ì§ì ‘ ë§í¬ë¥¼ 가져올 수 있는 최대 íŒŒì¼ ìˆ˜ëŸ‰ìž…ë‹ˆë‹¤. 0으로 입력하면 ì§ì ‘ ë§í¬ 가져오기를 허용하지 않습니다.", + "redirectedSource": "ë¦¬ë””ë ‰ì…˜ì„ ì‚¬ìš©í•˜ëŠ” ì§ì ‘ ë§í¬", + "redirectedSourceDes": "활성화를 권장합니다. 활성화하면 사용ìžê°€ 가져오는 íŒŒì¼ ì§ì ‘ ë§í¬ê°€ Cloudreve를 통해 중계ë˜ì–´ ë§í¬ê°€ ë” ì§§ì•„ì§‘ë‹ˆë‹¤. 비활성화하면 사용ìžê°€ 가져오는 íŒŒì¼ ì§ì ‘ ë§í¬ê°€ 파ì¼ì˜ ì›ë³¸ ë§í¬ë¡œ 변경ë˜ë©° íŒŒì¼ ë²„ì „ê³¼ ë°”ì¸ë”©ë©ë‹ˆë‹¤. ì¼ë¶€ 스토리지 ì •ì±…ì€ íŠ¹ì • 설정ì—서 가져오는 비중계 ì§ì ‘ ë§í¬ê°€ ì˜êµ¬ì ìœ¼ë¡œ 유효하지 ì•Šì„ ìˆ˜ 있으므로 Cloudreve 문서를 참조하세요.", + "reuseDirectLink": "기존 ì§ì ‘ ë§í¬ 재사용", + "reuseDirectLinkDes": "활성화하면 ê°™ì€ íŒŒì¼ì˜ ì§ì ‘ ë§í¬ë¥¼ 여러 번 요청할 때 ì´ë¯¸ ìƒì„±ëœ 중계 ì§ì ‘ ë§í¬ë¥¼ 재사용합니다.", + "downloadSpeedLimit": "다운로드 ì†ë„ 제한", + "downloadSpeedLimitDes": "0으로 입력하면 제한하지 않습니다. ì œí•œì„ í™œì„±í™”í•˜ë©´ 사용ìžê°€ ì†ë„ ì œí•œì„ ì§€ì›í•˜ëŠ” 모든 스토리지 ì •ì±… í•˜ì˜ íŒŒì¼ì„ 다운로드할 때 최대 다운로드 ì†ë„ê°€ 제한ë©ë‹ˆë‹¤.", + "anonymousHint": "ì´ ì‚¬ìš©ìž ê·¸ë£¹ì€ ë¡œê·¸ì¸í•˜ì§€ ì•Šì€ ìµëª… 방문ìžì— 해당합니다.", + "create": "새로 ìƒì„±", + "copyFromExisting": "기존 ì‚¬ìš©ìž ê·¸ë£¹ì—서 복사하시겠습니까?", + "notCopy": "복사하지 않ìŒ", + "confirmDelete": "ì‚¬ìš©ìž ê·¸ë£¹ {{group}}ì„(를) 삭제하시겠습니까?", + "new": "새 ì‚¬ìš©ìž ê·¸ë£¹", + "editGroup": "{{group}} 편집" + }, + "user": { + "createdAt": "ìƒì„± ë‚ ì§œ", + "originUserGroup": "ì›ëž˜ ì‚¬ìš©ìž ê·¸ë£¹", + "originUserGroupDes": "사용ìžê°€ ì‚¬ìš©ìž ê·¸ë£¹ì„ êµ¬ë§¤í•˜ê¸° ì „ì— ì†í–ˆë˜ ì‚¬ìš©ìž ê·¸ë£¹ìœ¼ë¡œ, 현재 ì‚¬ìš©ìž ê·¸ë£¹ì´ ë§Œë£Œë˜ë©´ ì´ ì‚¬ìš©ìž ê·¸ë£¹ìœ¼ë¡œ ë˜ëŒì•„갑니다.", + "noOriginUserGroup": "ì—†ìŒ", + "groupExpired": "ì‚¬ìš©ìž ê·¸ë£¹ 만료ì¼", + "groupExpiredDes": "ISO8601 형ì‹ì˜ ì‚¬ìš©ìž ê·¸ë£¹ 만료ì¼ë¡œ, 비워ë‘ë©´ 현재 ì‚¬ìš©ìž ê·¸ë£¹ì´ ì˜êµ¬ì ìœ¼ë¡œ 유효합니다.", + "openUserFiles": "ì‚¬ìš©ìž íŒŒì¼ ì—´ê¸°", + "id": "ID", + "idValue": "{{id}} ({{hash_id}})", + "avatar": "아바타", + "removeAvatar": "아바타 제거", + "userDialogTitle": "ì‚¬ìš©ìž ì„¸ë¶€ ì •ë³´", + "2FAEnabled": "2단계 ì¸ì¦ 활성화ë¨", + "qqEnabled": "QQ ë°”ì¸ë”©ë¨", + "logtoEnabled": "Logto ë°”ì¸ë”©ë¨", + "oidcEnabled": "OIDC ë°”ì¸ë”©ë¨", + "deleted": "사용ìžê°€ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤", + "new": "새 사용ìž", + "filter": "í•„í„°", + "emptyNoFilter": "비워ë‘ë©´ ì´ í•­ëª©ì„ í•„í„°ë§í•˜ì§€ 않습니다.", + "selectedObjects": "{{num}}ê°œ ê°ì²´ê°€ ì„ íƒë˜ì—ˆìŠµë‹ˆë‹¤", + "nick": "닉네임", + "email": "ì´ë©”ì¼", + "group": "ì‚¬ìš©ìž ê·¸ë£¹", + "status": "ìƒíƒœ", + "usedStorage": "ì‚¬ìš©ëœ ê³µê°„", + "status_active": "ì •ìƒ", + "status_inactive": "활성화ë˜ì§€ 않ìŒ", + "status_manual_banned": "ìˆ˜ë™ ì°¨ë‹¨", + "status_sys_banned": "시스템 차단", + "toggleBan": "차단/차단 í•´ì œ", + "filterCondition": "í•„í„° ì¡°ê±´", + "all": "ì „ì²´", + "userStatus": "ì‚¬ìš©ìž ìƒíƒœ", + "apply": "ì ìš©", + "editUser": "{{nick}} 편집", + "password": "비밀번호", + "passwordDes": "비워ë‘ë©´ 수정하지 않습니다", + "groupDes": "사용ìžê°€ ì†í•œ ì‚¬ìš©ìž ê·¸ë£¹", + "2FA": "2단계 ì¸ì¦", + "notEnabled": "활성화ë˜ì§€ 않ìŒ", + "reset2Fa": "비활성화", + "reset": "재설정", + "confirmDelete": "ì‚¬ìš©ìž {{user}}를 삭제하시겠습니까?", + "deleteXUsers": "{{num}}명 ì‚¬ìš©ìž ì‚­ì œ", + "confirmBatchDelete": "{{num}}명 사용ìžë¥¼ 삭제하시겠습니까?", + "calibrateStorage": "스토리지 공간 ë³´ì •", + "calibrateStorageSuccess": "스토리지 공간 ë³´ì • 성공" + }, + "file": { + "deleteXFiles": "{{num}}ê°œ íŒŒì¼ ì‚­ì œ", + "confirmBatchDelete": "{{num}}ê°œ 파ì¼ì„ 삭제하시겠습니까?", + "confirmDelete": "íŒŒì¼ {{file}}ì„(를) 삭제하시겠습니까?", + "haveShares": "공유 ë§í¬ 보유", + "haveDirectLinks": "중계 ì§ì ‘ ë§í¬ 보유", + "directLinkId": "ë§í¬ ì‹ë³„ìž", + "directLinks": "중계 ì§ì ‘ ë§í¬", + "noRecords": "기ë¡ì´ 없습니다", + "speed": "ì†ë„ 제한", + "downloads": "다운로드 횟수", + "shareLink": "공유 ë§í¬", + "shareLinkNum": "{{num}}ê°œ (<0>보기)", + "blobType": "유형", + "noEntities": "Blobì´ ì—†ìŠµë‹ˆë‹¤", + "blobs": "Blobs", + "creator": "ìƒì„±ìž", + "source": "소스", + "key": "키", + "value": "ê°’", + "isPublic": "공개", + "noMetadata": "메타ë°ì´í„°ê°€ 없습니다", + "metadata": "메타ë°ì´í„°", + "id": "ID", + "primaryStoragePolicy": "기본 스토리지 ì •ì±…", + "fileDialogTitle": "íŒŒì¼ ì„¸ë¶€ ì •ë³´", + "name": "파ì¼ëª…", + "deleteAsync": "ì‚­ì œ ìž‘ì—…ì´ ë°±ê·¸ë¼ìš´ë“œì—서 실행ë©ë‹ˆë‹¤", + "forceDelete": "ê°•ì œ ì‚­ì œ", + "size": "í¬ê¸°", + "sizeUsed": "사용 공간", + "uploader": "소유ìž", + "createdAt": "ìƒì„±ì¼", + "uploading": "업로드 중", + "unknownUploader": "알 수 ì—†ìŒ", + "uploaderID": "ì†Œìœ ìž ID", + "searchFileName": "파ì¼ëª… 검색", + "storagePolicy": "스토리지 ì •ì±…", + "selectTargetUser": "먼저 ëŒ€ìƒ ì‚¬ìš©ìžë¥¼ ì„ íƒí•˜ì„¸ìš”", + "importTaskCreated": "가져오기 ìž‘ì—…ì´ ìƒì„±ë˜ì—ˆìŠµë‹ˆë‹¤. \"백그ë¼ìš´ë“œ 작업\"ì—서 실행 ìƒí™©ì„ 확ì¸í•  수 있습니다", + "manuallyPathOnly": "ì„ íƒí•œ 스토리지 ì •ì±…ì€ ìˆ˜ë™ ê²½ë¡œ 입력만 ì§€ì›í•©ë‹ˆë‹¤", + "selectFolder": "디렉터리 ì„ íƒ", + "import": "가져오기", + "importExternalFolder": "외부 디렉터리 가져오기", + "importExternalFolderDes": "스토리지 ì •ì±…ì˜ ê¸°ì¡´ íŒŒì¼ ë° ë””ë ‰í„°ë¦¬ 구조를 Cloudreve로 가져올 수 있습니다. 가져오기 ìž‘ì—…ì€ ë¬¼ë¦¬ì  ìŠ¤í† ë¦¬ì§€ ê³µê°„ì„ ì¶”ê°€ë¡œ 차지하지 않지만 사용ìžì˜ ì‚¬ìš©ëœ ìš©ëŸ‰ ê³µê°„ì€ ì •ìƒì ìœ¼ë¡œ ì°¨ê°ë©ë‹ˆë‹¤.", + "storagePolicyDes": "가져올 파ì¼ì´ 현재 ì €ìž¥ëœ ìŠ¤í† ë¦¬ì§€ ì •ì±…ì„ ì„ íƒí•˜ì„¸ìš”.", + "targetUser": "ëŒ€ìƒ ì‚¬ìš©ìž", + "targetUserDes": "파ì¼ì„ ì–´ëŠ ì‚¬ìš©ìžì˜ íŒŒì¼ ì‹œìŠ¤í…œìœ¼ë¡œ 가져올지 ì„ íƒí•˜ì„¸ìš”.", + "srcFolderPath": "ì›ë³¸ 디렉터리 경로", + "select": "ì„ íƒ", + "selectSrcDes": "가져올 ë””ë ‰í„°ë¦¬ì˜ ìŠ¤í† ë¦¬ì§€ 측 경로입니다.", + "dstFolderPath": "ëŒ€ìƒ ë””ë ‰í„°ë¦¬ 경로", + "dstFolderPathDes": "디렉터리를 ì‚¬ìš©ìž íŒŒì¼ ì‹œìŠ¤í…œì˜ ì–´ëŠ ê²½ë¡œë¡œ 가져올지 지정합니다.", + "recursivelyImport": "하위 디렉터리 재귀 가져오기", + "recursivelyImportDes": "디렉터리 í•˜ì˜ ëª¨ë“  하위 디렉터리를 재귀ì ìœ¼ë¡œ 가져올지 여부입니다.", + "createImportTask": "가져오기 작업 ìƒì„±", + "unlink": "ì—°ê²° í•´ì œ (ë¬¼ë¦¬ì  íŒŒì¼ ë³´ì¡´)", + "searchUser": "ì‚¬ìš©ìž ë‹‰ë„¤ìž„ ë˜ëŠ” ì´ë©”ì¼ ê²€ìƒ‰...", + "extractMediaMeta": "미디어 ì •ë³´ 추출", + "extractMediaMetaDes": "파ì¼ì„ 가져오는 ë™ì‹œì— ê° íŒŒì¼ì˜ 미디어 ì •ë³´ ì¶”ì¶œì„ ì‹œë„í• ì§€ 여부입니다.", + "importWarning": "주ì˜ì‚¬í•­", + "importWarnings": [ + "가져온 후 ë¬¼ë¦¬ì  íŒŒì¼ì€ Cloudreveê°€ 관리하므로, ì´í›„ 외부ì—서 ì´ íŒŒì¼ì„ 수정하지 마세요.", + "ê°™ì€ íŒŒì¼ì„ 중복으로 가져오지 마세요.", + "ì‚¬ìš©ìž íŒŒì¼ì´ ì¶©ëŒí•˜ëŠ” 경우 ì´ íŒŒì¼ì€ 건너뛰어집니다." + ], + "otherConditions": "기타 ì¡°ê±´", + "shareLinkExisted": "공유 ë§í¬ 존재", + "directLinkExisted": "중계 ì§ì ‘ ë§í¬ 존재", + "isUploading": "업로드 중" + }, + "entity": { + "refenenceCount": "참조 횟수", + "waitForRecycle": "회수 대기", + "entityDialogTitle": "Blob 세부 ì •ë³´", + "uploadSessionID": "업로드 세션 ID", + "referredFiles": "ì—°ê´€ 파ì¼", + "confirmBatchDelete": "{{num}}ê°œ Blobì„ ì‚­ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?", + "deleteXEntities": "{{num}}ê°œ Blob ì‚­ì œ", + "forceDelete": "ê°•ì œ ì‚­ì œ", + "forceDeleteDes": "ë¬¼ë¦¬ì  íŒŒì¼ ì‚­ì œ 성공 여부와 ê´€ê³„ì—†ì´ Blob 기ë¡ì„ 삭제합니다." + }, + "event": { + "cleanup": "정리", + "cleanupAuditLog": "ì´ë²¤íЏ 정리", + "cleanupAuditLogDescription": "ë‹¤ìŒ ì¡°ê±´ì„ ë§Œì¡±í•˜ëŠ” 모든 ì´ë²¤íŠ¸ë¥¼ 삭제합니다:", + "cleanupNotAfter": "ì´ ë‚ ì§œ ì´ì „", + "cleanupEventTypes": "ì´ë²¤íЏ 유형", + "cleanupEventTypesDes": "정리할 ì´ë²¤íЏ ìœ í˜•ì„ ì„ íƒí•˜ì„¸ìš”. 비워ë‘ë©´ 모든 ìœ í˜•ì„ ì •ë¦¬í•©ë‹ˆë‹¤.", + "initiator": "발신ìž", + "event": "ì´ë²¤íЏ", + "userID": "ì‚¬ìš©ìž ID", + "ip": "IP", + "type": "유형", + "correlationId": "요청 ID", + "fileID": "íŒŒì¼ ID", + "emailSend": "ì´ë©”ì¼ \"{{title}}\"ì„(를) {{email}}로 발송", + "emailFailed": "ì´ë©”ì¼ í 시작 실패", + "signinFailed": "ë¡œê·¸ì¸ ì‹¤íŒ¨: {{reason}}", + "createDavAccount": "WebDAV 계정 ìƒì„±: {{account}}", + "updateDavAccount": "WebDAV 계정 ì—…ë°ì´íЏ: {{account}}", + "deleteDavAccount": "WebDAV 계정 ì‚­ì œ: {{account}}", + "pointsChange": "í¬ì¸íЏ 변경: {{points}}", + "storageAdded": "{{size}} ìš©ëŸ‰ì„ êµ¬ë§¤í–ˆìŠµë‹ˆë‹¤", + "nickChange": "ë‹‰ë„¤ìž„ì´ {{old}}ì—서 {{new}}로 변경ë˜ì—ˆìŠµë‹ˆë‹¤", + "eventDialogTitle": "ì´ë²¤íЏ 세부 ì •ë³´", + "userAgent": "ì‚¬ìš©ìž ì—ì´ì „트", + "linkedUser": "ì—°ê´€ 사용ìž", + "datetime": "시간", + "linkedFile": "ì—°ê´€ 파ì¼", + "linkedEntity": "ì—°ê´€ Blob", + "linkedShare": "ì—°ê´€ 공유", + "rawContent": "ì›ë³¸ 기ë¡", + "confirmDelete": "ì´ ì´ë²¤íŠ¸ë¥¼ 삭제하시겠습니까?", + "deleteXEvents": "{{num}}ê°œ ì´ë²¤íЏ ì‚­ì œ", + "confirmBatchDelete": "{{num}}ê°œ ì´ë²¤íŠ¸ë¥¼ 삭제하시겠습니까?" + }, + "share": { + "confirmBatchDelete": "{{num}}ê°œ 공유를 삭제하시겠습니까?", + "confirmDelete": "ì´ ê³µìœ ë¥¼ 삭제하시겠습니까?", + "deleteXShares": "{{num}}ê°œ 공유 ì‚­ì œ", + "shareDialogTitle": "공유 세부 ì •ë³´", + "shareLink": "공유 ë§í¬", + "deleted": "파ì¼ì´ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤", + "srcFileName": "ì›ë³¸ 파ì¼", + "views": "조회", + "downloads": "다운로드", + "price": "í¬ì¸íЏ", + "autoExpire": "ìžë™ 만료", + "owner": "공유ìž", + "createdAt": "공유ì¼", + "private": "ê°œì¸ í™ˆíŽ˜ì´ì§€ì—서 숨김", + "yes": "예", + "no": "아니오", + "afterNDownloads": "{{num}}회 다운로드 후", + "none": "ì—†ìŒ", + "srcType": "ì›ë³¸ íŒŒì¼ ìœ í˜•", + "folder": "디렉터리", + "file": "파ì¼" + }, + "task": { + "cleanupTasks": "작업 정리", + "cleanupTasksDescription": "ë‹¤ìŒ ì¡°ê±´ì„ ë§Œì¡±í•˜ëŠ” 모든 ìž‘ì—…ì„ ì •ë¦¬í•©ë‹ˆë‹¤:", + "cleanupNotAfter": "ì´ ë‚ ì§œ ì´ì „", + "cleanupTaskTypes": "작업 유형", + "cleanupTaskTypesDes": "정리할 작업 ìœ í˜•ì„ ì„ íƒí•˜ì„¸ìš”. 비워ë‘ë©´ 모든 ìœ í˜•ì„ ì •ë¦¬í•©ë‹ˆë‹¤.", + "cleanupTaskStatuses": "작업 ìƒíƒœ", + "cleanupTaskStatusesDes": "정리할 작업 ìƒíƒœë¥¼ ì„ íƒí•˜ì„¸ìš”. 비워ë‘ë©´ 모든 ì™„ë£Œëœ ìƒíƒœì˜ ìž‘ì—…ì„ ì •ë¦¬í•©ë‹ˆë‹¤.", + "confirmDelete": "ì´ ìž‘ì—…ì„ ì‚­ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?", + "confirmBatchDelete": "{{num}}ê°œ ìž‘ì—…ì„ ì‚­ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?", + "deleteXTasks": "{{num}}ê°œ 작업 ì‚­ì œ", + "blobID": "Blob ID", + "retryIndex": "ìž¬ì‹œë„ ìˆœë²ˆ", + "entityError": "회수 실패한 Blob", + "updatedAt": "ì—…ë°ì´íЏì¼", + "taskDialogTitle": "작업 세부 ì •ë³´", + "explicitEntityRecycle": "íŒŒì¼ Blob ëª…ì‹œì  íšŒìˆ˜: {{blobs}}", + "entityRecycleRoutine": "정기 스캔으로 íŒŒì¼ Blob 회수", + "mediaMetadata": "Blob <0>#{{entityID}}ì˜ ë¯¸ë””ì–´ ì •ë³´ 추출", + "uploadSentinelCheck": "업로드 세션 {{uploadSessionID}} ìƒíƒœ 확ì¸", + "remoteDownload": "오프ë¼ì¸ 다운로드:", + "owner": "소유ìž", + "content": "ë‚´ìš©", + "status": "ìƒíƒœ", + "create_archive": "ì••ì¶• íŒŒì¼ ìƒì„±", + "extract_archive": "íŒŒì¼ ì••ì¶• í•´ì œ", + "relocate": "스토리지 ì •ì±… 전송", + "remote_download": "오프ë¼ì¸ 다운로드", + "media_meta": "미디어 ì •ë³´ 추출", + "entity_recycle_routine": "Blob 스캔 회수", + "explicit_entity_recycle": "ëª…ì‹œì  Blob 회수", + "upload_sentinel_check": "업로드 센티넬 확ì¸", + "import": "외부 가져오기", + "type": "유형", + "node": "처리 노드", + "createdBy": "ìƒì„±ìž", + "ready": "준비", + "downloading": "다운로드 중", + "paused": "ì¼ì‹œì •ì§€ 중", + "seeding": "시딩 중", + "error": "오류", + "finished": "완료", + "canceled": "취소/중지", + "unknown": "알 수 ì—†ìŒ", + "errorMsg": "오류 ì •ë³´" + }, + "payment": { + "tradeNo": "거래 번호", + "productType": "ìƒí’ˆ 유형", + "providerID": "ê²°ì œ ë°©ì‹", + "status": "ìƒíƒœ", + "deleteXPayments": "{{num}}ê°œ 주문 ì‚­ì œ" + }, + "customProps": { + "add": "추가", + "type": "유형", + "default": "기본값", + "actions": "작업", + "text": "í…스트", + "number": "숫ìž", + "boolean": "ì²´í¬", + "select": "ë‹¨ì¼ ì„ íƒ", + "multiSelect": "다중 ì„ íƒ", + "user": "사용ìž", + "link": "ë§í¬", + "rating": "í‰ì ", + "addProp": "ì†ì„± 추가", + "editProp": "ì†ì„± 편집", + "icon": "ì•„ì´ì½˜", + "iconDes": "<0>Iconify ì•„ì´ì½˜ ì´ë¦„으로, 비워ë‘ë©´ ì•„ì´ì½˜ì„ 표시하지 않습니다.", + "id": "ì‹ë³„ìž", + "idDes": "ì†ì„± ì‹ë³„ìžë¡œ, 모든 ì†ì„± 중ì—서 고유해야 합니다.", + "idPatternDes": "문ìž, 숫ìž, 밑줄, 하ì´í”ˆë§Œ í¬í•¨í•  수 있습니다.", + "minLength": "최소 길ì´", + "maxLength": "최대 길ì´", + "emptyLimit": "비워ë‘ë©´ 제한하지 않습니다.", + "minValue": "최소값", + "maxValue": "최대값", + "options": "옵션", + "optionsDes": "한 ì¤„ì— í•˜ë‚˜ì˜ ì˜µì…˜ìž…ë‹ˆë‹¤." + }, + "vas": { + "disableSubAddressEmail": "서브 주소 ì´ë©”ì¼ ë¹„í™œì„±í™”", + "disableSubAddressEmailDes": "활성화하면 ë”하기 기호 <0>+ê°€ í¬í•¨ëœ ì´ë©”ì¼ ì£¼ì†Œë¡œëŠ” ê³„ì •ì„ ë“±ë¡í•  수 없습니다.", + "confirmDelete": "ì´ ì£¼ë¬¸ë“¤ì„ ì‚­ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ?", + "vas": "부가 서비스", + "reports": "ì‹ ê³ ", + "orders": "주문", + "initialFiles": "초기 파ì¼", + "initialFilesDes": "ì‚¬ìš©ìž ë“±ë¡ í›„ ì´ˆê¸°ì— ë³´ìœ í•˜ëŠ” 파ì¼ì„ 지정합니다. íŒŒì¼ ID를 입력하여 기존 파ì¼ì„ 검색하고 추가하세요.", + "filterEmailProvider": "ë“±ë¡ ì´ë©”ì¼ ë„ë©”ì¸ í•„í„°ë§", + "filterEmailProviderDisabled": "비활성화", + "filterEmailProviderWhitelist": "í™”ì´íŠ¸ë¦¬ìŠ¤íŠ¸", + "filterEmailProviderBlacklist": "블랙리스트", + "filterEmailProviderDes": "특정 ì´ë©”ì¼ë§Œ 사용하여 사ì´íŠ¸ì— ë“±ë¡í•  수 있ë„ë¡ í—ˆìš©í•˜ë©°, 타사 SSO 로그ì¸ì€ ì´ ì œí•œì„ ë°›ì§€ 않습니다.", + "filterEmailProviderRule": "ì´ë©”ì¼ ë„ë©”ì¸ í•„í„°ë§ ê·œì¹™", + "filterEmailProviderRuleDes": "여러 ë„ë©”ì¸ì€ ë°˜ê° ì‰¼í‘œë¡œ 구분하세요.", + "qqConnect": "QQ ì—°ë™", + "qqConnectHint": "<0>QQ ì—°ë™ ì˜¤í”ˆ 플랫í¼ì—서 애플리케ì´ì…˜ì„ ìƒì„±í•  때 콜백 주소는 {{url}}로 입력하세요", + "enableQQConnect": "QQ ì—°ë™ í™œì„±í™”", + "enableQQConnectDes": "QQ ë°”ì¸ë”© 허용 여부, QQ를 사용한 본 사ì´íЏ ë¡œê·¸ì¸ í—ˆìš© 여부", + "loginWithoutBinding": "ë°”ì¸ë”©ë˜ì§€ ì•Šì€ ê²½ìš° ì§ì ‘ ë¡œê·¸ì¸ ê°€ëŠ¥", + "loginWithoutBindingDes": "활성화하면 사용ìžê°€ 타사 로그ì¸ì„ 사용했지만 ë°”ì¸ë”©ëœ ë“±ë¡ ì‚¬ìš©ìžê°€ 없는 경우 ì‹œìŠ¤í…œì´ ì‚¬ìš©ìžë¥¼ ìƒì„±í•˜ê³  로그ì¸ì‹œí‚µë‹ˆë‹¤. ì´ëŸ° ë°©ì‹ìœ¼ë¡œ ìƒì„±ëœ 사용ìžëŠ” 향후 타사 로그ì¸ë§Œ 사용할 수 있습니다.", + "appid": "APP ID", + "appidDes": "애플리케ì´ì…˜ 관리 페ì´ì§€ì—서 íšë“한 APP ID", + "appKey": "APP KEY", + "appKeyDes": "애플리케ì´ì…˜ 관리 페ì´ì§€ì—서 íšë“한 APP KEY", + "overuseReminder": "초과 사용 알림", + "overuseReminderDes": "사용ìžì˜ 부가 서비스 만료로 ì¸í•´ ìš©ëŸ‰ì´ ì œí•œì„ ì´ˆê³¼í•œ 후 발송ë˜ëŠ” 알림 ì´ë©”ì¼ í…œí”Œë¦¿", + "vasSetting": "ê²°ì œ/기타 설정", + "storagePack": "용량 팩", + "purchasableGroups": "구매 가능 ì‚¬ìš©ìž ê·¸ë£¹", + "giftCodes": "êµí™˜ 코드", + "enable": "활성화", + "appID": "App- ID", + "appIDDes": "당면부 애플리케ì´ì…˜ì˜ APPID", + "rsaPrivate": "RSA 애플리케ì´ì…˜ ê°œì¸í‚¤", + "rsaPrivateDes": "당면부 애플리케ì´ì…˜ì˜ RSA2 (SHA256) ê°œì¸í‚¤ë¡œ, ì¼ë°˜ì ìœ¼ë¡œ ì§ì ‘ ìƒì„±í•©ë‹ˆë‹¤. ìžì„¸í•œ ë‚´ìš©ì€ <0>RSA 키 ìƒì„±ì„ 참조하세요.", + "alipayPublicKey": "Alipay 공개키", + "alipayPublicKeyDes": "Alipayì—서 제공하며, 애플리케ì´ì…˜ 관리 - 애플리케ì´ì…˜ ì •ë³´ - ì¸í„°íŽ˜ì´ìФ 서명 ë°©ì‹ì—서 íšë“í•  수 있습니다.", + "wechatPay": "WeChat ê³µì‹ QR코드 ê²°ì œ", + "applicationID": "애플리케ì´ì…˜ ID", + "applicationIDDes": "ì§ì ‘ ì—°ê²° 가맹ì ì´ 신청한 공중 계정 ë˜ëŠ” ëª¨ë°”ì¼ ì• í”Œë¦¬ì¼€ì´ì…˜ appid", + "merchantID": "ì§ì ‘ ì—°ê²° ê°€ë§¹ì  ë²ˆí˜¸", + "merchantIDDes": "ì§ì ‘ ì—°ê²° 가맹ì ì˜ ê°€ë§¹ì  ë²ˆí˜¸ë¡œ, WeChat Payì—서 ìƒì„±í•˜ê³  발급합니다.", + "apiV3Secret": "API v3 키", + "apiV3SecretDes": "가맹ì ì€ 먼저 ã€ê°€ë§¹ì  플랫í¼ã€‘-ã€API 보안】 페ì´ì§€ì—서 ì´ í‚¤ë¥¼ 설정해야 하며, ìš”ì²­ì´ WeChat Payì˜ ì„œëª… ê²€ì¦ì„ 통과할 수 있습니다. í‚¤ì˜ ê¸¸ì´ëŠ” 32ë°”ì´íŠ¸ìž…ë‹ˆë‹¤.", + "mcCertificateSerial": "ê°€ë§¹ì  ì¸ì¦ì„œ ì¼ë ¨ë²ˆí˜¸", + "mcCertificateSerialDes": "ê°€ë§¹ì  í”Œëž«í¼ã€API 보안】-ã€API ì¸ì¦ì„œã€‘-ã€ì¸ì¦ì„œ ë³´ê¸°ã€‘ì— ë¡œê·¸ì¸í•˜ì—¬ ê°€ë§¹ì  API ì¸ì¦ì„œ ì¼ë ¨ë²ˆí˜¸ë¥¼ 확ì¸í•  수 있습니다.", + "mcAPISecret": "ê°€ë§¹ì  API ê°œì¸í‚¤", + "mcAPISecretDes": "ê°œì¸í‚¤ íŒŒì¼ apiclient_key.pemì˜ ë‚´ìš©ìž…ë‹ˆë‹¤.", + "payjs": "PAYJS WeChat Pay", + "payjsWarning": "ì´ ì„œë¹„ìŠ¤ëŠ” 타사 í”Œëž«í¼ <0>PAYJSì—서 제공하며, ë°œìƒí•˜ëŠ” 모든 ë¶„ìŸì€ Cloudreve 개발ìžì™€ 무관합니다.", + "mcNumber": "ê°€ë§¹ì  ë²ˆí˜¸", + "mcNumberDes": "PAYJS 관리 íŒ¨ë„ í™ˆíŽ˜ì´ì§€ì—서 확ì¸í•  수 있습니다", + "communicationSecret": "통신 키", + "otherSettings": "기타 설정", + "banBufferPeriod": "차단 완충 기간 (ì´ˆ)", + "banBufferPeriodDes": "사용ìžê°€ 용량 초과 ìƒíƒœë¥¼ 유지하는 최대 기간으로, ì´ ê¸°ê°„ì„ ì´ˆê³¼í•˜ë©´ 해당 사용ìžê°€ ì‹œìŠ¤í…œì— ì˜í•´ ë™ê²°ë©ë‹ˆë‹¤.", + "allowSellShares": "ê³µìœ ì— ê°€ê²© 설정 허용", + "allowSellSharesDes": "활성화하면 사용ìžê°€ ê³µìœ ì— í¬ì¸íЏ ê°€ê²©ì„ ì„¤ì •í•  수 있으며, 다운로드 시 í¬ì¸íŠ¸ê°€ ì°¨ê°ë©ë‹ˆë‹¤.", + "creditPriceRatio": "í¬ì¸íЏ 입금 비율 (%)", + "creditPriceRatioDes": "ê°€ê²©ì´ ì„¤ì •ëœ ê³µìœ ë¥¼ 구매하여 다운로드할 때 공유ìžê°€ 실제로 입금받는 í¬ì¸íЏ 비율입니다.", + "creditPrice": "í¬ì¸íЏ 가격 (ë¶„)", + "creditPriceDes": "í¬ì¸íЏ ì¶©ì „ 시 가격", + "add": "추가", + "name": "ì´ë¦„", + "price": "단가", + "duration": "기간", + "size": "í¬ê¸°", + "actions": "작업", + "orCredits": " ë˜ëŠ” {{num}} í¬ì¸íЏ", + "highlight": "ê°•ì¡° 표시", + "yes": "예", + "no": "아니오", + "productName": "ìƒí’ˆëª…", + "qyt": "수량", + "code": "êµí™˜ 코드", + "status": "ìƒíƒœ", + "invalidProduct": "ë¬´íš¨í™”ëœ ìƒí’ˆ", + "used": "사용ë¨", + "notUsed": "사용ë˜ì§€ 않ìŒ", + "generatingResult": "ìƒì„± ê²°ê³¼", + "addStoragePack": "용량 팩 추가", + "editStoragePack": "용량 팩 편집", + "productNameDes": "ìƒí’ˆ 표시 ì´ë¦„", + "packSizeDes": "용량 íŒ©ì˜ í¬ê¸°", + "durationDay": "유효 기간 (ì¼)", + "durationDayDes": "ê° ìš©ëŸ‰ íŒ©ì˜ ìœ íš¨ 기간", + "priceYuan": "단가 (위안)", + "packPriceDes": "용량 íŒ©ì˜ ë‹¨ê°€", + "priceCredits": "단가 (í¬ì¸íЏ)", + "priceCreditsDes": "í¬ì¸íŠ¸ë¥¼ 사용하여 구매할 ë•Œì˜ ê°€ê²©ìœ¼ë¡œ, 0으로 입력하면 í¬ì¸íŠ¸ë¡œ 구매할 수 없습니다", + "editMembership": "구매 가능 ì‚¬ìš©ìž ê·¸ë£¹ 편집", + "addMembership": "구매 가능 ì‚¬ìš©ìž ê·¸ë£¹ 추가", + "group": "ì‚¬ìš©ìž ê·¸ë£¹", + "groupDes": "구매 후 업그레ì´ë“œë˜ëŠ” ì‚¬ìš©ìž ê·¸ë£¹", + "durationGroupDes": "구매 후 업그레ì´ë“œë˜ëŠ” ì‚¬ìš©ìž ê·¸ë£¹ 단위 구매 ì‹œê°„ì˜ ìœ íš¨ 기간", + "groupPriceDes": "ì‚¬ìš©ìž ê·¸ë£¹ì˜ ë‹¨ê°€", + "productDescription": "ìƒí’ˆ 설명 (한 ì¤„ì— í•˜ë‚˜ì”©)", + "productDescriptionDes": "구매 페ì´ì§€ì— 표시ë˜ëŠ” ìƒí’ˆ 설명", + "highlightDes": "활성화하면 ìƒí’ˆ ì„ íƒ íŽ˜ì´ì§€ì—서 ê°•ì¡° 표시ë©ë‹ˆë‹¤", + "generateGiftCode": "êµí™˜ 코드 ìƒì„±", + "numberOfCodes": "ìƒì„± 수량", + "numberOfCodesDes": "활성화 코드 ì¼ê´„ ìƒì„± 수량", + "linkedProduct": "해당 ìƒí’ˆ", + "productQyt": "ìƒí’ˆ 수량", + "productQytDes": "í¬ì¸íЏ 유형 ìƒí’ˆì˜ 경우 여기는 í¬ì¸íЏ 수량ì´ë©°, 다른 ìƒí’ˆì€ 기간 배수입니다", + "freeDownload": "í¬ì¸íЏ 무료 공유 다운로드", + "freeDownloadDes": "활성화하면 사용ìžê°€ í¬ì¸íŠ¸ê°€ 필요한 공유를 무료로 다운로드할 수 있습니다", + "credits": "í¬ì¸íЏ", + "markSuccessful": "성공으로 표시", + "markAsResolved": "처리ë¨ìœ¼ë¡œ 표시", + "reportedContent": "ì‹ ê³  대ìƒ", + "reason": "ì›ì¸", + "description": "추가 설명", + "reportTime": "ì‹ ê³  시간", + "invalid": "[무효화ë¨]", + "deleteShare": "공유 ì‚­ì œ", + "orderDeleted": "주문 기ë¡ì´ ì‚­ì œë˜ì—ˆìŠµë‹ˆë‹¤", + "orderName": "주문명", + "product": "ìƒí’ˆ", + "orderNumber": "주문 번호", + "paidBy": "ê²°ì œ ë°©ì‹", + "orderOwner": "ìƒì„±ìž", + "amount": "금액", + "unpaid": "미결제", + "paid": "ê²°ì œë¨", + "shareLink": "공유 ë§í¬", + "mobileApp": "ëª¨ë°”ì¼ í´ë¼ì´ì–¸íЏ", + "showAppPromotion": "í´ë¼ì´ì–¸íЏ ê°€ì´ë“œ 페ì´ì§€ 표시", + "showAppPromotionDes": "활성화하면 사용ìžê°€ \"ì—°ê²° ë° ë§ˆìš´íŠ¸\" 페ì´ì§€ì—서 ëª¨ë°”ì¼ í´ë¼ì´ì–¸íЏ 사용 ê°€ì´ë“œë¥¼ ë³¼ 수 있습니다.", + "customPaymentName": "ê²°ì œ ë°©ì‹ ì´ë¦„", + "customPaymentNameDes": "사용ìžì—게 표시할 ê²°ì œ ë°©ì‹ ì´ë¦„", + "customPaymentSecretDes": "Cloudreveê°€ ê²°ì œ ìš”ì²­ì— ì„œëª…í•˜ëŠ” ë° ì‚¬ìš©í•˜ëŠ” 키입니다.", + "customPaymentEndpoint": "ê²°ì œ ì¸í„°íŽ˜ì´ìФ 주소", + "customPaymentEndpointDes": "ê²°ì œ 주문 ìƒì„± 시 요청하는 ì¸í„°íŽ˜ì´ìФ URL", + "appFeedback": "피드백 페ì´ì§€ URL", + "appForum": "ì‚¬ìš©ìž í¬ëŸ¼ URL", + "appLinkDes": "앱 설정 페ì´ì§€ì— 표시하는 ë° ì‚¬ìš©ë˜ë©°, 비워ë‘ë©´ ë§í¬ ë²„íŠ¼ì„ í‘œì‹œí•˜ì§€ 않습니다. VOL ë¼ì´ì„ ìŠ¤ê°€ 유효할 때만 ì´ ì„¤ì •ì´ ì ìš©ë©ë‹ˆë‹¤." + }, + "pro": { + "title": "Pro 버전 ì „ìš© 기능", + "description": "시ë„하는 ê¸°ëŠ¥ì€ Cloudreve Pro 버전ì—서만 사용할 수 있으며, 모든 고급 ê¸°ëŠ¥ì„ ìž ê¸ˆ 해제하려면 업그레ì´ë“œí•˜ì„¸ìš”.", + "proInclude": "Pro 버전 í¬í•¨:", + "shareLinkCollabration": "공유 ë§í¬ 협업 편집", + "filePermission": "íŒŒì¼ ê¶Œí•œ 관리", + "multipleStoragePolicy": "다중 스토리지 ì •ì±… ë° ë””ë ‰í„°ë¦¬ 스토리지 ì •ì±… 전환", + "auditAndActivity": "íŒŒì¼ ë° ì‹œìŠ¤í…œ í™œë™ ë¡œê·¸", + "vasService": "부가 서비스 ë° í¬ì¸íЏ 시스템", + "sso": "SSO ë‹¨ì¼ ë¡œê·¸ì¸", + "more": "......", + "later": "ë‚˜ì¤‘ì— ë³´ê¸°", + "learnMore": "Pro 버전 ìžì„¸ížˆ 보기", + "promotionTitle": "커뮤니티 버전 업그레ì´ë“œ 특별 í• ì¸", + "promotion": "구매 시 í• ì¸ ì½”ë“œ <0>{{code}}를 사용하여 <1>-{{discount}}% í• ì¸ì„ 받으세요." + }, + "abuseReport": { + "deleteXAbuseReports": "{{num}}ê°œ ì‹ ê³  ì‚­ì œ", + "folderPath": "디렉터리 경로", + "reporter": "ì‹ ê³ ìž", + "shareLink": "공유 ë§í¬ <0>#{{id}}", + "deletedShare": "ì‚­ì œëœ ê³µìœ  ë§í¬", + "deletedUser": "ì‚­ì œëœ ì‚¬ìš©ìž", + "confirmDelete": "ì´ ì‹ ê³  기ë¡ì„ 삭제하시겠습니까?", + "confirmBatchDelete": "{{num}}ê°œ ì‹ ê³  기ë¡ì„ 삭제하시겠습니까?", + "reporterID": "ì‹ ê³ ìž ì‚¬ìš©ìž ID", + "reportedUserID": "ì‹ ê³  ëŒ€ìƒ ì‚¬ìš©ìž ID", + "shareID": "공유 ID", + "reason": "ì›ì¸" + } +} diff --git a/public/locales/ko-KR/image_editor.json b/public/locales/ko-KR/image_editor.json new file mode 100755 index 0000000..d789aa3 --- /dev/null +++ b/public/locales/ko-KR/image_editor.json @@ -0,0 +1,113 @@ +{ + "name": "ì´ë¦„", + "save": "저장", + "saveAs": "다른 ì´ë¦„으로 저장", + "back": "뒤로", + "loading": "로딩 중...", + "resetOperations": "모든 작업 재설정/ì‚­ì œ", + "changesLoseWarningHint": "\"재설정\" ë²„íŠ¼ì„ ëˆ„ë¥´ë©´ ë³€ê²½ì‚¬í•­ì´ ì†ì‹¤ë©ë‹ˆë‹¤. 계ì†í•˜ì‹œê² ìŠµë‹ˆê¹Œ?", + "discardChangesWarningHint": "ì°½ì„ ë‹«ìœ¼ë©´ 마지막 ë³€ê²½ì‚¬í•­ì´ ì €ìž¥ë˜ì§€ 않습니다.", + "cancel": "취소", + "apply": "ì ìš©", + "warning": "경고", + "confirm": "확ì¸", + "discardChanges": "변경사항 버리기", + "undoTitle": "마지막 작업 실행 취소", + "redoTitle": "마지막 작업 다시 실행", + "showImageTitle": "ì›ë³¸ ì´ë¯¸ì§€ 표시", + "zoomInTitle": "확대", + "zoomOutTitle": "축소", + "toggleZoomMenuTitle": "확대/축소 메뉴 전환", + "adjustTab": "ì¡°ì •", + "finetuneTab": "미세 ì¡°ì •", + "filtersTab": "í•„í„°", + "watermarkTab": "워터마í¬", + "annotateTabLabel": "주ì„", + "resize": "í¬ê¸° ì¡°ì •", + "resizeTab": "í¬ê¸° ì¡°ì •", + "imageName": "ì´ë¯¸ì§€ ì´ë¦„", + "invalidImageError": "ì œê³µëœ ì´ë¯¸ì§€ê°€ 유효하지 않습니다.", + "uploadImageError": "ì´ë¯¸ì§€ 업로드 중 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤.", + "areNotImages": "ì´ë¯¸ì§€ê°€ 아닙니다", + "isNotImage": "ì´ë¯¸ì§€ê°€ 아닙니다", + "toBeUploaded": "업로드 예정", + "cropTool": "ìžë¥´ê¸°", + "original": "ì›ë³¸", + "custom": "ì‚¬ìš©ìž ì •ì˜", + "square": "정사ê°í˜•", + "landscape": "가로", + "portrait": "세로", + "ellipse": "타ì›", + "classicTv": "í´ëž˜ì‹ TV", + "cinemascope": "시네마스코프", + "arrowTool": "화살표", + "blurTool": "í림", + "brightnessTool": "ë°ê¸°", + "contrastTool": "대비", + "ellipseTool": "타ì›", + "unFlipX": "Xì¶• 뒤집기 취소", + "flipX": "Xì¶• 뒤집기", + "unFlipY": "Yì¶• 뒤집기 취소", + "flipY": "Yì¶• 뒤집기", + "hsvTool": "HSV", + "hue": "색조", + "brightness": "ë°ê¸°", + "saturation": "채ë„", + "value": "명ë„", + "imageTool": "ì´ë¯¸ì§€", + "importing": "가져오는 중...", + "addImage": "+ ì´ë¯¸ì§€ 추가", + "uploadImage": "ì´ë¯¸ì§€ 업로드", + "fromGallery": "갤러리ì—서", + "lineTool": "ì„ ", + "penTool": "펜", + "polygonTool": "다ê°í˜•", + "sides": "ë©´", + "rectangleTool": "사ê°í˜•", + "cornerRadius": "모서리 반지름", + "resizeWidthTitle": "너비 (픽셀)", + "resizeHeightTitle": "ë†’ì´ (픽셀)", + "toggleRatioLockTitle": "비율 잠금 전환", + "resetSize": "ì›ë³¸ ì´ë¯¸ì§€ í¬ê¸°ë¡œ 재설정", + "rotateTool": "회전", + "textTool": "í…스트", + "textSpacings": "í…스트 간격", + "textAlignment": "í…스트 ì •ë ¬", + "fontFamily": "글꼴", + "size": "í¬ê¸°", + "letterSpacing": "ìžê°„", + "lineHeight": "줄 높ì´", + "warmthTool": "색온ë„", + "addWatermark": "+ ì›Œí„°ë§ˆí¬ ì¶”ê°€", + "addTextWatermark": "+ í…스트 ì›Œí„°ë§ˆí¬ ì¶”ê°€", + "addWatermarkTitle": "ì›Œí„°ë§ˆí¬ ìœ í˜• ì„ íƒ", + "uploadWatermark": "ì›Œí„°ë§ˆí¬ ì—…ë¡œë“œ", + "addWatermarkAsText": "í…스트로 추가", + "padding": "패딩", + "paddings": "패딩", + "shadow": "그림ìž", + "horizontal": "수í‰", + "vertical": "수ì§", + "blur": "í림", + "opacity": "불투명ë„", + "transparency": "투명ë„", + "position": "위치", + "stroke": "ì„ ", + "saveAsModalTitle": "다른 ì´ë¦„으로 저장", + "extension": "확장ìž", + "format": "형ì‹", + "nameIsRequired": "파ì¼ëª…ì€ í•„ìˆ˜ìž…ë‹ˆë‹¤.", + "quality": "품질", + "imageDimensionsHoverTitle": "ì €ìž¥ëœ ì´ë¯¸ì§€ í¬ê¸° (너비 x 높ì´)", + "cropSizeLowerThanResizedWarning": "주ì˜: ì„ íƒí•œ ìžë¥´ê¸° ì˜ì—­ì´ ì ìš©ëœ í¬ê¸° 조정보다 작아 í’ˆì§ˆì´ ì €í•˜ë  ìˆ˜ 있습니다", + "actualSize": "실제 í¬ê¸° (100%)", + "fitSize": "맞춤 í¬ê¸°", + "addImageTitle": "추가할 ì´ë¯¸ì§€ ì„ íƒ...", + "mutualizedFailedToLoadImg": "ì´ë¯¸ì§€ ë¡œë“œì— ì‹¤íŒ¨í–ˆìŠµë‹ˆë‹¤.", + "tabsMenu": "메뉴", + "download": "다운로드", + "width": "너비", + "height": "높ì´", + "plus": "+", + "cropItemNoEffect": "ì´ ìžë¥´ê¸° í•­ëª©ì— ì‚¬ìš© 가능한 미리보기가 없습니다" +} diff --git a/public/locales/ko-KR/markdown_editor.json b/public/locales/ko-KR/markdown_editor.json new file mode 100755 index 0000000..37d0ca0 --- /dev/null +++ b/public/locales/ko-KR/markdown_editor.json @@ -0,0 +1,114 @@ +{ + "frontmatterEditor": { + "title": "ì „ë©´ 메타ë°ì´í„° 편집", + "key": "키", + "value": "ê°’", + "addEntry": "항목 추가" + }, + "dialogControls": { + "save": "저장", + "cancel": "취소" + }, + "uploadImage": { + "dialogTitle": "ì´ë¯¸ì§€ 업로드", + "uploadInstructions": "기기ì—서 ì´ë¯¸ì§€ë¥¼ 업로드하세요:", + "addViaUrlInstructions": "ë˜ëŠ” ì´ë¯¸ì§€ URL / ìƒëŒ€ 경로를 입력하세요 (현재 íŒŒì¼ ê¸°ì¤€):", + "autoCompletePlaceholder": "ì´ë¯¸ì§€ URLì„ ì„ íƒí•˜ê±°ë‚˜ 붙여넣기", + "addViaUrlInstructionsNoUpload": "ì´ë¯¸ì§€ URL:", + "alt": "대체 í…스트:", + "title": "제목:" + }, + "imageEditor": { + "deleteImage": "ì´ë¯¸ì§€ ì‚­ì œ", + "editImage": "ì´ë¯¸ì§€ 편집" + }, + "createLink": { + "url": "URL", + "urlPlaceholder": "URLì„ ì„ íƒí•˜ê±°ë‚˜ 붙여넣기", + "title": "제목", + "saveTooltip": "URL 설정", + "cancelTooltip": "변경 취소" + }, + "linkPreview": { + "open": "새 ì°½ì—서 {{url}} 열기", + "edit": "ë§í¬ URL 편집", + "copyToClipboard": "í´ë¦½ë³´ë“œì— 복사", + "copied": "복사ë¨!", + "remove": "ë§í¬ 제거" + }, + "table": { + "deleteTable": "표 ì‚­ì œ", + "columnMenu": "ì—´ 메뉴", + "textAlignment": "í…스트 ì •ë ¬", + "alignLeft": "왼쪽 ì •ë ¬", + "alignCenter": "ê°€ìš´ë° ì •ë ¬", + "alignRight": "오른쪽 ì •ë ¬", + "insertColumnLeft": "현재 ì—´ ì™¼ìª½ì— ì—´ 삽입", + "insertColumnRight": "현재 ì—´ ì˜¤ë¥¸ìª½ì— ì—´ 삽입", + "deleteColumn": "ì´ ì—´ ì‚­ì œ", + "rowMenu": "í–‰ 메뉴", + "insertRowAbove": "현재 í–‰ ìœ„ì— í–‰ 삽입", + "insertRowBelow": "현재 í–‰ ì•„ëž˜ì— í–‰ 삽입", + "deleteRow": "ì´ í–‰ ì‚­ì œ" + }, + "toolbar": { + "blockTypes": { + "paragraph": "단ë½", + "quote": "ì¸ìš©", + "heading": "제목 {{level}}" + }, + "blockTypeSelect": { + "selectBlockTypeTooltip": "ë¸”ë¡ ìœ í˜• ì„ íƒ", + "placeholder": "ë¸”ë¡ ìœ í˜•" + }, + "toggleGroup": "그룹 전환", + "removeBold": "굵게 제거", + "bold": "굵게", + "removeItalic": "기울임 제거", + "italic": "기울임", + "underline": "밑줄 제거", + "removeUnderline": "밑줄", + "removeInlineCode": "ì¸ë¼ì¸ 코드 ìŠ¤íƒ€ì¼ ì œê±°", + "inlineCode": "ì¸ë¼ì¸ 코드 스타ì¼", + "link": "ë§í¬ ìƒì„±", + "richText": "리치 í…스트", + "diffMode": "ì°¨ì´ì  모드", + "source": "소스 모드", + "admonition": "ì£¼ì„ ë¸”ë¡ ì‚½ìž…", + "codeBlock": "코드 ë¸”ë¡ ì‚½ìž…", + "editFrontmatter": "ì „ë©´ 메타ë°ì´í„° 편집", + "insertFrontmatter": "ì „ë©´ 메타ë°ì´í„° 삽입", + "image": "ì´ë¯¸ì§€ 삽입", + "insertSandpack": "Sandpack 삽입", + "table": "표 삽입", + "thematicBreak": "주제 구분선 삽입", + "bulletedList": "순서 없는 목ë¡", + "numberedList": "순서 있는 목ë¡", + "checkList": "ì²´í¬ë¦¬ìŠ¤íŠ¸", + "deleteSandpack": "Sandpack ì‚­ì œ", + "undo": "실행 취소 {{shortcut}}", + "redo": "다시 실행 {{shortcut}}", + "superscript": "위첨ìž", + "subscript": "아래첨ìž", + "strikethrough": "취소선", + "removeSubscript": "ì•„ëž˜ì²¨ìž ì œê±°", + "removeSuperscript": "ìœ„ì²¨ìž ì œê±°", + "removeStrikethrough": "취소선 제거" + }, + "admonitions": { + "note": "주ì˜", + "tip": "íŒ", + "danger": "위험", + "info": "ì •ë³´", + "caution": "경고", + "changeType": "ì£¼ì„ ë¸”ë¡ ìœ í˜• ì„ íƒ", + "placeholder": "ì£¼ì„ ë¸”ë¡ ìœ í˜•" + }, + "codeBlock": { + "language": "코드 ë¸”ë¡ ì–¸ì–´", + "selectLanguage": "코드 ë¸”ë¡ ì–¸ì–´ ì„ íƒ" + }, + "contentArea": { + "editableMarkdown": "편집 가능한 Markdown" + } +} diff --git a/public/locales/pt-BR/application.json b/public/locales/pt-BR/application.json new file mode 100755 index 0000000..d826853 --- /dev/null +++ b/public/locales/pt-BR/application.json @@ -0,0 +1,1113 @@ +{ + "login": { + "lastStep": "Último passo", + "siginToYourAccount": "Entre na sua conta", + "createNewAccount": "Criar nova conta", + "enterPassword": "Digite sua senha", + "enterPasswordHint": "Digite a senha para {{email}}", + "paswordlessHint": "A conta {{email}} não possui senha, escolha uma das formas de autenticação abaixo:", + "noAccountSignupNow": "Não tem conta? <0>Cadastre-se agora", + "haveAccountSignInNow": "Já tem conta? <0>Faça login agora", + "privacyPolicy": "Política de privacidade", + "termOfUse": "Termos de uso", + "signupHint": "A conta {{email}} não existe, deseja se cadastrar agora?", + "accountNotFoundHint": "A conta {{email}} que você digitou não existe.", + "or": "Ou", + "selectAccountToUse": "Selecione uma conta para usar", + "useOtherAccount": "Usar outra conta", + "email": "E-mail", + "password": "Senha", + "captcha": "CAPTCHA", + "captchaError": "Falha ao carregar CAPTCHA: {{message}}", + "signIn": "Entrar", + "signUp": "Cadastrar", + "signUpAccount": "Cadastrar conta", + "useFIDO2": "Usar chave de acesso", + "usePassword": "Usar senha", + "forgetPassword": "Esqueceu a senha?", + "2FA": "Verificação 2FA", + "input2FACode": "Digite o código de verificação 2FA de 6 dígitos", + "passwordNotMatch": "As senhas não coincidem", + "findMyPassword": "Recuperar senha", + "passwordReset": "Senha redefinida", + "newPassword": "Nova senha", + "repeatNewPassword": "Repita a nova senha", + "repeatPassword": "Repita a senha", + "resetPassword": "Redefinir senha", + "backToSingIn": "Voltar ao login", + "sendMeAnEmail": "Enviar e-mail de redefinição", + "resetEmailSent": "E-mail de redefinição enviado, verifique sua caixa de entrada", + "browserNotSupport": "Navegador ou ambiente atual não é suportado", + "success": "Login realizado com sucesso", + "signUpSuccess": "Cadastro realizado com sucesso", + "activateSuccess": "Ativação realizada com sucesso", + "accountActivated": "Sua conta foi ativada com sucesso", + "title": "Entrar em {{title}}", + "sinUpTitle": "Cadastrar em {{title}}", + "activateTitle": "Ativação por e-mail", + "activateDescription": "Um e-mail de ativação foi enviado para seu endereço de e-mail, acesse o link no e-mail para concluir o cadastro.", + "continue": "Próximo", + "back": "Anterior", + "logout": "Sair", + "signingOut": "Saindo...", + "loggedOut": "Você saiu da conta", + "clickToRefresh": "Clique para atualizar o código", + "switchLanguage": "Mudar idioma" + }, + "navbar": { + "notBefore": "Não antes de", + "notAfter": "Não depois de", + "minimum": "Mínimo", + "maximum": "Máximo", + "fileSize": "Tamanho do arquivo", + "searchBase": "Buscar em", + "searchInBase": "Buscar em <0>", + "conditionDuplicate": "Condição já existe", + "fileType": "Tipo de arquivo", + "addCondition": "Adicionar condições", + "notNameOpOr": "Todas as palavras-chave devem estar presentes", + "caseFolding": "Ignorar maiúsculas/minúsculas", + "keywords": "Palavras-chave", + "fileNameKeywordsHelp": "Pressione Enter para adicionar nova palavra-chave", + "advancedSearch": "Busca avançada", + "searchFilesTitle": "Buscar arquivos", + "searchIn": "Buscar <0>{{keywords}}", + "recentlyViewed": "Visualizados recentemente", + "searchFiles": "Buscar arquivos...", + "showMore": "Mais", + "resetThumbnail": "Redefinir miniatura quebrada", + "resetThumbnailRequested": "Redefinição de miniatura solicitada.", + "noFileCanResetThumbnail": "Nenhum arquivo pode redefinir a miniatura.", + "myFiles": "Meus arquivos", + "hisFiles": "Arquivos dele/dela", + "trash": "Lixeira", + "sharedWithMe": "Compartilhados comigo", + "myShare": "Meus compartilhamentos", + "remoteDownload": "Download remoto", + "connect": "Conectar e montar", + "taskQueue": "Tarefas em segundo plano", + "setting": "Configurações", + "videos": "Vídeos", + "photos": "Fotos", + "music": "Música", + "documents": "Documentos", + "addATag": "Adicionar tag...", + "addTagDialog": { + "selectFolder": "Selecionar pasta", + "fileSelector": "Seletor de arquivos", + "folderLink": "Atalho de pasta", + "tagName": "Nome da tag", + "matchPattern": "Padrão de correspondência do nome do arquivo", + "matchPatternDescription": "Você pode usar <0>* como curinga. Por exemplo, <1>*.png significa corresponder a imagens no formato PNG. Regras de múltiplas linhas operam com relacionamento \"OU\".", + "icon": "Ãcone:", + "color": "Cor:", + "folderPath": "Caminho da pasta" + }, + "storage": "Armazenamento", + "storageDetail": "{{used}} de {{total}} usado", + "notLoginIn": "Não logado", + "visitor": "Visitante", + "objectsSelected": "{{num}} selecionados", + "searchPlaceholder": "Digite <0>/ para buscar", + "backToHomepage": "Voltar à página inicial", + "darkModeSwitch": "Alternar tema escuro", + "toDarkMode": "Escuro", + "toLightMode": "Claro", + "myProfile": "Meu perfil", + "dashboard": "Painel de controle" + }, + "fileManager": { + "currentStoragePolicy": "Política de armazenamento atual: {{policy}}", + "customProps": "Propriedades personalizadas", + "rating": "Avaliação", + "description": "Descrição", + "add": "Adicionar", + "clickToEdit": "Clique para editar...", + "clickToEditSelect": "Clique para selecionar...", + "enterUrl": "Digite a URL...", + "searchUser": "Buscar usuário...", + "typeToSearch": "Digite nome ou e-mail...", + "searchProperty": "Buscar arquivos com a mesma propriedade", + "quality": "Qualidade", + "audioTrack": "Faixa de áudio", + "auto": "Automático", + "default": "Padrão", + "shareWithMeEmpty": "Nenhum arquivo compartilhado encontrado", + "shareWithMeEmptyDes": "Se você precisar ver compartilhamentos de outras pessoas aqui, salve o atalho em qualquer local dos seus arquivos ao visitar um link de compartilhamento.", + "selectAll": "Selecionar tudo", + "selectNone": "Desselecionar tudo", + "invertSelection": "Inverter seleção", + "imageSize": "Tamanho da imagem", + "focalLength": "Distância focal", + "columnExisted": "Coluna já existe", + "metadataColumn": "Metadados ({{metadata}})", + "column": "Coluna", + "listColumnSetting": "Configuração de colunas", + "addColumn": "Adicionar coluna", + "failedLoadPreview": "Falha ao carregar visualização", + "recursiveLimitReached": "Limite de profundidade de busca atingido", + "recursiveLimitReachedDes": "O sistema parou de buscar em pastas mais profundas, tente reduzir o escopo da busca.", + "searchConditions": "{{num}} condição(ões)", + "createDate": "Data de criação", + "updatedDate": "Data de modificação", + "cameraMake": "Fabricante da câmera", + "cameraModel": "Modelo da câmera", + "lensModel": "Modelo da lente", + "lensMake": "Fabricante da lente", + "metadataKey": "Chave", + "metadataValue": "Valor", + "metadata": "Metadados", + "symbolicFile": "Link simbólico", + "relocation": "Realocar política de armazenamento", + "downloadingFile": "Baixando \"{{name}}\", não feche esta página...", + "mountOwner": "Apenas o proprietário da pasta atual pode montar políticas", + "uploading": "Enviando", + "noActionsCanBeDone": "Nenhuma ação pode ser realizada", + "newFileName": "Novo arquivo.{{ext}}", + "newDocumentType": "{{display_name}} (.{{ext}})", + "text": "Texto", + "diagram": "Diagrama", + "whiteboard": "Quadro branco", + "selectApplications": "Selecionar aplicativos...", + "newlyCreatedFolder": "Nova pasta", + "expandAllApp": "Expandir todos os aplicativos", + "epubViewer": "Leitor ePub", + "googledocs": "Visualizador Google Docs", + "m365viewer": "Visualizador Microsoft Office Online", + "pdfViewer": "Visualizador PDF", + "archivePreview": "Visualização de arquivo", + "extractSelected": "Extrair arquivos selecionados", + "viewerFileSizeWarning": "Tamanho do arquivo aberto ({{file_size}}) excede o limite ({{max}}) do {{app}}, pode não funcionar corretamente.", + "testSubtitleStyle": "Testar estilo de legenda AaBbCc", + "color": "Cor", + "fontSize": "Tamanho da fonte", + "disableSubtitle": "Desabilitar legenda", + "noSubtitle": "Nenhum arquivo de legenda ASS/SRT/VTT encontrado na pasta atual.", + "subtitleStyles": "Estilos de legenda", + "subtitles": "Legendas", + "markdownEditor": "Editor Markdown", + "saveSuccess": "Salvo com sucesso às {{time}}", + "drawioLng": "pt", + "charset": "Codificação", + "textType": "Tipo de texto", + "fileSaved": "Arquivo salvo", + "failedToLoadFile": "Falha ao carregar arquivo: {{msg}}", + "monacoEditor": "Editor de código Monaco", + "preparingOpenFile": "Preparando para abrir arquivo...", + "openWithDescription": "Selecione um aplicativo para abrir arquivo .{{ext}}.", + "openWith": "Abrir com", + "readOnly": "Somente leitura", + "save": "Salvar", + "noMoreImages": "Nenhuma imagem encontrada na página atual", + "imageViewer": "Visualizador de imagens", + "logFileDeleteShare": "Excluiu um link de compartilhamento", + "logFileEditShare": "Editou um link de compartilhamento", + "deleteShareWarning": "Tem certeza de que deseja excluir este link de compartilhamento?", + "edit": "Editar", + "editAndReactivate": "Editar e reativar", + "yes": "Sim", + "no": "Não", + "permanentValid": "Permanente", + "manageShares": "Gerenciar links de compartilhamento", + "manageDirectLinks": "Gerenciar links diretos", + "deleteLinkConfirm": "Tem certeza de que deseja excluir este link direto?", + "directLinkNotFound": "O link direto que você está procurando não existe.", + "versionNotFound": "A versão que você está procurando não existe.", + "setNow": "Definir agora", + "permissionNotSet": "Permissão não definida", + "permissionNotSetDes": "Seguirá as configurações de permissão da pasta pai ou do link de compartilhamento.", + "permissions": "Permissões", + "logFileUpdateMetadata": "Atualizou metadados", + "all": "Todos", + "updatesOnly": "Apenas atualizações", + "readsOnly": "Apenas leituras", + "myActivitiesOnly": "Apenas minhas atividades", + "logUpdateView": "Atualizou configurações de visualização", + "logDeleteDirectLink": "Excluiu link direto", + "logFileImported": "Importado de arquivo externo", + "logGetDirectLink": "Criou um <0>link direto", + "logFileMount": "Montou política de armazenamento em \"{{name}}\"", + "lookForThisVersion": "Procurar por esta versão", + "logFileThumbGenerated": "Disparou geração de miniatura", + "logFileLivePhotoUploaded": "Enviou Live Photo", + "logFileCreate": "Criou este objeto", + "logFileRename": "Renomeou este objeto de \"{{originalName}}\" para \"{{newName}}\"", + "logFileSetPermission": "Alterou permissão deste objeto", + "logFileEntityUpload": "Atualizou conteúdo do arquivo", + "logFileCopyFrom": "Objeto criado copiando de <0> para <1>", + "logFileCopyTo": "Foi copiado de <0> para <1>", + "logFileMoveTo": "Movido de <0> para <1>", + "logFileMoveToTrash": "Movido para lixeira de <0>", + "logFileShare": "Compartilhou este objeto", + "logFileSetCurrentVersion": "Reverteu versão do arquivo para <0>", + "logFileDeleteVersion": "Excluiu a versão criada em <0>", + "logEntityDownloaded": "Baixou ou leu este objeto", + "logDirectLinkDownloaded": "Baixou via <0>link direto", + "logRelocate": "Realocou política de armazenamento para {{newPolicy}}", + "logCreateArchive": "Adicionado ao arquivo comprimido <0>", + "logExtractArchive": "Extraído para <0>", + "deleteVersionWarning": "Tem certeza de que deseja excluir esta versão? Esta operação não pode ser desfeita.", + "setAsCurrent": "Definir como versão atual", + "current": "[Atual]", + "createdBy": "Criado por", + "manageVersions": "Gerenciar versões", + "livePhoto": "Live Photo", + "version": "Versão", + "actions": "Ações", + "versionEntity": "Dados do arquivo e versões", + "data": "Dados", + "owned": "Proprietário", + "ownedSymbolic": "Proprietário (Link simbólico)", + "expires": "Expira", + "originalLocation": "Local original", + "myPermissions": "Minhas permissões", + "descendant": "Descendente", + "folderChildren": "{{files}} arquivo(s), {{folders}} pasta(s)", + "moreThan": "Mais que {{text}}", + "calculate": "Calcular", + "unset": "Não definido", + "folder": "Pasta", + "file": "Arquivo", + "symbolicLink": "Link simbólico ({{srcType}})", + "type": "Tipo", + "storageUsed": "Armazenamento usado", + "location": "Local", + "basicInfo": "Informações básicas", + "format": "Formato", + "duration": "Duração", + "artist": "Artista", + "album": "Ãlbum", + "title": "Título", + "resolution": "Resolução", + "takenAt": "Capturado em", + "software": "Software", + "copyright": "Direitos autorais", + "exposureBias": "Compensação de exposição", + "flash": "Flash", + "copyToClipboard": "Copiar para área de transferência", + "searchSomething": "Buscar \"{{text}}\"...", + "iso": "ISO", + "exposureValue": "{{num}} s", + "exposure": "Exposição", + "aperture": "Abertura", + "address": "Endereço", + "street": "Rua", + "locality": "Localidade", + "place": "Cidade", + "district": "Distrito", + "region": "Província", + "country": "País", + "mediaInfo": "Informações de mídia", + "details": "Detalhes", + "activity": "Atividade", + "goToSharedLink": "Ir para link compartilhado", + "saveShortcut": "Salvar link como atalho", + "customizeIcon": "Personalizar ícone", + "tags": "Tags", + "apply": "Aplicar", + "customizeColor": "Personalizar cor", + "folderColor": "Cor da pasta", + "restore": "Restaurar", + "unpin": "Desafixar", + "youDontHaveReadPermissionToThisFile": "Você não tem permissão de acesso", + "anonymousAccessDenied": "Você não tem permissão de acesso, tente fazer login.", + "sharedWithOthers": "Compartilhado com outros", + "new": "Novo", + "open": "Abrir", + "openParentFolder": "Ir para pasta pai", + "download": "Baixar", + "batchDownload": "Download em lote", + "share": "Compartilhar", + "rename": "Renomear", + "organize": "Organizar", + "pin": "Fixar na barra lateral", + "pinAlias": "Nome de exibição", + "optional": "Opcional", + "move": "Mover", + "delete": "Excluir", + "moreActions": "Mais ações", + "refresh": "Atualizar", + "createArchive": "Criar arquivo comprimido", + "newFolder": "Nova pasta", + "newFile": "Novo arquivo", + "showFullPath": "Mostrar caminho completo", + "listView": "Lista", + "gridView": "Grade", + "galleryView": "Galeria", + "paginationSize": "Paginação", + "paginationOption": "{{option}} / página", + "noPagination": "Sem paginação", + "sortMethod": "Ordenar", + "sortMethods": { + "A-Z": "A-Z", + "Z-A": "Z-A", + "oldestUploaded": "Mais antigo enviado", + "newestUploaded": "Mais recente enviado", + "oldestModified": "Mais antigo modificado", + "newestModified": "Mais recente modificado", + "smallest": "Menor", + "largest": "Maior" + }, + "shareCreateBy": "Criado por {{nick}}", + "name": "Nome", + "size": "Tamanho", + "lastModified": "Última modificação", + "currentFolder": "Pasta atual", + "backToParentFolder": "Voltar à pasta pai", + "folders": "Pastas", + "files": "Arquivos", + "listError": "Falha ao listar arquivos", + "dropFileHere": "Arraste e solte o arquivo aqui", + "orClickUploadButton": "Ou clique no botão \"Novo\" no canto superior esquerdo para adicionar um arquivo", + "nothingFound": "Nada foi encontrado", + "uploadFiles": "Enviar arquivos", + "uploadFolder": "Enviar pasta", + "newRemoteDownloads": "Novo download remoto", + "enter": "Entrar", + "getSourceLink": "Obter link direto", + "createRemoteDownloadForTorrent": "Novo download remoto", + "extractArchive": "Extrair arquivo", + "createShareLink": "Compartilhar", + "viewDetails": "Ver detalhes", + "copy": "Copiar", + "bytes": " ({{bytes}} Bytes)", + "storagePolicy": "Política de armazenamento", + "inheritedFromParent": "(Herdado do pai)", + "childFolders": "Pastas filhas", + "childFiles": "Arquivos filhos", + "childCount": "{{num}}", + "parentFolder": "Pasta pai", + "rootFolder": "Pasta raiz", + "modifiedAt": "Modificado em", + "createdAt": "Criado em", + "statisticAt": "Estatística em", + "musicPlayer": "Player de música", + "closeAndStop": "Fechar e parar", + "playInBackground": "Reproduzir em segundo plano", + "copyTo": "Copiar para", + "copyToDst": "Copiar para <0>", + "moveTo": "Mover para", + "moveToDst": "Mover para <0>", + "errorReadFileContent": "Falha ao ler conteúdo do arquivo: {{msg}}", + "wordWrap": "Quebra de linha automática", + "pdfLoadingError": "Falha ao carregar PDF: {{msg}}", + "subtitleSwitchTo": "Legenda alterada para: {{subtitle}}", + "noSubtitleAvailable": "Nenhum arquivo de legenda disponível na pasta do vídeo (suportados: ASS/SRT/VTT)", + "subtitle": "Legendas", + "playlist": "Lista de reprodução", + "openInExternalPlayer": "Abrir em player externo", + "repeatMode": "Modo de repetição", + "listRepeat": "Repetir lista", + "singleRepeat": "Repetir única", + "shuffle": "Aleatório", + "playbackSpeed": "Velocidade de reprodução", + "searchResult": "Resultados da busca", + "preparingBathDownload": "Preparando download em lote...", + "preparingDownload": "Preparando para baixar...", + "browserDownload": "Download pelo navegador para pasta local", + "browserDownloadDescription": "Seu navegador baixa arquivos um por um e mantém a estrutura de pastas no diretório local especificado.", + "browserBatchDownload": "Compactação pelo navegador", + "browserBatchDownloadDescription": "Baixado e compactado em arquivo Zip pelo navegador em tempo real, não pode processar dados maiores que 4GB.", + "serverBatchDownload": "Compactação pelo servidor", + "serverBatchDownloadDescription": "Compactado pelo servidor em arquivo Zip e enviado para download no cliente em tempo real, atalho de link compartilhado não é suportado.", + "selectArchiveMethod": "Selecionar método de compactação", + "batchDownloadStarted": "Download em lote iniciado, não feche esta aba...", + "batchDownloadError": "Falha ao compactar: {{msg}}", + "userDenied": "Usuário negou", + "directoryDownloadReplace": "Substituir", + "directoryDownloadReplaceDescription": "Arquivo local \"{{name}}\" será substituído pelo arquivo baixado.", + "directoryDownloadSkip": "Pular", + "directoryDownloadSkipDescription": "\"{{name}}\" será pulado.", + "selectDirectoryDuplicationMethod": "Arquivo duplicado", + "directoryDownloadReplaceAll": "Substituir todos", + "directoryDownloadReplaceAllDescription": "Todos os arquivos com o mesmo nome serão substituídos pelos arquivos baixados.", + "directoryDownloadSkipAll": "Pular todos", + "directoryDownloadSkipAllDescription": "Todos os arquivos com o mesmo nome serão pulados.", + "directoryDownloadStarted": "Download iniciado, não feche esta aba.", + "directoryDownloadFinished": "Download concluído, nenhum objeto falhou.", + "directoryDownloadFinishedWithError": "Download concluído, {{failed}} objeto falhou.", + "directoryDownloadPermissionError": "Permissão negada, permita leitura e escrita de arquivos locais.", + "back": "Voltar", + "view": "Visualizar", + "layout": "Layout", + "thumbnails": "Miniaturas", + "on": "Ligado", + "off": "Desligado", + "viewSetting": "Configuração de visualização", + "saved": "Salvo", + "notSet": "Não definido", + "deleteViewSetting": "Excluir configuração de visualização" + }, + "modals": { + "includePasswordInShareLink": "Incluir senha no link de compartilhamento", + "includePasswordInShareLinkDes": "Se selecionado, a senha será incluída no link de compartilhamento e nenhuma senha será necessária ao acessar o link.", + "showFileName": "Mostrar nome do arquivo", + "forceDownload": "Forçar download", + "archiveFile": "Arquivo comprimido", + "cancelDownload": "Cancelar download", + "always": "Sempre", + "justOnce": "Apenas uma vez", + "quality": "Qualidade", + "saveAsOtherFormat": "Salvar em outro formato", + "conflictDes1": "Conflito de versão do arquivo, possíveis razões são:", + "conflictDes2": "<0>O arquivo foi atualizado para uma nova versão de outro lugar depois que você o abriu.<1>Se você o salvou com um novo nome ou novo local, o nome do arquivo já existe.", + "saveAs": "Salvar como", + "versionConflict": "Conflito de versão", + "overwrite": "Sobrescrever", + "editShareLink": "Editar link de compartilhamento", + "clearPermissions": "Limpar configurações de permissão", + "shortcutCreated": "Atalho criado", + "createShortcut": "Criar atalho", + "createShortcutTo": "Criar atalho em <0>", + "read": "Ler", + "readDes": "Para arquivos, você pode ver seu conteúdo, metadados, etc.; para pastas, você pode ver a lista de arquivos filhos e seus metadados.", + "createDes": "Válido apenas para pastas, você pode criar ou enviar novos arquivos nela e mover ou copiar arquivos para ela.", + "update": "Atualizar", + "updateDes": "Você pode modificar metadados, renomear objetos e ver logs de atividade; para arquivos, você pode atualizar seu conteúdo.", + "delete": "Excluir", + "deleteDes": "Você pode excluir objetos ou movê-los para outros lugares.", + "noAccess": "Sem acesso", + "targetExisted": "Destino já existe", + "explicitAccess": "Acesso explícito", + "generalAccess": "Acesso geral", + "users": "Usuários", + "groups": "Grupos", + "builtinCollections": "Coleções integradas", + "everyone": "Todos os outros", + "otherGroup": "Outros grupos", + "sameGroup": "Mesmo grupo que eu", + "anonymous": "Visitantes anônimos", + "noResults": "Nenhum resultado", + "searchGroupUser": "Buscar por e-mails ou grupos...", + "resetToDefault": "Redefinir para padrão", + "duplicateTag": "Tag \"{{tag}}\" já existe", + "colorForTag": "Personalizar cor para novas tags", + "enterForNewTag": "Pressione Enter para adicionar nova tag", + "manageTags": "Gerenciar tags", + "onlyOwner": "Apenas o proprietário deste arquivo pode forçar o desbloqueio", + "forceUnlock": "Forçar desbloqueio", + "forceUnlockAll": "Forçar desbloqueio de todos", + "forceUnlockDes": "Forçar desbloqueio pode corromper o estado do arquivo, recomendamos esperar que o arquivo seja liberado proativamente, tem certeza de que deseja continuar desbloqueando?", + "webdav": "WebDAV", + "soft-delete": "Mover para lixeira", + "updateMetadata": "Atualizar metadados", + "upload": "Enviar", + "moveCopy": "Mover ou copiar", + "view": "Visualizar", + "cannotPerformAction": "Mover ou copiar arquivos para cá não é suportado", + "cannotMoveCopyToChild": "Não é possível mover ou copiar para pasta descendente", + "copySuccess": "{{num}} arquivo(s) copiado(s) com sucesso", + "moveSuccess": "{{num}} arquivo(s) movido(s) com sucesso", + "setPermission": "Definir permissão", + "unknownParent": "Pai desconhecido", + "unknownParentDes": "A pasta ocupada é a pasta pai de uma pasta compartilhada e não é sua propriedade.", + "lockConflictTitle": "Arquivo ocupado", + "lockConflictDescription": "Esta operação não pode ser concluída porque o(s) seguinte(s) arquivo(s) está(ão) sendo usado(s) por outros, tente novamente mais tarde. Se você é o proprietário do arquivo e tem certeza de que o arquivo não está em uso, pode forçar o desbloqueio do arquivo e tentar novamente.", + "usedBy": "Usado por", + "application": "Aplicativo", + "errorDetailsTitle": "Detalhes do erro", + "processingMoving": "Movendo arquivos...", + "processingCopying": "Copiando arquivos...", + "processingRestoring": "Restaurando arquivos...", + "fileRestored": "{{num}} arquivo(s) restaurado(s) para seu local original", + "duplicatedObjectName": "Nome de arquivo duplicado", + "newNameLengthError": "Comprimento do nome do arquivo deve estar entre 1 e 255", + "newNameCharacterError": "Nome não deve conter nenhum destes caracteres: \\ / : * ? \" < > |", + "newNameDotError": "Nome não pode ser \".\" ou \"..\"", + "taskCreated": "Tarefa criada", + "taskCreateFailed": "{{failed}} tarefa(s) falharam ao ser criadas: {{details}}", + "linkCopied": "Link copiado", + "getSourceLinkTitle": "Obter link direto", + "sourceLink": "Link direto", + "folderName": "Nome da pasta", + "create": "Criar", + "fileName": "Nome do arquivo", + "renameDescription": "Digite o novo nome para <0>{{name}}:", + "newName": "Novo nome", + "moveToDescription": "Mover para <0>{{name}}", + "saveToTitle": "Salvar em", + "saveToTitleDescription": "Salvar em <0>{{name}}", + "deleteTitle": "Excluir objetos", + "deleteOneDescription": "Tem certeza de que deseja mover <0>{{name}} para a lixeira?", + "deleteMultipleDescription": "Tem certeza de que deseja mover estes {{num}} objetos para a lixeira?", + "deleteOneDescriptionHard": "Tem certeza de que deseja excluir permanentemente <0>{{name}}?", + "trashRetention": "Arquivos na lixeira serão excluídos após <0>{{num}}.", + "deleteMultipleDescriptionHard": "Tem certeza de que deseja excluir permanentemente estes {{num}} objetos?", + "newRemoteDownloadTitle": "Nova tarefa de download remoto", + "remoteDownloadURL": "URL de destino do download", + "remoteDownloadURLDescription": "Cole a URL de download, uma URL por linha", + "remoteDownloadDst": "Baixar para", + "processNode": "Nó de destino", + "remoteDownloadNodeAuto": "Despacho automático", + "createTask": "Criar tarefa", + "downloadToDst": "Baixar para <0>{{name}}", + "downloadTo": "Baixar para", + "decompressTo": "Extrair para", + "decompressToDst": "Extrair para <0>{{name}}", + "defaultEncoding": "Padrão", + "chineseMajorEncoding": "Codificação comum chinesa simplificado", + "selectEncoding": "Codificação do arquivo ZIP", + "password": "Senha", + "passwordDescription": "Se o arquivo comprimido não estiver criptografado, deixe este campo vazio.", + "noEncodingSelected": "Nenhum método de codificação selecionado", + "listingFiles": "Listando arquivos...", + "listingFileError": "Falha ao listar arquivos: {{message}}", + "generatingSourceLinks": "Gerando links de origem...", + "noFileCanGenerateSourceLink": "Não há arquivo que possa ser usado para gerar link de origem", + "sourceBatchSizeExceeded": "O grupo de usuário atual pode gerar links de origem para no máximo {{limit}} arquivos ao mesmo tempo.", + "zipFileName": "Nome do arquivo comprimido", + "shareLinkShareContent": "Compartilhei com você: {{name}} Link: {{link}}", + "shareLinkPasswordInfo": "Senha: {{password}}", + "createShareLink": "Criar link de compartilhamento", + "privateShare": "Proteger com senha", + "privateShareDes": "Se selecionado, senha é necessária para acessar o link de compartilhamento.", + "useCustomPassword": "Senha personalizada do link de compartilhamento", + "expireAfterDownload": "Expirar após ser baixado", + "sharePassword": "Senha de compartilhamento", + "randomlyGenerate": "Aleatório", + "expireAutomatically": "Expiração automática", + "downloadLimitOptions": "{{num}} downloads", + "or": "Ou após", + "5minutes": "5 minutos", + "1hour": "1 hora", + "1day": "1 dia", + "7days": "7 dias", + "30days": "30 dias", + "custom": "Personalizado", + "minutes": "minutos", + "downloads": "downloads", + "expirePrefix": "Expirar após", + "expireSuffix": "", + "allowPreview": "Habilitar visualização", + "allowPreviewDescription": "Se permitir visualização do conteúdo do arquivo do link de compartilhamento", + "shareLink": "Link de compartilhamento", + "sendLink": "Enviar o link", + "directoryDownloadReplaceNotifiction": "Sobrescrever {{name}}", + "directoryDownloadSkipNotifiction": "Pulado {{name}}", + "directoryDownloadTitle": "Logs de download em lote", + "directoryDownloadStarted": "Iniciando download \"{{name}}\"", + "directoryDownloadFinished": "Download concluído \"{{name}}\"", + "directoryDownloadError": "Erro: {{msg}}", + "directoryDownloadErrorNotification": "Erro ocorreu ao baixar {{name}}: {{msg}}", + "directoryDownloadAutoscroll": "Rolagem automática", + "directoryDownloadCancelled": "Download cancelado", + "advanceOptions": "Opções avançadas", + "skipSoftDelete": "Excluir permanentemente", + "skipSoftDeleteDes": "Pular movimentação para lixeira, excluir permanentemente", + "unlinkOnly": "Manter arquivos físicos", + "unlinkOnlyDes": "Excluir apenas registros de arquivo, arquivos físicos não serão excluídos", + "shareView": "Configuração de visualização de compartilhamento", + "shareViewDes": "Se selecionado, outros usuários podem ver sua configuração de visualização (layout, ordenação, etc.) salva no servidor ao acessar esta pasta compartilhada.", + "showReadme": "Mostrar arquivo README", + "showReadmeDes": "Se selecionado, o arquivo <0>README.md (sensível a maiúsculas/minúsculas) no diretório será automaticamente exibido para visitantes.", + "viewSetting": "Configuração de visualização", + "saved": "Salvo", + "notSet": "Não definido", + "deleteViewSetting": "Excluir configuração de visualização" + }, + "uploader": { + "fileCopyName": "Cópia de ", + "overwriteTooltip": "Sobrescrever arquivo existente se houver conflito, funciona apenas para tarefas recém-adicionadas.", + "rename": "Tentar novamente com novo nome", + "overwrite": "Sobrescrever arquivo existente", + "pasteFilesHere": "Colar arquivos aqui", + "clipboardDefaultFileName": "Ãrea de transferência {{date}}.png", + "uploadFromClipboard": "Enviar da área de transferência", + "uploadList": "Tarefas de envio", + "fileNotMatchError": "O arquivo selecionado não corresponde ao arquivo original.", + "unknownError": "Erro desconhecido ocorreu: {{msg}}", + "taskListEmpty": "Nenhuma tarefa de envio.", + "hideTaskList": "Ocultar a lista", + "uploadTasks": "Tarefas de envio", + "moreActions": "Mais ações", + "addNewFiles": "Adicionar novos arquivos", + "toggleTaskList": "Expandir/Recolher a lista", + "pendingInQueue": "Pendente na fila...", + "preparing": "Preparando...", + "processing": "Processando...", + "progressDescription": "{{uploaded}} enviado, {{total}} total - {{percentage}}%", + "progressDescriptionFull": "{{uploaded}} enviado, {{total}} total - {{percentage}}% ({{speed}})", + "progressDescriptionPlaceHolder": " - enviado", + "uploaded": "Enviado", + "rootFolder": "Pasta raiz", + "unknownStatus": "Desconhecido", + "resumed": "Retomado", + "resumable": "Retomável", + "retry": "Tentar novamente", + "deleteTask": "Excluir tarefa", + "cancelAndDelete": "Cancelar e excluir", + "selectAndResume": "Selecionar o mesmo arquivo e retomar envio", + "fileName": "Nome: ", + "fileSize": "Tamanho: ", + "sessionExpiredIn": "Expira <0>", + "chunkDescription": "({{total}} partes, {{size}} cada)", + "noChunks": "(Sem partes)", + "destination": "Destino: ", + "uploadSession": "Sessão de envio: ", + "storagePolicy": "Política de armazenamento: ", + "errorDetails": "Detalhes do erro: ", + "uploadSessionCleaned": "Todas as sessões de envio foram limpas.", + "hideCompletedTooltip": "Ocultar tarefas concluídas, falhadas e canceladas.", + "hideCompleted": "Ocultar tarefas concluídas", + "addTimeAscTooltip": "Tarefas adicionadas primeiro são classificadas primeiro.", + "addTimeAsc": "Mais antigas para mais novas", + "addTimeDescTooltip": "Tarefas adicionadas mais recentemente são classificadas primeiro.", + "addTimeDesc": "Mais novas para mais antigas", + "showInstantSpeedTooltip": "Velocidades de envio de tarefa são mostradas como velocidade instantânea.", + "showInstantSpeed": "Velocidade instantânea", + "showAvgSpeedTooltip": "Velocidades de envio de tarefa são mostradas como velocidades médias.", + "showAvgSpeed": "Velocidade média", + "cleanAllSessionTooltip": "Limpar todas as sessões de envio pendentes no lado do servidor.", + "cleanAllSession": "Limpar todas as sessões de envio", + "cleanCompletedTooltip": "Limpar tarefas concluídas, falhadas e canceladas", + "cleanCompleted": "Limpar tarefas concluídas", + "retryFailedTasks": "Tentar novamente todas as tarefas falhadas", + "retryFailedTasksTooltip": "Tentar novamente todas as tarefas falhadas na fila atual", + "setConcurrentTooltip": "Definir o número máximo de tarefas que podem ser enviadas simultaneamente.", + "setConcurrent": "Definir limite de tarefas simultâneas", + "sizeExceedLimitError": "Tamanho do arquivo excede limites da política de armazenamento. (Máximo: {{max}})", + "suffixNotAllowedError": "A política de armazenamento não suporta envio de arquivos com esta extensão.", + "regexpNotAllowedError": "A política de armazenamento não suporta envio de arquivos com este nome.", + "suffixAllowed": " (Suportados:{{supported}})", + "suffixDenied": " (Negados:{{denied}})", + "createUploadSessionError": "Não foi possível criar sessão de envio", + "deleteUploadSessionError": "Não foi possível excluir sessão de envio", + "requestError": "Falha na solicitação: {{msg}} ({{url}}).", + "chunkUploadError": "Falha ao enviar parte [{{index}}].", + "conflictError": "A tarefa de envio para arquivos com o mesmo nome já está sendo processada.", + "chunkUploadErrorWithMsg": "Falha no envio de parte: {{msg}}", + "chunkUploadErrorWithRetryAfter": "(Tente novamente após {{retryAfter}}s)", + "emptyFileError": "Envio de arquivos vazios para OneDrive não é suportado, crie arquivos vazios através do botão Criar Arquivo.", + "finishUploadError": "Não foi possível concluir o envio do arquivo.", + "finishUploadErrorWithMsg": "Não foi possível concluir o envio do arquivo: {{msg}}", + "ossFinishUploadError": "Não foi possível concluir o envio do arquivo: {{msg}} ({{code}})", + "cosUploadFailed": "Falha no envio: {{msg}} ({{code}})", + "upyunUploadFailed": "Falha no envio: {{msg}}", + "parseResponseError": "Não foi possível analisar resposta: {{msg}} ({{content}})", + "concurrentTaskNumber": "Limite de tarefas simultâneas", + "dropFileHere": "Solte arquivo para enviar" + }, + "share": { + "free": "Grátis", + "price": "Preço", + "points": "{{num}} Pontos", + "statistics": "Estatísticas", + "expireAt": "Expira <0>", + "expireAfterDownloads": "Expira após {{downloads}} download(s)", + "somebodyShare": "Compartilhado por {{name}}", + "expiredLink": "Compartilhamento expirado", + "sharedBy": "<0>{{nick}} compartilhou $t(share.files, {\"count\": {{num}} }) com você.", + "files": "1 arquivo", + "files_other": "{{count}} arquivos", + "statisticsViews": "$t(share.views, {\"count\": {{views}} })", + "statisticsDownloads": "$t(share.downloads, {\"count\": {{downloads}} })", + "views": "{{count}} visualização", + "views_other": "{{count}} visualizações", + "downloads": "{{count}} download", + "downloads_other": "{{count}} downloads", + "privateShareTitle": "Compartilhamento privado de {{nick}}", + "enterPassword": "Senha de compartilhamento", + "continue": "Continuar", + "shareCanceled": "Link de compartilhamento foi excluído.", + "listLoadingError": "Falha ao carregar.", + "sharedFiles": "Arquivos compartilhados", + "createdAtDesc": "Mais recentes", + "createdAtAsc": "Mais antigos", + "noRecords": "Nenhum arquivo compartilhado.", + "sourceNotFound": "[Origem não existe]", + "expired": "Expirado", + "changeToPublic": "Tornar público", + "changeToPrivate": "Tornar privado", + "viewPassword": "Ver senha", + "disablePreview": "Desabilitar visualização", + "enablePreview": "Habilitar visualização", + "cancelShare": "Cancelar compartilhamento", + "sharePassword": "Senha de compartilhamento", + "readmeError": "Não foi possível carregar README: {{msg}}", + "enterKeywords": "Digite palavras-chave de busca.", + "searchResult": "Resultados da busca", + "sharedAt": "Compartilhado em <0>", + "pleaseLogin": "Faça login primeiro.", + "cannotShare": "Este arquivo não pode ser visualizado.", + "preview": "Visualizar", + "incorrectPassword": "Senha incorreta.", + "shareNotExist": "Link de compartilhamento é inválido ou expirado.", + "copyLinkToClipboard": "Copiar link para área de transferência" + }, + "download": { + "noFilesFound": "Nenhum arquivo encontrado", + "filterByName": "Filtrar por nome", + "selectAll": "Selecionar tudo", + "reverseSelect": "Seleção reversa", + "saveChanges": "Salvar alterações", + "cancelTaskConfirm": "Tem certeza de que deseja cancelar esta tarefa de download?", + "failedToLoad": "Falha ao carregar.", + "active": "Ativo", + "finished": "Concluído", + "activeEmpty": "Nenhuma tarefa de download em andamento.", + "finishedEmpty": "Nenhuma tarefa de download concluída.", + "loadMore": "Carregar mais", + "taskFileDeleted": "Arquivo excluído.", + "unknownTaskName": "[Desconhecido]", + "taskCanceled": "Tarefa de download cancelada, status será atualizado posteriormente", + "operationSubmitted": "Operação enviada, status será atualizado posteriormente", + "deleteThisFile": "Excluir este arquivo", + "openDstFolder": "Abrir pasta de destino", + "selectDownloadingFile": "Selecionar arquivos para baixar", + "cancelTask": "Cancelar", + "updatedAt": "Atualizado em: ", + "uploaded": "Enviado", + "uploadSpeed": "Velocidade de envio", + "InfoHash": "InfoHash", + "seederCount": "Seeders:", + "seeding": "Seeding: ", + "downloadNode": "Nó: ", + "isSeeding": "Sim", + "notSeeding": "Não", + "chunkSize": "Tamanho da parte:", + "chunkNumbers": "Partes", + "taskDeleted": "Tarefa excluída.", + "transferFailed": "Falha ao transferir arquivos.", + "downloadFailed": "Falha no download: {{msg}}", + "canceledStatus": "Cancelado", + "finishedStatus": "Concluído", + "pending": "Concluído, transferência pendente na fila", + "transferring": "Concluído, transferindo", + "deleteRecord": "Excluir registro", + "createdAt": "Criado em: ", + "unknownSize": "Tamanho de arquivo desconhecido" + }, + "setting": { + "notifyStoragePolicyChange": "Notificar-me sobre mudança de política de armazenamento", + "notifyStoragePolicyChangeDes": "Quando habilitado, uma notificação será exibida ao entrar em um diretório vinculado a uma política de armazenamento diferente.", + "treeView": "Visualização em árvore", + "autoExpandTreeView": "Expandir automaticamente visualização em árvore", + "autoExpandTreeViewDes": "Quando habilitado, a árvore de arquivos na barra lateral seguirá o diretório atual e expandirá automaticamente.", + "syncView": "Configurações de visualização", + "syncViewDes": "Lembrar as configurações de visualização de cada diretório e sincronizar com o servidor.", + "syncViewOn": "Sincronizar com servidor", + "syncViewOff": "Não sincronizar", + "reason": "Razão", + "change": "Mudança", + "success": "Sucesso", + "loginWithPasskey": "Chave de acesso - {{name}}", + "loginWith": "Entrar com", + "result": "Resultado", + "device": "Dispositivo", + "ip": "IP", + "time": "Tempo", + "recentSignIn": "Atividades de login recentes", + "noAuthenticator": "Adicione uma chave de acesso para fazer login usando impressão digital, rosto ou chave USB.", + "neverUsed": "Nunca usado", + "usedAt": "Último uso em <0>", + "passkeyName": "{browser} no {os}", + "passwordlessHint": "Esta conta não possui senha.", + "versionRetentionMax": "Número máximo de versões, 0 significa sem limite.", + "versionRetentionEnabledExt": "Extensões de arquivo habilitadas", + "versionRetentionEnabledExtDes": "Pressione Enter para adicionar, deixe em branco para habilitar para todos os arquivos", + "enableVersionRetention": "Habilitar retenção de versão", + "enableVersionRetentionDes": "Se habilitado, versões históricas de arquivos que atendem às condições serão retidas.", + "versionRetention": "Retenção de versão", + "languageDes": "Selecione o idioma de exibição e idioma de e-mail preferido.", + "timezoneDes": "Definir fuso horário de exibição, padrão é fuso horário do sistema", + "unlinkConfirm": "Tem certeza de que deseja desvincular esta conta?", + "notLinked": "Não vinculado", + "linkedAt": "Vinculado em <0>", + "accountLinking": "Vinculação de conta", + "nickNameDes": "Este é seu nome de exibição público. Pode ser seu nome real ou um pseudônimo.", + "cropAvatar": "Recortar avatar", + "finance": "Finanças", + "preference": "Preferência", + "accountCreatedAt": "Criado em <0>", + "shoeQr": "Mostrar", + "deviceNothing": "WebDAV não é suportado no seu grupo de usuário.", + "connectionInfo": "Detalhes de conexão", + "proxyTooltip": "Fazer proxy de todas as solicitações de download de arquivo.", + "readonlyTooltip": "Usuário pode apenas ler arquivos através desta conta.", + "blockSysFilesUpload": "Bloquear upload de arquivos do sistema", + "blockSysFilesUploadTooltip": "Quando habilitado, arquivos que começam com <0>. serão bloqueados para upload.", + "rootFolderIn": "Selecionar <0>", + "createWebDavAccount": "Criar conta WebDAV", + "editWebDavAccount": "Editar {{name}}", + "seeding": "Seeding", + "awaitSeeding": "Aguardar seeding", + "awaitSeedingDes": "Aguardar conclusão do seeding.", + "downloadTransferDes": "Transferir arquivos para destino.", + "downloadDes": "Baixar arquivos desejados.", + "retryErrorHistory": "Histórico de erro de tentativa", + "retryCount": "Tentativas", + "resumeAt": "Retomar em", + "executeDuration": "Duração de execução", + "input": "Entrada", + "output": "Saída", + "suspended": " (Suspenso)", + "updatedAt": "Atualizado em", + "taskDetails": "Detalhes da tarefa", + "partialSuccessWarning": "Falha ao processar {{num}} objeto(s), eles foram pulados.", + "sendTask": "Enviar tarefa", + "sendTaskDes": "Enviar a tarefa para um nó processar.", + "downloaded": "Baixado", + "importingFiles": "Importar arquivos", + "importingFilesDes": "Indexar arquivos e importá-los para a pasta especificada.", + "importedFiles": "Arquivos importados", + "indexedFiles": "Arquivos indexados", + "extractedFiles": "Arquivos extraídos", + "extractedFilesSize": "Tamanho dos arquivos extraídos", + "extractingFiles": "Extraindo arquivos", + "extractingFilesDes": "Extrair todos os arquivos para a pasta dada.", + "downloadingZip": "Baixar arquivo", + "downloadingZipDes": "Baixar arquivo para espaço de trabalho temporário.", + "progressNotAvailable": "Progresso ainda não disponível.", + "uploadedSize": "Tamanho realocado", + "archivedFiles": "Arquivos processados", + "transferredFiles": "Arquivos realocados", + "archivedFilesSize": "Tamanho de arquivo processado", + "createArchiveFinishing": "Confirmar alterações para novos arquivos", + "indexForArchiveDes": "Indexar arquivos a serem arquivados.", + "prepare": "Preparar", + "preparingWorkspaceDes": "Preparar espaço de trabalho temporário.", + "compressFiles": "Criar arquivo", + "compressFilesDes": "Criar arquivo para espaço de trabalho temporário.", + "uploadArchiveFileDes": "Transferir arquivo comprimido para a pasta de destino.", + "uploadWorker": "Worker de envio #{{num}}", + "relocatedEntities": "Entidades realocadas", + "queueToStart": "Fila para iniciar", + "indexingFiles": "Indexar arquivos", + "indexingFilesDes": "Indexar arquivos a serem realocados, bloqueá-los.", + "transferring": "Transferir", + "transferringRelocateDes": "Transferir dados para nova política de armazenamento.", + "committingChanges": "Confirmar alterações", + "relocateFinishing": "Atualizar referência de entidade para nova política de armazenamento.", + "autoRefresh": "Atualização automática", + "avatarUpdated": "O avatar foi atualizado e entrará em vigor com atraso.", + "nickChanged": "Apelido alterado e entrará em vigor após atualizar.", + "settingSaved": "Configuração salva.", + "themeColorChanged": "Cor do tema alterada.", + "profile": "Perfil", + "avatar": "Foto do perfil", + "uid": "UID", + "nickname": "Nome de exibição", + "group": "Grupo", + "regTime": "Data de cadastro", + "security": "Senha e segurança", + "profilePage": "Perfil público", + "publicShareOnly": "Apenas compartilhamento público", + "publicShareOnlyDes": "Mostrar apenas compartilhamentos sem senha na página de perfil.", + "allShare": "Todos os compartilhamentos", + "allShareDes": "Mostrar todos os compartilhamentos na página de perfil (incluindo compartilhamentos protegidos por senha). Usuários ainda precisam inserir senha para acessá-los.", + "hideShare": "Ocultar todos os compartilhamentos", + "hideShareDes": "Ocultar todos os compartilhamentos na página de perfil.", + "userHideShare": "Usuário ocultou a lista de compartilhamentos", + "accountPassword": "Senha", + "2fa": "Autenticação 2FA", + "enabled": "Habilitado", + "disabled": "Desabilitado", + "appearance": "Aparência", + "themeColor": "Cor do tema", + "darkMode": "Modo escuro", + "syncWithSystem": "Sistema", + "fileList": "Lista de arquivos", + "timeZone": "Fuso horário", + "webdavServer": "Servidor", + "userName": "Nome de usuário", + "manageAccount": "Gerenciar contas", + "uploadImage": "Enviar do arquivo", + "useGravatar": "Usar Gravatar ", + "changeNick": "Alterar apelido", + "originalPassword": "Senha atual", + "enable2FA": "Habilitar autenticação 2FA", + "disable2FA": "Desabilitar autenticação 2FA", + "2faDescription": "Use qualquer aplicativo móvel 2FA ou software de gerenciamento de senhas que suporte 2FA para escanear o código QR para adicionar este site. Após escanear, preencha o código de verificação de 6 dígitos fornecido pelo aplicativo 2FA para habilitar 2FA.", + "inputCurrent2FACode": "Digite o código de verificação 2FA atual.", + "timeZoneCode": "Código de fuso horário IANA", + "authenticatorRemoved": "Autenticador removido.", + "authenticatorAdded": "Autenticador adicionado.", + "browserNotSupported": "Não suportado pelo navegador ou ambiente atual.", + "removedAuthenticator": "Remover autenticador", + "removedAuthenticatorConfirm": "Tem certeza de que deseja remover este autenticador?", + "addNewAuthenticator": "Adicionar uma chave de acesso", + "hardwareAuthenticator": "Autenticador de hardware", + "copied": "Copiado para área de transferência.", + "pleaseManuallyCopy": "Navegador atual não suporta, copie manualmente.", + "webdavAccounts": "Contas WebDAV", + "webdavHint": "Servidor WebDAV: {{url}}; Nome de usuário: {{name}} ; A senha é a senha da conta criada abaixo.", + "annotation": "Anotação", + "rootFolder": "Pasta raiz relativa", + "createdAt": "Criado em", + "action": "Ações", + "readonlyOn": "Somente leitura", + "readonlyOff": "Leitura e escrita", + "proxy": "Proxy reverso", + "none": "Nenhum", + "proxied": "Com proxy", + "delete": "Excluir", + "listEmpty": "Nenhum registro.", + "createNewAccount": "Criar nova conta", + "taskType": "Tipo de tarefa", + "taskStatus": "Status", + "taskProgress": "Progresso da tarefa", + "errorDetails": "Detalhes do erro", + "queueing": "Na fila", + "processing": "Processando", + "failed": "Falhou", + "canceled": "Cancelado", + "finished": "Concluído", + "fileTransfer": "Transferência de arquivo", + "fileRecycle": "Reciclagem de arquivo", + "importFiles": "Importar arquivos externos", + "transferProgress": "{{num}} arquivos concluídos", + "waiting": "Pendente", + "compressing": "Comprimindo", + "decompressing": "Descomprimindo", + "downloading": "Baixando", + "indexing": "Indexando", + "listing": "Inserindo", + "allShares": "Compartilhado", + "trendingShares": "Em alta", + "totalShares": "Compartilhamentos criados", + "fileName": "Nome do arquivo", + "shareDate": "Compartilhado em", + "downloadNumber": "Downloads", + "viewNumber": "Visualizações", + "language": "Idioma", + "iOSApp": "App iOS/iPadOS", + "connectByiOS": "Conectar ao <0>{{title}} através de dispositivos iOS/iPadOS.", + "downloadOurApp": "Baixe nosso APP:", + "fillInEndpoint": "Escaneie o código QR abaixo com nosso App (NÃO use outro app para escanear):", + "loginApp": "Você pode começar a usar o App agora. Se encontrar problemas com o código QR, também pode tentar inserir manualmente seu nome de usuário e senha para fazer login.", + "relocateFileTo": "Realocar política de armazenamento para {{policy}} para <0>{{more}}", + "extractFileTo": "Extrair <0>{{more}} para <1>", + "createArchiveTo": "Criar arquivo comprimido para <1> para <0>{{more}}", + "importFileTo": "Importar arquivos de {{policy}} para <0>" + }, + "vas": { + "points": "Pontos", + "paid": "Pago", + "fulfillFailedStatus": "Falha ao cumprir", + "unpaid": "Não pago", + "amount": "Quantia", + "tradeNo": "Nº da Transação", + "payments": "Pagamentos", + "creditReasonShareGain": "Link de compartilhamento comprado", + "creditReasonSharePay": "Compra na loja", + "creditReasonRecharge": "Recarga", + "creditChanges": "Alterações de crédito", + "payXPoints": "Pagar com <0>", + "pointsPayAvailable": "Este produto suporta pagamento por pontos, você pode escolher pagar <0> no próximo passo.", + "payAmount": "Pagar {{price}}", + "purchaseSomething": "Comprar {{name}}", + "redeem": "Resgatar", + "shop": "Loja", + "resumeTicket": "Ticket de retomada", + "resumeTicketDes": "Você pode encontrá-lo no e-mail de confirmação do pedido enviado após o pagamento.", + "restorePurchase": "Restaurar compra", + "restorePurchaseDes": "Restaurar compra com o \"Ticket de retomada\" no e-mail de confirmação do pedido.", + "paymentSuccess": "Pagamento bem-sucedido", + "fulfillFailed": "Falha ao cumprir pedido, entre em contato com o administrador do site.", + "paidButton": "Pagamento concluído", + "payInNewWindow": "Complete o pagamento na nova janela. Não feche esta página antes que o pagamento seja concluído. Se a nova janela não aparecer, <0>clique aqui.", + "paymentFailedTitle": "Falha ao processar pagamento", + "paymentEmailHelper": "E-mail é necessário para enviar recibo de compra, já que você não está logado.", + "payEquivalentCash": "Pagar dinheiro equivalente: {{num}}", + "payWithCash": "Pagar com dinheiro", + "recharge": "Recarregar", + "pointsBalance": "Saldo de pontos: {{num}}", + "loginRequired": "Login necessário", + "payWithPoints": "Pagar com pontos", + "purchaseLogin": "Faça <0>login antes de continuar comprando.", + "noAvailableSharePurchaseMethod": "Nenhum método de compra disponível.", + "purchaseShareLink": "Comprar link de compartilhamento", + "loginWith": "Entrar com {{name}}", + "sso": "SSO", + "qq": "QQ", + "quota": "Cota", + "exceedQuota": "Sua capacidade usada excedeu a cota, exclua os arquivos extras ou compre mais armazenamento o mais rápido possível.", + "extendStorage": "Comprar armazenamento", + "folderPolicySwitched": "Política de armazenamento para pasta atual foi alterada para \"{{name}}\"", + "switchFolderPolicy": "Alternando políticas de armazenamento de pasta", + "setPolicyForFolder": "Definir a política de armazenamento para a pasta atual: ", + "manageMount": "Gerenciar montagens", + "saveToMyFiles": "Salvar em meus arquivos", + "report": "Denunciar abuso", + "reportTarget": "Alvo da denúncia", + "reportReason": "Razão", + "reportReasonOptions": ["Violação de direitos autorais", "Conteúdo prejudicial", "Spam", "Outro"], + "reportDescription": "Descrição adicional", + "reportAbuseSuccess": "Denúncia enviada.", + "migrateStoragePolicy": "Migrar política de armazenamento", + "fileSaved": "Arquivo(s) salvo(s).", + "sharePurchaseTitle": "Você precisa pagar <0> antes de acessar este link.", + "payToDownload": "Pagar para baixar", + "creditToBePaid": "Pontos a serem pagos", + "creditGainPredict": "Ganhar {{num}} pontos para você por compra", + "creditPrice": " (Custo {{num}} créditos)", + "creditFree": " (Créditos grátis)", + "cancelSubscription": "O cancelamento foi bem-sucedido e a mudança entrará em vigor em alguns minutos ou horas.", + "qqUnlinked": "Desvinculado da conta QQ.", + "groupExpire": "(Expira em <0>)", + "manuallyCancelSubscription": "Cancelar assinatura do grupo de usuário atual", + "qqAccount": "Conta QQ", + "connect": "Conectar", + "unlink": "Desvincular", + "credits": "Créditos", + "cancelSubscriptionTitle": "Cancelar assinatura", + "cancelSubscriptionWarning": "Você retornará ao grupo de usuário inicial e os créditos pagos não são reembolsáveis, tem certeza de que deseja continuar?", + "mountPolicy": "Montar política de armazenamento", + "mountDescription": "Após montar uma política de armazenamento em uma pasta, novos arquivos enviados para esta pasta ou subpastas serão armazenados usando a política de armazenamento montada. Copiar e mover para esta pasta não aplicará a política de armazenamento montada; quando múltiplas pastas pai são especificadas, a política de armazenamento da pasta pai mais próxima será selecionada.", + "mountNewFolder": "Montar nova pasta", + "nsfw": "NSFW", + "malware": "Malware", + "copyright": "Direitos autorais", + "inappropriateStatements": "Declarações inadequadas", + "other": "Outro", + "groupBaseQuota": "Cota base do grupo - {{size}}", + "validPackQuota": "Pacotes de armazenamento - {{size}}", + "used": "Usado - {{size}}", + "total": "Total - {{size}}", + "validStorage": "Armazenamento extra válido", + "buyStorage": "Comprar armazenamento", + "useGiftCode": "Resgatar com código de presente", + "packName": "Nome do pacote", + "activationDate": "Data de ativação", + "validDuration": "Duração", + "expiredAt": "Expira em", + "days": "{{num}} dias", + "pleaseInputGiftCode": "Digite o código de presente.", + "pleaseSelectAStoragePack": "Selecione um pacote de armazenamento.", + "paymentMethod": "Pagar com", + "noAvailableMethod": "Nenhum método de pagamento disponível", + "alipay": "Alipay", + "wechatPay": "Wechat Pay", + "payByCredits": "Créditos", + "purchaseDuration": "Duração", + "creditsNum": "Qtd. de créditos:", + "store": "Loja", + "storageExpansion": "Expansão de armazenamento", + "membership": "Assinaturas", + "buyCredits": "Créditos", + "subtotal": "Subtotal: ", + "creditsTotalNum": "{{num}} créditos", + "purchaseNow": "Comprar agora", + "recommended": "Recomendado", + "enterGiftCode": "Digite código de presente", + "qrcodeAlipay": "Use o Alipay para escanear o código QR abaixo para completar o pagamento, esta página será automaticamente atualizada após o pagamento ser concluído.", + "qrcodeWechat": "Use o WeChat para escanear o código QR abaixo para completar o pagamento, esta página será automaticamente atualizada após o pagamento ser concluído.", + "qrcodeCustom": "Escaneie o código QR abaixo para completar o pagamento, ou <0>abra o link de pagamento diretamente, esta página será automaticamente atualizada após o pagamento ser concluído.", + "paymentCompleted": "Pagamento concluído", + "productDelivered": "Suas compras foram processadas.", + "confirmRedeem": "Resgatar", + "productType": "Produto:", + "qyt": "Qtd: ", + "duration": "Duração: ", + "subscribe": "Assinar", + "selected": "Selecionado: ", + "paymentQrcode": "QRCode de pagamento", + "validDurationDays": "{{num}} dias", + "reportSuccessful": "Denúncia enviada.", + "additionalDescription": "Descrição adicional", + "announcement": "Anúncio", + "dontShowAgain": "Não mostrar novamente", + "openPaymentLink": "Abrir link de pagamento", + "creditReasonAdjust": "Ajuste manual" + } +} diff --git a/public/locales/pt-BR/common.json b/public/locales/pt-BR/common.json new file mode 100755 index 0000000..6ec9a13 --- /dev/null +++ b/public/locales/pt-BR/common.json @@ -0,0 +1,120 @@ +{ + "pageNotFound": "Página não encontrada", + "unknownError": "Erro desconhecido", + "errLoadingSiteConfig": "Não foi possível carregar configuração do site: ", + "newVersionRefresh": "Uma nova versão da página atual está disponível.", + "update": "Atualizar", + "errorDetails": "Detalhes", + "renderError": "Há um erro na renderização da página, tente atualizar esta página.", + "ok": "OK", + "cancel": "Cancelar", + "select": "Selecionar", + "copyToClipboard": "Copiar", + "close": "Fechar", + "dismiss": "Descartar", + "intlDateTime": "{{val, datetime}}", + "seconds": "s [segundos]", + "minutes": "m [minutos] s [segundos]", + "hours": "H [horas] m [minutos]", + "days": "{{d}} dias", + "timeAgoLocaleCode": "pt_BR", + "forEditorLocaleCode": "pt-BR", + "artPlayerLocaleCode": "pt", + "requestID": "ID da Solicitação: {{id}}", + "object": "Objeto", + "error": "Erro", + "areYouSure": "Tem certeza?", + "incorrectSizeInput": "Entrada de tamanho incorreta", + "of": "de", + "rowsPerPage": "Linhas por página", + "custom": "Personalizado", + "enter": "Entrar", + "captcha": { + "cap": { + "human": "Sou humano", + "verifying": "Verificando...", + "verified": "Você é humano" + } + }, + "errors": { + "401": "Faça login primeiro.", + "403": "Você não tem permissão para realizar esta ação.", + "404": "Recurso não encontrado.", + "409": "Conflito. ({{message}})", + "40001": "Parâmetros de entrada inválidos ({{message}}).", + "40002": "Falha no envio.", + "40003": "Falha ao criar pasta.", + "40004": "Objeto com o mesmo nome já existe.", + "40005": "Assinatura expirada.", + "40006": "Tipo de política não suportado.", + "40007": "Grupo atual não tem permissão para realizar tal ação.", + "40011": "Sessão de envio não existe ou expirou.", + "40012": "Ãndice de parte inválido. ({{message}})", + "40013": "Comprimento de conteúdo inválido. ({{message}})", + "40014": "Excede limite de tamanho de lote para obter link de origem.", + "40015": "Excede limite de tamanho de lote do aria2.", + "40016": "Caminho não encontrado.", + "40017": "Esta conta foi bloqueada.", + "40018": "Esta conta não está ativada.", + "40019": "Este recurso não está habilitado.", + "40020": "Credencial inválida ou expirada.", + "40021": "Usuário não encontrado.", + "40022": "Código de verificação não está correto.", + "40023": "Sessão de login não existe.", + "40024": "Não foi possível inicializar WebAuthn.", + "40025": "Falha na autenticação.", + "40026": "Código CAPTCHA não está correto.", + "40027": "Falha na verificação, atualize a página e tente novamente.", + "40028": "Falha na entrega do e-mail.", + "40029": "Este link é inválido.", + "40030": "Este link expirou.", + "40032": "Este e-mail já está em uso.", + "40033": "Esta conta não está ativada, e-mail de ativação foi reenviado.", + "40034": "Este usuário não pode ser ativado.", + "40035": "Política de armazenamento não encontrada.", + "40039": "Grupo não encontrado.", + "40044": "Arquivo não encontrado.", + "40045": "Falha ao listar objetos na pasta fornecida.", + "40047": "Falha ao inicializar sistema de arquivos.", + "40048": "Falha ao criar tarefa", + "40049": "Tamanho do arquivo excede limite.", + "40050": "Tipo de arquivo não permitido.", + "40051": "Cota de armazenamento insuficiente.", + "40052": "Este nome de arquivo ou extensão não é permitido.", + "40053": "Não é possível realizar tal ação na pasta raiz", + "40054": "Arquivo com o mesmo nome já está sendo enviado nesta pasta, limpe as sessões de envio.", + "40055": "Incompatibilidade de metadados do arquivo.", + "40056": "Tipo de arquivo comprimido não suportado.", + "40057": "Política de armazenamento disponível mudou, atualize a lista de arquivos e adicione esta tarefa novamente.", + "40058": "Este compartilhamento não existe ou já expirou.", + "40069": "Senha incorreta.", + "40070": "Este compartilhamento não suporta visualização.", + "40071": "Assinatura inválida.", + "40073": "Arquivo sendo ocupado.", + "40074": "Muitos arquivos selecionados.", + "40079": "Limite máximo de arquivos percorridos excedido, tente reduzir o escopo da operação.", + "40081": "Operação não totalmente bem-sucedida.", + "40082": "Apenas o proprietário do arquivo pode realizar esta ação.", + "40080": "E-mail ou senha incorretos.", + "50001": "Operação de banco de dados falhou. ({{message}})", + "50002": "Falha ao assinar a URL ou solicitação. ({{message}})", + "50004": "Operação de E/S falhou. ({{message}})", + "50005": "Erro interno.", + "50010": "Nó desejado não está disponível.", + "50011": "Falha ao consultar metadados do arquivo." + }, + "vasErrors": { + "40031": "Este provedor de e-mail é proibido, mude para outro.", + "40059": "Você não pode salvar seu próprio compartilhamento.", + "40062": "Créditos insuficientes.", + "40063": "Sua assinatura atual ainda não expirou, vá para a página de configurações para cancelar manualmente a assinatura primeiro.", + "40064": "Você já está nesta assinatura.", + "40065": "Código de presente inválido.", + "40066": "Você já tem uma identidade vinculada, desvincule-a primeiro.", + "40067": "Esta identidade já está vinculada a outra conta.", + "40068": "Esta identidade não está vinculada a nenhuma conta.", + "40072": "Você é administrador, não pode comprar outro grupo.", + "40084": "Sua conta não possui senha, você deve manter pelo menos uma conta vinculada.", + "40085": "O valor total deste pedido é muito pequeno para checkout." + } +} diff --git a/public/locales/pt-BR/dashboard.json b/public/locales/pt-BR/dashboard.json new file mode 100755 index 0000000..5067b53 --- /dev/null +++ b/public/locales/pt-BR/dashboard.json @@ -0,0 +1,1621 @@ +{ + "errors": { + "40036": "A política de armazenamento padrão não pode ser excluída.", + "40037": "Alguns blob(s) de arquivo estão usando esta política, por favor exclua esses blobs de arquivo primeiro.", + "40038": "{{message}} grupo(s) estão usando esta política, por favor desvincule esses grupos primeiro.", + "40040": "Não é possível executar essa ação no grupo do sistema.", + "40041": "{{message}} usuários ainda estão neste grupo, por favor exclua ou desvincule esses usuários primeiro.", + "40042": "Não é possível alterar o grupo do usuário do grupo do sistema.", + "40043": "Não é possível executar essa ação no usuário padrão.", + "40046": "Não é possível executar essa ação no nó principal.", + "40060": "O nó escravo não pode enviar solicitação de callback para o mestre, por favor verifique a configuração do nó mestre: Básico - Informações do Site - URL do Site, certifique-se de que o nó escravo possa acessar esta URL. ({{message}})", + "40061": "Versão do Cloudreve incompatível. ({{message}})", + "40086": "O nó está sendo usado pelas seguintes políticas de armazenamento: {{message}}.", + "50008": "Falha ao atualizar configuração. ({{message}})", + "50009": "Falha ao adicionar política CORS." + }, + "nav": { + "summary": "Resumo", + "settings": "Configurações", + "basicSetting": "Básico", + "email": "Email", + "transportation": "Transmissão", + "appearance": "Aparência", + "image": "Imagens", + "captcha": "Captcha", + "storagePolicy": "Política de Armazenamento", + "nodes": "Nós", + "groups": "Grupos", + "users": "Usuários", + "files": "Arquivos", + "entities": "Blobs de Arquivo", + "shares": "Compartilhamentos", + "tasks": "Tarefas em Segundo Plano", + "remoteDownload": "Download Remoto", + "generalTasks": "Geral", + "title": "Dashboard", + "dashboard": "Dashboard do Cloudreve", + "userSession": "Sessão do usuário", + "fileSystem": "Sistema de arquivos", + "mediaProcessing": "Processamento de mídia", + "queue": "Fila", + "events": "Eventos", + "server": "Servidor", + "customProps": "Propriedades personalizadas", + "abuseReport": "Relatório de abuso" + }, + "summary": { + "generatedAt": "Gerado em <0>", + "confirmSiteURLTitle": "Confirmar URL do site", + "siteURLNotMatch": "A URL do site que você definiu não contém a atual ({{current}}), deseja adicioná-la à lista?", + "setAsPrimary": "Definir como URL principal do site", + "setAsPrimaryDes": "Definir {{current}} como URL principal do site, usado para comunicação com serviços externos e recebimento de callbacks. Por favor, use uma URL que possa ser acessada pela WAN.", + "setAsSecondary": "Adicionar às URLs secundárias", + "setAsSecondaryDes": "Adicionar {{current}} às URLs secundárias, o Cloudreve selecionará automaticamente se deve usá-la com base na URL realmente acessada pelo usuário.", + "siteURLDescription": "Esta configuração é muito importante, certifique-se de que corresponda à URL real do seu site. Você pode alterar esta configuração em Configurações - Básico.", + "ignore": "Ignorar", + "changeIt": "Alterar", + "trend": "Tendência", + "summary": "Resumo", + "totalUsers": "Usuários", + "totalFilesAndFolders": "Arquivos e Pastas", + "shareLinks": "Links de compartilhamento", + "totalBlobs": "Blobs", + "homepage": "Página inicial", + "github": "GitHub", + "documents": "Documentos", + "discordCommunity": "Comunidade Discord", + "telegram": "Grupo Telegram", + "forum": "GitHub Discussions", + "buyPro": "Atualizar para Pro", + "publishedAt": "publicado em <0>", + "licenseExpireAt": "Data de expiração da licença", + "permanentLicense": "Licença permanente", + "offlineLicenseExpireAy": "Data de expiração da licença offline", + "offlineLicenseDes": "O Cloudreve atualizará automaticamente a licença offline antes de expirar se seu servidor estiver conectado à rede.", + "licensedDomains": "Domínios licenciados", + "renew": "Atualizar licença offline", + "manageLicense": "Gerenciar licença", + "volPurchase": "A licença VOL do cliente precisa ser adquirida separadamente no <0>Dashboard de Gerenciamento de Licença. A licença VOL permite que seus usuários se conectem ao seu site usando o <1>Cloudreve iOS gratuitamente, sem a necessidade de os usuários pagarem por uma assinatura para o próprio aplicativo iOS. Após adquirir uma licença, clique em \"Atualizar licença offline\" abaixo.", + "iosVol": "Licença de volume do cliente iOS (VOL)", + "refreshSuccessfully": "Atualizado com sucesso.", + "manualRefresh": "Atualizar licença offline manualmente", + "manualRefreshDes": "Falha ao atualizar a licença offline automaticamente, tente fazer login no <0>Dashboard de Gerenciamento de Licença para obter a licença offline mais recente e cole-a abaixo.", + "announcement": "Anúncio" + }, + "queue": { + "queueName_io_intense": "IO Intensivo", + "queueName_io_intenseDes": "Fila para lidar com grandes quantidades de operações de IO, incluindo: transferência de política de armazenamento, descompressão, compressão.", + "queueName_media_meta": "Extração de Metadados de Mídia", + "queueName_media_metaDes": "Usado para extrair metadados de arquivos de mídia.", + "queueName_recycle": "Reciclagem de Blob", + "queueName_recycleDes": "Usado para excluir blobs de arquivo expirados.", + "queueName_thumb": "Geração de Miniatura", + "queueName_thumbDes": "Usado para gerar miniaturas para arquivos.", + "queueName_remote_download": "Download Remoto", + "queueName_remote_downloadDes": "Usado para processar tarefas de download remoto.", + "failed": "Falhou ({{count}})", + "success": "Sucesso ({{count}})", + "suspending": "Suspenso ({{count}})", + "busyWorker": "Processando ({{count}})", + "submited": "Submetido ({{count}})", + "editQueueSettings": "Editar configurações da fila - {{name}}", + "workerNum": "Threads de trabalho", + "workerNumDes": "Número máximo de tarefas a serem executadas em paralelo na fila de tarefas", + "maxExecution": "Tempo máximo de execução", + "maxExecutionDes": "Tempo máximo de execução (segundos) para uma tarefa, após o qual a tarefa será terminada.", + "backoffFactor": "Fator de recuo", + "backoffFactorDes": "Fator de crescimento para intervalos de tempo de repetição de tarefa.", + "backoffMaxDuration": "Tempo máximo de recuo", + "backoffMaxDurationDes": "Tempo máximo de recuo (segundos) para repetições de tarefa.", + "maxRetry": "Máximo de tentativas", + "maxRetryDes": "Número máximo de tentativas após uma falha de tarefa.", + "retryDelay": "Atraso de repetição", + "retryDelayDes": "Tempo de atraso inicial (segundos) para repetições de tarefa." + }, + "settings": { + "headlessFooter": "Rodapé da página de destino", + "headlessFooterDes": "Conteúdo HTML personalizado exibido na parte inferior das páginas de login, inscrição e resultado de callback.", + "headlessBottom": "Parte inferior da página de destino", + "headlessBottomDes": "Conteúdo HTML personalizado exibido na parte inferior das páginas de login, inscrição e resultado de callback.", + "customHTML": "HTML personalizado", + "customHTMLDes": "Inserir conteúdo HTML personalizado na posição predefinida do site.", + "sidebarBottom": "Parte inferior da barra lateral", + "sidebarBottomDes": "Conteúdo HTML personalizado exibido na parte inferior da barra lateral.", + "addNavItem": "Adicionar item de navegação", + "customNavItems": "Itens personalizados da barra lateral", + "customNavItemsDes": "Você pode adicionar itens personalizados à barra lateral, e os usuários serão redirecionados para o link correspondente quando clicarem.", + "navItemUrl": "Link", + "iconifyNamePlaceholder": "Identificador do ícone Iconify, ex: fluent:home-24-regular", + "imageUrl": "URL da imagem", + "iconifyName": "Nome do ícone Iconify", + "oidc": "OpenID Connect (OIDC)", + "oidcDes": "OpenID Connect (OIDC) é um protocolo de autenticação aberto para verificação de identidade entre diferentes sistemas. Após criar um aplicativo em uma plataforma de identidade de terceiros, adicione <0>{{url}} ao campo \"URI de Redirecionamento\". Para mais detalhes, consulte a <1>documentação.", + "clientID": "ID do Cliente", + "clientIDDes": "O ID do cliente do aplicativo criado na plataforma de identidade de terceiros.", + "clientSecret": "Segredo do cliente", + "clientSecretDes": "O segredo do cliente do aplicativo criado na plataforma de identidade de terceiros.", + "scope": "Escopo", + "scopeDes": "Escopos adicionais para solicitar, separados por vírgulas <0>,. Por padrão, o Cloudreve solicitará <0>openid, <0>email e <0>profile; não é necessário repetir aqui.", + "oidcWellknown": "Configuração OIDC Wellknown", + "oidcWellknownDes": "Documento wellknown da plataforma de identidade de terceiros, contendo as informações de configuração do OpenID Connect.", + "importFromWellknown": "Importar da URL", + "importOidc": "Importar Configuração OIDC Wellknown", + "oidcWellknownUrl": "URL Wellknown", + "oidcWellknownUrlDes": "URL do documento wellknown da plataforma de identidade de terceiros, como <0>https://accounts.google.com/.well-known/openid-configuration.", + "resetUrl": "URL de redefinição", + "exceedToleranceDays": "Dias de tolerância para banimento", + "activateUrl": "URL de ativação", + "domainNotLicensed": "Domínio não licenciado", + "domainNotLicensedDes": "A URL do site que você definiu contém um domínio não autorizado, adicione este subdomínio no <0>Dashboard de Gerenciamento de Licença e clique no botão abaixo para atualizar a licença e tente novamente.", + "showSettings": "Mostrar configurações", + "perPage": "{{num}} por página", + "noNodes": "Nenhum nó disponível.", + "extractMediaMeta": "Extrair metadados de mídia", + "extractMediaMetaDes": "Extrair metadados de arquivos de mídia para exibição e busca. Por padrão, políticas de armazenamento não locais usarão apenas o gerador \"Nativo na política de armazenamento\". Você pode estender a capacidade de miniatura de políticas de armazenamento de terceiros habilitando o recurso \"Proxy do extrator\" na página de configuração da política de armazenamento. Para mais detalhes, consulte a <0>documentação.", + "exif": "EXIF", + "exifDes": "Extrair metadados EXIF de arquivos de imagem para exibição e busca.", + "music": "Metadados de música", + "musicDes": "Extrair metadados de arquivos de música, incluindo título, artista, álbum, etc.", + "ffprobe": "FFprobe", + "ffprobeDes": "Usar FFprobe para extrair metadados de arquivos de vídeo e áudio.", + "maxSizeLocal": "Tamanho máximo do arquivo (Armazenamento local)", + "maxSizeLocalDes": "Tamanho máximo do arquivo para extração de metadados quando o arquivo está armazenado na política de armazenamento local, 0 significa sem limite.", + "maxSizeRemote": "Tamanho máximo do arquivo (Armazenamento remoto)", + "maxSizeRemoteDes": "Tamanho máximo do arquivo para extração de metadados quando o arquivo está armazenado em políticas de armazenamento de terceiros, 0 significa sem limite.", + "exifBruteForce": "Usar força bruta se necessário", + "exifBruteForceDes": "Quando habilitado, o arquivo inteiro será escaneado para encontrar dados EXIF se não puder ser encontrado no local padrão do cabeçalho. Isso pode aumentar o tempo de processamento, mas pode encontrar dados EXIF em locais não padrão.", + "musicCover": "Capa da música", + "musicCoverDes": "Extrair capa do álbum de arquivos de música, suporta contêiner ID3 (v1, 2.2, 2.3 e 2.4). Este gerador depende de qualquer outro gerador de miniatura de imagem (Cloudreve integrado ou VIPS).", + "geocoding": "Geocodificação", + "geocodingDes": "Obter informações de endereço usando o serviço Mapbox baseado nas informações de coordenadas registradas no EXIF da mídia.", + "mapboxAK": "Chave API do Mapbox", + "mapboxAKDes": "Chave API criada no console do Mapbox.", + "geocodingDependencyWarning": "O gerador de geocodificação depende do gerador EXIF, por favor habilite o gerador EXIF.", + "notAppliedToNativeGenerator": "{{prefix}}Não aplicável ao gerador nativo de políticas de armazenamento.", + "notAppliedToOneDriveNativeGenerator": "{{prefix}}Não aplicável ao gerador nativo de políticas de armazenamento OneDrive ou SharePoint.", + "fileBlobMargin": "Margem do Cache de URL do Blob de Arquivo (segundos)", + "fileBlobMarginDes": "Quando o mesmo blob de arquivo é solicitado várias vezes, se a URL inicial tiver um período de validade restante maior que a margem, a mesma URL será reutilizada.", + "fileBlobTimeout": "TTL da URL do Blob de Arquivo (segundos)", + "fileBlobTimeoutDes": "Limitar o período de validade da URL temporária obtida quando os usuários abrem ou baixam arquivos, aplicável apenas a políticas de armazenamento local, WebDAV ou arquivos baixados através de retransmissão do Cloudreve.", + "wopiSessionTimeout": "TTL da sessão WOPI (segundos)", + "wopiSessionTimeoutDes": "Limitar o período de validade de uma única sessão quando os usuários editam arquivos usando WOPI. Após a expiração, os usuários precisam reabrir o arquivo do Cloudreve.", + "oauthRefresh": "Intervalo de atualização para política de armazenamento OAuth", + "oauthRefreshDes": "Definir com que frequência atualizar as credenciais OAuth para políticas de armazenamento (ex: OneDrive) que requerem OAuth. Isso pode prevenir a expiração de credenciais devido a longos períodos de inatividade", + "transitParallelNum": "Máximo de transferências de retransmissão paralelas", + "transitParallelNumDes": "O número máximo de uploads paralelos quando uma única tarefa de retransmissão de arquivo do lado do servidor contém múltiplos arquivos.", + "failedChunkRetry": "Número máximo de tentativas para falhas de upload de chunk", + "failedChunkRetryDes": "O número máximo de tentativas para falhas de upload de chunk, aplicável apenas a uploads do lado do servidor ou transferências de retransmissão.", + "cacheChunks": "Cache de chunks de streaming", + "cacheChunksDes": "Se habilitado, os dados do chunk serão armazenados em cache no diretório temporário do sistema durante a transferência de streaming, para que possam ser usados para tentar novamente uploads de chunk falhados;\n Se desabilitado, uploads de chunk de transferência de streaming não ocuparão espaço extra em disco, mas todo o upload falhará imediatamente se o upload do chunk falhar.", + "folderPropsTimeout": "TTL do cache de estatísticas da pasta (segundos)", + "folderPropsTimeoutDes": "O período de validade do cache de resultado quando os usuários calculam estatísticas da pasta (tamanho, número de arquivos, etc.).", + "slaveAPIExpiration": "TTL da assinatura da API do escravo (segundos)", + "slaveAPIExpirationDes": "O período de validade da assinatura usado pelo nó mestre ao acessar a API do nó escravo.", + "uploadSessionTimeout": "TTL da sessão de upload (segundos)", + "uploadSessionDes": "Em um período de sessão de upload válido, para políticas de armazenamento suportadas, os usuários podem retomar tarefas não concluídas. O valor máximo que pode ser definido é limitado pelas regras de diferentes provedores de política de armazenamento.", + "archiveTimeout": "TTL da sessão de download em lote do lado do servidor (segundos)", + "advanceOptions": "Opções avançadas", + "emojiOptions": "Opções de emoji", + "addCategorize": "Adicionar uma categoria", + "category": "Categoria", + "searchQuery": "Consulta de categorização de arquivo", + "importWopi": "Importar configurações do aplicativo WOPI", + "wopiEndpoint": "Endpoint de Descoberta WOPI", + "wopiDes": "Estender as capacidades de visualização e edição online do Cloudreve integrando com sistemas de processamento de documentos online que suportam o protocolo WOPI. Preencha o endereço de descoberta do serviço WOPI aqui, como <0>https://example.com/hosting/discovery. Para mais detalhes, consulte a <1>documentação.", + "embeddedWebpageViewer": "Visualizador de Página Web Incorporada", + "wopiViewer": "Aplicação WOPI", + "ext": "Extensão", + "invalidWopiActionMapping": "Mapeamento de ação WOPI inválido", + "woapiActionMapping": "Mapeamentos de ação WOPI", + "drawioHost": "Instância DrawIO", + "drawioHostDes": "Você pode usar URL para instância auto-hospedada.", + "openInNew": "Abrir em nova janela", + "openInNewDes": "Se marcado, abrirá diretamente uma nova aba para abrir esta aplicação.", + "maxSize": "Tamanho máximo do arquivo", + "maxSizeDes": "O tamanho máximo do arquivo suportado por esta aplicação. 0 significa sem limite. Se o arquivo exceder este tamanho, ainda será aberto, mas os usuários serão avisados.", + "srcEncodedVar": "URL de acesso temporário do blob de arquivo codificada em URL", + "srcVar": "URL de acesso temporário do blob de arquivo", + "srcBase64Var": "URL de acesso temporário do blob de arquivo codificada em Base64", + "nameEncodedVar": "Nome do arquivo codificado em URL", + "versionEntityVar": "O ID do blob da versão do arquivo aberto, vazio significa a versão mais recente.", + "fileIdVar": "ID do arquivo", + "userIdVar": "ID do usuário, vazio quando não logado.", + "userDisplayNameVar": "Nome de exibição do usuário codificado em URL.", + "fileViewers": "Aplicações de arquivo", + "addViewer": "Adicionar uma aplicação", + "viewerGroupTitle": "Grupo de aplicação #{{index}}", + "viewerType": "Tipo", + "viewerPlatform": "Plataforma", + "viewerPlatformDes": "Selecione a plataforma correspondente para exibir a aplicação apenas nessa plataforma.", + "viewerPlatformPC": "Desktop", + "viewerPlatformMobile": "Mobile", + "viewerPlatformAll": "Todas", + "displayName": "Nome de exibição", + "displayNameDes": "Nome de exibição para usuários, suporta chave i18next.", + "viewerEnabled": "Habilitado", + "newFileAction": "Ações de novo arquivo", + "newFileActionDes": "Ao adicionar este mapeamento, os usuários verão esta opção de aplicação ao clicar no botão \"Novo\".", + "addNewFileAction": "Adicionar um mapeamento", + "builtinViewerType": "Aplicação integrada", + "wopiViewerType": "WOPI", + "customViewerType": "Personalizado", + "nMapping": "{{num}} mapeamento(s)", + "editViewerTitle": "Editar {{name}}", + "builtInIconUrlDes": "Esta aplicação integrada tem um ícone padrão. Quando a URL do ícone é deixada em branco, o ícone padrão será usado.", + "viewerUrl": "URL da aplicação", + "viewerUrlDes": "URL da aplicação personalizada, <0>variáveis mágicas são suportadas.", + "addIcon": "Adicionar um ícone", + "exts": "Lista de extensões", + "icon": "Ãcone", + "iconUrl": "URL do ícone", + "iconColor": "Cor", + "iconColorDark": "Cor (Modo escuro)", + "fileIcons": "Ãcones de arquivo", + "builtinIcon": "Integrado", + "mimeMapping": "Mapeamento de tipo MIME", + "mimeMappingDes": "Mapeamento de tipo MIME em formato JSON, onde a chave é a extensão do arquivo e o valor é o tipo MIME. O Cloudreve determinará o tipo MIME do arquivo com base na extensão do arquivo e nesta configuração.", + "mapProvider": "Provedor de mapa", + "mapProviderDes": "Provedor de mapa usado para exibir informações de localização de mídia.", + "mapGoogle": "Google Maps (Leaflet)", + "mapOpenStreetMap": "OpenStreetMap (Leaflet)", + "mapboxMap": "OpenStreetMap (Mapbox)", + "mapboxAccessToken": "Token de acesso Mapbox", + "mapboxAccessTokenDes": "Token de acesso público criado no <0>Console Mapbox.", + "tileType": "Tipo de tile padrão", + "tileTypeDes": "Tipo de tile padrão para Google Maps.", + "tileTypeTerrain": "Terreno", + "tileTypeSatellite": "Satélite", + "tileTypeGeneral": "Regular", + "maxPageSize": "Tamanho máximo da página", + "maxPageSizeDes": "Limitar o número máximo de arquivos que os usuários podem ajustar por página.", + "maxRecursiveSearch": "Contagem máxima de busca recursiva", + "maxRecursiveSearchDes": "O número máximo de buscas recursivas permitidas ao procurar arquivos. Se o número de arquivos pesquisados exceder este limite, a busca será interrompida e avisará o usuário.", + "maxBatchSize": "Tamanho máximo do lote", + "maxBatchSizeDes": "O número máximo de arquivos que os usuários podem operar em lote, apenas o nível superior será contado, e o número de arquivos em subdiretórios não será contado.", + "defaultPagination": "Método de paginação para lista de arquivos", + "cursorPagination": "Paginação por cursor", + "cursorPaginationDes": "Mais arquivos serão carregados automaticamente quando o usuário rolar para o fundo. Este método tem melhor desempenho para listas grandes de arquivos, mas o número total de páginas não pode ser visto.", + "offsetPagination": "Paginação por offset", + "offsetPaginationDes": "A navegação de paginação será exibida na parte inferior da página; os usuários podem ver o número total de páginas e pular para uma página específica. Este método tem desempenho ligeiramente pior para listas grandes de arquivos.", + "defaultPaginationDes": "A paginação por cursor será forçada a ser usada ao pesquisar, independentemente das configurações acima.", + "publicResourceMaxAge": "Idade máxima do cache de recursos estáticos (segundos)", + "publicResourceMaxAgeDes": "A idade máxima do cache para recursos estáticos publicamente acessíveis (ex: arquivos, miniaturas e fotos de perfil do usuário).", + "cronDes": "{{des}} Uma <0>sintaxe Cron correta é necessária aqui. É necessário reiniciar o Cloudreve para entrar em vigor.", + "entityCollectInterval": "Intervalo de reciclagem de Blob de arquivo", + "entityCollectIntervalDes": "Definir com que frequência escanear e excluir blobs de arquivo expirados.", + "trashBinInterval": "Intervalo de escaneamento da lixeira", + "trashBinIntervalDes": "Definir com que frequência escanear e excluir arquivos expirados na lixeira.", + "logtoName": "Nome do método de login", + "logtoNameDes": "Nome do método de login, exibido aos usuários. Padrão é \"SSO\", suporta chave i18next.", + "logtoDirectSSO": "Login direto", + "logtoDirectSSODes": "Se você quiser pular a tela de login do Logto e pular diretamente para o login de terceiros ou SSO, preencha o identificador do conector social aqui. Para detalhes, consulte a <0>documentação do Logto.", + "logtoEndpoint": "Endpoint do Logto", + "logtoEndpointDes": "A URL do endpoint do Logto obtida do painel de gerenciamento de aplicação, que pode ser uma instância auto-hospedada.", + "logtoKey": "Segredo da aplicação", + "logtoKeyDes": "Segredo da aplicação criado na página de gerenciamento de aplicação.", + "logtoAppIDDes": "ID da aplicação criado na página de gerenciamento de aplicação.", + "logto": "Logto", + "logtoDes": "Com <0>Logto, você pode conseguir mais logins de plataformas de terceiros, como Apple, GitHub, Microsoft Entra ID, Google, SMS, etc. Crie uma \"Aplicação Web Tradicional\" no portal de gerenciamento do Logto e adicione <1>{{url}} aos \"URIs de Redirecionamento\".", + "thirdPartySignIn": "Login de terceiros", + "logo": "LOGO", + "logoDes": "URL do LOGO, forneça logos diferentes para modos escuro e claro.", + "dark": "Modo escuro", + "light": "Modo claro", + "tosUrl": "URL dos termos de serviço", + "tosUrlDes": "Será exibido no rodapé da página de login ou registro, deixe em branco para não exibir.", + "privacyUrl": "URL da política de privacidade", + "privacyUrlDes": "Será exibido no rodapé da página de login ou registro, deixe em branco para não exibir.", + "addSecondary": "Adicionar URL secundária do site", + "secondarySiteURL": "Secundária", + "secondaryDes": "Você também pode adicionar outras URLs secundárias, o Cloudreve selecionará automaticamente se deve usá-la com base na URL realmente acessada pelo usuário. A URL do seu site deve estar licenciada.", + "primarySiteURL": "Principal", + "primarySiteURLDes": "A URL principal do site é usada para comunicação com serviços externos e recebimento de callbacks (ex: pagamento, provedor de armazenamento), use uma URL que possa ser acessada pela WAN.", + "revert": "Reverter alterações", + "saved": "Configurações salvas.", + "save": "Salvar", + "basicInformation": "Informações Básicas", + "mainTitle": "Nome do site", + "mainTitleDes": "Nome da instância.", + "siteDescription": "Descrição do site", + "siteDescriptionDes": "Descrição do website, que pode ser exibida no resumo da página compartilhada.", + "siteURL": "URL do Site", + "customFooterHTML": "HTML do rodapé personalizado", + "customFooterHTMLDes": "Código HTML personalizado inserido na parte inferior da página.", + "announcement": "Anúncio", + "announcementDes": "Anúncios exibidos para usuários logados. Valor em branco não será exibido. Após este conteúdo ser alterado, todos os usuários verão o anúncio novamente.", + "supportHTML": "Digite HTML ou texto simples.", + "branding": "Marca", + "smallIcon": "Ãcone pequeno", + "smallIconDes": "URL do ícone pequeno, formato ico ou svg. Este ícone também será mostrado em abas do navegador, favoritos e atalhos da área de trabalho.", + "mediumIcon": "Ãcone médio", + "mediumIconDes": "URL do ícone médio, tamanho preferido 192x192, formato png.", + "largeIcon": "Ãcone grande", + "largeIconDes": "URL do ícone grande, tamanho preferido 512x512, formato png. Este ícone também será mostrado ao trocar de conta no aplicativo iOS.", + "displayMode": "Modo de exibição", + "displayModeDes": "O modo de exibição de uma aplicação PWA após ser instalada.", + "themeColor": "Cor do tema", + "themeColorDes": "Valor de cor CSS que afeta a cor da barra de status na tela de inicialização do PWA, a barra de status na página de conteúdo e a barra de endereços.", + "backgroundColor": "Cor de fundo", + "backgroundColorDes": "Valor de cor CSS.", + "hint": "Dica", + "webauthnNoHttps": "Web Authn requer que seu site tenha HTTPS habilitado, e confirme que em Configurações - Básico - URL do Site também usa HTTPS.", + "accountManagement": "Contas", + "allowNewRegistrations": "Aceitar novos cadastros", + "allowNewRegistrationsDes": "Após desabilitado, nenhum novo usuário pode ser registrado, a menos que seja adicionado manualmente pelos administradores.", + "emailActivation": "Ativação por email", + "emailActivationDes": "Após habilitado, novos usuários precisam clicar no link de ativação no email para completar os cadastros. Certifique-se de que as <0>configurações de entrega de email estejam corretas, caso contrário o email de ativação não será entregue.", + "captchaForSignup": "Captcha para cadastros", + "captchaForSignupDes": "Se deve habilitar o captcha para cadastros.", + "captchaForLogin": "Captcha para logins", + "captchaForLoginDes": "Se deve habilitar o captcha para logins.", + "captchaForReset": "Captcha para redefinir senha", + "captchaForResetDes": "Se deve habilitar o captcha para redefinir senha.", + "captchaForAbuseReport": "Captcha para relatório de abuso", + "captchaForAbuseReportDes": "Se deve habilitar o captcha para relatório de abuso.", + "webauthnDes": "Se deve permitir que os usuários façam login com dispositivos de autenticação de hardware, como: rosto, impressão digital ou chave USB; o site deve habilitar HTTPS.", + "webauthn": "Login com Passkeys", + "defaultSymbolics": "Atalhos de compartilhamento padrão", + "defaultSymbolicsDes": "Atalhos de link de compartilhamento padrão no diretório raiz de novos usuários. Procure por links de compartilhamento por ID, você pode ver o ID no lado esquerdo da <0>lista de compartilhamento.", + "searchShare": "Procurar ID de compartilhamento...", + "defaultGroup": "Grupo padrão", + "defaultGroupDes": "O grupo de usuário inicial após o registro do usuário.", + "testMailSent": "Email de teste enviado.", + "testSMTPSettings": "Testar configurações SMTP", + "testSMTPTooltip": "O Cloudreve usará suas configurações SMTP atuais para enviar um email de teste, não é necessário salvar as configurações antes de testar.", + "recipient": "Destinatário", + "send": "Enviar", + "smtp": "SMTP", + "senderName": "Nome do remetente", + "senderNameDes": "O nome do remetente exibido no email.", + "senderAddress": "Endereço do remetente", + "senderAddressDes": "Endereço de email do remetente.", + "smtpServer": "Servidor SMTP", + "smtpServerDes": "Endereço do servidor SMTP, sem número da porta.", + "smtpPort": "Porta SMTP", + "smtpPortDes": "Porta do servidor SMTP.", + "smtpUsername": "Nome de usuário SMTP", + "smtpUsernameDes": "Nome de usuário SMTP, geralmente o mesmo que o endereço do remetente.", + "smtpPassword": "Senha SMTP", + "smtpPasswordDes": "Senha da caixa de correio do remetente.", + "replyToAddress": "Endereço de resposta", + "replyToAddressDes": "A caixa de correio usada para receber emails de resposta quando os usuários respondem aos emails enviados pelo sistema.", + "enforceSSL": "Forçar conexão SSL", + "enforceSSLDes": "Se deve forçar uma conexão criptografada SSL. Se você não conseguir enviar emails, pode desativar isso e o Cloudreve tentará usar STARTTLS e decidir se deve usar conexões criptografadas.", + "smtpTTL": "TTL da conexão SMTP (segundos)", + "smtpTTLDes": "Conexões SMTP estabelecidas durante o período TTL serão reutilizadas por novas solicitações de entrega de email.", + "emailTemplates": "Modelos de Email", + "activateNewUser": "Ativar novo usuário", + "resetPassword": "Redefinir senha", + "sendTestEmail": "Enviar email de teste", + "transportation": "Transmissão", + "workerNum": "Número de worker", + "workerNumDes": "O número máximo de tarefas a serem executadas em paralelo pela fila de tarefas do nó mestre, é necessário reiniciar o Cloudreve para entrar em vigor.", + "tempFolder": "Pasta temporária", + "tempFolderDes": "Usada para armazenar arquivos temporários gerados por tarefas como descompressão, compressão, etc.", + "textEditMaxSize": "Tamanho máximo de arquivos de documento editáveis", + "textEditMaxSizeDes": "O tamanho máximo de um arquivo de documento que pode ser editado online, arquivos além deste tamanho não podem ser editados online. Esta configuração se aplica a editores Web online como texto simples, código e documentos do Office (WOPI).", + "resetConnection": "Redefinir conexão após falha no upload", + "resetConnectionDes": "Se habilitado, o servidor forçará a redefinição da conexão se a verificação do upload falhar.", + "batchDownload": "Download em lote", + "previewURL": "URL de visualização", + "cannotDeleteDefaultTheme": "Não é possível excluir o tema padrão.", + "themeConfig": "Configurações", + "actions": "Ações", + "wrongFormat": "Formato incorreto.", + "avatar": "Avatar", + "gravatarServer": "Servidor Gravatar", + "gravatarServerDes": "URL do servidor espelho do Gravatar.", + "avatarFilePath": "Caminho do arquivo de avatar", + "avatarFilePathDes": "Caminho para salvar arquivos de avatar do usuário, relativo à pasta de dados do Cloudreve.", + "avatarSize": "Tamanho máximo do arquivo de avatar", + "avatarSizeDes": "Tamanho máximo dos arquivos de avatar que os usuários podem fazer upload.", + "avatarImageSize": "Tamanho da imagem (px)", + "avatarImageSizeDes": "A imagem de perfil selecionada será redimensionada para o tamanho dado, em pixels.", + "filePreview": "Visualização de Arquivo", + "thumbnails": "Miniaturas", + "thumbnailDoc": "Para mais informações sobre miniatura, veja o <0>documento.", + "thumbnailDocLink": "https://docs.cloudreve.org/usage/thumbnails", + "thumbnailBasic": "Básico", + "generators": "Geradores de miniatura", + "thumbMaxSize": "Tamanho máximo do arquivo original", + "thumbMaxSizeDes": "O tamanho máximo do arquivo original para o qual miniaturas podem ser geradas, miniaturas não serão geradas se os arquivos excederem este tamanho.", + "generatorProxyWarning": "Por padrão, políticas de armazenamento não locais usarão apenas o gerador \"Nativo na política de armazenamento\". Você pode estender a capacidade de miniatura de políticas de armazenamento de terceiros habilitando o recurso \"Proxy do gerador\" na página de configuração da política de armazenamento. Para mais detalhes, consulte a <0>documentação.", + "policyBuiltin": "Nativo na política de armazenamento", + "policyBuiltinDes": "Usar a API nativa do provedor de armazenamento para processar miniaturas. Para política local e S3, este gerador não está disponível e fará fallback automaticamente para outros geradores. Para outras políticas de armazenamento, vá para a página de configuração da política de armazenamento para configurar este gerador.", + "cloudreveBuiltin": "Cloudreve integrado", + "cloudreveBuiltinDes": "Apenas imagens nos formatos PNG, JPEG, GIF são suportadas usando as capacidades de processamento de imagem integradas do Cloudreve.", + "libreOffice": "LibreOffice", + "libreOfficeDes": "Usar LibreOffice para gerar miniaturas para documentos do Office. Este gerador depende de qualquer outro gerador de miniatura de imagem (Cloudreve integrado ou VIPS).", + "libraw": "LibRaw / DCRaw", + "librawDes": "Usar o programa de amostra DCRaw do LibRaw, ou o executável DCRaw original para gerar miniaturas para imagens RAW.", + "vips": "VIPS", + "vipsDes": "Usar libvips para processar imagens de miniatura, suporta mais formatos de imagem e consome menos recursos.", + "thumbDependencyWarning": "Os geradores LibreOffice ou capa de música dependem dos geradores Cloudreve integrado ou VIPS, habilite um deles.", + "ffmpeg": "FFmpeg", + "ffmpegDes": "Usar FFmpeg para gerar miniaturas de vídeo.", + "executable": "Executável", + "executableDes": "O caminho ou comando do executável do gerador de terceiros.", + "executableTest": "Testar", + "executableTestSuccess": "Gerador funciona, versão: {{version}}", + "generatorExts": "Extensões disponíveis", + "generatorExtsDes": "Lista de extensões de arquivo disponíveis para este gerador, use vírgula , para separar múltiplas.", + "ffmpegSeek": "Local de captura da miniatura", + "ffmpegSeekDes": "Definir o tempo de interceptação da miniatura, é recomendado escolher um valor menor para acelerar o processo de geração. Se o comprimento real do vídeo for excedido, a geração da miniatura falhará.", + "ffmpegExtraArgs": "Argumentos de entrada extras", + "ffmpegExtraArgsDes": "Argumentos de entrada extras para chamar o FFmpeg.", + "generatorProxy": "Proxy do gerador", + "enableThumbProxy": "Usar proxy do gerador", + "proxyPolicyList": "Política de armazenamento habilitada", + "proxyPolicyListDes": "Multi-selecionável. Se habilitado, arquivos cuja política de armazenamento não suporta geração nativa, suas miniaturas serão geradas por proxy pelo Cloudreve.", + "thumbWidth": "Largura máxima", + "thumbHeight": "Altura máxima", + "thumbSuffix": "Sufixo do arquivo Blob", + "thumbSuffixDes": "O sufixo anexado ao nome do arquivo Blob original para a miniatura gerada, ", + "thumbFormat": "Formato da imagem", + "thumbFormatDes": "Formato de imagem preferido, se o gerador não suportar, fará downgrade automaticamente para formato jpg.", + "thumbQuality": "Qualidade", + "thumbQualityDes": "Porcentagem de qualidade de compressão, válida apenas para codificação jpg e webp. ", + "thumbGC": "Executar GC após geração da miniatura", + "captcha": "Captcha", + "captchaType": "Tipo de captcha", + "captchaTypeDes": "Selecionar tipo de captcha e provedor.", + "plainCaptcha": "Gráfico simples", + "reCaptchaV2": "reCAPTCHA V2", + "turnstile": "Cloudflare Turnstile", + "turnstileSiteKey": "Chave do Site", + "turnstileSiteKSecret": "Segredo", + "cap": "Cap", + "capInstanceURL": "URL da Instância", + "capInstanceURLDes": "A URL do seu servidor Cap auto-hospedado. Para mais detalhes, veja a <0>documentação do modo standalone.", + "capSiteKey": "Chave do Site", + "capSiteKeyDes": "A chave do site do dashboard do seu servidor Cap.", + "capSecretKey": "Chave Secreta", + "capSecretKeyDes": "A chave secreta do dashboard do seu servidor Cap.", + "capAssetServer": "Fonte do Servidor de Assets", + "capAssetServerDes": "Escolha a fonte para carregar assets estáticos do captcha Cap. Usar servidor auto-implantado requer definir variáveis de ambiente no lado do servidor, consulte <0>habilitar servidor de assets.", + "capAssetServerJsdelivr": "jsDelivr CDN", + "capAssetServerUnpkg": "unpkg CDN", + "capAssetServerInstance": "Servidor auto-hospedado", + "captchaProvider": "Provedor de captcha", + "captchaWidth": "Largura", + "captchaHeight": "Altura", + "captchaLength": "Comprimento", + "captchaLengthDes": "O comprimento dos caracteres no captcha.", + "captchaMode": "Modo", + "captchaModeNumber": "Números", + "captchaModeLetter": "Letras", + "captchaModeMath": "Matemática", + "captchaModeNumberLetter": "Números + Letras", + "captchaElement": "Elementos dentro da imagem do captcha.", + "complexOfNoiseText": "Complexidade do texto de ruído", + "complexOfNoiseDot": "Complexidade dos pontos de ruído", + "showHollowLine": "Mostrar linhas ocas", + "showNoiseDot": "Mostrar pontos de ruído", + "showNoiseText": "Mostrar texto de ruído", + "showSlimeLine": "Mostrar linhas finas", + "showSineLine": "Mostrar linhas senoidais", + "siteKey": "Chave do Site", + "siteKeyDes": "Você pode encontrá-la na <0>Página de Gerenciamento de Aplicação.", + "siteSecret": "Segredo", + "siteSecretDes": "Você pode encontrá-lo na <0>Página de Gerenciamento de Aplicação.", + "secretID": "SecretId", + "secretIDDes": "Você pode encontrá-lo na <0>Página de Gerenciamento de Acesso.", + "secretKey": "SecretKey", + "secretKeyDes": "Você pode encontrá-la na <0>Página de Gerenciamento de Acesso.", + "tCaptchaAppID": "APPID", + "tCaptchaAppIDDes": "Você pode encontrá-lo na <0>Página de Gerenciamento de Captcha.", + "tCaptchaSecretKey": "Chave Secreta do App", + "tCaptchaSecretKeyDes": "Você pode encontrá-la na <0>Página de Gerenciamento de Captcha.", + "staticResourceCache": "Cache de recursos estáticos públicos", + "staticResourceCacheDes": "Idade máxima do cache para recursos estáticos publicamente acessíveis (ex: link de origem da política local, link de download).", + "creditSystem": "Sistema de crédito", + "creditAndVAS": "Crédito e VAS", + "enableCredit": "Habilitar sistema de crédito", + "enableCreditDes": "Habilitar sistema de crédito para permitir que os usuários definam preços para seus links de compartilhamento.", + "creditPrice": "Preço do crédito", + "creditPriceDes": "Preço para recarregar pontos de crédito com dinheiro (em unidade mínima de moeda). Preencha 0 para desabilitar recarga de crédito.", + "shareScoreRate": "Taxa de comissão do proprietário do compartilhamento", + "shareScoreRateDes": "Porcentagem (1-100) de pontos de crédito que os proprietários do compartilhamento recebem quando seus links de compartilhamento são comprados.", + "cronNotifyUser": "Intervalo de escaneamento para usuários acima do limite", + "cronNotifyUserDes": "Escanear e enviar lembretes por email para usuários acima do limite, ", + "cronBanUser": "Cronograma de banimento de usuário", + "cronBanUserDes": "Escanear e banir usuários excedendo limites de armazenamento e períodos de buffer", + "anonymousPurchase": "Compra anônima", + "anonymousPurchaseDes": "Permitir que usuários não logados comprem links de compartilhamento diretamente", + "shopNavEnabled": "Mostrar Navegação da Loja", + "shopNavEnabledDes": "Exibir itens 'Loja' na navegação da barra lateral", + "paymentSettings": "Configurações de pagamento", + "currencyCode": "Código da moeda", + "currencyCodeDes": "Código de moeda de três letras (ex: USD, CNY, EUR)", + "currencySymbol": "Símbolo da moeda", + "currencySymbolDes": "Símbolo da moeda para exibir (ex: $, Â¥, €)", + "currencyUnit": "Unidade da moeda", + "currencyUnitDes": "Unidade mínima da moeda (ex: 100 para dólares/centavos)", + "paymentProviders": "Provedores de pagamento", + "providerName": "Nome do provedor, usado para exibir aos usuários.", + "providerType": "Tipo do provedor", + "providerKey": "Chave secreta", + "selectCurrency": "Selecionar moeda comum", + "addPaymentProvider": "Adicionar provedor de pagamento", + "stripeProvider": "Stripe", + "weixinProvider": "WeChat Pay", + "alipayProvider": "Alipay", + "customProvider": "Provedor de pagamento personalizado", + "customProviderDes": "Criar um plugin para conectar a outros gateways de pagamento, veja a <0>documentação para mais detalhes.", + "providerKeyDes": "Chave secreta da API do Stripe.", + "storageProductSettings": "Produto de armazenamento", + "storageProductsDes": "Configurar produtos que os usuários podem comprar para estender seu espaço de armazenamento.", + "addStorageProduct": "Adicionar SKU", + "editStorageProduct": "Editar SKU", + "storageSize": "Tamanho do armazenamento", + "storageSizeBytes": "Tamanho incluído neste SKU", + "duration": "Duração", + "durationSeconds": "Duração em segundos (ex: 2592000 para 30 dias)", + "price": "Preço", + "priceInUnits": "Preço (em unidade mínima de moeda)", + "priceInUnitsDes": "O preço será exibido como:", + "chipLabel": "Rótulo (opcional)", + "chipLabelHelp": "Um rótulo de texto curto exibido ao lado do nome do produto", + "usePoints": "Permitir pagamento com pontos", + "points": "Pontos", + "pointsHelp": "Número de pontos necessários para comprar este produto", + "pointsUnit": "pontos", + "groupProductSettings": "Produto de grupo", + "groupProductsDes": "Configurar produtos que os usuários podem comprar para ingressar em grupos de usuários específicos.", + "addGroupProduct": "Adicionar produto de grupo", + "editGroupProduct": "Editar produto de grupo", + "groupId": "ID do Grupo", + "groupIdHelp": "O grupo de usuário para o qual fazer upgrade após comprar este produto.", + "description": "Descrição", + "descriptionHelp": "Digite recursos ou benefícios, um por linha", + "receiptEmailTemplate": "Modelo de recibo de pagamento", + "receiptEmailTemplateDes": "Modelo de email enviado aos usuários quando um pagamento é confirmado.", + "activationEmailTemplate": "Modelo de ativação de conta", + "activationEmailTemplateDes": "Modelo de email enviado aos usuários para ativar suas contas.", + "quotaExceededEmailTemplate": "Modelo de cota de armazenamento excedida", + "quotaExceededEmailTemplateDes": "Modelo de email enviado aos usuários quando excedem sua cota de armazenamento.", + "resetPasswordEmailTemplate": "Modelo de redefinição de senha", + "resetPasswordEmailTemplateDes": "Modelo de email enviado aos usuários quando solicitam redefinição de senha.", + "preferredLanguage": "Idioma preferido", + "setAsPreferredLanguage": "Definir como idioma preferido", + "setAsPreferredLanguageDes": "Se o idioma preferido do usuário não puder ser obtido, o modelo de email deste idioma será usado.", + "alreadyAsPreferredLanguageDes": "O idioma atual já está definido como preferido. Se o idioma preferido do usuário não puder ser obtido, o modelo de email deste idioma será usado.", + "addLanguage": "Adicionar idioma", + "removeLanguage": "Remover idioma", + "removeLanguageBtn": "Remover idioma", + "cannotRemovePreferredLanguageDes": "Não é possível remover o idioma preferido. Defina outro idioma como preferido e tente novamente.", + "languageCodeDes": "Selecione o idioma que deseja adicionar.", + "emailSubject": "Assunto do email", + "emailSubjectDes": "A linha de assunto do email. Você pode usar <0>variáveis mágicas para personalizar o assunto do email.", + "emailBody": "Corpo do email", + "emailBodyDes": "Conteúdo HTML do email. Você pode usar <0>variáveis mágicas para personalizar o conteúdo do email.", + "orderTitle": "Título do pedido", + "themeOptions": "Opções de tema", + "themeOptionsDes": "Configure opções de tema personalizadas para seu site. Estes temas estarão disponíveis para os usuários selecionarem em suas preferências.", + "primaryColor": "Cor primária", + "secondaryColor": "Cor secundária", + "primaryColorDark": "Cor primária (Escuro)", + "secondaryColorDark": "Cor secundária (Escuro)", + "addThemeOption": "Adicionar opção de tema", + "editThemeOption": "Editar opção de tema", + "invalidThemeConfig": "Configuração de tema inválida. Verifique sua sintaxe JSON.", + "themeConfiguration": "Configuração do tema", + "themePreview": "Visualização do tema", + "lightTheme": "Tema claro", + "darkTheme": "Tema escuro", + "previewTitle": "Título da visualização", + "previewTextField": "Campo de entrada", + "previewPrimary": "Primário", + "invalidThemePreview": "Configuração de tema inválida para visualização", + "duplicateThemeColor": "Um tema com esta cor primária já existe. Escolha uma cor diferente.", + "themeDes": "Configurações completas disponíveis podem ser consultadas em <0>Visualizador de tema padrão - Material-UI.", + "defaultTheme": "Padrão", + "auditLog": "Eventos", + "auditLogDes": "Configure quais eventos devem ser registrados. Alguns eventos podem ser usados pelo sistema para fornecer recursos adicionais, ex: atividade de arquivo e atividade de login.", + "systemEvents": "Eventos do sistema", + "systemEventsDes": "Eventos relacionados a operações e status do sistema.", + "userEvents": "Eventos de usuário", + "userEventsDes": "Eventos relacionados a contas de usuário, autenticação e alterações de perfil.", + "fileEvents": "Eventos de arquivo", + "fileEventsDes": "Eventos relacionados a operações de arquivo como upload, download e modificação.", + "shareEvents": "Eventos de compartilhamento", + "shareEventsDes": "Eventos relacionados a compartilhamento de arquivos e acesso a links.", + "versionEvents": "Eventos de versão", + "versionEventsDes": "Eventos relacionados ao gerenciamento de versão de arquivos.", + "mediaEvents": "Eventos de mídia", + "mediaEventsDes": "Eventos relacionados ao processamento de mídia como geração de miniaturas.", + "filesystemEvents": "Eventos do sistema de arquivos", + "filesystemEventsDes": "Eventos relacionados a operações do sistema de arquivos como montagem e manipulação de arquivos.", + "webdavEvents": "Eventos WebDAV", + "webdavEventsDes": "Eventos relacionados ao gerenciamento e acesso de contas WebDAV.", + "paymentEvents": "Eventos de pagamento", + "paymentEventsDes": "Eventos relacionados a pagamentos, pontos e gerenciamento de membros.", + "emailEvents": "Eventos de email", + "emailEventsDes": "Eventos relacionados ao envio de email e notificações.", + "toggleAll": "Alternar todos", + "toggleAllDes": "Habilitar ou desabilitar todos os eventos nesta categoria.", + "event": { + "file_imported": "Arquivo externo importado", + "server_start": "Início do servidor", + "user_signup": "Cadastro de usuário", + "email_sent": "Email enviado", + "user_activated": "Usuário ativado", + "user_login_failed": "Falha no login", + "user_login": "Login do usuário", + "user_token_refresh": "Atualização de token", + "file_create": "Arquivo criado", + "file_rename": "Arquivo renomeado", + "set_file_permission": "Permissão alterada", + "entity_uploaded": "Arquivo enviado ou atualizado", + "entity_downloaded": "Arquivo baixado", + "copy_from": "Copiar de", + "copy_to": "Copiar para", + "move_to": "Mover para", + "delete_file": "Arquivo excluído", + "move_to_trash": "Mover para lixeira", + "share": "Compartilhamento criado", + "share_link_viewed": "Link de compartilhamento visualizado", + "set_current_version": "Definir versão atual", + "delete_version": "Excluir versão", + "thumb_generated": "Miniatura gerada", + "live_photo_uploaded": "Live photo enviada", + "update_metadata": "Metadados atualizados", + "edit_share": "Compartilhamento editado", + "delete_share": "Compartilhamento excluído", + "mount": "Montar", + "relocate": "Realocar", + "create_archive": "Criar arquivo", + "extract_archive": "Extrair arquivo", + "webdav_login_failed": "Falha no login WebDAV", + "webdav_account_create": "Conta WebDAV criada", + "webdav_account_update": "Conta WebDAV atualizada", + "webdav_account_delete": "Conta WebDAV excluída", + "payment_created": "Pagamento criado", + "points_change": "Pontos alterados", + "payment_paid": "Pagamento pago", + "payment_fulfilled": "Pedido cumprido", + "payment_fulfill_failed": "Falha no cumprimento do pedido", + "storage_added": "Armazenamento adicionado", + "group_changed": "Grupo alterado", + "user_exceed_quota_notified": "Notificação de cota excedida", + "user_changed": "Status do usuário alterado", + "get_direct_link": "Obter link direto", + "link_account": "Vincular conta externa", + "unlink_account": "Desvincular conta externa", + "change_nick": "Alterar apelido", + "change_avatar": "Alterar avatar", + "membership_unsubscribe": "Cancelar assinatura de membros", + "change_password": "Alterar senha", + "enable_2fa": "Habilitar 2FA", + "disable_2fa": "Desabilitar 2FA", + "add_passkey": "Adicionar passkey", + "remove_passkey": "Remover passkey", + "redeem_gift_code": "Resgatar código de presente", + "update_view": "Configuração de visualização alterada", + "delete_direct_link": "Excluir link direto", + "report_abuse": "Relatar abuso" + }, + "server": "Servidor", + "tempPath": "Caminho temporário", + "tempPathDes": "O diretório para armazenar arquivos temporários, relativo ao diretório de dados do Cloudreve. Certifique-se de que não há tarefas de fila em execução antes de modificá-lo.", + "siteID": "ID do Site", + "siteIDDes": "Um ID único para identificar o site, geralmente não precisa ser modificado.", + "siteSecretKey": "Chave mestra", + "siteSecretKeyDes": "A chave mestra usada para criptografar tokens de usuário e assinaturas. Após a rotação, todos os tokens de usuário e assinaturas serão inválidos. Entra em vigor após reiniciar o Cloudreve.", + "rotateSecretKey": "Rotacionar chave mestra", + "hashidSalt": "Salt do HashID", + "hashidSaltDes": "O valor salt usado para gerar HashID. Seja cauteloso ao alterá-lo, pois invalidará links diretos e links de compartilhamento existentes.", + "accessTokenTTL": "TTL do token de acesso", + "accessTokenTTLDes": "O TTL dos tokens de acesso, em segundos.", + "refreshTokenTTL": "TTL do token de atualização", + "refreshTokenTTLDes": "O TTL dos tokens de atualização, em segundos. Afeta a duração do status de login do usuário.", + "cronGarbageCollect": "Intervalo de escaneamento de coleta de lixo", + "cronGarbageCollectDes": "Definir com que frequência escanear e reciclar dados expirados em arquivos temporários e armazenamento KV.", + "startWithProtocol": "Deve começar com http:// ou https://", + "tlsWarning": "O site atual está usando https, preencher uma URL http aqui pode causar exceções.", + "blobUrlCache": "Cache de URL Blob", + "clearBlobUrlCache": "Limpar cache de URL Blob", + "clearBlobUrlCacheDes": "Para aumentar a taxa de acerto do cache, o Cloudreve armazena em cache e reutiliza URLs Blob. Quando o endereço CDN ou outras configurações mudam, limpe o cache.", + "cacheCleared": "Cache limpo." + }, + "giftCodes": { + "giftCodesSettings": "Códigos de Presente", + "generateGiftCodes": "Gerar Códigos de Presente", + "giftCodeQuantity": "Quantidade", + "giftCodeQuantityHelp": "Número de códigos de presente para gerar", + "giftCodeProductType": "Tipo de Produto", + "giftCodeTypePoints": "Pontos", + "giftCodeTypeStorage": "Armazenamento", + "giftCodeTypeGroup": "Grupo", + "giftCodePointsAmount": "Quantidade de Pontos", + "giftCodePointsAmountHelp": "Número de pontos a creditar quando o código for resgatado", + "giftCodeProduct": "Produto", + "selectStorageProduct": "Selecionar produto de armazenamento", + "selectGroupProduct": "Selecionar produto de grupo", + "giftCodeType": "Tipo", + "giftCodeAmount": "Quantidade", + "giftCode": "Código de Presente", + "giftCodeStatus": "Status", + "giftCodeUsedBy": "Usado por", + "giftCodeUsed": "Usado", + "giftCodeUnused": "Disponível", + "giftCodeDeleted": "Código de presente excluído com sucesso", + "giftCodesGenerated": "Códigos de presente gerados com sucesso", + "noGiftCodes": "Nenhum código de presente disponível", + "generatedCodesTitle": "Códigos de Presente Gerados", + "generatedCodesDescription": "Copie estes códigos de presente para compartilhar com os usuários. Cada código pode ser usado uma vez.", + "copyAndClose": "Copiar e Fechar", + "duratonTimes": "Quantidade", + "duratonTimesDes": "Quantas quantidades do produto estão incluídas em cada código de presente.", + "unknownProduct": "Produto Desconhecido" + }, + "policy": { + "acceleratedDomainUpload": "Usar domínio de aceleração de transferência para upload", + "acceleratedDomainUploadDes": "Quando habilitado, o <0>domínio de aceleração de transferência do Qiniu será usado ao fazer upload de arquivos.", + "compare": "Comparar", + "deletePolicyConfirmation": "Tem certeza de que deseja excluir a política de armazenamento {{name}}?", + "streamSaver": "Download via navegador", + "streamSaverDes": "Quando habilitado, as solicitações de download dos usuários serão manipuladas pelo navegador. Devido à limitação da política de armazenamento OneDrive, o nome do arquivo baixado diretamente pelos usuários não pode ser o mesmo que o nome do arquivo no Cloudreve, usar o navegador para manipular downloads pode resolver este problema.", + "oauthCallbackFailed": "Falha na autorização", + "httpsRequired": "O aplicativo Entra ID requer URL de redirecionamento HTTPS, mas o site atual está usando HTTP, o que pode causar falha de redirecionamento após o login, substitua manualmente o HTTPS na barra de endereços do navegador por HTTP.", + "authorizeMicrosoft": "Entrar com Microsoft", + "redirectUrl": "URL de Redirecionamento", + "redirectUrlDes": "A exibição atual é a URL de redirecionamento mais recente que atende aos requisitos. Confirme se a URL de redirecionamento nas configurações do aplicativo é consistente com a atual.", + "authorizeOneDrive": "Confirmar configurações do aplicativo Entra ID", + "authorizeOneDriveDes": "Confirme se as seguintes informações do aplicativo Entra ID ainda são válidas. Se necessário, faça alterações.", + "authorizeNow": "Autorizar", + "authorizeAgain": "Autorizar novamente", + "notGranted": "Nenhuma conta autorizada, política de armazenamento não pode ser usada.", + "granted": "Conta autorizada, credencial atualizada em <0>{{time}}.", + "grantedNotRefresh": "Conta autorizada, credencial não atualizada desde a última inicialização.", + "batchDeleteSize": "Tamanho máximo de exclusão em lote", + "batchDeleteSizeDes": "Limita o número máximo de arquivos que podem ser excluídos em uma única solicitação de API. Esta configuração não afetará a exclusão de arquivos em lote do usuário. Se não preenchido, o valor padrão <0>1000 será usado. Este é o valor máximo permitido para a API oficial do S3.", + "bucketPolicy": "Política de bucket", + "cdnOrCustomDomain": "CDN ou CNAME personalizado", + "bucketDomain": "Domínio do bucket", + "bucketDomainDes": "Preencha o domínio acelerado por CDN ou domínio CNAME personalizado que você vinculou ao bucket de armazenamento.", + "storageNodeInternal": "Nó de armazenamento (Endpoint da Intranet)", + "chunkSizeDesOssObs": "Faixa permitida: 100 KB ~ 5 GB.", + "chunkSizeDesQiniuCos": "Faixa permitida: 1 MB ~ 1 GB.", + "chunkSizeDesS3": "Faixa permitida: 5 MB ~ 5 GB.", + "thisIsACustomDomain": "Este é um domínio personalizado", + "thisIsACustomDomainDes": "Se você vinculou um domínio personalizado ao bucket de armazenamento e precisa gerenciar o bucket através do domínio personalizado, marque esta opção. Após habilitada, o Cloudreve não tentará anexar o nome do Bucket no domínio da solicitação.", + "addedManually": "Eu configurei manualmente", + "origin": "Origem", + "allowMethods": "Métodos permitidos", + "exposeHeaders": "Headers expostos", + "allowHeaders": "Headers permitidos", + "maxAge": "Idade máxima", + "accessCredential": "Credencial de acesso", + "downloadTrafficDiagram": "Demonstração do caminho de tráfego de download", + "downloadRelay": "Relay de download", + "downloadRelayDes": "Quando habilitado, as solicitações de download dos usuários serão proxificadas pelo Cloudreve.", + "download": "Download", + "downloadCdn": "CDN de download", + "useDownloadCdn": "Usar CDN para tráfego de download", + "skipSign": "Pular assinatura de URL para CDN", + "skipSignDes": "Se você habilitou \"Usar autenticação de origem\" para este domínio nas configurações do bucket, marque esta opção.", + "cdnHost": "Host CDN", + "downloadCdnDes": "O host, protocolo e porta da URL que os usuários usam para acessar arquivos serão substituídos pelo host CDN especificado.", + "mediaExtractorProxy": "Proxy de extração de mídia", + "mediaExtractorProxyDes": "Habilite este recurso para extrair metadados de mídia de arquivos que não são suportados pelos extratores nativos do provedor de armazenamento. Configure o extrator de mídia em <0>Processamento de mídia.", + "mediaExtractorNative": "extratores nativos", + "mediaExtractorOss": "Gerenciamento Inteligente de Mídia (IMM)", + "mediaExtractorQiniu": "Qiniu DORA", + "mediaExtractorCos": "Processamento de Dados da Tencent Cloud", + "mediaExtractorObs": "serviço de processamento de imagem", + "nativeMediaMetaExts": "Extensões de arquivo habilitadas para <0>{{name}}", + "nativeMediaMetaExtsGeneralDes": "Separado por vírgulas, valor vazio significa desabilitar <0>{{name}}.", + "nativeMediaMetaExtsRemote": "Para armazenamento escravo, o suporte padrão é EXIF e metadados de música, você pode substituir isso configurando o nó escravo com mais extratores.", + "nativeMediaMetaExtOss": "O serviço de Gerenciamento Inteligente de Mídia (IMM) suporta processamento de áudio, vídeo e imagens. O processamento de imagens não requer configuração manual, mas se você precisar processar áudio ou vídeo, precisa ativar manualmente o IMM e vinculá-lo ao Bucket, consulte a <0>documentação para vinculação. Após a vinculação, adicione as extensões que deseja processar ao campo acima.", + "nativeMediaMetaExtQiniu": "O serviço Qiniu DORA suporta processamento de áudio, vídeo e imagens comuns, nenhuma configuração adicional é necessária, preencha as extensões que deseja processar acima.", + "nativeMediaMetaExtCos": "O serviço de Processamento de Dados da Tencent Cloud suporta processamento de áudio, vídeo e imagens. O processamento de imagens não requer configuração manual, mas se você precisar processar áudio ou vídeo, primeiro vá para <0>Processamento de Dados da Tencent Cloud para ativar e vincular o bucket de armazenamento, depois vá para Configurações do Bucket - Processamento de mídia para ativar o serviço de processamento de imagens. Após a vinculação, adicione as extensões que deseja processar ao campo acima.", + "nativeMediaMetaExtObs": "O serviço de processamento de imagem suporta <0>extração de EXIF de imagem. Nenhuma configuração manual é necessária, apenas adicione as extensões que deseja processar acima.", + "thumbProxy": "Geração de miniaturas por proxy", + "thumbProxyDes": "Habilite este recurso para gerar miniaturas para arquivos que não atendem às condições de miniatura nativa. O Cloudreve tentará gerar miniaturas e carregá-las no lado do armazenamento. Configure o gerador de miniaturas em <0>Processamento de mídia.", + "nativeThumbnailMaxSize": "Tamanho máximo de miniaturas nativas", + "nativeThumbnailMaxSizeDes": "Digite 0 para desabilitar o limite de tamanho, arquivos maiores que este tamanho não usarão miniaturas nativas.", + "nativeThumbNailsSupportAllExts": "Habilitar para todas as extensões de arquivo", + "nativeThumbNails": "Extensões de arquivo para miniaturas nativas", + "nativeThumbNailsGeneralDes": "Separado por vírgulas, valor vazio significa desabilitar miniatura nativa, para as extensões de arquivo listadas acima, o Cloudreve usará o recurso de miniatura nativa do provedor de armazenamento para gerar miniaturas.", + "nativeThumbNailsGeneralRemote": "Para armazenamento escravo, o suporte integrado é para miniaturas simples de imagem e capa de música, você pode substituir isso configurando o nó escravo com mais geradores.", + "nativeThumbNailsGeneralOss": "Para armazenamento Alibaba Cloud OSS, o serviço de <0>processamento de imagem será usado para gerar miniaturas.", + "nativeThumbNailsGeneralQiniu": "Para armazenamento Qiniu Cloud, o serviço de <0>processamento básico de imagem (imageView2) será usado para gerar miniaturas.", + "nativeThumbNailsGeneralCos": "Para armazenamento Tencent Cloud COS, o serviço <0>Processamento de Dados da Tencent Cloud será usado para gerar miniaturas.", + "nativeThumbNailsGeneralObs": "Para armazenamento Huawei Cloud OBS, o serviço de <0>processamento de imagem será usado para gerar miniaturas.", + "nativeThumbNailsGeneralUpyun": "Para armazenamento Upyun, o serviço de <0>processamento de imagem será usado para gerar miniaturas.", + "preallocate": "Pré-alocar espaço em disco", + "preallocateDes": "Quando habilitado, a solicitação de upload do usuário pré-alocará espaço em disco no nó de armazenamento, e também suporta upload paralelo de chunks. Eficaz apenas no Linux ou Darwin.", + "chunkConcurrency": "Uploads de chunk concorrentes", + "chunkConcurrencyDes": "Define o número de uploads de chunk concorrentes ao usar upload direto da web.", + "sourceWebEdit": "Edição online na Web", + "uploadRelay": "Relay de upload", + "uploadRelayDes": "Se habilitado, as solicitações de upload dos usuários serão retransmitidas para o nó de armazenamento via Cloudreve, devido à incapacidade de realizar uploads fragmentados, ajuste o limite de tamanho máximo de upload do servidor web adequadamente.", + "customProxy": "Proxy personalizado", + "storageNode": "Provedor de armazenamento", + "sourceWeb": "Web / Aplicativo oficial", + "sourceDav": "WebDAV", + "uploadTrafficDiagram": "Demonstração do caminho de tráfego de upload", + "node": "Nó de armazenamento", + "nodeDes": "Selecione um nó escravo para armazenamento de arquivos, você pode criar ou gerenciar nós de armazenamento escravo em <0>Lista de nós.", + "noBindedGroupWarning": "A política de armazenamento atual não está vinculada a nenhum grupo de usuários, vá para <0>Lista de grupos para vincular a política de armazenamento atual a um grupo de usuários.", + "nameRuleImmutable": "Modificar configurações não afetará arquivos existentes na política de armazenamento. O caminho do Blob é fixo após a criação, mesmo se as variáveis mágicas nele mudarem, o caminho não será atualizado.", + "uniqueVarRequired": "Inclua pelo menos uma variável única no caminho do diretório ou nome do blob: {uuid}, {randomkey8}, {randomkey16}.", + "storageAndUpload": "Armazenamento e Upload", + "blobFolderNaming": "Diretório de Armazenamento Blob", + "blobFolderNamingDes": "O diretório onde os Blobs de arquivo são armazenados, você pode usar <0>variáveis mágicas.", + "blobNameDes": "O nome do Blob do arquivo, você pode usar <0>variáveis mágicas, certifique-se de que seja absolutamente único, mesmo para múltiplos uploads do mesmo nome de arquivo no mesmo caminho em pouco tempo.", + "blobName": "Nome do Blob", + "basicInfo": "Informações básicas", + "editX": "Editar {{name}}", + "noGroupBinded": "Nenhum grupo vinculado", + "create": "Criar", + "addXStoragePolicy": "Adicionar política de armazenamento {{type}}", + "loadSummary": "Carregar resumo", + "policySummary": "{{count}} Blobs de arquivo ({{size}})", + "sharp": "#", + "name": "Nome", + "type": "Tipo", + "childFiles": "Arquivos filhos", + "totalSize": "Tamanho total", + "actions": "Ações", + "authSuccess": "Autorização concedida.", + "policyDeleted": "Política excluída.", + "newStoragePolicy": "Nova política de armazenamento", + "all": "Todos", + "local": "Local", + "remote": "Nó Remoto", + "qiniu": "Qiniu", + "upyun": "Upyun", + "oss": "Alibaba Cloud OSS", + "cos": "Tencent Cloud COS", + "onedrive": "OneDrive", + "s3": "Compatível com S3", + "ks3": "Kingsoft Cloud S3", + "obs": "Huawei Cloud OBS", + "load_balance": "Balanceamento de Carga", + "childPolicy": "Política de Armazenamento Filho", + "childPolicyDes": "Selecione as políticas de armazenamento filho para adicionar ao pool de balanceamento de carga.", + "weight": "Peso", + "addTargetPolicy": "Adicionar Política Filho", + "selectPolicies": "Selecionar Políticas", + "selectPoliciesDes": "Selecione políticas de armazenamento para adicionar ao pool de balanceamento de carga.", + "loadBalanceDes": "Ao usar a política de armazenamento balanceada, novos uploads serão distribuídos aleatoriamente para diferentes políticas de armazenamento filho baseadas no peso.", + "xChildPolicies": "{{count}} políticas de armazenamento filho", + "refresh": "Atualizar", + "delete": "Excluir", + "edit": "Editar", + "selectAStorageProvider": "Selecione um provedor de armazenamento", + "maxSizeOfSingleFile": "Tamanho máximo de arquivo único", + "maxSizeOfSingleFileDes": "Digite 0 para desabilitar o limite.", + "enterFileExt": "Separado por vírgulas com ponto e vírgula, deixe em branco para permitir todas as extensões de arquivo.", + "extList": "Restrições de extensão de arquivo", + "noLimit": "Sem limite", + "whitelist": "Permitir", + "blacklist": "Negar", + "fileNameRegex": "Regras regex de nome de arquivo", + "fileNameRegexDes": "Expressão regular para corresponder nomes de arquivo, deixe em branco para nenhuma restrição.", + "chunkSizeDes": "Especifique o tamanho do chunk para uploads fragmentados. Um valor de 0 significa que nenhum upload fragmentado é usado, mas o tamanho máximo de upload pode ser limitado pelo servidor web.", + "chunkSizeDesSuffix": "{{prefix}} Com upload fragmentado, os arquivos enviados pelos usuários serão fatiados em chunks e enviados para o lado do armazenamento um por um. Após a interrupção do upload, os usuários podem escolher continuar enviando a partir do último chunk enviado.", + "chunkSize": "Tamanho do chunk", + "policyName": "O nome de exibição da política de armazenamento, também usado para ser apresentado aos usuários.", + "magicVar": { + "fileNameMagicVar": "Variáveis mágicas de nome de arquivo", + "pathMagicVar": "Variáveis mágicas de caminho", + "variable": "Variável", + "description": "Descrição", + "example": "Exemplo", + "16digitsRandomString": "String aleatória de 16 dígitos", + "8digitsRandomString": "String aleatória de 8 dígitos", + "secondTimestamp": "Timestamp", + "nanoTimestamp": "Timestamp nano", + "uid": "ID do usuário", + "originalFileName": "Nome original do arquivo", + "originFileNameNoext": "Nome original do arquivo sem extensão", + "extension": "Nome da extensão do arquivo", + "uuidV4": "UUID V4", + "date": "Data", + "dateAndTime": "Data e hora", + "randomNumber": "Número aleatório dentro do intervalo", + "year": "Ano", + "month": "Mês", + "day": "Dia", + "hour": "Hora", + "minute": "Minuto", + "second": "Segundo", + "path": "O caminho inicial enquanto o usuário envia o arquivo" + }, + "storageBucket": "Bucket de armazenamento", + "wanSiteURLDes": "Antes de usar esta política, certifique-se de que o endereço inserido em Configurações Básicas - Informações do Site - URL do Site corresponde ao endereço real e <0>pode ser acessado adequadamente pela WAN.", + "enterQiniuBucket": "Vá para <0>painel do Qiniu para criar um bucket de armazenamento. Digite o \"Nome do bucket\" que você acabou de criar.", + "aclType": "Tipo de controle de acesso", + "accessTypePulic": "Leitura pública escrita privada", + "accessTypePrivate": "Leitura/escrita privada", + "accessType": "Tipo de acesso", + "qiniuBucketName": "Nome do bucket", + "cosObsBucketName": "Nome do bucket", + "bucketType": "ACL do bucket", + "bucketTypeDes": "Selecione o tipo de ACL para o bucket que você acabou de criar.", + "privateBucket": "Privado", + "privateDes": "Cloudreve assinará a URL do arquivo.", + "publicBucket": "Leitura pública", + "publicStorage": "Público", + "publicDes": "Não recomendado, Cloudreve retornará diretamente o link direto do arquivo, que não pode controlar efetivamente o acesso aos arquivos.", + "bucketCDNDes": "Preencha o nome de domínio acelerado por CDN que você vinculou ao bucket de armazenamento.", + "bucketCDNDomain": "Domínio CDN", + "qiniuCredentialDes": "Vá para Centro Pessoal - Gerenciamento de Credenciais no painel do Qiniu e preencha o AK, SK obtidos.", + "ak": "AK", + "sk": "SK", + "cannotEnableForPrivateBucket": "Se este recurso for habilitado para bucket privado, você precisa habilitar \"Usar link de origem redirecionado\" para grupos de usuários.", + "chunkSizeLabelQiniu": "Especifique o tamanho do chunk para uploads resumíveis. A faixa permitida é 1 MB - 1 GB.", + "corsSettingStep": "Política CORS", + "corsPolicyAdded": "Política CORS foi adicionada.", + "createOSSBucketDes": "Vá para <0>Painel OSS para criar um Bucket. Apenas classes de armazenamento <1>Padrão e <2>IA são suportadas.", + "bucketName": "Nome do bucket", + "publicReadBucket": "Leitura pública", + "ossEndpointDes": "Vá para a página de resumo do Bucket, digite a <2>Porta na seção <1>Acesso pela Internet, na página <0>Endpoint.", + "ossEndpointDesInternalHint": "Se você precisar configurar endpoint de intranet ou domínio personalizado, pode configurá-lo após criar a política de armazenamento.", + "obsEndpointCnameHint": "Se você precisar configurar endpoint de domínio personalizado, pode configurá-lo após criar a política de armazenamento.", + "endpoint": "EndPoint", + "ossLANEndpointDes": "Deixar em branco significa não usar. Se seu Cloudreve estiver implantado em serviços de computação relacionados ao Alibaba Cloud que estão na mesma zona de disponibilidade que o bucket OSS, você pode especificar adicionalmente um endpoint de intranet, Cloudreve tentará usar este endpoint no lado do servidor para reduzir o custo de tráfego.", + "intranetEndPoint": "Endpoint de intranet", + "ossCDNDes": "Você quer usar Alibaba Cloud CDN para acelerar o acesso a arquivos?", + "createOSSCDNDes": "Vá para <0>Painel CDN do Alibaba Cloud para criar um domínio CDN, a origem do CDN deve ser seu bucket OSS. Digite o domínio CDN e selecione se você quer usar HTTPS:", + "ossAKDes": "Obtenha sua AccessKey na página <0>Gerenciamento de Informações de Segurança. Você também pode criar uma AccessKey com permissão <1>AliyunOSSFullAccess em <2>Controle de Acesso RAM.", + "shouldNotContainSpace": "Isso não pode conter espaços.", + "nameThePolicyFirst": "Nomeie a política de armazenamento:", + "chunkSizeLabelOSS": "Especifique o tamanho do chunk para uploads resumíveis. A faixa permitida é 100 KB - 5 GB.", + "ossCORSDes": "Esta política de armazenamento requer uma política CORS para habilitar upload do navegador. Cloudreve pode configurá-la automaticamente para você, ou você pode configurá-la manualmente seguindo os passos na documentação. Se você já configurou a política CORS para este Bucket, este passo pode ser pulado.", + "letCloudreveHelpMe": "Deixe o Cloudreve configurar para mim", + "skip": "Pular", + "createUpyunBucketDes": "Preencha o nome do serviço de armazenamento criado no <0>Painel Upyun.", + "storageServiceName": "Nome do serviço", + "operatorName": "Nome do operador", + "operatorPassword": "Senha do operador", + "tokenStatus": "Anti-hotlinking de token", + "upyunTokenDes": "É altamente recomendado habilitar o Anti-Hotlinking de Token, vá para o painel <0>Configuração de Recursos do serviço de armazenamento criado, vá para a aba <1>Controle de Acesso, habilite o Anti-Hotlinking de Token e defina um segredo.", + "tokenEnabled": "Habilitar Anti-Hotlinking de Token", + "tokenDisabled": "Não usar Anti-Hotlinking de Token", + "upyunTokenSecretDes": "Digite o segredo do Anti-Hotlinking de Token.", + "upyunTokenSecret": "Segredo do Anti-Hotlinking de Token", + "createCOSBucketDes": "Vá para <0>Painel COS para criar um bucket de armazenamento. Vá para a página de configuração básica do bucket criado e copie o <1>Nome do bucket acima.", + "obsBucketDes": "Vá para <0>Painel OBS para criar um bucket de armazenamento. Digite o <1>Nome do bucket que você acabou de criar. A classe de armazenamento suporta apenas <2>Padrão ou <3>Acesso Infrequente.", + "cosPrivateRW": "Leitura/Escrita Privada", + "cosPublicRW": "Leitura Pública e Escrita Privada", + "cosAccessDomainDes": "Na página de visão geral do Bucket criado, preencha o <1>Domínio de Acesso fornecido na seção <0>Informações do Domínio. Você também pode usar seu domínio CNAME ou domínio de aceleração CDN.", + "obsEndpointDes": "Na página de visão geral do Bucket criado, preencha o <1>Endpoint fornecido na seção <0>Informações do Domínio.", + "accessDomain": "Domínio de acesso", + "cosCDNDomainDes": "Vá para <0>Console de Gerenciamento CDN da Tencent Cloud para criar um domínio de aceleração CDN e defina o site de origem como o bucket COS que você acabou de criar. Preencha o nome do domínio CDN abaixo e selecione se deseja usar HTTPS.", + "cosCredentialDes": "Preencha as chaves de acesso obtidas da página <0>Chaves de Acesso da Tencent Cloud. Certifique-se de que o par de chaves tenha permissão de acesso aos serviços COS. Você também pode criar um <2>sub-usuário com permissão de <1>Acesso Programático e conceder-lhe acesso ao serviço COS.", + "obsCredentialDes": "Preencha as chaves de acesso obtidas da página <0>Chaves de Acesso da Huawei Cloud. Você também pode criar um <2>usuário IAM com permissão de <1>Acesso Programático e conceder-lhe permissão <3>OBS OperateAccess.", + "grantAccess": "Conceder acesso", + "grantAccessLater": "Após criar a política de armazenamento, você precisa fazer login e conceder acesso na página de configurações da política de armazenamento.", + "odHttpsWarning": "Você deve habilitar HTTPS para usar políticas de armazenamento OneDrive/SharePoint; após habilitado, certifique-se de alterar Configurações - Básico - Informações do Site - URL do Site.", + "creatAadAppDes": "Vá para <0>Painel Microsoft Entra ID, após fazer login, vá para o painel administrativo <1>Microsoft Entra ID, você pode opcionalmente usar uma conta diferente da usada para armazenar arquivos para fazer login.", + "createAadAppDes2": "Vá para o menu <0>Registros de Aplicativo à esquerda e clique no botão <1>Novo registro. Preencha o formulário de registro do aplicativo. Certifique-se de que <2>Tipos de conta suportados esteja selecionado como <3>Contas em qualquer diretório organizacional (Qualquer diretório Azure AD - Multilocatário) e contas pessoais da Microsoft (ex. Skype, Xbox); <4>URI de redirecionamento (opcional) esteja selecionado como <5>Web e preencha <6>{{url}}; Para outros campos, apenas deixe como padrão.", + "entraIdApp": "Informações do aplicativo Entra ID", + "aadAppIDDes": "Vá para a página <0>Visão Geral no Gerenciamento de Aplicativo, o valor do <1>ID do Aplicativo (Cliente).", + "aadAppID": "ID do Aplicativo (Cliente)", + "addAppSecretDes": "A maneira de criar segredo do cliente: Vá para o menu <0>Certificados e segredos no lado esquerdo, clique no botão <1>Novo segredo do cliente, e selecione o tempo mais longo para <2>Expira. Você precisa criar um novo segredo do cliente após o antigo expirar, e atualizar o novo nas configurações da política de armazenamento.", + "aadAppSecret": "Segredo do cliente", + "aadAccountCloud": "Endpoint Microsoft Graph", + "aadAccountCloudDes": "Selecione o endpoint de acordo com o tipo de conta Microsoft 365 que você está usando.", + "multiTenant": "Nuvem pública mundial", + "gallatin": "Nuvem chinesa 21V", + "sharePointDes": "Você quer armazenar arquivos no SharePoint?", + "saveToOneDrive": "Armazenar arquivos no OneDrive padrão", + "spSiteURL": "URL do Site SharePoint", + "odReverseProxyURLDes": "Você quer usar servidor proxy reverso personalizado para download de arquivos?", + "odReverseProxyURL": "URL do servidor proxy reverso", + "chunkSizeDesOd": "Faixa permitida: 5 MB ~ 5GB, OneDrive requer que seja um múltiplo inteiro de 320 KiB (327,680 bytes).", + "limitOdTPSDes": "Limitar frequência de solicitação da API OneDrive", + "tps": "Limite TPS", + "tpsDes": "Deixar em branco indica sem limite. Limita esta política de armazenamento o número máximo de solicitações de API enviadas ao OneDrive por segundo. Solicitações que excedem esta frequência serão limitadas por taxa. Quando múltiplos nós Cloudreve transferem arquivos, cada um usa seu próprio bucket de token, então reduza este número adequadamente nesta condição.", + "tpsBurst": "Rajada TPS", + "tpsBurstDes": "Quando a solicitação está ociosa, Cloudreve pode reservar um número especificado de slots para futuras rajadas de tráfego.", + "odOauthDes": "No entanto, você precisará clicar no botão abaixo e autorizar com login da conta Microsoft para completar a inicialização antes de poder usá-lo. Você pode reautorizar mais tarde na página Lista de Políticas de Armazenamento.", + "gotoAuthPage": "Ir para a página de autorização", + "s3BucketDes": "Vá para o painel AWS S3 para criar um bucket, digite o <0>Nome do bucket que você acabou de criar:", + "s3EndpointDes": "Especifique o EndPoint (nó geográfico) do bucket de armazenamento em formato de URL completa, ex. <0>https://bucket.region.example.com.", + "selectRegionDes": "Digite o código da região do bucket de armazenamento, ex. <0>us-east-1. Para provedores de armazenamento compatíveis com S3 não-AWS, consulte sua documentação sobre como preencher este campo.", + "chunkSizeLabelS3": "Especifique o tamanho do chunk para uploads resumíveis. A faixa permitida é 5 MB - 5 GB.", + "policyEndpoint": "Endpoint.", + "s3Region": "Região", + "s3EndpointPathStyle": "Selecione o formato do endereço do Endpoint S3. Algumas políticas de armazenamento compatíveis com S3 de terceiros podem exigir esta opção para funcionar. Quando ativado, forçaremos o uso de endereços no formato de caminho, como <0>http://s3.amazonaws.com/BUCKET/KEY.", + "usePathEndpoint": "Forçar estilo de caminho", + "thumbExt": "Extensões que suportam miniaturas", + "thumbExtDes": "Deixar em branco indica que o conjunto predefinido da política de armazenamento é usado. Não válido para políticas de armazenamento local, S3.", + "driverRoot": "Raiz do Driver", + "driverRootDes": "Escolha onde salvar arquivos em sua conta OneDrive. Alterar esta opção tornará arquivos existentes na política de armazenamento inacessíveis.", + "saveToDefaultOneDrive": "Salvar arquivos no driver OneDrive padrão", + "saveToSharePoint": "Salvar arquivos no SharePoint", + "sharePointUrlDes": "Digite a URL do site SharePoint. Após perder o foco, o sistema converterá automaticamente para o identificador de driver correto.", + "ks3selectRegionDes": "Digite o código da região do bucket de armazenamento, ex. <0>BEIJING .", + "ks3EndpointPathStyle": "Selecione o formato do endereço do Endpoint KS3.", + "ossRegionDes": "Digite o código da região onde está localizado o bucket, ex. <0>cn-hangzhou. Você pode encontrar a região correspondente na tabela <1>Regiões e endpoints do OSS e preencher o <2>ID da região correspondente." + }, + "node": { + "slave": "escravo", + "master": "mestre", + "noCapabilities": "Nenhuma capacidade habilitada.", + "active": "Ativo", + "suspended": "Suspenso", + "deleteNodeConfirmation": "Tem certeza de que deseja excluir o nó {{name}}?", + "editNode": "Editar nó {{node}}", + "thisIsMasterNodes": "Você está editando um nó mestre, que está servindo o site atual.", + "enableNode": "Habilitar nó", + "enableNodeDes": "Após habilitado, o nó aceitará e processará os recursos que foram habilitados.", + "name": "Nome", + "nameNode": "Nome do nó, também usado para exibir aos usuários.", + "type": "Tipo", + "server": "Endpoint do nó", + "serverDes": "Endpoint usado para comunicação do nó. Se você quiser armazenar arquivos neste nó, este endereço também será exposto ao lado do usuário para uploads de arquivos.", + "loadBalancerRankDes": "Especifique um peso de balanceamento de carga para este nó, o valor é um inteiro, quanto maior o valor, maior a probabilidade de ser selecionado.", + "loadBalancerRank": "Peso do balanceamento de carga", + "slaveSecret": "Segredo do escravo", + "slaveSecretDes": "Segredo usado para comunicação do nó escravo com o nó mestre. Precisa ser consistente com <1>Secret na seção <1>Slave do arquivo de configuração do nó escravo.", + "testNode": "Testar comunicação do nó", + "testNodeSuccess": "Nó se comunica com sucesso.", + "createArchiveDes": "Aceitar solicitações de tarefas de criação de arquivo.", + "extractArchiveDes": "Aceitar solicitações de tarefas de extração de arquivo.", + "remoteDownloadDes": "Aceitar solicitações de tarefas de download remoto. Após habilitado, você também precisa configurar as informações relacionadas ao download remoto abaixo.", + "downloader": "Downloader", + "aria2Des": "Inicie o Aria2 como o mesmo usuário/nível de acesso executando Cloudreve no servidor do nó de destino, habilite o serviço RPC no arquivo de configuração do Aria2, para mais informações e diretrizes, consulte a seção \"Download remoto\" da documentação.", + "qbittorrentDes": "Inicie o qBittorrent como o mesmo usuário executando Cloudreve no servidor do nó de destino, habilite o serviço Web UI nas configurações do qBittorrent, para mais informações e diretrizes, consulte a seção \"Download remoto\" da documentação.", + "rpcServer": "Servidor RPC", + "rpcServerHelpDes": "Endereço do servidor RPC contém número de porta completo, ex. <0>http://127.0.0.1:6800/.", + "rpcToken": "Token RPC", + "rpcTokenDes": "Consistente com <0>rpc-secret no arquivo de configuração do Aria2; deixe em branco se não configurado.", + "downloaderOptionDes": "Configuração adicional do downloader ao criar uma tarefa de download, escrita em formato JSON chave-valor, veja a <0>documentação oficial do downloader para parâmetros disponíveis.", + "refreshInterval": "Intervalo de atualização de status (segundos)", + "refreshIntervalDes": "O intervalo no qual Cloudreve solicita uma atualização do estado da tarefa do downloader. O intervalo de atualização real também depende da configuração da fila \"Download remoto\" e da ocupação do downloader.", + "waitForSeeding": "Aguardar seeding", + "waitForSeedingDes": "Após habilitado, quando a tarefa de download remoto for concluída, o nó manterá a tarefa no estado de seeding até que a condição de conclusão de seeding na configuração do downloader seja atendida. Este recurso só tem efeito após a conclusão da tarefa de download remoto, e não afetará o uso dos arquivos baixados pelo usuário.", + "webUIEndpoint": "Endpoint da Web UI", + "webUIEndpointDes": "O endpoint da Web UI do qBittorrent, ex. <0>http://127.0.0.1:8080/.", + "tempPath": "Diretório de download temporário", + "tempPathDes": "O diretório no nó que o Aria2 usa como diretório de download temporário. O processo Cloudreve no nó precisa de permissões de leitura, escrita e execução neste diretório, e o downloader também precisa poder acessar este diretório. Deixe em branco para usar o caminho de arquivo temporário padrão.", + "webUIUsername": "Nome de usuário da Web UI", + "webUIPassword": "Senha da Web UI", + "webUICredDes": "Deixe em branco se a autenticação não estiver habilitada.", + "downloaderTestPass": "Conectado com sucesso ao downloader, versão: {{version}}", + "testDownloader": "Testar comunicação do downloader", + "addNewNode": "Novo nó", + "nameTheNode": "Nomeie o nó:", + "copyBinary": "", + "runCrSlave": "Execute Cloudreve no nó com a mesma versão do mestre, e inicie-o com o seguinte arquivo de configuração:", + "keepIfUpload": "Se você precisar usar este nó para políticas de armazenamento no futuro, mantenha a seguinte configuração CORS.", + "storeFiles": "Armazenar arquivos", + "storeFilesDes": "Use este nó para armazenar arquivos de usuário.", + "storeFilesHint": "Se você quiser usar este nó para políticas de armazenamento, crie uma política de armazenamento escravo e selecione este nó.", + "runCrWithConfig": "Salve o arquivo acima como arquivo <0>config.ini, e inicie Cloudreve com este arquivo: <0>./cloudreve -c config.ini. Uma instância Cloudreve escravo pode servir múltiplos nós mestre Cloudreve; simplesmente adicione este nó escravo a todos os nós mestre e mantenha o segredo igual.", + "inputServer": "Digite o endpoint do nó:", + "testButton": "Você pode clicar no botão abaixo para testar se a comunicação é bem-sucedida.", + "hostHeaderHint": "Se houver erro de assinatura, verifique se o proxy reverso na frente do nó está passando o cabeçalho <0>Host.", + "features": "Recursos habilitados", + "remoteDownload": "Download remoto", + "refresh": "Atualizar" + }, + "group": { + "countUser": "Contagem", + "anonymous": "Grupo de usuário anônimo", + "sysGroup": "Grupo de usuário do sistema", + "adminGroup": "Grupo de usuário administrador", + "#": "#", + "name": "Nome", + "type": "Política de armazenamento", + "count": "Usuários filhos", + "size": "Cota de armazenamento", + "nameOfGroup": "Nome", + "nameOfGroupDes": "Nome do grupo, usado para exibir aos usuários.", + "availablePolicies": "Políticas de armazenamento disponíveis", + "availablePoliciesDes": "Selecione as políticas de armazenamento que este grupo pode usar. Modificar esta configuração não afetará os arquivos enviados pelos usuários.", + "initialStorageQuota": "Cota de armazenamento inicial", + "initialStorageQuotaDes": "Armazenamento máximo que pode ser usado por um único usuário neste grupo.", + "isAdmin": "Grupo administrador", + "isAdminDes": "Quando habilitado, usuários neste grupo terão permissões de administrador.", + "share": "Compartilhar", + "allowCreateShareLink": "Criar link de compartilhamento", + "allowCreateShareLinkDes": "Se desabilitado, usuários não podem criar links de compartilhamento.", + "shareFree": "Link de compartilhamento gratuito", + "shareFreeDes": "Quando habilitado, usuários podem acessar todos os links de compartilhamento pagos sem comprar.", + "fileManagement": "Gerenciamento de arquivos", + "allowWabDAV": "WebDAV", + "allowWabDAVDes": "Se desabilitado, usuários não podem conectar ao armazenamento via protocolo WebDAV", + "allowWabDAVProxy": "Proxy WebDAV", + "allowWabDAVProxyDes": "Se habilitado, usuários podem configurar o WebDAV para ser proxificado pelo Cloudreve ao baixar arquivos.", + "compressTask": "Tarefas de compressão/descompressão", + "compressTaskDes": "Se habilitado, usuários podem fazer compressão/descompressão de arquivos online.", + "compressSize": "Tamanho máximo de arquivo para compressão", + "compressSizeDes": "O tamanho total máximo de arquivo de trabalhos de compressão que podem ser criados pelo usuário, preencha 0 para indicar sem limite. Este limite não é verificado ao criar tarefas de compressão, e se o tamanho total dos arquivos originais exceder este limite ao executar, a tarefa falhará.", + "decompressSize": "Tamanho máximo de arquivo para descompressão", + "decompressSizeDes": "O tamanho total máximo de arquivo de trabalhos de descompressão que podem ser criados pelo usuário, preencha 0 para indicar sem limite.", + "allowRemoteDownload": "Download remoto", + "allowRemoteDownloadDes": "Se permitir que usuários criem tarefas de download remoto. Se você precisar usar download remoto, também precisa ter nós com download remoto habilitado na <0>Lista de Nós.", + "aria2Options": "Opções de trabalho do downloader", + "aria2OptionsDes": "Parâmetros extras para downloaders (qBittorrent ou Aria2), escritos em formato JSON chave-valor, veja a documentação oficial do downloader para parâmetros disponíveis.", + "aria2BatchSize": "Tamanho máximo de lote de tarefas de download remoto", + "aria2BatchSizeDes": "Número máximo para enviar tarefas de download remoto em lote, preencha 0 para indicar sem limite.", + "migratePolicy": "Realocar política de armazenamento", + "migratePolicyDes": "Se o usuário cria uma tarefa de realocação de política de armazenamento.", + "advanceDelete": "Opções avançadas de exclusão de arquivo", + "advanceDeleteDes": "Uma vez habilitado, usuários podem escolher se manter arquivos físicos ao excluir arquivos. Por favor, habilite esta opção apenas para grupos de usuários confiáveis.", + "allowSelectNode": "Permitir selecionar nó", + "allowSelectNodeDes": "Quando habilitado, usuário pode selecionar nó preferido antes de criar tarefas. Quando desabilitado, o nó será balanceado pelo sistema dentro dos nós permitidos para o grupo.", + "allowedNodes": "Nós permitidos", + "allowedNodesDes": "Especifique os nós que este grupo pode usar para criar tarefas. Lista vazia significa que todos os nós estão disponíveis. Usuários só podem selecionar ou ser atribuídos nós dentro desta lista pelo balanceador de carga. Atualmente, as tarefas cobertas são: download remoto, compressão/descompressão de arquivo. Outras tarefas serão atribuídas ao nó mestre.", + "allNodes": "Todos os nós", + "esclateAnonymity": "Escalar anonimato", + "esclateAnonymityDes": "Quando habilitado, usuários podem atribuir permissões mais altas para usuários anônimos (escrever/excluir/criar). Quando desabilitado, usuários só podem atribuir permissão somente leitura para usuários anônimos. Alterar esta configuração não afetará links de compartilhamento ou arquivos existentes.", + "allowDownloadShare": "Acessar links compartilhados", + "allowDownloadShareDes": "Quando desabilitado, usuários não podem visualizar links compartilhados de outros. Esta configuração tem precedência sobre as configurações de permissão do link de compartilhamento.", + "deletedNode": "Nó excluído #{{id}}", + "maxWalkedFiles": "Máximo de arquivos percorridos", + "maxWalkedFilesDes": "Em algumas operações que requerem percorrimento profundo de arquivos, o número máximo de arquivos permitidos para serem percorridos.", + "trashBinDuration": "Duração da lixeira (segundos)", + "trashBinDurationDes": "O tempo de retenção de arquivos na lixeira, arquivos serão permanentemente excluídos após o tempo de expiração. Alterar esta configuração não afetará arquivos já na lixeira.", + "serverSideBatchDownload": "Download em lote do lado do servidor", + "serverSideBatchDownloadDes": "Se permitir que usuários selecionem múltiplos arquivos para usar o download em lote de relay do lado do servidor, após desabilitado, usuários ainda podem usar o recurso de download em lote baseado puramente no navegador.", + "uploadDownload": "Upload e download", + "getDirectLink": "Obter link direto", + "getDirectLinkDes": "Se permitir que usuários obtenham o link direto do arquivo.", + "bathSourceLinkLimit": "Tamanho máximo de links diretos em lote", + "bathSourceLinkLimitDes": "O número máximo de arquivos permitidos para usuários obterem links diretos em um único lote, preencha 0 significa que nenhuma geração em lote de links diretos é permitida.", + "redirectedSource": "Usar link direto redirecionado", + "redirectedSourceDes": "Recomendado habilitar. Quando habilitado, o link direto para o arquivo obtido pelo usuário será redirecionado pelo Cloudreve com um link mais curto. Quando desabilitado, o link direto para o arquivo obtido pelo usuário torna-se a URL original para o arquivo, e é vinculado à versão do arquivo. Algumas políticas produzem links diretos não redirecionados que não permanecem persistentes; veja documentos do Cloudreve para detalhes.", + "reuseDirectLink": "Reutilizar link direto existente", + "reuseDirectLinkDes": "Quando habilitado, múltiplas solicitações para o link direto do mesmo arquivo reutilizarão o link de redirecionamento existente.", + "downloadSpeedLimit": "Velocidade máxima de download", + "downloadSpeedLimitDes": "Preencha 0 para indicar sem limite. Quando a restrição estiver ativada, a velocidade máxima de download será limitada quando usuários baixarem todos os arquivos sob a política de armazenamento que suporta o limite de velocidade.", + "anonymousHint": "Este grupo de usuários corresponde ao visitante anônimo que não está logado.", + "create": "Criar", + "copyFromExisting": "Copiar de grupo existente?", + "notCopy": "Não copiar", + "confirmDelete": "Tem certeza de que deseja excluir o grupo {{group}}?", + "new": "Novo grupo", + "editGroup": "Editar {{group}}" + }, + "user": { + "createdAt": "Criado em", + "originUserGroup": "Grupo de usuário original", + "originUserGroupDes": "Grupo de usuários ao qual o usuário pertencia antes de comprar o grupo atual, o grupo atual reverterá para este grupo após a expiração.", + "noOriginUserGroup": "Não", + "groupExpired": "Data de expiração do grupo", + "groupExpiredDes": "Data de expiração do grupo no formato ISO8601, deixe em branco significa que o grupo é permanente.", + "openUserFiles": "Abrir arquivos do usuário", + "id": "ID", + "idValue": "{{id}} ({{hash_id}})", + "avatar": "Foto do perfil", + "removeAvatar": "Remover foto do perfil", + "userDialogTitle": "Detalhes do usuário", + "2FAEnabled": "2FA habilitado", + "qqEnabled": "QQ vinculado", + "logtoEnabled": "Logto vinculado", + "oidcEnabled": "OIDC vinculado", + "deleted": "Usuário excluído.", + "new": "Novo usuário", + "filter": "Filtro", + "emptyNoFilter": "Deixe em branco significa sem filtro.", + "selectedObjects": "{{num}} objetos selecionados.", + "nick": "Nome de exibição", + "email": "Email", + "group": "Grupo", + "status": "Status", + "usedStorage": "Armazenamento usado", + "status_active": "Ativo", + "status_inactive": "Inativo", + "status_manual_banned": "Bloqueado manualmente", + "status_sys_banned": "Bloqueado pelo sistema", + "toggleBan": "Bloquear/Desbloquear", + "filterCondition": "Condições de filtro", + "all": "Todos", + "userStatus": "Status do usuário", + "apply": "Aplicar", + "editUser": "Editar {{nick}}", + "password": "Senha", + "passwordDes": "Deixe em branco significa sem modificação.", + "groupDes": "Grupo ao qual o usuário pertence.", + "2FA": "2FA", + "notEnabled": "Não habilitado", + "reset2Fa": "Desabilitar", + "reset": "Redefinir", + "confirmDelete": "Tem certeza de que deseja excluir o usuário {{user}}?", + "deleteXUsers": "Excluir {{num}} usuários", + "confirmBatchDelete": "Tem certeza de que deseja excluir {{num}} usuários?", + "calibrateStorage": "Calibrar armazenamento", + "calibrateStorageSuccess": "Armazenamento calibrado com sucesso." + }, + "file": { + "deleteXFiles": "Excluir {{num}} arquivos", + "confirmBatchDelete": "Tem certeza de que deseja excluir {{num}} arquivos?", + "confirmDelete": "Tem certeza de que deseja excluir o arquivo {{file}}?", + "haveShares": "Compartilhado", + "haveDirectLinks": "Tem links diretos redirecionados", + "directLinkId": "Identificador do link", + "directLinks": "Links diretos redirecionados", + "noRecords": "Nenhum registro", + "speed": "Limite de velocidade", + "downloads": "Downloads", + "shareLink": "Links de compartilhamento", + "shareLinkNum": "{{num}} (<0>Visualizar)", + "blobType": "Tipo", + "noEntities": "Nenhum Blob", + "blobs": "Blobs", + "creator": "Criador", + "source": "Origem", + "key": "Chave", + "value": "Valor", + "isPublic": "Público", + "noMetadata": "Nenhum metadado", + "metadata": "Metadados", + "id": "ID", + "primaryStoragePolicy": "Política de armazenamento primária", + "fileDialogTitle": "Detalhes do arquivo", + "name": "Nome do arquivo", + "deleteAsync": "Tarefa de exclusão será executada em segundo plano.", + "forceDelete": "Forçar exclusão", + "size": "Tamanho", + "sizeUsed": "Armazenamento usado", + "uploader": "Proprietário", + "createdAt": "Criado em", + "uploading": "Enviando", + "unknownUploader": "Desconhecido", + "uploaderID": "ID do proprietário", + "searchFileName": "Pesquisar nome do arquivo", + "storagePolicy": "Política de armazenamento", + "selectTargetUser": "Selecionar usuário alvo", + "importTaskCreated": "Tarefa de importação criada, você pode visualizar seu status na lista de tarefas em segundo plano.", + "manuallyPathOnly": "A política de armazenamento selecionada suporta apenas inserção manual do caminho.", + "selectFolder": "Selecionar pasta", + "import": "Importar", + "importExternalFolder": "Importar pastas externas", + "importExternalFolderDes": "Você pode importar arquivos existentes e estruturas de diretório de sua política de armazenamento para o Cloudreve. A operação de importação não ocupará armazenamento físico adicional, mas ainda deduzirá a cota de armazenamento usado do usuário normalmente.", + "storagePolicyDes": "Selecione a política de armazenamento onde os arquivos a serem importados estão atualmente armazenados.", + "targetUser": "Usuário alvo", + "targetUserDes": "Selecione para qual sistema de arquivos de usuário você deseja importar os arquivos.", + "srcFolderPath": "Caminho da pasta de origem", + "select": "Selecionar", + "selectSrcDes": "O caminho do diretório a ser importado no lado do armazenamento.", + "dstFolderPath": "Caminho da pasta de destino", + "dstFolderPathDes": "Caminho no sistema de arquivos do usuário para conter todos os arquivos importados.", + "recursivelyImport": "Importar recursivamente", + "recursivelyImportDes": "Se importar todos os subdiretórios sob o diretório recursivamente.", + "createImportTask": "Criar tarefa de importação", + "unlink": "Desvincular (Manter arquivo físico)", + "searchUser": "Pesquisar usuário por nome ou email...", + "extractMediaMeta": "Extrair informações de mídia", + "extractMediaMetaDes": "Se extrair informações de mídia para cada arquivo durante a importação.", + "importWarning": "Aviso", + "importWarnings": [ + "Após a importação, o arquivo físico será assumido pelo Cloudreve, não o modifique externamente depois.", + "Não importe o mesmo arquivo várias vezes.", + "Se o arquivo do usuário entrar em conflito, este arquivo será pulado." + ], + "otherConditions": "Outras condições", + "shareLinkExisted": "Tem link de compartilhamento", + "directLinkExisted": "Tem link direto", + "isUploading": "Está enviando" + }, + "entity": { + "refenenceCount": "Contagem de referência", + "waitForRecycle": "Aguardando reciclagem", + "entityDialogTitle": "Detalhes do Blob", + "uploadSessionID": "ID da sessão de upload", + "referredFiles": "Arquivos referenciados", + "confirmBatchDelete": "Tem certeza de que deseja excluir {{num}} Blobs?", + "deleteXEntities": "Excluir {{num}} Blobs", + "forceDelete": "Forçar exclusão", + "forceDeleteDes": "Se excluir o registro do Blob independentemente de o arquivo físico ser excluído." + }, + "event": { + "cleanup": "Limpeza", + "cleanupAuditLog": "Limpeza de eventos", + "cleanupAuditLogDescription": "Excluir todos os eventos que atendem às seguintes condições:", + "cleanupNotAfter": "Antes desta data", + "cleanupEventTypes": "Tipos de evento", + "cleanupEventTypesDes": "Selecione os tipos de evento para limpar. Deixe em branco para limpar todos os tipos.", + "initiator": "Iniciador", + "event": "Evento", + "userID": "ID do usuário", + "ip": "IP", + "type": "Tipo", + "correlationId": "ID de correlação", + "fileID": "ID do arquivo", + "emailSend": "Enviar email \"{{title}}\" para {{email}}", + "emailFailed": "Falha ao iniciar fila de email", + "signinFailed": "Falha no login: {{reason}}", + "createDavAccount": "Criar conta WebDAV: {{account}}", + "updateDavAccount": "Atualizar conta WebDAV: {{account}}", + "deleteDavAccount": "Excluir conta WebDAV: {{account}}", + "pointsChange": "Mudança de pontos: {{points}}", + "storageAdded": "Comprado {{size}} armazenamento", + "nickChange": "Nome de exibição alterado de {{old}} para {{new}}", + "eventDialogTitle": "Detalhes do evento", + "userAgent": "User agent", + "linkedUser": "Usuário vinculado", + "datetime": "Hora", + "linkedFile": "Arquivo vinculado", + "linkedEntity": "Blob vinculado", + "linkedShare": "Compartilhamento vinculado", + "rawContent": "Conteúdo bruto", + "confirmDelete": "Tem certeza de que deseja excluir este evento?", + "deleteXEvents": "Excluir {{num}} eventos", + "confirmBatchDelete": "Tem certeza de que deseja excluir {{num}} eventos?" + }, + "share": { + "confirmBatchDelete": "Tem certeza de que deseja excluir {{num}} compartilhamentos?", + "confirmDelete": "Tem certeza de que deseja excluir este compartilhamento?", + "deleteXShares": "Excluir {{num}} compartilhamentos", + "shareDialogTitle": "Detalhes do compartilhamento", + "shareLink": "Link de compartilhamento", + "deleted": "Arquivo excluído", + "srcFileName": "Arquivo de origem", + "views": "Visualizações", + "downloads": "Downloads", + "price": "Preço", + "autoExpire": "Expiração automática", + "owner": "Proprietário", + "createdAt": "Criado em", + "private": "Ocultar da página de perfil", + "yes": "Sim", + "no": "Não", + "afterNDownloads": "Após {{num}} download(s).", + "none": "Nenhum", + "srcType": "Tipo de objeto de origem", + "folder": "Pasta", + "file": "Arquivo" + }, + "task": { + "cleanupTasks": "Limpar tarefas", + "cleanupTasksDescription": "Limpar todas as tarefas que atendem às seguintes condições:", + "cleanupNotAfter": "Antes desta data", + "cleanupTaskTypes": "Tipos de tarefa", + "cleanupTaskTypesDes": "Selecione os tipos de tarefa para limpar. Deixe em branco para limpar todos os tipos.", + "cleanupTaskStatuses": "Status das tarefas", + "cleanupTaskStatusesDes": "Selecione os status das tarefas para limpar. Deixe em branco para limpar todas as tarefas com status concluído.", + "confirmDelete": "Tem certeza de que deseja excluir esta tarefa?", + "confirmBatchDelete": "Tem certeza de que deseja excluir {{num}} tarefas?", + "deleteXTasks": "Excluir {{num}} tarefas", + "blobID": "ID do Blob", + "retryIndex": "Ãndice de tentativa", + "entityError": "Blobs que falharam ao reciclar", + "updatedAt": "Atualizado em", + "taskDialogTitle": "Detalhes da tarefa", + "explicitEntityRecycle": "Reciclar explicitamente Blobs de arquivos: {{blobs}}", + "entityRecycleRoutine": "Escanear e reciclar Blob de arquivos", + "mediaMetadata": "Extrair meta de mídia do Blob <0>#{{entityID}}", + "uploadSentinelCheck": "Verificar status da sessão de upload {{uploadSessionID}}", + "remoteDownload": "Download remoto: ", + "owner": "Proprietário", + "content": "Conteúdo", + "status": "Status", + "create_archive": "Criar arquivo", + "extract_archive": "Extrair arquivo", + "relocate": "Realocar", + "remote_download": "Download remoto", + "media_meta": "Metadados de mídia", + "entity_recycle_routine": "Rotina de reciclagem de entidade", + "explicit_entity_recycle": "Reciclagem explícita de entidade", + "upload_sentinel_check": "Verificação de sentinela de upload", + "import": "Importação externa", + "type": "Tipo", + "node": "Nó distribuído", + "createdBy": "Criado por", + "ready": "Pronto", + "downloading": "Baixando", + "paused": "Pausado", + "seeding": "Semeando", + "error": "Erro", + "finished": "Finalizado", + "canceled": "Cancelado/Parado", + "unknown": "Desconhecido", + "errorMsg": "Mensagem de erro" + }, + "payment": { + "tradeNo": "Nº do Comércio", + "productType": "Tipo de produto", + "providerID": "ID do provedor", + "status": "Status", + "deleteXPayments": "Excluir {{num}} pagamentos" + }, + "customProps": { + "add": "Adicionar", + "type": "Tipo", + "default": "Valor padrão", + "actions": "Ações", + "text": "Texto", + "number": "Número", + "boolean": "Caixa de seleção", + "select": "Seleção única", + "multiSelect": "Seleção múltipla", + "user": "Usuário", + "link": "Link", + "rating": "Avaliação", + "addProp": "Adicionar propriedade", + "editProp": "Editar propriedade", + "icon": "Ãcone", + "iconDes": "Nome do ícone <0>Iconify, deixe em branco para ocultar o ícone.", + "id": "ID", + "idDes": "ID da propriedade, certifique-se de que seja único em todas as propriedades.", + "idPatternDes": "Apenas letras, números, sublinhados e hífens são permitidos.", + "minLength": "Comprimento mínimo", + "maxLength": "Comprimento máximo", + "emptyLimit": "Deixe em branco para não limitar.", + "minValue": "Valor mínimo", + "maxValue": "Valor máximo", + "options": "Opções", + "optionsDes": "Uma opção por linha." + }, + "vas": { + "disableSubAddressEmail": "Desabilitar email de sub-endereço", + "disableSubAddressEmailDes": "Após habilitado, endereços de email contendo <0>+ não podem ser usados para inscrição.", + "confirmDelete": "Tem certeza de que deseja excluir estes pedidos?", + "vas": "VAS", + "reports": "Relatórios", + "orders": "Pagamentos", + "initialFiles": "Arquivos iniciais", + "initialFilesDes": "Especifique os arquivos que o usuário possui inicialmente após as inscrições. Digite um ID de arquivo para pesquisar arquivos existentes.", + "filterEmailProvider": "Filtrar provedor de email", + "filterEmailProviderDisabled": "Desabilitado", + "filterEmailProviderWhitelist": "Lista branca", + "filterEmailProviderBlacklist": "Lista negra", + "filterEmailProviderDes": "Restringir o provedor de email para registro, login SSO de terceiros não é restrito.", + "filterEmailProviderRule": "Regras de filtro de domínio de email", + "filterEmailProviderRuleDes": "Separe múltiplos campos com vírgula e ponto e vírgula.", + "qqConnect": "QQ Connect", + "qqConnectHint": "Ao criar o aplicativo, preencha a URL de callback: {{url}}", + "enableQQConnect": "Habilitar QQ Connect", + "enableQQConnectDes": "Se permitir vincular QQ, usar QQ para fazer login no site.", + "loginWithoutBinding": "Login sem registro", + "loginWithoutBindingDes": "Após habilitado, se um usuário fizer login de terceiros mas não tiver uma conta vinculada, o sistema criará uma conta para eles. Usuários que fazem login desta forma só poderão fazer login usando este terceiro no futuro.", + "appid": "APP ID", + "appidDes": "O APP ID obtido da página de gerenciamento do aplicativo.", + "appKey": "APP KEY", + "appKeyDes": "A APP KEY obtida da página de gerenciamento do aplicativo.", + "overuseReminder": "Lembrete de uso excessivo", + "overuseReminderDes": "Modelo de email de lembrete enviado aos usuários após sua capacidade exceder o limite devido ao VAS expirado.", + "vasSetting": "Configurações VAS", + "storagePack": "Pacotes de armazenamento", + "purchasableGroups": "Associações", + "giftCodes": "Códigos de presente", + "enable": "Habilitar", + "appID": "ID do App", + "appIDDes": "APPID do aplicativo de pagamento.", + "rsaPrivate": "Chave privada RSA do aplicativo", + "rsaPrivateDes": "A chave privada RSA2 (SHA256) para o aplicativo de pagamento, tipicamente gerada por você. Para detalhes, consulte <0>Gerando Chaves RSA.", + "alipayPublicKey": "Chave pública Alipay", + "alipayPublicKeyDes": "Fornecida pelo Alipay, disponível em Gerenciamento de Aplicativo - Informações do Aplicativo - Método de Assinatura da API.", + "wechatPay": "WeChat Pay", + "applicationID": "ID do aplicativo", + "applicationIDDes": "Número público ou appid de aplicativo móvel aplicado por comerciantes.", + "merchantID": "Número do comerciante", + "merchantIDDes": "O número do comerciante gerado e emitido pelo WeChat Pay.", + "apiV3Secret": "Segredo da API v3", + "apiV3SecretDes": "O comerciante precisa definir o segredo em [Plataforma do Comerciante] - [Segurança da API] antes da solicitação do WeChat Pay. O comprimento da chave é de 32 bytes.", + "mcCertificateSerial": "Número serial do certificado do comerciante", + "mcCertificateSerialDes": "Navegue para [Segurança da API] - [Certificado da API] - [Visualizar Certificado] para visualizar o número serial do certificado da API do comerciante.", + "mcAPISecret": "Segredo da API do Comerciante", + "mcAPISecretDes": "Conteúdo do arquivo secreto apiclient_key.pem.", + "payjs": "PAYJS", + "payjsWarning": "Este serviço é fornecido pelo <0>PAYJS, uma plataforma de terceiros, e quaisquer disputas decorrentes dele não são de responsabilidade dos desenvolvedores do Cloudreve.", + "mcNumber": "Número do comerciante", + "mcNumberDes": "Disponível na página inicial do painel administrativo PAYJS.", + "communicationSecret": "Chave de comunicação", + "otherSettings": "Outras Configurações", + "banBufferPeriod": "Período de buffer de suspensão (segundos)", + "banBufferPeriodDes": "O período máximo de tempo que um usuário pode manter o status de excesso de capacidade, além do qual o usuário será suspenso pelo sistema.", + "allowSellShares": "Permitir preços para compartilhamentos", + "allowSellSharesDes": "Uma vez habilitado, usuários podem definir um preço de crédito para compartilhamento e crédito será deduzido para download.", + "creditPriceRatio": "Taxa de chegada de crédito (%)", + "creditPriceRatioDes": "A taxa de créditos que realmente chegam ao compartilhador pela compra de um compartilhamento com preço definido para download.", + "creditPrice": "Preço do crédito (centavo)", + "creditPriceDes": "Preço ao recarregar créditos", + "add": "Adicionar", + "name": "Nome", + "price": "Preço", + "duration": "Duração", + "size": "Tamanho", + "actions": "Ações", + "orCredits": " Ou {{num}} créditos", + "highlight": "Destacar", + "yes": "Sim", + "no": "Não", + "productName": "Nome do produto", + "qyt": "Qtd.", + "code": "Código", + "status": "Status", + "invalidProduct": "Produto inválido", + "used": "Usado", + "notUsed": "Não usado", + "generatingResult": "Resultado", + "addStoragePack": "Adicionar pacote de armazenamento", + "editStoragePack": "Editar pacote de armazenamento", + "productNameDes": "Nome de exibição do produto", + "packSizeDes": "Tamanho do pacote de armazenamento", + "durationDay": "Duração (dia)", + "durationDayDes": "Duração válida de cada pacote de armazenamento.", + "priceYuan": "Preço (Yuan)", + "packPriceDes": "Preço do pacote de armazenamento.", + "priceCredits": "Preço (Créditos)", + "priceCreditsDes": "O preço ao usar créditos para comprar, preencha 0 significa que não pode usar créditos para comprar.", + "editMembership": "Editar associação", + "addMembership": "Adicionar associação", + "group": "Grupo", + "groupDes": "Grupos de usuários atualizados após a compra.", + "durationGroupDes": "A validade do tempo de compra da unidade do grupo de usuários atualizada após a compra.", + "groupPriceDes": "Preço da associação", + "productDescription": "Descrição do produto (Uma por linha)", + "productDescriptionDes": "Descrição do produto exibida na página de compra.", + "highlightDes": "Após habilitado, será destacado na página de seleção de produto.", + "generateGiftCode": "Gerar códigos de presente", + "numberOfCodes": "Número de códigos", + "numberOfCodesDes": "Número de códigos de presente para gerar.", + "linkedProduct": "Produto vinculado", + "productQyt": "Qtd. do produto", + "productQytDes": "Para produtos de crédito, este é o número de pontos e outros produtos são múltiplos de durações.", + "freeDownload": "Baixar arquivos compartilhados gratuitamente", + "freeDownloadDes": "Após habilitado, usuário pode baixar compartilhamentos pagos gratuitamente.", + "credits": "Créditos", + "markSuccessful": "Marcado com sucesso.", + "markAsResolved": "Marcar como resolvido", + "reportedContent": "Conteúdo reportado", + "reason": "Razão", + "description": "Descrição", + "reportTime": "Reportado em", + "invalid": "[Inválido]", + "deleteShare": "Excluir link de compartilhamento", + "orderDeleted": "Pedido excluído.", + "orderName": "Nome", + "product": "Produto", + "paymentId": "ID de pagamento", + "orderNumber": "Nº do Comércio", + "amount": "Valor", + "paidBy": "Pago com", + "orderOwner": "Criado por", + "unpaid": "Não pago", + "paid": "Pago", + "shareLink": "Link compartilhado", + "mobileApp": "Aplicativo móvel", + "showAppPromotion": "Mostrar página de promoção", + "showAppPromotionDes": "Após habilitado, usuário pode ver a página de orientação para aplicativo móvel na página \"Conectar e Montar\".", + "customPaymentName": "Nome do método de pagamento", + "customPaymentNameDes": "Nome do método de pagamento usado para exibir ao usuário.", + "customPaymentSecretDes": "Chave secreta para assinar solicitações de pagamento.", + "customPaymentEndpoint": "URL da API de pagamento", + "customPaymentEndpointDes": "URL a ser solicitada ao criar um pedido de pagamento.", + "appFeedback": "URL de feedback", + "appForum": "URL do fórum do usuário", + "appLinkDes": "Será exibido no cliente móvel, deixe vazio para ocultar item do menu. Esta configuração terá efeito apenas se a licença VOL for válida." + }, + "pro": { + "title": "Funcionalidades exclusivas da versão Pro", + "description": "A funcionalidade que você está tentando acessar está disponível apenas na versão Pro do Cloudreve, atualize para desbloquear todas as funcionalidades avançadas.", + "proInclude": "A versão Pro inclui:", + "shareLinkCollabration": "Compartilhar link de colaboração", + "filePermission": "Gestão de permissões de arquivos", + "multipleStoragePolicy": "Troca de políticas de armazenamento e políticas de armazenamento de diretório", + "auditAndActivity": "Registro de atividades de arquivos e sistema", + "vasService": "Serviços adicionais e sistema de pontos", + "sso": "SSO de login único", + "more": "......", + "later": "Mais tarde", + "learnMore": "Saiba mais sobre a versão Pro", + "promotionTitle": "Promoção especial de atualização da versão comunitária", + "promotion": "Use o código de promoção <0>{{code}} ao comprar para obter um <1>-{{discount}}% de desconto." + }, + "abuseReport": { + "deleteXAbuseReports": "Excluir {{num}} relatórios de abuso", + "folderPath": "Caminho da pasta", + "reporter": "Relator", + "shareLink": "Link compartilhado <0>#{{id}}", + "deletedShare": "Link compartilhado excluído", + "deletedUser": "Usuário excluído", + "confirmDelete": "Tem certeza de que deseja excluir este relatório de abuso?", + "confirmBatchDelete": "Tem certeza de que deseja excluir {{num}} relatórios de abuso?", + "reporterID": "ID do usuário relator", + "reportedUserID": "ID do usuário reportado", + "shareID": "ID compartilhado", + "reason": "Razão" + } +} diff --git a/public/locales/pt-BR/image_editor.json b/public/locales/pt-BR/image_editor.json new file mode 100755 index 0000000..9a5efdc --- /dev/null +++ b/public/locales/pt-BR/image_editor.json @@ -0,0 +1,113 @@ +{ + "name": "Nome", + "save": "Salvar", + "saveAs": "Salvar como", + "back": "Voltar", + "loading": "Carregando...", + "resetOperations": "Redefinir/excluir todas as operações", + "changesLoseWarningHint": "Se você pressionar o botão \"redefinir\", suas alterações serão perdidas. Gostaria de continuar?", + "discardChangesWarningHint": "Se você fechar o modal, sua última alteração não será salva.", + "cancel": "Cancelar", + "apply": "Aplicar", + "warning": "Aviso", + "confirm": "Confirmar", + "discardChanges": "Descartar alterações", + "undoTitle": "Desfazer última operação", + "redoTitle": "Refazer última operação", + "showImageTitle": "Mostrar imagem original", + "zoomInTitle": "Ampliar", + "zoomOutTitle": "Reduzir", + "toggleZoomMenuTitle": "Alternar menu de zoom", + "adjustTab": "Ajustar", + "finetuneTab": "Ajuste fino", + "filtersTab": "Filtros", + "watermarkTab": "Marca d'água", + "annotateTabLabel": "Anotar", + "resize": "Redimensionar", + "resizeTab": "Redimensionar", + "imageName": "Nome da imagem", + "invalidImageError": "Imagem fornecida inválida.", + "uploadImageError": "Erro ao enviar a imagem.", + "areNotImages": "não são imagens", + "isNotImage": "não é imagem", + "toBeUploaded": "a ser enviada", + "cropTool": "Cortar", + "original": "Original", + "custom": "Personalizado", + "square": "Quadrado", + "landscape": "Paisagem", + "portrait": "Retrato", + "ellipse": "Elipse", + "classicTv": "TV Clássica", + "cinemascope": "Cinemascope", + "arrowTool": "Seta", + "blurTool": "Desfoque", + "brightnessTool": "Brilho", + "contrastTool": "Contraste", + "ellipseTool": "Elipse", + "unFlipX": "Desfazer espelhamento X", + "flipX": "Espelhar X", + "unFlipY": "Desfazer espelhamento Y", + "flipY": "Espelhar Y", + "hsvTool": "HSV", + "hue": "Matiz", + "brightness": "Brilho", + "saturation": "Saturação", + "value": "Valor", + "imageTool": "Imagem", + "importing": "Importando...", + "addImage": "+ Adicionar imagem", + "uploadImage": "Enviar imagem", + "fromGallery": "Da galeria", + "lineTool": "Linha", + "penTool": "Caneta", + "polygonTool": "Polígono", + "sides": "Lados", + "rectangleTool": "Retângulo", + "cornerRadius": "Raio do canto", + "resizeWidthTitle": "Largura em pixels", + "resizeHeightTitle": "Altura em pixels", + "toggleRatioLockTitle": "Alternar bloqueio de proporção", + "resetSize": "Redefinir para tamanho original da imagem", + "rotateTool": "Rotacionar", + "textTool": "Texto", + "textSpacings": "Espaçamentos de texto", + "textAlignment": "Alinhamento do texto", + "fontFamily": "Família da fonte", + "size": "Tamanho", + "letterSpacing": "Espaçamento entre letras", + "lineHeight": "Altura da linha", + "warmthTool": "Temperatura", + "addWatermark": "+ Adicionar marca d'água", + "addTextWatermark": "+ Adicionar marca d'água de texto", + "addWatermarkTitle": "Escolha o tipo de marca d'água", + "uploadWatermark": "Enviar marca d'água", + "addWatermarkAsText": "Adicionar como texto", + "padding": "Preenchimento", + "paddings": "Preenchimentos", + "shadow": "Sombra", + "horizontal": "Horizontal", + "vertical": "Vertical", + "blur": "Desfoque", + "opacity": "Opacidade", + "transparency": "Transparência", + "position": "Posição", + "stroke": "Contorno", + "saveAsModalTitle": "Salvar como", + "extension": "Extensão", + "format": "Formato", + "nameIsRequired": "Nome é obrigatório.", + "quality": "Qualidade", + "imageDimensionsHoverTitle": "Tamanho da imagem salva (largura x altura)", + "cropSizeLowerThanResizedWarning": "Nota: a área de corte selecionada é menor que o redimensionamento aplicado, o que pode causar diminuição da qualidade", + "actualSize": "Tamanho real (100%)", + "fitSize": "Ajustar tamanho", + "addImageTitle": "Selecionar imagem para adicionar...", + "mutualizedFailedToLoadImg": "Falha ao carregar imagem.", + "tabsMenu": "Menu", + "download": "Baixar", + "width": "Largura", + "height": "Altura", + "plus": "+", + "cropItemNoEffect": "Nenhuma visualização disponível para este item de corte" +} diff --git a/public/locales/pt-BR/markdown_editor.json b/public/locales/pt-BR/markdown_editor.json new file mode 100755 index 0000000..b2acb3f --- /dev/null +++ b/public/locales/pt-BR/markdown_editor.json @@ -0,0 +1,114 @@ +{ + "frontmatterEditor": { + "title": "Editar frontmatter do documento", + "key": "Chave", + "value": "Valor", + "addEntry": "Adicionar entrada" + }, + "dialogControls": { + "save": "Salvar", + "cancel": "Cancelar" + }, + "uploadImage": { + "dialogTitle": "Enviar imagem", + "uploadInstructions": "Envie uma imagem do seu dispositivo:", + "addViaUrlInstructions": "Ou adicione uma imagem de uma URL / caminho relativo (relativo ao arquivo atual):", + "autoCompletePlaceholder": "Selecione ou cole um src de imagem", + "addViaUrlInstructionsNoUpload": "URL da imagem:", + "alt": "Texto alternativo:", + "title": "Título:" + }, + "imageEditor": { + "deleteImage": "Excluir imagem", + "editImage": "Editar imagem" + }, + "createLink": { + "url": "URL", + "urlPlaceholder": "Selecione ou cole uma URL", + "title": "Título", + "saveTooltip": "Definir URL", + "cancelTooltip": "Cancelar alteração" + }, + "linkPreview": { + "open": "Abrir {{url}} em nova janela", + "edit": "Editar URL do link", + "copyToClipboard": "Copiar para área de transferência", + "copied": "Copiado!", + "remove": "Remover link" + }, + "table": { + "deleteTable": "Excluir tabela", + "columnMenu": "Menu da coluna", + "textAlignment": "Alinhamento do texto", + "alignLeft": "Alinhar à esquerda", + "alignCenter": "Alinhar ao centro", + "alignRight": "Alinhar à direita", + "insertColumnLeft": "Inserir uma coluna à esquerda desta", + "insertColumnRight": "Inserir uma coluna à direita desta", + "deleteColumn": "Excluir esta coluna", + "rowMenu": "Menu da linha", + "insertRowAbove": "Inserir uma linha acima desta", + "insertRowBelow": "Inserir uma linha abaixo desta", + "deleteRow": "Excluir esta linha" + }, + "toolbar": { + "blockTypes": { + "paragraph": "Parágrafo", + "quote": "Citação", + "heading": "Cabeçalho {{level}}" + }, + "blockTypeSelect": { + "selectBlockTypeTooltip": "Selecionar tipo de bloco", + "placeholder": "Tipo de bloco" + }, + "toggleGroup": "alternar grupo", + "removeBold": "Remover negrito", + "bold": "Negrito", + "removeItalic": "Remover itálico", + "italic": "Itálico", + "underline": "Remover sublinhado", + "removeUnderline": "Sublinhado", + "removeInlineCode": "Remover formato de código", + "inlineCode": "Formato de código inline", + "link": "Criar link", + "richText": "Texto rico", + "diffMode": "Modo diff", + "source": "Modo código-fonte", + "admonition": "Inserir admoestação", + "codeBlock": "Inserir bloco de código", + "editFrontmatter": "Editar frontmatter", + "insertFrontmatter": "Inserir frontmatter", + "image": "Inserir imagem", + "insertSandpack": "Inserir Sandpack", + "table": "Inserir tabela", + "thematicBreak": "Inserir quebra temática", + "bulletedList": "Lista com marcadores", + "numberedList": "Lista numerada", + "checkList": "Lista de verificação", + "deleteSandpack": "Excluir este bloco de código", + "undo": "Desfazer {{shortcut}}", + "redo": "Refazer {{shortcut}}", + "superscript": "Sobrescrito", + "subscript": "Subscrito", + "strikethrough": "Tachado", + "removeSubscript": "Remover subscrito", + "removeSuperscript": "Remover sobrescrito", + "removeStrikethrough": "Remover tachado" + }, + "admonitions": { + "note": "Nota", + "tip": "Dica", + "danger": "Perigo", + "info": "Informação", + "caution": "Cuidado", + "changeType": "Selecionar tipo de admoestação", + "placeholder": "Tipo de admoestação" + }, + "codeBlock": { + "language": "Linguagem do bloco de código", + "selectLanguage": "Selecionar linguagem do bloco de código" + }, + "contentArea": { + "editableMarkdown": "markdown editável" + } +} diff --git a/public/locales/ru-RU/application.json b/public/locales/ru-RU/application.json new file mode 100755 index 0000000..787c00b --- /dev/null +++ b/public/locales/ru-RU/application.json @@ -0,0 +1,1113 @@ +{ + "login": { + "lastStep": "ПоÑледний шаг", + "siginToYourAccount": "Войти в Ñвой аккаунт", + "createNewAccount": "Создать новый аккаунт", + "enterPassword": "Введите пароль", + "enterPasswordHint": "ПожалуйÑта, введите пароль Ð´Ð»Ñ {{email}}", + "paswordlessHint": "Ðккаунт {{email}} ÑвлÑетÑÑ Ð±ÐµÑпарольным, выберите один из Ñледующих ÑпоÑобов аутентификации:", + "noAccountSignupNow": "Ðет аккаунта? <0>ЗарегиÑтрироватьÑÑ ÑейчаÑ", + "haveAccountSignInNow": "Уже еÑть аккаунт? <0>Войти ÑейчаÑ", + "privacyPolicy": "Политика конфиденциальноÑти", + "termOfUse": "УÑÐ»Ð¾Ð²Ð¸Ñ Ð¸ÑпользованиÑ", + "signupHint": "Введённый вами аккаунт {{email}} не ÑущеÑтвует, хотите ли вы зарегиÑтрироватьÑÑ ÑейчаÑ?", + "accountNotFoundHint": "Введённый вами аккаунт {{email}} не ÑущеÑтвует.", + "or": "Или", + "selectAccountToUse": "Выберите аккаунт Ð´Ð»Ñ Ð¸ÑпользованиÑ", + "useOtherAccount": "ИÑпользовать другой аккаунт", + "email": "Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð°", + "password": "Пароль", + "captcha": "Капча", + "captchaError": "Ошибка загрузки капчи: {{message}}", + "signIn": "Войти", + "signUp": "ЗарегиÑтрироватьÑÑ", + "signUpAccount": "РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð°", + "useFIDO2": "Войти Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ ключа доÑтупа", + "usePassword": "Войти Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ паролÑ", + "forgetPassword": "Забыли пароль?", + "2FA": "Ð”Ð²ÑƒÑ…Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð°Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ", + "input2FACode": "Введите 6-значный код двухфакторной аутентификации", + "passwordNotMatch": "Пароли не Ñовпадают", + "findMyPassword": "ВоÑÑтановить пароль", + "passwordReset": "Пароль Ñброшен", + "newPassword": "Ðовый пароль", + "repeatNewPassword": "Повторите новый пароль", + "repeatPassword": "Повторите пароль", + "resetPassword": "СброÑить пароль", + "backToSingIn": "ВернутьÑÑ Ðº входу", + "sendMeAnEmail": "Отправить пиÑьмо Ð´Ð»Ñ ÑброÑа паролÑ", + "resetEmailSent": "ПиÑьмо Ð´Ð»Ñ ÑброÑа Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¾, пожалуйÑта, проверьте почту", + "browserNotSupport": "Текущий браузер или Ñреда не поддерживаетÑÑ", + "success": "Вход выполнен уÑпешно", + "signUpSuccess": "РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð¿Ñ€Ð¾ÑˆÐ»Ð° уÑпешно", + "activateSuccess": "ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð¿Ñ€Ð¾ÑˆÐ»Ð° уÑпешно", + "accountActivated": "Ваш аккаунт был уÑпешно активирован", + "title": "Войти в {{title}}", + "sinUpTitle": "ЗарегиÑтрироватьÑÑ Ð² {{title}}", + "activateTitle": "ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð¿Ð¾ Ñлектронной почте", + "activateDescription": "ПиÑьмо Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸ÐµÐ¹ было отправлено на вашу почту, пожалуйÑта, перейдите по ÑÑылке в пиÑьме, чтобы завершить региÑтрацию.", + "continue": "Далее", + "back": "Ðазад", + "logout": "Выйти", + "signingOut": "Выход из ÑиÑтемы...", + "loggedOut": "Ð’Ñ‹ вышли из ÑиÑтемы", + "clickToRefresh": "Ðажмите Ð´Ð»Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÐ°Ð¿Ñ‡Ð¸", + "switchLanguage": "Сменить Ñзык" + }, + "navbar": { + "notBefore": "Ðе раньше", + "notAfter": "Ðе позже", + "minimum": "Минимум", + "maximum": "МакÑимум", + "fileSize": "Размер файла", + "searchBase": "Путь поиÑка", + "searchInBase": "ПоиÑк в <0>", + "conditionDuplicate": "УÑловие уже ÑущеÑтвует", + "fileType": "Тип файла", + "addCondition": "Добавить уÑловие", + "notNameOpOr": "Должны ÑодержатьÑÑ Ð²Ñе ключевые Ñлова", + "caseFolding": "Игнорировать региÑтр", + "keywords": "Ключевые Ñлова", + "resetThumbnail": "СброÑить повреждённую миниатюру", + "resetThumbnailRequested": "Запрошен ÑÐ±Ñ€Ð¾Ñ Ð¼Ð¸Ð½Ð¸Ð°Ñ‚ÑŽÑ€Ñ‹.", + "noFileCanResetThumbnail": "Ðет файлов Ð´Ð»Ñ ÑброÑа миниатюры.", + "fileNameKeywordsHelp": "Ðажмите Enter Ð´Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÐ»ÑŽÑ‡ÐµÐ²Ð¾Ð³Ð¾ Ñлова", + "advancedSearch": "РаÑширенный поиÑк", + "searchFilesTitle": "ПоиÑк файлов", + "searchIn": "ПоиÑк <0>{{keywords}}", + "recentlyViewed": "Ðедавно проÑмотренные", + "searchFiles": "ПоиÑк файлов...", + "showMore": "Ещё", + "myFiles": "Мои файлы", + "hisFiles": "Его файлы", + "trash": "Корзина", + "sharedWithMe": "ПоделилиÑÑŒ Ñо мной", + "myShare": "Мои публикации", + "remoteDownload": "Ð£Ð´Ð°Ð»Ñ‘Ð½Ð½Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ°", + "connect": "Подключение и монтирование", + "taskQueue": "Фоновые задачи", + "setting": "ÐаÑтройки", + "videos": "Видео", + "photos": "Фотографии", + "music": "Музыка", + "documents": "Документы", + "addATag": "Добавить тег...", + "addTagDialog": { + "selectFolder": "Выбрать папку", + "fileSelector": "КлаÑÑÐ¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²", + "folderLink": "Ярлык папки", + "tagName": "Ðазвание тега", + "matchPattern": "Правило ÑоответÑÑ‚Ð²Ð¸Ñ Ð¸Ð¼ÐµÐ½Ð¸ файла", + "matchPatternDescription": "Ð’Ñ‹ можете иÑпользовать <0>* как подÑтановочный знак. Ðапример, <1>*.png означает ÑоответÑтвие изображениÑм в формате png. Правила в неÑкольких Ñтроках будут работать по принципу \"ИЛИ\".", + "icon": "Иконка:", + "color": "Цвет:", + "folderPath": "Путь к папке" + }, + "storage": "Хранилище", + "storageDetail": "ИÑпользовано {{used}} из {{total}}", + "notLoginIn": "Ðе вошёл в ÑиÑтему", + "visitor": "ГоÑть", + "objectsSelected": "Выбрано {{num}} объектов", + "searchPlaceholder": "Ðажмите <0>/ Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка", + "backToHomepage": "ВернутьÑÑ Ð½Ð° главную", + "darkModeSwitch": "Переключить тёмную тему", + "toDarkMode": "ТёмнаÑ", + "toLightMode": "СветлаÑ", + "myProfile": "Мой профиль", + "dashboard": "Панель управлениÑ" + }, + "fileManager": { + "currentStoragePolicy": "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ° хранениÑ: {{policy}}", + "customProps": "ПользовательÑкие ÑвойÑтва", + "rating": "Рейтинг", + "description": "ОпиÑание", + "add": "Добавить", + "clickToEdit": "Ðажмите Ð´Ð»Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ...", + "clickToEditSelect": "Ðажмите Ð´Ð»Ñ Ð²Ñ‹Ð±Ð¾Ñ€Ð°...", + "enterUrl": "Введите URL...", + "searchUser": "ПоиÑк пользователÑ...", + "typeToSearch": "Введите Ð¸Ð¼Ñ Ð¸Ð»Ð¸ email...", + "searchProperty": "ПоиÑк файлов Ñ Ñ‚ÐµÐ¼ же ÑвойÑтвом", + "quality": "КачеÑтво", + "audioTrack": "Ðудиодорожка", + "auto": "Ðвто", + "default": "По умолчанию", + "shareWithMeEmpty": "Файлы, которыми поделилиÑÑŒ Ñ Ð²Ð°Ð¼Ð¸, не найдены", + "shareWithMeEmptyDes": "ЕÑли вы хотите видеть здеÑÑŒ файлы других пользователей, Ñохраните Ñрлык в любое меÑто в ваших файлах при поÑещении ÑÑылки на публикацию.", + "selectAll": "Выбрать вÑÑ‘", + "selectNone": "Отменить выбор", + "invertSelection": "Инвертировать выбор", + "imageSize": "Размер изображениÑ", + "focalLength": "ФокуÑное раÑÑтоÑние", + "columnExisted": "Столбец уже ÑущеÑтвует", + "metadataColumn": "Метаданные ({{metadata}})", + "column": "Столбец", + "listColumnSetting": "ÐаÑтройки Ñтолбцов", + "addColumn": "Добавить Ñтолбец", + "failedLoadPreview": "Ðе удалоÑÑŒ загрузить предварительный проÑмотр", + "recursiveLimitReached": "ДоÑтигнут предел глубины поиÑка", + "recursiveLimitReachedDes": "СиÑтема оÑтановила поиÑк в более глубоких папках, попробуйте Ñузить облаÑть поиÑка.", + "searchConditions": "{{num}} уÑловий", + "createDate": "Дата ÑозданиÑ", + "updatedDate": "Дата изменениÑ", + "cameraMake": "Производитель камеры", + "cameraModel": "Модель камеры", + "lensModel": "Модель объектива", + "lensMake": "Производитель объектива", + "metadataKey": "Ключ", + "metadataValue": "Значение", + "metadata": "Метаданные", + "symbolicFile": "Ярлык", + "relocation": "ÐŸÐµÑ€ÐµÐ½Ð¾Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ¸ хранениÑ", + "downloadingFile": "Загрузка \"{{name}}\", пожалуйÑта, не закрывайте Ñту Ñтраницу...", + "mountOwner": "Только владелец текущей папки может монтировать политики", + "uploading": "Загрузка", + "noActionsCanBeDone": "Ðет доÑтупных дейÑтвий", + "newFileName": "Ðовый файл.{{ext}}", + "newDocumentType": "{{display_name}} (.{{ext}})", + "text": "ТекÑÑ‚", + "diagram": "Диаграмма", + "whiteboard": "ДоÑка", + "selectApplications": "Выбрать приложениÑ...", + "newlyCreatedFolder": "ÐÐ¾Ð²Ð°Ñ Ð¿Ð°Ð¿ÐºÐ°", + "expandAllApp": "Развернуть вÑе приложениÑ", + "epubViewer": "ПроÑмотрщик ePub", + "googledocs": "Онлайн-проÑмотрщик Google Docs", + "m365viewer": "Онлайн-проÑмотрщик Microsoft Office", + "pdfViewer": "ПроÑмотрщик PDF", + "archivePreview": "Предварительный проÑмотр архива", + "extractSelected": "Извлечь выбранные файлы", + "viewerFileSizeWarning": "Размер открываемого файла ({{file_size}}) превышает лимит {{app}} ({{max}}), возможно, он не будет работать корректно.", + "testSubtitleStyle": "ТеÑÑ‚ ÑÑ‚Ð¸Ð»Ñ Ñубтитров AaBbCc", + "color": "Цвет", + "fontSize": "Размер шрифта", + "disableSubtitle": "Отключить Ñубтитры", + "noSubtitle": "Файлы Ñубтитров ASS/SRT/VTT не найдены в текущей папке.", + "subtitleStyles": "Стили Ñубтитров", + "subtitles": "Субтитры", + "markdownEditor": "Редактор Markdown", + "saveSuccess": "УÑпешно Ñохранено в {{time}}", + "drawioLng": "ru", + "charset": "Кодировка", + "textType": "Тип текÑта", + "fileSaved": "Файл Ñохранён", + "failedToLoadFile": "Ðе удалоÑÑŒ загрузить файл: {{msg}}", + "monacoEditor": "Редактор кода Monaco", + "preparingOpenFile": "Подготовка к открытию файла...", + "openWithDescription": "Выберите приложение Ð´Ð»Ñ Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° .{{ext}}.", + "openWith": "Открыть Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ", + "readOnly": "Только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ", + "save": "Сохранить", + "noMoreImages": "Ðа текущей Ñтранице нет изображений Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñмотра", + "imageViewer": "ПроÑмотрщик изображений", + "logFileDeleteShare": "Удалить ÑÑылку на публикацию", + "logFileEditShare": "Редактировать ÑÑылку на публикацию", + "deleteShareWarning": "Ð’Ñ‹ уверены, что хотите удалить Ñту ÑÑылку на публикацию?", + "edit": "Редактировать", + "editAndReactivate": "Редактировать и повторно активировать", + "yes": "Да", + "no": "Ðет", + "permanentValid": "ДейÑтвует поÑтоÑнно", + "manageShares": "Управление ÑÑылками на публикации", + "manageDirectLinks": "Управление прÑмыми ÑÑылками", + "deleteLinkConfirm": "Ð’Ñ‹ уверены, что хотите удалить Ñту прÑмую ÑÑылку?", + "directLinkNotFound": "ПрÑÐ¼Ð°Ñ ÑÑылка, которую вы ищете, больше не ÑущеÑтвует.", + "versionNotFound": "ВерÑиÑ, которую вы ищете, больше не ÑущеÑтвует.", + "setNow": "УÑтановить ÑейчаÑ", + "permissionNotSet": "Ð Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñтого файла не уÑтановлены", + "permissionNotSetDes": "ЕÑли Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð½Ðµ уÑтановлены, будут иÑпользоватьÑÑ Ð½Ð°Ñтройки родительÑкой папки или ÑÑылки на публикацию", + "permissions": "РазрешениÑ", + "logFileUpdateMetadata": "Обновить метаданные файла", + "all": "Ð’Ñе", + "updatesOnly": "Только обновлениÑ", + "readsOnly": "Только чтение", + "myActivitiesOnly": "Только мои дейÑтвиÑ", + "logUpdateView": "Обновить наÑтройки проÑмотра", + "logDeleteDirectLink": "Удалить прÑмую ÑÑылку", + "logFileImported": "Импортировано из внешнего файла", + "logGetDirectLink": "Получена <0>прÑÐ¼Ð°Ñ ÑÑылка", + "logFileMount": "ПривÑзано к политике Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ \"{{name}}\"", + "lookForThisVersion": "Ðайти Ñту верÑию", + "logFileThumbGenerated": "Запущена Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¼Ð¸Ð½Ð¸Ð°Ñ‚ÑŽÑ€Ñ‹", + "logFileLivePhotoUploaded": "Загружено Live Photo", + "logFileCreate": "Создан Ñтот объект", + "logFileRename": "Объект переименован Ñ \"{{originalName}}\" на \"{{newName}}\"", + "logFileSetPermission": "Изменены Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð°", + "logFileEntityUpload": "Обновлено Ñодержимое файла", + "logFileCopyFrom": "Создан Ñтот объект копированием из <0> в <1>", + "logFileCopyTo": "Скопирован из <0> в <1>", + "logFileMoveTo": "Перемещён из <0> в <1>", + "logFileMoveToTrash": "Перемещён Ñтот объект из <0> в корзину", + "logFileShare": "Опубликован Ñтот объект", + "logFileSetCurrentVersion": "ВерÑÐ¸Ñ Ñ„Ð°Ð¹Ð»Ð° откачена к <0>", + "logFileDeleteVersion": "Удалена верÑиÑ, ÑÐ¾Ð·Ð´Ð°Ð½Ð½Ð°Ñ <0>", + "logEntityDownloaded": "Загружен или прочитан Ñтот объект", + "logDirectLinkDownloaded": "Прочитан через <0>прÑмую ÑÑылку", + "logRelocate": "Политика Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¿ÐµÑ€ÐµÐ½ÐµÑена в {{newPolicy}}", + "logCreateArchive": "Добавлено в архивный файл <0>", + "logExtractArchive": "Извлечено в <0>", + "deleteVersionWarning": "Ð’Ñ‹ уверены, что хотите удалить Ñту верÑию? Это дейÑтвие Ð½ÐµÐ»ÑŒÐ·Ñ Ð¾Ñ‚Ð¼ÐµÐ½Ð¸Ñ‚ÑŒ.", + "setAsCurrent": "УÑтановить как текущую верÑию", + "current": "[Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð²ÐµÑ€ÑиÑ]", + "createdBy": "Создано", + "manageVersions": "Управление верÑиÑми", + "livePhoto": "Live Photo", + "version": "ВерÑиÑ", + "actions": "ДейÑтвиÑ", + "versionEntity": "Данные файла и иÑÑ‚Ð¾Ñ€Ð¸Ñ Ð²ÐµÑ€Ñий", + "data": "Данные", + "owned": "Владеет Ñтим файлом", + "ownedSymbolic": "Владеет Ñтим Ñрлыком", + "expires": "ИÑтекает", + "originalLocation": "ИÑходное меÑтоположение", + "myPermissions": "Мои разрешениÑ", + "descendant": "Дочерние объекты", + "folderChildren": "{{files}} файлов, {{folders}} папок", + "moreThan": "Больше чем {{text}}", + "calculate": "ВычиÑлить", + "unset": "Ðе уÑтановлено", + "folder": "Папка", + "file": "Файл", + "symbolicLink": "Ярлык ({{srcType}})", + "type": "Тип", + "storageUsed": "ЗанÑто меÑта", + "location": "МеÑтоположение", + "basicInfo": "ОÑÐ½Ð¾Ð²Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ", + "format": "Формат", + "duration": "ДлительноÑть", + "artist": "ИÑполнитель", + "album": "Ðльбом", + "title": "Ðазвание", + "resolution": "Разрешение", + "takenAt": "Ð’Ñ€ÐµÐ¼Ñ Ñъёмки", + "software": "ПО", + "copyright": "Ðвтор", + "exposureBias": "КомпенÑÐ°Ñ†Ð¸Ñ ÑкÑпозиции", + "flash": "Ð’Ñпышка", + "copyToClipboard": "Скопировать в буфер обмена", + "searchSomething": "ПоиÑк \"{{text}}\"...", + "iso": "ISO", + "exposureValue": "{{num}} Ñек", + "exposure": "ЭкÑпозициÑ", + "aperture": "Диафрагма", + "address": "ÐдреÑ", + "street": "Улица", + "locality": "МеÑтноÑть", + "place": "Город", + "district": "Район", + "region": "ПровинциÑ", + "country": "Страна", + "mediaInfo": "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ медиа", + "details": "ПодробноÑти", + "activity": "ÐктивноÑть", + "goToSharedLink": "Перейти к ÑÑылке на публикацию", + "saveShortcut": "Сохранить публикацию как Ñрлык", + "customizeIcon": "ÐаÑтроить иконку", + "tags": "Теги", + "apply": "Применить", + "customizeColor": "ÐаÑтроить цвет", + "folderColor": "Цвет папки", + "restore": "ВоÑÑтановить", + "unpin": "Открепить", + "youDontHaveReadPermissionToThisFile": "У Ð²Ð°Ñ Ð½ÐµÑ‚ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð½Ð° чтение Ñтого Ñодержимого", + "anonymousAccessDenied": "У Ð²Ð°Ñ Ð½ÐµÑ‚ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð½Ð° чтение Ñтого Ñодержимого, попробуйте войти в аккаунт.", + "sharedWithOthers": "ПоделитьÑÑ Ñ Ð´Ñ€ÑƒÐ³Ð¸Ð¼Ð¸", + "new": "Создать", + "open": "Открыть", + "openParentFolder": "Перейти в папку", + "download": "Скачать", + "batchDownload": "ÐŸÐ°ÐºÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ°", + "share": "ПоделитьÑÑ", + "rename": "Переименовать", + "organize": "УпорÑдочить", + "pin": "Закрепить в боковой панели", + "pinAlias": "Отображаемое имÑ", + "optional": "ÐеобÑзательно", + "move": "ПеремеÑтить", + "delete": "Удалить", + "moreActions": "Больше дейÑтвий", + "refresh": "Обновить", + "createArchive": "Создать архив", + "newFolder": "Создать папку", + "newFile": "Создать файл", + "showFullPath": "Показать путь", + "listView": "СпиÑок", + "gridView": "Сетка", + "galleryView": "ГалереÑ", + "paginationSize": "Размер Ñтраницы", + "paginationOption": "{{option}} / Ñтраница", + "noPagination": "Без разбивки на Ñтраницы", + "sortMethod": "Сортировка", + "sortMethods": { + "A-Z": "Ð-Я", + "Z-A": "Я-Ð", + "oldestUploaded": "Самые Ñтарые загруженные", + "newestUploaded": "Самые новые загруженные", + "oldestModified": "Самые Ñтарые изменённые", + "newestModified": "Самые новые изменённые", + "smallest": "Самые маленькие", + "largest": "Самые большие" + }, + "shareCreateBy": "Создано {{nick}}", + "name": "ИмÑ", + "size": "Размер", + "lastModified": "Дата изменениÑ", + "currentFolder": "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð¿Ð°Ð¿ÐºÐ°", + "backToParentFolder": "РодительÑÐºÐ°Ñ Ð¿Ð°Ð¿ÐºÐ°", + "folders": "Папки", + "files": "Файлы", + "listError": "Ошибка при запроÑе", + "dropFileHere": "Перетащите файлы Ñюда", + "orClickUploadButton": "или нажмите кнопку \"Создать\" в левом верхнем углу Ð´Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²", + "nothingFound": "Ðичего не найдено", + "uploadFiles": "Загрузить файлы", + "uploadFolder": "Загрузить папку", + "newRemoteDownloads": "Ð£Ð´Ð°Ð»Ñ‘Ð½Ð½Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ°", + "enter": "Войти", + "getSourceLink": "Получить прÑмую ÑÑылку", + "createRemoteDownloadForTorrent": "Создать задачу удалённой загрузки", + "extractArchive": "Извлечь архив", + "createShareLink": "Создать ÑÑылку на публикацию", + "viewDetails": "ÐŸÐ¾Ð´Ñ€Ð¾Ð±Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ", + "copy": "Копировать", + "bytes": " ({{bytes}} байт)", + "storagePolicy": "Политика хранениÑ", + "inheritedFromParent": "(ÐаÑледуетÑÑ Ð¾Ñ‚ родительÑкой папки)", + "childFolders": "Содержит папки", + "childFiles": "Содержит файлы", + "childCount": "{{num}}", + "parentFolder": "РодительÑÐºÐ°Ñ Ð¿Ð°Ð¿ÐºÐ°", + "rootFolder": "ÐšÐ¾Ñ€Ð½ÐµÐ²Ð°Ñ Ð¿Ð°Ð¿ÐºÐ°", + "modifiedAt": "Изменено", + "createdAt": "Создано", + "statisticAt": "СтатиÑтика на", + "musicPlayer": "Ðудиоплеер", + "closeAndStop": "Закрыть и оÑтановить", + "playInBackground": "ВоÑпроизводить в фоне", + "copyTo": "Копировать в", + "copyToDst": "Копировать в <0>", + "moveTo": "ПеремеÑтить в", + "moveToDst": "ПеремеÑтить в <0>", + "errorReadFileContent": "Ðе удалоÑÑŒ прочитать Ñодержимое файла: {{msg}}", + "wordWrap": "ÐŸÐµÑ€ÐµÐ½Ð¾Ñ Ñтрок", + "pdfLoadingError": "Ошибка загрузки PDF: {{msg}}", + "subtitleSwitchTo": "Субтитры переключены на: {{subtitle}}", + "noSubtitleAvailable": "Ð’ папке Ñ Ð²Ð¸Ð´ÐµÐ¾ нет доÑтупных файлов Ñубтитров (поддерживаютÑÑ: ASS/SRT/VTT)", + "subtitle": "Выбрать Ñубтитры", + "playlist": "ПлейлиÑÑ‚", + "openInExternalPlayer": "Открыть во внешнем плеере", + "repeatMode": "Режим повтора", + "listRepeat": "Повтор ÑпиÑка", + "singleRepeat": "Повтор трека", + "shuffle": "Случайное воÑпроизведение", + "playbackSpeed": "СкороÑть воÑпроизведениÑ", + "searchResult": "Результаты поиÑка", + "preparingBathDownload": "Подготовка пакетной загрузки...", + "preparingDownload": "Подготовка к загрузке...", + "browserDownload": "Загрузка браузером в локальную папку", + "browserDownloadDescription": "Браузер загружает файлы один за другим и ÑохранÑет Ñтруктуру папок в указанную вами локальную папку.", + "browserBatchDownload": "Упаковка браузером", + "browserBatchDownloadDescription": "Браузер в реальном времени загружает и упаковывает в Zip-файл, не может обработать данные больше 4ГБ.", + "serverBatchDownload": "Упаковка Ñервером Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡ÐµÐ¹", + "serverBatchDownloadDescription": "Сервер упаковывает в Zip-файл и отправлÑет клиенту Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸ в реальном времени, не поддерживает Ñрлыки публикаций.", + "selectArchiveMethod": "Выберите ÑпоÑоб пакетной загрузки", + "batchDownloadStarted": "ÐŸÐ°ÐºÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ° началаÑÑŒ, пожалуйÑта, не закрывайте Ñту Ñтраницу...", + "batchDownloadError": "Ошибка при упаковке: {{msg}}", + "userDenied": "Пользователь отклонил", + "directoryDownloadReplace": "Заменить Ñтот файл", + "directoryDownloadReplaceDescription": "Локальный \"{{name}}\" будет перезапиÑан", + "directoryDownloadSkip": "ПропуÑтить Ñтот файл", + "directoryDownloadSkipDescription": "\"{{name}}\" будет пропущен", + "selectDirectoryDuplicationMethod": "Дублирование файла", + "directoryDownloadReplaceAll": "Заменить Ñтот файл и вÑе поÑледующие одноимённые файлы", + "directoryDownloadReplaceAllDescription": "Локальный \"{{name}}\" будет перезапиÑан, и выбор будет запомнен", + "directoryDownloadSkipAll": "ПропуÑтить Ñтот файл и вÑе поÑледующие одноимённые файлы", + "directoryDownloadSkipAllDescription": "\"{{name}}\" будет пропущен, и выбор будет запомнен", + "directoryDownloadStarted": "Загрузка началаÑÑŒ, пожалуйÑта, не закрывайте Ñту вкладку", + "directoryDownloadFinished": "Загрузка завершена, нет неудачных объектов", + "directoryDownloadFinishedWithError": "Загрузка завершена, неудачно {{failed}} объектов", + "directoryDownloadPermissionError": "Ðет Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð½Ð° операцию, пожалуйÑта, разрешите чтение и запиÑÑŒ локальных файлов", + "back": "Ðазад", + "view": "ПроÑмотр", + "layout": "Макет", + "thumbnails": "Миниатюры", + "on": "Включено", + "off": "Выключено", + "viewSetting": "ÐаÑтройки проÑмотра", + "saved": "Сохранено", + "notSet": "Ðе уÑтановлено", + "deleteViewSetting": "Удалить наÑтройки проÑмотра" + }, + "modals": { + "includePasswordInShareLink": "Включить пароль в ÑÑылку на публикацию", + "includePasswordInShareLinkDes": "ЕÑли отмечено, пароль будет включён в ÑÑылку на публикацию, и пароль не потребуетÑÑ Ð¿Ñ€Ð¸ доÑтупе по Ñтой ÑÑылке.", + "showFileName": "Показать Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°", + "forceDownload": "ÐŸÑ€Ð¸Ð½ÑƒÐ´Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ°", + "archiveFile": "Ðрхивный файл", + "cancelDownload": "Отменить загрузку", + "always": "Ð’Ñегда", + "justOnce": "Только один раз", + "quality": "КачеÑтво", + "saveAsOtherFormat": "Сохранить в другом формате", + "conflictDes1": "Конфликт верÑий файла, возможные причины:", + "conflictDes2": "<0>Файл был обновлён до новой верÑии из другого меÑта поÑле того, как вы его открыли.<1>ЕÑли вы Ñохранили его Ñ Ð½Ð¾Ð²Ñ‹Ð¼ именем или в новом меÑте, возможно, файл Ñ Ñ‚Ð°ÐºÐ¸Ð¼ именем уже ÑущеÑтвует.", + "saveAs": "Сохранить как", + "versionConflict": "Конфликт верÑий", + "overwrite": "ПерезапиÑать", + "editShareLink": "Редактировать ÑÑылку на публикацию", + "clearPermissions": "ОчиÑтить наÑтройки разрешений", + "shortcutCreated": "Ярлык Ñоздан", + "createShortcut": "Создать Ñрлык", + "createShortcutTo": "Создать Ñрлык в <0>", + "read": "ПроÑмотр", + "readDes": "Ð”Ð»Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð² позволÑет проÑматривать их Ñодержимое, метаданные и другую подробную информацию; Ð´Ð»Ñ Ð¿Ð°Ð¿Ð¾Ðº позволÑет проÑматривать ÑпиÑок дочерних файлов и их метаданные.", + "createDes": "ДейÑтвует только Ð´Ð»Ñ Ð¿Ð°Ð¿Ð¾Ðº, позволÑет Ñоздавать или загружать новые файлы в неё, а также перемещать или копировать файлы в неё.", + "update": "Изменение", + "updateDes": "Можно изменÑть метаданные, переименовывать объекты, проÑматривать журнал активноÑти; Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð² можно обновлÑть их Ñодержимое.", + "delete": "Удаление", + "deleteDes": "Можно удалÑть объекты или перемещать их в другое меÑто.", + "noAccess": "Ðет доÑтупа", + "targetExisted": "Цель уже ÑущеÑтвует", + "explicitAccess": "Точные права доÑтупа", + "generalAccess": "Общие права доÑтупа", + "users": "Пользователи", + "groups": "Группы пользователей", + "builtinCollections": "Ð’Ñтроенные коллекции", + "everyone": "Ð’Ñе оÑтальные", + "otherGroup": "Другие группы пользователей", + "sameGroup": "Та же группа пользователей, что и Ñ", + "anonymous": "Ðнонимные поÑетители", + "noResults": "Ðет результатов", + "searchGroupUser": "ПоиÑк пользователей или групп пользователей...", + "resetToDefault": "СброÑить к значениÑм по умолчанию", + "duplicateTag": "Тег \"{{tag}}\" уже ÑущеÑтвует", + "colorForTag": "ÐаÑтроить цвет нового тега", + "enterForNewTag": "Ðажмите Enter Ð´Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ тега", + "manageTags": "Управление тегами", + "onlyOwner": "Только владелец файла может принудительно разблокировать его", + "forceUnlock": "ÐŸÑ€Ð¸Ð½ÑƒÐ´Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñ€Ð°Ð·Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²ÐºÐ°", + "forceUnlockAll": "Принудительно разблокировать вÑÑ‘", + "forceUnlockDes": "ÐŸÑ€Ð¸Ð½ÑƒÐ´Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñ€Ð°Ð·Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²ÐºÐ° может привеÑти к нарушению ÑоÑтоÑÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð°, рекомендуетÑÑ Ñначала дождатьÑÑ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾Ð³Ð¾ оÑÐ²Ð¾Ð±Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð°. Ð’Ñ‹ уверены, что хотите продолжить разблокировку?", + "webdav": "WebDAV", + "soft-delete": "ПеремеÑтить в корзину", + "updateMetadata": "Обновить метаданные", + "upload": "Загрузка", + "moveCopy": "Перемещение или копирование", + "view": "ПроÑмотр", + "cannotPerformAction": "Перемещение или копирование файлов Ñюда не поддерживаетÑÑ", + "cannotMoveCopyToChild": "ÐÐµÐ»ÑŒÐ·Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰Ð°Ñ‚ÑŒ или копировать в дочернюю папку", + "copySuccess": "УÑпешно Ñкопировано {{num}} файлов", + "moveSuccess": "УÑпешно перемещено {{num}} файлов", + "setPermission": "УÑтановить разрешениÑ", + "unknownParent": "ÐеизвеÑÑ‚Ð½Ð°Ñ Ñ€Ð¾Ð´Ð¸Ñ‚ÐµÐ»ÑŒÑÐºÐ°Ñ Ð¿Ð°Ð¿ÐºÐ°", + "unknownParentDes": "ЗанÑÑ‚Ð°Ñ Ð¿Ð°Ð¿ÐºÐ° ÑвлÑетÑÑ Ñ€Ð¾Ð´Ð¸Ñ‚ÐµÐ»ÑŒÑкой папкой общей папки, она вам не принадлежит", + "lockConflictTitle": "Файл занÑÑ‚", + "lockConflictDescription": "ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð½Ðµ может быть завершена, поÑкольку Ñледующие файлы в наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð¸ÑпользуютÑÑ, пожалуйÑта, повторите попытку позже. ЕÑли вы владелец файла и уверены, что файл не иÑпользуетÑÑ, вы можете принудительно разблокировать файл и повторить попытку.", + "usedBy": "ИÑпользуетÑÑ", + "application": "Приложение", + "errorDetailsTitle": "ПодробноÑти ошибки", + "processingMoving": "Перемещение файлов...", + "processingCopying": "Копирование файлов...", + "processingRestoring": "ВоÑÑтановление файлов...", + "fileRestored": "ВоÑÑтановлено {{num}} файлов в иÑходное меÑтоположение", + "duplicatedObjectName": "Ðовое Ð¸Ð¼Ñ Ñовпадает Ñ ÑущеÑтвующим файлом", + "newNameLengthError": "Длина имени файла должна быть от 1 до 255 Ñимволов", + "newNameCharacterError": "Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° не может Ñодержать Ñледующие Ñимволы: \\ / : * ? \" < > |", + "newNameDotError": "Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° не может быть \".\" или \"..\"", + "taskCreated": "Задача Ñоздана", + "taskCreateFailed": "Ðе удалоÑÑŒ Ñоздать {{failed}} задач: {{details}}", + "linkCopied": "СÑылка Ñкопирована", + "getSourceLinkTitle": "Получить прÑмую ÑÑылку на файл", + "sourceLink": "ПрÑÐ¼Ð°Ñ ÑÑылка на файл", + "folderName": "Ð˜Ð¼Ñ Ð¿Ð°Ð¿ÐºÐ¸", + "create": "Создать", + "fileName": "Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°", + "renameDescription": "Введите новое Ð¸Ð¼Ñ Ð´Ð»Ñ <0>{{name}}:", + "newName": "Ðовое имÑ", + "moveToDescription": "ПеремеÑтить в <0>{{name}}", + "saveToTitle": "Сохранить в", + "saveToTitleDescription": "Сохранить в <0>{{name}}", + "deleteTitle": "Удалить объекты", + "deleteOneDescription": "Ð’Ñ‹ уверены, что хотите перемеÑтить <0>{{name}} в корзину?", + "deleteMultipleDescription": "Ð’Ñ‹ уверены, что хотите перемеÑтить Ñти {{num}} объектов в корзину?", + "deleteOneDescriptionHard": "Ð’Ñ‹ уверены, что хотите навÑегда удалить <0>{{name}}?", + "trashRetention": "Файлы в корзине будут автоматичеÑки удалены через <0>{{num}}.", + "deleteMultipleDescriptionHard": "Ð’Ñ‹ уверены, что хотите навÑегда удалить Ñти {{num}} объектов?", + "newRemoteDownloadTitle": "Создать новую задачу удалённой загрузки", + "remoteDownloadURL": "СÑылка Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸", + "remoteDownloadURLDescription": "Введите Ð°Ð´Ñ€ÐµÑ Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸ файла, по одному в Ñтроке", + "remoteDownloadDst": "Загрузить в", + "processNode": "Узел обработки", + "remoteDownloadNodeAuto": "ÐвтоматичеÑкое назначение", + "createTask": "Создать задачу", + "downloadToDst": "Загрузить в <0>{{name}}", + "downloadTo": "Загрузить в", + "decompressTo": "Извлечь в", + "decompressToDst": "Извлечь в <0>{{name}}", + "defaultEncoding": "По умолчанию", + "chineseMajorEncoding": "РаÑпроÑтранённые кодировки упрощённого китайÑкого", + "selectEncoding": "Кодировка ZIP-файла", + "password": "Пароль архива", + "passwordDescription": "ЕÑли архив не зашифрован, оÑтавьте Ñто поле пуÑтым.", + "noEncodingSelected": "СпоÑоб ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð½Ðµ выбран", + "listingFiles": "Получение ÑпиÑка файлов...", + "listingFileError": "Ошибка при получении ÑпиÑка файлов: {{message}}", + "generatingSourceLinks": "Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ð²Ð½ÐµÑˆÐ½Ð¸Ñ… ÑÑылок...", + "noFileCanGenerateSourceLink": "Ðет файлов, Ð´Ð»Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… можно Ñоздать внешние ÑÑылки", + "sourceBatchSizeExceeded": "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° пользователей может одновременно Ñоздать внешние ÑÑылки макÑимум Ð´Ð»Ñ {{limit}} файлов", + "zipFileName": "Ð˜Ð¼Ñ Ð°Ñ€Ñ…Ð¸Ð²Ð½Ð¾Ð³Ð¾ файла", + "shareLinkShareContent": "Я поделилÑÑ Ñ Ð²Ð°Ð¼Ð¸: {{name}} СÑылка: {{link}}", + "shareLinkPasswordInfo": " Пароль: {{password}}", + "createShareLink": "Создать ÑÑылку на публикацию", + "privateShare": "Защитить ÑÑылку паролем", + "privateShareDes": "ЕÑли отмечено, Ð´Ð»Ñ Ð´Ð¾Ñтупа к ÑÑылке на публикацию потребуетÑÑ Ð¿Ð°Ñ€Ð¾Ð»ÑŒ.", + "useCustomPassword": "ÐаÑтроить пароль Ð´Ð»Ñ Ð¿ÑƒÐ±Ð»Ð¸ÐºÐ°Ñ†Ð¸Ð¸", + "expireAfterDownload": "ÐвтоматичеÑки иÑтекает поÑле загрузки", + "sharePassword": "Пароль Ð´Ð»Ñ Ð¿ÑƒÐ±Ð»Ð¸ÐºÐ°Ñ†Ð¸Ð¸", + "randomlyGenerate": "Ð¡Ð»ÑƒÑ‡Ð°Ð¹Ð½Ð°Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ", + "expireAutomatically": "ÐвтоматичеÑкое иÑтечение по времени", + "downloadLimitOptions": "{{num}} загрузок", + "or": "Или", + "5minutes": "5 минут", + "1hour": "1 чаÑ", + "1day": "1 день", + "7days": "7 дней", + "30days": "30 дней", + "custom": "ÐаÑтраиваемый", + "minutes": "минут", + "downloads": "загрузок", + "expirePrefix": "", + "expireSuffix": "иÑтекает через", + "allowPreview": "Разрешить предварительный проÑмотр", + "allowPreviewDescription": "Разрешить ли предварительный проÑмотр Ñодержимого файла на Ñтранице публикации", + "shareLink": "СÑылка на публикацию", + "sendLink": "Отправить ÑÑылку", + "directoryDownloadReplaceNotifiction": "ПерезапиÑан {{name}}", + "directoryDownloadSkipNotifiction": "Пропущен {{name}}", + "directoryDownloadTitle": "Журнал пакетной загрузки", + "directoryDownloadStarted": "Ðачата загрузка \"{{name}}\"", + "directoryDownloadFinished": "Завершена загрузка \"{{name}}\"", + "directoryDownloadError": "Произошла ошибка: {{msg}}", + "directoryDownloadErrorNotification": "Ошибка при загрузке {{name}}: {{msg}}", + "directoryDownloadAutoscroll": "Ðвтопрокрутка", + "directoryDownloadCancelled": "Загрузка отменена", + "advanceOptions": "Дополнительные параметры", + "skipSoftDelete": "ПолноÑтью удалить файлы", + "skipSoftDeleteDes": "ПропуÑтить корзину, удалить файлы напрÑмую", + "unlinkOnly": "Сохранить физичеÑкие файлы", + "unlinkOnlyDes": "Удалить только запиÑи о файлах, физичеÑкие файлы не будут удалены", + "shareView": "ÐаÑтройки проÑмотра публикации", + "shareViewDes": "ЕÑли отмечено, другие пользователи при доÑтупе к Ñтой общей папке Ñмогут видеть ваши наÑтройки проÑмотра, Ñохранённые на Ñервере (макет, Ñортировка и Ñ‚.д.).", + "showReadme": "Показать файл README", + "showReadmeDes": "ЕÑли отмечено, автоматичеÑки отобразит файл <0>README.md (Ñ ÑƒÑ‡Ñ‘Ñ‚Ð¾Ð¼ региÑтра) в папке Ð´Ð»Ñ Ð¿Ð¾Ñетителей.", + "viewSetting": "ÐаÑтройки проÑмотра", + "saved": "Сохранено", + "notSet": "Ðе уÑтановлено", + "deleteViewSetting": "Удалить наÑтройки проÑмотра" + }, + "uploader": { + "fileCopyName": "КопиÑ_", + "overwriteTooltip": "ПерезапиÑать ÑущеÑтвующий файл при конфликте имён, дейÑтвует только Ð´Ð»Ñ Ð²Ð½Ð¾Ð²ÑŒ добавленных задач", + "rename": "Повторить Ñ Ð½Ð¾Ð²Ñ‹Ð¼ именем", + "overwrite": "ПерезапиÑать ÑущеÑтвующий файл", + "pasteFilesHere": "Ð’Ñтавить файлы Ñюда", + "clipboardDefaultFileName": "Буфер обмена {{date}}.png", + "uploadFromClipboard": "Загрузить из буфера обмена", + "uploadList": "СпиÑок загрузки", + "fileNotMatchError": "Выбранный файл не ÑоответÑтвует иÑходному файлу", + "unknownError": "Произошла неизвеÑÑ‚Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°: {{msg}}", + "taskListEmpty": "Ðет задач загрузки", + "hideTaskList": "Скрыть ÑпиÑок", + "uploadTasks": "Очередь загрузки", + "moreActions": "Больше дейÑтвий", + "addNewFiles": "Добавить новые файлы", + "toggleTaskList": "Развернуть/Ñвернуть очередь", + "pendingInQueue": "Ð’ очереди...", + "preparing": "Подготовка...", + "processing": "Обработка...", + "progressDescription": "Загружено {{uploaded}}, вÑего {{total}} - {{percentage}}%", + "progressDescriptionFull": "{{speed}} Загружено {{uploaded}}, вÑего {{total}} - {{percentage}}%", + "progressDescriptionPlaceHolder": "Загружено - ", + "uploaded": "Загружено", + "rootFolder": "ÐšÐ¾Ñ€Ð½ÐµÐ²Ð°Ñ Ð¿Ð°Ð¿ÐºÐ°", + "unknownStatus": "ÐеизвеÑтно", + "resumed": "Возобновление Ñ Ñ‚Ð¾Ñ‡ÐºÐ¸ оÑтановки", + "resumable": "Можно воÑÑтановить прогреÑÑ", + "retry": "Повторить", + "deleteTask": "Удалить запиÑÑŒ о задаче", + "cancelAndDelete": "Отменить и удалить", + "selectAndResume": "Выбрать тот же файл и возобновить загрузку", + "fileName": "Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°:", + "fileSize": "Размер файла:", + "sessionExpiredIn": "<0> иÑтекает", + "chunkDescription": "({{total}} фрагментов, по {{size}} каждый)", + "noChunks": "(Без фрагментов)", + "destination": "МеÑто хранениÑ:", + "storagePolicy": "Политика хранениÑ:", + "uploadSession": "СеÑÑÐ¸Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸:", + "errorDetails": "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ð± ошибке:", + "uploadSessionCleaned": "СеÑÑÐ¸Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸ очищена", + "hideCompletedTooltip": "Ðе показывать в ÑпиÑке завершённые, неудачные, отменённые задачи", + "hideCompleted": "Скрыть завершённые задачи", + "addTimeAscTooltip": "Задачи, добавленные первыми, отображаютÑÑ Ð¿ÐµÑ€Ð²Ñ‹Ð¼Ð¸", + "addTimeAsc": "Первые добавленные впереди", + "addTimeDescTooltip": "Задачи, добавленные поÑледними, отображаютÑÑ Ð¿ÐµÑ€Ð²Ñ‹Ð¼Ð¸", + "addTimeDesc": "ПоÑледние добавленные впереди", + "showInstantSpeedTooltip": "СкороÑть загрузки отдельной задачи отображаетÑÑ ÐºÐ°Ðº Ð¼Ð³Ð½Ð¾Ð²ÐµÐ½Ð½Ð°Ñ ÑкороÑть", + "showInstantSpeed": "ÐœÐ³Ð½Ð¾Ð²ÐµÐ½Ð½Ð°Ñ ÑкороÑть", + "showAvgSpeedTooltip": "СкороÑть загрузки отдельной задачи отображаетÑÑ ÐºÐ°Ðº ÑреднÑÑ ÑкороÑть", + "showAvgSpeed": "СреднÑÑ ÑкороÑть", + "cleanAllSessionTooltip": "ОчиÑтить вÑе незавершённые ÑеÑÑии загрузки на Ñтороне Ñервера", + "cleanAllSession": "ОчиÑтить вÑе ÑеÑÑии загрузки", + "cleanCompletedTooltip": "ОчиÑтить завершённые, неудачные, отменённые задачи из ÑпиÑка", + "cleanCompleted": "ОчиÑтить завершённые задачи", + "retryFailedTasks": "Повторить вÑе неудачные задачи", + "retryFailedTasksTooltip": "Повторить вÑе неудачные задачи в очереди", + "setConcurrentTooltip": "УÑтановить количеÑтво одновременно выполнÑемых задач", + "setConcurrent": "УÑтановить количеÑтво параллельных задач", + "sizeExceedLimitError": "Размер файла превышает Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ¸ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ (макÑимум: {{max}})", + "suffixNotAllowedError": "Политика Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð½Ðµ поддерживает загрузку файлов Ñ Ñтим раÑширением", + "regexpNotAllowedError": "Политика Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð½Ðµ поддерживает загрузку файлов Ñ Ñ‚Ð°ÐºÐ¸Ð¼ именем", + "suffixAllowed": "(Поддерживаемые раÑширениÑ: {{supported}})", + "suffixDenied": "(Запрещённые раÑширениÑ: {{denied}})", + "createUploadSessionError": "Ðе удалоÑÑŒ Ñоздать ÑеÑÑию загрузки", + "deleteUploadSessionError": "Ðе удалоÑÑŒ удалить ÑеÑÑию загрузки", + "requestError": "Ошибка запроÑа: {{msg}} ({{url}})", + "chunkUploadError": "Ðе удалоÑÑŒ загрузить фрагмент [{{index}}]", + "conflictError": "Задача загрузки файла Ñ Ñ‚Ð°ÐºÐ¸Ð¼ именем уже обрабатываетÑÑ", + "chunkUploadErrorWithMsg": "Ошибка загрузки фрагмента: {{msg}}", + "chunkUploadErrorWithRetryAfter": "(ПожалуйÑта, повторите через {{retryAfter}} Ñекунд)", + "emptyFileError": "Загрузка пуÑтых файлов в OneDrive временно не поддерживаетÑÑ, пожалуйÑта, иÑпользуйте кнопку ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿ÑƒÑтых файлов", + "finishUploadError": "Ðе удалоÑÑŒ завершить загрузку файла", + "finishUploadErrorWithMsg": "Ðе удалоÑÑŒ завершить загрузку файла: {{msg}}", + "ossFinishUploadError": "Ðе удалоÑÑŒ завершить загрузку файла: {{msg}} ({{code}})", + "cosUploadFailed": "Ошибка загрузки: {{msg}} ({{code}})", + "upyunUploadFailed": "Ошибка загрузки: {{msg}}", + "parseResponseError": "Ðе удалоÑÑŒ разобрать ответ: {{msg}} ({{content}})", + "concurrentTaskNumber": "КоличеÑтво одновременно загружаемых задач", + "dropFileHere": "ОтпуÑтите мышь Ð´Ð»Ñ Ð½Ð°Ñ‡Ð°Ð»Ð° загрузки" + }, + "share": { + "free": "БеÑплатно", + "price": "Цена", + "points": "{{num}} очков", + "statistics": "СтатиÑтика", + "expireAt": "<0> иÑтекает", + "expireAfterDownloads": "ИÑтекает поÑле {{downloads}} загрузок", + "somebodyShare": "ÐŸÑƒÐ±Ð»Ð¸ÐºÐ°Ñ†Ð¸Ñ {{name}}", + "expiredLink": "ÐедейÑÑ‚Ð²Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¿ÑƒÐ±Ð»Ð¸ÐºÐ°Ñ†Ð¸Ñ", + "sharedBy": "<0>{{nick}} поделилÑÑ Ñ Ð²Ð°Ð¼Ð¸ {{num}} файлами", + "files": "1 файл", + "files_other": "{{count}} файлов", + "statisticsViews": "{{views}} проÑмотров", + "statisticsDownloads": "{{downloads}} загрузок", + "views": "{{count}} проÑмотр", + "views_other": "{{count}} проÑмотров", + "downloads": "{{count}} загрузка", + "downloads_other": "{{count}} загрузок", + "privateShareTitle": "Ð—Ð°ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð¿ÑƒÐ±Ð»Ð¸ÐºÐ°Ñ†Ð¸Ñ {{nick}}", + "enterPassword": "Пароль публикации", + "continue": "Продолжить", + "shareCanceled": "СÑылка на публикацию удалена", + "listLoadingError": "Ошибка загрузки", + "sharedFiles": "Мои публикации", + "createdAtDesc": "Ðовейшие", + "createdAtAsc": "Самые ранние", + "noRecords": "Ðет запиÑей о публикациÑÑ….", + "sourceNotFound": "[ИÑходный объект не ÑущеÑтвует]", + "expired": "ÐедейÑтвительна", + "changeToPublic": "Сделать публичной", + "changeToPrivate": "Сделать приватной", + "viewPassword": "ПоÑмотреть пароль", + "disablePreview": "Запретить предварительный проÑмотр", + "enablePreview": "Разрешить предварительный проÑмотр", + "cancelShare": "Отменить публикацию", + "sharePassword": "Пароль публикации", + "readmeError": "Ðе удалоÑÑŒ прочитать Ñодержимое README: {{msg}}", + "enterKeywords": "ПожалуйÑта, введите ключевые Ñлова Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка", + "searchResult": "Результаты поиÑка", + "sharedAt": "Опубликовано <0>", + "pleaseLogin": "ПожалуйÑта, Ñначала войдите в ÑиÑтему", + "cannotShare": "Этот файл не может быть предварительно проÑмотрен", + "preview": "Предварительный проÑмотр", + "incorrectPassword": "Ðеверный пароль", + "shareNotExist": "ÐŸÑƒÐ±Ð»Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð½Ðµ ÑущеÑтвует или иÑтекла", + "copyLinkToClipboard": "Скопировать ÑÑылку в буфер обмена" + }, + "download": { + "noFilesFound": "Файлы не найдены", + "filterByName": "Фильтр по имени", + "selectAll": "Выбрать вÑÑ‘", + "reverseSelect": "Обратный выбор", + "cancelTaskConfirm": "Ð’Ñ‹ уверены, что хотите отменить Ñту задачу?", + "saveChanges": "Сохранить изменениÑ", + "failedToLoad": "Ошибка загрузки", + "active": "Ðктивные", + "finished": "Завершённые", + "activeEmpty": "Ðет активных задач загрузки", + "finishedEmpty": "Ðет завершённых задач загрузки", + "loadMore": "Загрузить ещё", + "taskFileDeleted": "Файл удалён", + "unknownTaskName": "[ÐеизвеÑтно]", + "taskCanceled": "Задача отменена, ÑÑ‚Ð°Ñ‚ÑƒÑ Ð±ÑƒÐ´ÐµÑ‚ обновлён позже", + "operationSubmitted": "ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð° уÑпешно, ÑÑ‚Ð°Ñ‚ÑƒÑ Ð±ÑƒÐ´ÐµÑ‚ обновлён позже", + "deleteThisFile": "Удалить Ñтот файл", + "openDstFolder": "Открыть папку назначениÑ", + "selectDownloadingFile": "Выбрать файлы Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸", + "cancelTask": "Отменить задачу", + "updatedAt": "Обновлено:", + "uploaded": "Размер загрузки", + "uploadSpeed": "СкороÑть загрузки", + "InfoHash": "InfoHash", + "seederCount": "Сидеры:", + "seeding": "Раздача:", + "downloadNode": "Узел:", + "isSeeding": "Да", + "notSeeding": "Ðет", + "chunkSize": "Размер фрагмента:", + "chunkNumbers": "КоличеÑтво фрагментов", + "taskDeleted": "Задача удалена", + "transferFailed": "Ðе удалоÑÑŒ перенеÑти файлы", + "downloadFailed": "Ошибка загрузки: {{msg}}", + "canceledStatus": "Отменено", + "finishedStatus": "Завершено", + "pending": "Завершено, Ð¿ÐµÑ€ÐµÐ½Ð¾Ñ Ð² очереди", + "transferring": "ПереноÑ", + "deleteRecord": "Удалить запиÑÑŒ", + "createdAt": "Дата ÑозданиÑ:", + "unknownSize": "ÐеизвеÑтный размер файла" + }, + "setting": { + "notifyStoragePolicyChange": "УведомлÑть Ð¼ÐµÐ½Ñ Ð¿Ñ€Ð¸ переключении на папки Ñ Ñ€Ð°Ð·Ð½Ð¾Ð¹ политикой хранениÑ", + "notifyStoragePolicyChangeDes": "При включении будет поÑвлÑтьÑÑ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ðµ при входе в папку, привÑзанную к другой политике хранениÑ.", + "treeView": "Древовидный вид", + "autoExpandTreeView": "ÐвтоматичеÑки разворачивать древовидный вид", + "autoExpandTreeViewDes": "При включении дерево файлов в боковой панели будет Ñледовать за текущей папкой и автоматичеÑки разворачиватьÑÑ.", + "syncView": "ÐаÑтройки проÑмотра", + "syncViewDes": "Запоминать ли наÑтройки проÑмотра каждой папки и Ñинхронизировать их Ñ Ñервером.", + "syncViewOn": "Синхронизировать Ñ Ñервером", + "syncViewOff": "Ðе Ñинхронизировать", + "reason": "Причина", + "change": "Изменение", + "success": "УÑпешно", + "loginWithPasskey": "Ключ доÑтупа - {{name}}", + "loginWith": "СпоÑоб входа", + "result": "Результат", + "device": "УÑтройÑтво", + "ip": "IP", + "time": "ВремÑ", + "recentSignIn": "Ðедавние входы в ÑиÑтему", + "noAuthenticator": "Добавьте ключ доÑтупа Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ð° Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ лица, отпечатка пальца или USB-ключа", + "neverUsed": "Ðикогда не иÑпользовалÑÑ", + "usedAt": "ПоÑледнее иÑпользование <0>", + "passkeyName": "{browser} на {os}", + "passwordlessHint": "Этот аккаунт ÑвлÑетÑÑ Ð±ÐµÑпарольным", + "versionRetentionMax": "МакÑимальное количеÑтво верÑий, 0 означает без ограничений", + "versionRetentionEnabledExt": "Включённые раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²", + "versionRetentionEnabledExtDes": "Ðажмите Enter Ð´Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ, оÑтавьте пуÑтым Ð´Ð»Ñ Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð²Ñех файлов", + "enableVersionRetention": "Включить Ñохранение верÑий", + "enableVersionRetentionDes": "При включении Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð², ÑоответÑтвующих уÑловиÑм, ÑиÑтема будет ÑохранÑть их иÑторичеÑкие верÑии", + "versionRetention": "Сохранение верÑий", + "languageDes": "УÑтановить Ñзык Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¸ предпочитаемый Ñзык Ñлектронной почты", + "timezoneDes": "УÑтановить чаÑовой поÑÑ Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ, по умолчанию Ñледует ÑиÑтемному чаÑовому поÑÑу", + "unlinkConfirm": "Ð’Ñ‹ уверены, что хотите отвÑзать ÑвÑзь Ñ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð¾Ð¼?", + "notLinked": "Ðе привÑзано", + "linkedAt": "ПривÑзано <0>", + "accountLinking": "СвÑзывание аккаунта", + "nickNameDes": "Ð˜Ð¼Ñ Ð´Ð»Ñ Ð¿ÑƒÐ±Ð»Ð¸Ñ‡Ð½Ð¾Ð³Ð¾ отображениÑ, можно иÑпользовать наÑтоÑщее Ð¸Ð¼Ñ Ð¸Ð»Ð¸ пÑевдоним", + "cropAvatar": "Обрезать аватар", + "finance": "ФинанÑÑ‹", + "preference": "ПредпочтениÑ", + "accountCreatedAt": "Создано <0>", + "shoeQr": "Показать", + "deviceNothing": "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° пользователей не поддерживает WebDAV", + "connectionInfo": "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ подключении", + "proxyTooltip": "ПрокÑировать вÑе запроÑÑ‹ на загрузку файлов через Ñервер.", + "readonlyTooltip": "Пользователь может только читать файлы через Ñтот аккаунт.", + "blockSysFilesUpload": "Блокировать загрузку ÑиÑтемных файлов", + "blockSysFilesUploadTooltip": "При включении файлы, начинающиеÑÑ Ñ <0>., будут заблокированы Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸.", + "rootFolderIn": "Выбрать <0>", + "createWebDavAccount": "Создать аккаунт WebDAV", + "editWebDavAccount": "Редактировать {{name}}", + "seeding": "Раздача", + "awaitSeeding": "Ожидание раздачи", + "awaitSeedingDes": "Ожидание Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ð´Ð°Ñ‡Ð¸ задачи загрузки.", + "downloadTransferDes": "ПеренеÑти файлы в меÑто назначениÑ.", + "downloadDes": "Загрузить указанные файлы.", + "retryErrorHistory": "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð¾ÑˆÐ¸Ð±Ð¾Ðº повторных попыток", + "retryCount": "КоличеÑтво повторных попыток", + "resumeAt": "Следующее возобновление выполнениÑ", + "executeDuration": "ЧиÑтое Ð²Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ", + "input": "Ввод", + "output": "Вывод", + "suspended": " (ПриоÑтановлено)", + "updatedAt": "Обновлено", + "taskDetails": "ПодробноÑти задачи", + "partialSuccessWarning": "{{num}} объектов не удалоÑÑŒ обработать, они были пропущены.", + "sendTask": "Отправить задачу", + "sendTaskDes": "Отправить задачу на узел обработки.", + "downloaded": "Загружено", + "importingFiles": "Импорт файлов", + "importingFilesDes": "Извлечение файлов и импорт их в указанную папку.", + "importedFiles": "Импортированные файлы", + "indexedFiles": "ПроиндекÑированные файлы", + "extractedFiles": "Извлечённые файлы", + "extractedFilesSize": "Размер извлечённых файлов", + "extractingFiles": "Извлечение файлов", + "extractingFilesDes": "Извлечь вÑе файлы в указанную папку.", + "downloadingZip": "Получение архива", + "downloadingZipDes": "Загрузить архив во временную рабочую облаÑть.", + "progressNotAvailable": "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ прогреÑÑе пока недоÑтупна", + "uploadedSize": "ПеренеÑённые файлы", + "archivedFiles": "Обработанные файлы", + "transferredFiles": "ПеренеÑённые файлы", + "archivedFilesSize": "Размер обработанных файлов", + "createArchiveFinishing": "Подтверждение изменений новых файлов.", + "indexForArchiveDes": "Извлечение вÑех файлов Ð´Ð»Ñ Ð°Ñ€Ñ…Ð¸Ð²Ð°Ñ†Ð¸Ð¸.", + "prepare": "Подготовка", + "preparingWorkspaceDes": "Подготовка временной рабочей облаÑти.", + "compressFiles": "Создание архива", + "compressFilesDes": "Сжатие файлов во временную рабочую облаÑть.", + "uploadArchiveFileDes": "ÐŸÐµÑ€ÐµÐ½Ð¾Ñ Ð°Ñ€Ñ…Ð¸Ð²Ð½Ð¾Ð³Ð¾ файла в меÑто назначениÑ.", + "uploadWorker": "Поток загрузки #{{num}}", + "relocatedEntities": "ПеренеÑённые ÑущноÑти", + "queueToStart": "Ð’ очереди на запуÑк", + "indexingFiles": "Извлечение файлов", + "indexingFilesDes": "Извлечение вÑех файлов Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ½Ð¾Ñа и их блокировка.", + "transferring": "ПереноÑ", + "transferringRelocateDes": "ÐŸÐµÑ€ÐµÐ½Ð¾Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… в новую политику хранениÑ.", + "committingChanges": "Подтверждение изменений", + "relocateFinishing": "Обновление ÑÑылок ÑущноÑтей на новую политику хранениÑ.", + "autoRefresh": "Ðвтообновление", + "avatarUpdated": "Ðватар обновлён, отображение нового аватара может быть Ñ Ð·Ð°Ð´ÐµÑ€Ð¶ÐºÐ¾Ð¹", + "nickChanged": "ПÑевдоним изменён, вÑтупит в Ñилу поÑле обновлениÑ", + "settingSaved": "ÐаÑтройки Ñохранены", + "themeColorChanged": "Ð¦Ð²ÐµÑ‚Ð¾Ð²Ð°Ñ Ñхема темы изменена", + "profile": "Профиль", + "avatar": "Ðватар", + "uid": "UID", + "nickname": "ПÑевдоним", + "group": "Группа пользователей", + "regTime": "Ð’Ñ€ÐµÐ¼Ñ Ñ€ÐµÐ³Ð¸Ñтрации", + "security": "Пароль и безопаÑноÑть", + "profilePage": "Ð›Ð¸Ñ‡Ð½Ð°Ñ Ñтраница", + "publicShareOnly": "Показывать только публикации без паролÑ", + "publicShareOnlyDes": "Показывать на личной Ñтранице только ÑÑылки на публикации без паролÑ.", + "allShare": "Ð’Ñе публикации", + "allShareDes": "Показывать на личной Ñтранице вÑе ÑÑылки на публикации (Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ Ð·Ð°Ñ‰Ð¸Ñ‰Ñ‘Ð½Ð½Ñ‹Ðµ паролем). Ð”Ð»Ñ Ð·Ð°Ñ‰Ð¸Ñ‰Ñ‘Ð½Ð½Ñ‹Ñ… паролем публикаций пользователÑм вÑÑ‘ равно нужно будет вводить пароль Ð´Ð»Ñ Ð´Ð¾Ñтупа.", + "hideShare": "Скрыть вÑе ÑÑылки на публикации", + "hideShareDes": "Скрыть вÑе ÑÑылки на публикации на личной Ñтранице.", + "userHideShare": "Пользователь Ñкрыл ÑпиÑок ÑÑылок на публикации", + "accountPassword": "Пароль Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ð°", + "2fa": "Ð”Ð²ÑƒÑ…Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð°Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ", + "enabled": "Включено", + "disabled": "Ðе включено", + "appearance": "ПерÑонализациÑ", + "themeColor": "Ð¦Ð²ÐµÑ‚Ð¾Ð²Ð°Ñ Ñхема темы", + "darkMode": "Тёмный режим", + "syncWithSystem": "СиÑтемный", + "fileList": "СпиÑок файлов", + "timeZone": "ЧаÑовой поÑÑ", + "webdavServer": "ÐÐ´Ñ€ÐµÑ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ", + "userName": "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ", + "manageAccount": "Управление аккаунтами", + "uploadImage": "Загрузить из файла", + "useGravatar": "ИÑпользовать аватар Gravatar", + "changeNick": "Изменить пÑевдоним", + "originalPassword": "ИÑходный пароль", + "enable2FA": "Включить двухфакторную аутентификацию", + "disable2FA": "Отключить двухфакторную аутентификацию", + "2faDescription": "ПожалуйÑта, иÑпользуйте любое приложение Ð´Ð»Ñ Ð´Ð²ÑƒÑ…Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð¾Ð¹ аутентификации или программу Ð´Ð»Ñ ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñми, поддерживающую двухфакторную аутентификацию, чтобы отÑканировать QR-код и добавить Ñтот Ñайт. ПоÑле ÑÐºÐ°Ð½Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð²Ð²ÐµÐ´Ð¸Ñ‚Ðµ 6-значный код проверки, предоÑтавленный приложением двухфакторной аутентификации, чтобы включить двухфакторную аутентификацию.", + "inputCurrent2FACode": "Введите текущий 6-значный код проверки, предоÑтавленный приложением двухфакторной аутентификации:", + "timeZoneCode": "Идентификатор чаÑового поÑÑа IANA", + "authenticatorRemoved": "Учётные данные удалены", + "authenticatorAdded": "Ðутентификатор добавлен", + "browserNotSupported": "Текущий браузер или Ñреда не поддерживаетÑÑ", + "removedAuthenticator": "Удалить учётные данные", + "removedAuthenticatorConfirm": "Ð’Ñ‹ уверены, что хотите отозвать Ñти учётные данные?", + "addNewAuthenticator": "Добавить новые учётные данные", + "hardwareAuthenticator": "Ключ доÑтупа", + "copied": "Скопировано в буфер обмена", + "pleaseManuallyCopy": "Текущий браузер не поддерживаетÑÑ, пожалуйÑта, Ñкопируйте вручную", + "webdavAccounts": "Управление аккаунтами WebDAV", + "webdavHint": "ÐÐ´Ñ€ÐµÑ WebDAV: {{url}}; Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ð° единое: {{name}}; Пароль - Ñто пароль Ñозданного аккаунта.", + "annotation": "Ðазвание Ð´Ð»Ñ Ð·Ð°Ð¼ÐµÑ‚Ð¾Ðº", + "rootFolder": "ОтноÑÐ¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ ÐºÐ¾Ñ€Ð½ÐµÐ²Ð°Ñ Ð¿Ð°Ð¿ÐºÐ°", + "createdAt": "Дата ÑозданиÑ", + "action": "Операции", + "readonlyOn": "Только чтение", + "readonlyOff": "Чтение и запиÑÑŒ", + "proxy": "Обратный прокÑи", + "none": "Ðет", + "proxied": "ПрокÑировано", + "delete": "Удалить", + "listEmpty": "Ðет запиÑей", + "createNewAccount": "Создать новый аккаунт", + "taskType": "Тип задачи", + "taskStatus": "СтатуÑ", + "taskProgress": "ПрогреÑÑ Ð·Ð°Ð´Ð°Ñ‡Ð¸", + "errorDetails": "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ð± ошибке", + "queueing": "Ð’ очереди", + "processing": "Обработка", + "failed": "Ðеудача", + "canceled": "Отмена", + "finished": "Завершено", + "fileTransfer": "Передача файлов", + "fileRecycle": "Переработка файлов", + "importFiles": "Импорт внешней папки", + "transferProgress": "Завершено {{num}} файлов", + "waiting": "Ожидание", + "compressing": "Сжатие", + "decompressing": "РаÑпаковка", + "downloading": "Загрузка", + "indexing": "ИндекÑациÑ", + "listing": "Ð’Ñтавка", + "allShares": "Ð’Ñе публикации", + "trendingShares": "ПопулÑрные публикации", + "totalShares": "Общее количеÑтво публикаций", + "fileName": "Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°", + "shareDate": "Дата публикации", + "downloadNumber": "КоличеÑтво загрузок", + "viewNumber": "КоличеÑтво проÑмотров", + "language": "Язык", + "iOSApp": "Клиент iOS/iPadOS", + "connectByiOS": "Подключение к <0>{{title}} через уÑтройÑтва iOS/iPadOS", + "downloadOurApp": "Скачайте и уÑтановите наше приложение:", + "fillInEndpoint": "ОтÑканируйте QR-код ниже Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ (другие Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÑÐºÐ°Ð½Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð½ÐµÐ´ÐµÐ¹Ñтвительны):", + "loginApp": "Завершите привÑзку, теперь вы можете начать иÑпользовать клиент. ЕÑли у Ð²Ð°Ñ Ð²Ð¾Ð·Ð½Ð¸ÐºÐ»Ð¸ проблемы Ñ Ð¿Ñ€Ð¸Ð²Ñзкой по QR-коду, вы также можете попробовать вручную ввеÑти Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸ пароль Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ð°.", + "relocateFileTo": "ПеренеÑти <0>{{more}} политику Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð² {{policy}}", + "extractFileTo": "Извлечь <0>{{more}} в <1>", + "createArchiveTo": "Упаковать <0>{{more}} в <1>", + "importFileTo": "Импортировать файлы из {{policy}} в <0>" + }, + "vas": { + "points": "Очки", + "paid": "Оплачено", + "fulfillFailedStatus": "Ðе удалоÑÑŒ выполнить", + "unpaid": "Ðе оплачено", + "amount": "Сумма", + "tradeNo": "Ðомер Ñделки", + "payments": "Заказы", + "creditReasonShareGain": "ÐŸÑƒÐ±Ð»Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð±Ñ‹Ð»Ð° куплена", + "creditReasonSharePay": "Покупка в магазине", + "creditReasonRecharge": "Пополнение", + "creditChanges": "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¾Ñ‡ÐºÐ¾Ð²", + "payXPoints": "Оплатить <0>", + "pointsPayAvailable": "Этот товар поддерживает оплату очками, вы можете выбрать иÑпользование <0> Ð´Ð»Ñ Ð¾Ð±Ð¼ÐµÐ½Ð° на Ñледующем шаге.", + "payAmount": "Оплатить {{price}}", + "purchaseSomething": "Купить {{name}}", + "redeem": "ОбменÑть", + "shop": "Магазин", + "resumeTicket": "Билет Ð´Ð»Ñ Ð²Ð¾ÑÑтановлениÑ", + "resumeTicketDes": "Ð’Ñ‹ можете найти его в пиÑьме Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸ÐµÐ¼ заказа, отправленном поÑле оплаты", + "restorePurchase": "ВоÑÑтановить покупку", + "restorePurchaseDes": "ВоÑÑтановить покупку Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ \"Билета Ð´Ð»Ñ Ð²Ð¾ÑÑтановлениÑ\" из пиÑьма Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸ÐµÐ¼ заказа", + "paymentSuccess": "Оплата прошла уÑпешно", + "fulfillFailed": "Ðе удалоÑÑŒ выполнить заказ, пожалуйÑта, ÑвÑжитеÑÑŒ Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратором Ñайта.", + "paidButton": "Я оплатил", + "payInNewWindow": "ПожалуйÑта, завершите оплату во вÑплывающем новом окне, не закрывайте Ñту Ñтраницу до Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð¾Ð¿Ð»Ð°Ñ‚Ñ‹. ЕÑли новое окно не поÑвилоÑÑŒ, пожалуйÑта, <0>нажмите здеÑÑŒ.", + "paymentFailedTitle": "Ошибка обработки платежа", + "paymentEmailHelper": "ПоÑкольку вы не вошли в ÑиÑтему, нам нужен ваш Ð°Ð´Ñ€ÐµÑ Ñлектронной почты Ð´Ð»Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²ÐºÐ¸ Ñертификата покупки", + "payEquivalentCash": "Оплатить Ñквивалентную Ñумму: {{num}}", + "payWithCash": "Оплатить наличными", + "recharge": "Пополнить", + "pointsBalance": "Ð‘Ð°Ð»Ð°Ð½Ñ Ð¾Ñ‡ÐºÐ¾Ð²: {{num}}", + "loginRequired": "ТребуетÑÑ Ð²Ñ…Ð¾Ð´ в ÑиÑтему", + "payWithPoints": "Оплатить очками", + "purchaseLogin": "ПожалуйÑта, Ñначала <0>войдите в ÑиÑтему, а затем продолжите покупку", + "noAvailableSharePurchaseMethod": "Ðет доÑтупных ÑпоÑобов покупки", + "purchaseShareLink": "Купить ÑÑылку на публикацию", + "loginWith": "Войти Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ {{name}}", + "sso": "SSO", + "qq": "QQ", + "quota": "Квота ёмкоÑти", + "exceedQuota": "Ваша иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÐµÐ¼Ð°Ñ Ñ‘Ð¼ÐºÐ¾Ñть превыÑила квоту ёмкоÑти, пожалуйÑта, как можно Ñкорее удалите лишние файлы или купите ёмкоÑть", + "extendStorage": "РаÑширить хранилище", + "folderPolicySwitched": "Политика Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹ папки переключена на \"{{name}}\"", + "switchFolderPolicy": "Переключить политику Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¿Ð°Ð¿ÐºÐ¸", + "setPolicyForFolder": "УÑтановить политику Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹ папки:", + "manageMount": "Управление привÑзкой", + "saveToMyFiles": "Сохранить в мои файлы", + "report": "Сообщить о злоупотреблении", + "reportTarget": "Объект жалобы", + "reportReason": "Причина", + "reportReasonOptions": ["Ðарушение авторÑких прав", "ВредоноÑный контент", "Спам", "Другое"], + "reportDescription": "Дополнительное опиÑание", + "reportAbuseSuccess": "Жалоба подана", + "migrateStoragePolicy": "ПеренеÑти политику хранениÑ", + "fileSaved": "Файл Ñохранён", + "sharePurchaseTitle": "Эта ÑÑылка на публикацию требует оплаты <0> Ð´Ð»Ñ Ð´Ð¾Ñтупа", + "payToDownload": "ÐŸÐ»Ð°Ñ‚Ð½Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ°", + "creditToBePaid": "Очки к оплате", + "creditGainPredict": "ОжидаетÑÑ Ð¿Ð¾Ñтупление {{num}} очков Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ покупки", + "creditPrice": " ({{num}} очков)", + "creditFree": " (беÑплатно по очкам)", + "cancelSubscription": "Отмена прошла уÑпешно, Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð²ÑтупÑÑ‚ в Ñилу через неÑколько минут или чаÑов", + "qqUnlinked": "СвÑзь Ñ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð¾Ð¼ QQ отменена", + "groupExpire": "(<0> иÑтекает)", + "manuallyCancelSubscription": "Вручную отменить текущую группу пользователей", + "qqAccount": "Ðккаунт QQ", + "connect": "ПривÑзать", + "unlink": "ОтвÑзать", + "credits": "Очки", + "cancelSubscriptionTitle": "Отменить группу пользователей", + "cancelSubscriptionWarning": "Ð’Ñ‹ вернётеÑÑŒ к начальной группе пользователей, и Ð¾Ð¿Ð»Ð°Ñ‡ÐµÐ½Ð½Ð°Ñ Ñумма не может быть возвращена, вы уверены, что хотите продолжить?", + "mountPolicy": "ПривÑзка политики хранениÑ", + "mountDescription": "ПоÑле привÑзки политики Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ðº папке новые файлы, загруженные в Ñту папку или её подпапки, будут хранитьÑÑ Ñ Ð¸Ñпользованием привÑзанной политики хранениÑ. Копирование и перемещение в Ñту папку не применит привÑзанную политику хранениÑ; когда неÑколько родительÑких папок указывают политику хранениÑ, будет выбрана политика Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð±Ð»Ð¸Ð¶Ð°Ð¹ÑˆÐµÐ¹ родительÑкой папки.", + "mountNewFolder": "ПривÑзать новую папку", + "nsfw": "ПорнографичеÑÐºÐ°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ", + "malware": "Содержит вируÑÑ‹", + "copyright": "Ðарушение авторÑких прав", + "inappropriateStatements": "Ðеподобающие заÑвлениÑ", + "other": "Другое", + "groupBaseQuota": "Ð‘Ð°Ð·Ð¾Ð²Ð°Ñ Ñ‘Ð¼ÐºÐ¾Ñть группы пользователей - {{size}}", + "validPackQuota": "ÐмкоÑть раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ - {{size}}", + "used": "ИÑпользовано - {{size}}", + "total": "ÐžÐ±Ñ‰Ð°Ñ Ñ‘Ð¼ÐºÐ¾Ñть - {{size}}", + "validStorage": "ДейÑтвующее раÑширение", + "buyStorage": "Купить ёмкоÑть", + "useGiftCode": "ИÑпользовать код активации Ð´Ð»Ñ Ð¾Ð±Ð¼ÐµÐ½Ð°", + "packName": "Ðазвание пакета ёмкоÑти", + "activationDate": "Дата активации", + "validDuration": "Срок дейÑтвиÑ", + "expiredAt": "Дата иÑтечениÑ", + "days": "{{num}} дней", + "pleaseInputGiftCode": "ПожалуйÑта, введите код Ð´Ð»Ñ Ð¾Ð±Ð¼ÐµÐ½Ð°", + "pleaseSelectAStoragePack": "ПожалуйÑта, Ñначала выберите пакет ёмкоÑти", + "paymentMethod": "СпоÑоб оплаты", + "noAvailableMethod": "Ðет доÑтупных ÑпоÑобов оплаты", + "alipay": "Сканирование Alipay", + "wechatPay": "Сканирование WeChat", + "payByCredits": "Оплата очками", + "purchaseDuration": "ДлительноÑть покупки", + "creditsNum": "КоличеÑтво очков Ð´Ð»Ñ Ð¿Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ", + "store": "Магазин", + "storageExpansion": "РаÑширение хранилища", + "membership": "ЧленÑтво", + "buyCredits": "Пополнение очков", + "subtotal": "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ ÑтоимоÑть:", + "creditsTotalNum": "{{num}} очков", + "purchaseNow": "Купить ÑейчаÑ", + "recommended": "РекомендуетÑÑ", + "enterGiftCode": "Введите код Ð´Ð»Ñ Ð¾Ð±Ð¼ÐµÐ½Ð°", + "qrcodeAlipay": "ПожалуйÑта, иÑпользуйте Alipay Ð´Ð»Ñ ÑÐºÐ°Ð½Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ QR-кода ниже Ð´Ð»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð¾Ð¿Ð»Ð°Ñ‚Ñ‹, Ñта Ñтраница автоматичеÑки обновитÑÑ Ð¿Ð¾Ñле Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð¾Ð¿Ð»Ð°Ñ‚Ñ‹.", + "qrcodeWechat": "ПожалуйÑта, иÑпользуйте WeChat Ð´Ð»Ñ ÑÐºÐ°Ð½Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ QR-кода ниже Ð´Ð»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð¾Ð¿Ð»Ð°Ñ‚Ñ‹, Ñта Ñтраница автоматичеÑки обновитÑÑ Ð¿Ð¾Ñле Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð¾Ð¿Ð»Ð°Ñ‚Ñ‹.", + "qrcodeCustom": "ПожалуйÑта, отÑканируйте QR-код ниже Ð´Ð»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð¾Ð¿Ð»Ð°Ñ‚Ñ‹ или <0>откройте платёжную ÑÑылку напрÑмую, Ñта Ñтраница автоматичеÑки обновитÑÑ Ð¿Ð¾Ñле Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð¾Ð¿Ð»Ð°Ñ‚Ñ‹.", + "paymentCompleted": "Оплата завершена", + "productDelivered": "Ваша покупка поÑтупила на Ñчёт.", + "confirmRedeem": "Подтвердить обмен", + "productType": "Товар", + "qyt": "КоличеÑтво:", + "duration": "ДлительноÑть:", + "subscribe": "Купить группу пользователей", + "selected": "Выбрано:", + "paymentQrcode": "QR-код Ð´Ð»Ñ Ð¾Ð¿Ð»Ð°Ñ‚Ñ‹", + "validDurationDays": "{{num}} дней", + "reportSuccessful": "Жалоба уÑпешно подана", + "additionalDescription": "Дополнительное опиÑание", + "announcement": "ОбъÑвление", + "dontShowAgain": "Ðе показывать Ñнова", + "openPaymentLink": "Открыть платёжную ÑÑылку напрÑмую", + "creditReasonAdjust": "Ð ÑƒÑ‡Ð½Ð°Ñ ÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð¸Ñ€Ð¾Ð²ÐºÐ°" + } +} diff --git a/public/locales/ru-RU/common.json b/public/locales/ru-RU/common.json new file mode 100755 index 0000000..775bea4 --- /dev/null +++ b/public/locales/ru-RU/common.json @@ -0,0 +1,120 @@ +{ + "pageNotFound": "Страница не найдена", + "unknownError": "ÐеизвеÑÑ‚Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°", + "errLoadingSiteConfig": "Ðе удалоÑÑŒ загрузить конфигурацию Ñайта:", + "newVersionRefresh": "ДоÑтупна Ð½Ð¾Ð²Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹ Ñтраницы.", + "update": "Обновить", + "errorDetails": "ПодробноÑти", + "renderError": "Произошла ошибка при отображении Ñтраницы, попробуйте обновить Ñту Ñтраницу.", + "ok": "ОК", + "cancel": "Отмена", + "select": "Выбрать", + "copyToClipboard": "Копировать", + "close": "Закрыть", + "dismiss": "Закрыть", + "intlDateTime": "{{val, datetime}}", + "seconds": "Ñ Ñекунд", + "minutes": "м минут Ñ Ñекунд", + "hours": "ч чаÑов м минут", + "days": "{{d}} дней", + "timeAgoLocaleCode": "ru", + "forEditorLocaleCode": "ru", + "artPlayerLocaleCode": "ru", + "requestID": "ID запроÑа: {{id}}", + "object": "Объект", + "error": "Ошибка", + "areYouSure": "Подтверждение", + "incorrectSizeInput": "Ðе ÑоответÑтвует ограничениÑм размера", + "of": "из", + "rowsPerPage": "Строк на Ñтранице", + "custom": "ÐаÑтраиваемый", + "enter": "Введите", + "captcha": { + "cap": { + "human": "Я человек", + "verifying": "Проверка…", + "verified": "Ð’Ñ‹ прошли проверку" + } + }, + "errors": { + "401": "ПожалуйÑта, войдите в ÑиÑтему", + "403": "У Ð²Ð°Ñ Ð½ÐµÑ‚ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð½Ð° выполнение Ñтого дейÑтвиÑ", + "404": "РеÑÑƒÑ€Ñ Ð½Ðµ ÑущеÑтвует", + "409": "Произошёл конфликт ({{message}})", + "40001": "Ðеверные входные параметры ({{message}})", + "40002": "Ошибка загрузки", + "40003": "Ðе удалоÑÑŒ Ñоздать папку", + "40004": "Объект Ñ Ñ‚Ð°ÐºÐ¸Ð¼ именем уже ÑущеÑтвует", + "40005": "ПодпиÑÑŒ иÑтекла", + "40006": "Ðеподдерживаемый тип политики хранениÑ", + "40007": "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° пользователей не может выполнить Ñто дейÑтвие", + "40011": "СеÑÑÐ¸Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸ не ÑущеÑтвует или иÑтекла", + "40012": "Ðеверный Ð¸Ð½Ð´ÐµÐºÑ Ñ„Ñ€Ð°Ð³Ð¼ÐµÐ½Ñ‚Ð° ({{message}})", + "40013": "ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° Ñодержимого ({{message}})", + "40014": "Превышен лимит пакетного Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð²Ð½ÐµÑˆÐ½Ð¸Ñ… ÑÑылок", + "40015": "Превышено макÑимальное количеÑтво задач удалённой загрузки", + "40016": "Путь не найден", + "40017": "Этот аккаунт заблокирован", + "40018": "Этот аккаунт не активирован", + "40019": "Эта Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ Ð½Ðµ включена", + "40020": "Ðеверные или иÑтёкшие учётные данные", + "40021": "Пользователь не найден", + "40022": "Код проверки неверен", + "40023": "СеÑÑÐ¸Ñ Ð²Ñ…Ð¾Ð´Ð° не ÑущеÑтвует", + "40024": "Ðе удалоÑÑŒ инициализировать WebAuthn", + "40025": "Ошибка проверки", + "40026": "Код капчи неверен", + "40027": "Ошибка проверки, пожалуйÑта, обновите веб-Ñтраницу и повторите попытку", + "40028": "Ошибка отправки Ñлектронной почты", + "40029": "ÐедейÑÑ‚Ð²Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ ÑÑылка", + "40030": "Эта ÑÑылка иÑтекла", + "40032": "Этот Ð°Ð´Ñ€ÐµÑ Ñлектронной почты уже иÑпользуетÑÑ", + "40033": "Пользователь не активирован, пиÑьмо Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸ÐµÐ¹ отправлено повторно", + "40034": "Этот пользователь не может быть активирован", + "40035": "Политика Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð½Ðµ ÑущеÑтвует", + "40039": "Группа пользователей не ÑущеÑтвует", + "40044": "Файл не найден", + "40045": "Ðе удалоÑÑŒ получить ÑпиÑок объектов в данной папке", + "40047": "Ðе удалоÑÑŒ инициализировать файловую ÑиÑтему", + "40048": "Ошибка ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸", + "40049": "Размер файла превышает лимит", + "40050": "Тип файла не разрешён", + "40051": "ÐедоÑтаточно меÑта в хранилище", + "40052": "Это Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° или раÑширение не разрешено", + "40053": "ÐÐµÐ»ÑŒÐ·Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÑŒ такое дейÑтвие Ñ ÐºÐ¾Ñ€Ð½ÐµÐ²Ð¾Ð¹ папкой", + "40054": "Файл Ñ Ñ‚Ð°ÐºÐ¸Ð¼ именем уже загружаетÑÑ Ð² Ñту папку, пожалуйÑта, очиÑтите ÑеÑÑии загрузки", + "40055": "ÐеÑоответÑтвие информации о файле", + "40056": "Ðеподдерживаемый тип архивного файла", + "40057": "ДоÑÑ‚ÑƒÐ¿Ð½Ð°Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ° Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¸Ð·Ð¼ÐµÐ½Ð¸Ð»Ð°ÑÑŒ, пожалуйÑта, обновите ÑпиÑок файлов и добавьте Ñту задачу Ñнова", + "40058": "Эта Ð¿ÑƒÐ±Ð»Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð½Ðµ ÑущеÑтвует или уже иÑтекла", + "40069": "Ðеверный пароль", + "40070": "Эта Ð¿ÑƒÐ±Ð»Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð½Ðµ поддерживает предварительный проÑмотр", + "40071": "ÐедейÑÑ‚Ð²Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¿Ð¾Ð´Ð¿Ð¸ÑÑŒ", + "40073": "Файл занÑÑ‚", + "40074": "Выбрано Ñлишком много файлов", + "40079": "Превышен лимит макÑимального количеÑтва обходимых файлов, попробуйте Ñузить облаÑть операции", + "40081": "ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð° не полноÑтью", + "40082": "Только владелец файла может выполнить Ñто дейÑтвие", + "40080": "ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ ÑÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð° или пароль пользователÑ", + "50001": "Ошибка операции Ñ Ð±Ð°Ð·Ð¾Ð¹ данных ({{message}})", + "50002": "Ошибка подпиÑи URL или запроÑа ({{message}})", + "50004": "Ошибка операции ввода-вывода ({{message}})", + "50005": "ВнутреннÑÑ Ð¾ÑˆÐ¸Ð±ÐºÐ° ({{message}})", + "50010": "Целевой узел недоÑтупен", + "50011": "Ðе удалоÑÑŒ запроÑить метаинформацию файла" + }, + "vasErrors": { + "40031": "Этот провайдер Ñлектронной почты недоÑтупен, пожалуйÑта, Ñмените Ð°Ð´Ñ€ÐµÑ Ñлектронной почты", + "40059": "ÐÐµÐ»ÑŒÐ·Ñ Ñохранить Ñвою ÑобÑтвенную публикацию", + "40062": "ÐедоÑтаточно очков", + "40063": "Ваша Ñ‚ÐµÐºÑƒÑ‰Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° пользователей ещё не иÑтекла, пожалуйÑта, перейдите в личные наÑтройки и вручную отмените подпиÑку, чтобы продолжить", + "40064": "Ð’Ñ‹ уже находитеÑÑŒ в Ñтой группе пользователей", + "40065": "ÐедейÑтвительный код активации", + "40066": "Ð’Ñ‹ уже привÑзали другую личноÑть, пожалуйÑта, Ñначала отвÑжите её", + "40067": "Эта личноÑть уже привÑзана к другому аккаунту", + "40068": "Эта личноÑть не привÑзана ни к одному аккаунту", + "40072": "ÐдминиÑтратор не может перейти в другую группу пользователей", + "40084": "Ваш аккаунт ÑвлÑетÑÑ Ð±ÐµÑпарольным, вы должны оÑтавить Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ один ÑвÑзанный аккаунт", + "40085": "Сумма заказа Ñлишком мала Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð»Ð¶ÐµÐ½Ð¸Ñ Ð¾Ð¿Ð»Ð°Ñ‚Ñ‹" + } +} diff --git a/public/locales/ru-RU/dashboard.json b/public/locales/ru-RU/dashboard.json new file mode 100755 index 0000000..d024c86 --- /dev/null +++ b/public/locales/ru-RU/dashboard.json @@ -0,0 +1,1622 @@ +{ + "errors": { + "40036": "Политика Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¿Ð¾ умолчанию не может быть удалена.", + "40037": "Ðекоторые файловые блобы иÑпользуют Ñту политику, Ñначала удалите Ñти файловые блобы.", + "40038": "{{message}} групп(Ñ‹) иÑпользуют Ñту политику, Ñначала отвÑжите Ñти группы.", + "40040": "Ðевозможно выполнить такое дейÑтвие Ð´Ð»Ñ ÑиÑтемной группы.", + "40041": "{{message}} пользователей вÑе еще находÑÑ‚ÑÑ Ð² Ñтой группе, Ñначала удалите или отвÑжите Ñтих пользователей.", + "40042": "Ðевозможно изменить группу ÑиÑтемной группы пользователей.", + "40043": "Ðевозможно выполнить такое дейÑтвие Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¿Ð¾ умолчанию.", + "40046": "Ðевозможно выполнить такое дейÑтвие Ð´Ð»Ñ Ð³Ð»Ð°Ð²Ð½Ð¾Ð³Ð¾ узла.", + "40060": "Подчиненный узел не может отправить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð¾Ð±Ñ€Ð°Ñ‚Ð½Ð¾Ð³Ð¾ вызова на главный узел, проверьте наÑтройки главного узла: ОÑновные - Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ Ñайте - URL Ñайта, убедитеÑÑŒ, что подчиненный узел может получить доÑтуп к Ñтому URL. ({{message}})", + "40061": "ÐеÑоответÑтвие верÑий Cloudreve. ({{message}})", + "40086": "Узел иÑпользуетÑÑ Ñледующими политиками хранениÑ: {{message}}.", + "50008": "Ðе удалоÑÑŒ обновить наÑтройку. ({{message}})", + "50009": "Ðе удалоÑÑŒ добавить политику CORS." + }, + "nav": { + "summary": "Сводка", + "settings": "ÐаÑтройки", + "basicSetting": "ОÑновные", + "email": "Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð°", + "transportation": "Передача", + "appearance": "Внешний вид", + "image": "ИзображениÑ", + "captcha": "Капча", + "storagePolicy": "Политика хранениÑ", + "nodes": "Узлы", + "groups": "Группы", + "users": "Пользователи", + "files": "Файлы", + "entities": "Файловые блобы", + "shares": "Общий доÑтуп", + "tasks": "Фоновые задачи", + "remoteDownload": "Ð£Ð´Ð°Ð»ÐµÐ½Ð½Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ°", + "generalTasks": "Общие", + "title": "Панель управлениÑ", + "dashboard": "Панель ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Cloudreve", + "userSession": "ПользовательÑÐºÐ°Ñ ÑеÑÑиÑ", + "fileSystem": "Ð¤Ð°Ð¹Ð»Ð¾Ð²Ð°Ñ ÑиÑтема", + "mediaProcessing": "Обработка медиа", + "queue": "Очередь", + "events": "СобытиÑ", + "server": "Сервер", + "customProps": "ПользовательÑкие ÑвойÑтва", + "abuseReport": "Жалобы на злоупотреблениÑ" + }, + "summary": { + "generatedAt": "Создано в <0>", + "confirmSiteURLTitle": "Подтвердить URL Ñайта", + "siteURLNotMatch": "УÑтановленный вами URL Ñайта не Ñодержит текущий ({{current}}), хотите добавить его в ÑпиÑок?", + "setAsPrimary": "УÑтановить как оÑновной URL Ñайта", + "setAsPrimaryDes": "УÑтановить {{current}} как оÑновной URL Ñайта, иÑпользуемый Ð´Ð»Ñ ÑвÑзи Ñ Ð²Ð½ÐµÑˆÐ½Ð¸Ð¼Ð¸ ÑервиÑами и Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¾Ð±Ñ€Ð°Ñ‚Ð½Ñ‹Ñ… вызовов. ИÑпользуйте URL, доÑтупный из WAN.", + "setAsSecondary": "Добавить к дополнительным URL", + "setAsSecondaryDes": "Добавить {{current}} к дополнительным URL, Cloudreve автоматичеÑки выберет, иÑпользовать ли его, оÑновываÑÑÑŒ на URL, к которому фактичеÑки обращаетÑÑ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÑŒ.", + "siteURLDescription": "Этот параметр очень важен, убедитеÑÑŒ, что он ÑоответÑтвует фактичеÑкому URL вашего Ñайта. Ð’Ñ‹ можете изменить Ñтот параметр в ÐаÑтройки - ОÑновные.", + "ignore": "Игнорировать", + "changeIt": "Изменить", + "trend": "ТенденциÑ", + "summary": "Сводка", + "totalUsers": "Пользователи", + "totalFilesAndFolders": "Файлы и папки", + "shareLinks": "СÑылки общего доÑтупа", + "totalBlobs": "Блобы", + "homepage": "Ð“Ð»Ð°Ð²Ð½Ð°Ñ Ñтраница", + "github": "GitHub", + "documents": "ДокументациÑ", + "discordCommunity": "СообщеÑтво Discord", + "telegram": "Группа Telegram", + "forum": "GitHub Discussions", + "buyPro": "Обновить до Pro", + "publishedAt": "опубликовано в <0>", + "licenseExpireAt": "Дата иÑÑ‚ÐµÑ‡ÐµÐ½Ð¸Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ð¸", + "permanentLicense": "ПоÑтоÑÐ½Ð½Ð°Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ", + "offlineLicenseExpireAy": "Дата иÑÑ‚ÐµÑ‡ÐµÐ½Ð¸Ñ Ð¾Ñ„Ð»Ð°Ð¹Ð½ лицензии", + "offlineLicenseDes": "Cloudreve автоматичеÑки обновит офлайн лицензию до иÑÑ‚ÐµÑ‡ÐµÐ½Ð¸Ñ Ñрока дейÑтвиÑ, еÑли ваш Ñервер подключен к Ñети.", + "licensedDomains": "Лицензированные домены", + "renew": "Обновить офлайн лицензию", + "manageLicense": "Управление лицензией", + "volPurchase": "КлиентÑкую VOL лицензию необходимо приобреÑти отдельно в <0>Панели ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñми. VOL Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ Ð¿Ð¾Ð·Ð²Ð¾Ð»Ñет вашим пользователÑм беÑплатно подключатьÑÑ Ðº вашему Ñайту, иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ <1>Cloudreve iOS, без необходимоÑти платить за подпиÑку на Ñамо iOS приложение. ПоÑле покупки лицензии нажмите «Обновить офлайн лицензию» ниже.", + "iosVol": "ÐšÐ¾Ñ€Ð¿Ð¾Ñ€Ð°Ñ‚Ð¸Ð²Ð½Ð°Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ Ð´Ð»Ñ iOS клиента (VOL)", + "refreshSuccessfully": "УÑпешно обновлено.", + "manualRefresh": "Обновить офлайн лицензию вручную", + "manualRefreshDes": "Ðе удалоÑÑŒ автоматичеÑки обновить офлайн лицензию, попробуйте войти в <0>Панель ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñми, чтобы получить поÑледнюю офлайн лицензию и вÑтавить её ниже.", + "announcement": "ОбъÑвление" + }, + "queue": { + "queueName_io_intense": "ИнтенÑивный ввод-вывод", + "queueName_io_intenseDes": "Очередь Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸ большого количеÑтва операций ввода-вывода, включаÑ: Ð¿ÐµÑ€ÐµÐ½Ð¾Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸Ðº хранениÑ, раÑпаковку, Ñжатие.", + "queueName_media_meta": "Извлечение метаданных медиа", + "queueName_media_metaDes": "ИÑпользуетÑÑ Ð´Ð»Ñ Ð¸Ð·Ð²Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ Ð¼ÐµÑ‚Ð°Ð´Ð°Ð½Ð½Ñ‹Ñ… из медиафайлов.", + "queueName_recycle": "Переработка блобов", + "queueName_recycleDes": "ИÑпользуетÑÑ Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¸Ñтекших файловых блобов.", + "queueName_thumb": "Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¼Ð¸Ð½Ð¸Ð°Ñ‚ÑŽÑ€", + "queueName_thumbDes": "ИÑпользуетÑÑ Ð´Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ð¸ миниатюр файлов.", + "queueName_remote_download": "Ð£Ð´Ð°Ð»ÐµÐ½Ð½Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ°", + "queueName_remote_downloadDes": "ИÑпользуетÑÑ Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸ задач удаленной загрузки.", + "failed": "Ðеудачно ({{count}})", + "success": "УÑпешно ({{count}})", + "suspending": "ПриоÑтановлено ({{count}})", + "busyWorker": "ОбрабатываетÑÑ ({{count}})", + "submited": "Отправлено ({{count}})", + "editQueueSettings": "Редактировать наÑтройки очереди - {{name}}", + "workerNum": "Рабочие потоки", + "workerNumDes": "МакÑимальное количеÑтво задач Ð´Ð»Ñ Ð¿Ð°Ñ€Ð°Ð»Ð»ÐµÐ»ÑŒÐ½Ð¾Ð³Ð¾ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð² очереди задач", + "maxExecution": "МакÑимальное Ð²Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ", + "maxExecutionDes": "МакÑимальное Ð²Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ (Ñекунды) Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸, поÑле которого задача будет завершена.", + "backoffFactor": "КоÑффициент отката", + "backoffFactorDes": "КоÑффициент роÑта интервалов времени повторных попыток задач.", + "backoffMaxDuration": "МакÑимальное Ð²Ñ€ÐµÐ¼Ñ Ð¾Ñ‚ÐºÐ°Ñ‚Ð°", + "backoffMaxDurationDes": "МакÑимальное Ð²Ñ€ÐµÐ¼Ñ Ð¾Ñ‚ÐºÐ°Ñ‚Ð° (Ñекунды) Ð´Ð»Ñ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€Ð½Ñ‹Ñ… попыток задач.", + "maxRetry": "МакÑимальное количеÑтво повторов", + "maxRetryDes": "МакÑимальное количеÑтво повторных попыток поÑле ÑÐ±Ð¾Ñ Ð·Ð°Ð´Ð°Ñ‡Ð¸.", + "retryDelay": "Задержка повтора", + "retryDelayDes": "Ðачальное Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð´ÐµÑ€Ð¶ÐºÐ¸ (Ñекунды) Ð´Ð»Ñ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€Ð½Ñ‹Ñ… попыток задач." + }, + "settings": { + "headlessFooter": "Ðижний колонтитул Ñтраницы входа", + "headlessFooterDes": "ПользовательÑкий HTML-контент, отображаемый в нижней чаÑти Ñтраниц входа, региÑтрации и результатов обратного вызова.", + "headlessBottom": "ÐижнÑÑ Ñ‡Ð°Ñть Ñтраницы входа", + "headlessBottomDes": "ПользовательÑкий HTML-контент, отображаемый в нижней чаÑти оÑновного фрейма Ñтраниц входа, региÑтрации и результатов обратного вызова.", + "customHTML": "ПользовательÑкий HTML", + "customHTMLDes": "Ð’Ñтавить пользовательÑкий HTML-контент в предуÑтановленное меÑто Ñайта.", + "sidebarBottom": "ÐижнÑÑ Ñ‡Ð°Ñть боковой панели", + "sidebarBottomDes": "ПользовательÑкий HTML-контент, отображаемый в нижней чаÑти боковой панели.", + "addNavItem": "Добавить Ñлемент навигации", + "customNavItems": "ПользовательÑкие Ñлементы боковой панели", + "customNavItemsDes": "Ð’Ñ‹ можете добавить пользовательÑкие Ñлементы в боковую панель, и пользователи будут перенаправлены на ÑоответÑтвующую ÑÑылку при нажатии.", + "navItemUrl": "СÑылка", + "iconifyNamePlaceholder": "Идентификатор иконки Iconify, например fluent:home-24-regular", + "imageUrl": "URL изображениÑ", + "iconifyName": "Ð˜Ð¼Ñ Ð¸ÐºÐ¾Ð½ÐºÐ¸ Iconify", + "oidc": "OpenID Connect (OIDC)", + "oidcDes": "OpenID Connect (OIDC) - Ñто открытый протокол аутентификации Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸ личноÑти между различными ÑиÑтемами. ПоÑле ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð½Ð° Ñторонней платформе идентификации добавьте <0>{{url}} в поле «Redirect URI». Ð”Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð¹ информации обратитеÑÑŒ к <1>документации.", + "clientID": "ID клиента", + "clientIDDes": "ID клиента приложениÑ, Ñозданного на Ñторонней платформе идентификации.", + "clientSecret": "Секрет клиента", + "clientSecretDes": "Секрет клиента приложениÑ, Ñозданного на Ñторонней платформе идентификации.", + "scope": "ОблаÑть", + "scopeDes": "Дополнительные облаÑти Ð´Ð»Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа, разделенные запÑтыми <0>,. По умолчанию Cloudreve будет запрашивать <0>openid, <0>email и <0>profile; нет необходимоÑти повторÑть здеÑÑŒ.", + "oidcWellknown": "ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ OIDC Wellknown", + "oidcWellknownDes": "Документ Wellknown Ñторонней платформы идентификации, Ñодержащий информацию о конфигурации OpenID Connect.", + "importFromWellknown": "Импорт из URL", + "importOidc": "Импорт конфигурации OIDC Wellknown", + "oidcWellknownUrl": "URL Wellknown", + "oidcWellknownUrlDes": "URL документа wellknown Ñторонней платформы идентификации, например <0>https://accounts.google.com/.well-known/openid-configuration.", + "resetUrl": "URL ÑброÑа", + "exceedToleranceDays": "Дни терпимоÑти Ð´Ð»Ñ Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²ÐºÐ¸", + "activateUrl": "URL активации", + "domainNotLicensed": "Домен не лицензирован", + "domainNotLicensedDes": "УÑтановленный вами URL Ñайта Ñодержит неÑанкционированный домен, добавьте Ñтот поддомен в <0>Панель ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñми и нажмите кнопку ниже, чтобы обновить лицензию и повторить попытку.", + "showSettings": "Показать наÑтройки", + "perPage": "{{num}} на Ñтраницу", + "noNodes": "Ðет доÑтупных узлов.", + "extractMediaMeta": "Извлечь метаданные медиа", + "extractMediaMetaDes": "Извлекать метаданные медиафайлов Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¸ поиÑка. По умолчанию политики Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð½Ðµ на локальном компьютере будут иÑпользовать только генератор «Ðативный в политике хранениÑ». Ð’Ñ‹ можете раÑширить возможноÑти миниатюр Ñторонних политик хранениÑ, включив функцию «ПрокÑи ÑкÑтрактора» на Ñтранице наÑтроек политики хранениÑ. Ð”Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð¹ информации обратитеÑÑŒ к <0>документации.", + "exif": "EXIF", + "exifDes": "Извлекать метаданные EXIF из файлов изображений Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¸ поиÑка.", + "music": "Метаданные музыки", + "musicDes": "Извлекать метаданные из музыкальных файлов, Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ Ð½Ð°Ð·Ð²Ð°Ð½Ð¸Ðµ, иÑполнителÑ, альбом и Ñ‚.д.", + "ffprobe": "FFprobe", + "ffprobeDes": "ИÑпользовать FFprobe Ð´Ð»Ñ Ð¸Ð·Ð²Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ Ð¼ÐµÑ‚Ð°Ð´Ð°Ð½Ð½Ñ‹Ñ… из видео и аудио файлов.", + "maxSizeLocal": "МакÑимальный размер файла (локальное хранилище)", + "maxSizeLocalDes": "МакÑимальный размер файла Ð´Ð»Ñ Ð¸Ð·Ð²Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ Ð¼ÐµÑ‚Ð°Ð´Ð°Ð½Ð½Ñ‹Ñ…, когда файл хранитÑÑ Ð² политике локального хранениÑ, 0 означает отÑутÑтвие ограничениÑ.", + "maxSizeRemote": "МакÑимальный размер файла (удаленное хранилище)", + "maxSizeRemoteDes": "МакÑимальный размер файла Ð´Ð»Ñ Ð¸Ð·Ð²Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ Ð¼ÐµÑ‚Ð°Ð´Ð°Ð½Ð½Ñ‹Ñ…, когда файл хранитÑÑ Ð² Ñторонних политиках хранениÑ, 0 означает отÑутÑтвие ограничениÑ.", + "exifBruteForce": "ИÑпользовать перебор при необходимоÑти", + "exifBruteForceDes": "При включении веÑÑŒ файл будет проÑканирован Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка данных EXIF, еÑли они не могут быть найдены в Ñтандартном раÑположении заголовка. Это может увеличить Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸, но может найти данные EXIF в неÑтандартных меÑтах.", + "musicCover": "Обложка музыки", + "musicCoverDes": "Извлекать обложку альбома из музыкальных файлов, поддерживает контейнер ID3 (v1, 2.2, 2.3 и 2.4). Этот генератор завиÑит от любого другого генератора миниатюр изображений (вÑтроенного Cloudreve или VIPS).", + "geocoding": "Геокодирование", + "geocodingDes": "Получить информацию об адреÑе Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ ÑервиÑа Mapbox на оÑнове информации о координатах, запиÑанной в EXIF медиафайлов.", + "mapboxAK": "API-ключ Mapbox", + "mapboxAKDes": "API-ключ, Ñозданный в конÑоли Mapbox.", + "geocodingDependencyWarning": "Генератор Ð³ÐµÐ¾ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð·Ð°Ð²Ð¸Ñит от генератора EXIF, пожалуйÑта, включите генератор EXIF.", + "notAppliedToNativeGenerator": "{{prefix}}Ðе применимо к нативному генератору политик хранениÑ.", + "notAppliedToOneDriveNativeGenerator": "{{prefix}}Ðе применимо к нативному генератору политик Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ OneDrive или SharePoint.", + "fileBlobMargin": "Ð—Ð°Ð¿Ð°Ñ ÐºÑша URL файлового блоба (Ñекунды)", + "fileBlobMarginDes": "Когда один и тот же файловый блоб запрашиваетÑÑ Ð½ÐµÑколько раз, еÑли начальный URL имеет оÑтавшийÑÑ Ð¿ÐµÑ€Ð¸Ð¾Ð´ дейÑÑ‚Ð²Ð¸Ñ Ð±Ð¾Ð»ÑŒÑˆÐµ запаÑа, тот же URL будет иÑпользован повторно.", + "fileBlobTimeout": "TTL URL файлового блоба (Ñекунды)", + "fileBlobTimeoutDes": "Ограничить Ñрок дейÑÑ‚Ð²Ð¸Ñ Ð²Ñ€ÐµÐ¼ÐµÐ½Ð½Ð¾Ð³Ð¾ URL, полученного при открытии или загрузке файлов пользователÑми, применимо только к политикам локального хранениÑ, WebDAV или файлам, загружаемым через ретранÑлÑцию Cloudreve.", + "wopiSessionTimeout": "TTL ÑеÑÑии WOPI (Ñекунды)", + "wopiSessionTimeoutDes": "Ограничить Ñрок дейÑÑ‚Ð²Ð¸Ñ Ð¾Ð´Ð½Ð¾Ð¹ ÑеÑÑии при редактировании файлов пользователÑми Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ WOPI. ПоÑле иÑÑ‚ÐµÑ‡ÐµÐ½Ð¸Ñ Ñрока пользователÑм необходимо повторно открыть файл из Cloudreve.", + "oauthRefresh": "Интервал Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ¸ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ OAuth", + "oauthRefreshDes": "УÑтановить, как чаÑто обновлÑть учетные данные OAuth Ð´Ð»Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸Ðº Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ (например, OneDrive), которые требуют OAuth. Это может предотвратить иÑтечение Ñрока дейÑÑ‚Ð²Ð¸Ñ ÑƒÑ‡ÐµÑ‚Ð½Ñ‹Ñ… данных из-за длительных периодов бездейÑтвиÑ", + "transitParallelNum": "МакÑимальное количеÑтво параллельных ретранÑлÑционных передач", + "transitParallelNumDes": "МакÑимальное количеÑтво параллельных загрузок, когда одна ÑÐµÑ€Ð²ÐµÑ€Ð½Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° ретранÑлÑции файлов Ñодержит неÑколько файлов.", + "failedChunkRetry": "МакÑимальное количеÑтво повторных попыток Ð´Ð»Ñ Ñбоев загрузки фрагментов", + "failedChunkRetryDes": "МакÑимальное количеÑтво повторных попыток Ð´Ð»Ñ Ñбоев загрузки фрагментов, применимо только к Ñерверным загрузкам или ретранÑлÑционным передачам.", + "cacheChunks": "КÑшировать потоковые фрагменты", + "cacheChunksDes": "При включении данные фрагментов будут кÑшироватьÑÑ Ð² ÑиÑтемном временном каталоге во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ð¾Ñ‚Ð¾ÐºÐ¾Ð²Ð¾Ð¹ передачи, чтобы их можно было иÑпользовать Ð´Ð»Ñ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€Ð½Ñ‹Ñ… попыток неудачных загрузок фрагментов;\n При отключении Ð¿Ð¾Ñ‚Ð¾ÐºÐ¾Ð²Ð°Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð° фрагментов не займет дополнительное диÑковое проÑтранÑтво, но вÑÑ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ° немедленно завершитÑÑ Ð½ÐµÑƒÐ´Ð°Ñ‡ÐµÐ¹, еÑли загрузка фрагмента не удалаÑÑŒ.", + "folderPropsTimeout": "TTL кÑша ÑтатиÑтики папки (Ñекунды)", + "folderPropsTimeoutDes": "Срок дейÑÑ‚Ð²Ð¸Ñ ÐºÑша результатов при вычиÑлении пользователÑми ÑтатиÑтики папки (размер, количеÑтво файлов и Ñ‚.д.).", + "slaveAPIExpiration": "TTL подпиÑи API подчиненного узла (Ñекунды)", + "slaveAPIExpirationDes": "Срок дейÑÑ‚Ð²Ð¸Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñи, иÑпользуемый главным узлом при доÑтупе к API подчиненного узла.", + "uploadSessionTimeout": "TTL ÑеÑÑии загрузки (Ñекунды)", + "uploadSessionDes": "Ð’ течение дейÑтвительного периода ÑеÑÑии загрузки Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÐµÐ¼Ñ‹Ñ… политик Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ð¸ могут возобновить незавершенные задачи. МакÑимальное значение, которое можно уÑтановить, ограничено правилами различных провайдеров политик хранениÑ.", + "archiveTimeout": "TTL ÑеÑÑии Ñерверной пакетной загрузки (Ñекунды)", + "advanceOptions": "РаÑширенные параметры", + "emojiOptions": "Параметры Ñмодзи", + "addCategorize": "Добавить категорию", + "category": "КатегориÑ", + "searchQuery": "Ð—Ð°Ð¿Ñ€Ð¾Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ð¸ файлов", + "importWopi": "Импорт наÑтроек Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ WOPI", + "wopiEndpoint": "ÐšÐ¾Ð½ÐµÑ‡Ð½Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ð¸Ñ WOPI", + "wopiDes": "РаÑширьте возможноÑти онлайн-предварительного проÑмотра и Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Cloudreve, интегрируÑÑÑŒ Ñ ÑиÑтемами онлайн-обработки документов, поддерживающими протокол WOPI. ПожалуйÑта, заполните здеÑÑŒ Ð°Ð´Ñ€ÐµÑ Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ð¸Ñ ÑервиÑа WOPI, например <0>https://example.com/hosting/discovery. Ð”Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð¹ информации обратитеÑÑŒ к <1>документации.", + "embeddedWebpageViewer": "Ð’Ñтроенный проÑмотрщик веб-Ñтраниц", + "wopiViewer": "Приложение WOPI", + "ext": "РаÑширение", + "invalidWopiActionMapping": "ÐедопуÑтимое ÑопоÑтавление дейÑтвий WOPI", + "woapiActionMapping": "СопоÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð´ÐµÐ¹Ñтвий WOPI", + "drawioHost": "ЭкземплÑÑ€ DrawIO", + "drawioHostDes": "Ð’Ñ‹ можете иÑпользовать URL Ð´Ð»Ñ ÑамоÑтоÑтельно размещенного ÑкземплÑра.", + "openInNew": "Открыть в новом окне", + "openInNewDes": "ЕÑли отмечено, будет напрÑмую открыта Ð½Ð¾Ð²Ð°Ñ Ð²ÐºÐ»Ð°Ð´ÐºÐ° Ð´Ð»Ñ Ñтого приложениÑ.", + "maxSize": "МакÑимальный размер файла", + "maxSizeDes": "МакÑимальный размер файла, поддерживаемый Ñтим приложением. 0 означает отÑутÑтвие ограничениÑ. ЕÑли файл превышает Ñтот размер, он вÑе равно будет открыт, но пользователи будут предупреждены.", + "srcEncodedVar": "URL-кодированный временный URL доÑтупа к файловому блобу", + "srcVar": "Временный URL доÑтупа к файловому блобу", + "srcBase64Var": "Base64-кодированный временный URL доÑтупа к файловому блобу", + "nameEncodedVar": "URL-кодированное Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°", + "versionEntityVar": "ID блоба открытой верÑии файла, пуÑтое означает поÑледнюю верÑию.", + "fileIdVar": "ID файла", + "userIdVar": "ID пользователÑ, пуÑтой при отÑутÑтвии входа в ÑиÑтему.", + "userDisplayNameVar": "URL-кодированное отображаемое Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ.", + "fileViewers": "Файловые приложениÑ", + "addViewer": "Добавить приложение", + "viewerGroupTitle": "Группа приложений #{{index}}", + "viewerType": "Тип", + "viewerPlatform": "Платформа", + "viewerPlatformDes": "Выберите ÑоответÑтвующую платформу Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ на Ñтой платформе.", + "viewerPlatformPC": "Рабочий Ñтол", + "viewerPlatformMobile": "Мобильный", + "viewerPlatformAll": "Ð’Ñе", + "displayName": "Отображаемое имÑ", + "displayNameDes": "Отображаемое Ð¸Ð¼Ñ Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÐµÐ¹, поддержка ключа i18next.", + "viewerEnabled": "Включено", + "newFileAction": "ДейÑÑ‚Ð²Ð¸Ñ Ð´Ð»Ñ Ð½Ð¾Ð²Ñ‹Ñ… файлов", + "newFileActionDes": "Добавив Ñто ÑопоÑтавление, пользователи увидÑÑ‚ опцию Ñтого Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ нажатии кнопки «Создать».", + "addNewFileAction": "Добавить ÑопоÑтавление", + "builtinViewerType": "Ð’Ñтроенное приложение", + "wopiViewerType": "WOPI", + "customViewerType": "ПользовательÑкое", + "nMapping": "{{num}} ÑопоÑтавление(й)", + "editViewerTitle": "Редактировать {{name}}", + "builtInIconUrlDes": "Это вÑтроенное приложение имеет иконку по умолчанию. Когда URL иконки оÑтавлен пуÑтым, будет иÑпользоватьÑÑ Ð¸ÐºÐ¾Ð½ÐºÐ° по умолчанию.", + "viewerUrl": "URL приложениÑ", + "viewerUrlDes": "URL пользовательÑкого приложениÑ, поддерживаютÑÑ <0>магичеÑкие переменные.", + "addIcon": "Добавить иконку", + "exts": "СпиÑок раÑширений", + "icon": "Иконка", + "iconUrl": "URL иконки", + "iconColor": "Цвет", + "iconColorDark": "Цвет (темный режим)", + "fileIcons": "Иконки файлов", + "builtinIcon": "Ð’ÑтроеннаÑ", + "mimeMapping": "СопоÑтавление MIME-типов", + "mimeMappingDes": "СопоÑтавление MIME-типов в формате JSON, где ключ - раÑширение файла, а значение - MIME-тип. Cloudreve будет определÑть MIME-тип файла на оÑнове раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° и Ñтой наÑтройки.", + "mapProvider": "ПоÑтавщик карт", + "mapProviderDes": "ПоÑтавщик карт, иÑпользуемый Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ð¸ о меÑтоположении медиа.", + "mapGoogle": "Google Maps (Leaflet)", + "mapOpenStreetMap": "OpenStreetMap (Leaflet)", + "mapboxMap": "OpenStreetMap (Mapbox)", + "mapboxAccessToken": "Токен доÑтупа Mapbox", + "mapboxAccessTokenDes": "Публичный токен доÑтупа, Ñозданный в <0>конÑоли Mapbox.", + "tileType": "Тип плитки по умолчанию", + "tileTypeDes": "Тип плитки по умолчанию Ð´Ð»Ñ Google Maps.", + "tileTypeTerrain": "Рельеф", + "tileTypeSatellite": "Спутник", + "tileTypeGeneral": "Обычный", + "maxPageSize": "МакÑимальный размер Ñтраницы", + "maxPageSizeDes": "Ограничить макÑимальное количеÑтво файлов, которые пользователи могут наÑтроить на Ñтранице.", + "maxRecursiveSearch": "МакÑимальное количеÑтво рекурÑивных поиÑков", + "maxRecursiveSearchDes": "МакÑимальное количеÑтво рекурÑивных поиÑков, разрешенное при поиÑке файлов. ЕÑли количеÑтво найденных файлов превышает Ñтот лимит, поиÑк оÑтановитÑÑ Ð¸ предупредит пользователÑ.", + "maxBatchSize": "МакÑимальный размер пакета", + "maxBatchSizeDes": "МакÑимальное количеÑтво файлов, Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ð¼Ð¸ пользователи могут работать в пакете, будет подÑчитыватьÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ верхний уровень, количеÑтво файлов в подкаталогах не будет учитыватьÑÑ.", + "defaultPagination": "Метод пагинации Ð´Ð»Ñ ÑпиÑка файлов", + "cursorPagination": "КурÑÐ¾Ñ€Ð½Ð°Ñ Ð¿Ð°Ð³Ð¸Ð½Ð°Ñ†Ð¸Ñ", + "cursorPaginationDes": "Больше файлов будет автоматичеÑки загружено, когда пользователь прокрутит до конца. Этот метод работает лучше Ð´Ð»Ñ Ð±Ð¾Ð»ÑŒÑˆÐ¸Ñ… ÑпиÑков файлов, но общее количеÑтво Ñтраниц не видно.", + "offsetPagination": "Ð¡Ð¼ÐµÑ‰ÐµÐ½Ð½Ð°Ñ Ð¿Ð°Ð³Ð¸Ð½Ð°Ñ†Ð¸Ñ", + "offsetPaginationDes": "ÐÐ°Ð²Ð¸Ð³Ð°Ñ†Ð¸Ñ Ð¿Ð¾ Ñтраницам будет отображатьÑÑ Ð²Ð½Ð¸Ð·Ñƒ Ñтраницы; пользователи могут видеть общее количеÑтво Ñтраниц и переходить на определенную Ñтраницу. Этот метод работает немного хуже Ð´Ð»Ñ Ð±Ð¾Ð»ÑŒÑˆÐ¸Ñ… ÑпиÑков файлов.", + "defaultPaginationDes": "КурÑÐ¾Ñ€Ð½Ð°Ñ Ð¿Ð°Ð³Ð¸Ð½Ð°Ñ†Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ принудительно иÑпользоватьÑÑ Ð¿Ñ€Ð¸ поиÑке, незавиÑимо от приведенных выше наÑтроек.", + "publicResourceMaxAge": "МакÑимальный возраÑÑ‚ кÑша ÑтатичеÑких реÑурÑов (Ñекунды)", + "publicResourceMaxAgeDes": "МакÑимальный возраÑÑ‚ кÑша Ð´Ð»Ñ Ð¿ÑƒÐ±Ð»Ð¸Ñ‡Ð½Ð¾ доÑтупных ÑтатичеÑких реÑурÑов (например, файлы, миниатюры и Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ).", + "cronDes": "{{des}} ЗдеÑÑŒ требуетÑÑ Ð¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ñ‹Ð¹ <0>ÑинтакÑÐ¸Ñ Cron. Ð”Ð»Ñ Ð²ÑÑ‚ÑƒÐ¿Ð»ÐµÐ½Ð¸Ñ Ð² Ñилу необходим перезапуÑк Cloudreve.", + "entityCollectInterval": "Интервал переработки файловых блобов", + "entityCollectIntervalDes": "УÑтановить, как чаÑто Ñканировать и удалÑть иÑтекшие файловые блобы.", + "trashBinInterval": "Интервал ÑÐºÐ°Ð½Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ ÐºÐ¾Ñ€Ð·Ð¸Ð½Ñ‹", + "trashBinIntervalDes": "УÑтановить, как чаÑто Ñканировать и удалÑть иÑтекшие файлы в корзине.", + "logtoName": "Ðазвание метода входа", + "logtoNameDes": "Ðазвание метода входа, отображаемое пользователÑм. По умолчанию «SSO», поддержка ключа i18next.", + "logtoDirectSSO": "ПрÑмой вход", + "logtoDirectSSODes": "ЕÑли вы хотите пропуÑтить Ñкран входа Logto и напрÑмую перейти к Ñтороннему входу или SSO, заполните здеÑÑŒ идентификатор Ñоциального коннектора. Ð”Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð´Ñ€Ð¾Ð±Ð½Ð¾Ð¹ информации обратитеÑÑŒ к <0>документации Logto.", + "logtoEndpoint": "ÐšÐ¾Ð½ÐµÑ‡Ð½Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° Logto", + "logtoEndpointDes": "URL конечной точки Logto, полученный из панели ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸ÐµÐ¼, который может быть ÑамоÑтоÑтельно размещенным ÑкземплÑром.", + "logtoKey": "Секрет приложениÑ", + "logtoKeyDes": "Секрет приложениÑ, Ñозданный на Ñтранице ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸ÐµÐ¼.", + "logtoAppIDDes": "ID приложениÑ, Ñозданного на Ñтранице ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸ÐµÐ¼.", + "logto": "Logto", + "logtoDes": "С <0>Logto вы можете реализовать больше входов через Ñторонние платформы, такие как Apple, GitHub, Microsoft Entra ID, Google, SMS и Ñ‚.д. Создайте «Традиционное веб-приложение» на портале ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Logto и добавьте <1>{{url}} в «URI перенаправлениÑ».", + "thirdPartySignIn": "Сторонний вход", + "logo": "ЛОГОТИП", + "logoDes": "URL ЛОГОТИПÐ, предоÑтавьте различные логотипы Ð´Ð»Ñ Ñ‚ÐµÐ¼Ð½Ð¾Ð³Ð¾ и Ñветлого режимов.", + "dark": "Темный режим", + "light": "Светлый режим", + "tosUrl": "URL уÑловий обÑлуживаниÑ", + "tosUrlDes": "Будет отображатьÑÑ Ð² нижнем колонтитуле Ñтраницы входа или региÑтрации, оÑтавьте пуÑтым, чтобы не отображать.", + "privacyUrl": "URL политики конфиденциальноÑти", + "privacyUrlDes": "Будет отображатьÑÑ Ð² нижнем колонтитуле Ñтраницы входа или региÑтрации, оÑтавьте пуÑтым, чтобы не отображать.", + "addSecondary": "Добавить дополнительный URL Ñайта", + "secondarySiteURL": "Дополнительный", + "secondaryDes": "Ð’Ñ‹ также можете добавить другие дополнительные URL, Cloudreve автоматичеÑки выберет, иÑпользовать ли их, оÑновываÑÑÑŒ на URL, к которому фактичеÑки обращаетÑÑ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÑŒ. Ваш URL Ñайта должен быть лицензирован.", + "primarySiteURL": "ОÑновной", + "primarySiteURLDes": "ОÑновной URL Ñайта иÑпользуетÑÑ Ð´Ð»Ñ ÑвÑзи Ñ Ð²Ð½ÐµÑˆÐ½Ð¸Ð¼Ð¸ ÑервиÑами и Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¾Ð±Ñ€Ð°Ñ‚Ð½Ñ‹Ñ… вызовов (например, платежи, поÑтавщик хранилища), иÑпользуйте URL, доÑтупный из WAN.", + "revert": "Отменить изменениÑ", + "saved": "ÐаÑтройки Ñохранены.", + "save": "Сохранить", + "basicInformation": "ОÑÐ½Ð¾Ð²Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ", + "mainTitle": "Ðазвание Ñайта", + "mainTitleDes": "Ðазвание ÑкземплÑра.", + "siteDescription": "ОпиÑание Ñайта", + "siteDescriptionDes": "ОпиÑание веб-Ñайта, которое может отображатьÑÑ Ð² Ñводке общей Ñтраницы.", + "siteURL": "URL Ñайта", + "customFooterHTML": "Код нижнего колонтитула", + "customFooterHTMLDes": "ПользовательÑкий HTML-код, вÑтавлÑемый в нижнюю чаÑть Ñтраницы.", + "announcement": "ОбъÑвление Ñайта", + "announcementDes": "ОбъÑвление, отображаемое авторизованным пользователÑм. ОÑтавьте пуÑтым, чтобы не показывать. При изменении Ñтого Ñодержимого вÑе пользователи увидÑÑ‚ объÑвление заново.", + "supportHTML": "Поддержка HTML-кода", + "branding": "Иконки", + "smallIcon": "ÐœÐ°Ð»Ð°Ñ Ð¸ÐºÐ¾Ð½ÐºÐ°", + "smallIconDes": "ÐÐ´Ñ€ÐµÑ Ð¼Ð°Ð»Ð¾Ð¹ иконки в формате ico или svg. Эта иконка также будет иÑпользоватьÑÑ Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð²Ð¾ вкладках браузера, закладках и Ñрлыках рабочего Ñтола.", + "mediumIcon": "СреднÑÑ Ð¸ÐºÐ¾Ð½ÐºÐ°", + "mediumIconDes": "ÐÐ´Ñ€ÐµÑ Ñредней иконки размером 192x192 в формате png.", + "largeIcon": "Ð‘Ð¾Ð»ÑŒÑˆÐ°Ñ Ð¸ÐºÐ¾Ð½ÐºÐ°", + "largeIconDes": "ÐÐ´Ñ€ÐµÑ Ð±Ð¾Ð»ÑŒÑˆÐ¾Ð¹ иконки размером 512x512 в формате png. Эта иконка также будет иÑпользоватьÑÑ Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ переключении Ñайтов в iOS-клиенте.", + "displayMode": "Режим отображениÑ", + "displayModeDes": "Режим Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ PWA-Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾Ñле добавлениÑ.", + "themeColor": "Цвет темы", + "themeColorDes": "CSS-значение цвета, влиÑющее на цвет Ñтроки ÑоÑтоÑÐ½Ð¸Ñ Ð½Ð° Ñкране запуÑка PWA, Ñтроки ÑоÑтоÑÐ½Ð¸Ñ Ð½Ð° Ñтраницах контента и адреÑной Ñтроки.", + "backgroundColor": "Цвет фона", + "backgroundColorDes": "CSS-значение цвета", + "hint": "ПодÑказка", + "webauthnNoHttps": "Web Authn требует, чтобы ваш Ñайт иÑпользовал HTTPS, и убедитеÑÑŒ, что в наÑтройках параметров - Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ Ñайте - URL Ñайта также иÑпользуетÑÑ HTTPS, прежде чем включать Ñту функцию.", + "accountManagement": "РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð¸ вход", + "allowNewRegistrations": "Разрешить региÑтрацию новых пользователей", + "allowNewRegistrationsDes": "ПоÑле Ð¾Ñ‚ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð½Ð¾Ð²Ñ‹Ðµ пользователи не Ñмогут региÑтрироватьÑÑ Ñ‡ÐµÑ€ÐµÐ· интерфейÑ.", + "emailActivation": "ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð¿Ð¾ Ñлектронной почте", + "emailActivationDes": "ПоÑле Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð½Ð¾Ð²Ñ‹Ðµ пользователи должны будут нажать на ÑÑылку активации в Ñлектронном пиÑьме Ð´Ð»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ñ€ÐµÐ³Ð¸Ñтрации. УбедитеÑÑŒ, что <0>наÑтройки отправки Ñлектронной почты корректны, иначе пиÑьма активации не будут доÑтавлены.", + "captchaForSignup": "Капча при региÑтрации", + "captchaForSignupDes": "Включить ли капчу в форме региÑтрации.", + "captchaForLogin": "Капча при входе", + "captchaForLoginDes": "Включить ли капчу в форме входа.", + "captchaForReset": "Капча при воÑÑтановлении паролÑ", + "captchaForResetDes": "Включить ли капчу в форме воÑÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ.", + "captchaForAbuseReport": "Капча при жалобе на нарушениÑ", + "captchaForAbuseReportDes": "Включить ли капчу в форме жалобы на нарушениÑ.", + "webauthnDes": "Разрешить ли пользователÑм входить Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ привÑзанных уÑтройÑтв аппаратной аутентификации, таких как: лицо, отпечаток пальца или USB-ключ; Ñайт должен иÑпользовать HTTPS Ð´Ð»Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ Ñтой функции.", + "webauthn": "Вход Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ ключей доÑтупа", + "defaultSymbolics": "Ðачальные Ñрлыки общих реÑурÑов", + "defaultSymbolicsDes": "Ярлыки ÑÑылок Ð´Ð»Ñ Ð¾Ð±Ñ‰ÐµÐ³Ð¾ доÑтупа, которые по умолчанию ÑущеÑтвуют в корневом каталоге новых пользователей. ПожалуйÑта, найдите ÑÑылки Ð´Ð»Ñ Ð¾Ð±Ñ‰ÐµÐ³Ð¾ доÑтупа по цифровому ID. Ð’Ñ‹ можете увидеть цифровой ID в крайнем левом Ñтолбце <0>ÑпиÑка общих реÑурÑов.", + "searchShare": "ПоиÑк ID общего реÑурÑа...", + "defaultGroup": "Группа пользователей по умолчанию", + "defaultGroupDes": "ÐÐ°Ñ‡Ð°Ð»ÑŒÐ½Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° пользователей поÑле региÑтрации.", + "testMailSent": "ТеÑтовое пиÑьмо отправлено", + "testSMTPSettings": "ТеÑÑ‚ отправки", + "testSMTPTooltip": "Cloudreve отправит теÑтовое пиÑьмо, иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ Ð²Ð°ÑˆÐ¸ текущие наÑтройки SMTP. СохранÑть наÑтройки перед теÑтированием не требуетÑÑ.", + "recipient": "ÐÐ´Ñ€ÐµÑ Ð¿Ð¾Ð»ÑƒÑ‡Ð°Ñ‚ÐµÐ»Ñ", + "send": "Отправить", + "smtp": "Отправка пиÑем", + "senderName": "Ð˜Ð¼Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð¸Ñ‚ÐµÐ»Ñ", + "senderNameDes": "Ð˜Ð¼Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð¸Ñ‚ÐµÐ»Ñ, отображаемое в Ñлектронном пиÑьме.", + "senderAddress": "ÐÐ´Ñ€ÐµÑ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð¸Ñ‚ÐµÐ»Ñ", + "senderAddressDes": "ÐÐ´Ñ€ÐµÑ Ñлектронной почты отправителÑ.", + "smtpServer": "SMTP-Ñервер", + "smtpServerDes": "ÐÐ´Ñ€ÐµÑ Ñервера отправки, без номера порта.", + "smtpPort": "SMTP-порт", + "smtpPortDes": "Ðомер порта Ñервера отправки.", + "smtpUsername": "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ SMTP", + "smtpUsernameDes": "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¿Ð¾Ñ‡Ñ‚Ð¾Ð²Ð¾Ð³Ð¾ Ñщика отправителÑ, обычно Ñовпадает Ñ Ð°Ð´Ñ€ÐµÑом Ñлектронной почты.", + "smtpPassword": "Пароль SMTP", + "smtpPasswordDes": "Пароль почтового Ñщика отправителÑ", + "replyToAddress": "ÐÐ´Ñ€ÐµÑ Ð´Ð»Ñ Ð¾Ñ‚Ð²ÐµÑ‚Ð¾Ð²", + "replyToAddressDes": "Почтовый Ñщик Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¾Ñ‚Ð²ÐµÑ‚Ð¾Ð², когда пользователи отвечают на пиÑьма, отправленные ÑиÑтемой.", + "enforceSSL": "Принудительное иÑпользование SSL-ÑоединениÑ", + "enforceSSLDes": "Принудительно ли иÑпользовать SSL-шифрованное Ñоединение. ЕÑли не удаетÑÑ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð¸Ñ‚ÑŒ Ñлектронную почту, можно отключить Ñту опцию, Cloudreve попытаетÑÑ Ð¸Ñпользовать STARTTLS и решит, иÑпользовать ли шифрованное Ñоединение.", + "smtpTTL": "Срок дейÑÑ‚Ð²Ð¸Ñ SMTP-ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ (Ñекунды)", + "smtpTTLDes": "SMTP-ÑоединениÑ, уÑтановленные в течение Ñрока дейÑтвиÑ, будут повторно иÑпользоватьÑÑ Ð½Ð¾Ð²Ñ‹Ð¼Ð¸ запроÑами на отправку Ñлектронной почты.", + "emailTemplates": "Шаблоны Ñлектронных пиÑем", + "activateNewUser": "ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ пользователÑ", + "resetPassword": "Ð¡Ð±Ñ€Ð¾Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ", + "sendTestEmail": "Отправить теÑтовое пиÑьмо", + "transportation": "Передача данных", + "workerNum": "КоличеÑтво воркеров", + "workerNumDes": "МакÑимальное количеÑтво задач, выполнÑемых параллельно в очереди задач главного узла. ТребуетÑÑ Ð¿ÐµÑ€ÐµÐ·Ð°Ð¿ÑƒÑк Cloudreve поÑле ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð²ÑÑ‚ÑƒÐ¿Ð»ÐµÐ½Ð¸Ñ Ð² Ñилу", + "tempFolder": "Ð’Ñ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ Ð¿Ð°Ð¿ÐºÐ°", + "tempFolderDes": "Путь к каталогу Ð´Ð»Ñ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð²Ñ€ÐµÐ¼ÐµÐ½Ð½Ñ‹Ñ… файлов, Ñоздаваемых при выполнении задач раÑпаковки, ÑÐ¶Ð°Ñ‚Ð¸Ñ Ð¸ Ñ‚.д.", + "textEditMaxSize": "МакÑимальный размер Ð´Ð»Ñ Ð¾Ð½Ð»Ð°Ð¹Ð½-Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð¾Ð²", + "textEditMaxSizeDes": "МакÑимальный размер файлов документов, которые можно редактировать онлайн. Файлы, превышающие Ñтот размер, Ð½ÐµÐ»ÑŒÐ·Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ñ‚ÑŒ онлайн. Эта наÑтройка применÑетÑÑ Ðº текÑтовым файлам, файлам кода, документам Office (WOPI) и другим веб-редакторам.", + "resetConnection": "Принудительный ÑÐ±Ñ€Ð¾Ñ ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ Ñбое проверки загрузки", + "resetConnectionDes": "ПоÑле включениÑ, еÑли проверка загрузки данных политики, аватара и Ñ‚.д. не удалаÑÑŒ, Ñервер принудительно ÑброÑит Ñоединение", + "batchDownload": "ÐŸÐ°ÐºÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ°", + "previewURL": "СÑылка предварительного проÑмотра", + "cannotDeleteDefaultTheme": "ÐÐµÐ»ÑŒÐ·Ñ ÑƒÐ´Ð°Ð»Ð¸Ñ‚ÑŒ тему по умолчанию", + "themeConfig": "ÐаÑтройка цветов", + "actions": "ДейÑтвиÑ", + "wrongFormat": "Ðеправильный формат", + "avatar": "Ðватар", + "gravatarServer": "Сервер Gravatar", + "gravatarServerDes": "ÐÐ´Ñ€ÐµÑ Ñервера Gravatar, можно выбрать иÑпользование отечеÑтвенного зеркала", + "avatarFilePath": "Путь Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð°Ð²Ð°Ñ‚Ð°Ñ€Ð¾Ð²", + "avatarFilePathDes": "Путь Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÑŒÑких аватаров, загружаемых пользователÑми, отноÑительно каталога данных Cloudreve.", + "avatarSize": "Ограничение размера файла аватара", + "avatarSizeDes": "МакÑимальный размер файла аватара, который могут загружать пользователи", + "avatarImageSize": "Размер Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ (px)", + "avatarImageSizeDes": "Загружаемые пользователÑми аватары будут изменены до заданного размера в пикÑелÑÑ….", + "filePreview": "Предварительный проÑмотр файлов", + "thumbnails": "Миниатюры", + "thumbnailDoc": "Ð”Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð¹ информации о наÑтройке миниатюр, пожалуйÑта, обратитеÑÑŒ к <0>официальной документации.", + "thumbnailDocLink": "https://docs.cloudreve.org/usage/thumbnails", + "thumbnailBasic": "ОÑновные наÑтройки", + "generators": "Генераторы миниатюр", + "thumbMaxSize": "МакÑимальный размер иÑходного файла", + "thumbMaxSizeDes": "МакÑимальный размер иÑходного файла, Ð´Ð»Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ð¾Ð³Ð¾ можно Ñоздать миниатюру. Файлы, превышающие Ñтот размер, не будут генерировать миниатюры.", + "generatorProxyWarning": "По умолчанию, Ñтратегии хранениÑ, отличные от локального, будут иÑпользовать только генератор \"Ðативный Ð´Ð»Ñ Ñтратегии хранениÑ\". Ð’Ñ‹ можете включить функцию \"ПрокÑи генератора\" на Ñтранице наÑтроек Ñтратегии хранениÑ, чтобы раÑширить возможноÑти миниатюр Ð´Ð»Ñ Ñторонних Ñтратегий хранениÑ. ПодробноÑти Ñм. в <0>официальной документации.", + "policyBuiltin": "Ðативный Ð´Ð»Ñ Ñтратегии хранениÑ", + "policyBuiltinDes": "ИÑпользует нативный Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸ изображений поÑтавщика хранилища. Ð”Ð»Ñ Ð»Ð¾ÐºÐ°Ð»ÑŒÐ½Ñ‹Ñ… и S3 Ñтратегий Ñтот генератор недоÑтупен и автоматичеÑки переключитÑÑ Ð½Ð° другие генераторы. Ð”Ð»Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… Ñтратегий Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¿ÐµÑ€ÐµÐ¹Ð´Ð¸Ñ‚Ðµ на Ñтраницу наÑтроек Ñтратегии хранениÑ, чтобы наÑтроить разрешенные раÑширениÑ.", + "cloudreveBuiltin": "Ð’Ñтроенный в Cloudreve", + "cloudreveBuiltinDes": "ИÑпользует вÑтроенные возможноÑти обработки изображений Cloudreve, поддерживает только Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð² форматах PNG, JPEG, GIF.", + "libreOffice": "LibreOffice", + "libreOfficeDes": "ИÑпользует LibreOffice Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¼Ð¸Ð½Ð¸Ð°Ñ‚ÑŽÑ€ документов Office. Этот генератор завиÑит от любого другого генератора изображений (вÑтроенного в Cloudreve или VIPS).", + "libraw": "LibRaw / DCRaw", + "librawDes": "ИÑпользует процедуры ÑмулÑции DCRaw, поÑтавлÑемые Ñ LibRaw, или оригинальную программу DCRaw Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¼Ð¸Ð½Ð¸Ð°Ñ‚ÑŽÑ€ RAW-изображений.", + "vips": "VIPS", + "vipsDes": "ИÑпользует libvips Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸ миниатюр изображений, поддерживает больше форматов изображений Ñ Ð¼ÐµÐ½ÑŒÑˆÐ¸Ð¼ потреблением реÑурÑов.", + "thumbDependencyWarning": "Генераторы LibreOffice или обложек пеÑен завиÑÑÑ‚ от вÑтроенного в Cloudreve или VIPS генератора, пожалуйÑта, включите любой из них.", + "ffmpeg": "FFmpeg", + "ffmpegDes": "ИÑпользует FFmpeg Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¼Ð¸Ð½Ð¸Ð°Ñ‚ÑŽÑ€ видео.", + "executable": "ИÑполнÑемый файл", + "executableDes": "Путь или команда к иÑполнÑемому файлу Ñтороннего генератора.", + "executableTest": "ТеÑÑ‚", + "executableTestSuccess": "Генератор работает нормально, верÑиÑ: {{version}}", + "generatorExts": "ДоÑтупные раÑширениÑ", + "generatorExtsDes": "СпиÑок раÑширений файлов, доÑтупных Ð´Ð»Ñ Ñтого генератора. Ð”Ð»Ñ Ð½ÐµÑкольких раÑширений иÑпользуйте запÑтую , Ð´Ð»Ñ Ñ€Ð°Ð·Ð´ÐµÐ»ÐµÐ½Ð¸Ñ.", + "ffmpegSeek": "ÐŸÐ¾Ð·Ð¸Ñ†Ð¸Ñ Ð·Ð°Ñ…Ð²Ð°Ñ‚Ð° миниатюры", + "ffmpegSeekDes": "ОпределÑет Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ñ…Ð²Ð°Ñ‚Ð° миниатюры. РекомендуетÑÑ Ð²Ñ‹Ð±Ð¸Ñ€Ð°Ñ‚ÑŒ меньшие Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÑƒÑÐºÐ¾Ñ€ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ñ†ÐµÑÑа генерации. ЕÑли превышает фактичеÑкую длину видео, захват миниатюры не удаÑÑ‚ÑÑ.", + "ffmpegExtraArgs": "Дополнительные входные параметры", + "ffmpegExtraArgsDes": "Дополнительные параметры, вводимые при вызове FFmpeg.", + "generatorProxy": "ПрокÑи генератора", + "enableThumbProxy": "ИÑпользовать прокÑи генератора", + "proxyPolicyList": "Стратегии Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ‹Ð¼ прокÑи", + "proxyPolicyListDes": "Можно выбрать неÑколько. ПоÑле выбора типы, Ð´Ð»Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… ÑÑ‚Ñ€Ð°Ñ‚ÐµÐ³Ð¸Ñ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð½Ðµ поддерживает нативную генерацию миниатюр, будут генерироватьÑÑ Ñ‡ÐµÑ€ÐµÐ· прокÑи Cloudreve.", + "thumbWidth": "МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ ÑˆÐ¸Ñ€Ð¸Ð½Ð°", + "thumbHeight": "МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð²Ñ‹Ñота", + "thumbSuffix": "Ð¡ÑƒÑ„Ñ„Ð¸ÐºÑ Blob-файла", + "thumbSuffixDes": "СуффикÑ, добавлÑемый к Ñозданному Blob миниатюры отноÑительно иÑходного Blob,", + "thumbFormat": "Формат миниатюры", + "thumbFormatDes": "Предпочтительный формат миниатюры. ЕÑли генератор не поддерживает его, автоматичеÑки переключитÑÑ Ð½Ð° формат jpg.", + "thumbQuality": "КачеÑтво изображениÑ", + "thumbQualityDes": "Процент качеÑтва ÑжатиÑ, дейÑтвует только Ð´Ð»Ñ ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ jpg и webp.", + "thumbGC": "ÐÐµÐ¼ÐµÐ´Ð»ÐµÐ½Ð½Ð°Ñ Ð¾Ñ‡Ð¸Ñтка памÑти поÑле генерации", + "captcha": "Капча", + "captchaType": "Тип капчи", + "captchaTypeDes": "Выберите тип капчи и поÑтавщика уÑлуг капчи.", + "plainCaptcha": "ГрафичеÑкаÑ", + "reCaptchaV2": "reCAPTCHA V2", + "turnstile": "Cloudflare Turnstile", + "turnstileSiteKey": "Ключ Ñайта", + "turnstileSiteKSecret": "Секретный ключ", + "cap": "Cap", + "capInstanceURL": "URL ÑкземплÑра", + "capInstanceURLDes": "URL-Ð°Ð´Ñ€ÐµÑ ÑамоÑтоÑтельно развернутого Ñервера Cap. Подробную информацию Ñм. в <0>документации по режиму ÑамоÑтоÑтельного развертываниÑ.", + "capSiteKey": "Ключ Ñайта", + "capSiteKeyDes": "Ключ Ñайта, полученный из панели ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñервера Cap.", + "capSecretKey": "Секретный ключ", + "capSecretKeyDes": "Секретный ключ, полученный из панели ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñервера Cap.", + "capAssetServer": "ИÑточник ÑтатичеÑких реÑурÑов", + "capAssetServerDes": "Выберите иÑточник загрузки ÑтатичеÑких реÑурÑов капчи Cap. Ð”Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ ÑамоÑтоÑтельно развернутого Ñервера необходимо уÑтановить переменные Ñреды на Ñтороне Ñервера, Ñм. <0>Включение Ñлужбы ÑтатичеÑких реÑурÑов.", + "capAssetServerJsdelivr": "jsDelivr CDN", + "capAssetServerUnpkg": "unpkg CDN", + "capAssetServerInstance": "СамоÑтоÑтельно развернутый Ñервер", + "captchaProvider": "Тип капчи", + "captchaWidth": "Ширина", + "captchaHeight": "Ð’Ñ‹Ñота", + "captchaLength": "Длина", + "captchaLengthDes": "Длина Ñимволов в капче.", + "captchaMode": "Режим", + "captchaModeNumber": "Цифры", + "captchaModeLetter": "Буквы", + "captchaModeMath": "Ðрифметика", + "captchaModeNumberLetter": "Цифры+буквы", + "captchaElement": "Форма капчи", + "complexOfNoiseText": "УÑиленный помехи текÑта", + "complexOfNoiseDot": "УÑиленные помехи точек", + "showHollowLine": "ИÑпользовать полые линии", + "showNoiseDot": "ИÑпользовать шумовые точки", + "showNoiseText": "ИÑпользовать помехи текÑта", + "showSlimeLine": "ИÑпользовать волниÑтые линии", + "showSineLine": "ИÑпользовать ÑинуÑоидальные линии", + "siteKey": "Site KEY", + "siteKeyDes": "Ключ веб-Ñайта, полученный Ñо <0>Ñтраницы ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñми.", + "siteSecret": "Secret", + "siteSecretDes": "Секретный ключ, полученный Ñо <0>Ñтраницы ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñми.", + "secretID": "SecretId", + "secretIDDes": "SecretId, полученный Ñо <0>Ñтраницы ключей доÑтупа", + "secretKey": "SecretKey", + "secretKeyDes": "SecretKey, полученный Ñо <0>Ñтраницы ключей доÑтупа", + "tCaptchaAppID": "APPID", + "tCaptchaAppIDDes": "APPID, полученный Ñо <0>Ñтраницы графичеÑкой верификации", + "tCaptchaSecretKey": "App Secret Key", + "tCaptchaSecretKeyDes": "App Secret Key, полученный Ñо <0>Ñтраницы графичеÑкой верификации", + "staticResourceCache": "КÑш ÑтатичеÑких публичных реÑурÑов", + "staticResourceCacheDes": "Срок дейÑÑ‚Ð²Ð¸Ñ ÐºÑша Ð´Ð»Ñ Ð¿ÑƒÐ±Ð»Ð¸Ñ‡Ð½Ð¾ доÑтупных ÑтатичеÑких реÑурÑов (например: прÑмые ÑÑылки локальной Ñтратегии, ÑÑылки загрузки файлов)", + "creditSystem": "СиÑтема баллов", + "creditAndVAS": "Баллы и дополнительные уÑлуги", + "enableCredit": "Включить ÑиÑтему баллов", + "enableCreditDes": "Включить ÑиÑтему баллов, позволÑющую пользователÑм уÑтанавливать цены Ð´Ð»Ñ ÑÑылок общего доÑтупа.", + "creditPrice": "Цена баллов", + "creditPriceDes": "Цена Ð¿Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð±Ð°Ð»Ð»Ð¾Ð² валютой (в минимальных единицах валюты). Введите 0, чтобы запретить пополнение баллов.", + "shareScoreRate": "Процент комиÑÑии Ð´Ð»Ñ Ð²Ð»Ð°Ð´ÐµÐ»ÑŒÑ†Ð° ÑÑылки", + "shareScoreRateDes": "Процент баллов (1-100), который получает владелец ÑÑылки при покупке ÑÑылки общего доÑтупа", + "cronNotifyUser": "Интервал ÑÐºÐ°Ð½Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð´Ð»Ñ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÐµÐ¹ Ñ Ð¿Ñ€ÐµÐ²Ñ‹ÑˆÐµÐ½Ð¸ÐµÐ¼ лимита", + "cronNotifyUserDes": "Сканировать и отправлÑть Ñлектронные пиÑьма Ñ Ð½Ð°Ð¿Ð¾Ð¼Ð¸Ð½Ð°Ð½Ð¸Ñми пользователÑм Ñ Ð¿Ñ€ÐµÐ²Ñ‹ÑˆÐµÐ½Ð¸ÐµÐ¼ лимита,", + "cronBanUser": "Интервал ÑÐºÐ°Ð½Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð´Ð»Ñ Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²ÐºÐ¸ пользователей", + "cronBanUserDes": "Сканировать и блокировать пользователей, превыÑивших хранилище и льготный период", + "anonymousPurchase": "ÐÐ½Ð¾Ð½Ð¸Ð¼Ð½Ð°Ñ Ð¿Ð¾ÐºÑƒÐ¿ÐºÐ°", + "anonymousPurchaseDes": "Разрешить неавторизованным пользователÑм напрÑмую покупать ÑÑылки общего доÑтупа.", + "shopNavEnabled": "Показать навигацию магазина", + "shopNavEnabledDes": "Отображать пункт \"Магазин\" в боковой навигации.", + "paymentSettings": "ÐаÑтройки платежей", + "currencyCode": "Код валюты", + "currencyCodeDes": "Трехбуквенный код валюты (например, USD, CNY, EUR)", + "currencySymbol": "Символ валюты", + "currencySymbolDes": "Отображаемый Ñимвол валюты (например, $, Â¥, €)", + "currencyUnit": "Единица валюты", + "currencyUnitDes": "ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ ÐµÐ´Ð¸Ð½Ð¸Ñ†Ð° валюты (например, доллар/цент = 100)", + "paymentProviders": "ПоÑтавщики платежей", + "providerName": "Ðазвание поÑтавщика Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñм.", + "providerType": "Тип поÑтавщика", + "providerKey": "Ключ", + "selectCurrency": "Выбрать популÑрную валюту", + "addPaymentProvider": "Добавить поÑтавщика платежей", + "stripeProvider": "Stripe", + "weixinProvider": "WeChat Pay", + "alipayProvider": "Alipay", + "customProvider": "ПользовательÑкий платежный канал", + "customProviderDes": "Ð˜Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸Ñ Ñ Ð´Ñ€ÑƒÐ³Ð¸Ð¼Ð¸ Ñторонними платежными платформами путем реализации ÑовмеÑтимого Ñ Cloudreve платежного интерфейÑа. ПодробноÑти Ñм. в <0>официальной документации.", + "providerKeyDes": "Введите API-ключ Stripe.", + "storageProductSettings": "Продукты хранилища", + "storageProductsDes": "ÐаÑтройте продукты, которые пользователи могут приобреÑти Ð´Ð»Ñ Ñ€Ð°ÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾ÑтранÑтва хранениÑ.", + "addStorageProduct": "Добавить продукт", + "editStorageProduct": "Редактировать продукт", + "storageSize": "Размер хранилища", + "storageSizeBytes": "Размер хранилища, включенный в Ñтот продукт.", + "duration": "ПродолжительноÑть", + "durationSeconds": "ПродолжительноÑть (в Ñекундах, например: 2592000 означает 30 дней).", + "price": "Цена", + "priceInUnits": "Цена (в минимальных единицах валюты)", + "priceInUnitsDes": "Цена будет отображатьÑÑ ÐºÐ°Ðº:", + "chipLabel": "Метка (необÑзательно)", + "chipLabelHelp": "ÐšÐ¾Ñ€Ð¾Ñ‚ÐºÐ°Ñ Ñ‚ÐµÐºÑÑ‚Ð¾Ð²Ð°Ñ Ð¼ÐµÑ‚ÐºÐ°, Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶Ð°ÐµÐ¼Ð°Ñ Ñ€Ñдом Ñ Ð½Ð°Ð·Ð²Ð°Ð½Ð¸ÐµÐ¼ продукта.", + "usePoints": "Разрешить иÑпользование баллов", + "points": "Баллы", + "pointsHelp": "КоличеÑтво баллов, необходимое Ð´Ð»Ñ Ð¿Ð¾ÐºÑƒÐ¿ÐºÐ¸ Ñтого продукта.", + "pointsUnit": "баллов", + "groupProductSettings": "Продукты групп пользователей", + "groupProductsDes": "ÐаÑтройте продукты, которые пользователи могут приобреÑти Ð´Ð»Ñ Ð¿Ñ€Ð¸ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ðº определенным группам пользователей.", + "addGroupProduct": "Добавить продукт группы пользователей", + "editGroupProduct": "Редактировать продукт группы пользователей", + "groupId": "ID группы пользователей", + "groupIdHelp": "Группа пользователей, в которую будет переведен пользователь поÑле покупки Ñтого продукта.", + "description": "ОпиÑание", + "descriptionHelp": "Введите оÑобенноÑти или преимущеÑтва, по одному на Ñтроку", + "receiptEmailTemplate": "Шаблон чека об оплате", + "receiptEmailTemplateDes": "Шаблон Ñлектронного пиÑьма, отправлÑемого пользователю при подтверждении платежа.", + "activationEmailTemplate": "Шаблон активации аккаунта", + "activationEmailTemplateDes": "Шаблон Ñлектронного пиÑьма, отправлÑемого пользователю при активации аккаунта.", + "quotaExceededEmailTemplate": "Шаблон Ð¿Ñ€ÐµÐ²Ñ‹ÑˆÐµÐ½Ð¸Ñ ÐºÐ²Ð¾Ñ‚Ñ‹ хранилища", + "quotaExceededEmailTemplateDes": "Шаблон Ñлектронного пиÑьма, отправлÑемого пользователю при превышении квоты хранилища.", + "resetPasswordEmailTemplate": "Шаблон ÑброÑа паролÑ", + "resetPasswordEmailTemplateDes": "Шаблон Ñлектронного пиÑьма, отправлÑемого пользователю при запроÑе ÑброÑа паролÑ.", + "preferredLanguage": "Предпочитаемый Ñзык", + "setAsPreferredLanguage": "УÑтановить как предпочитаемый Ñзык", + "setAsPreferredLanguageDes": "ЕÑли Ñзык Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð½Ðµ может быть определен, будет иÑпользоватьÑÑ Ð¿Ñ€ÐµÐ´Ð¿Ð¾Ñ‡Ð¸Ñ‚Ð°ÐµÐ¼Ñ‹Ð¹ Ñзык.", + "alreadyAsPreferredLanguageDes": "Этот Ñзык уже уÑтановлен как предпочитаемый. ЕÑли Ñзык Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð½Ðµ может быть определен, будет иÑпользоватьÑÑ Ñтот шаблон пиÑьма.", + "addLanguage": "Добавить Ñзык", + "removeLanguage": "Удалить Ñзык", + "removeLanguageBtn": "Удалить Ñзык", + "cannotRemovePreferredLanguageDes": "ÐÐµÐ»ÑŒÐ·Ñ ÑƒÐ´Ð°Ð»Ð¸Ñ‚ÑŒ предпочитаемый Ñзык. ПожалуйÑта, уÑтановите другой Ñзык в качеÑтве предпочитаемого и попробуйте Ñнова.", + "languageCodeDes": "ПожалуйÑта, выберите Ñзык Ð´Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ.", + "emailSubject": "Тема пиÑьма", + "emailSubjectDes": "Тема Ñлектронного пиÑьма. Ð’Ñ‹ можете иÑпользовать <0>магичеÑкие переменные Ð´Ð»Ñ Ð½Ð°Ñтройки темы пиÑьма.", + "emailBody": "Содержимое пиÑьма", + "emailBodyDes": "Содержимое Ñлектронного пиÑьма. Ð’Ñ‹ можете иÑпользовать <0>магичеÑкие переменные Ð´Ð»Ñ Ð½Ð°Ñтройки Ñодержимого пиÑьма.", + "orderTitle": "Ðазвание заказа", + "themeOptions": "Параметры темы", + "themeOptionsDes": "ÐаÑтройте пользовательÑкие параметры темы Ð´Ð»Ñ Ð²Ð°ÑˆÐµÐ³Ð¾ Ñайта. Эти темы будут доÑтупны пользователÑм в их наÑтройках предпочтений.", + "primaryColor": "ОÑновной цвет", + "secondaryColor": "Вторичный цвет", + "primaryColorDark": "ОÑновной цвет (темный режим)", + "secondaryColorDark": "Вторичный цвет (темный режим)", + "addThemeOption": "Добавить параметр темы", + "editThemeOption": "Редактировать параметр темы", + "invalidThemeConfig": "ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ ÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ñ‚ÐµÐ¼Ñ‹. ПожалуйÑта, проверьте ÑинтакÑÐ¸Ñ JSON.", + "themeConfiguration": "ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ñ‚ÐµÐ¼Ñ‹", + "themePreview": "Предварительный проÑмотр темы", + "lightTheme": "Ð¡Ð²ÐµÑ‚Ð»Ð°Ñ Ñ‚ÐµÐ¼Ð°", + "darkTheme": "Ð¢ÐµÐ¼Ð½Ð°Ñ Ñ‚ÐµÐ¼Ð°", + "previewTitle": "Заголовок предварительного проÑмотра", + "previewTextField": "Поле ввода", + "previewPrimary": "ОÑновной цвет", + "invalidThemePreview": "ÐедопуÑÑ‚Ð¸Ð¼Ð°Ñ ÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ñ‚ÐµÐ¼Ñ‹, предварительный проÑмотр невозможен", + "duplicateThemeColor": "Уже ÑущеÑтвует тема Ñ Ñтим оÑновным цветом. ПожалуйÑта, выберите другой цвет.", + "themeDes": "Полный ÑпиÑок наÑтраиваемых параметров Ñм. в <0>Material-UI Default theme viewer.", + "defaultTheme": "По умолчанию", + "auditLog": "СобытиÑ", + "auditLogDes": "ÐаÑтройте, какие ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð´Ð¾Ð»Ð¶Ð½Ñ‹ региÑтрироватьÑÑ. Ðекоторые ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ иÑпользоватьÑÑ ÑиÑтемой Ð´Ð»Ñ Ð¿Ñ€ÐµÐ´Ð¾ÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ñ‹Ñ… функций, например, активноÑть файлов и активноÑть входа в ÑиÑтему.", + "systemEvents": "СиÑтемные ÑобытиÑ", + "systemEventsDes": "СобытиÑ, ÑвÑзанные Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñми и ÑоÑтоÑнием ÑиÑтемы.", + "userEvents": "ПользовательÑкие ÑобытиÑ", + "userEventsDes": "СобытиÑ, ÑвÑзанные Ñ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð°Ð¼Ð¸ пользователей, аутентификацией и изменениÑми профилÑ.", + "fileEvents": "Файловые ÑобытиÑ", + "fileEventsDes": "СобытиÑ, ÑвÑзанные Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñми Ñ Ñ„Ð°Ð¹Ð»Ð°Ð¼Ð¸, такими как загрузка, Ñкачивание и изменение.", + "shareEvents": "Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð¾Ð±Ñ‰ÐµÐ³Ð¾ доÑтупа", + "shareEventsDes": "СобытиÑ, ÑвÑзанные Ñ Ð¾Ð±Ñ‰Ð¸Ð¼ доÑтупом к файлам и доÑтупом по ÑÑылкам.", + "versionEvents": "Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð²ÐµÑ€Ñий", + "versionEventsDes": "СобытиÑ, ÑвÑзанные Ñ ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸ÐµÐ¼ верÑиÑми файлов.", + "mediaEvents": "Медиа ÑобытиÑ", + "mediaEventsDes": "СобытиÑ, ÑвÑзанные Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¾Ð¹ медиафайлов, такими как Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¼Ð¸Ð½Ð¸Ð°Ñ‚ÑŽÑ€.", + "filesystemEvents": "Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²Ð¾Ð¹ ÑиÑтемы", + "filesystemEventsDes": "СобытиÑ, ÑвÑзанные Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñми файловой ÑиÑтемы, такими как монтирование и обработка архивов.", + "webdavEvents": "Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ WebDAV", + "webdavEventsDes": "СобытиÑ, ÑвÑзанные Ñ ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸ÐµÐ¼ аккаунтами WebDAV и доÑтупом.", + "paymentEvents": "Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð¿Ð»Ð°Ñ‚ÐµÐ¶ÐµÐ¹", + "paymentEventsDes": "СобытиÑ, ÑвÑзанные Ñ Ð¿Ð»Ð°Ñ‚ÐµÐ¶Ð½Ñ‹Ð¼Ð¸ транзакциÑми и обработкой.", + "emailEvents": "Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ Ñлектронной почты", + "emailEventsDes": "СобытиÑ, ÑвÑзанные Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²ÐºÐ¾Ð¹ Ñлектронной почты и уведомлениÑми.", + "toggleAll": "Включить/отключить вÑе ÑобытиÑ", + "toggleAllDes": "Включить или отключить вÑе ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð² Ñтой категории.", + "event": { + "file_imported": "Импорт внешнего файла", + "server_start": "ЗапуÑк Ñервера", + "user_signup": "РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ", + "email_sent": "Отправка Ñлектронной почты", + "user_activated": "ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ", + "user_login_failed": "Ðеудачный вход в ÑиÑтему", + "user_login": "Вход Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð² ÑиÑтему", + "user_token_refresh": "Обновление токена", + "file_create": "Создание файла", + "file_rename": "Переименование файла", + "set_file_permission": "Изменение разрешений", + "entity_uploaded": "Загрузка или обновление файла", + "entity_downloaded": "Скачивание файла", + "copy_from": "Копирование из", + "copy_to": "Копирование в", + "move_to": "Перемещение в", + "delete_file": "Удаление файла", + "move_to_trash": "Перемещение в корзину", + "share": "Создание общего доÑтупа", + "share_link_viewed": "ПроÑмотр ÑÑылки общего доÑтупа", + "set_current_version": "УÑтановка текущей верÑии", + "delete_version": "Удаление верÑии", + "thumb_generated": "Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¼Ð¸Ð½Ð¸Ð°Ñ‚ÑŽÑ€Ñ‹", + "live_photo_uploaded": "Загрузка Live Photo", + "update_metadata": "Обновление метаданных", + "edit_share": "Редактирование общего доÑтупа", + "delete_share": "Удаление общего доÑтупа", + "mount": "Монтирование", + "relocate": "ÐŸÐµÑ€ÐµÐ½Ð¾Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ¸ хранениÑ", + "create_archive": "Создание архива", + "extract_archive": "Извлечение архива", + "webdav_login_failed": "Ðеудачный вход в WebDAV", + "webdav_account_create": "Создание аккаунта WebDAV", + "webdav_account_update": "Обновление аккаунта WebDAV", + "webdav_account_delete": "Удаление аккаунта WebDAV", + "payment_created": "Создание платежа", + "points_change": "Изменение баллов", + "payment_paid": "Завершение платежа", + "payment_fulfilled": "Выполнение заказа", + "payment_fulfill_failed": "Ðеудача Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð·Ð°ÐºÐ°Ð·Ð°", + "storage_added": "РаÑширение хранилища", + "group_changed": "Изменение группы пользователÑ", + "user_exceed_quota_notified": "Уведомление о превышении квоты", + "user_changed": "Изменение ÑтатуÑа пользователÑ", + "get_direct_link": "Получение прÑмой ÑÑылки", + "link_account": "ПривÑзка внешнего аккаунта", + "unlink_account": "ОтвÑзка внешнего аккаунта", + "change_nick": "Изменение пÑевдонима", + "change_avatar": "Изменение аватара", + "membership_unsubscribe": "Отмена подпиÑки", + "change_password": "Изменение паролÑ", + "enable_2fa": "Включение 2FA", + "disable_2fa": "Отключение 2FA", + "add_passkey": "Добавление ключа доÑтупа", + "remove_passkey": "Удаление ключа доÑтупа", + "redeem_gift_code": "ÐÐºÑ‚Ð¸Ð²Ð°Ñ†Ð¸Ñ Ð¿Ð¾Ð´Ð°Ñ€Ð¾Ñ‡Ð½Ð¾Ð³Ð¾ кода", + "update_view": "Изменение наÑтроек проÑмотра", + "delete_direct_link": "Удаление прÑмой ÑÑылки", + "report_abuse": "Сообщение о нарушении" + }, + "server": "ÐаÑтройки Ñервера", + "tempPath": "Временный путь", + "tempPathDes": "Каталог Ð´Ð»Ñ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð²Ñ€ÐµÐ¼ÐµÐ½Ð½Ñ‹Ñ… файлов, отноÑительно каталога данных Cloudreve. Перед изменением убедитеÑÑŒ, что нет выполнÑющихÑÑ Ð·Ð°Ð´Ð°Ñ‡ в очереди.", + "siteID": "ID Ñайта", + "siteIDDes": "Уникальный ID Ð´Ð»Ñ Ð¸Ð´ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ð¸ Ñайта, обычно не требует изменениÑ.", + "siteSecretKey": "Главный ключ", + "siteSecretKeyDes": "Главный ключ, иÑпользуемый Ð´Ð»Ñ ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÑŒÑких токенов и подпиÑей. ПоÑле ротации вÑе пользовательÑкие токены и подпиÑи Ñтанут недейÑтвительными. ПерезапуÑтите Cloudreve поÑле ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹.", + "rotateSecretKey": "Ð Ð¾Ñ‚Ð°Ñ†Ð¸Ñ Ð³Ð»Ð°Ð²Ð½Ð¾Ð³Ð¾ ключа", + "hashidSalt": "Соль HashID", + "hashidSaltDes": "Соль, иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÐµÐ¼Ð°Ñ Ð´Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ð¸ HashID. ИзменÑйте Ñ Ð¾ÑторожноÑтью, так как Ñто приведет к недейÑтвительноÑти вÑех ÑущеÑтвующих прÑмых ÑÑылок и ÑÑылок общего доÑтупа.", + "accessTokenTTL": "TTL токена доÑтупа", + "accessTokenTTLDes": "Срок дейÑÑ‚Ð²Ð¸Ñ Ñ‚Ð¾ÐºÐµÐ½Ð° доÑтупа в Ñекундах.", + "refreshTokenTTL": "TTL токена обновлениÑ", + "refreshTokenTTLDes": "Срок дейÑÑ‚Ð²Ð¸Ñ Ñ‚Ð¾ÐºÐµÐ½Ð° Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð² Ñекундах. ВлиÑет на продолжительноÑть ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ ÑоÑтоÑÐ½Ð¸Ñ Ð²Ñ…Ð¾Ð´Ð° Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð² ÑиÑтему.", + "cronGarbageCollect": "Интервал Ñборки муÑора", + "cronGarbageCollectDes": "УÑтановите, как чаÑто Ñканировать и очищать временные файлы и проÑроченные данные в хранилище KV", + "startWithProtocol": "Должно начинатьÑÑ Ñ http:// или https://", + "tlsWarning": "Текущий Ñайт иÑпользует https, указание здеÑÑŒ URL Ñ http может привеÑти к аномалиÑм.", + "blobUrlCache": "КÑш Blob URL", + "clearBlobUrlCache": "ОчиÑтить кÑш Blob URL", + "clearBlobUrlCacheDes": "Ð”Ð»Ñ ÑƒÐ²ÐµÐ»Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ñ‡Ð°Ñтоты попаданий в кÑш Cloudreve кÑширует и повторно иÑпользует Blob URL. При изменении наÑтроек, таких как Ð°Ð´Ñ€ÐµÑ CDN, пожалуйÑта, очиÑтите кÑш.", + "cacheCleared": "КÑш очищен" + }, + "giftCodes": { + "giftCodesSettings": "Подарочные коды", + "generateGiftCodes": "Генерировать подарочные коды", + "giftCodeQuantity": "КоличеÑтво", + "giftCodeQuantityHelp": "КоличеÑтво подарочных кодов Ð´Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ð¸.", + "giftCodeProductType": "Тип продукта", + "giftCodeTypePoints": "Баллы", + "giftCodeTypeStorage": "МеÑто Ð´Ð»Ñ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ", + "giftCodeTypeGroup": "Группа пользователей", + "giftCodePointsAmount": "КоличеÑтво баллов", + "giftCodePointsAmountHelp": "КоличеÑтво баллов, которое будет получено при иÑпользовании кода.", + "giftCodeProduct": "Продукт", + "selectStorageProduct": "Выберите продукт хранениÑ", + "selectGroupProduct": "Выберите продукт группы пользователей", + "giftCodeType": "Тип", + "giftCodeAmount": "КоличеÑтво", + "giftCode": "Подарочный код", + "giftCodeStatus": "СтатуÑ", + "giftCodeUsedBy": "ИÑпользован", + "giftCodeUsed": "ИÑпользован", + "giftCodeUnused": "ДоÑтупен", + "giftCodeDeleted": "Подарочный код уÑпешно удален", + "giftCodesGenerated": "Подарочные коды уÑпешно Ñгенерированы", + "noGiftCodes": "Ðет подарочных кодов", + "generatedCodesTitle": "Сгенерированные подарочные коды", + "generatedCodesDescription": "Скопируйте Ñти подарочные коды Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð¸ пользователÑм. Каждый код можно иÑпользовать только один раз.", + "copyAndClose": "Скопировать и закрыть", + "duratonTimes": "Множитель длительноÑти", + "duratonTimesDes": "Сколько единиц ÑоответÑтвующего товара Ñодержит каждый подарочный код.", + "unknownProduct": "ÐеизвеÑтный продукт" + }, + "policy": { + "acceleratedDomainUpload": "ИÑпользовать уÑкоренный домен Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸", + "acceleratedDomainUploadDes": "При включении Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸ файлов будет иÑпользоватьÑÑ <0>уÑкоренный домен передачи Qiniu.", + "compare": "Сравнить политики хранениÑ", + "deletePolicyConfirmation": "Ð’Ñ‹ уверены, что хотите удалить политику Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ {{name}}?", + "streamSaver": "Обработка загрузки браузером", + "streamSaverDes": "При включении загрузка файлов пользователÑми будет принудительно обрабатыватьÑÑ Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€Ð¾Ð¼. Из-за ограничений политики Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ OneDrive Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° при прÑмой загрузке может не Ñовпадать Ñ Ð¸Ð¼ÐµÐ½ÐµÐ¼ файла в Cloudreve. Обработка загрузки браузером решает Ñту проблему.", + "oauthCallbackFailed": "Ошибка авторизации", + "httpsRequired": "Приложение Entra ID требует HTTPS URL Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ, но текущий Ñайт иÑпользует HTTP. ПоÑле Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð²Ñ…Ð¾Ð´Ð° может произойти ошибка перенаправлениÑ. Ð’ таком Ñлучае вручную замените HTTPS на HTTP в адреÑной Ñтроке браузера.", + "authorizeMicrosoft": "Войти через Microsoft", + "redirectUrl": "URL перенаправлениÑ", + "redirectUrlDes": "ОтображаетÑÑ Ð°ÐºÑ‚ÑƒÐ°Ð»ÑŒÐ½Ñ‹Ð¹ URL перенаправлениÑ, ÑоответÑтвующий требованиÑм. УбедитеÑÑŒ, что URL Ð¿ÐµÑ€ÐµÐ½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð² наÑтройках Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ñовпадает.", + "authorizeOneDrive": "Подтвердить наÑтройки Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Entra ID", + "authorizeOneDriveDes": "ПожалуйÑта, подтвердите, что ÑÐ»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ приложении Entra ID вÑе еще дейÑтвительна, и внеÑите Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ необходимоÑти.", + "authorizeNow": "Ðвторизовать ÑейчаÑ", + "authorizeAgain": "ÐŸÐ¾Ð²Ñ‚Ð¾Ñ€Ð½Ð°Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ", + "notGranted": "Ðет авторизованного аккаунта, политика Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð½ÐµÐ´Ð¾Ñтупна.", + "granted": "Ðккаунт авторизован, учетные данные обновлены <0>.", + "grantedNotRefresh": "Ðккаунт авторизован, учетные данные не обновлÑлиÑÑŒ Ñ Ð¼Ð¾Ð¼ÐµÐ½Ñ‚Ð° поÑледнего запуÑка.", + "batchDeleteSize": "МакÑимальное количеÑтво Ð´Ð»Ñ Ð¿Ð°ÐºÐµÑ‚Ð½Ð¾Ð³Ð¾ удалениÑ", + "batchDeleteSizeDes": "Ограничивает макÑимальное количеÑтво удалений в одном API-запроÑе. Эта наÑтройка не влиÑет на пакетное удаление файлов пользователÑми. ЕÑли не заполнено, будет иÑпользоватьÑÑ Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ðµ по умолчанию <0>1000, что ÑвлÑетÑÑ Ð¼Ð°ÐºÑимально допуÑтимым значением Ð´Ð»Ñ Ð¾Ñ„Ð¸Ñ†Ð¸Ð°Ð»ÑŒÐ½Ð¾Ð³Ð¾ S3 API.", + "bucketPolicy": "Политика корзины", + "cdnOrCustomDomain": "CDN или пользовательÑкий домен иÑточника", + "bucketDomain": "Домен хранилища", + "bucketDomainDes": "Введите CDN-домен или пользовательÑкий домен иÑточника, привÑзанный к вашему хранилищу.", + "storageNodeInternal": "Узел Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ (внутреннÑÑ ÐºÐ¾Ð½ÐµÑ‡Ð½Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ°)", + "chunkSizeDesOssObs": "ДопуÑтимый диапазон: 100 КБ ~ 5 ГБ, ", + "chunkSizeDesQiniuCos": "ДопуÑтимый диапазон: 1 МБ ~ 1 ГБ, ", + "chunkSizeDesS3": "ДопуÑтимый диапазон: 5 МБ ~ 5 ГБ, ", + "thisIsACustomDomain": "Это пользовательÑкий домен", + "thisIsACustomDomainDes": "ЕÑли вы привÑзали пользовательÑкий домен к корзине и нужно выполнÑть операции управлениÑ, такие как загрузка, через пользовательÑкий домен, отметьте Ñту опцию. ПоÑле отметки Cloudreve не будет пытатьÑÑ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÑŒ Ð¸Ð¼Ñ ÐºÐ¾Ñ€Ð·Ð¸Ð½Ñ‹ в домене запроÑа.", + "addedManually": "Я наÑтроил ÑамоÑтоÑтельно", + "origin": "ИÑточник", + "allowMethods": "Разрешенные методы", + "exposeHeaders": "Открытые заголовки", + "allowHeaders": "Разрешенные заголовки", + "maxAge": "Ð’Ñ€ÐµÐ¼Ñ ÐºÑшированиÑ", + "accessCredential": "Учетные данные доÑтупа", + "downloadTrafficDiagram": "Схема пути трафика загрузки", + "downloadRelay": "РетранÑлÑÑ†Ð¸Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸", + "downloadRelayDes": "При включении загрузка файлов пользователÑми будет проходить через прокÑи Cloudreve.", + "download": "Загрузка", + "downloadCdn": "CDN Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸", + "useDownloadCdn": "ИÑпользовать CDN Ð´Ð»Ñ ÑƒÑÐºÐ¾Ñ€ÐµÐ½Ð¸Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸", + "skipSign": "Ðе подпиÑывать URL файлов Ð´Ð»Ñ CDN", + "skipSignDes": "ЕÑли вы включили \"ÐÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð¸Ñточника\" в наÑтройках домена COS, отметьте Ñту опцию.", + "cdnHost": "ÐÐ´Ñ€ÐµÑ CDN", + "downloadCdnDes": "Ð˜Ð¼Ñ Ñ…Ð¾Ñта, протокол и другие чаÑти URL при доÑтупе пользователей к файлам будут заменены на указанный вами CDN-домен.", + "mediaExtractorProxy": "ПрокÑи Ð´Ð»Ñ Ð¸Ð·Ð²Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ Ð¼ÐµÐ´Ð¸Ð°-информации", + "mediaExtractorProxyDes": "При включении Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð², не поддерживаемых ÑкÑтрактором хранилища, Cloudreve попытаетÑÑ Ð¸Ð·Ð²Ð»ÐµÑ‡ÑŒ медиа-информацию файла. ÐаÑтройте ÑкÑтрактор медиа-информации Cloudreve в разделе <0>Обработка медиа.", + "mediaExtractorNative": "Ð’Ñтроенный ÑкÑтрактор", + "mediaExtractorOss": "Интеллектуальное управление медиа (IMM)", + "mediaExtractorQiniu": "Ð˜Ð½Ñ‚ÐµÐ»Ð»ÐµÐºÑ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ Ð¼ÑƒÐ»ÑŒÑ‚Ð¸Ð¼ÐµÐ´Ð¸Ð¹Ð½Ð°Ñ Ñлужба", + "mediaExtractorCos": "Tencent Cloud Data万象", + "mediaExtractorObs": "Служба обработки изображений", + "mediaExtractorUpyun": "Служба обработки изображений", + "nativeMediaMetaExts": "РаÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð² Ð´Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ <0>{{name}}", + "nativeMediaMetaExtsGeneralDes": "РазделÑйте запÑтыми, оÑтавьте пуÑтым, чтобы не иÑпользовать <0>{{name}}.", + "nativeMediaMetaExtsRemote": "Ð”Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð½Ð¾Ð³Ð¾ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¿Ð¾ умолчанию поддерживаютÑÑ EXIF и метаданные музыки. Ð’Ñ‹ можете переопределить конфигурацию Ð´Ð»Ñ Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… генераторов на удаленной Ñтороне.", + "nativeMediaMetaExtOss": "Служба интеллектуального ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¼ÐµÐ´Ð¸Ð° (IMM) поддерживает обработку аудио, видео и изображений. Обработка изображений не требует ручной наÑтройки, но еÑли вам нужно обрабатывать аудио или видео, необходимо вручную включить IMM и привÑзать к корзине. См. <0>документацию Ð´Ð»Ñ Ð¿Ñ€Ð¸Ð²Ñзки. ПоÑле Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð²Ñзки добавьте раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð°ÑƒÐ´Ð¸Ð¾ и видео файлов, которые хотите обрабатывать.", + "nativeMediaMetaExtQiniu": "Ð˜Ð½Ñ‚ÐµÐ»Ð»ÐµÐºÑ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ Ð¼ÑƒÐ»ÑŒÑ‚Ð¸Ð¼ÐµÐ´Ð¸Ð¹Ð½Ð°Ñ Ñлужба поддерживает обработку раÑпроÑтраненных аудио, видео и изображений без дополнительной наÑтройки. ПроÑто укажите раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð¼ÐµÐ´Ð¸Ð°-файлов, которые хотите обрабатывать.", + "nativeMediaMetaExtCos": "Служба Tencent Cloud Data万象 поддерживает обработку аудио, видео и изображений. Обработка изображений не требует ручной наÑтройки, но еÑли вам нужно обрабатывать аудио или видео, Ñначала перейдите в <0>Data万象, включите и привÑжите корзину хранениÑ, затем перейдите в ÐаÑтройки корзины - Обработка медиа и включите Ñлужбу обработки изображений. ПоÑле Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð²Ñзки добавьте раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð°ÑƒÐ´Ð¸Ð¾ и видео файлов, которые хотите обрабатывать.", + "nativeMediaMetaExtObs": "Служба обработки изображений поддерживает <0>извлечение EXIF изображений. Ð ÑƒÑ‡Ð½Ð°Ñ Ð½Ð°Ñтройка не требуетÑÑ, проÑто добавьте раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ð¹, которые хотите обрабатывать.", + "nativeMediaMetaExtUpyun": "Служба обработки изображений поддерживает <0>извлечение EXIF изображений. Ð ÑƒÑ‡Ð½Ð°Ñ Ð½Ð°Ñтройка не требуетÑÑ, проÑто добавьте раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ð¹, которые хотите обрабатывать.", + "thumbProxy": "ПрокÑи Ð´Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ð¸ миниатюр", + "thumbProxyDes": "При включении Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð², не ÑоответÑтвующих уÑловиÑм вÑтроенных миниатюр, Cloudreve попытаетÑÑ Ñгенерировать файлы миниатюр и загрузить их в хранилище. ÐаÑтройте генератор миниатюр Cloudreve в разделе <0>Обработка медиа.", + "nativeThumbnailMaxSize": "МакÑимальный размер файла Ð´Ð»Ñ Ð²Ñтроенных миниатюр", + "nativeThumbnailMaxSizeDes": "Введите 0 Ð´Ð»Ñ Ð¾Ñ‚ÑутÑÑ‚Ð²Ð¸Ñ Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ð¹. Файлы, превышающие Ñтот размер, не будут иÑпользовать вÑтроенные миниатюры.", + "nativeThumbNailsSupportAllExts": "ИÑпользовать Ð´Ð»Ñ Ð²Ñех раÑширений файлов", + "nativeThumbNails": "РаÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð²Ñтроенных миниатюр", + "nativeThumbNailsGeneralDes": "РазделÑйте запÑтыми, оÑтавьте пуÑтым, чтобы не иÑпользовать вÑтроенные миниатюры. Ð”Ð»Ñ Ñ€Ð°Ñширений файлов в ÑпиÑке Cloudreve будет иÑпользовать вÑтроенные миниатюры хранилища.", + "nativeThumbNailsGeneralRemote": "Ð”Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð½Ð¾Ð³Ð¾ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¿Ð¾ умолчанию поддерживаютÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ проÑтые миниатюры изображений и обложек пеÑен. Ð’Ñ‹ можете переопределить конфигурацию Ð´Ð»Ñ Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… генераторов на удаленной Ñтороне.", + "nativeThumbNailsGeneralOss": "Ð”Ð»Ñ Ñ…Ñ€Ð°Ð½Ð¸Ð»Ð¸Ñ‰Ð° Alibaba Cloud OSS будет иÑпользоватьÑÑ Ñлужба <0>обработки изображений Ð´Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ð¸ миниатюр.", + "nativeThumbNailsGeneralQiniu": "Ð”Ð»Ñ Ñ…Ñ€Ð°Ð½Ð¸Ð»Ð¸Ñ‰Ð° Qiniu будет иÑпользоватьÑÑ Ñлужба <0>базовой обработки изображений (imageView2) Ð´Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ð¸ миниатюр.", + "nativeThumbNailsGeneralCos": "Ð”Ð»Ñ Ñ…Ñ€Ð°Ð½Ð¸Ð»Ð¸Ñ‰Ð° Tencent Cloud COS будет иÑпользоватьÑÑ Ñлужба <0>Tencent Cloud Data万象 Ð´Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ð¸ миниатюр.", + "nativeThumbNailsGeneralObs": "Ð”Ð»Ñ Ñ…Ñ€Ð°Ð½Ð¸Ð»Ð¸Ñ‰Ð° Huawei Cloud OBS будет иÑпользоватьÑÑ Ñлужба <0>обработки изображений Ð´Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ð¸ миниатюр.", + "nativeThumbNailsGeneralUpyun": "Ð”Ð»Ñ Ñ…Ñ€Ð°Ð½Ð¸Ð»Ð¸Ñ‰Ð° Upyun будет иÑпользоватьÑÑ Ñлужба <0>обработки изображений Ð´Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ð¸ миниатюр.", + "preallocate": "Предварительное выделение диÑкового проÑтранÑтва", + "preallocateDes": "При включении диÑковое проÑтранÑтво будет предварительно выделÑтьÑÑ Ð¿Ñ€Ð¸ загрузке файлов пользователÑми, а также поддерживает параллельную загрузку фрагментов. ДейÑтвует только в Linux или Darwin.", + "chunkConcurrency": "ÐŸÐ°Ñ€Ð°Ð»Ð»ÐµÐ»ÑŒÐ½Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ° фрагментов", + "chunkConcurrencyDes": "УÑтанавливает количеÑтво параллельных загрузок фрагментов при прÑмой веб-загрузке.", + "sourceWebEdit": "Веб-редактирование онлайн", + "uploadRelay": "РетранÑлÑÑ†Ð¸Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸", + "uploadRelayDes": "При включении запроÑÑ‹ загрузки пользователей будут ретранÑлироватьÑÑ Ñ‡ÐµÑ€ÐµÐ· Cloudreve в хранилище. ПоÑкольку Ñ„Ñ€Ð°Ð³Ð¼ÐµÐ½Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ° невозможна, обратите внимание на наÑтройку макÑимального размера загрузки веб-Ñервера.", + "customProxy": "ПользовательÑкий прокÑи", + "storageNode": "ПоÑтавщик хранилища", + "sourceWeb": "Веб / Официальный клиент", + "sourceDav": "WebDAV", + "uploadTrafficDiagram": "Схема пути трафика загрузки", + "node": "Узел хранениÑ", + "nodeDes": "Выберите узел-подчиненный Ð´Ð»Ñ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð². Ð’Ñ‹ можете Ñоздавать или управлÑть узлами-подчиненными в <0>ÑпиÑке узлов хранениÑ.", + "noBindedGroupWarning": "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ° Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð½Ðµ назначена ни одной группе пользователей. Перейдите в <0>ÑпиÑок групп пользователей, чтобы привÑзать группу пользователей к текущей политике хранениÑ.", + "nameRuleImmutable": "Изменение Ñтой наÑтройки не повлиÑет на ÑущеÑтвующие файлы в политике хранениÑ. Путь Blob фикÑируетÑÑ Ð¿Ñ€Ð¸ Ñоздании, и даже еÑли магичеÑкие переменные в нем изменÑÑ‚ÑÑ, путь не обновитÑÑ.", + "uniqueVarRequired": "ПожалуйÑта, включите Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ одну переменную уникальноÑти в путь каталога или Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°: {uuid}, {randomkey8}, {randomkey16}.", + "storageAndUpload": "Хранение и загрузка", + "blobFolderNaming": "Каталог Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Blob", + "blobFolderNamingDes": "Каталог Ð´Ð»Ñ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð² Blob, можно иÑпользовать <0>магичеÑкие переменные.", + "blobName": "Ð˜Ð¼Ñ Blob", + "blobNameDes": "Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° Blob, можно иÑпользовать <0>магичеÑкие переменные. Должно быть абÑолютно уникальным, даже при многократной загрузке одного и того же файла за короткое времÑ.", + "basicInfo": "ОÑÐ½Ð¾Ð²Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ", + "editX": "Редактировать {{name}}", + "noGroupBinded": "Ðе привÑзана ни одна группа пользователей", + "create": "Создать", + "addXStoragePolicy": "Добавить политику Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ {{type}}", + "loadSummary": "Загрузить ÑтатиÑтику", + "policySummary": "{{count}} файлов Blob ({{size}})", + "sharp": "#", + "name": "Ðазвание", + "type": "Тип", + "childFiles": "КоличеÑтво подчиненных файлов", + "totalSize": "Объем данных", + "actions": "ДейÑтвиÑ", + "authSuccess": "ÐÐ²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ ÑƒÑпешна", + "policyDeleted": "Политика Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð°", + "newStoragePolicy": "Добавить политику хранениÑ", + "all": "Ð’Ñе", + "local": "Локальное хранилище", + "remote": "Удаленное хранилище", + "qiniu": "Qiniu", + "upyun": "Upyun", + "oss": "Alibaba Cloud OSS", + "cos": "Tencent Cloud COS", + "onedrive": "OneDrive", + "s3": "S3-ÑовмеÑтимое", + "ks3": "Kingsoft Cloud KS3", + "obs": "Huawei Cloud OBS", + "load_balance": "БаланÑировка нагрузки", + "childPolicy": "ДочернÑÑ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ° хранениÑ", + "childPolicyDes": "Выберите дочерние политики хранениÑ, которые хотите добавить в баланÑировку нагрузки.", + "weight": "ВеÑ", + "addTargetPolicy": "Добавить дочернюю политику хранениÑ", + "selectPolicies": "Выбрать политики", + "selectPoliciesDes": "Выберите политики Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð² баланÑировку нагрузки.", + "loadBalanceDes": "При иÑпользовании политики Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ Ð±Ð°Ð»Ð°Ð½Ñировкой нагрузки новые загружаемые файлы будут Ñлучайным образом раÑпределÑтьÑÑ Ð¼ÐµÐ¶Ð´Ñƒ различными дочерними политиками Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð² ÑоответÑтвии Ñ Ð¸Ñ… веÑом.", + "xChildPolicies": "{{count}} дочерних политик", + "refresh": "Обновить", + "delete": "Удалить", + "edit": "Редактировать", + "selectAStorageProvider": "Выберите ÑпоÑоб хранениÑ", + "maxSizeOfSingleFile": "Ограничение размера файла", + "maxSizeOfSingleFileDes": "МакÑимальный размер одного файла. Введите 0 Ð´Ð»Ñ Ð¾Ñ‚ÑутÑÑ‚Ð²Ð¸Ñ Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ð¹ на размер файла.", + "enterFileExt": "ОÑтавьте пуÑтым Ð´Ð»Ñ Ð¾Ñ‚ÑутÑÑ‚Ð²Ð¸Ñ Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ð¹ на раÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð². Ð”Ð»Ñ Ð½ÐµÑкольких раÑширений разделÑйте запÑтыми.", + "extList": "ÐžÐ³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ñ€Ð°Ñширений файлов", + "noLimit": "Без ограничений", + "whitelist": "Разрешить", + "blacklist": "Запретить", + "fileNameRegex": "РегулÑрное выражение Ð´Ð»Ñ Ð¸Ð¼ÐµÐ½Ð¸ файла", + "fileNameRegexDes": "РегулÑрное выражение Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸ имен файлов. ОÑтавьте пуÑтым Ð´Ð»Ñ Ð¾Ñ‚ÑутÑÑ‚Ð²Ð¸Ñ Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ð¹.", + "chunkSizeDes": "Укажите размер фрагмента Ð´Ð»Ñ Ñ„Ñ€Ð°Ð³Ð¼ÐµÐ½Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð¾Ð¹ загрузки. Введите 0 Ð´Ð»Ñ Ð¾Ñ‚ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ñ„Ñ€Ð°Ð³Ð¼ÐµÐ½Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð¾Ð¹ загрузки, но макÑимальный размер загрузки может быть ограничен веб-Ñервером.", + "chunkSizeDesSuffix": "{{prefix}}При фрагментированной загрузке файлы пользователей будут разделены на фрагменты и загружены по чаÑÑ‚Ñм в хранилище. При прерывании загрузки пользователь может продолжить Ñ Ð¿Ð¾Ñледнего загруженного фрагмента.", + "chunkSize": "Размер фрагмента загрузки", + "policyName": "Отображаемое название политики хранениÑ, также иÑпользуетÑÑ Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ð° пользователÑм.", + "magicVar": { + "fileNameMagicVar": "МагичеÑкие переменные имени файла", + "pathMagicVar": "МагичеÑкие переменные пути", + "variable": "МагичеÑÐºÐ°Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ", + "description": "ОпиÑание", + "example": "Пример", + "16digitsRandomString": "16-Ð·Ð½Ð°Ñ‡Ð½Ð°Ñ ÑÐ»ÑƒÑ‡Ð°Ð¹Ð½Ð°Ñ Ñтрока", + "8digitsRandomString": "8-Ð·Ð½Ð°Ñ‡Ð½Ð°Ñ ÑÐ»ÑƒÑ‡Ð°Ð¹Ð½Ð°Ñ Ñтрока", + "secondTimestamp": "Ð’Ñ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ Ð¼ÐµÑ‚ÐºÐ° в Ñекундах", + "nanoTimestamp": "Ð’Ñ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ Ð¼ÐµÑ‚ÐºÐ° в наноÑекундах", + "uid": "ID пользователÑ", + "originalFileName": "ИÑходное Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°", + "originFileNameNoext": "ИÑходное Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð° без раÑширениÑ", + "extension": "РаÑширение файла", + "uuidV4": "UUID V4", + "date": "Дата", + "dateAndTime": "Дата и времÑ", + "randomNumber": "Случайное чиÑло в диапазоне", + "year": "Год", + "month": "МеÑÑц", + "day": "День", + "hour": "ЧаÑ", + "minute": "Минута", + "second": "Секунда", + "path": "ИÑходный путь файла при загрузке пользователем" + }, + "storageBucket": "Хранилище", + "wanSiteURLDes": "Перед иÑпользованием Ñтой политики Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ ÑƒÐ±ÐµÐ´Ð¸Ñ‚ÐµÑÑŒ, что URL Ñайта в разделе ÐаÑтройки параметров - Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ Ñайте ÑоответÑтвует дейÑтвительноÑти и <0>доÑтупен из внешней Ñети.", + "enterQiniuBucket": "Перейдите в <0>панель ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Qiniu Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñ€ÐµÑурÑа объектного хранилища. Введите название хранилища, указанное при Ñоздании в Qiniu.", + "qiniuBucketName": "Ðазвание хранилища", + "cosObsBucketName": "Ðазвание корзины", + "bucketType": "Права доÑтупа к корзине", + "bucketTypeDes": "Выберите тип прав доÑтупа Ñозданного хранилища.", + "aclType": "Тип ÐºÐ¾Ð½Ñ‚Ñ€Ð¾Ð»Ñ Ð´Ð¾Ñтупа", + "accessTypePulic": "Публичное чтение, Ð¿Ñ€Ð¸Ð²Ð°Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ", + "accessTypePrivate": "Приватное чтение и запиÑÑŒ", + "accessType": "Права доÑтупа", + "privateBucket": "Приватное", + "privateDes": "Cloudreve будет подпиÑывать URL файлов.", + "publicBucket": "Публичное чтение", + "publicStorage": "Публичное", + "publicDes": "Ðе рекомендуетÑÑ. Cloudreve будет возвращать прÑмые ÑÑылки на файлы, что не позволит Ñффективно контролировать доÑтуп к файлам.", + "bucketCDNDes": "Введите доменное Ð¸Ð¼Ñ CDN-уÑкорениÑ, привÑзанное к хранилищу.", + "bucketCDNDomain": "Доменное Ð¸Ð¼Ñ CDN-уÑкорениÑ", + "qiniuCredentialDes": "Ð’ панели ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Qiniu перейдите в Личный центр - Управление ключами и введите полученные AK и SK.", + "ak": "AK", + "sk": "SK", + "cannotEnableForPrivateBucket": "ПоÑле Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð²Ð½ÐµÑˆÐ½Ð¸Ñ… ÑÑылок Ð´Ð»Ñ Ð¿Ñ€Ð¸Ð²Ð°Ñ‚Ð½Ð¾Ð³Ð¾ хранилища необходимо также включить \"ИÑпользовать перенаправлÑющие внешние ÑÑылки\" в наÑтройках группы пользователей, иначе внешние ÑÑылки не будут работать корректно", + "chunkSizeLabelQiniu": "Укажите размер фрагмента Ð´Ð»Ñ Ñ„Ñ€Ð°Ð³Ð¼ÐµÐ½Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð¾Ð¹ загрузки в диапазоне 1 МБ - 1 ГБ.", + "corsSettingStep": "Политика CORS", + "corsPolicyAdded": "Политика CORS добавлена.", + "createOSSBucketDes": "Ð’Ñ‹ можете перейти в <0>конÑоль ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ OSS Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ ÐºÐ¾Ñ€Ð·Ð¸Ð½Ñ‹. ПоддерживаютÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ корзины типа <1>Стандартное хранилище и <2>ÐечаÑтый доÑтуп.", + "bucketName": "Ðазвание корзины", + "publicReadBucket": "Публичное чтение", + "ossEndpointDes": "Перейдите на Ñтраницу обзора Ñозданной корзины и введите <2>EndPoint (региональный узел) из Ñтроки <1>Внешний доÑтуп в разделе <0>Доменные имена доÑтупа.", + "ossEndpointDesInternalHint": "Ð”Ð»Ñ Ð½Ð°Ñтройки внутреннего или пользовательÑкого доменного Endpoint можно наÑтроить поÑле ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ¸ хранениÑ.", + "obsEndpointCnameHint": "Ð”Ð»Ñ Ð½Ð°Ñтройки пользовательÑкого доменного Endpoint можно наÑтроить поÑле ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ¸ хранениÑ.", + "endpoint": "EndPoint", + "ossLANEndpointDes": "ОÑтавьте пуÑтым Ð´Ð»Ñ Ð½ÐµÐ¸ÑпользованиÑ. ЕÑли ваш Cloudreve развернут в облачных ÑервиÑах Alibaba и находитÑÑ Ð² той же зоне доÑтупноÑти, что и OSS, вы можете дополнительно указать иÑпользование внутреннего EndPoint Ð´Ð»Ñ Ñкономии трафика. Cloudreve переключитÑÑ Ð½Ð° внутренний EndPoint при выполнении уÑловий.", + "intranetEndPoint": "Внутренний EndPoint", + "ossCDNDes": "Хотите ли вы иÑпользовать Alibaba Cloud CDN Ð´Ð»Ñ ÑƒÑÐºÐ¾Ñ€ÐµÐ½Ð¸Ñ Ð´Ð¾Ñтупа к OSS?", + "createOSSCDNDes": "Перейдите в <0>конÑоль ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Alibaba Cloud CDN Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð´Ð¾Ð¼ÐµÐ½Ð½Ð¾Ð³Ð¾ имени CDN-уÑÐºÐ¾Ñ€ÐµÐ½Ð¸Ñ Ð¸ уÑтановите иÑточник как Ñозданную корзину OSS. Введите ниже доменное Ð¸Ð¼Ñ CDN-уÑÐºÐ¾Ñ€ÐµÐ½Ð¸Ñ Ð¸ выберите, иÑпользовать ли HTTPS:", + "ossAKDes": "Получите AccessKey на Ñтранице <0>ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸ÐµÐ¹ безопаÑноÑти Alibaba Cloud. Ð’Ñ‹ также можете Ñоздать AccessKey Ñ Ð¿Ñ€Ð°Ð²Ð°Ð¼Ð¸ <2>AliyunOSSFullAccess в <1>RAM контроле доÑтупа.", + "shouldNotContainSpace": "Ðе должно Ñодержать пробелов", + "nameThePolicyFirst": "Ðазовите Ñту политику хранениÑ:", + "chunkSizeLabelOSS": "Укажите размер фрагмента Ð´Ð»Ñ Ñ„Ñ€Ð°Ð³Ð¼ÐµÐ½Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð¾Ð¹ загрузки в диапазоне 100 КБ ~ 5 ГБ.", + "ossCORSDes": "Эта политика Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ‚Ñ€ÐµÐ±ÑƒÐµÑ‚ правильной наÑтройки указанной выше политики CORS Ð´Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸ файлов через веб-интерфейÑ. Cloudreve может помочь вам наÑтроить Ñто автоматичеÑки, или вы можете наÑтроить вручную. ЕÑли вы уже наÑтроили политику CORS Ð´Ð»Ñ Ñтой корзины, Ñтот шаг можно пропуÑтить.", + "letCloudreveHelpMe": "ПуÑть Cloudreve поможет мне наÑтроить", + "skip": "ПропуÑтить", + "createUpyunBucketDes": "Введите название облачного ÑервиÑа хранениÑ, Ñозданного в <0>панели Upyun.", + "storageServiceName": "Ðазвание ÑервиÑа", + "operatorName": "Ð˜Ð¼Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ‚Ð¾Ñ€Ð°", + "operatorPassword": "Пароль оператора", + "tokenStatus": "Token-защита от хотлинкинга", + "upyunTokenDes": "ÐаÑтоÑтельно рекомендуетÑÑ Ð²ÐºÐ»ÑŽÑ‡Ð¸Ñ‚ÑŒ Token-защиту от хотлинкинга. Перейдите в панель <0>ÐšÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ñ Ñ„ÑƒÐ½ÐºÑ†Ð¸Ð¹ Ñозданного облачного ÑервиÑа хранениÑ, перейдите на вкладку <1>Контроль доÑтупа, включите Token-защиту от хотлинкинга и уÑтановите пароль.", + "tokenEnabled": "Token-защита от хотлинкинга включена", + "tokenDisabled": "Token-защита от хотлинкинга отключена", + "upyunTokenSecretDes": "Введите уÑтановленный вами Ñекретный ключ Token-защиты от хотлинкинга.", + "upyunTokenSecret": "Секретный ключ Token-защиты", + "createCOSBucketDes": "Перейдите в <0>конÑоль ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ COS Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ ÐºÐ¾Ñ€Ð·Ð¸Ð½Ñ‹, перейдите на Ñтраницу базовой конфигурации Ñозданной корзины и введите <1>Ðазвание корзины выше.", + "obsBucketDes": "Перейдите в <0>конÑоль ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ OBS Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ ÐºÐ¾Ñ€Ð·Ð¸Ð½Ñ‹ и введите <1>Ðазвание корзины выше. ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ ÐºÐ¾Ñ€Ð·Ð¸Ð½Ñ‹ поддерживает только <2>Стандартное хранилище или <3>ÐечаÑтый доÑтуп.", + "cosPrivateRW": "Приватное чтение и запиÑÑŒ", + "cosPublicRW": "Публичное чтение, Ð¿Ñ€Ð¸Ð²Ð°Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ", + "cosAccessDomainDes": "Ðа Ñтранице обзора Ñозданной корзины введите <1>Доменное Ð¸Ð¼Ñ Ð´Ð¾Ñтупа из раздела <0>Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ доменах. Ð’Ñ‹ также можете иÑпользовать ÑобÑтвенное привÑзанное доменное Ð¸Ð¼Ñ Ð¸Ñточника или доменное Ð¸Ð¼Ñ CDN-уÑкорениÑ.", + "obsEndpointDes": "Ðа Ñтранице обзора Ñозданной корзины введите <1>Endpoint (ÐºÐ¾Ð½ÐµÑ‡Ð½Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ°) из раздела <0>Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ доменах.", + "accessDomain": "Доменное Ð¸Ð¼Ñ Ð´Ð¾Ñтупа", + "cosCDNDomainDes": "Перейдите в <0>конÑоль ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Tencent Cloud CDN Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð´Ð¾Ð¼ÐµÐ½Ð½Ð¾Ð³Ð¾ имени CDN-уÑÐºÐ¾Ñ€ÐµÐ½Ð¸Ñ Ð¸ уÑтановите иÑточник как Ñозданную корзину COS. Введите ниже доменное Ð¸Ð¼Ñ CDN-уÑÐºÐ¾Ñ€ÐµÐ½Ð¸Ñ Ð¸ выберите, иÑпользовать ли HTTPS:", + "cosCredentialDes": "Введите пару ключей доÑтупа, полученную на Ñтранице <0>ключей доÑтупа Tencent Cloud. УбедитеÑÑŒ, что Ñта пара ключей имеет права доÑтупа к ÑервиÑу COS. Ð’Ñ‹ также можете Ñоздать <2>Ð¿Ð¾Ð´Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ñтью <1>программного доÑтупа и предоÑтавить ему права доÑтупа к ÑервиÑу COS.", + "obsCredentialDes": "Введите пару ключей доÑтупа, полученную на Ñтранице <0>ключей доÑтупа Huawei Cloud. Ð’Ñ‹ также можете Ñоздать <2>Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ IAM Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ñтью <1>программного доÑтупа и предоÑтавить ему права <3>OBS OperateAccess.", + "grantAccess": "ÐÐ²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð°", + "grantAccessLater": "ПоÑле Ð½Ð°Ð¶Ð°Ñ‚Ð¸Ñ ÐºÐ½Ð¾Ð¿ÐºÐ¸ ниже Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ¸ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð²Ð°Ð¼ также потребуетÑÑ Ð²Ñ‹Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÑŒ авторизацию аккаунта на Ñтранице наÑтроек политики хранениÑ.", + "odHttpsWarning": "Ð’Ñ‹ должны включить HTTPS Ð´Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ¸ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ OneDrive/SharePoint; поÑле Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ñинхронно измените URL Ñайта в разделе ÐаÑтройки параметров - Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ Ñайте.", + "creatAadAppDes": "Перейдите в <0>конÑоль Microsoft Entra ID и войдите в ÑиÑтему. ПоÑле входа перейдите в панель ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ <1>Microsoft Entra ID. Ðккаунт Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ð° и аккаунт OneDrive Ð´Ð»Ñ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ отличатьÑÑ.", + "createAadAppDes2": "Перейдите в левое меню <0>РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ð¹ и нажмите кнопку <1>ÐÐ¾Ð²Ð°Ñ Ñ€ÐµÐ³Ð¸ÑтрациÑ. Заполните форму региÑтрации приложениÑ. Ðазвание может быть любым; Ð´Ð»Ñ <2>Поддерживаемых типов аккаунтов выберите <3>Ðккаунты в любом организационном каталоге (любой каталог Azure AD - мультитенант) и личные аккаунты Microsoft (например, Skype, Xbox); Ð´Ð»Ñ <4>URI Ð¿ÐµÑ€ÐµÐ½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ (необÑзательно) выберите <5>Web и введите <6>{{url}}; оÑтальное оÑтавьте по умолчанию.", + "aadAppIDDes": "Перейдите на Ñтраницу <0>Обзор ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸ÐµÐ¼ и найдите значение <1>ID Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ (клиента).", + "entraIdApp": "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ приложении Entra ID", + "aadAppID": "ID Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ (клиента)", + "addAppSecretDes": "СпоÑоб ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñекрета клиента: перейдите в левое меню <0>Сертификаты и Ñекреты Ñтраницы ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸ÐµÐ¼, нажмите кнопку <1>Ðовый Ñекрет клиента, Ð´Ð»Ñ <2>Срока дейÑÑ‚Ð²Ð¸Ñ Ð²Ñ‹Ð±ÐµÑ€Ð¸Ñ‚Ðµ макÑимальное времÑ. ПоÑле иÑÑ‚ÐµÑ‡ÐµÐ½Ð¸Ñ Ñрока дейÑÑ‚Ð²Ð¸Ñ Ñекрета клиента необходимо Ñоздать новый и ввеÑти его в наÑтройки политики хранениÑ.", + "aadAppSecret": "Секрет клиента", + "aadAccountCloud": "ÐšÐ¾Ð½ÐµÑ‡Ð½Ð°Ñ Ñ‚Ð¾Ñ‡ÐºÐ° Microsoft Graph", + "aadAccountCloudDes": "Выберите ÑоответÑтвующую конечную точку в завиÑимоÑти от типа вашего аккаунта Microsoft 365.", + "multiTenant": "ÐŸÑƒÐ±Ð»Ð¸Ñ‡Ð½Ð°Ñ (Ð¼ÐµÐ¶Ð´ÑƒÐ½Ð°Ñ€Ð¾Ð´Ð½Ð°Ñ Ð²ÐµÑ€ÑиÑ)", + "gallatin": "21Vianet", + "sharePointDes": "СохранÑть ли файлы в SharePoint?", + "saveToOneDrive": "Сохранить в диÑк OneDrive аккаунта по умолчанию", + "spSiteURL": "ÐÐ´Ñ€ÐµÑ Ñайта SharePoint", + "odReverseProxyURLDes": "ИÑпользовать ли ÑобÑтвенный прокÑи-Ñервер при Ñкачивании файлов?", + "odReverseProxyURL": "ÐÐ´Ñ€ÐµÑ Ð¿Ñ€Ð¾ÐºÑи-Ñервера", + "chunkSizeDesOd": "ДопуÑтимый диапазон: 5 МБ ~ 5 ГБ. OneDrive требует, чтобы размер был кратен 320 КиБ (327,680 байт).", + "limitOdTPSDes": "Ограничить чаÑтоту запроÑов к API OneDrive", + "tps": "Ограничение TPS", + "tpsDes": "ОÑтавьте пуÑтым Ð´Ð»Ñ Ð¾Ñ‚ÑутÑÑ‚Ð²Ð¸Ñ Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ð¹. Ограничивает макÑимальное количеÑтво API-запроÑов к OneDrive в Ñекунду Ð´Ð»Ñ Ñтой политики хранениÑ. ЗапроÑÑ‹, превышающие Ñту чаÑтоту, будут ограничены. При переноÑе файлов неÑколькими узлами Cloudreve каждый будет иÑпользовать ÑобÑтвенный ограничитель, поÑтому пропорционально уменьшите Ñто значение. ЗапроÑÑ‹ загрузки через веб-Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð½Ðµ ограничиваютÑÑ.", + "tpsBurst": "Пиковые запроÑÑ‹ TPS", + "tpsBurstDes": "Во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ñ€Ð¾ÑÑ‚Ð¾Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñов Cloudreve может зарезервировать указанное количеÑтво квот Ð´Ð»Ñ Ð±ÑƒÐ´ÑƒÑ‰ÐµÐ³Ð¾ пикового трафика.", + "odOauthDes": "Ðо вам нужно нажать кнопку ниже и авторизоватьÑÑ Ñ‡ÐµÑ€ÐµÐ· OneDrive Ð´Ð»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð¸Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ð¸ перед иÑпользованием. Ð’ дальнейшем вы можете повторно выполнить авторизацию на Ñтранице ÑпиÑка политик хранениÑ.", + "gotoAuthPage": "Перейти на Ñтраницу авторизации", + "s3BucketDes": "Перейдите в конÑоль AWS S3 Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ ÐºÐ¾Ñ€Ð·Ð¸Ð½Ñ‹ и введите ниже <0>Ðазвание корзины, указанное при Ñоздании:", + "s3EndpointDes": "Укажите EndPoint (региональный узел) корзины в полном URL-формате, например <0>https://bucket.region.example.com.", + "selectRegionDes": "Введите код региона, где находитÑÑ ÐºÐ¾Ñ€Ð·Ð¸Ð½Ð°, например <0>us-east-1. Ð”Ð»Ñ Ð½Ðµ-AWS S3-ÑовмеÑтимых провайдеров Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¾Ð±Ñ€Ð°Ñ‚Ð¸Ñ‚ÐµÑÑŒ к их документации Ð´Ð»Ñ Ð·Ð°Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ñтого полÑ.", + "chunkSizeLabelS3": "Укажите размер фрагмента Ð´Ð»Ñ Ñ„Ñ€Ð°Ð³Ð¼ÐµÐ½Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð¾Ð¹ загрузки в диапазоне 5 МБ ~ 5 ГБ.", + "policyEndpoint": "Endpoint", + "s3Region": "Код региона", + "s3EndpointPathStyle": "Выберите, принудительно ли иÑпользовать Endpoint в формате пути. Ðекоторые Ñторонние S3-ÑовмеÑтимые хранилища могут требовать Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ñтой опции. При включении будет принудительно иÑпользоватьÑÑ Ð°Ð´Ñ€ÐµÑ Ð² формате пути, например <0>http://s3.amazonaws.com/BUCKET/KEY.", + "usePathEndpoint": "Принудительный Endpoint в формате пути", + "thumbExt": "РаÑÑˆÐ¸Ñ€ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð² Ð´Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ð¸ миниатюр", + "thumbExtDes": "ОÑтавьте пуÑтым Ð´Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ñ€ÐµÐ´Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð½Ð¾Ð³Ð¾ набора политики хранениÑ. Ðе дейÑтвует Ð´Ð»Ñ Ð»Ð¾ÐºÐ°Ð»ÑŒÐ½Ñ‹Ñ… и S3 политик хранениÑ", + "driverRoot": "Корневой каталог диÑка", + "driverRootDes": "Выберите меÑто ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð² в аккаунте OneDrive. Изменение Ñтой опции приведет к недоÑтупноÑти ÑущеÑтвующих файлов в политике хранениÑ.", + "saveToDefaultOneDrive": "Сохранить файлы на диÑк OneDrive по умолчанию", + "saveToSharePoint": "Сохранить файлы в SharePoint", + "sharePointUrlDes": "Введите URL Ñайта SharePoint. ПоÑле потери фокуÑа ÑиÑтема автоматичеÑки преобразует его в правильный идентификатор диÑка.", + "ks3selectRegionDes": "Введите код региона, где находитÑÑ ÐºÐ¾Ñ€Ð·Ð¸Ð½Ð°, например <0>BEIJING.", + "ks3EndpointPathStyle": "Выберите, принудительно ли иÑпользовать Endpoint в формате пути.", + "ossRegionDes": "Введите код региона, где находитÑÑ ÐºÐ¾Ñ€Ð·Ð¸Ð½Ð°, например <0>cn-hangzhou. Ð’Ñ‹ можете найти ÑоответÑтвующий регион в таблице <1>Регионы и конечные точки OSS и заполнить ÑоответÑтвующий <2>ID региона." + }, + "node": { + "slave": "Подчиненный узел", + "master": "Главный узел", + "noCapabilities": "Функции не включены", + "active": "Включен", + "suspended": "Отключен", + "deleteNodeConfirmation": "Ð’Ñ‹ уверены, что хотите удалить узел {{name}}?", + "editNode": "Редактировать узел {{node}}", + "thisIsMasterNodes": "Ð’Ñ‹ редактируете главный узел, то еÑть ÑкземплÑÑ€ Cloudreve, который обÑлуживает текущий Ñайт.", + "enableNode": "Включить узел", + "enableNodeDes": "ПоÑле Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ ÑƒÐ·ÐµÐ» будет принимать задачи Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸ включенных функций.", + "name": "Ðазвание", + "nameNode": "Ðазвание узла, также иÑпользуетÑÑ Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñм.", + "type": "Тип", + "server": "ÐÐ´Ñ€ÐµÑ ÑƒÐ·Ð»Ð°", + "serverDes": "ÐÐ´Ñ€ÐµÑ Ð´Ð»Ñ ÑвÑзи Ñ ÑƒÐ·Ð»Ð¾Ð¼. ЕÑли вы планируете хранить файлы на Ñтом узле, Ñтот Ð°Ð´Ñ€ÐµÑ Ñ‚Ð°ÐºÐ¶Ðµ будет доÑтупен клиентам Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸ файлов.", + "loadBalancerRankDes": "Укажите Ð²ÐµÑ Ð±Ð°Ð»Ð°Ð½Ñировки нагрузки Ð´Ð»Ñ Ñтого узла, значение должно быть целым чиÑлом. Чем выше веÑ, тем больше вероÑтноÑть выбора узла.", + "loadBalancerRank": "Ð’ÐµÑ Ð±Ð°Ð»Ð°Ð½Ñировки нагрузки", + "slaveSecret": "Секретный ключ подчиненного узла", + "slaveSecretDes": "Ключ Ð´Ð»Ñ ÑвÑзи между подчиненным и главным узлами. Должен Ñовпадать Ñ <1>Secret в разделе <0>Slave конфигурационного файла подчиненного узла.", + "testNode": "ТеÑтировать ÑвÑзь Ñ ÑƒÐ·Ð»Ð¾Ð¼", + "testNodeSuccess": "СвÑзь Ñ ÑƒÐ·Ð»Ð¾Ð¼ уÑпешна", + "createArchiveDes": "Принимать запроÑÑ‹ на Ñоздание архивов.", + "extractArchiveDes": "Принимать запроÑÑ‹ на извлечение файлов из архивов.", + "remoteDownloadDes": "Принимать запроÑÑ‹ на офлайн-загрузку. ПоÑле Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð½ÐµÐ¾Ð±Ñ…Ð¾Ð´Ð¸Ð¼Ð¾ также наÑтроить параметры офлайн-загрузки ниже.", + "downloader": "Загрузчик", + "aria2Des": "ЗапуÑтите Aria2 на целевом Ñервере узла Ñ Ñ‚ÐµÐ¼Ð¸ же правами пользователÑ, что и Cloudreve, и включите RPC-ÑÐµÑ€Ð²Ð¸Ñ Ð² конфигурационном файле Aria2. Подробную информацию и инÑтрукции Ñм. в разделе \"Офлайн-загрузка\" документации.", + "qbittorrentDes": "ЗапуÑтите qBittorrent на целевом Ñервере узла Ñ Ñ‚ÐµÐ¼Ð¸ же правами пользователÑ, что и Cloudreve, и включите ÑÐµÑ€Ð²Ð¸Ñ \"Web UI\" в наÑтройках qBittorrent. Подробную информацию и инÑтрукции Ñм. в разделе \"Офлайн-загрузка\" документации.", + "rpcServer": "ÐÐ´Ñ€ÐµÑ RPC-Ñервера", + "rpcServerHelpDes": "Полный Ð°Ð´Ñ€ÐµÑ RPC-Ñервера Ñ Ð¿Ð¾Ñ€Ñ‚Ð¾Ð¼, например: <0>http://127.0.0.1:6800/.", + "rpcToken": "RPC-токен авторизации", + "rpcTokenDes": "Должен Ñовпадать Ñ <0>rpc-secret в конфигурационном файле Aria2. ОÑтавьте пуÑтым, еÑли не наÑтроено.", + "downloaderOptionDes": "Дополнительные наÑтройки загрузчика, передаваемые при Ñоздании задач загрузки, в формате JSON ключ-значение. ПодробноÑти Ñм. в <0>официальной документации загрузчика.", + "refreshInterval": "Интервал Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ ÑтатуÑа (Ñекунды)", + "refreshIntervalDes": "Интервал, Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ð¼ Cloudreve запрашивает у загрузчика обновление ÑтатуÑа задач. ФактичеÑкий интервал также завиÑит от наÑтроек и загруженноÑти очереди \"Офлайн-загрузка\".", + "waitForSeeding": "Ожидать Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ð´Ð°Ñ‡Ð¸", + "waitForSeedingDes": "При включении поÑле Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð¾Ñ„Ð»Ð°Ð¹Ð½-загрузки задача будет Ñохранена в ÑоÑтоÑнии раздачи до Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ ÑƒÑловий Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ð´Ð°Ñ‡Ð¸, наÑтроенных в загрузчике. Ожидание раздачи проиÑходит поÑле Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ð¾Ñ„Ð»Ð°Ð¹Ð½-загрузки и не влиÑет на иÑпользование загруженных файлов пользователÑми.", + "webUIEndpoint": "ÐÐ´Ñ€ÐµÑ Web UI", + "webUIEndpointDes": "ÐÐ´Ñ€ÐµÑ Web UI qBittorrent, например <0>http://127.0.0.1:8080/.", + "tempPath": "Ð’Ñ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ Ð¿Ð°Ð¿ÐºÐ° загрузок", + "tempPathDes": "Папка на узле Ð´Ð»Ñ Ð²Ñ€ÐµÐ¼ÐµÐ½Ð½Ð¾Ð³Ð¾ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¾Ñ„Ð»Ð°Ð¹Ð½-загруженных файлов. ПроцеÑÑ Cloudreve на узле должен иметь права чтениÑ, запиÑи и Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñтой папки, загрузчик также должен иметь доÑтуп к ней. ОÑтавьте пуÑтым Ð´Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿ÑƒÑ‚Ð¸ временных файлов по умолчанию.", + "webUIUsername": "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Web UI", + "webUIPassword": "Пароль Web UI", + "webUICredDes": "ЕÑли Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð½Ðµ включена, оÑтавьте Ñти Ð¿Ð¾Ð»Ñ Ð¿ÑƒÑтыми.", + "downloaderTestPass": "УÑпешное подключение к загрузчику, верÑиÑ: {{version}}", + "testDownloader": "ТеÑтировать ÑвÑзь Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·Ñ‡Ð¸ÐºÐ¾Ð¼", + "addNewNode": "Создать новый узел", + "nameTheNode": "Ðазовите узел:", + "runCrSlave": "ЗапуÑтите на узле Cloudreve той же верÑии, что и главный Ñайт, иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ Ñледующий конфигурационный файл:", + "keepIfUpload": "ЕÑли в будущем вы планируете иÑпользовать Ñтот узел Ð´Ð»Ñ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ, Ñохраните приведенную ниже конфигурацию CORS.", + "storeFiles": "Хранить файлы", + "storeFilesDes": "ИÑпользовать Ñтот узел Ð´Ð»Ñ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»ÑŒÑких файлов.", + "storeFilesHint": "ЕÑли вы хотите иÑпользовать Ñтот узел Ð´Ð»Ñ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð², перейдите на Ñтраницу <0>Политики хранениÑ, Ñоздайте новую политику Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ‡Ð¸Ð½ÐµÐ½Ð½Ð¾Ð³Ð¾ узла и выберите Ñтот узел.", + "runCrWithConfig": "Сохраните приведенный выше файл как <0>config.ini и запуÑтите Cloudreve Ñ Ñтим файлом: <0>./cloudreve -c config.ini. Один ÑкземплÑÑ€ подчиненного Cloudreve может подключатьÑÑ Ðº неÑкольким главным узлам Cloudreve, проÑто добавьте Ñтот подчиненный узел во вÑе главные узлы и Ñохраните одинаковый Ñекретный ключ.", + "inputServer": "Введите Ð°Ð´Ñ€ÐµÑ ÑƒÐ·Ð»Ð°:", + "testButton": "Ð’Ñ‹ можете нажать кнопку ниже, чтобы проверить, работает ли ÑвÑзь нормально.", + "hostHeaderHint": "ЕÑли возникают ошибки подпиÑи, проверьте, передает ли обратный прокÑи перед подчиненным узлом заголовок <0>Host.", + "features": "Включенные функции", + "remoteDownload": "Офлайн-загрузка", + "refresh": "Обновить" + }, + "group": { + "countUser": "СтатиÑтика", + "anonymous": "Группа неавторизованных поÑетителей", + "sysGroup": "СиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° пользователей", + "adminGroup": "Группа админиÑтраторов", + "#": "#", + "name": "Ðазвание", + "type": "Политика хранениÑ", + "count": "КоличеÑтво пользователей", + "size": "МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ ÐµÐ¼ÐºÐ¾Ñть", + "nameOfGroup": "Ðазвание группы пользователей", + "nameOfGroupDes": "Ðазвание группы пользователей, отображаемое пользователÑм.", + "availablePolicies": "ДоÑтупные политики хранениÑ", + "availablePoliciesDes": "Укажите доÑтупные политики Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ñ‹ пользователей. Можно выбрать неÑколько. Пользователи могут Ñвободно переключатьÑÑ Ð¼ÐµÐ¶Ð´Ñƒ выбранными политиками хранениÑ. Изменение Ñтой наÑтройки не повлиÑет на уже загруженные пользователÑми файлы.", + "initialStorageQuota": "ÐÐ°Ñ‡Ð°Ð»ÑŒÐ½Ð°Ñ ÐµÐ¼ÐºÐ¾Ñть", + "initialStorageQuotaDes": "МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð½Ð°Ñ‡Ð°Ð»ÑŒÐ½Ð°Ñ ÐµÐ¼ÐºÐ¾Ñть, доÑÑ‚ÑƒÐ¿Ð½Ð°Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñм в Ñтой группе.", + "isAdmin": "Группа админиÑтраторов", + "isAdminDes": "При включении пользователи в Ñтой группе получат права админиÑтратора.", + "share": "Общий доÑтуп", + "allowCreateShareLink": "Создание ÑÑылок Ð´Ð»Ñ Ð¾Ð±Ñ‰ÐµÐ³Ð¾ доÑтупа", + "allowCreateShareLinkDes": "При отключении пользователи не Ñмогут Ñоздавать ÑÑылки Ð´Ð»Ñ Ð¾Ð±Ñ‰ÐµÐ³Ð¾ доÑтупа.", + "shareFree": "БеÑплатный доÑтуп к платным ÑÑылкам", + "shareFreeDes": "При включении пользователи Ñмогут получать доÑтуп ко вÑем платным ÑÑылкам без покупки.", + "fileManagement": "Управление файлами", + "allowWabDAV": "WebDAV", + "allowWabDAVDes": "При отключении пользователи не Ñмогут подключатьÑÑ Ðº облачному хранилищу через протокол WebDAV.", + "allowWabDAVProxy": "ПрокÑи WebDAV", + "allowWabDAVProxyDes": "При включении пользователи Ñмогут наÑтроить загрузку WebDAV через Cloudreve.", + "compressTask": "Сжатие/раÑпаковка файлов", + "compressTaskDes": "При включении пользователи Ñмогут Ñжимать/раÑпаковывать файлы онлайн.", + "compressSize": "МакÑимальный размер файлов Ð´Ð»Ñ ÑжатиÑ", + "compressSizeDes": "МакÑимальный общий размер файлов Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡ ÑжатиÑ, которые может Ñоздать пользователь. Укажите 0 Ð´Ð»Ñ Ð¾Ñ‚ÑутÑÑ‚Ð²Ð¸Ñ Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ð¹. Это ограничение не проверÑетÑÑ Ð¿Ñ€Ð¸ Ñоздании задачи ÑжатиÑ, но задача завершитÑÑ Ð½ÐµÑƒÐ´Ð°Ñ‡Ð½Ð¾, еÑли общий размер обработанных иÑходных файлов превыÑит Ñто ограничение во Ð²Ñ€ÐµÐ¼Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ.", + "decompressSize": "МакÑимальный размер файлов Ð´Ð»Ñ Ñ€Ð°Ñпаковки", + "decompressSizeDes": "МакÑимальный общий размер файлов Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡ раÑпаковки, которые может Ñоздать пользователь. Укажите 0 Ð´Ð»Ñ Ð¾Ñ‚ÑутÑÑ‚Ð²Ð¸Ñ Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ð¹.", + "allowRemoteDownload": "Офлайн-загрузка", + "allowRemoteDownloadDes": "Разрешить ли пользователÑм Ñоздавать задачи офлайн-загрузки. Ð”Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¾Ñ„Ð»Ð°Ð¹Ð½-загрузки также необходимо иметь узлы Ñ Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ð¾Ð¹ функцией офлайн-загрузки в <0>ÑпиÑке узлов.", + "aria2Options": "Параметры задач загрузчика", + "aria2OptionsDes": "Дополнительные параметры конфигурации задач Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·Ñ‡Ð¸ÐºÐ¾Ð² qBittorrent или Aria2, запиÑанные в формате JSON ключ-значение. ДоÑтупные параметры Ñм. в официальной документации.", + "aria2BatchSize": "МакÑимальное количеÑтво пакетных офлайн-загрузок", + "aria2BatchSizeDes": "МакÑимальное количеÑтво при пакетном Ñоздании офлайн-загрузок. Укажите 0 Ð´Ð»Ñ Ð¾Ñ‚ÑутÑÑ‚Ð²Ð¸Ñ Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ð¹.", + "migratePolicy": "ÐŸÐµÑ€ÐµÐ½Ð¾Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ¸ хранениÑ", + "migratePolicyDes": "Разрешить ли пользователÑм Ñоздавать задачи переноÑа политики хранениÑ.", + "advanceDelete": "РаÑширенные параметры ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²", + "advanceDeleteDes": "При включении пользователи Ñмогут выбирать, ÑохранÑть ли физичеÑкие файлы при удалении файлов в интерфейÑе. ПредоÑтавлÑйте только доверенным группам пользователей.", + "allowSelectNode": "Разрешить выбор узла", + "allowSelectNodeDes": "При включении пользователи Ñмогут выбирать узел обработки перед Ñозданием задачи. При отключении ÑиÑтема автоматичеÑки назначит узел из разрешенных Ð´Ð»Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ñ‹ пользователей.", + "allowedNodes": "ДоÑтупные узлы", + "allowedNodesDes": "Укажите узлы обработки задач, доÑтупные Ð´Ð»Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ñ‹ пользователей. ОÑтавьте пуÑтым, чтобы разрешить вÑе узлы. Пользователи могут выбирать только из Ñтого ÑпиÑка или получать узлы через баланÑировку нагрузки. Ð’ наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð¾Ñ…Ð²Ð°Ñ‚Ñ‹Ð²Ð°ÐµÑ‚ Ñледующие задачи: офлайн-загрузка, Ñжатие или раÑпаковка файлов. Другие задачи будут назначены главному узлу Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸.", + "allNodes": "Ð’Ñе узлы", + "esclateAnonymity": "Повышение прав анонимных пользователей", + "esclateAnonymityDes": "При включении пользователи Ñмогут уÑтанавливать более выÑокие права Ð´Ð»Ñ Ð°Ð½Ð¾Ð½Ð¸Ð¼Ð½Ñ‹Ñ… пользователей (изменение/Ñоздание/удаление). При отключении пользователи Ñмогут предоÑтавлÑть анонимным пользователÑм макÑимум права только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ. Изменение Ñтой наÑтройки не повлиÑет на уже наÑтроенные ÑÑылки общего доÑтупа или файлы.", + "allowDownloadShare": "ДоÑтуп к ÑÑылкам общего доÑтупа", + "allowDownloadShareDes": "При отключении пользователи не Ñмогут проÑматривать ÑÑылки общего доÑтупа других пользователей. Эта наÑтройка имеет более выÑокий приоритет, чем наÑтройки прав ÑÑылок общего доÑтупа.", + "deletedNode": "Удаленный узел #{{id}}", + "maxWalkedFiles": "МакÑимальное количеÑтво обходимых файлов", + "maxWalkedFilesDes": "МакÑимальное количеÑтво файлов, которое разрешено обходить в операциÑÑ…, требующих глубокого обхода файлов.", + "trashBinDuration": "Ð’Ñ€ÐµÐ¼Ñ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð² корзине (Ñекунды)", + "trashBinDurationDes": "Ð’Ñ€ÐµÐ¼Ñ Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð² в корзине. По иÑтечении Ñтого времени файлы будут окончательно удалены. Изменение Ñтой наÑтройки не повлиÑет на файлы, уже находÑщиеÑÑ Ð² корзине.", + "serverSideBatchDownload": "ÐŸÐ°ÐºÐµÑ‚Ð½Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ° на Ñтороне Ñервера", + "serverSideBatchDownloadDes": "Разрешить ли пользователÑм выбирать неÑколько файлов Ð´Ð»Ñ Ð¿Ð°ÐºÐµÑ‚Ð½Ð¾Ð¹ загрузки через Ñервер. При отключении пользователи по-прежнему Ñмогут иÑпользовать функцию пакетной загрузки на Ñтороне веб-клиента.", + "uploadDownload": "Загрузка и Ñкачивание", + "getDirectLink": "Получение прÑмых ÑÑылок", + "getDirectLinkDes": "Разрешить ли пользователÑм получать прÑмые ÑÑылки на файлы.", + "bathSourceLinkLimit": "Ограничение на пакетное Ñоздание прÑмых ÑÑылок", + "bathSourceLinkLimitDes": "МакÑимальное количеÑтво файлов, Ð´Ð»Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… пользователь может одновременно получить прÑмые ÑÑылки. Укажите 0, чтобы запретить получение прÑмых ÑÑылок.", + "redirectedSource": "ИÑпользование перенаправленных прÑмых ÑÑылок", + "redirectedSourceDes": "РекомендуетÑÑ Ð²ÐºÐ»ÑŽÑ‡Ð¸Ñ‚ÑŒ. При включении прÑмые ÑÑылки на файлы, получаемые пользователÑми, будут проходить через Cloudreve, что делает ÑÑылки короче. При отключении пользователи будут получать иÑходные ÑÑылки на файлы, привÑзанные к верÑии файла. Ðекоторые политики Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ определенных наÑтройках не могут обеÑпечить поÑтоÑнную дейÑтвительноÑть непереадреÑованных прÑмых ÑÑылок. См. документацию Cloudreve.", + "reuseDirectLink": "Повторное иÑпользование ÑущеÑтвующих прÑмых ÑÑылок", + "reuseDirectLinkDes": "При включении при повторных запроÑах прÑмой ÑÑылки на один и тот же файл будет повторно иÑпользоватьÑÑ ÑƒÐ¶Ðµ ÑÐ¾Ð·Ð´Ð°Ð½Ð½Ð°Ñ Ð¿ÐµÑ€ÐµÐ°Ð´Ñ€ÐµÑÐ¾Ð²Ð°Ð½Ð½Ð°Ñ Ð¿Ñ€ÑÐ¼Ð°Ñ ÑÑылка.", + "downloadSpeedLimit": "Ограничение ÑкороÑти загрузки", + "downloadSpeedLimitDes": "Укажите 0 Ð´Ð»Ñ Ð¾Ñ‚ÑутÑÑ‚Ð²Ð¸Ñ Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ð¹. При включении Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð¼Ð°ÐºÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ ÑкороÑть загрузки пользователÑми файлов из вÑех политик хранениÑ, поддерживающих ограничение ÑкороÑти, будет ограничена.", + "anonymousHint": "Эта группа пользователей ÑоответÑтвует неавторизованным анонимным поÑетителÑм.", + "create": "Создать", + "copyFromExisting": "Скопировать из ÑущеÑтвующей группы пользователей?", + "notCopy": "Ðе копировать", + "confirmDelete": "Подтвердите удаление группы пользователей {{group}}?", + "new": "ÐÐ¾Ð²Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° пользователей", + "editGroup": "Редактировать {{group}}" + }, + "user": { + "createdAt": "Дата ÑозданиÑ", + "originUserGroup": "ИÑÑ…Ð¾Ð´Ð½Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° пользователей", + "originUserGroupDes": "Группа пользователей, к которой принадлежал пользователь до покупки группы пользователей. ПоÑле иÑÑ‚ÐµÑ‡ÐµÐ½Ð¸Ñ Ñрока дейÑÑ‚Ð²Ð¸Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹ группы пользователь вернетÑÑ Ðº Ñтой группе.", + "noOriginUserGroup": "Ðет", + "groupExpired": "Дата иÑÑ‚ÐµÑ‡ÐµÐ½Ð¸Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ñ‹ пользователей", + "groupExpiredDes": "Дата иÑÑ‚ÐµÑ‡ÐµÐ½Ð¸Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ñ‹ пользователей в формате ISO8601. ОÑтавьте пуÑтым, еÑли Ñ‚ÐµÐºÑƒÑ‰Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° пользователей дейÑтвует беÑÑрочно.", + "openUserFiles": "Открыть файлы пользователÑ", + "id": "ID", + "idValue": "{{id}} ({{hash_id}})", + "avatar": "Ðватар", + "removeAvatar": "Удалить аватар", + "userDialogTitle": "Детали пользователÑ", + "2FAEnabled": "Ð”Ð²ÑƒÑ…Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð°Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½Ð°", + "qqEnabled": "QQ привÑзан", + "logtoEnabled": "Logto привÑзан", + "oidcEnabled": "OIDC привÑзан", + "deleted": "Пользователь удален", + "new": "Ðовый пользователь", + "filter": "Фильтр", + "emptyNoFilter": "ОÑтавьте пуÑтым, чтобы не фильтровать Ñтот Ñлемент.", + "selectedObjects": "Выбрано объектов: {{num}}", + "nick": "ПÑевдоним", + "email": "Email", + "group": "Группа пользователей", + "status": "СтатуÑ", + "usedStorage": "ИÑпользованное проÑтранÑтво", + "status_active": "Ðктивен", + "status_inactive": "Ðе активирован", + "status_manual_banned": "Заблокирован вручную", + "status_sys_banned": "Заблокирован ÑиÑтемой", + "toggleBan": "Заблокировать/разблокировать", + "filterCondition": "УÑловие фильтрации", + "all": "Ð’Ñе", + "userStatus": "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ", + "apply": "Применить", + "editUser": "Редактировать {{nick}}", + "password": "Пароль", + "passwordDes": "ОÑтавьте пуÑтым, чтобы не изменÑть", + "groupDes": "Группа пользователей, к которой принадлежит пользователь", + "2FA": "Ð”Ð²ÑƒÑ…Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð°Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ð¸ÐºÐ°Ñ†Ð¸Ñ", + "notEnabled": "Ðе включена", + "reset2Fa": "Отключить", + "reset": "СброÑить", + "confirmDelete": "Подтвердите удаление Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ {{user}}?", + "deleteXUsers": "Удалить {{num}} пользователей", + "confirmBatchDelete": "Подтвердите удаление {{num}} пользователей?", + "calibrateStorage": "Калибровать хранилище", + "calibrateStorageSuccess": "Хранилище уÑпешно откалибровано" + }, + "file": { + "deleteXFiles": "Удалить {{num}} файлов", + "confirmBatchDelete": "Подтвердите удаление {{num}} файлов?", + "confirmDelete": "Подтвердите удаление файла {{file}}?", + "haveShares": "Имеет ÑÑылки Ð´Ð»Ñ Ð¾Ð±Ñ‰ÐµÐ³Ð¾ доÑтупа", + "haveDirectLinks": "Имеет прÑмые ÑÑылки", + "directLinkId": "Идентификатор ÑÑылки", + "directLinks": "ПрÑмые ÑÑылки", + "noRecords": "Ðет запиÑей", + "speed": "Ограничение ÑкороÑти", + "downloads": "КоличеÑтво загрузок", + "shareLink": "СÑылка Ð´Ð»Ñ Ð¾Ð±Ñ‰ÐµÐ³Ð¾ доÑтупа", + "shareLinkNum": "{{num}} шт. (<0>проÑмотреть)", + "blobType": "Тип", + "noEntities": "Ðет Blob", + "blobs": "Blobs", + "creator": "Создатель", + "source": "ИÑточник", + "key": "Ключ", + "value": "Значение", + "isPublic": "Публичный", + "noMetadata": "Ðет метаданных", + "metadata": "Метаданные", + "id": "ID", + "primaryStoragePolicy": "ОÑÐ½Ð¾Ð²Ð½Ð°Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ° хранениÑ", + "fileDialogTitle": "Детали файла", + "name": "Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°", + "deleteAsync": "Задача ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ выполнена в фоновом режиме", + "forceDelete": "Принудительное удаление", + "size": "Размер", + "sizeUsed": "Занимаемое проÑтранÑтво", + "uploader": "Владелец", + "createdAt": "Создан", + "uploading": "ЗагружаетÑÑ", + "unknownUploader": "ÐеизвеÑтно", + "uploaderID": "ID владельца", + "searchFileName": "ПоиÑк по имени файла", + "storagePolicy": "Политика хранениÑ", + "selectTargetUser": "Сначала выберите целевого пользователÑ", + "importTaskCreated": "Задача импорта Ñоздана. Ð’Ñ‹ можете проÑмотреть ход Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð² разделе \"Фоновые задачи\"", + "manuallyPathOnly": "Ð’Ñ‹Ð±Ñ€Ð°Ð½Ð½Ð°Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ° Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÐµÑ‚ только ручной ввод пути", + "selectFolder": "Выбрать папку", + "import": "Импорт", + "importExternalFolder": "Импорт внешней папки", + "importExternalFolderDes": "Ð’Ñ‹ можете импортировать ÑущеÑтвующие файлы и Ñтруктуру папок из политики Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð² Cloudreve. ÐžÐ¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð¸Ð¼Ð¿Ð¾Ñ€Ñ‚Ð° не займет дополнительное физичеÑкое проÑтранÑтво хранениÑ, но вÑе равно будет нормально вычитать иÑпользованное проÑтранÑтво пользователÑ.", + "storagePolicyDes": "Выберите политику хранениÑ, в которой в наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ñ…Ñ€Ð°Ð½ÑÑ‚ÑÑ Ð¸Ð¼Ð¿Ð¾Ñ€Ñ‚Ð¸Ñ€ÑƒÐµÐ¼Ñ‹Ðµ файлы.", + "targetUser": "Целевой пользователь", + "targetUserDes": "Выберите, в файловую ÑиÑтему какого Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð¼Ð¿Ð¾Ñ€Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ñ‚ÑŒ файлы.", + "srcFolderPath": "Путь к иÑходной папке", + "select": "Выбрать", + "selectSrcDes": "Путь к импортируемой папке в хранилище.", + "dstFolderPath": "Путь к целевой папке", + "dstFolderPathDes": "Путь Ð´Ð»Ñ Ð¸Ð¼Ð¿Ð¾Ñ€Ñ‚Ð° папки в файловую ÑиÑтему пользователÑ.", + "recursivelyImport": "РекурÑивный импорт подпапок", + "recursivelyImportDes": "Следует ли рекурÑивно импортировать вÑе подпапки в папке.", + "createImportTask": "Создать задачу импорта", + "unlink": "ОтвÑзать (Ñохранить физичеÑкий файл)", + "searchUser": "ПоиÑк по пÑевдониму или email пользователÑ...", + "extractMediaMeta": "Извлечь медиа-информацию", + "extractMediaMetaDes": "Следует ли пытатьÑÑ Ð¸Ð·Ð²Ð»ÐµÑ‡ÑŒ медиа-информацию Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ файла при импорте файлов.", + "importWarning": "Важные замечаниÑ", + "importWarnings": [ + "ПоÑле импорта физичеÑкие файлы будут управлÑтьÑÑ Cloudreve. ПожалуйÑта, не изменÑйте Ñти файлы извне в дальнейшем;", + "Ðе импортируйте одни и те же файлы повторно;", + "ЕÑли возникнет конфликт файлов пользователÑ, Ñтот файл будет пропущен;" + ], + "otherConditions": "Другие уÑловиÑ", + "shareLinkExisted": "СущеÑтвует ÑÑылка Ð´Ð»Ñ Ð¾Ð±Ñ‰ÐµÐ³Ð¾ доÑтупа", + "directLinkExisted": "СущеÑтвует прÑÐ¼Ð°Ñ ÑÑылка", + "isUploading": "ЗагружаетÑÑ" + }, + "entity": { + "refenenceCount": "КоличеÑтво ÑÑылок", + "waitForRecycle": "Ожидает переработки", + "entityDialogTitle": "Детали Blob", + "uploadSessionID": "ID ÑеÑÑии загрузки", + "referredFiles": "СвÑзанные файлы", + "confirmBatchDelete": "Подтвердите удаление {{num}} Blob?", + "deleteXEntities": "Удалить {{num}} Blob", + "forceDelete": "Принудительное удаление", + "forceDeleteDes": "ЗапиÑÑŒ Blob будет удалена незавиÑимо от того, уÑпешно ли удален физичеÑкий файл." + }, + "event": { + "cleanup": "ОчиÑтка", + "cleanupAuditLog": "ОчиÑтить ÑобытиÑ", + "cleanupAuditLogDescription": "Удалить вÑе ÑобытиÑ, ÑоответÑтвующие Ñледующим уÑловиÑм:", + "cleanupNotAfter": "До Ñтой даты", + "cleanupEventTypes": "Типы Ñобытий", + "cleanupEventTypesDes": "Выберите типы Ñобытий Ð´Ð»Ñ Ð¾Ñ‡Ð¸Ñтки. ОÑтавьте пуÑтым Ð´Ð»Ñ Ð¾Ñ‡Ð¸Ñтки вÑех типов.", + "initiator": "Инициатор", + "event": "Событие", + "userID": "ID пользователÑ", + "ip": "IP", + "type": "Тип", + "correlationId": "ID запроÑа", + "fileID": "ID файла", + "emailSend": "Отправить email \"{{title}}\" на {{email}}", + "emailFailed": "Ðе удалоÑÑŒ запуÑтить очередь email", + "signinFailed": "Ошибка входа: {{reason}}", + "createDavAccount": "Создать WebDAV аккаунт: {{account}}", + "updateDavAccount": "Обновить WebDAV аккаунт: {{account}}", + "deleteDavAccount": "Удалить WebDAV аккаунт: {{account}}", + "pointsChange": "Изменение баллов: {{points}}", + "storageAdded": "Приобретено {{size}} проÑтранÑтва", + "nickChange": "ПÑевдоним изменен Ñ {{old}} на {{new}}", + "eventDialogTitle": "Детали ÑобытиÑ", + "userAgent": "ПользовательÑкий агент", + "linkedUser": "СвÑзанный пользователь", + "datetime": "ВремÑ", + "linkedFile": "СвÑзанный файл", + "linkedEntity": "СвÑзанный Blob", + "linkedShare": "СвÑзанный общий доÑтуп", + "rawContent": "ИÑÑ…Ð¾Ð´Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ", + "confirmDelete": "Подтвердите удаление Ñтого ÑобытиÑ?", + "deleteXEvents": "Удалить {{num}} Ñобытий", + "confirmBatchDelete": "Подтвердите удаление {{num}} Ñобытий?" + }, + "share": { + "confirmBatchDelete": "Подтвердите удаление {{num}} общих доÑтупов?", + "confirmDelete": "Подтвердите удаление Ñтого общего доÑтупа?", + "deleteXShares": "Удалить {{num}} общих доÑтупов", + "shareDialogTitle": "Детали общего доÑтупа", + "shareLink": "СÑылка Ð´Ð»Ñ Ð¾Ð±Ñ‰ÐµÐ³Ð¾ доÑтупа", + "deleted": "Файл удален", + "srcFileName": "ИÑходный файл", + "views": "ПроÑмотры", + "downloads": "Загрузки", + "price": "Баллы", + "autoExpire": "ÐвтоматичеÑкое иÑтечение", + "owner": "Владелец", + "createdAt": "Создано", + "private": "Скрыто Ñ Ð»Ð¸Ñ‡Ð½Ð¾Ð¹ Ñтраницы", + "yes": "Да", + "no": "Ðет", + "afterNDownloads": "ПоÑле {{num}} загрузок", + "none": "Ðет", + "srcType": "Тип иÑходного файла", + "folder": "Папка", + "file": "Файл" + }, + "task": { + "cleanupTasks": "ОчиÑтить задачи", + "cleanupTasksDescription": "ОчиÑтить вÑе задачи, ÑоответÑтвующие Ñледующим уÑловиÑм:", + "cleanupNotAfter": "До Ñтой даты", + "cleanupTaskTypes": "Типы задач", + "cleanupTaskTypesDes": "Выберите типы задач Ð´Ð»Ñ Ð¾Ñ‡Ð¸Ñтки, оÑтавьте пуÑтым Ð´Ð»Ñ Ð¾Ñ‡Ð¸Ñтки вÑех типов.", + "cleanupTaskStatuses": "СтатуÑÑ‹ задач", + "cleanupTaskStatusesDes": "Выберите ÑтатуÑÑ‹ задач Ð´Ð»Ñ Ð¾Ñ‡Ð¸Ñтки, оÑтавьте пуÑтым Ð´Ð»Ñ Ð¾Ñ‡Ð¸Ñтки вÑех завершенных задач.", + "confirmDelete": "Подтвердите удаление Ñтой задачи?", + "confirmBatchDelete": "Подтвердите удаление {{num}} задач?", + "deleteXTasks": "Удалить {{num}} задач", + "blobID": "ID Blob", + "retryIndex": "Ðомер повтора", + "entityError": "Ошибка переработки Blob", + "updatedAt": "Обновлено", + "taskDialogTitle": "Детали задачи", + "explicitEntityRecycle": "Ð¯Ð²Ð½Ð°Ñ Ð¿ÐµÑ€ÐµÑ€Ð°Ð±Ð¾Ñ‚ÐºÐ° файлов Blob: {{blobs}}", + "entityRecycleRoutine": "Плановое Ñканирование и переработка файлов Blob", + "mediaMetadata": "Извлечение медиа-информации Blob <0>#{{entityID}}", + "uploadSentinelCheck": "Проверка ÑтатуÑа ÑеÑÑии загрузки {{uploadSessionID}}", + "remoteDownload": "Ð£Ð´Ð°Ð»ÐµÐ½Ð½Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ°:", + "owner": "Владелец", + "content": "Содержимое", + "status": "СтатуÑ", + "create_archive": "Создание архива", + "extract_archive": "Извлечение архива", + "relocate": "ÐŸÐµÑ€ÐµÐ½Ð¾Ñ Ð¿Ð¾Ð»Ð¸Ñ‚Ð¸ÐºÐ¸ хранениÑ", + "remote_download": "Ð£Ð´Ð°Ð»ÐµÐ½Ð½Ð°Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ°", + "media_meta": "Извлечение медиа-информации", + "entity_recycle_routine": "ÐŸÐ»Ð°Ð½Ð¾Ð²Ð°Ñ Ð¿ÐµÑ€ÐµÑ€Ð°Ð±Ð¾Ñ‚ÐºÐ° Blob", + "explicit_entity_recycle": "Ð¯Ð²Ð½Ð°Ñ Ð¿ÐµÑ€ÐµÑ€Ð°Ð±Ð¾Ñ‚ÐºÐ° Blob", + "upload_sentinel_check": "Проверка загрузки", + "import": "Внешний импорт", + "type": "Тип", + "node": "Узел обработки", + "createdBy": "Создано", + "ready": "Готово", + "downloading": "ЗагружаетÑÑ", + "paused": "ПриоÑтановлено", + "seeding": "Раздача", + "error": "Ошибка", + "finished": "Завершено", + "canceled": "Отменено/ОÑтановлено", + "unknown": "ÐеизвеÑтно", + "errorMsg": "Сообщение об ошибке" + }, + "payment": { + "tradeNo": "Ðомер транзакции", + "productType": "Тип товара", + "providerID": "СпоÑоб оплаты", + "status": "СтатуÑ", + "deleteXPayments": "Удалить {{num}} заказов" + }, + "customProps": { + "add": "Добавить", + "type": "Тип", + "default": "Значение по умолчанию", + "actions": "ДейÑтвиÑ", + "text": "ТекÑÑ‚", + "number": "ЧиÑло", + "boolean": "Флажок", + "select": "Одиночный выбор", + "multiSelect": "МножеÑтвенный выбор", + "user": "Пользователь", + "link": "СÑылка", + "rating": "Рейтинг", + "addProp": "Добавить ÑвойÑтво", + "editProp": "Редактировать ÑвойÑтво", + "icon": "Иконка", + "iconDes": "Ðазвание иконки <0>Iconify, оÑтавьте пуÑтым, чтобы не показывать иконку.", + "id": "Идентификатор", + "idDes": "Идентификатор ÑвойÑтва, должен быть уникальным Ñреди вÑех ÑвойÑтв.", + "idPatternDes": "Может Ñодержать только буквы, цифры, Ð¿Ð¾Ð´Ñ‡ÐµÑ€ÐºÐ¸Ð²Ð°Ð½Ð¸Ñ Ð¸ дефиÑÑ‹.", + "minLength": "ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð°", + "maxLength": "МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð°", + "emptyLimit": "ОÑтавьте пуÑтым Ð´Ð»Ñ Ð¾Ñ‚ÑутÑÑ‚Ð²Ð¸Ñ Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ð¹.", + "minValue": "Минимальное значение", + "maxValue": "МакÑимальное значение", + "options": "Варианты", + "optionsDes": "Один вариант на Ñтроку." + }, + "vas": { + "disableSubAddressEmail": "Отключить ÑубадреÑные email", + "disableSubAddressEmailDes": "При включении email-адреÑа, Ñодержащие знак Ð¿Ð»ÑŽÑ <0>+, не Ñмогут региÑтрировать аккаунты.", + "confirmDelete": "Подтвердить удаление Ñтих заказов?", + "vas": "Дополнительные уÑлуги", + "reports": "Жалобы", + "orders": "Заказы", + "initialFiles": "Ðачальные файлы", + "initialFilesDes": "Укажите файлы, которые пользователь получит поÑле региÑтрации. Введите ID файла Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка и Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÑущеÑтвующих файлов.", + "filterEmailProvider": "Фильтр доменов email Ð´Ð»Ñ Ñ€ÐµÐ³Ð¸Ñтрации", + "filterEmailProviderDisabled": "Ðе включено", + "filterEmailProviderWhitelist": "Белый ÑпиÑок", + "filterEmailProviderBlacklist": "Черный ÑпиÑок", + "filterEmailProviderDes": "Разрешить региÑтрацию только Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð½Ñ‹Ð¼Ð¸ email-адреÑами, не раÑпроÑтранÑетÑÑ Ð½Ð° Ñторонний SSO вход.", + "filterEmailProviderRule": "Правила фильтрации доменов email", + "filterEmailProviderRuleDes": "ÐеÑколько доменов разделÑйте запÑтыми.", + "qqConnect": "QQ Connect", + "qqConnectHint": "При Ñоздании Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð½Ð° <0>открытой платформе QQ Connect, укажите URL обратного вызова: {{url}}", + "enableQQConnect": "Включить QQ Connect", + "enableQQConnectDes": "Разрешить привÑзку QQ и вход через QQ на Ñайт", + "loginWithoutBinding": "ПрÑмой вход без привÑзки", + "loginWithoutBindingDes": "При включении, еÑли пользователь иÑпользует Ñторонний вход, но не имеет привÑзанного зарегиÑтрированного пользователÑ, ÑиÑтема ÑоздаÑÑ‚ Ð´Ð»Ñ Ð½ÐµÐ³Ð¾ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸ выполнит вход. Такие пользователи в дальнейшем Ñмогут входить только через Ñторонний вход.", + "appid": "APP ID", + "appidDes": "APP ID, полученный на Ñтранице ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸ÐµÐ¼", + "appKey": "APP KEY", + "appKeyDes": "APP KEY, полученный на Ñтранице ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸ÐµÐ¼", + "overuseReminder": "Ðапоминание о превышении лимита", + "overuseReminderDes": "Шаблон напоминающего email, отправлÑемого пользователю при превышении лимита хранилища из-за иÑÑ‚ÐµÑ‡ÐµÐ½Ð¸Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ñ‹Ñ… уÑлуг", + "vasSetting": "ÐаÑтройки платежей/разное", + "storagePack": "Пакеты хранилища", + "purchasableGroups": "Покупаемые группы пользователей", + "giftCodes": "Коды активации", + "enable": "Включить", + "appID": "App ID", + "appIDDes": "APPID Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð¿Ñ€Ñмых платежей", + "rsaPrivate": "RSA приватный ключ приложениÑ", + "rsaPrivateDes": "RSA2 (SHA256) приватный ключ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð¿Ñ€Ñмых платежей, обычно генерируетÑÑ Ð²Ð°Ð¼Ð¸. ПодробноÑти Ñм. <0>Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ RSA ключей.", + "alipayPublicKey": "Публичный ключ Alipay", + "alipayPublicKeyDes": "ПредоÑтавлÑетÑÑ Alipay, можно получить в Управление приложениÑми - Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ приложении - СпоÑоб подпиÑи интерфейÑа.", + "wechatPay": "Официальные QR-платежи WeChat", + "applicationID": "ID приложениÑ", + "applicationIDDes": "Appid публичного аккаунта или мобильного приложениÑ, поданного прÑмым продавцом", + "merchantID": "Ðомер прÑмого продавца", + "merchantIDDes": "Ðомер продавца прÑмого продавца, генерируетÑÑ Ð¸ выдаетÑÑ WeChat Pay.", + "apiV3Secret": "Секретный ключ API v3", + "apiV3SecretDes": "Продавец должен Ñначала уÑтановить Ñтот ключ на Ñтранице [Платформа продавца] - [БезопаÑноÑть API], чтобы Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð¿Ñ€Ð¾ÑˆÐµÐ» проверку подпиÑи WeChat Pay. Длина ключа ÑоÑтавлÑет 32 байта.", + "mcCertificateSerial": "Серийный номер Ñертификата продавца", + "mcCertificateSerialDes": "Войдите в платформу продавца [БезопаÑноÑть API] - [Сертификат API] - [ПроÑмотр Ñертификата], чтобы увидеть Ñерийный номер Ñертификата API продавца.", + "mcAPISecret": "Приватный ключ API продавца", + "mcAPISecretDes": "Содержимое файла приватного ключа apiclient_key.pem.", + "payjs": "PAYJS WeChat Pay", + "payjsWarning": "Эта уÑлуга предоÑтавлÑетÑÑ Ñторонней платформой <0>PAYJS, любые Ñпоры не ÑвÑзаны Ñ Ñ€Ð°Ð·Ñ€Ð°Ð±Ð¾Ñ‚Ñ‡Ð¸ÐºÐ°Ð¼Ð¸ Cloudreve.", + "mcNumber": "Ðомер продавца", + "mcNumberDes": "Можно увидеть на главной Ñтранице панели ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ PAYJS", + "communicationSecret": "Секретный ключ ÑвÑзи", + "otherSettings": "Прочие наÑтройки", + "banBufferPeriod": "Период отÑрочки блокировки (Ñекунды)", + "banBufferPeriodDes": "МакÑимальное времÑ, в течение которого пользователь может превышать лимит хранилища, поÑле чего пользователь будет заморожен ÑиÑтемой.", + "allowSellShares": "Разрешить уÑтановку цены за общие реÑурÑÑ‹", + "allowSellSharesDes": "При включении пользователи могут уÑтанавливать цену в баллах за общие реÑурÑÑ‹, Ð´Ð»Ñ ÑÐºÐ°Ñ‡Ð¸Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾Ñ‚Ñ€ÐµÐ±ÑƒÐµÑ‚ÑÑ ÑпиÑание баллов.", + "creditPriceRatio": "КоÑффициент зачиÑÐ»ÐµÐ½Ð¸Ñ Ð±Ð°Ð»Ð»Ð¾Ð² (%)", + "creditPriceRatioDes": "При покупке ÑÐºÐ°Ñ‡Ð¸Ð²Ð°Ð½Ð¸Ñ Ð¾Ð±Ñ‰Ð¸Ñ… реÑурÑов Ñ ÑƒÑтановленной ценой, фактичеÑкий коÑффициент зачиÑÐ»ÐµÐ½Ð¸Ñ Ð±Ð°Ð»Ð»Ð¾Ð² Ð´Ð»Ñ Ð²Ð»Ð°Ð´ÐµÐ»ÑŒÑ†Ð° реÑурÑа.", + "creditPrice": "Цена баллов (копейки)", + "creditPriceDes": "Цена при пополнении баллов", + "add": "Добавить", + "name": "Ðазвание", + "price": "Цена за единицу", + "duration": "ДлительноÑть", + "size": "Размер", + "actions": "ДейÑтвиÑ", + "orCredits": " или {{num}} баллов", + "highlight": "Выделить", + "yes": "Да", + "no": "Ðет", + "productName": "Ðазвание товара", + "qyt": "КоличеÑтво", + "code": "Код активации", + "status": "СтатуÑ", + "invalidProduct": "ÐедейÑтвительный товар", + "used": "ИÑпользован", + "notUsed": "Ðе иÑпользован", + "generatingResult": "Результат генерации", + "addStoragePack": "Добавить пакет хранилища", + "editStoragePack": "Редактировать пакет хранилища", + "productNameDes": "Отображаемое название товара", + "packSizeDes": "Размер пакета хранилища", + "durationDay": "Срок дейÑÑ‚Ð²Ð¸Ñ (дни)", + "durationDayDes": "Срок дейÑÑ‚Ð²Ð¸Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ пакета хранилища", + "priceYuan": "Цена за единицу (юани)", + "packPriceDes": "Цена за единицу пакета хранилища", + "priceCredits": "Цена за единицу (баллы)", + "priceCreditsDes": "Цена при покупке за баллы, 0 означает, что Ð½ÐµÐ»ÑŒÐ·Ñ ÐºÑƒÐ¿Ð¸Ñ‚ÑŒ за баллы", + "editMembership": "Редактировать покупаемую группу пользователей", + "addMembership": "Добавить покупаемую группу пользователей", + "group": "Группа пользователей", + "groupDes": "Группа пользователей Ð´Ð»Ñ Ð¿Ð¾Ð²Ñ‹ÑˆÐµÐ½Ð¸Ñ Ð¿Ð¾Ñле покупки", + "durationGroupDes": "Срок дейÑÑ‚Ð²Ð¸Ñ ÐµÐ´Ð¸Ð½Ð¸Ñ†Ñ‹ времени покупки Ð´Ð»Ñ Ð¿Ð¾Ð²Ñ‹ÑˆÐµÐ½Ð¸Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ñ‹ пользователей поÑле покупки", + "groupPriceDes": "Цена за единицу группы пользователей", + "productDescription": "ОпиÑание товара (по одному на Ñтроку)", + "productDescriptionDes": "ОпиÑание товара, отображаемое на Ñтранице покупки", + "highlightDes": "При включении будет выделено на Ñтранице выбора товаров", + "generateGiftCode": "Сгенерировать код активации", + "numberOfCodes": "КоличеÑтво Ð´Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ð¸", + "numberOfCodesDes": "КоличеÑтво кодов активации Ð´Ð»Ñ Ð¼Ð°ÑÑовой генерации", + "linkedProduct": "СоответÑтвующий товар", + "productQyt": "КоличеÑтво товара", + "productQytDes": "Ð”Ð»Ñ Ñ‚Ð¾Ð²Ð°Ñ€Ð¾Ð² Ñ Ð±Ð°Ð»Ð»Ð°Ð¼Ð¸ Ñто количеÑтво баллов, Ð´Ð»Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… товаров - множитель длительноÑти", + "freeDownload": "БеÑплатное Ñкачивание общих реÑурÑов", + "freeDownloadDes": "При включении пользователи могут беÑплатно Ñкачивать общие реÑурÑÑ‹, требующие оплаты баллами", + "credits": "Баллы", + "markSuccessful": "Отметить как уÑпешное", + "markAsResolved": "Отметить как обработанное", + "reportedContent": "Объект жалобы", + "reason": "Причина", + "description": "Дополнительное опиÑание", + "reportTime": "Ð’Ñ€ÐµÐ¼Ñ Ð¶Ð°Ð»Ð¾Ð±Ñ‹", + "invalid": "[ÐедейÑтвительно]", + "deleteShare": "Удалить общий реÑурÑ", + "orderDeleted": "ЗапиÑÑŒ заказа удалена", + "orderName": "Ðазвание заказа", + "product": "Товар", + "paymentId": "ID платежа", + "orderNumber": "Ðомер заказа", + "paidBy": "СпоÑоб оплаты", + "orderOwner": "Создатель", + "amount": "Сумма", + "unpaid": "Ðе оплачено", + "paid": "Оплачено", + "shareLink": "СÑылка на общий реÑурÑ", + "mobileApp": "Мобильное приложение", + "showAppPromotion": "Показать Ñтраницу руководÑтва по клиенту", + "showAppPromotionDes": "При включении пользователи Ñмогут видеть руководÑтво по иÑпользованию мобильного клиента на Ñтранице \"Подключение и монтирование\".", + "customPaymentName": "Ðазвание ÑпоÑоба оплаты", + "customPaymentNameDes": "Ðазвание ÑпоÑоба оплаты Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñм", + "customPaymentSecretDes": "Ключ, иÑпользуемый Cloudreve Ð´Ð»Ñ Ð¿Ð¾Ð´Ð¿Ð¸Ñи запроÑов на оплату.", + "customPaymentEndpoint": "ÐÐ´Ñ€ÐµÑ Ð¿Ð»Ð°Ñ‚ÐµÐ¶Ð½Ð¾Ð³Ð¾ интерфейÑа", + "customPaymentEndpointDes": "URL интерфейÑа, запрашиваемый при Ñоздании платежного заказа", + "appFeedback": "URL Ñтраницы обратной ÑвÑзи", + "appForum": "URL пользовательÑкого форума", + "appLinkDes": "ИÑпользуетÑÑ Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð½Ð° Ñтранице наÑтроек приложениÑ, оÑтавьте пуÑтым, чтобы не показывать кнопку ÑÑылки. Эта наÑтройка дейÑтвует только при дейÑтвительной лицензии VOL." + }, + "pro": { + "title": "Функции Pro-верÑии", + "description": "ФункциÑ, которую вы пытаетеÑÑŒ иÑпользовать, доÑтупна только в верÑии Cloudreve Pro. ОбновитеÑÑŒ, чтобы разблокировать вÑе функции.", + "proInclude": "Ð’ Pro-верÑии доÑтупны:", + "shareLinkCollabration": "СовмеÑÑ‚Ð½Ð°Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ð° через ÑÑылки на общий доÑтуп", + "filePermission": "Управление правами доÑтупа", + "multipleStoragePolicy": "Переключение политики Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¸ политики Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ ÐºÐ°Ñ‚Ð°Ð»Ð¾Ð³Ð°", + "auditAndActivity": "Журнал дейÑтвий файлов и ÑиÑтемы", + "vasService": "Дополнительные уÑлуги и ÑиÑтему баллов", + "sso": "Однократный вход", + "more": "......", + "later": "Позже", + "learnMore": "Узнать больше о верÑии Pro", + "promotionTitle": "Ð¡Ð¿ÐµÑ†Ð¸Ð°Ð»ÑŒÐ½Ð°Ñ Ñкидка при обновлении верÑии ÑообщеÑтва", + "promotion": "При покупке иÑпользуйте промокод <0>{{code}}, чтобы получить <1>-{{discount}}% Ñкидку." + }, + "abuseReport": { + "deleteXAbuseReports": "Удалить {{num}} жалоб", + "folderPath": "Путь к папке", + "reporter": "ЗаÑвитель", + "shareLink": "СÑылка на общий реÑÑƒÑ€Ñ <0>#{{id}}", + "deletedShare": "Ð£Ð´Ð°Ð»ÐµÐ½Ð½Ð°Ñ ÑÑылка на общий реÑурÑ", + "deletedUser": "Удаленный пользователь", + "confirmDelete": "Подтвердить удаление Ñтой запиÑи жалобы?", + "confirmBatchDelete": "Подтвердить удаление {{num}} запиÑей жалоб?", + "reporterID": "ID пользователÑ-заÑвителÑ", + "reportedUserID": "ID пользователÑ-объекта жалобы", + "shareID": "ID общего реÑурÑа", + "reason": "Причина" + } +} diff --git a/public/locales/ru-RU/image_editor.json b/public/locales/ru-RU/image_editor.json new file mode 100755 index 0000000..72ea1d1 --- /dev/null +++ b/public/locales/ru-RU/image_editor.json @@ -0,0 +1,113 @@ +{ + "name": "ИмÑ", + "save": "Сохранить", + "saveAs": "Сохранить как", + "back": "Ðазад", + "loading": "Загрузка...", + "resetOperations": "СброÑить/удалить вÑе операции", + "changesLoseWarningHint": "ЕÑли вы нажмёте кнопку \"ÑброÑить\", ваши Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð±ÑƒÐ´ÑƒÑ‚ потерÑны. Хотите ли вы продолжить?", + "discardChangesWarningHint": "ЕÑли вы закроете окно, ваши поÑледние Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð½Ðµ будут Ñохранены.", + "cancel": "Отмена", + "apply": "Применить", + "warning": "Предупреждение", + "confirm": "Подтвердить", + "discardChanges": "ОтброÑить изменениÑ", + "undoTitle": "Отменить поÑледнюю операцию", + "redoTitle": "Повторить поÑледнюю операцию", + "showImageTitle": "Показать иÑходное изображение", + "zoomInTitle": "Увеличить", + "zoomOutTitle": "Уменьшить", + "toggleZoomMenuTitle": "Переключить меню маÑштабированиÑ", + "adjustTab": "ÐаÑтройка", + "finetuneTab": "Ð¢Ð¾Ð½ÐºÐ°Ñ Ð½Ð°Ñтройка", + "filtersTab": "Фильтры", + "watermarkTab": "ВодÑной знак", + "annotateTabLabel": "ÐннотациÑ", + "resize": "Изменить размер", + "resizeTab": "Изменить размер", + "imageName": "Ð˜Ð¼Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ", + "invalidImageError": "ПредоÑтавлено недейÑтвительное изображение.", + "uploadImageError": "Ошибка при загрузке изображениÑ.", + "areNotImages": "не ÑвлÑÑŽÑ‚ÑÑ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñми", + "isNotImage": "не ÑвлÑетÑÑ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸ÐµÐ¼", + "toBeUploaded": "Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸", + "cropTool": "Обрезка", + "original": "ИÑходный", + "custom": "ÐаÑтраиваемый", + "square": "Квадрат", + "landscape": "ÐльбомнаÑ", + "portrait": "ПортретнаÑ", + "ellipse": "ЭллипÑ", + "classicTv": "КлаÑÑичеÑкое ТВ", + "cinemascope": "ШирокоÑкранное кино", + "arrowTool": "Стрелка", + "blurTool": "Размытие", + "brightnessTool": "ЯркоÑть", + "contrastTool": "КонтраÑтноÑть", + "ellipseTool": "ЭллипÑ", + "unFlipX": "Отменить отражение по X", + "flipX": "Отразить по X", + "unFlipY": "Отменить отражение по Y", + "flipY": "Отразить по Y", + "hsvTool": "HSV", + "hue": "Оттенок", + "brightness": "ЯркоÑть", + "saturation": "ÐаÑыщенноÑть", + "value": "Значение", + "imageTool": "Изображение", + "importing": "Импорт...", + "addImage": "+ Добавить изображение", + "uploadImage": "Загрузить изображение", + "fromGallery": "Из галереи", + "lineTool": "ЛиниÑ", + "penTool": "Перо", + "polygonTool": "Многоугольник", + "sides": "Стороны", + "rectangleTool": "ПрÑмоугольник", + "cornerRadius": "Ð Ð°Ð´Ð¸ÑƒÑ ÑƒÐ³Ð»Ð¾Ð²", + "resizeWidthTitle": "Ширина в пикÑелÑÑ…", + "resizeHeightTitle": "Ð’Ñ‹Ñота в пикÑелÑÑ…", + "toggleRatioLockTitle": "Переключить блокировку ÑоотношениÑ", + "resetSize": "СброÑить к иÑходному размеру изображениÑ", + "rotateTool": "Поворот", + "textTool": "ТекÑÑ‚", + "textSpacings": "Интервалы текÑта", + "textAlignment": "Выравнивание текÑта", + "fontFamily": "СемейÑтво шрифтов", + "size": "Размер", + "letterSpacing": "Межбуквенный интервал", + "lineHeight": "Ð’Ñ‹Ñота Ñтроки", + "warmthTool": "Теплота", + "addWatermark": "+ Добавить водÑной знак", + "addTextWatermark": "+ Добавить текÑтовый водÑной знак", + "addWatermarkTitle": "Выберите тип водÑного знака", + "uploadWatermark": "Загрузить водÑной знак", + "addWatermarkAsText": "Добавить как текÑÑ‚", + "padding": "ОтÑтупы", + "paddings": "ОтÑтупы", + "shadow": "Тень", + "horizontal": "Горизонтально", + "vertical": "Вертикально", + "blur": "Размытие", + "opacity": "ÐепрозрачноÑть", + "transparency": "ПрозрачноÑть", + "position": "ПозициÑ", + "stroke": "Обводка", + "saveAsModalTitle": "Сохранить как", + "extension": "РаÑширение", + "format": "Формат", + "nameIsRequired": "Ð˜Ð¼Ñ Ð¾Ð±Ñзательно Ð´Ð»Ñ Ð·Ð°Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ.", + "quality": "КачеÑтво", + "imageDimensionsHoverTitle": "Размер ÑохранÑемого Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ (ширина x выÑота)", + "cropSizeLowerThanResizedWarning": "Обратите внимание, что Ð²Ñ‹Ð±Ñ€Ð°Ð½Ð½Ð°Ñ Ð¾Ð±Ð»Ð°Ñть обрезки меньше применённого Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€Ð°, что может привеÑти к Ñнижению качеÑтва", + "actualSize": "ФактичеÑкий размер (100%)", + "fitSize": "Подогнать размер", + "addImageTitle": "Выберите изображение Ð´Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ...", + "mutualizedFailedToLoadImg": "Ðе удалоÑÑŒ загрузить изображение.", + "tabsMenu": "Меню", + "download": "Скачать", + "width": "Ширина", + "height": "Ð’Ñ‹Ñота", + "plus": "+", + "cropItemNoEffect": "Предварительный проÑмотр Ð´Ð»Ñ Ñтого Ñлемента обрезки недоÑтупен" +} diff --git a/public/locales/ru-RU/markdown_editor.json b/public/locales/ru-RU/markdown_editor.json new file mode 100755 index 0000000..c6f24ef --- /dev/null +++ b/public/locales/ru-RU/markdown_editor.json @@ -0,0 +1,114 @@ +{ + "frontmatterEditor": { + "title": "Редактировать метаданные документа", + "key": "Ключ", + "value": "Значение", + "addEntry": "Добавить запиÑÑŒ" + }, + "dialogControls": { + "save": "Сохранить", + "cancel": "Отмена" + }, + "uploadImage": { + "dialogTitle": "Загрузить изображение", + "uploadInstructions": "Загрузить изображение Ñ Ð²Ð°ÑˆÐµÐ³Ð¾ уÑтройÑтва:", + "addViaUrlInstructions": "Или введите URL Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ / отноÑительный путь (отноÑительно текущего файла):", + "autoCompletePlaceholder": "Выберите или вÑтавьте иÑточник изображениÑ", + "addViaUrlInstructionsNoUpload": "URL изображениÑ:", + "alt": "Ðльтернативный текÑÑ‚:", + "title": "Заголовок:" + }, + "imageEditor": { + "deleteImage": "Удалить изображение", + "editImage": "Редактировать изображение" + }, + "createLink": { + "url": "URL", + "urlPlaceholder": "Выберите или вÑтавьте URL", + "title": "Заголовок", + "saveTooltip": "УÑтановить URL", + "cancelTooltip": "Отменить изменениÑ" + }, + "linkPreview": { + "open": "Открыть {{url}} в новом окне", + "edit": "Редактировать URL ÑÑылки", + "copyToClipboard": "Скопировать в буфер обмена", + "copied": "Скопировано!", + "remove": "Удалить ÑÑылку" + }, + "table": { + "deleteTable": "Удалить таблицу", + "columnMenu": "Меню Ñтолбца", + "textAlignment": "Выравнивание текÑта", + "alignLeft": "По левому краю", + "alignCenter": "По центру", + "alignRight": "По правому краю", + "insertColumnLeft": "Ð’Ñтавить Ñтолбец Ñлева от текущего", + "insertColumnRight": "Ð’Ñтавить Ñтолбец Ñправа от текущего", + "deleteColumn": "Удалить Ñтот Ñтолбец", + "rowMenu": "Меню Ñтроки", + "insertRowAbove": "Ð’Ñтавить Ñтроку выше текущей", + "insertRowBelow": "Ð’Ñтавить Ñтроку ниже текущей", + "deleteRow": "Удалить Ñту Ñтроку" + }, + "toolbar": { + "blockTypes": { + "paragraph": "Ðбзац", + "quote": "Цитата", + "heading": "Заголовок {{level}}" + }, + "blockTypeSelect": { + "selectBlockTypeTooltip": "Выбрать тип блока", + "placeholder": "Тип блока" + }, + "toggleGroup": "Переключить группу", + "removeBold": "Убрать полужирный", + "bold": "Полужирный", + "removeItalic": "Убрать курÑив", + "italic": "КурÑив", + "underline": "Убрать подчёркивание", + "removeUnderline": "Подчёркивание", + "removeInlineCode": "Убрать Ñтиль вÑтроенного кода", + "inlineCode": "Стиль вÑтроенного кода", + "link": "Создать ÑÑылку", + "richText": "Форматированный текÑÑ‚", + "diffMode": "Режим различий", + "source": "Режим иÑходного кода", + "admonition": "Ð’Ñтавить блок примечаниÑ", + "codeBlock": "Ð’Ñтавить блок кода", + "editFrontmatter": "Редактировать метаданные", + "insertFrontmatter": "Ð’Ñтавить метаданные", + "image": "Ð’Ñтавить изображение", + "insertSandpack": "Ð’Ñтавить Sandpack", + "table": "Ð’Ñтавить таблицу", + "thematicBreak": "Ð’Ñтавить тематичеÑкий разрыв", + "bulletedList": "Маркированный ÑпиÑок", + "numberedList": "Ðумерованный ÑпиÑок", + "checkList": "СпиÑок задач", + "deleteSandpack": "Удалить Sandpack", + "undo": "Отменить {{shortcut}}", + "redo": "Повторить {{shortcut}}", + "superscript": "ÐадÑтрочный индекÑ", + "subscript": "ПодÑтрочный индекÑ", + "strikethrough": "Зачёркнутый", + "removeSubscript": "Убрать подÑтрочный индекÑ", + "removeSuperscript": "Убрать надÑтрочный индекÑ", + "removeStrikethrough": "Убрать зачёркивание" + }, + "admonitions": { + "note": "Примечание", + "tip": "Совет", + "danger": "ОпаÑно", + "info": "ИнформациÑ", + "caution": "ОÑторожно", + "changeType": "Выбрать тип блока примечаниÑ", + "placeholder": "Тип блока примечаниÑ" + }, + "codeBlock": { + "language": "Язык блока кода", + "selectLanguage": "Выбрать Ñзык блока кода" + }, + "contentArea": { + "editableMarkdown": "редактируемый markdown" + } +} diff --git a/public/locales/zh-CN/application.json b/public/locales/zh-CN/application.json new file mode 100755 index 0000000..b11521f --- /dev/null +++ b/public/locales/zh-CN/application.json @@ -0,0 +1,919 @@ +{ + "login": { + "lastStep": "最åŽä¸€æ­¥", + "siginToYourAccount": "登录你的账å·", + "createNewAccount": "创建新账å·", + "enterPassword": "请输入密ç ", + "enterPasswordHint": "è¯·è¾“å…¥è´¦å· {{email}} 对应的密ç ", + "paswordlessHint": "è´¦å· {{email}} 为无密ç è´¦æˆ·ï¼Œè¯·é€‰æ‹©ä¸‹åˆ—æ–¹å¼è®¤è¯ï¼š", + "noAccountSignupNow": "还没有账å·ï¼Ÿ<0>ç«‹å³æ³¨å†Œ", + "haveAccountSignInNow": "已有账å·ï¼Ÿ<0>ç«‹å³ç™»å½•", + "privacyPolicy": "éšç§æ”¿ç­–", + "termOfUse": "ä½¿ç”¨æ¡æ¬¾", + "signupHint": "你输入的账户 {{email}} ä¸å­˜åœ¨ï¼Œæ˜¯å¦ç«‹å³æ³¨å†Œï¼Ÿ", + "accountNotFoundHint": "你输入的账户 {{email}} ä¸å­˜åœ¨ã€‚", + "or": "或者", + "selectAccountToUse": "选择è¦ä½¿ç”¨çš„è´¦å·", + "useOtherAccount": "使用其他账å·", + "email": "电å­é‚®ç®±", + "password": "密ç ", + "captcha": "验è¯ç ", + "captchaError": "验è¯ç åŠ è½½å¤±è´¥: {{message}}", + "signIn": "登录", + "signUp": "注册", + "signUpAccount": "注册账å·", + "useFIDO2": "使用通行密钥器登录", + "usePassword": "使用密ç ç™»å½•", + "forgetPassword": "忘记密ç ï¼Ÿ", + "2FA": "二步验è¯", + "input2FACode": "请输入 6 ä½äºŒæ­¥éªŒè¯ä»£ç ", + "passwordNotMatch": "两次密ç è¾“å…¥ä¸ä¸€è‡´", + "findMyPassword": "找回密ç ", + "passwordReset": "密ç å·²é‡è®¾", + "newPassword": "新密ç ", + "repeatNewPassword": "é‡å¤æ–°å¯†ç ", + "repeatPassword": "é‡å¤å¯†ç ", + "resetPassword": "é‡è®¾å¯†ç ", + "backToSingIn": "返回登录", + "sendMeAnEmail": "å‘é€å¯†ç é‡ç½®é‚®ä»¶", + "resetEmailSent": "密ç é‡ç½®é‚®ä»¶å·²å‘é€ï¼Œè¯·æ³¨æ„查收", + "browserNotSupport": "当剿µè§ˆå™¨æˆ–çŽ¯å¢ƒä¸æ”¯æŒ", + "success": "登录æˆåŠŸ", + "signUpSuccess": "注册æˆåŠŸ", + "activateSuccess": "激活æˆåŠŸ", + "accountActivated": "您的账å·å·²è¢«æˆåŠŸæ¿€æ´»", + "title": "登录 {{title}}", + "sinUpTitle": "注册 {{title}}", + "activateTitle": "邮件激活", + "activateDescription": "䏀尿¿€æ´»é‚®ä»¶å·²ç»å‘é€è‡³æ‚¨çš„é‚®ç®±ï¼Œè¯·è®¿é—®é‚®ä»¶ä¸­çš„é“¾æŽ¥ä»¥ç»§ç»­å®Œæˆæ³¨å†Œã€‚", + "continue": "下一步", + "back": "上一步", + "logout": "退出登录", + "signingOut": "正在退出登录...", + "loggedOut": "您已退出登录", + "clickToRefresh": "点击刷新验è¯ç ", + "switchLanguage": "切æ¢è¯­è¨€" + }, + "navbar": { + "notBefore": "䏿—©äºŽ", + "notAfter": "䏿™šäºŽ", + "minimum": "最å°", + "maximum": "最大", + "fileSize": "文件大å°", + "searchBase": "æœç´¢è·¯å¾„", + "searchInBase": "æœç´¢ <0>", + "conditionDuplicate": "æ¡ä»¶å·²å­˜åœ¨", + "fileType": "文件类型", + "addCondition": "添加æ¡ä»¶", + "notNameOpOr": "需包å«å…¨éƒ¨å…³é”®è¯", + "caseFolding": "忽略大å°å†™", + "keywords": "关键字", + "fileNameKeywordsHelp": "è¾“å…¥åŽæŒ‰å›žè½¦é”®æ·»åР关键字", + "advancedSearch": "高级æœç´¢", + "searchFilesTitle": "æœç´¢æ–‡ä»¶", + "searchIn": "æœç´¢ <0>{{keywords}}", + "recentlyViewed": "最近æµè§ˆ", + "searchFiles": "æœç´¢æ–‡ä»¶...", + "showMore": "更多", + "myFiles": "我的文件", + "hisFiles": "他的文件", + "trash": "回收站", + "sharedWithMe": "与我共享", + "myShare": "我的分享", + "remoteDownload": "离线下载", + "connect": "连接与挂载", + "taskQueue": "åŽå°ä»»åŠ¡", + "setting": "设置", + "videos": "视频", + "photos": "图片", + "music": "音ä¹", + "documents": "文档", + "addATag": "添加标签...", + "addTagDialog": { + "selectFolder": "选择目录", + "fileSelector": "文件分类", + "folderLink": "ç›®å½•å¿«æ·æ–¹å¼", + "tagName": "标签å", + "matchPattern": "文件å匹é…规则", + "matchPatternDescription": "ä½ å¯ä»¥ä½¿ç”¨ <0>* 作为通é…符。比如 <1>*.png è¡¨ç¤ºåŒ¹é… png æ ¼å¼å›¾åƒã€‚多行规则间会以 “或†的关系进行è¿ç®—。", + "icon": "图标:", + "color": "颜色:", + "folderPath": "目录路径" + }, + "storage": "存储空间", + "storageDetail": "已使用 {{used}}, å…± {{total}}", + "notLoginIn": "未登录", + "visitor": "游客", + "objectsSelected": "{{num}} 个对象", + "searchPlaceholder": "按下 <0>/ 开始æœç´¢", + "backToHomepage": "返回主页", + "darkModeSwitch": "设置黑暗模å¼", + "toDarkMode": "黑暗", + "toLightMode": "浅色", + "myProfile": "个人主页", + "dashboard": "管ç†é¢æ¿" + }, + "fileManager": { + "customProps": "自定义属性", + "rating": "评级", + "description": "æè¿°", + "add": "添加", + "clickToEdit": "点击编辑...", + "clickToEditSelect": "点击选择...", + "enterUrl": "输入 URL...", + "searchUser": "æœç´¢ç”¨æˆ·...", + "typeToSearch": "输入昵称或邮箱...", + "searchProperty": "æœç´¢ç›¸åŒå±žæ€§çš„æ–‡ä»¶", + "permissions": "æƒé™", + "quality": "清晰度", + "audioTrack": "音轨", + "auto": "自动", + "default": "默认", + "shareWithMeEmpty": "没有找到别人的分享", + "shareWithMeEmptyDes": "如需è¦åœ¨æ­¤çœ‹åˆ°åˆ«äººçš„分享,请在访问别人分享链接时,在å³ä¸Šè§’å°†å¿«æ·æ–¹å¼ä¿å­˜åˆ°ä½ çš„æ–‡ä»¶ä¸­çš„ä»»æ„ä½ç½®ã€‚", + "selectAll": "全选", + "selectNone": "å–æ¶ˆé€‰æ‹©", + "invertSelection": "å选", + "imageSize": "图片尺寸", + "focalLength": "焦è·", + "columnExisted": "列已存在", + "metadataColumn": "å…ƒæ•°æ® ({{metadata}})", + "column": "列", + "listColumnSetting": "列设置", + "addColumn": "添加列", + "failedLoadPreview": "预览加载失败", + "recursiveLimitReached": "æœç´¢æ·±åº¦è¾¾åˆ°ä¸Šé™", + "recursiveLimitReachedDes": "ç³»ç»Ÿå·²åœæ­¢æœç´¢æ›´æ·±å±‚的目录,请å°è¯•ç¼©å°æœç´¢ç›®å½•的范围。", + "searchConditions": "{{num}} 个æ¡ä»¶", + "createDate": "创建日期", + "updatedDate": "修改日期", + "cameraMake": "相机制造商", + "cameraModel": "相机型å·", + "lensModel": "镜头型å·", + "lensMake": "镜头制造商", + "metadataKey": "é”®", + "metadataValue": "值", + "metadata": "元数æ®", + "symbolicFile": "å¿«æ·æ–¹å¼", + "relocation": "转移存储策略", + "downloadingFile": "正在下载 “{{name}}â€ï¼Œ 请ä¸è¦å…³é—­æœ¬é¡µé¢...", + "mountOwner": "åªæœ‰å½“å‰ç›®å½•的所有者å¯ä»¥æŒ‚载策略", + "uploading": "上传中", + "noActionsCanBeDone": "没有å¯ä»¥è¿›è¡Œçš„æ“ä½œ", + "newFileName": "新文件.{{ext}}", + "newDocumentType": "{{display_name}} (.{{ext}})", + "text": "文本", + "diagram": "图表", + "whiteboard": "白æ¿", + "selectApplications": "选择应用...", + "newlyCreatedFolder": "新建文件夹", + "expandAllApp": "展开所有应用", + "epubViewer": "ePub 阅读器", + "googledocs": "Google Docs 在线阅读器", + "m365viewer": "Microsoft Office 在线阅读器", + "pdfViewer": "PDF 阅读器", + "archivePreview": "压缩包预览", + "extractSelected": "解压缩选中的文件", + "viewerFileSizeWarning": "æ‰“å¼€çš„æ–‡ä»¶å¤§å° ({{file_size}}) 超过了 {{app}} çš„é™åˆ¶ ({{max}}),å¯èƒ½æ— æ³•正常工作。", + "testSubtitleStyle": "æµ‹è¯•å­—å¹•æ ·å¼ AaBbCc", + "color": "颜色", + "fontSize": "字体大å°", + "disableSubtitle": "ç¦ç”¨å­—幕", + "noSubtitle": "没有在当å‰ç›®å½•下找到 ASS/SRT/VTT 字幕文件。", + "subtitleStyles": "字幕样å¼", + "subtitles": "字幕", + "markdownEditor": "Markdown 编辑器", + "saveSuccess": "在 {{time}} ä¿å­˜æˆåŠŸ", + "drawioLng": "zh", + "charset": "ç¼–ç ", + "textType": "文本类型", + "fileSaved": "文件已ä¿å­˜", + "failedToLoadFile": "文件加载失败: {{msg}}", + "monacoEditor": "Monaco 代ç ç¼–辑器", + "preparingOpenFile": "正在准备打开文件...", + "openWithDescription": "选择一个应用打开 .{{ext}} 文件。", + "openWith": "打开方å¼", + "readOnly": "åªè¯»", + "save": "ä¿å­˜", + "noMoreImages": "当å‰é¡µé¢æ— å¯æµè§ˆå›¾åƒ", + "imageViewer": "图片查看器", + "logFileDeleteShare": "删除分享链接", + "logFileEditShare": "编辑分享链接", + "deleteShareWarning": "确定è¦åˆ é™¤æ­¤åˆ†äº«é“¾æŽ¥å—?", + "edit": "编辑", + "editAndReactivate": "ç¼–è¾‘å¹¶é‡æ–°æ¿€æ´»", + "yes": "是", + "no": "å¦", + "permanentValid": "永久有效", + "manageShares": "管ç†åˆ†äº«é“¾æŽ¥", + "manageDirectLinks": "管ç†ç›´é“¾", + "deleteLinkConfirm": "确定è¦åˆ é™¤æ­¤ç›´é“¾å—?", + "directLinkNotFound": "你查找的直链已ç»ä¸å­˜åœ¨ã€‚", + "versionNotFound": "你查找的版本已ç»ä¸å­˜åœ¨ã€‚", + "deleteVersionWarning": "确定è¦åˆ é™¤æ­¤ç‰ˆæœ¬å—?此æ“作无法撤销。", + "setAsCurrent": "设为当å‰ç‰ˆæœ¬", + "current": "[当å‰ç‰ˆæœ¬]", + "createdBy": "创建者", + "manageVersions": "管ç†ç‰ˆæœ¬", + "livePhoto": "Live Photo", + "version": "版本", + "actions": "æ“作", + "versionEntity": "文件数æ®å’Œåކå²ç‰ˆæœ¬", + "data": "æ•°æ®", + "owned": "拥有此文件", + "ownedSymbolic": "æ‹¥æœ‰æ­¤å¿«æ·æ–¹å¼", + "expires": "过期时间", + "originalLocation": "原始ä½ç½®", + "descendant": "å­å¯¹è±¡", + "folderChildren": "{{files}} 个文件,{{folders}} 个文件夹", + "moreThan": "大于 {{text}}", + "calculate": "计算", + "unset": "未设置", + "folder": "文件夹", + "file": "文件", + "symbolicLink": "å¿«æ·æ–¹å¼ ({{srcType}})", + "type": "类型", + "storageUsed": "å ç”¨ç©ºé—´", + "location": "ä½ç½®", + "basicInfo": "基本信æ¯", + "format": "æ ¼å¼", + "duration": "æ—¶é•¿", + "artist": "艺术家", + "album": "专辑", + "title": "标题", + "resolution": "分辨率", + "takenAt": "æ‹æ‘„æ—¶é—´", + "software": "软件", + "copyright": "作者", + "exposureBias": "æ›å…‰è¡¥å¿", + "flash": "闪光ç¯", + "copyToClipboard": "å¤åˆ¶åˆ°å‰ªåˆ‡æ¿", + "searchSomething": "æœç´¢ \"{{text}}\"...", + "iso": "ISO", + "exposureValue": "{{num}} ç§’", + "exposure": "æ›å…‰", + "aperture": "光圈", + "address": "地å€", + "street": "è¡—é“", + "locality": "城市区", + "place": "城市", + "district": "区", + "region": "çœ", + "country": "国家", + "mediaInfo": "媒体信æ¯", + "details": "详情", + "activity": "活动", + "goToSharedLink": "转到分享链接", + "saveShortcut": "ä¿å­˜åˆ†äº«ä¸ºå¿«æ·æ–¹å¼", + "customizeIcon": "自定义图标", + "tags": "标签", + "apply": "应用", + "customizeColor": "自定义颜色", + "folderColor": "文件夹颜色", + "restore": "还原", + "unpin": "å–æ¶ˆå›ºå®š", + "youDontHaveReadPermissionToThisFile": "你没有æƒé™è¯»å–此内容", + "anonymousAccessDenied": "你没有æƒé™è¯»å–此内容,请å°è¯•登录账å·ã€‚", + "sharedWithOthers": "与他人分享", + "new": "新建", + "open": "打开", + "openParentFolder": "转到所在目录", + "download": "下载", + "batchDownload": "打包下载", + "share": "分享", + "rename": "é‡å‘½å", + "organize": "æ•´ç†", + "pin": "固定到侧边æ ", + "pinAlias": "展示别å", + "optional": "å¯é€‰", + "move": "移动", + "delete": "删除", + "moreActions": "更多æ“作", + "refresh": "刷新", + "createArchive": "创建压缩文件", + "resetThumbnail": "é‡ç½®å¤±è´¥çš„缩略图", + "resetThumbnailRequested": "已请求é‡ç½®ç¼©ç•¥å›¾ã€‚", + "noFileCanResetThumbnail": "没有å¯é‡ç½®ç¼©ç•¥å›¾çš„æ–‡ä»¶ã€‚", + "newFolder": "创建文件夹", + "newFile": "创建文件", + "showFullPath": "显示路径", + "listView": "列表", + "gridView": "网格", + "galleryView": "画廊", + "paginationSize": "分页大å°", + "paginationOption": "{{option}} / 页", + "noPagination": "ä¸åˆ†é¡µ", + "sortMethod": "排åº", + "sortMethods": { + "A-Z": "A-Z", + "Z-A": "Z-A", + "oldestUploaded": "最早上传", + "newestUploaded": "最新上传", + "oldestModified": "最早修改", + "newestModified": "最新修改", + "smallest": "最å°", + "largest": "最大" + }, + "shareCreateBy": "ç”± {{nick}} 创建", + "name": "åç§°", + "size": "大å°", + "lastModified": "修改日期", + "currentFolder": "当å‰ç›®å½•", + "backToParentFolder": "上级目录", + "folders": "文件夹", + "files": "文件", + "listError": "请求时出现错误", + "dropFileHere": "拖拽文件至此", + "orClickUploadButton": "æˆ–ç‚¹å‡»å·¦ä¸Šæ–¹â€œæ–°å»ºâ€æŒ‰é’®æ·»åŠ æ–‡ä»¶", + "nothingFound": "什么都没有找到", + "uploadFiles": "上传文件", + "uploadFolder": "上传目录", + "newRemoteDownloads": "离线下载", + "enter": "进入", + "getSourceLink": "获å–直链", + "createRemoteDownloadForTorrent": "创建离线下载任务", + "extractArchive": "解压缩", + "createShareLink": "创建分享链接", + "viewDetails": "详细信æ¯", + "copy": "å¤åˆ¶", + "bytes": " ({{bytes}} 字节)", + "storagePolicy": "存储策略", + "childFolders": "包å«ç›®å½•", + "childFiles": "åŒ…å«æ–‡ä»¶", + "childCount": "{{num}} 个", + "parentFolder": "所在目录", + "rootFolder": "根目录", + "modifiedAt": "修改于", + "createdAt": "创建于", + "statisticAt": "统计于", + "musicPlayer": "音频播放器", + "closeAndStop": "退出播放", + "playInBackground": "åŽå°æ’­æ”¾", + "copyTo": "å¤åˆ¶åˆ°", + "copyToDst": "å¤åˆ¶åˆ° <0>", + "moveTo": "移动到", + "moveToDst": "移动到 <0>", + "errorReadFileContent": "æ— æ³•è¯»å–æ–‡ä»¶å†…容:{{msg}}", + "wordWrap": "自动æ¢è¡Œ", + "pdfLoadingError": "PDF 加载失败:{{msg}}", + "subtitleSwitchTo": "字幕切æ¢åˆ°ï¼š{{subtitle}}", + "noSubtitleAvailable": "视频目录下没有å¯ç”¨å­—幕文件 (支æŒï¼šASS/SRT/VTT)", + "subtitle": "选择字幕", + "playlist": "播放列表", + "openInExternalPlayer": "用外部播放器打开", + "repeatMode": "循环模å¼", + "listRepeat": "列表循环", + "singleRepeat": "啿›²å¾ªçޝ", + "shuffle": "éšæœºæ’­æ”¾", + "playbackSpeed": "播放速度", + "searchResult": "æœç´¢ç»“æžœ", + "preparingBathDownload": "正在准备打包下载...", + "preparingDownload": "正在准备下载...", + "browserDownload": "æµè§ˆå™¨ç«¯ä¸‹è½½åˆ°æœ¬åœ°ç›®å½•", + "browserDownloadDescription": "ç”±æµè§ˆå™¨é€ä¸€ä¸‹è½½æ–‡ä»¶ç»“构到你指定到本地目录。", + "browserBatchDownload": "æµè§ˆå™¨ç«¯æ‰“包", + "browserBatchDownloadDescription": "ç”±æµè§ˆå™¨å®žæ—¶ä¸‹è½½å¹¶æ‰“包为 Zip 文件,无法下载大于 4GB 的数æ®ã€‚", + "serverBatchDownload": "æœåŠ¡ç«¯ä¸­è½¬æ‰“åŒ…", + "serverBatchDownloadDescription": "ç”±æœåŠ¡ç«¯ä¸­è½¬æ‰“åŒ…ä¸º Zip 文件并实时å‘é€åˆ°å®¢æˆ·ç«¯ä¸‹è½½ï¼Œä¸æ”¯æŒåˆ†äº«å¿«æ·æ–¹å¼ã€‚", + "selectArchiveMethod": "选择批é‡ä¸‹è½½æ–¹å¼", + "batchDownloadStarted": "打包下载已开始,请ä¸è¦å…³é—­æ­¤é¡µé¢...", + "batchDownloadError": "打包é‡åˆ°é”™è¯¯ï¼š{{msg}}", + "userDenied": "用户拒ç»", + "directoryDownloadReplace": "æ›¿æ¢æ­¤æ–‡ä»¶", + "directoryDownloadReplaceDescription": "将会覆盖本地的 “{{name}}â€", + "directoryDownloadSkip": "跳过此文件", + "directoryDownloadSkipDescription": "将会跳过下载 “{{name}}â€", + "selectDirectoryDuplicationMethod": "文件é‡å", + "directoryDownloadReplaceAll": "æ›¿æ¢æ­¤æ–‡ä»¶å’ŒåŽç»­æ‰€æœ‰é‡å文件", + "directoryDownloadReplaceAllDescription": "将会覆盖本地的 “{{name}}â€ï¼Œå¹¶è®°ä½é€‰æ‹©", + "directoryDownloadSkipAll": "跳过此文件和åŽç»­æ‰€æœ‰é‡å文件", + "directoryDownloadSkipAllDescription": "将会跳过下载 “{{name}}â€ï¼Œå¹¶è®°ä½é€‰æ‹©", + "directoryDownloadStarted": "下载已开始,请ä¸è¦å…³é—­æ­¤æ ‡ç­¾é¡µ", + "directoryDownloadFinished": "下载完æˆï¼Œæ— å¤±è´¥å¯¹è±¡", + "directoryDownloadFinishedWithError": "下载完æˆ, 失败 {{failed}} 个对象", + "directoryDownloadPermissionError": "æ— æƒé™æ“作,请å…许读写本地文件", + "back": "åŽé€€", + "view": "视图", + "layout": "布局", + "thumbnails": "缩略图", + "on": "å¼€å¯", + "off": "关闭", + "viewSetting": "视图设置", + "saved": "å·²ä¿å­˜", + "notSet": "未设置", + "deleteViewSetting": "删除视图设置" + }, + "modals": { + "includePasswordInShareLink": "在链接中包å«å¯†ç ", + "includePasswordInShareLinkDes": "勾选åŽï¼Œåˆ†äº«é“¾æŽ¥ä¸­ä¼šåŒ…å«å¯†ç ï¼Œé€šè¿‡æ­¤é“¾æŽ¥è®¿é—®æ—¶ä¸éœ€è¦å†è¾“入密ç ã€‚", + "showFileName": "显示文件å", + "forceDownload": "强制下载", + "archiveFile": "压缩文件", + "cancelDownload": "å–æ¶ˆä¸‹è½½", + "always": "始终", + "justOnce": "仅一次", + "quality": "è´¨é‡", + "saveAsOtherFormat": "å¦å­˜ä¸ºå…¶ä»–æ ¼å¼", + "conflictDes1": "文件版本å‘生冲çªï¼Œå¯èƒ½çš„原因是:", + "conflictDes2": "<0>该文件在你打开åŽè¢«ä»Žå®ƒå¤„更新了新版本。<1>如果你å¦å­˜ä¸ºäº†æ–°æ–‡ä»¶å或新ä½ç½®ï¼Œå¯èƒ½å·²æœ‰åŒå文件存在。", + "saveAs": "å¦å­˜ä¸º", + "versionConflict": "版本冲çª", + "overwrite": "覆盖", + "editShareLink": "编辑分享链接", + "clearPermissions": "清除æƒé™è®¾ç½®", + "shortcutCreated": "å¿«æ·æ–¹å¼å·²åˆ›å»º", + "createShortcut": "åˆ›å»ºå¿«æ·æ–¹å¼", + "createShortcutTo": "在 <0> åˆ›å»ºå¿«æ·æ–¹å¼", + "targetExisted": "目标已存在", + "users": "用户", + "groups": "用户组", + "noResults": "没有结果", + "resetToDefault": "é‡ç½®ä¸ºé»˜è®¤", + "duplicateTag": "标签 \"{{tag}}\" 已存在", + "colorForTag": "自定义新标签颜色", + "enterForNewTag": "按回车键添加新标签", + "manageTags": "ç®¡ç†æ ‡ç­¾", + "onlyOwner": "åªæœ‰æ–‡ä»¶æ‰€æœ‰è€…å¯ä»¥å¼ºåˆ¶è§£é”此文件", + "forceUnlock": "强制解é”", + "forceUnlockAll": "强制解é”全部", + "forceUnlockDes": "强制解é”å¯èƒ½ä¼šå¯¼è‡´æ–‡ä»¶çжæ€å¼‚常,推è优先等待文件被主动释放。确定è¦ç»§ç»­è§£é”å—?", + "webdav": "WebDAV", + "soft-delete": "移至回收站", + "updateMetadata": "更新元数æ®", + "upload": "上传", + "moveCopy": "移动或å¤åˆ¶", + "view": "查看", + "cannotPerformAction": "䏿”¯æŒç§»åŠ¨æˆ–å¤åˆ¶åˆ°æ­¤å¤„", + "cannotMoveCopyToChild": "无法移动或å¤åˆ¶åˆ°å­ç›®å½•", + "copySuccess": "æˆåŠŸå¤åˆ¶ {{num}} 个文件", + "moveSuccess": "æˆåŠŸç§»åŠ¨ {{num}} 个文件", + "unknownParent": "未知父目录", + "unknownParentDes": "被å ç”¨çš„目录是共享目录的父目录,它ä¸å±žäºŽä½ æ‰€æœ‰", + "lockConflictTitle": "文件被å ç”¨", + "lockConflictDescription": "æ“作无法完æˆï¼Œå› ä¸ºä¸‹åˆ—文件正在被使用,请ç¨åŽé‡è¯•。 如果你是文件所有者,并且确定文件没有被使用,你å¯ä»¥å¼ºåˆ¶è§£é”文件并é‡è¯•。", + "application": "应用", + "errorDetailsTitle": "错误详情", + "processingMoving": "正在移动文件...", + "processingCopying": "正在å¤åˆ¶æ–‡ä»¶...", + "processingRestoring": "正在æ¢å¤æ–‡ä»¶...", + "fileRestored": "å·²æ¢å¤ {{num}} 个文件至原ä½", + "duplicatedObjectName": "æ–°å称与已有文件é‡å¤", + "newNameLengthError": "文件å长度必须在 1~255 个字符之间", + "newNameCharacterError": "文件åä¸èƒ½åŒ…å«ä»¥ä¸‹å­—符:\\ / : * ? \" < > |", + "newNameDotError": "文件åä¸èƒ½ä¸º \".\" 或 \"..\"", + "taskCreated": "任务已创建", + "taskCreateFailed": "{{failed}} 个任务创建失败:{{details}}", + "linkCopied": "链接已å¤åˆ¶", + "getSourceLinkTitle": "èŽ·å–æ–‡ä»¶ç›´é“¾", + "sourceLink": "文件直链", + "folderName": "文件夹åç§°", + "create": "创建", + "fileName": "文件å", + "renameDescription": "输入 <0>{{name}} 的新å称:", + "newName": "æ–°åç§°", + "moveToDescription": "移动至 <0>{{name}}", + "saveToTitle": "ä¿å­˜è‡³", + "saveToTitleDescription": "ä¿å­˜è‡³ <0>{{name}}", + "deleteTitle": "删除对象", + "deleteOneDescription": "确定è¦å°† <0>{{name}} 移至回收站å—?", + "deleteMultipleDescription": "确定è¦å°†è¿™ {{num}} 个对象移至回收站å—?", + "deleteOneDescriptionHard": "ç¡®å®šè¦æ°¸ä¹…删除 <0>{{name}} å—?", + "trashRetention": "回收站中的文件会在 <0>{{num}} åŽè‡ªåŠ¨åˆ é™¤ã€‚", + "deleteMultipleDescriptionHard": "ç¡®å®šè¦æ°¸ä¹…删除这 {{num}} 个对象å—?", + "newRemoteDownloadTitle": "新建离线下载任务", + "remoteDownloadURL": "下载链接", + "remoteDownloadURLDescription": "输入文件下载地å€ï¼Œä¸€è¡Œä¸€ä¸ª", + "remoteDownloadDst": "下载至", + "processNode": "处ç†èŠ‚ç‚¹", + "remoteDownloadNodeAuto": "自动分é…", + "createTask": "创建任务", + "downloadToDst": "下载至 <0>{{name}}", + "downloadTo": "下载至", + "decompressTo": "解压缩至", + "decompressToDst": "解压缩至 <0>{{name}}", + "defaultEncoding": "缺çœ", + "chineseMajorEncoding": "简体中文常è§ç¼–ç ", + "selectEncoding": "ZIP 文件编ç ", + "password": "压缩文件密ç ", + "passwordDescription": "如果压缩文件未加密,此处请留空。", + "noEncodingSelected": "æœªé€‰æ‹©ç¼–ç æ–¹å¼", + "listingFiles": "åˆ—å–æ–‡ä»¶ä¸­...", + "listingFileError": "åˆ—å–æ–‡ä»¶æ—¶å‡ºé”™ï¼š{{message}}", + "generatingSourceLinks": "生æˆå¤–链中...", + "noFileCanGenerateSourceLink": "没有å¯ä»¥ç”Ÿæˆå¤–链的文件", + "sourceBatchSizeExceeded": "当å‰ç”¨æˆ·ç»„最大å¯åŒæ—¶ä¸º {{limit}} 个文件生æˆå¤–链", + "zipFileName": "压缩文件å", + "shareLinkShareContent": "我å‘你分享了:{{name}} 链接:{{link}}", + "shareLinkPasswordInfo": " 密ç ï¼š {{password}}", + "createShareLink": "创建分享链接", + "privateShare": "使用密ç ä¿æŠ¤é“¾æŽ¥", + "privateShareDes": "勾选åŽï¼Œéœ€è¦ä½¿ç”¨å¯†ç è®¿é—®åˆ†äº«é“¾æŽ¥ã€‚", + "useCustomPassword": "自定义分享密ç ", + "expireAfterDownload": "下载åŽè‡ªåŠ¨è¿‡æœŸ", + "sharePassword": "分享密ç ", + "randomlyGenerate": "éšæœºç”Ÿæˆ", + "expireAutomatically": "超时自动过期", + "downloadLimitOptions": "{{num}} 次下载", + "or": "或者", + "5minutes": "5 分钟", + "1hour": "1 å°æ—¶", + "1day": "1 天", + "7days": "7 天", + "30days": "30 天", + "custom": "自定义", + "minutes": "分钟", + "downloads": "次下载", + "expirePrefix": "", + "expireSuffix": "åŽè¿‡æœŸ", + "allowPreview": "å…许预览", + "allowPreviewDescription": "是å¦å…许在分享页é¢é¢„览文件内容", + "shareLink": "分享链接", + "sendLink": "å‘é€é“¾æŽ¥", + "directoryDownloadReplaceNotifiction": "已覆盖 {{name}}", + "directoryDownloadSkipNotifiction": "已跳过 {{name}}", + "directoryDownloadTitle": "批é‡ä¸‹è½½æ—¥å¿—", + "directoryDownloadStarted": "开始下载 “{{name}}â€", + "directoryDownloadFinished": "ä¸‹è½½å®Œæˆ â€œ{{name}}â€", + "directoryDownloadError": "é‡åˆ°é”™è¯¯ï¼š{{msg}}", + "directoryDownloadErrorNotification": "下载 {{name}} é‡åˆ°é”™è¯¯ï¼š{{msg}}", + "directoryDownloadAutoscroll": "自动滚动", + "directoryDownloadCancelled": "已喿¶ˆä¸‹è½½", + "advanceOptions": "高级选项", + "skipSoftDelete": "彻底删除文件", + "skipSoftDeleteDes": "跳过回收站,直接删除文件", + "unlinkOnly": "ä¿ç•™ç‰©ç†æ–‡ä»¶", + "unlinkOnlyDes": "ä»…åˆ é™¤æ–‡ä»¶è®°å½•ï¼Œç‰©ç†æ–‡ä»¶ä¸ä¼šè¢«åˆ é™¤", + "shareView": "分享视图设置", + "shareViewDes": "勾选åŽï¼Œå…¶ä»–用户访问此共享文件夹时å¯ä»¥çœ‹åˆ°ä½ ä¿å­˜åœ¨æœåŠ¡å™¨çš„è§†å›¾è®¾ç½®ï¼ˆå¸ƒå±€ã€æŽ’åºç­‰ï¼‰ã€‚", + "showReadme": "显示 README 文件", + "showReadmeDes": "勾选åŽï¼Œä¼šè‡ªåŠ¨ä¸ºè®¿é—®è€…å±•ç¤ºç›®å½•ä¸‹çš„ <0>README.md (区分大å°å†™) 文件。", + "viewSetting": "视图设置", + "saved": "å·²ä¿å­˜", + "notSet": "未设置", + "deleteViewSetting": "删除视图设置" + }, + "uploader": { + "fileCopyName": "副本_", + "overwriteTooltip": "文件é‡å时覆盖已有文件,åªé’ˆå¯¹æ–°æ·»åŠ çš„ä»»åŠ¡æœ‰æ•ˆ", + "rename": "使用新文件åé‡è¯•", + "overwrite": "覆盖已有文件", + "pasteFilesHere": "将文件粘贴到此处", + "clipboardDefaultFileName": "å‰ªè´´æ¿ {{date}}.png", + "uploadFromClipboard": "从剪贴æ¿ä¸Šä¼ ", + "uploadList": "上传列表", + "fileNotMatchError": "所选择文件与原始文件ä¸ç¬¦", + "unknownError": "出现未知错误:{{msg}}", + "taskListEmpty": "没有上传任务", + "hideTaskList": "éšè—列表", + "uploadTasks": "上传队列", + "moreActions": "更多æ“作", + "addNewFiles": "添加新文件", + "toggleTaskList": "展开/折å é˜Ÿåˆ—", + "pendingInQueue": "排队中...", + "preparing": "准备中...", + "processing": "处ç†ä¸­...", + "progressDescription": "已上传 {{uploaded}} , å…± {{total}} - {{percentage}}%", + "progressDescriptionFull": "{{speed}} 已上传 {{uploaded}} , å…± {{total}} - {{percentage}}%", + "progressDescriptionPlaceHolder": "已上传 - ", + "uploaded": "已上传", + "rootFolder": "根目录", + "unknownStatus": "未知", + "resumed": "断点续传", + "resumable": "坿¢å¤è¿›åº¦", + "retry": "é‡è¯•", + "deleteTask": "删除任务记录", + "cancelAndDelete": "å–æ¶ˆå¹¶åˆ é™¤", + "selectAndResume": "选å–åŒæ ·æ–‡ä»¶å¹¶æ¢å¤ä¸Šä¼ ", + "fileName": "文件å:", + "fileSize": "文件大å°ï¼š", + "sessionExpiredIn": "<0>过期", + "chunkDescription": "({{total}} 个分片, æ¯ä¸ªåˆ†ç‰‡ {{size}})", + "noChunks": "(无分片)", + "destination": "存放ä½ç½®ï¼š", + "storagePolicy": "存储策略:", + "uploadSession": "上传会è¯ï¼š", + "errorDetails": "错误信æ¯ï¼š", + "uploadSessionCleaned": "上传会è¯å·²æ¸…除", + "hideCompletedTooltip": "åˆ—è¡¨ä¸­ä¸æ˜¾ç¤ºå·²å®Œæˆã€å¤±è´¥ã€è¢«å–消的任务", + "hideCompleted": "éšè—已完æˆä»»åŠ¡", + "addTimeAscTooltip": "最先添加的任务排在最å‰", + "addTimeAsc": "最先添加é å‰", + "addTimeDescTooltip": "æœ€åŽæ·»åŠ çš„ä»»åŠ¡æŽ’åœ¨æœ€å‰", + "addTimeDesc": "æœ€åŽæ·»åŠ é å‰", + "showInstantSpeedTooltip": "å•个任务上传速度展示为瞬时速度", + "showInstantSpeed": "瞬时速度", + "showAvgSpeedTooltip": "å•个任务上传速度展示为平å‡é€Ÿåº¦", + "showAvgSpeed": "å¹³å‡é€Ÿåº¦", + "cleanAllSessionTooltip": "清空æœåŠ¡ç«¯æ‰€æœ‰æœªå®Œæˆçš„上传会è¯", + "cleanAllSession": "清空所有上传会è¯", + "cleanCompletedTooltip": "清除列表中已完æˆã€å¤±è´¥ã€è¢«å–消的任务", + "cleanCompleted": "清除已完æˆä»»åŠ¡", + "retryFailedTasks": "é‡è¯•所有失败任务", + "retryFailedTasksTooltip": "é‡è¯•队列中所有已失败的任务", + "setConcurrentTooltip": "è®¾å®šåŒæ—¶è¿›è¡Œçš„任务数é‡", + "setConcurrent": "设置并行数é‡", + "sizeExceedLimitError": "文件大å°è¶…出存储策略é™åˆ¶ï¼ˆæœ€å¤§ï¼š{{max}})", + "suffixNotAllowedError": "å­˜å‚¨ç­–ç•¥ä¸æ”¯æŒä¸Šä¼ æ­¤æ‰©å±•å的文件", + "regexpNotAllowedError": "å­˜å‚¨ç­–ç•¥ä¸æ”¯æŒä¸Šä¼ æ­¤å称的文件", + "suffixAllowed": "(支æŒçš„æ‰©å±•å:{{supported}})", + "suffixDenied": "ï¼ˆç¦æ­¢çš„æ‰©å±•å:{{denied}})", + "createUploadSessionError": "无法创建上传会è¯", + "deleteUploadSessionError": "无法删除上传会è¯", + "requestError": "请求失败: {{msg}} ({{url}})", + "chunkUploadError": "分片 [{{index}}] 上传失败", + "conflictError": "åŒå文件的上传任务已ç»åœ¨å¤„ç†ä¸­", + "chunkUploadErrorWithMsg": "分片上传失败: {{msg}}", + "chunkUploadErrorWithRetryAfter": "(请在 {{retryAfter}} ç§’åŽé‡è¯•)", + "emptyFileError": "æš‚ä¸æ”¯æŒä¸Šä¼ ç©ºæ–‡ä»¶è‡³ OneDrive,请通过创建文件按钮创建空文件", + "finishUploadError": "æ— æ³•å®Œæˆæ–‡ä»¶ä¸Šä¼ ", + "finishUploadErrorWithMsg": "æ— æ³•å®Œæˆæ–‡ä»¶ä¸Šä¼ : {{msg}}", + "ossFinishUploadError": "æ— æ³•å®Œæˆæ–‡ä»¶ä¸Šä¼ : {{msg}} ({{code}})", + "cosUploadFailed": "上传失败: {{msg}} ({{code}})", + "upyunUploadFailed": "上传失败: {{msg}}", + "parseResponseError": "无法解æžå“应: {{msg}} ({{content}})", + "concurrentTaskNumber": "åŒæ—¶ä¸Šä¼ çš„任务数é‡", + "dropFileHere": "æ¾å¼€é¼ æ ‡å¼€å§‹ä¸Šä¼ " + }, + "share": { + "statistics": "统计", + "expireAt": "<0>过期", + "expireAfterDownloads": "{{downloads}} 次下载åŽè¿‡æœŸ", + "somebodyShare": "{{name}} 的分享", + "expiredLink": "已失效的分享", + "sharedBy": "<0>{{nick}} 呿‚¨åˆ†äº«äº† {{num}} 个文件", + "files": "1 file", + "files_other": "{{count}} files", + "statisticsViews": "{{views}} 次æµè§ˆ", + "statisticsDownloads": "{{downloads}} 次下载 ", + "views": "{{count}} view", + "views_other": "{{count}} views", + "downloads": "{{count}} download", + "downloads_other": "{{count}} downloads", + "privateShareTitle": "{{nick}} 的加密分享", + "enterPassword": "分享密ç ", + "continue": "ç»§ç»­", + "shareCanceled": "分享链接已删除", + "listLoadingError": "加载失败", + "sharedFiles": "我的分享", + "createdAtDesc": "最新", + "createdAtAsc": "最早", + "noRecords": "没有分享记录.", + "sourceNotFound": "[原始对象ä¸å­˜åœ¨]", + "expired": "已失效", + "changeToPublic": "å˜æ›´ä¸ºå…¬å¼€åˆ†äº«", + "changeToPrivate": "å˜æ›´ä¸ºç§å¯†åˆ†äº«", + "viewPassword": "查看密ç ", + "disablePreview": "ç¦æ­¢é¢„览", + "enablePreview": "å…许预览", + "cancelShare": "å–æ¶ˆåˆ†äº«", + "sharePassword": "分享密ç ", + "readmeError": "æ— æ³•è¯»å– README 内容:{{msg}}", + "enterKeywords": "请输入æœç´¢å…³é”®è¯", + "searchResult": "æœç´¢ç»“æžœ", + "sharedAt": "分享于 <0>", + "pleaseLogin": "请先登录", + "cannotShare": "此文件无法预览", + "preview": "预览", + "incorrectPassword": "密ç ä¸æ­£ç¡®", + "shareNotExist": "分享ä¸å­˜åœ¨æˆ–已过期", + "copyLinkToClipboard": "å¤åˆ¶é“¾æŽ¥åˆ°å‰ªåˆ‡æ¿" + }, + "download": { + "noFilesFound": "没有找到任何文件", + "filterByName": "按å称过滤", + "selectAll": "全选", + "reverseSelect": "å选", + "cancelTaskConfirm": "确定è¦å–消此任务å—?", + "saveChanges": "ä¿å­˜æ›´æ”¹", + "failedToLoad": "加载失败", + "active": "进行中", + "finished": "已完æˆ", + "activeEmpty": "没有下载中的任务", + "finishedEmpty": "没有已完æˆçš„任务", + "loadMore": "加载更多", + "taskFileDeleted": "文件已删除", + "unknownTaskName": "[未知]", + "taskCanceled": "ä»»åŠ¡å·²å–æ¶ˆï¼Œçжæ€ä¼šåœ¨ç¨åŽæ›´æ–°", + "operationSubmitted": "æ“作æˆåŠŸï¼ŒçŠ¶æ€ä¼šåœ¨ç¨åŽæ›´æ–°", + "deleteThisFile": "删除此文件", + "openDstFolder": "打开存放目录", + "selectDownloadingFile": "选择è¦ä¸‹è½½çš„æ–‡ä»¶", + "cancelTask": "å–æ¶ˆä»»åŠ¡", + "updatedAt": "更新于:", + "uploaded": "上传大å°", + "uploadSpeed": "上传速度", + "InfoHash": "InfoHash", + "seederCount": "åšç§è€…:", + "seeding": "åšç§ä¸­ï¼š", + "downloadNode": "节点:", + "isSeeding": "是", + "notSeeding": "å¦", + "chunkSize": "分片大å°ï¼š", + "chunkNumbers": "分片数é‡", + "taskDeleted": "删除æˆåŠŸ", + "transferFailed": "文件转存失败", + "downloadFailed": "下载出错:{{msg}}", + "canceledStatus": "已喿¶ˆ", + "finishedStatus": "已完æˆ", + "pending": "已完æˆï¼Œè½¬å­˜æŽ’队中", + "transferring": "转存中", + "deleteRecord": "删除记录", + "createdAt": "创建日期:", + "unknownSize": "未知文件大å°" + }, + "setting": { + "treeView": "树视图", + "autoExpandTreeView": "自动展开树视图", + "autoExpandTreeViewDes": "å¼€å¯åŽï¼Œä¾§è¾¹æ çš„æ–‡ä»¶æ ‘会跟éšå½“å‰ç›®å½•并自动展开。", + "syncView": "视图设置", + "syncViewDes": "是å¦è®°ä½å„ä¸ªç›®å½•çš„è§†å›¾è®¾ç½®ï¼Œå¹¶åŒæ­¥åˆ°æœåŠ¡å™¨ã€‚", + "syncViewOn": "åŒæ­¥åˆ°æœåС噍", + "syncViewOff": "ä¸åŒæ­¥", + "noAuthenticator": "æ·»åŠ é€šè¡Œå¯†é’¥ä»¥ä½¿ç”¨äººè„¸ã€æŒ‡çº¹æˆ– USB 密钥登录账å·", + "neverUsed": "从未使用过", + "usedAt": "上次使用于 <0>", + "passkeyName": "{os} 上的 {browser}", + "versionRetentionMax": "最大版本数é‡ï¼Œ0 表示无é™åˆ¶", + "versionRetentionEnabledExt": "å¯ç”¨çš„æ–‡ä»¶æ‰©å±•å", + "versionRetentionEnabledExtDes": "按回车键添加,留空时会对所有文件å¯ç”¨", + "enableVersionRetention": "å¯ç”¨ç‰ˆæœ¬ä¿ç•™", + "enableVersionRetentionDes": "å¯ç”¨åŽï¼Œå¯¹äºŽç¬¦åˆæ¡ä»¶çš„æ–‡ä»¶ï¼Œç³»ç»Ÿä¼šä¿ç•™å…¶çš„历å²ç‰ˆæœ¬", + "versionRetention": "版本ä¿ç•™", + "languageDes": "设置应用展示语言和首选邮件语言", + "timezoneDes": "设置展示时区,默认跟éšç³»ç»Ÿæ—¶åŒº", + "nickNameDes": "用于公开展示的å字,å¯ä½¿ç”¨çœŸå®žå§“åæˆ–昵称", + "cropAvatar": "è£å‰ªå¤´åƒ", + "preference": "å好", + "accountCreatedAt": "创建于 <0>", + "shoeQr": "显示", + "deviceNothing": "当å‰ç”¨æˆ·ç»„䏿”¯æŒ WebDAV", + "connectionInfo": "连接信æ¯", + "proxyTooltip": "æœåŠ¡ç«¯ä»£ç†æ‰€æœ‰æ–‡ä»¶ä¸‹è½½è¯·æ±‚。", + "readonlyTooltip": "用户åªèƒ½é€šè¿‡æ­¤è´¦å·è¯»å–文件。", + "blockSysFilesUpload": "阻止上传系统文件", + "blockSysFilesUploadTooltip": "å¼€å¯åŽï¼Œä»¥ <0>. 开头的文件会被阻止上传。", + "rootFolderIn": "选择 <0>", + "createWebDavAccount": "创建 WebDAV è´¦å·", + "editWebDavAccount": "编辑 {{name}}", + "seeding": "åšç§ä¸­", + "awaitSeeding": "等待åšç§", + "awaitSeedingDes": "等待下载任务åšç§å®Œæˆã€‚", + "downloadTransferDes": "将文件转存到目的地。", + "downloadDes": "下载指定的文件。", + "retryErrorHistory": "历å²é‡è¯•错误", + "retryCount": "é‡è¯•次数", + "resumeAt": "下次æ¢å¤æ‰§è¡Œ", + "executeDuration": "执行净耗时", + "input": "输入", + "output": "输出", + "suspended": " (已挂起)", + "updatedAt": "更新于", + "taskDetails": "任务详情", + "partialSuccessWarning": "有 {{num}} 个对象处ç†å¤±è´¥ï¼Œå·²å°†å…¶è·³è¿‡ã€‚", + "sendTask": "å‘é€ä»»åŠ¡", + "sendTaskDes": "将任务å‘é€åˆ°å¤„ç†èŠ‚ç‚¹ã€‚", + "downloaded": "已下载", + "importingFiles": "导入文件", + "importingFilesDes": "检索文件并将其导入到指定目录。", + "importedFiles": "已导入文件", + "indexedFiles": "已索引文件", + "extractedFiles": "已解压文件数é‡", + "extractedFilesSize": "已解压文件大å°", + "extractingFiles": "解压文件", + "extractingFilesDes": "将所有文件解压到指定目录。", + "downloadingZip": "获å–压缩文件", + "downloadingZipDes": "将压缩文件下载到临时工作区。", + "progressNotAvailable": "进度信æ¯å°šæœªå¯ç”¨", + "uploadedSize": "转存文件", + "archivedFiles": "å·²å¤„ç†æ–‡ä»¶æ•°é‡", + "transferredFiles": "已转存文件数é‡", + "archivedFilesSize": "å·²å¤„ç†æ–‡ä»¶å¤§å°", + "createArchiveFinishing": "æäº¤æ–°å¢žæ–‡ä»¶æ›´æ”¹ã€‚", + "indexForArchiveDes": "检索所有待压缩文件。", + "prepare": "准备", + "preparingWorkspaceDes": "准备临时工作区。", + "compressFiles": "创建压缩文件", + "compressFilesDes": "将文件压缩到临时工作区。", + "uploadArchiveFileDes": "将压缩文件转存到目的地。", + "uploadWorker": "上传线程 #{{num}}", + "queueToStart": "排队开始", + "indexingFiles": "检索文件", + "indexingFilesDes": "检索所有待转移文件,并将其é”定。", + "transferring": "转存", + "committingChanges": "æäº¤æ›´æ”¹", + "autoRefresh": "自动刷新", + "avatarUpdated": "头åƒå·²æ›´æ–°ï¼Œæœ€æ–°å¤´åƒå±•示å¯èƒ½æœ‰å»¶è¿Ÿ", + "nickChanged": "昵称已更改,刷新åŽç”Ÿæ•ˆ", + "settingSaved": "设置已ä¿å­˜", + "themeColorChanged": "主题é…色已更æ¢", + "profile": "个人资料", + "avatar": "头åƒ", + "uid": "UID", + "nickname": "昵称", + "group": "用户组", + "regTime": "注册时间", + "security": "密ç å’Œå®‰å…¨", + "profilePage": "个人主页", + "publicShareOnly": "仅展示无密ç åˆ†äº«é“¾æŽ¥", + "publicShareOnlyDes": "仅在个人主页展示没有设置密ç çš„分享链接。", + "allShare": "所有分享", + "allShareDes": "在个人主页展示所有分享链接(包括有密ç çš„分享)。对于有密ç çš„分享,用户还需è¦è¾“å…¥å¯†ç æ‰èƒ½è®¿é—®ã€‚", + "hideShare": "éšè—所有分享链接", + "hideShareDes": "在个人主页éšè—所有分享链接。", + "userHideShare": "用户éšè—了分享链接列表", + "accountPassword": "登录密ç ", + "2fa": "二步验è¯", + "enabled": "已开å¯", + "disabled": "未开å¯", + "appearance": "个性化", + "themeColor": "主题é…色", + "darkMode": "黑暗模å¼", + "syncWithSystem": "系统", + "fileList": "文件列表", + "timeZone": "时区", + "webdavServer": "连接地å€", + "userName": "用户å", + "manageAccount": "è´¦å·ç®¡ç†", + "uploadImage": "从文件上传", + "useGravatar": "使用 Gravatar å¤´åƒ ", + "changeNick": "修改昵称", + "originalPassword": "原密ç ", + "enable2FA": "å¯ç”¨äºŒæ­¥éªŒè¯", + "disable2FA": "关闭二步验è¯", + "2faDescription": "请使用任æ„äºŒæ­¥éªŒè¯ APP 或者支æŒäºŒæ­¥éªŒè¯çš„密ç ç®¡ç†è½¯ä»¶æ‰«æäºŒç»´ç æ·»åŠ æœ¬ç«™ã€‚æ‰«æå®ŒæˆåŽè¯·å¡«å†™äºŒæ­¥éªŒè¯ APP 给出的 6 ä½éªŒè¯ç ä»¥å¼€å¯äºŒæ­¥éªŒè¯ã€‚", + "inputCurrent2FACode": "输入当å‰äºŒæ­¥éªŒè¯ APP 给出的 6 ä½éªŒè¯ç ï¼š", + "timeZoneCode": "IANA 时区å称标识", + "authenticatorRemoved": "凭è¯å·²åˆ é™¤", + "authenticatorAdded": "验è¯å™¨å·²æ·»åŠ ", + "browserNotSupported": "当剿µè§ˆå™¨æˆ–çŽ¯å¢ƒä¸æ”¯æŒ", + "removedAuthenticator": "删除凭è¯", + "removedAuthenticatorConfirm": "确定è¦åŠé”€è¿™ä¸ªå‡­è¯å—?", + "addNewAuthenticator": "添加新凭è¯", + "hardwareAuthenticator": "通行密钥", + "copied": "å·²å¤åˆ¶åˆ°å‰ªåˆ‡æ¿", + "pleaseManuallyCopy": "当剿µè§ˆå™¨ä¸æ”¯æŒï¼Œè¯·æ‰‹åЍå¤åˆ¶", + "webdavAccounts": "WebDAV è´¦å·ç®¡ç†", + "webdavHint": "WebDAV的地å€ä¸ºï¼š{{url}};登录用户å统一为:{{name}} ;密ç ä¸ºæ‰€åˆ›å»ºè´¦å·çš„密ç ã€‚", + "annotation": "备注å", + "rootFolder": "相对根目录", + "createdAt": "创建日期", + "action": "æ“作", + "readonlyOn": "åªè¯»", + "readonlyOff": "读写", + "proxy": "åå‘代ç†", + "none": "æ— ", + "proxied": "已代ç†", + "delete": "删除", + "listEmpty": "没有记录", + "createNewAccount": "创建新账å·", + "taskType": "任务类型", + "taskStatus": "状æ€", + "taskProgress": "任务进度", + "errorDetails": "错误信æ¯", + "queueing": "排队中", + "processing": "处ç†ä¸­", + "failed": "失败", + "canceled": "å–æ¶ˆ", + "finished": "已完æˆ", + "fileTransfer": "文件中转", + "fileRecycle": "文件回收", + "importFiles": "导入外部目录", + "transferProgress": "å·²å®Œæˆ {{num}} 个文件", + "waiting": "等待中", + "compressing": "压缩中", + "decompressing": "解压缩中", + "downloading": "下载中", + "indexing": "索引中", + "listing": "æ’入中", + "allShares": "全部分享", + "trendingShares": "热门分享", + "totalShares": "分享总数", + "fileName": "文件å", + "shareDate": "分享日期", + "downloadNumber": "下载次数", + "viewNumber": "æµè§ˆæ¬¡æ•°", + "language": "语言", + "iOSApp": "iOS/iPadOS 客户端", + "connectByiOS": "通过 iOS/iPadOS 设备连接到 <0>{{title}}", + "downloadOurApp": "下载并安装我们的应用:", + "fillInEndpoint": "使用应用扫æä¸‹æ–¹äºŒç»´ç ï¼ˆå…¶ä»–扫ç åº”用无效):", + "loginApp": "完æˆç»‘定,你å¯ä»¥å¼€å§‹ä½¿ç”¨å®¢æˆ·ç«¯äº†ã€‚如果扫ç ç»‘定é‡åˆ°é—®é¢˜ï¼Œä½ ä¹Ÿå¯ä»¥å°è¯•手动输入用户å和密ç ç™»å½•。", + "relocateFileTo": "å°† <0>{{more}} 的存储策略转移至 {{policy}}", + "extractFileTo": "å°† <0>{{more}} 解压缩至 <1>", + "createArchiveTo": "å°† <0>{{more}} 打包至 <1>", + "importFileTo": "å°† {{policy}} 中的文件导入至 <0>" + }, + "vas": { + "points": "积分", + "quota": "容é‡é…é¢", + "used": "已使用 - {{size}}", + "total": "æ€»å®¹é‡ - {{size}}", + "report": "滥用举报", + "validDurationDays": "{{num}} 天", + "reportTarget": "举报对象", + "reportReason": "原因", + "reportReasonOptions": ["ä¾µæƒ", "有害内容", "垃圾信æ¯", "å…¶ä»–"], + "reportDescription": "补充说明", + "reportAbuseSuccess": "举报已æäº¤" + } +} diff --git a/public/locales/zh-CN/common.json b/public/locales/zh-CN/common.json new file mode 100755 index 0000000..025d6b4 --- /dev/null +++ b/public/locales/zh-CN/common.json @@ -0,0 +1,106 @@ +{ + "pageNotFound": "页é¢ä¸å­˜åœ¨", + "unknownError": "未知错误", + "errLoadingSiteConfig": "无法加载站点é…置:", + "newVersionRefresh": "当å‰é¡µé¢æœ‰æ–°ç‰ˆæœ¬å¯ç”¨ã€‚", + "update": "æ›´æ–°", + "errorDetails": "详情", + "renderError": "页颿¸²æŸ“出现错误,请å°è¯•刷新此页é¢ã€‚", + "ok": "确定", + "cancel": "å–æ¶ˆ", + "select": "选择", + "copyToClipboard": "å¤åˆ¶", + "close": "关闭", + "dismiss": "关闭", + "intlDateTime": "{{val, datetime}}", + "seconds": "s ç§’", + "minutes": "m 分 s ç§’", + "hours": "H å°æ—¶ m 分", + "days": "{{d}} 天", + "timeAgoLocaleCode": "zh_CN", + "forEditorLocaleCode": "zh-CN", + "artPlayerLocaleCode": "zh-cn", + "requestID": "请求 ID: {{id}}", + "object": "对象", + "error": "错误", + "areYouSure": "确认", + "incorrectSizeInput": "ä¸ç¬¦åˆå°ºå¯¸é™åˆ¶", + "of": "å…±", + "rowsPerPage": "æ¯é¡µè¡Œæ•°", + "custom": "自定义", + "enter": "输入", + "captcha": { + "cap": { + "human": "我是人类", + "verifying": "验è¯ä¸­â€¦", + "verified": "您通过了验è¯" + } + }, + "errors": { + "401": "请先登录", + "403": "你没有æƒé™æ‰§è¡Œæ­¤æ“作", + "404": "资æºä¸å­˜åœ¨", + "409": "å‘ç”Ÿå†²çª ({{message}})", + "40001": "è¾“å…¥å‚æ•°æœ‰è¯¯ ({{message}})", + "40002": "上传失败", + "40003": "目录创建失败", + "40004": "åŒå对象已存在", + "40005": "ç­¾å过期", + "40006": "䏿”¯æŒçš„存储策略类型", + "40007": "当å‰ç”¨æˆ·ç»„无法进行此æ“作", + "40011": "上传会è¯ä¸å­˜åœ¨æˆ–已过期", + "40012": "分片åºå·æ— æ•ˆ ({{message}})", + "40013": "正文长度无效 ({{message}})", + "40014": "超出批é‡èŽ·å–外链é™åˆ¶", + "40015": "超出最大离线下载任务数é‡é™åˆ¶", + "40016": "路径ä¸å­˜åœ¨", + "40017": "该账å·å·²è¢«å°ç¦", + "40018": "è¯¥è´¦å·æœªæ¿€æ´»", + "40019": "此功能未å¯ç”¨", + "40020": "å‡­è¯æ— æ•ˆæˆ–过期", + "40021": "用户ä¸å­˜åœ¨", + "40022": "验è¯ä»£ç ä¸æ­£ç¡®", + "40023": "登录会è¯ä¸å­˜åœ¨", + "40024": "无法åˆå§‹åŒ– WebAuthn", + "40025": "验è¯å¤±è´¥", + "40026": "验è¯ç é”™è¯¯", + "40027": "验è¯å¤±è´¥ï¼Œè¯·åˆ·æ–°ç½‘页é‡è¯•", + "40028": "邮件å‘é€å¤±è´¥", + "40029": "无效的链接", + "40030": "此链接已过期", + "40032": "此邮箱已被使用", + "40033": "ç”¨æˆ·æœªæ¿€æ´»ï¼Œå·²é‡æ–°å‘逿¿€æ´»é‚®ä»¶", + "40034": "该用户无法被激活", + "40035": "存储策略ä¸å­˜åœ¨", + "40039": "用户组ä¸å­˜åœ¨", + "40044": "文件ä¸å­˜åœ¨", + "40045": "无法列å–目录下的对象", + "40047": "无法åˆå§‹åŒ–文件系统", + "40048": "创建任务出错", + "40049": "文件大å°è¶…出é™åˆ¶", + "40050": "文件类型ä¸å…许", + "40051": "容é‡ç©ºé—´ä¸è¶³", + "40052": "æ­¤æ–‡ä»¶åæˆ–扩展åä¸è¢«å…许", + "40053": "䏿”¯æŒå¯¹æ ¹ç›®å½•执行此æ“作", + "40054": "è¯å½“å‰ç›®å½•ä¸‹å·²ç»æœ‰åŒå文件正在上传中,请å°è¯•清空上传会è¯", + "40055": "文件信æ¯ä¸ä¸€è‡´", + "40056": "䏿”¯æŒè¯¥æ ¼å¼çš„压缩文件", + "40057": "å¯ç”¨å­˜å‚¨ç­–ç•¥å‘生å˜åŒ–ï¼Œè¯·åˆ·æ–°æ–‡ä»¶åˆ—è¡¨å¹¶é‡æ–°æ·»åŠ æ­¤ä»»åŠ¡", + "40058": "分享ä¸å­˜åœ¨æˆ–已过期", + "40069": "密ç ä¸æ­£ç¡®", + "40070": "此分享无法预览", + "40071": "ç­¾åæ— æ•ˆ", + "40073": "文件被å ç”¨", + "40074": "所选文件数é‡è¶…出é™åˆ¶", + "40079": "超出最大é历文件数é™åˆ¶ï¼Œè¯·ç¼©å°æ“作范围", + "40081": "æ“作未完全æˆåŠŸ", + "40082": "åªæœ‰æ–‡ä»¶æ‰€æœ‰è€…å¯ä»¥æ‰§è¡Œæ­¤æ“作", + "40080": "用户邮箱或密ç é”™è¯¯", + "50001": "æ•°æ®åº“æ“作失败 ({{message}})", + "50002": "URL 或请求签å失败 ({{message}})", + "50004": "I/O æ“作失败 ({{message}})", + "50005": "内部错误 ({{message}})", + "50010": "目标节点ä¸å¯ç”¨", + "50011": "æ–‡ä»¶å…ƒä¿¡æ¯æŸ¥è¯¢å¤±è´¥" + } +} diff --git a/public/locales/zh-CN/dashboard.json b/public/locales/zh-CN/dashboard.json new file mode 100755 index 0000000..f8cfe32 --- /dev/null +++ b/public/locales/zh-CN/dashboard.json @@ -0,0 +1,1623 @@ +{ + "errors": { + "40036": "默认存储策略无法删除", + "40037": "有文件 Blob ä»åœ¨ä½¿ç”¨æ­¤å­˜å‚¨ç­–略,请先删除这些文件 Blob", + "40038": "有 {{message}} 个用户组绑定了此存储策略,请先解除绑定", + "40040": "无法对系统用户组执行此æ“作", + "40041": "有 {{message}} ä½ç”¨æˆ·ä»å±žäºŽæ­¤ç”¨æˆ·ç»„,请先删除这些用户或者更改用户组", + "40042": "无法更改åˆå§‹ç”¨æˆ·çš„用户组", + "40043": "无法对åˆå§‹ç”¨æˆ·æ‰§è¡Œæ­¤æ“作", + "40046": "无法对主机节点执行此æ“作", + "40060": "从机无法å‘主机å‘é€å›žè°ƒè¯·æ±‚,请检查主机端 傿•°è®¾ç½® - ç«™ç‚¹ä¿¡æ¯ - 站点URL设置,并确ä¿ä»Žæœºå¯ä»¥è¿žæŽ¥åˆ°æ­¤åœ°å€ ({{message}})", + "40061": "Cloudreve 版本ä¸ä¸€è‡´ ({{message}})", + "40086": "节点正在被以下存储策略使用:{{message}}", + "50008": "设置项更新失败 ({{message}})", + "50009": "跨域策略添加失败" + }, + "nav": { + "summary": "颿¿é¦–页", + "settings": "傿•°è®¾ç½®", + "basicSetting": "站点信æ¯", + "email": "邮件", + "transportation": "传输与通信", + "appearance": "外观", + "image": "图åƒä¸Žé¢„览", + "captcha": "验è¯ç ", + "storagePolicy": "存储策略", + "nodes": "节点", + "groups": "用户组", + "users": "用户", + "files": "文件", + "entities": "文件 Blob", + "shares": "分享", + "tasks": "åŽå°ä»»åŠ¡", + "remoteDownload": "离线下载", + "generalTasks": "常规任务", + "title": "仪表盘", + "dashboard": "Cloudreve 仪表盘", + "userSession": "用户会è¯", + "fileSystem": "文件系统", + "mediaProcessing": "媒体处ç†", + "queue": "队列", + "events": "事件", + "server": "æœåС噍", + "customProps": "自定义属性", + "abuseReport": "滥用举报" + }, + "summary": { + "generatedAt": "生æˆäºŽ <0>", + "confirmSiteURLTitle": "确定站点URL设置", + "siteURLNotMatch": "你设置的站点 URL 并未包å«å½“å‰çš„ {{current}},是å¦è¦æ›´æ”¹è®¾ç½®ï¼Ÿ", + "setAsPrimary": "设置为主è¦ç«™ç‚¹ URL", + "setAsPrimaryDes": "å°† {{current}} 设置为主è¦ç«™ç‚¹ URL,用于与外部æœåŠ¡é€šä¿¡å’ŒæŽ¥å—回调,请使用能被公网访问的 URL。", + "setAsSecondary": "添加到备选站点 URL", + "setAsSecondaryDes": "å°† {{current}} 添加到备选站点 URL,Cloudreve 会根æ®ç”¨æˆ·å®žé™…访问的 URL 自动选择是å¦ä½¿ç”¨ã€‚", + "siteURLDescription": "此设置éžå¸¸é‡è¦ï¼Œè¯·ç¡®ä¿å…¶ä¸Žä½ ç«™ç‚¹çš„实际地å€ä¸€è‡´ã€‚ä½ å¯ä»¥åœ¨ 傿•°è®¾ç½® - ç«™ç‚¹ä¿¡æ¯ ä¸­æ›´æ”¹æ­¤è®¾ç½®ã€‚", + "ignore": "忽略", + "changeIt": "更改", + "trend": "趋势", + "summary": "总计", + "totalUsers": "注册用户", + "totalFilesAndFolders": "文件与目录", + "shareLinks": "分享链接", + "totalBlobs": "文件 Blob", + "homepage": "主页", + "github": "GitHub", + "documents": "文档", + "discordCommunity": "Discord 社群", + "telegram": "Telegram 社群", + "forum": "社区论å›", + "buyPro": "å‡çº§åˆ° Pro", + "publishedAt": "å‘表于 <0>", + "licenseExpireAt": "æŽˆæƒæœ‰æ•ˆæœŸ", + "permanentLicense": "永久", + "offlineLicenseExpireAy": "离线授æƒè¿‡æœŸæ—¥æœŸ", + "offlineLicenseDes": "在连接网络的情况下,Cloudreve 会在过期å‰è‡ªåŠ¨æ›´æ–°ç¦»çº¿æŽˆæƒ", + "licensedDomains": "授æƒçš„域å", + "renew": "更新离线授æƒ", + "manageLicense": "ç®¡ç†æŽˆæƒ", + "volPurchase": "客户端 VOL 授æƒéœ€è¦å•独在 <0>授æƒç®¡ç†é¢æ¿ 购买。VOL 授æƒå…许你的用户å…费使用 <1>Cloudreve iOS 客户端 连接到你的站点,无需用户å†ä»˜è´¹è®¢é˜… iOS 客户端。购买授æƒåŽè¯·ç‚¹å‡»ä¸‹æ–¹æ›´æ–°æŽˆæƒã€‚", + "iosVol": "iOS å®¢æˆ·ç«¯æ‰¹é‡æŽˆæƒ (VOL)", + "refreshSuccessfully": "刷新æˆåŠŸ", + "manualRefresh": "手动刷新离线授æƒ", + "manualRefreshDes": "自动刷新离线授æƒå¤±è´¥ï¼Œè¯·å°è¯•登录 <0>授æƒç®¡ç†é¢æ¿ èŽ·å–æœ€æ–°çš„离线授æƒï¼Œå°†å…¶ç²˜è´´åœ¨ä¸‹æ–¹ã€‚" + }, + "queue": { + "queueName_io_intense": "IO 密集型", + "queueName_io_intenseDes": "用于处ç†å¤§é‡ IO æ“作的队列,包括:转移存储策略ã€è§£åŽ‹ç¼©ã€åŽ‹ç¼©ã€‚", + "queueName_media_meta": "åª’ä½“å…ƒæ•°æ®æå–", + "queueName_media_metaDes": "用于æå–媒体文件的元数æ®ã€‚", + "queueName_recycle": "Blob 回收", + "queueName_recycleDes": "用于删除过期的文件 Blob。", + "queueName_thumb": "缩略图生æˆ", + "queueName_thumbDes": "用于为文件生æˆç¼©ç•¥å›¾ã€‚", + "queueName_remote_download": "离线下载", + "queueName_remote_downloadDes": "用于处ç†ç¦»çº¿ä¸‹è½½ä»»åŠ¡ã€‚", + "failed": "失败 ({{count}})", + "success": "æˆåŠŸ ({{count}})", + "suspending": "挂起 ({{count}})", + "busyWorker": "处ç†ä¸­ ({{count}})", + "submited": "å·²æäº¤ ({{count}})", + "editQueueSettings": "编辑队列设置 - {{name}}", + "workerNum": "工作线程数", + "workerNumDes": "任务队列最多并行执行的任务数。", + "maxExecution": "最大执行时间", + "maxExecutionDes": "任务最大执行时间(秒),超过此时间任务将被终止。", + "backoffFactor": "退é¿å› å­", + "backoffFactorDes": "任务é‡è¯•时间间隔的增长因å­ã€‚", + "backoffMaxDuration": "æœ€å¤§é€€é¿æ—¶é—´", + "backoffMaxDurationDes": "任务é‡è¯•çš„æœ€å¤§é€€é¿æ—¶é—´ï¼ˆç§’)。", + "maxRetry": "最大é‡è¯•次数", + "maxRetryDes": "任务失败åŽçš„æœ€å¤§é‡è¯•次数。", + "retryDelay": "é‡è¯•延迟", + "retryDelayDes": "任务é‡è¯•çš„åˆå§‹å»¶è¿Ÿæ—¶é—´ï¼ˆç§’)。" + }, + "settings": { + "headlessFooter": "登录会è¯é¡µé¢åº•部", + "headlessFooterDes": "ç”¨æˆ·ç™»å½•ã€æ³¨å†Œã€å›žè°ƒç»“果等页é¢åº•部展示的自定义 HTML 内容。", + "headlessBottom": "登录会è¯é¡µé¢ä¸»ä½“底部", + "headlessBottomDes": "ç”¨æˆ·ç™»å½•ã€æ³¨å†Œã€å›žè°ƒç»“果等页é¢ä¸»ä½“框底部展示的自定义 HTML 内容。", + "customHTML": "自定义 HTML", + "customHTMLDes": "在站点的预设ä½ç½®æ’入展示自定义的 HTML 内容。", + "sidebarBottom": "ä¾§è¾¹æ åº•部", + "sidebarBottomDes": "在侧边æ åº•部展示的自定义 HTML 内容。", + "addNavItem": "添加导航æ¡ç›®", + "customNavItems": "自定义侧边导航æ ", + "customNavItemsDes": "ä½ å¯ä»¥åœ¨å·¦ä¾§å¯¼èˆªæ ä¸­æ·»åŠ è‡ªå®šä¹‰çš„æ¡ç›®ï¼Œç”¨æˆ·ç‚¹å‡»åŽä¼šè·³è½¬åˆ°å¯¹åº”的链接。", + "navItemUrl": "链接", + "iconifyNamePlaceholder": "Iconify 图标标识,比如:fluent:home-24-regular", + "imageUrl": "å›¾åƒ URL", + "iconifyName": "Iconify 图标å", + "oidc": "OpenID Connect (OIDC)", + "oidcDes": "OpenID Connect (OIDC) 是一ç§å¼€æ”¾çš„认è¯å议,用于在ä¸åŒçš„系统之间进行身份验è¯ã€‚在第三方身份平å°ä¸­åˆ›å»ºåº”用åŽï¼Œè¯·å°† <0>{{url}} 添加到 “é‡å®šå‘ URI†中。详情请å‚考 <1>官方文档。", + "clientID": "客户端 ID", + "clientIDDes": "第三方身份平å°åˆ›å»ºçš„应用的客户端 ID。", + "clientSecret": "客户端密钥", + "clientSecretDes": "第三方身份平å°åˆ›å»ºçš„应用的客户端密钥。", + "scope": "Scope", + "scopeDes": "需è¦é¢å¤–请求的 Scopeï¼Œä»¥é€—å· <0>, 分隔。默认情况下,Cloudreve 会请求 <0>openid , <0>email å’Œ <0>profile;无需在此é‡å¤å¡«å†™ã€‚", + "oidcWellknown": "å‘现文档 (Wellknown)", + "oidcWellknownDes": "第三方身份平å°å‘çŽ°æ–‡æ¡£ï¼ŒåŒ…å« OpenID Connect çš„é…置信æ¯ã€‚", + "importFromWellknown": "从 URL 导入", + "importOidc": "导入 OIDC å‘现文档", + "oidcWellknownUrl": "å‘现文档 URL", + "oidcWellknownUrlDes": "第三方身份平å°å‘现文档的 URL 地å€ï¼Œæ¯”如 <0>https://accounts.google.com/.well-known/openid-configuration。", + "resetUrl": "é‡ç½®é“¾æŽ¥", + "exceedToleranceDays": "设置的å°ç¦å®½å®¹å¤©æ•°", + "activateUrl": "激活链接", + "domainNotLicensed": "åŸŸåæœªæŽˆæƒ", + "domainNotLicensedDes": "你设置的站点 URL ä¸­åŒ…å«æœªæŽˆæƒçš„域å,请在 <0>授æƒç®¡ç†é¢æ¿ 中添加此å­åŸŸå,然åŽç‚¹å‡»ä¸‹æ–¹æŒ‰é’®æ›´æ–°æŽˆæƒåŽé‡è¯•。", + "showSettings": "显示设置", + "perPage": "æ¯é¡µ {{num}} æ¡", + "noNodes": "没有å¯ç”¨çš„节点。", + "extractMediaMeta": "åª’ä½“ä¿¡æ¯æå–", + "extractMediaMetaDes": "æå–媒体文件的元数æ®ä»¥ç”¨äºŽå±•示和æœç´¢ã€‚é»˜è®¤æƒ…å†µä¸‹ï¼Œéžæœ¬æœºå­˜å‚¨ç­–ç•¥åªä¼šä½¿ç”¨â€œå­˜å‚¨ç­–ç•¥åŽŸç”Ÿâ€æ–¹å¼æå–。你å¯ä»¥åœ¨å­˜å‚¨ç­–略设置页é¢å¼€å¯â€œæå–器代ç†â€åŠŸèƒ½æ‰©å±•ç¬¬ä¸‰æ–¹å­˜å‚¨ç­–ç•¥çš„ç¼©ç•¥å›¾èƒ½åŠ›ã€‚è¯¦æƒ…è¯·å‚考 <0>官方文档。", + "exif": "EXIF", + "exifDes": "从图片文件中æå– EXIF 元数æ®ä»¥ç”¨äºŽå±•示和æœç´¢ã€‚", + "music": "音ä¹å…ƒæ•°æ®", + "musicDes": "ä»ŽéŸ³ä¹æ–‡ä»¶ä¸­æå–元数æ®ï¼ŒåŒ…括标题ã€è‰ºæœ¯å®¶ã€ä¸“辑等信æ¯ã€‚", + "ffprobe": "FFprobe", + "ffprobeDes": "使用 FFprobe 从视频和音频文件中æå–元数æ®ã€‚", + "maxSizeLocal": "最大文件大å°ï¼ˆæœ¬åœ°å­˜å‚¨ï¼‰", + "maxSizeLocalDes": "当文件存储在本地存储策略时,å…许æå–元数æ®çš„æœ€å¤§æ–‡ä»¶å¤§å°ï¼Œå¡«å†™ä¸º 0 æ—¶ä¸é™åˆ¶ã€‚", + "maxSizeRemote": "最大文件大å°ï¼ˆè¿œç¨‹å­˜å‚¨ï¼‰", + "maxSizeRemoteDes": "当文件存储在第三方存储策略时,å…许æå–元数æ®çš„æœ€å¤§æ–‡ä»¶å¤§å°ï¼Œå¡«å†™ä¸º 0 æ—¶ä¸é™åˆ¶ã€‚", + "exifBruteForce": "å¿…è¦æ—¶ä½¿ç”¨æš´åŠ›æœç´¢", + "exifBruteForceDes": "å¯ç”¨åŽï¼Œå¦‚果在标准头部ä½ç½®æ‰¾ä¸åˆ° EXIF æ•°æ®ï¼Œå°†æ‰«ææ•´ä¸ªæ–‡ä»¶ä»¥æŸ¥æ‰¾ EXIF æ•°æ®ã€‚è¿™å¯èƒ½ä¼šå¢žåŠ å¤„ç†æ—¶é—´ï¼Œä½†å¯ä»¥æ‰¾åˆ°éžæ ‡å‡†ä½ç½®çš„ EXIF æ•°æ®ã€‚", + "musicCover": "歌曲å°é¢", + "musicCoverDes": "æå–音频文件中的专辑å°é¢, æ”¯æŒ ID3 (v1, 2.2, 2.3, 2.4) 元数æ®å®¹å™¨ã€‚这一生æˆå™¨ä¾èµ–于任一其他图åƒç”Ÿæˆå™¨ï¼ˆCloudreve 内置 或 VIPS)。", + "geocoding": "地ç†ç¼–ç ", + "geocodingDes": "æ ¹æ®åª’体 EXIF ä¸­è®°å½•çš„åæ ‡ä¿¡æ¯ï¼Œä½¿ç”¨ Mapbox æœåŠ¡èŽ·å–地å€ä¿¡æ¯ã€‚", + "mapboxAK": "Mapbox 密钥", + "mapboxAKDes": "在 Mapbox 控制å°åˆ›å»ºçš„密钥。", + "geocodingDependencyWarning": "地ç†ç¼–ç ç”Ÿæˆå™¨ä¾èµ–于 EXIF 生æˆå™¨ï¼Œè¯·å¼€å¯ EXIF 生æˆå™¨ã€‚", + "notAppliedToNativeGenerator": "{{prefix}}ä¸é€‚用于存储策略原生生æˆå™¨ã€‚", + "notAppliedToOneDriveNativeGenerator": "{{prefix}}ä¸é€‚用于 OneDrive 或 SharePoint 存储策略原生生æˆå™¨ã€‚", + "fileBlobMargin": "文件 Blob 临时 URL 缓存冗余(秒)", + "fileBlobMarginDes": "当相åŒçš„æ–‡ä»¶ Blob 被多次请求时,如果最åˆçš„ URL 剩余有效期大于冗余时长,相åŒçš„ URL 会被å¤ç”¨ã€‚", + "fileBlobTimeout": "文件 Blob 临时 URL 有效期(秒)", + "fileBlobTimeoutDes": "é™åˆ¶ç”¨æˆ·æ‰“开或下载文件时,所获得的临时链接的有效期,åªé’ˆå¯¹æœ¬æœºå­˜å‚¨ç­–ç•¥ã€WebDAV æˆ–ç» Cloudreve 代ç†çš„æ–‡ä»¶ä¸‹è½½ã€‚", + "wopiSessionTimeout": "WOPI ä¼šè¯æœ‰æ•ˆæœŸï¼ˆç§’)", + "wopiSessionTimeoutDes": "é™åˆ¶ç”¨æˆ·ä½¿ç”¨ WOPI 编辑文件时,å•个会è¯çš„æœ‰æ•ˆæœŸï¼Œè¿‡æœŸåŽç”¨æˆ·éœ€è¦é‡æ–°ä»Ž Cloudreve 打开文件。", + "oauthRefresh": "OAuth 存储策略凭è¯åˆ·æ–°é—´éš”", + "oauthRefreshDes": "设定多久刷新需è¦ä½¿ç”¨ OAuth 的存储策略(OneDrive)的凭è¯ï¼Œå¯ä»¥é¿å…长期未使用存储策略导致的凭è¯è¿‡æœŸ", + "transitParallelNum": "中转最大并行传输", + "transitParallelNumDes": "当å•个æœåŠ¡ç«¯æ–‡ä»¶ä¸­è½¬ä»»åŠ¡åŒ…å«å¤šä¸ªæ–‡ä»¶æ—¶ï¼Œæœ€å¤§å¹¶è¡Œä¸Šä¼ çš„æ•°é‡ã€‚", + "failedChunkRetry": "分片错误最大é‡è¯•", + "failedChunkRetryDes": "分片上传失败åŽé‡è¯•的最大次数,åªé€‚用于æœåŠ¡ç«¯ä¸Šä¼ æˆ–ä¸­è½¬åˆ†ç‰‡ä¸Šä¼ ã€‚", + "cacheChunks": "缓存æµå¼åˆ†ç‰‡æ–‡ä»¶ä»¥ç”¨äºŽé‡è¯•", + "cacheChunksDes": "å¼€å¯åŽï¼Œæµå¼ä¸­è½¬åˆ†ç‰‡ä¸Šä¼ æ—¶ä¼šå°†åˆ†ç‰‡æ•°æ®ç¼“存在系统临时目录,以便用于分片上传失败åŽçš„é‡è¯•ï¼›\n 关闭åŽï¼Œæµå¼ä¸­è½¬åˆ†ç‰‡ä¸Šä¼ ä¸ä¼šé¢å¤–å ç”¨ç¡¬ç›˜ç©ºé—´ï¼Œä½†åˆ†ç‰‡ä¸Šä¼ å¤±è´¥åŽæ•´ä¸ªä¸Šä¼ ä¼šç«‹å³å¤±è´¥ã€‚", + "folderPropsTimeout": "ç›®å½•ç»Ÿè®¡ä¿¡æ¯æœ‰æ•ˆæœŸï¼ˆç§’)", + "folderPropsTimeoutDes": "用户计算目录统计信æ¯ï¼ˆå¤§å°ï¼ŒåŒ…嫿–‡ä»¶æ•°é‡ç­‰ï¼‰æ—¶ï¼Œç»“果缓存的有效期。", + "slaveAPIExpiration": "从机 API ç­¾åæœ‰æ•ˆæœŸï¼ˆç§’)", + "slaveAPIExpirationDes": "主机访问从机 API æ—¶ä½¿ç”¨çš„ç­¾åæœ‰æ•ˆæœŸã€‚", + "uploadSessionTimeout": "ä¸Šä¼ ä¼šè¯æœ‰æ•ˆæœŸ (ç§’)", + "uploadSessionDes": "åœ¨ä¸Šä¼ ä¼šè¯æœ‰æ•ˆæœŸå†…,对于支æŒçš„存储策略,用户å¯ä»¥æ–­ç‚¹ç»­ä¼ æœªå®Œæˆçš„任务。最大å¯è®¾å®šçš„值å—é™äºŽä¸åŒå­˜å‚¨ç­–ç•¥æœåŠ¡å•†çš„è§„åˆ™ã€‚", + "archiveTimeout": "æœåŠ¡ç«¯æ‰“åŒ…ä¸‹è½½ä¼šè¯æœ‰æ•ˆæœŸ (ç§’)", + "advanceOptions": "高级设置", + "emojiOptions": "Emoji 选项", + "addCategorize": "添加分类", + "category": "分类", + "searchQuery": "文件分类查询", + "importWopi": "导入 WOPI 应用设置", + "wopiEndpoint": "WOPI Discovery Endpoint", + "wopiDes": "é€šè¿‡å¯¹æŽ¥æ”¯æŒ WOPI å议的在线文档处ç†ç³»ç»Ÿï¼Œæ‰©å±• Cloudreve 的文档在线预览和编辑能力。请在此填写 WOPI æœåŠ¡å‘现地å€ï¼Œæ¯”如 <0>https://example.com/hosting/discovery。详情请å‚考 <1>官方文档。", + "embeddedWebpageViewer": "嵌入网页å¼åº”用", + "wopiViewer": "WOPI å议应用", + "ext": "扩展å", + "invalidWopiActionMapping": "WOPI Action 映射无效", + "woapiActionMapping": "WOPI Action 映射", + "drawioHost": "DrawIO 实例", + "drawioHostDes": "ä½ å¯ä»¥å¡«å†™è‡ªå»ºå®žä¾‹çš„地å€ã€‚", + "openInNew": "在新窗å£ç›´æŽ¥æ‰“å¼€", + "openInNewDes": "勾选åŽï¼Œä¼šç›´æŽ¥å¼¹å‡ºæ–°æ ‡ç­¾æ‰“开此应用。", + "maxSize": "最大文件大å°", + "maxSizeDes": "此应用支æŒçš„æœ€å¤§æ–‡ä»¶å¤§å°ï¼Œå¡«å†™ 0 表示ä¸é™åˆ¶ï¼Œè¶…å‡ºå¤§å°æ—¶ä»ä¼šå°è¯•打开文件,但会警告用户。", + "srcEncodedVar": "ç»è¿‡ URL ç¼–ç åŽçš„æ–‡ä»¶ Blob 临时访问地å€", + "srcVar": "文件 Blob 临时访问地å€", + "srcBase64Var": "ç»è¿‡ Base64 ç¼–ç åŽçš„æ–‡ä»¶ Blob 临时访问地å€", + "nameEncodedVar": "ç»è¿‡ URL ç¼–ç åŽçš„æ–‡ä»¶å", + "versionEntityVar": "打开的文件版本 Blob ID,为空时表示打开的是最新版本。", + "fileIdVar": "文件 ID", + "userIdVar": "用户 ID,未登录时为空。", + "userDisplayNameVar": "ç»è¿‡ URL ç¼–ç åŽçš„用户昵称。", + "fileViewers": "文件æµè§ˆåº”用", + "addViewer": "添加应用", + "viewerGroupTitle": "应用分组 #{{index}}", + "viewerType": "类型", + "viewerPlatform": "å¹³å°", + "viewerPlatformDes": "选择对应的平å°ï¼Œä½¿åº”用仅在对应平å°ä¸Šå±•示。", + "viewerPlatformPC": "PC端", + "viewerPlatformMobile": "移动端", + "viewerPlatformAll": "全平å°", + "displayName": "åç§°", + "displayNameDes": "展示åç§°ï¼Œæ”¯æŒ i18next 键值。", + "viewerEnabled": "å¯ç”¨", + "newFileAction": "新建文件映射", + "newFileActionDes": "添加映射åŽï¼Œç”¨æˆ·ç‚¹å‡»â€œæ–°å»ºâ€æŒ‰é’®åŽä¼šå‡ºçŽ°æ­¤åº”ç”¨çš„é€‰é¡¹ã€‚", + "addNewFileAction": "添加映射", + "builtinViewerType": "内置应用", + "wopiViewerType": "WOPI", + "customViewerType": "自定义", + "nMapping": "{{num}} 个", + "editViewerTitle": "编辑 {{name}}", + "builtInIconUrlDes": "此内置应用有默认图标,图标地å€ç•™ç©ºæ—¶ä¼šä½¿ç”¨é»˜è®¤å›¾æ ‡ã€‚", + "viewerUrl": "应用 URL", + "viewerUrlDes": "自定义应用的 URL 地å€ï¼Œæ”¯æŒä½¿ç”¨ <0>魔法å˜é‡ã€‚", + "addIcon": "添加图标", + "exts": "扩展å列表", + "icon": "图标", + "iconUrl": "图标地å€", + "iconColor": "图标颜色", + "iconColorDark": "图标颜色(黑暗模å¼ï¼‰", + "fileIcons": "文件图标", + "builtinIcon": "内置图标", + "mimeMapping": "MIME 类型映射", + "mimeMappingDes": "JSON æ ¼å¼çš„ MIME 类型映射表,键为文件扩展å,值为 MIME 类型。Cloudreve ä¼šæ ¹æ®æ–‡ä»¶æ‰©å±•å和此设置判断文件 MIME 类型。", + "mapProvider": "地图æä¾›å•†", + "mapProviderDes": "展示媒体ä½ç½®ä¿¡æ¯æ—¶ä½¿ç”¨çš„地图æä¾›å•†ã€‚", + "mapGoogle": "Google Maps (Leaflet)", + "mapOpenStreetMap": "OpenStreetMap (Leaflet)", + "mapboxMap": "OpenStreetMap (Mapbox)", + "mapboxAccessToken": "Mapbox 密钥", + "mapboxAccessTokenDes": "在 <0>Mapbox æŽ§åˆ¶å° åˆ›å»ºçš„å…¬å…±å¯†é’¥ã€‚", + "tileType": "默认地图类型", + "tileTypeDes": "Google Maps 默认地图类型。", + "tileTypeTerrain": "地形", + "tileTypeSatellite": "嫿˜Ÿ", + "tileTypeGeneral": "常规", + "maxPageSize": "最大分页大å°", + "maxPageSizeDes": "é™åˆ¶ç”¨æˆ·å¯è°ƒæ•´çš„æ¯é¡µæœ€å¤§æ–‡ä»¶æ•°é‡ã€‚", + "maxRecursiveSearch": "最大递归æœç´¢æ•°é‡", + "maxRecursiveSearchDes": "用户æœç´¢æ–‡ä»¶æ—¶ï¼Œå¦‚果已æœç´¢çš„æ–‡ä»¶æ•°é‡è¶…出此é™åˆ¶ï¼Œæœç´¢ä¼šåœæ­¢å¹¶è­¦å‘Šç”¨æˆ·ã€‚", + "maxBatchSize": "æœ€å¤§æ‰¹é‡æ“作数é‡", + "maxBatchSizeDes": "ç”¨æˆ·å¯æ‰¹é‡æ“作的最大文件数é‡ï¼Œåªä¼šç»Ÿè®¡é¡¶å±‚æ•°é‡ï¼Œå­ç›®å½•下的文件数é‡ä¸ä¼šè®¡å…¥ã€‚", + "defaultPagination": "文件列表分页方å¼", + "cursorPagination": "游标分页", + "cursorPaginationDes": "用户滚动到底端åŽä¼šè‡ªåŠ¨åŠ è½½æ›´å¤šæ–‡ä»¶ï¼Œå¯¹äºŽå¤§é‡æ–‡ä»¶åˆ—表性能较好,但无法看到总页数。", + "offsetPagination": "传统分页", + "offsetPaginationDes": "页é¢åº•部会展示分页导航,用户å¯ä»¥çœ‹åˆ°æ€»é¡µæ•°å¹¶è·³è½¬åˆ°æŸä¸€é¡µï¼Œåœ¨å¯¹äºŽå¤§é‡æ–‡ä»¶åˆ—表下性能较差。", + "defaultPaginationDes": "无论上é¢è®¾ç½®å¦‚何,用户在æœç´¢æ—¶ä¼šå¼ºåˆ¶ä½¿ç”¨æ¸¸æ ‡åˆ†é¡µã€‚", + "publicResourceMaxAge": "陿€èµ„æºç¼“存有效期 (ç§’)", + "publicResourceMaxAgeDes": "用于告知æµè§ˆå™¨æˆ– CDN ç¼“å­˜é™æ€èµ„æºçš„æœ‰æ•ˆæœŸï¼Œå•ä½ä¸ºç§’。影å“范围包括文件ã€ç¼©ç•¥å›¾å’Œç”¨æˆ·å¤´åƒã€‚", + "cronDes": "{{des}},此处需è¦å¡«å†™æ­£ç¡®çš„ <0>Cron 表达å¼ã€‚é‡å¯ Cloudreve åŽç”Ÿæ•ˆã€‚", + "entityCollectInterval": "文件 Blob 回收间隔", + "entityCollectIntervalDes": "设定多久扫æå¹¶åˆ é™¤è¿‡æœŸçš„æ–‡ä»¶ Blob", + "trashBinInterval": "回收站扫æé—´éš”", + "trashBinIntervalDes": "设定多久扫æå¹¶åˆ é™¤å›žæ”¶ç«™ä¸­çš„过期文件", + "logtoName": "登录方å¼åç§°", + "logtoNameDes": "用于展示给用户的登录方å¼å称,默认为 “SSOâ€ï¼Œæ”¯æŒ i18next 键值。", + "logtoDirectSSO": "直达第三方登录", + "logtoDirectSSODes": "如果你想è¦è·³è¿‡ Logto 登录å±å¹•,直接跳转到对接的第三方登录或 SSO,请在此填写第三方登录连接器的标识,详情请å‚考 <0>Logto 文档。", + "logtoEndpoint": "Logto 端点", + "logtoEndpointDes": "应用管ç†é¢æ¿èŽ·å–到的 Logto 端点地å€ï¼Œå¯ä»¥ä¸ºè‡ªå·±éƒ¨ç½²çš„实例。", + "logtoKey": "应用密钥", + "logtoKeyDes": "应用管ç†é¡µé¢åˆ›å»ºçš„应用密钥。", + "logtoAppIDDes": "你所创建的应用 ID。", + "logto": "Logto", + "logtoDes": "借由 <0>Logto, ä½ å¯ä»¥å®žçŽ°æ›´å¤šç¬¬ä¸‰æ–¹å¹³å°çš„互è”登录,比如 Appleã€GitHubã€Microsoft Entra IDã€Googleã€çŸ­ä¿¡ç­‰ã€‚请在 Logto 管ç†é¢æ¿åˆ›å»ºä¸€ä¸ª “传统网页应用â€ï¼Œå¹¶å°† <1>{{url}} 加入到 “é‡å®šå‘ URIs†中。", + "thirdPartySignIn": "第三方登录", + "logo": "LOGO", + "logoDes": "LOGO 图åƒçš„地å€ï¼Œç”¨äºŽåœ¨å·¦ä¸Šè§’展示;请分别æä¾›é»‘暗模å¼å’Œæ—¥é—´æ¨¡å¼ä¸‹ä¸åŒçš„ LOGO。", + "dark": "黑暗模å¼", + "light": "日间模å¼", + "tosUrl": "ä½¿ç”¨æ¡æ¬¾é“¾æŽ¥", + "tosUrlDes": "用于在用户登录或注册页脚展示,留空ä¸å±•示。", + "privacyUrl": "éšç§æ”¿ç­–链接", + "privacyUrlDes": "用于在用户登录或注册页脚展示,留空ä¸å±•示。", + "addSecondary": "添加备选站点 URL", + "secondarySiteURL": "备选", + "secondaryDes": "你还å¯ä»¥æ·»åŠ å…¶ä»–å¤‡é€‰ç«™ç‚¹ URL,Cloudreve 会根æ®ç”¨æˆ·å®žé™…访问的 URL 自动选择是å¦ä½¿ç”¨", + "primarySiteURL": "主è¦", + "primarySiteURLDes": "主è¦ç«™ç‚¹ URL 用于与外部æœåŠ¡é€šä¿¡å’ŒæŽ¥å—回调(比如:存储æä¾›å•†ï¼‰ï¼Œè¯·ä½¿ç”¨èƒ½è¢«å…¬ç½‘访问的 URL。", + "revert": "撤销更改", + "saved": "设置已更改", + "save": "ä¿å­˜", + "basicInformation": "基本信æ¯", + "mainTitle": "站点åç§°", + "mainTitleDes": "站点的å称。", + "siteDescription": "站点æè¿°", + "siteDescriptionDes": "站点æè¿°ä¿¡æ¯ï¼Œå¯èƒ½ä¼šåœ¨åˆ†äº«é¡µé¢æ‘˜è¦å†…展示。", + "siteURL": "站点 URL", + "customFooterHTML": "页脚代ç ", + "customFooterHTMLDes": "在页é¢åº•部æ’入的自定义 HTML 代ç ã€‚", + "announcement": "站点公告", + "announcementDes": "展示给已登录用户的公告,留空ä¸å±•ç¤ºã€‚å½“æ­¤é¡¹å†…å®¹æ›´æ”¹æ—¶ï¼Œæ‰€æœ‰ç”¨æˆ·ä¼šé‡æ–°çœ‹åˆ°å…¬å‘Šã€‚", + "supportHTML": "æ”¯æŒ HTML 代ç ", + "branding": "图标", + "smallIcon": "å°å›¾æ ‡", + "smallIconDes": "å°å›¾æ ‡åœ°å€ï¼Œico 或 svg æ ¼å¼ã€‚此图标还会被用于在æµè§ˆå™¨æ ‡ç­¾é¡µã€ä¹¦ç­¾å’Œæ¡Œé¢å¿«æ·æ–¹å¼ç­‰ä½ç½®å±•示。", + "mediumIcon": "中图标", + "mediumIconDes": "192x192 的中图标地å€ï¼Œpng æ ¼å¼ã€‚", + "largeIcon": "大图标", + "largeIconDes": "512x512 的大图标地å€ï¼Œpng æ ¼å¼ã€‚此图标还会被用于在 iOS 客户端切æ¢ç«™ç‚¹æ—¶å±•示。", + "displayMode": "展示模å¼", + "displayModeDes": "PWA 应用添加åŽçš„展示模å¼ã€‚", + "themeColor": "主题色", + "themeColorDes": "CSS è‰²å€¼ï¼Œå½±å“ PWA å¯åŠ¨ç”»é¢ä¸ŠçŠ¶æ€æ ã€å†…å®¹é¡µä¸­çŠ¶æ€æ ã€åœ°å€æ çš„颜色。", + "backgroundColor": "背景色", + "backgroundColorDes": "CSS 色值", + "hint": "æç¤º", + "webauthnNoHttps": "Web Authn 需è¦ä½ çš„站点å¯ç”¨ HTTPS,并确认 傿•°è®¾ç½® - ç«™ç‚¹ä¿¡æ¯ - 站点URL 也使用了 HTTPS åŽæ‰èƒ½å¼€å¯ã€‚", + "accountManagement": "注册与登录", + "allowNewRegistrations": "å…许新用户注册", + "allowNewRegistrationsDes": "关闭åŽï¼Œæ— æ³•å†é€šè¿‡å‰å°æ³¨å†Œæ–°çš„用户。", + "emailActivation": "邮件激活", + "emailActivationDes": "å¼€å¯åŽï¼Œæ–°ç”¨æˆ·æ³¨å†Œéœ€è¦ç‚¹å‡»é‚®ä»¶ä¸­çš„æ¿€æ´»é“¾æŽ¥æ‰èƒ½å®Œæˆã€‚请确认 <0>邮件å‘信设置 æ˜¯å¦æ­£ç¡®ï¼Œå¦åˆ™æ¿€æ´»é‚®ä»¶æ— æ³•é€è¾¾ã€‚", + "captchaForSignup": "注册验è¯ç ", + "captchaForSignupDes": "是å¦å¯ç”¨æ³¨å†Œè¡¨å•验è¯ç ã€‚", + "captchaForLogin": "登录验è¯ç ", + "captchaForLoginDes": "是å¦å¯ç”¨ç™»å½•表å•验è¯ç ã€‚", + "captchaForReset": "找回密ç éªŒè¯ç ", + "captchaForResetDes": "是å¦å¯ç”¨æ‰¾å›žå¯†ç è¡¨å•验è¯ç ã€‚", + "captchaForAbuseReport": "滥用举报验è¯ç ", + "captchaForAbuseReportDes": "是å¦å¯ç”¨æ»¥ç”¨ä¸¾æŠ¥è¡¨å•验è¯ç ã€‚", + "webauthnDes": "是å¦å…许用户使用绑定的硬件认è¯è®¾å¤‡ç™»å½•ï¼Œæ¯”å¦‚ï¼šäººè„¸ã€æŒ‡çº¹æˆ– USB 密钥;站点必须å¯ç”¨ HTTPS æ‰èƒ½ä½¿ç”¨ã€‚", + "webauthn": "使用通行密钥登录", + "defaultSymbolics": "åˆå§‹åˆ†äº«å¿«æ·æ–¹å¼", + "defaultSymbolicsDes": "æ–°ç”¨æˆ·æ ¹ç›®å½•ä¸‹é»˜è®¤å­˜åœ¨çš„åˆ†äº«é“¾æŽ¥å¿«æ·æ–¹å¼ã€‚请通过数字 ID æœç´¢åˆ†äº«é“¾æŽ¥ï¼Œä½ å¯åœ¨ <0>分享列表 最左侧看到数字 ID。", + "searchShare": "æœç´¢åˆ†äº« ID...", + "defaultGroup": "默认用户组", + "defaultGroupDes": "用户注册åŽçš„åˆå§‹ç”¨æˆ·ç»„。", + "testMailSent": "测试邮件已å‘é€", + "testSMTPSettings": "å‘件测试", + "testSMTPTooltip": "Cloudreve ä¼šä½¿ç”¨ä½ å½“å‰ SMTP 设置å‘逿µ‹è¯•é‚®ä»¶ï¼Œæµ‹è¯•å‰æ— éœ€ä¿å­˜è®¾ç½®ã€‚", + "recipient": "收件人地å€", + "send": "å‘é€", + "smtp": "å‘ä¿¡", + "senderName": "å‘件人å", + "senderNameDes": "邮件中展示的å‘件人姓å。", + "senderAddress": "å‘件人邮箱", + "senderAddressDes": "å‘件邮箱的地å€ã€‚", + "smtpServer": "SMTP æœåС噍", + "smtpServerDes": "å‘ä»¶æœåŠ¡å™¨åœ°å€ï¼Œä¸å«ç«¯å£å·ã€‚", + "smtpPort": "SMTP 端å£", + "smtpPortDes": "å‘ä»¶æœåŠ¡å™¨ç«¯å£å·ã€‚", + "smtpUsername": "SMTP 用户å", + "smtpUsernameDes": "å‘信邮箱用户å,一般与邮箱地å€ç›¸åŒã€‚", + "smtpPassword": "SMTP 密ç ", + "smtpPasswordDes": "å‘信邮箱密ç ã€‚", + "replyToAddress": "回信邮箱", + "replyToAddressDes": "用户回å¤ç³»ç»Ÿå‘é€çš„邮件时,用于接收回信的邮箱。", + "enforceSSL": "强制使用 SSL 连接", + "enforceSSLDes": "是å¦å¼ºåˆ¶ä½¿ç”¨ SSL 加密连接。如果无法å‘é€é‚®ä»¶ï¼Œå¯å…³é—­æ­¤é¡¹ï¼ŒCloudreve 会å°è¯•使用 STARTTLS 并决定是å¦ä½¿ç”¨åŠ å¯†è¿žæŽ¥ã€‚", + "smtpTTL": "SMTP 连接有效期 (ç§’)", + "smtpTTLDes": "有效期内建立的 SMTP 连接会被新邮件å‘é€è¯·æ±‚å¤ç”¨ã€‚", + "emailTemplates": "邮件模æ¿", + "activateNewUser": "新用户激活", + "resetPassword": "é‡ç½®å¯†ç ", + "sendTestEmail": "å‘逿µ‹è¯•邮件", + "transportation": "传输", + "workerNum": "Worker æ•°é‡", + "workerNumDes": "主机节点任务队列最多并行执行的任务数,ä¿å­˜åŽéœ€è¦é‡å¯ Cloudreve 生效", + "tempFolder": "临时目录", + "tempFolderDes": "用于存放解压缩ã€åŽ‹ç¼©ç­‰ä»»åŠ¡äº§ç”Ÿçš„ä¸´æ—¶æ–‡ä»¶çš„ç›®å½•è·¯å¾„", + "textEditMaxSize": "文档在线编辑最大大å°", + "textEditMaxSizeDes": "文档文件å¯åœ¨çº¿ç¼–辑的最大大å°ï¼Œè¶…出此大å°çš„æ–‡ä»¶æ— æ³•在线编辑。此项设置适用于纯文本文件ã€ä»£ç æ–‡ä»¶ã€Office 文档(WOPI)等 Web 在线编辑器。", + "resetConnection": "上传校验失败时强制é‡ç½®è¿žæŽ¥", + "resetConnectionDes": "å¼€å¯åŽï¼Œå¦‚果本次策略ã€å¤´åƒç­‰æ•°æ®ä¸Šä¼ æ ¡éªŒå¤±è´¥ï¼ŒæœåŠ¡å™¨ä¼šå¼ºåˆ¶é‡ç½®è¿žæŽ¥", + "batchDownload": "打包下载", + "previewURL": "预览链接", + "cannotDeleteDefaultTheme": "ä¸èƒ½åˆ é™¤é»˜è®¤é…色", + "themeConfig": "色彩é…ç½®", + "actions": "æ“作", + "wrongFormat": "æ ¼å¼ä¸æ­£ç¡®", + "avatar": "头åƒ", + "gravatarServer": "Gravatar æœåС噍", + "gravatarServerDes": "Gravatar æœåŠ¡å™¨åœ°å€ï¼Œå¯é€‰æ‹©ä½¿ç”¨å›½å†…镜åƒã€‚", + "avatarFilePath": "头åƒå­˜å‚¨è·¯å¾„", + "avatarFilePathDes": "用户上传自定义头åƒçš„存储路径,相对于 Cloudreve æ•°æ®ç›®å½•。", + "avatarSize": "å¤´åƒæ–‡ä»¶å¤§å°é™åˆ¶", + "avatarSizeDes": "用户å¯ä¸Šä¼ å¤´åƒæ–‡ä»¶çš„æœ€å¤§å¤§å°ã€‚", + "avatarImageSize": "图åƒå°ºå¯¸ (px)", + "avatarImageSizeDes": "用户所上传头åƒä¼šè¢«è°ƒæ•´åˆ°ç»™å®šçš„尺寸,å•ä½ä¸ºåƒç´ ã€‚", + "filePreview": "文件预览", + "thumbnails": "缩略图", + "thumbnailDoc": "有关é…置缩略图的更多信æ¯ï¼Œè¯·å‚阅 <0>官方文档。", + "thumbnailDocLink": "https://docs.cloudreve.org/usage/thumbnails", + "thumbnailBasic": "基本设置", + "generators": "缩略图生æˆå™¨", + "thumbMaxSize": "最大原始文件尺寸", + "thumbMaxSizeDes": "å¯ç”Ÿæˆç¼©ç•¥å›¾çš„æœ€å¤§åŽŸå§‹æ–‡ä»¶çš„å¤§å°ï¼Œè¶…出此大å°çš„æ–‡ä»¶ä¸ä¼šç”Ÿæˆç¼©ç•¥å›¾ã€‚", + "generatorProxyWarning": "é»˜è®¤æƒ…å†µä¸‹ï¼Œéžæœ¬æœºå­˜å‚¨ç­–ç•¥åªä¼šä½¿ç”¨â€œå­˜å‚¨ç­–略原生â€ç”Ÿæˆå™¨ã€‚ä½ å¯ä»¥åœ¨å­˜å‚¨ç­–略设置页é¢å¼€å¯â€œç”Ÿæˆå™¨ä»£ç†â€åŠŸèƒ½æ‰©å±•ç¬¬ä¸‰æ–¹å­˜å‚¨ç­–ç•¥çš„ç¼©ç•¥å›¾èƒ½åŠ›ã€‚è¯¦æƒ…è¯·å‚考 <0>官方文档。", + "policyBuiltin": "存储策略原生", + "policyBuiltinDes": "使用存储æä¾›æ–¹åŽŸç”Ÿçš„å›¾åƒå¤„ç†æŽ¥å£ã€‚对于本机和 S3 策略,这一生æˆå™¨ä¸å¯ç”¨ï¼Œå°†ä¼šè‡ªåŠ¨é¡ºæ²¿å…¶ä»–ç”Ÿæˆå™¨ã€‚对于其他存储策略,请å‰å¾€å­˜å‚¨ç­–略设置页é¢è®¾ç½®å…许的扩展å。", + "cloudreveBuiltin": "Cloudreve 内置", + "cloudreveBuiltinDes": "使用 Cloudreve 内置的图åƒå¤„ç†èƒ½åŠ›ï¼Œä»…æ”¯æŒ PNGã€JPEGã€GIF æ ¼å¼çš„图片。", + "libreOffice": "LibreOffice", + "libreOfficeDes": "使用 LibreOffice ç”Ÿæˆ Office 文档的缩略图。这一生æˆå™¨ä¾èµ–于任一其他图åƒç”Ÿæˆå™¨ï¼ˆCloudreve 内置 或 VIPS)。", + "libraw": "LibRaw / DCRaw", + "librawDes": "使用 LibRaw 附带的 DCRaw 模拟例程,或者原始 DCRaw 程åºç”Ÿæˆ RAW 图åƒçš„缩略图。", + "vips": "VIPS", + "vipsDes": "使用 libvips 处ç†ç¼©ç•¥å›¾å›¾åƒï¼Œæ”¯æŒæ›´å¤šå›¾åƒæ ¼å¼ï¼Œèµ„æºæ¶ˆè€—更低。", + "thumbDependencyWarning": "LibreOffice 或歌曲å°é¢ç”Ÿæˆå™¨ä¾èµ–于 Cloudreve 内置 或 VIPS 生æˆå™¨ï¼Œè¯·å¼€å¯å…¶ä¸­ä»»ä¸€ç”Ÿæˆå™¨ã€‚", + "ffmpeg": "FFmpeg", + "ffmpegDes": "使用 FFmpeg 生æˆè§†é¢‘缩略图。", + "executable": "坿‰§è¡Œæ–‡ä»¶", + "executableDes": "第三方生æˆå™¨å¯æ‰§è¡Œæ–‡ä»¶çš„路径或命令。", + "executableTest": "测试", + "executableTestSuccess": "生æˆå™¨æ­£å¸¸ï¼Œç‰ˆæœ¬ï¼š{{version}}", + "generatorExts": "å¯ç”¨æ‰©å±•å", + "generatorExtsDes": "此生æˆå™¨å¯ç”¨çš„æ–‡ä»¶æ‰©å±•å列表,多个请使用åŠè§’é€—å· , 隔开。", + "ffmpegSeek": "缩略图截å–ä½ç½®", + "ffmpegSeekDes": "定义缩略图截å–的时间,推è选择较å°å€¼ä»¥åŠ é€Ÿç”Ÿæˆè¿‡ç¨‹ã€‚如果超出视频实际长度,会导致缩略图截å–失败。", + "ffmpegExtraArgs": "é¢å¤–è¾“å…¥å‚æ•°", + "ffmpegExtraArgsDes": "调用 FFmpeg æ—¶é¢å¤–è¾“å…¥çš„å‚æ•°ã€‚", + "generatorProxy": "生æˆå™¨ä»£ç†", + "enableThumbProxy": "使用生æˆå™¨ä»£ç†", + "proxyPolicyList": "å¯åЍ代ç†çš„存储策略", + "proxyPolicyListDes": "å¯å¤šé€‰ã€‚选中åŽï¼Œå­˜å‚¨ç­–ç•¥ä¸æ”¯æŒåŽŸç”Ÿç”Ÿæˆç¼©ç•¥å›¾çš„类型会由 Cloudreve 代ç†ç”Ÿæˆã€‚", + "thumbWidth": "最大宽度", + "thumbHeight": "最大高度", + "thumbSuffix": "Blob 文件åŽç¼€", + "thumbSuffixDes": "生æˆçš„缩略图 Blob 相对于原始 Blob 增加的åŽç¼€ï¼Œ", + "thumbFormat": "缩略图格å¼", + "thumbFormatDes": "优先使用的缩略图格å¼ï¼Œå¦‚果生æˆå™¨ä¸æ”¯æŒï¼Œä¼šè‡ªåЍé™çº§ä¸º jpg æ ¼å¼ã€‚", + "thumbQuality": "图åƒè´¨é‡", + "thumbQualityDes": "压缩质é‡ç™¾åˆ†æ¯”,åªé’ˆå¯¹ jpg å’Œ webp æ ¼å¼æœ‰æ•ˆã€‚", + "thumbGC": "生æˆå®ŒæˆåŽç«‹å³å›žæ”¶å†…å­˜", + "captcha": "验è¯ç ", + "captchaType": "验è¯ç ç±»åž‹", + "captchaTypeDes": "选择验è¯ç ç±»åž‹å’ŒéªŒè¯ç æœåŠ¡æä¾›å•†ã€‚", + "plainCaptcha": "图形", + "reCaptchaV2": "reCAPTCHA V2", + "turnstile": "Cloudflare Turnstile", + "turnstileSiteKey": "站点密钥", + "turnstileSiteKSecret": "密钥", + "cap": "Cap", + "capInstanceURL": "实例 URL", + "capInstanceURLDes": "自部署 Cap æœåŠ¡å™¨çš„ URL 地å€ã€‚详细信æ¯è¯·å‚考 <0>è‡ªéƒ¨ç½²æ¨¡å¼æ–‡æ¡£ã€‚", + "capSiteKey": "站点密钥", + "capSiteKeyDes": "从 Cap æœåŠ¡å™¨æŽ§åˆ¶é¢æ¿èŽ·å–的站点密钥。", + "capSecretKey": "ç§å¯†å¯†é’¥", + "capSecretKeyDes": "从 Cap æœåŠ¡å™¨æŽ§åˆ¶é¢æ¿èŽ·å–çš„ç§å¯†å¯†é’¥ã€‚", + "capAssetServer": "陿€èµ„æºæœåŠ¡æº", + "capAssetServerDes": "选择 Cap 验è¯ç é™æ€èµ„æºçš„加载æºã€‚使用自部署æœåŠ¡å™¨éœ€è¦åœ¨æœåŠ¡å™¨ç«¯è®¾ç½®çŽ¯å¢ƒå˜é‡ï¼Œè¯·å‚考 <0>å¼€å¯é™æ€èµ„æºæœåŠ¡ã€‚", + "capAssetServerJsdelivr": "jsDelivr CDN", + "capAssetServerUnpkg": "unpkg CDN", + "capAssetServerInstance": "自部署æœåС噍", + "captchaProvider": "验è¯ç ç±»åž‹", + "captchaWidth": "宽度", + "captchaHeight": "高度", + "captchaLength": "长度", + "captchaLengthDes": "验è¯ç ä¸­å­—符的长度。", + "captchaMode": "模å¼", + "captchaModeNumber": "æ•°å­—", + "captchaModeLetter": "å­—æ¯", + "captchaModeMath": "ç®—æ•°", + "captchaModeNumberLetter": "æ•°å­—+å­—æ¯", + "captchaElement": "验è¯ç çš„å½¢å¼", + "complexOfNoiseText": "加强干扰文字", + "complexOfNoiseDot": "加强干扰点", + "showHollowLine": "使用空心线", + "showNoiseDot": "使用噪点", + "showNoiseText": "使用干扰文字", + "showSlimeLine": "使用波浪线", + "showSineLine": "使用正弦线", + "siteKey": "Site KEY", + "siteKeyDes": "<0>应用管ç†é¡µé¢ 获å–到的 网站密钥。", + "siteSecret": "Secret", + "siteSecretDes": "<0>应用管ç†é¡µé¢ 获å–到的 秘钥。", + "secretID": "SecretId", + "secretIDDes": "<0>è®¿é—®å¯†é’¥é¡µé¢ èŽ·å–到的 SecretId", + "secretKey": "SecretKey", + "secretKeyDes": "<0>è®¿é—®å¯†é’¥é¡µé¢ èŽ·å–到的 SecretKey", + "tCaptchaAppID": "APPID", + "tCaptchaAppIDDes": "<0>图形验è¯é¡µé¢ 获å–到的 APPID", + "tCaptchaSecretKey": "App Secret Key", + "tCaptchaSecretKeyDes": "<0>图形验è¯é¡µé¢ 获å–到的 App Secret Key", + "staticResourceCache": "陿€å…¬å…±èµ„æºç¼“å­˜", + "staticResourceCacheDes": "公共å¯è®¿é—®çš„陿€èµ„æºï¼ˆå¦‚ï¼šæœ¬æœºç­–ç•¥ç›´é“¾ã€æ–‡ä»¶ä¸‹è½½é“¾æŽ¥ï¼‰çš„缓存有效期", + "creditSystem": "积分系统", + "creditAndVAS": "积分与增值æœåŠ¡", + "enableCredit": "å¯ç”¨ç§¯åˆ†ç³»ç»Ÿ", + "enableCreditDes": "å¯ç”¨ç§¯åˆ†ç³»ç»Ÿï¼Œå…许用户为分享链接设置价格。", + "creditPrice": "积分价格", + "creditPriceDes": "使用货å¸å……值积分的价格(以最å°è´§å¸å•ä½è®¡ï¼‰ï¼Œå¡«å†™ 0 è¡¨ç¤ºç¦æ­¢å……值积分。", + "shareScoreRate": "分享者佣金比例", + "shareScoreRateDes": "分享链接被购买时,分享者获得的积分百分比(1-100)。", + "cronNotifyUser": "通知超é¢ç”¨æˆ·æ‰«æé—´éš”", + "cronNotifyUserDes": "扫æå¹¶å‘é€é‚®ä»¶æé†’è¶…é¢ç”¨æˆ·", + "cronBanUser": "用户å°ç¦æ‰«æé—´éš”", + "cronBanUserDes": "扫æå¹¶å°ç¦è¶…出存储且超出缓冲期的用户", + "anonymousPurchase": "匿åè´­ä¹°", + "anonymousPurchaseDes": "å…许未登录用户直接购买分享链接。", + "shopNavEnabled": "显示商店导航", + "shopNavEnabledDes": "在侧边æ å¯¼èˆªä¸­æ˜¾ç¤ºâ€œå•†åº—â€æ¡ç›®ã€‚", + "paymentSettings": "支付设置", + "currencyCode": "è´§å¸ä»£ç ", + "currencyCodeDes": "三字æ¯è´§å¸ä»£ç ï¼ˆå¦‚ USDã€CNYã€EUR)。", + "currencySymbol": "è´§å¸ç¬¦å·", + "currencySymbolDes": "显示的货å¸ç¬¦å·ï¼ˆå¦‚ $ã€Â¥ã€â‚¬ï¼‰ã€‚", + "currencyUnit": "è´§å¸å•ä½", + "currencyUnitDes": "最å°è´§å¸å•ä½ï¼ˆå¦‚美元/分为100)。", + "paymentProviders": "支付æä¾›å•†", + "providerName": "æä¾›å•†å称,用于展示给用户。", + "providerType": "æä¾›å•†ç±»åž‹", + "providerKey": "密钥", + "selectCurrency": "选择常用货å¸", + "addPaymentProvider": "添加支付æä¾›å•†", + "stripeProvider": "Stripe", + "weixinProvider": "微信支付", + "alipayProvider": "支付å®", + "customProvider": "自定义支付渠é“", + "customProviderDes": "通过实现 Cloudreve å…¼å®¹ä»˜æ¬¾æŽ¥å£æ¥å¯¹æŽ¥å…¶ä»–第三方支付平å°ï¼Œè¯¦æƒ…请å‚考 <0>官方文档。", + "providerKeyDes": "输入 Stripe çš„ API 密钥。", + "storageProductSettings": "存储产å“", + "storageProductsDes": "é…置用户å¯ä»¥è´­ä¹°ä»¥æ‰©å±•存储空间的产å“。", + "addStorageProduct": "添加产å“", + "editStorageProduct": "编辑产å“", + "storageSize": "存储大å°", + "storageSizeBytes": "此产å“包å«çš„存储大å°ã€‚", + "duration": "æ—¶é•¿", + "durationSeconds": "时长(秒,例如:2592000 表示 30 天)。", + "price": "ä»·æ ¼", + "priceInUnits": "价格(以最å°è´§å¸å•ä½è®¡ï¼‰", + "priceInUnitsDes": "价格将显示为:", + "chipLabel": "标签(å¯é€‰ï¼‰", + "chipLabelHelp": "显示在产å“åç§°æ—边的短文本标签。", + "usePoints": "å…许使用积分", + "points": "积分", + "pointsHelp": "è´­ä¹°æ­¤äº§å“æ‰€éœ€çš„积分数é‡ã€‚", + "pointsUnit": "积分", + "groupProductSettings": "用户组产å“", + "groupProductsDes": "é…置用户å¯ä»¥è´­ä¹°ä»¥åŠ å…¥ç‰¹å®šç”¨æˆ·ç»„çš„äº§å“。", + "addGroupProduct": "添加用户组产å“", + "editGroupProduct": "编辑用户组产å“", + "groupId": "用户组 ID", + "groupIdHelp": "购买此产å“åŽå‡çº§åˆ°çš„用户组。", + "description": "æè¿°", + "descriptionHelp": "输入特性或优势,æ¯è¡Œä¸€é¡¹", + "receiptEmailTemplate": "æ”¯ä»˜æ”¶æ®æ¨¡æ¿", + "receiptEmailTemplateDes": "当支付被确认时å‘é€ç»™ç”¨æˆ·çš„邮件模æ¿ã€‚", + "activationEmailTemplate": "账户激活模æ¿", + "activationEmailTemplateDes": "当用户激活账户时å‘é€ç»™ç”¨æˆ·çš„邮件模æ¿ã€‚", + "quotaExceededEmailTemplate": "存储é…é¢è¶…出模æ¿", + "quotaExceededEmailTemplateDes": "当用户超出存储é…颿—¶å‘é€ç»™ç”¨æˆ·çš„邮件模æ¿ã€‚", + "resetPasswordEmailTemplate": "密ç é‡ç½®æ¨¡æ¿", + "resetPasswordEmailTemplateDes": "当用户请求é‡ç½®å¯†ç æ—¶å‘é€ç»™ç”¨æˆ·çš„邮件模æ¿ã€‚", + "preferredLanguage": "首选语言", + "setAsPreferredLanguage": "设为首选语言", + "setAsPreferredLanguageDes": "如果无法获å–用户的语言å好,将使用首选语言的邮件模æ¿ã€‚", + "alreadyAsPreferredLanguageDes": "当å‰è¯­è¨€å·²è®¾ä¸ºé¦–选语言。如果无法获å–用户的语言å好,将使用此邮件模æ¿ã€‚", + "addLanguage": "添加语言", + "removeLanguage": "移除语言", + "removeLanguageBtn": "移除语言", + "cannotRemovePreferredLanguageDes": "无法移除首选语言。请设置其他语言为首选语言åŽé‡è¯•。", + "languageCodeDes": "è¯·é€‰æ‹©è¦æ·»åŠ çš„è¯­è¨€ã€‚", + "emailSubject": "邮件主题", + "emailSubjectDes": "邮件的主题。你å¯ä»¥ä½¿ç”¨ <0>魔法å˜é‡ æ¥å®šåˆ¶é‚®ä»¶ä¸»é¢˜ã€‚", + "emailBody": "邮件内容", + "emailBodyDes": "邮件的内容。你å¯ä»¥ä½¿ç”¨ <0>魔法å˜é‡ æ¥å®šåˆ¶é‚®ä»¶å†…容。", + "orderTitle": "è®¢å•æ ‡é¢˜", + "themeOptions": "主题选项", + "themeOptionsDes": "为你的站点é…置自定义主题选项。这些主题将å¯ä¾›ç”¨æˆ·åœ¨å…¶å好设置中选择。", + "primaryColor": "主色调", + "secondaryColor": "次色调", + "primaryColorDark": "主色调(暗色模å¼ï¼‰", + "secondaryColorDark": "次色调(暗色模å¼ï¼‰", + "addThemeOption": "添加主题选项", + "editThemeOption": "编辑主题选项", + "invalidThemeConfig": "无效的主题é…置。请检查你的 JSON 语法。", + "themeConfiguration": "主题é…ç½®", + "themePreview": "主题预览", + "lightTheme": "亮色主题", + "darkTheme": "暗色主题", + "previewTitle": "预览标题", + "previewTextField": "输入字段", + "previewPrimary": "主色调", + "invalidThemePreview": "无效的主题é…置,无法预览", + "duplicateThemeColor": "已存在使用此主色调的主题。请选择ä¸åŒçš„颜色。", + "themeDes": "完整的å¯é…置项请å‚考 <0>Material-UI Default theme viewer。", + "defaultTheme": "默认", + "auditLog": "事件", + "auditLogDes": "é…置哪些事件应该被记录。æŸäº›äº‹ä»¶å¯èƒ½ä¼šè¢«ç³»ç»Ÿç”¨äºŽæä¾›é¢å¤–功能,例如文件活动和登录活动。", + "systemEvents": "系统事件", + "systemEventsDes": "与系统æ“作和状æ€ç›¸å…³çš„事件。", + "userEvents": "用户事件", + "userEventsDes": "与用户账户ã€è®¤è¯å’Œé…置文件更改相关的事件。", + "fileEvents": "文件事件", + "fileEventsDes": "与文件æ“作相关的事件,如上传ã€ä¸‹è½½å’Œä¿®æ”¹ã€‚", + "shareEvents": "分享事件", + "shareEventsDes": "与文件分享和链接访问相关的事件。", + "versionEvents": "版本事件", + "versionEventsDes": "与文件版本管ç†ç›¸å…³çš„事件。", + "mediaEvents": "媒体事件", + "mediaEventsDes": "与媒体文件处ç†ç›¸å…³çš„事件,如缩略图生æˆã€‚", + "filesystemEvents": "文件系统事件", + "filesystemEventsDes": "与文件系统æ“作相关的事件,如挂载和归档处ç†ã€‚", + "webdavEvents": "WebDAV 事件", + "webdavEventsDes": "与 WebDAV 账户管ç†å’Œè®¿é—®ç›¸å…³çš„事件。", + "paymentEvents": "支付事件", + "paymentEventsDes": "与支付交易和处ç†ç›¸å…³çš„事件。", + "emailEvents": "Email 事件", + "emailEventsDes": "与邮件å‘é€å’Œé€šçŸ¥ç›¸å…³çš„事件。", + "toggleAll": "å¯ç”¨/ç¦ç”¨æ‰€æœ‰äº‹ä»¶", + "toggleAllDes": "å¯ç”¨æˆ–ç¦ç”¨æ­¤ç±»åˆ«ä¸­çš„æ‰€æœ‰äº‹ä»¶ã€‚", + "event": { + "file_imported": "外部文件导入", + "server_start": "æœåС噍å¯åЍ", + "user_signup": "用户注册", + "email_sent": "邮件å‘é€", + "user_activated": "用户激活", + "user_login_failed": "登录失败", + "user_login": "用户登录", + "user_token_refresh": "令牌刷新", + "file_create": "文件创建", + "file_rename": "文件é‡å‘½å", + "set_file_permission": "æƒé™æ›´æ”¹", + "entity_uploaded": "文件上传或更新", + "entity_downloaded": "文件下载", + "copy_from": "å¤åˆ¶æ¥æº", + "copy_to": "å¤åˆ¶åˆ°", + "move_to": "移动到", + "delete_file": "文件删除", + "move_to_trash": "移动到回收站", + "share": "分享创建", + "share_link_viewed": "分享链接查看", + "set_current_version": "设置当å‰ç‰ˆæœ¬", + "delete_version": "删除版本", + "thumb_generated": "缩略图生æˆ", + "live_photo_uploaded": "上传 Live Photo", + "update_metadata": "å…ƒæ•°æ®æ›´æ–°", + "edit_share": "分享编辑", + "delete_share": "分享删除", + "mount": "挂载", + "relocate": "转移存储策略", + "create_archive": "创建归档", + "extract_archive": "解压归档", + "webdav_login_failed": "WebDAV 登录失败", + "webdav_account_create": "WebDAV 账户创建", + "webdav_account_update": "WebDAV 账户更新", + "webdav_account_delete": "WebDAV 账户删除", + "payment_created": "支付创建", + "points_change": "积分更改", + "payment_paid": "支付完æˆ", + "payment_fulfilled": "履行订å•", + "payment_fulfill_failed": "履行订å•失败", + "storage_added": "存储扩容", + "group_changed": "用户组更改", + "user_exceed_quota_notified": "超出é…é¢é€šçŸ¥", + "user_changed": "ç”¨æˆ·çŠ¶æ€æ›´æ”¹", + "get_direct_link": "获å–直链", + "link_account": "链接外部账户", + "unlink_account": "å–æ¶ˆé“¾æŽ¥å¤–部账户", + "change_nick": "更改昵称", + "change_avatar": "更改头åƒ", + "membership_unsubscribe": "å–æ¶ˆè®¢é˜…", + "change_password": "更改密ç ", + "enable_2fa": "å¯ç”¨ 2FA", + "disable_2fa": "ç¦ç”¨ 2FA", + "add_passkey": "添加通行密钥", + "remove_passkey": "移除通行密钥", + "redeem_gift_code": "å…‘æ¢ç¤¼å“ç ", + "update_view": "更改视图设置", + "delete_direct_link": "删除直链", + "report_abuse": "举报滥用" + }, + "server": "æœåŠ¡å™¨è®¾ç½®", + "tempPath": "临时路径", + "tempPathDes": "存储临时文件的目录,相对于 Cloudreve æ•°æ®ç›®å½•。修改å‰è¯·ç¡®ä¿æ²¡æœ‰æ­£åœ¨è¿›è¡Œçš„队列任务。", + "siteID": "站点 ID", + "siteIDDes": "用于标识站点的唯一 ID,一般无需修改。", + "siteSecretKey": "主密钥", + "siteSecretKeyDes": "用于加密用户令牌ã€ç­¾å的主密钥。轮转åŽï¼Œæ‰€æœ‰ç”¨æˆ·ä»¤ç‰Œã€ç­¾å都将失效。ä¿å­˜åŽé‡å¯ Cloudreve 生效。", + "rotateSecretKey": "轮转主密钥", + "hashidSalt": "HashID ç›å€¼", + "hashidSaltDes": "ç”¨äºŽç”Ÿæˆ HashID çš„ç›å€¼ï¼Œè¯·è°¨æ…Žæ›´æ”¹ï¼Œæ›´æ”¹åŽä¼šå¯¼è‡´çŽ°æœ‰çš„ç›´é“¾ã€åˆ†äº«é“¾æŽ¥ç­‰å…¨éƒ¨å¤±æ•ˆã€‚", + "accessTokenTTL": "访问令牌 TTL", + "accessTokenTTLDes": "访问令牌的有效期,å•ä½ä¸ºç§’。", + "refreshTokenTTL": "刷新令牌 TTL", + "refreshTokenTTLDes": "刷新令牌的有效期,å•ä½ä¸ºç§’。影å“用户登录状æ€çš„ä¿æŒæ—¶é—´ã€‚", + "cronGarbageCollect": "垃圾回收扫æé—´éš”", + "cronGarbageCollectDes": "设定多久扫æå¹¶å›žæ”¶ä¸´æ—¶æ–‡ä»¶å’Œ KV 存储中的过期数æ®", + "startWithProtocol": "必须以 http:// 或 https:// 开头", + "tlsWarning": "当å‰ç«™ç‚¹ä½¿ç”¨ https,这里填写 http çš„ URL å¯èƒ½ä¼šå¯¼è‡´å¼‚常。", + "blobUrlCache": "Blob URL 缓存", + "clearBlobUrlCache": "清除 Blob URL 缓存", + "clearBlobUrlCacheDes": "为了增加缓存命中率,Cloudreve 会缓存并å¤ç”¨ Blob URL。当 CDN 地å€ç­‰è®¾ç½®å‘ç”Ÿå˜æ›´æ—¶ï¼Œè¯·æ¸…除缓存。", + "cacheCleared": "缓存已清除" + }, + "giftCodes": { + "giftCodesSettings": "礼å“ç ", + "generateGiftCodes": "生æˆç¤¼å“ç ", + "giftCodeQuantity": "æ•°é‡", + "giftCodeQuantityHelp": "è¦ç”Ÿæˆçš„礼å“ç æ•°é‡ã€‚", + "giftCodeProductType": "产å“类型", + "giftCodeTypePoints": "积分", + "giftCodeTypeStorage": "存储空间", + "giftCodeTypeGroup": "用户组", + "giftCodePointsAmount": "积分数é‡", + "giftCodePointsAmountHelp": "å…‘æ¢ç è¢«ä½¿ç”¨æ—¶å°†èŽ·å¾—çš„ç§¯åˆ†æ•°é‡ã€‚", + "giftCodeProduct": "产å“", + "selectStorageProduct": "选择存储产å“", + "selectGroupProduct": "选择用户组产å“", + "giftCodeType": "类型", + "giftCodeAmount": "æ•°é‡", + "giftCode": "礼å“ç ", + "giftCodeStatus": "状æ€", + "giftCodeUsedBy": "使用者", + "giftCodeUsed": "已使用", + "giftCodeUnused": "å¯ç”¨", + "giftCodeDeleted": "礼å“ç å·²æˆåŠŸåˆ é™¤", + "giftCodesGenerated": "礼å“ç å·²æˆåŠŸç”Ÿæˆ", + "noGiftCodes": "暂无礼å“ç ", + "generatedCodesTitle": "已生æˆçš„礼å“ç ", + "generatedCodesDescription": "å¤åˆ¶è¿™äº›ç¤¼å“ç ä»¥åˆ†äº«ç»™ç”¨æˆ·ã€‚æ¯ä¸ªç¤¼å“ç åªèƒ½ä½¿ç”¨ä¸€æ¬¡ã€‚", + "copyAndClose": "å¤åˆ¶å¹¶å…³é—­", + "duratonTimes": "æ—¶é•¿å€æ•°", + "duratonTimesDes": "æ¯ä¸ªç¤¼å“ç åŒ…å«äº†å¤šå°‘份对应商å“。", + "unknownProduct": "未知产å“" + }, + "policy": { + "acceleratedDomainUpload": "使用传输加速域å上传", + "acceleratedDomainUploadDes": "å¼€å¯åŽï¼Œä¸Šä¼ æ–‡ä»¶æ—¶ä¼šä½¿ç”¨ä¸ƒç‰›çš„ <0>传输加速域å。", + "compare": "对比存储策略", + "deletePolicyConfirmation": "确定è¦åˆ é™¤å­˜å‚¨ç­–ç•¥ {{name}} å—?", + "streamSaver": "ç”±æµè§ˆå™¨å¤„ç†ä¸‹è½½", + "streamSaverDes": "å¼€å¯åŽï¼Œç”¨æˆ·ä¸‹è½½æ–‡ä»¶æ—¶ä¼šå¼ºåˆ¶ç”±æµè§ˆå™¨å¤„ç†ã€‚因为 OneDrive 存储策略的é™åˆ¶ï¼Œç”¨æˆ·ç›´æŽ¥ä¸‹è½½æ–‡ä»¶æ—¶å¾—åˆ°çš„æ–‡ä»¶åæ— æ³•与 Cloudreve 内文件å一致,由æµè§ˆå™¨å¤„ç†ä¸‹è½½å¯ä»¥è§£å†³æ­¤é—®é¢˜ã€‚", + "oauthCallbackFailed": "授æƒå¤±è´¥", + "httpsRequired": "Entra ID 应用需è¦ä½¿ç”¨ HTTPS é‡å®šå‘ URL,但是当å‰ç«™ç‚¹ä½¿ç”¨çš„æ˜¯ HTTP,åŽç»­ç™»å½•完æˆåŽå¯èƒ½ä¼šå¯¼è‡´é‡å®šå‘失败,届时请手动将æµè§ˆå™¨åœ°å€æ ä¸­çš„ HTTPS 替æ¢ä¸º HTTP。", + "authorizeMicrosoft": "使用 Microsoft 登录", + "redirectUrl": "é‡å®šå‘ URL", + "redirectUrlDes": "当å‰å±•示的是最新的符åˆè¦æ±‚çš„é‡å®šå‘ URL,请确认应用设置中的é‡å®šå‘ URL 一致。", + "authorizeOneDrive": "确认 Entra ID 应用设置", + "authorizeOneDriveDes": "请确认以下 Entra ID åº”ç”¨ä¿¡æ¯æ˜¯å¦ä»ç„¶æœ‰æ•ˆï¼Œå¦‚有需è¦è¯·åšå‡ºæ›´æ”¹ã€‚", + "authorizeNow": "ç«‹å³æŽˆæƒ", + "authorizeAgain": "釿–°æŽˆæƒ", + "notGranted": "无授æƒè´¦å·ï¼Œå­˜å‚¨ç­–略无法使用。", + "granted": "已授æƒè´¦å·ï¼Œå‡­è¯åˆ·æ–°äºŽ <0>。", + "grantedNotRefresh": "已授æƒè´¦å·ï¼Œå‡­è¯è‡ªä¸Šæ¬¡å¯åЍåŽå°šæœªåˆ·æ–°ã€‚", + "batchDeleteSize": "最大批é‡åˆ é™¤æ•°é‡", + "batchDeleteSizeDes": "é™åˆ¶å•次 API 请求的最大删除数é‡ï¼Œæ­¤è®¾ç½®ä¸ä¼šå½±å“ç”¨æˆ·åˆ é™¤æ‰¹é‡æ–‡ä»¶ã€‚ä¸å¡«å†™ä¼šä½¿ç”¨é»˜è®¤å€¼ <0>1000,这是官方 S3 API 的最大å…许值。", + "bucketPolicy": "æ¡¶ç­–ç•¥", + "cdnOrCustomDomain": "CDN 或自定义æºç«™åŸŸå", + "bucketDomain": "存储空间域å", + "bucketDomainDes": "填写你为存储空间绑定的 CDN åŠ é€ŸåŸŸåæˆ–者自定义æºç«™åŸŸå。", + "storageNodeInternal": "存储节点(内网 Endpoint)", + "chunkSizeDesOssObs": "å…许范围:100 KB ~ 5 GB,", + "chunkSizeDesQiniuCos": "å…许范围:1 MB ~ 1 GB,", + "chunkSizeDesS3": "å…许范围:5 MB ~ 5 GB,", + "thisIsACustomDomain": "这是一个自定义域å", + "thisIsACustomDomainDes": "如果你为 Bucket 绑定了自定义域å,且需è¦é€šè¿‡è‡ªå®šä¹‰åŸŸåè¿›è¡Œä¸Šä¼ ç­‰ç®¡ç†æ“作,请勾选此选项。勾选åŽï¼ŒCloudreve ä¸ä¼šåœ¨è¯·æ±‚域å中å°è¯•补全 Bucket å称。", + "addedManually": "我已自行设置", + "origin": "æ¥æº", + "allowMethods": "å…许 Methods", + "exposeHeaders": "暴露 Headers", + "allowHeaders": "å…许 Headers", + "maxAge": "缓存时间", + "accessCredential": "访问凭è¯", + "downloadTrafficDiagram": "下载æµé‡è·¯å¾„演示图", + "downloadRelay": "下载中转", + "downloadRelayDes": "å¼€å¯åŽï¼Œç”¨æˆ·ä¸‹è½½æ–‡ä»¶æ—¶ä¼šé€šè¿‡ Cloudreve 代ç†ã€‚", + "download": "下载", + "downloadCdn": "下载 CDN", + "useDownloadCdn": "使用 CDN 加速下载", + "skipSign": "ä¸ä¸º CDN ç­¾åæ–‡ä»¶ URL", + "skipSignDes": "如果你在 COS 域å设置中开å¯äº† “回æºé‰´æƒâ€ï¼Œè¯·å‹¾é€‰æ­¤é¡¹ã€‚", + "cdnHost": "CDN 地å€", + "downloadCdnDes": "用户访问文件时的 URL 中的主机åã€å议等部分会被替æ¢ä¸ºä½ æŒ‡å®šçš„ CDN 域å。", + "mediaExtractorProxy": "ä»£ç†æå–媒体信æ¯", + "mediaExtractorProxyDes": "å¼€å¯åŽï¼Œå¯¹äºŽå­˜å‚¨ç«¯æå–噍䏿”¯æŒçš„æ–‡ä»¶ï¼ŒCloudreve 会å°è¯•æå–文件媒体信æ¯ã€‚请在 <0>åª’ä½“å¤„ç† ä¸­é…ç½® Cloudreve åª’ä½“ä¿¡æ¯æå–器。", + "mediaExtractorNative": "原生æå–器", + "mediaExtractorOss": "智能媒体管ç†ï¼ˆIMM)", + "mediaExtractorQiniu": "智能多媒体æœåŠ¡", + "mediaExtractorCos": "腾讯云数æ®ä¸‡è±¡", + "mediaExtractorObs": "å›¾ç‰‡å¤„ç†æœåŠ¡", + "mediaExtractorUpyun": "å›¾ç‰‡å¤„ç†æœåŠ¡", + "nativeMediaMetaExts": "使用<0>{{name}}的文件扩展å", + "nativeMediaMetaExtsGeneralDes": "åŠè§’é€—å· , 隔开,留空表示ä¸ä½¿ç”¨<0>{{name}}。", + "nativeMediaMetaExtsRemote": "å¯¹äºŽä»Žæœºå­˜å‚¨ï¼Œé»˜è®¤æƒ…å†µä¸‹æ”¯æŒ EXIF 和音ä¹å…ƒæ•°æ®ï¼Œä½ å¯ä»¥é€šè¿‡é…置覆写在从机端å¯ç”¨å…¶ä»–生æˆå™¨ã€‚", + "nativeMediaMetaExtOss": "智能媒体管ç†ï¼ˆIMM)æœåŠ¡æ”¯æŒå¤„ç†éŸ³é¢‘ã€è§†é¢‘和图片。处ç†å›¾ç‰‡æ— éœ€æ‰‹åЍé…置,但如果你需è¦å¤„ç†éŸ³é¢‘æˆ–è§†é¢‘ï¼Œéœ€è¦æ‰‹åŠ¨å¼€é€š IMM 并绑定到 Bucket, 请å‚考 <0>文档 绑定。绑定完æˆåŽè¯·åœ¨ä¸Šé¢åŠ ä¸Šä½ æƒ³è¦å¤„ç†çš„音视频的扩展å。", + "nativeMediaMetaExtQiniu": "智能多媒体æœåŠ¡æ”¯æŒå¤„ç†å¸¸è§éŸ³é¢‘ã€è§†é¢‘和图片,无需é¢å¤–é…置,在上方填写你想è¦å¤„ç†çš„媒体的扩展åå³å¯ã€‚", + "nativeMediaMetaExtCos": "腾讯云数æ®ä¸‡è±¡æœåŠ¡æ”¯æŒå¤„ç†éŸ³é¢‘ã€è§†é¢‘和图片。处ç†å›¾ç‰‡æ— éœ€æ‰‹åЍé…置,但如果你需è¦å¤„ç†éŸ³é¢‘或视频, 请先å‰å¾€ <0>æ•°æ®ä¸‡è±¡ 开通并绑定存储桶,然åŽå‰å¾€ 存储桶设置 - åª’ä½“å¤„ç† ä¸­å¼€é€šç¾Žå›¾å¤„ç†æœåŠ¡ã€‚ç»‘å®šå®ŒæˆåŽè¯·åœ¨ä¸Šé¢åŠ ä¸Šä½ æƒ³è¦å¤„ç†çš„音视频的扩展å。", + "nativeMediaMetaExtObs": "å›¾ç‰‡å¤„ç†æœåŠ¡æ”¯æŒ<0>æå–图片 EXIF。无需手动é…置,在上é¢åŠ ä¸Šä½ æƒ³è¦å¤„ç†çš„图片的扩展åå³å¯ã€‚", + "nativeMediaMetaExtUpyun": "å›¾ç‰‡å¤„ç†æœåŠ¡æ”¯æŒ<0>æå–图片 EXIF。无需手动é…置,在上é¢åŠ ä¸Šä½ æƒ³è¦å¤„ç†çš„图片的扩展åå³å¯ã€‚", + "thumbProxy": "代ç†ç”Ÿæˆç¼©ç•¥å›¾", + "thumbProxyDes": "å¼€å¯åŽï¼Œå¯¹äºŽä¸ç¬¦åˆåŽŸç”Ÿç¼©ç•¥å›¾æ¡ä»¶çš„æ–‡ä»¶ï¼ŒCloudreve 会å°è¯•为其生æˆç¼©ç•¥å›¾æ–‡ä»¶ï¼Œå¹¶ä¸Šä¼ åˆ°å­˜å‚¨ç«¯ã€‚请在 <0>åª’ä½“å¤„ç† ä¸­é…ç½® Cloudreve 缩略图生æˆå™¨ã€‚", + "nativeThumbnailMaxSize": "使用原生缩略图的最大文件大å°", + "nativeThumbnailMaxSizeDes": "填写 0 表示ä¸é™åˆ¶ï¼Œè¶…出此大å°çš„æ–‡ä»¶å°†ä¸ä¼šä½¿ç”¨åŽŸç”Ÿç¼©ç•¥å›¾ã€‚", + "nativeThumbNailsSupportAllExts": "对所有文件扩展å使用", + "nativeThumbNails": "使用原生缩略图的扩展å", + "nativeThumbNailsGeneralDes": "åŠè§’é€—å· , 隔开,留空表示ä¸ä½¿ç”¨åŽŸç”Ÿç¼©ç•¥å›¾ã€‚å¯¹äºŽåˆ—è¡¨ä¸­åˆ—å‡ºçš„æ–‡ä»¶æ‰©å±•å,Cloudreve 会使用存储端的原生缩略图。", + "nativeThumbNailsGeneralRemote": "å¯¹äºŽä»Žæœºå­˜å‚¨ï¼Œé»˜è®¤æƒ…å†µä¸‹åªæ”¯æŒç®€å•图åƒå’Œæ­Œæ›²å°é¢ç¼©ç•¥å›¾ï¼Œä½ å¯ä»¥é€šè¿‡é…置覆写在从机端å¯ç”¨å…¶ä»–生æˆå™¨ã€‚", + "nativeThumbNailsGeneralOss": "对于阿里云 OSS 存储,<0>å›¾ç‰‡å¤„ç†æœåŠ¡ä¼šè¢«ç”¨æ¥ç”Ÿæˆç¼©ç•¥å›¾ã€‚", + "nativeThumbNailsGeneralQiniu": "对于七牛云存储,<0>图片基本处ç†(imageView2)æœåŠ¡ä¼šè¢«ç”¨æ¥ç”Ÿæˆç¼©ç•¥å›¾ã€‚", + "nativeThumbNailsGeneralCos": "对于腾讯云 COS 存储,<0>腾讯云数æ®ä¸‡è±¡æœåŠ¡ä¼šè¢«ç”¨æ¥ç”Ÿæˆç¼©ç•¥å›¾ã€‚", + "nativeThumbNailsGeneralObs": "对于åŽä¸ºäº‘ OBS 存储,<0>å›¾ç‰‡å¤„ç†æœåŠ¡ä¼šè¢«ç”¨æ¥ç”Ÿæˆç¼©ç•¥å›¾ã€‚", + "nativeThumbNailsGeneralUpyun": "å¯¹äºŽåˆæ‹äº‘存储,<0>å›¾ç‰‡å¤„ç†æœåŠ¡ä¼šè¢«ç”¨æ¥ç”Ÿæˆç¼©ç•¥å›¾ã€‚", + "preallocate": "预分é…硬盘空间", + "preallocateDes": "å¼€å¯åŽï¼Œç”¨æˆ·ä¸Šä¼ æ–‡ä»¶æ—¶ä¼šé¢„先分é…ç¡¬ç›˜ç©ºé—´ï¼ŒåŒæ—¶ä¹Ÿå¯æ”¯æŒå¹¶è¡Œåˆ†ç‰‡ä¸Šä¼ ã€‚åªåœ¨ Linux 或 Darwin 下有效。", + "chunkConcurrency": "并行上传分片数", + "chunkConcurrencyDes": "设定 Web ç«¯ç›´ä¼ æ—¶ï¼ŒåŒæ—¶è¿›è¡Œçš„分片上传数é‡ã€‚", + "sourceWebEdit": "Web 在线编辑", + "uploadRelay": "中转上传", + "uploadRelayDes": "å¼€å¯åŽï¼Œç”¨æˆ·çš„上传请求会通过 Cloudreve 中转到存储端,因为无法进行分片上传,请注æ„调整 Web æœåŠ¡å™¨ç«¯æœ€å¤§ä¸Šä¼ å¤§å°é™åˆ¶ã€‚", + "customProxy": "自定义代ç†", + "storageNode": "存储æä¾›å•†", + "sourceWeb": "Web / 官方客户端", + "sourceDav": "WebDAV", + "uploadTrafficDiagram": "上传æµé‡è·¯å¾„演示图", + "node": "存储节点", + "nodeDes": "请选择一个从机节点用于存储文件,你å¯ä»¥åˆ° <0>存储节点列表 中创建或管ç†ä»ŽæœºèŠ‚ç‚¹ã€‚", + "noBindedGroupWarning": "当å‰å­˜å‚¨ç­–略没有被分é…给任何用户组,请å‰å¾€ <0>用户组列表 为当å‰å­˜å‚¨ç­–略绑定用户组。", + "nameRuleImmutable": "修改此设置ä¸ä¼šå½±å“存储策略下已有文件。Blob 路径在创建åŽå›ºå®šï¼Œå³ä½¿å…¶ä¸­é­”法å˜é‡å‘生改å˜ï¼Œè·¯å¾„也ä¸ä¼šæ›´æ–°ã€‚", + "uniqueVarRequired": "请在目录路径或文件å中至少包å«ä¸€ä¸ªå”¯ä¸€æ€§å˜é‡ï¼š{uuid}ã€{randomkey8}ã€{randomkey16}。", + "storageAndUpload": "存储与上传", + "blobFolderNaming": "Blob 存储目录", + "blobFolderNamingDes": "文件 Blob 的存放目录,å¯ä»¥ä½¿ç”¨ <0>魔法å˜é‡ 。", + "blobName": "Blob åç§°", + "blobNameDes": "文件 Blob çš„å称,å¯ä»¥ä½¿ç”¨ <0>魔法å˜é‡ï¼Œéœ€è¦ç¡®ä¿ä¸ºç»å¯¹å”¯ä¸€ï¼Œå³ä½¿åœ¨çŸ­æ—¶é—´å†…多次上传åŒä¸€æ–‡ä»¶ã€‚", + "basicInfo": "基本信æ¯", + "editX": "编辑 {{name}}", + "noGroupBinded": "没有绑定任何用户组", + "create": "创建", + "addXStoragePolicy": "添加 {{type}} 存储策略", + "loadSummary": "加载统计数æ®", + "policySummary": "{{count}} 个文件 Blob ({{size}})", + "sharp": "#", + "name": "åç§°", + "type": "类型", + "childFiles": "下属文件数", + "totalSize": "æ•°æ®é‡", + "actions": "æ“作", + "authSuccess": "æŽˆæƒæˆåŠŸ", + "policyDeleted": "存储策略已删除", + "newStoragePolicy": "添加存储策略", + "all": "全部", + "local": "本机存储", + "remote": "从机存储", + "qiniu": "七牛", + "upyun": "åˆæ‹äº‘", + "oss": "阿里云 OSS", + "cos": "腾讯云 COS", + "onedrive": "OneDrive", + "s3": "S3 兼容", + "ks3": "金山云 KS3", + "obs": "åŽä¸ºäº‘ OBS", + "load_balance": "è´Ÿè½½å‡è¡¡", + "childPolicy": "å­å­˜å‚¨ç­–ç•¥", + "childPolicyDes": "é€‰æ‹©ä½ è¦æ·»åŠ åˆ°è´Ÿè½½å‡è¡¡ä¸­çš„å­å­˜å‚¨ç­–略。", + "weight": "æƒé‡", + "addTargetPolicy": "添加å­å­˜å‚¨ç­–ç•¥", + "selectPolicies": "选择策略", + "selectPoliciesDes": "é€‰æ‹©è¦æ·»åŠ åˆ°è´Ÿè½½å‡è¡¡çš„存储策略。", + "loadBalanceDes": "使用负载å‡è¡¡å­˜å‚¨ç­–ç•¥æ—¶ï¼Œæ–°ä¸Šä¼ çš„æ–‡ä»¶ä¼šæ ¹æ®æƒé‡éšæœºåˆ†é…到ä¸åŒçš„å­å­˜å‚¨ç­–略中。", + "xChildPolicies": "{{count}} 个å­å­˜å‚¨ç­–ç•¥", + "refresh": "刷新", + "delete": "删除", + "edit": "编辑", + "selectAStorageProvider": "选择存储方å¼", + "maxSizeOfSingleFile": "文件大å°é™åˆ¶", + "maxSizeOfSingleFileDes": "å•个文件的最大大å°ï¼Œè¾“å…¥é™åˆ¶ä¸º 0 时表示ä¸é™åˆ¶å•文件大å°ã€‚", + "enterFileExt": "留空表示ä¸é™åˆ¶æ–‡ä»¶æ‰©å±•å,多个请以åŠè§’é€—å· , 隔开。", + "extList": "文件扩展åé™åˆ¶", + "noLimit": "æ— é™åˆ¶", + "whitelist": "å…许", + "blacklist": "æ‹’ç»", + "fileNameRegex": "æ–‡ä»¶åæ­£åˆ™è§„则", + "fileNameRegexDes": "ç”¨äºŽåŒ¹é…æ–‡ä»¶å的正则表达å¼ï¼Œç•™ç©ºè¡¨ç¤ºæ— é™åˆ¶ã€‚", + "chunkSizeDes": "请指定分片上传时的分片大å°ï¼Œå¡«å†™ä¸º 0 表示ä¸ä½¿ç”¨åˆ†ç‰‡ä¸Šä¼ ï¼Œä½†æœ€å¤§ä¸Šä¼ å¤§å°å¯èƒ½å—é™äºŽ Web æœåŠ¡å™¨ã€‚", + "chunkSizeDesSuffix": "{{prefix}}通过分片上传,用户上传的文件将会被切分æˆåˆ†ç‰‡é€ä¸ªä¸Šä¼ åˆ°å­˜å‚¨ç«¯ï¼Œå½“上传中断åŽï¼Œç”¨æˆ·å¯ä»¥é€‰æ‹©ä»Žä¸Šæ¬¡ä¸Šä¼ çš„分片åŽç»§ç»­å¼€å§‹ä¸Šä¼ ã€‚", + "chunkSize": "上传分片大å°", + "policyName": "存储策略的展示å,也会用于å‘用户展示。", + "magicVar": { + "fileNameMagicVar": "文件å魔法å˜é‡", + "pathMagicVar": "路径魔法å˜é‡", + "variable": "魔法å˜é‡", + "description": "æè¿°", + "example": "示例", + "16digitsRandomString": "16 ä½éšæœºå­—ç¬¦", + "8digitsRandomString": "8 ä½éšæœºå­—ç¬¦", + "secondTimestamp": "秒级时间戳", + "nanoTimestamp": "纳秒级时间戳", + "uid": "用户 ID", + "originalFileName": "原始文件å", + "originFileNameNoext": "无扩展å的原始文件å", + "extension": "文件扩展å", + "uuidV4": "UUID V4", + "date": "日期", + "dateAndTime": "日期时间", + "randomNumber": "èŒƒå›´å†…çš„éšæœºæ•°", + "year": "年份", + "month": "月份", + "day": "æ—¥", + "hour": "å°æ—¶", + "minute": "分钟", + "second": "ç§’", + "path": "用户上传文件时的åˆå§‹è·¯å¾„" + }, + "storageBucket": "存储空间", + "wanSiteURLDes": "在使用此存储策略å‰ï¼Œè¯·ç¡®ä¿ä½ åœ¨ 傿•°è®¾ç½® - ç«™ç‚¹ä¿¡æ¯ - 站点URL 中填写的 地å€ä¸Žå®žé™…相符,并且 <0>能够被外网正常访问。", + "enterQiniuBucket": "å‰å¾€ <0>ä¸ƒç‰›æŽ§åˆ¶é¢æ¿ 创建对象存储资æºã€‚在填写你在七牛创建存储空间时指定的“存储空间åç§°â€ã€‚", + "qiniuBucketName": "存储空间åç§°", + "cosObsBucketName": "存储桶åç§°", + "bucketType": "Bucket 读写æƒé™", + "bucketTypeDes": "请选择你创建的存储空间的读写æƒé™ç±»åž‹ã€‚", + "aclType": "访问控制类型", + "accessTypePulic": "å…¬æœ‰è¯»ç§æœ‰å†™", + "accessTypePrivate": "ç§æœ‰è¯»å†™", + "accessType": "访问æƒé™", + "privateBucket": "ç§æœ‰", + "privateDes": "Cloudreve 会对文件 URL ç­¾å。", + "publicBucket": "公共读", + "publicStorage": "公开", + "publicDes": "䏿ލè选择,Cloudreve 会直接返回文件的直链,无法有效控制文件的访问æƒé™ã€‚", + "bucketCDNDes": "填写你为存储空间绑定的 CDN 加速域å。", + "bucketCDNDomain": "CDN 加速域å", + "qiniuCredentialDes": "åœ¨ä¸ƒç‰›æŽ§åˆ¶é¢æ¿è¿›å…¥ 个人中心 - 密钥管ç†ï¼Œå¡«å†™èŽ·å¾—åˆ°çš„ AKã€SK。", + "ak": "AK", + "sk": "SK", + "cannotEnableForPrivateBucket": "ç§æœ‰ç©ºé—´å¼€å¯å¤–链功能åŽï¼Œè¿˜éœ€è¦åœ¨ç”¨æˆ·ç»„里设置开å¯â€œä½¿ç”¨é‡å®šå‘的外链â€ï¼Œå¦åˆ™æ— æ³•正常生æˆå¤–链", + "chunkSizeLabelQiniu": "请指定分片上传时的分片大å°ï¼ŒèŒƒå›´ 1 MB - 1 GB。", + "corsSettingStep": "跨域策略", + "corsPolicyAdded": "跨域策略已添加。", + "createOSSBucketDes": "ä½ å¯å‰å¾€ <0>OSS ç®¡ç†æŽ§åˆ¶å° åˆ›å»º Bucketã€‚åªæ”¯æŒ <1>标准存储 å’Œ <2>低频访问 类型的 Bucket。", + "bucketName": "Bucket åç§°", + "publicReadBucket": "公共读", + "ossEndpointDes": "转到所创建 Bucket 的概览页é¢ï¼Œå¡«å†™ <0>è®¿é—®åŸŸå æ ç›®ä¸‹ <1>外网访问 一行中间的 <2>EndPoint(地域节点)。", + "ossEndpointDesInternalHint": "如需é…置内网或自定义域å Endpoint,å¯åœ¨åˆ›å»ºå­˜å‚¨ç­–ç•¥åŽè®¾ç½®ã€‚", + "obsEndpointCnameHint": "如需é…置自定义域å Endpoint,å¯åœ¨åˆ›å»ºå­˜å‚¨ç­–ç•¥åŽè®¾ç½®ã€‚", + "endpoint": "EndPoint", + "ossLANEndpointDes": "留空为ä¸ä½¿ç”¨ã€‚如果你的 Cloudreve 部署在阿里云计算æœåŠ¡ä¸­ï¼Œå¹¶ä¸”ä¸Ž OSS 处在åŒä¸€å¯ç”¨åŒºä¸‹ï¼Œä½ å¯ä»¥é¢å¤–指定使用内网 EndPoint ä»¥èŠ‚çœæµé‡å¼€æ”¯, Cloudreve 会在æ¡ä»¶æ»¡è¶³æ—¶åˆ‡æ¢åˆ°å†…网 EndPoint å‘é€è¯·æ±‚。", + "intranetEndPoint": "内网 EndPoint", + "ossCDNDes": "是å¦è¦ä½¿ç”¨é…套的 阿里云CDN 加速 OSS 访问?", + "createOSSCDNDes": "å‰å¾€ <0>阿里云 CDN ç®¡ç†æŽ§åˆ¶å° åˆ›å»º CDN 加速域å,并设定æºç«™ä¸ºåˆšåˆ›å»ºçš„ OSS Bucket。在下方填写 CDN 加速域å,并选择是å¦ä½¿ç”¨ HTTPS:", + "ossAKDes": "在阿里云 <0>安全信æ¯ç®¡ç† 页é¢èŽ·å– AccessKey。你也å¯ä»¥åœ¨ <1>RAM 访问控制 中创建拥有 <2>AliyunOSSFullAccess æƒé™çš„ AccessKey。", + "shouldNotContainSpace": "ä¸èƒ½å«æœ‰ç©ºæ ¼", + "nameThePolicyFirst": "为此存储策略命å:", + "chunkSizeLabelOSS": "请指定分片上传时的分片大å°ï¼ŒèŒƒå›´ 100 KB ~ 5 GB。", + "ossCORSDes": "æ­¤å­˜å‚¨ç­–ç•¥éœ€è¦æ­£ç¡®é…ç½®å¦‚ä¸Šè·¨åŸŸç­–ç•¥åŽæ‰èƒ½ä½¿ç”¨ Web 端上传文件,Cloudreve å¯ä»¥å¸®ä½ è‡ªåŠ¨è®¾ç½®ï¼Œä½ ä¹Ÿå¯ä»¥æ‰‹åŠ¨è®¾ç½®ã€‚å¦‚æžœä½ å·²è®¾ç½®è¿‡æ­¤ Bucket 的跨域策略,此步骤å¯ä»¥è·³è¿‡ã€‚", + "letCloudreveHelpMe": "让 Cloudreve 帮我设置", + "skip": "跳过", + "createUpyunBucketDes": "填写在 <0>åˆæ‹äº‘颿¿ 创建云存储æœåŠ¡å称。", + "storageServiceName": "æœåŠ¡åç§°", + "operatorName": "æ“作员å", + "operatorPassword": "æ“作员密ç ", + "tokenStatus": "Token 防盗链", + "upyunTokenDes": "å¼ºçƒˆå»ºè®®å¼€å¯ Token 防盗链,å‰å¾€æ‰€åˆ›å»ºäº‘存储æœåŠ¡çš„ <0>功能é…ç½® 颿¿ï¼Œè½¬åˆ° <1>访问控制 选项å¡ï¼Œå¼€å¯ Token 防盗链并设定密ç ã€‚", + "tokenEnabled": "å·²å¼€å¯ Token 防盗链", + "tokenDisabled": "æœªå¼€å¯ Token 防盗链", + "upyunTokenSecretDes": "填写你所设置的 Token 防盗链密钥。", + "upyunTokenSecret": "Token 防盗链密钥", + "createCOSBucketDes": "å‰å¾€ <0>COS ç®¡ç†æŽ§åˆ¶å° åˆ›å»ºå­˜å‚¨æ¡¶ï¼Œè½¬åˆ°æ‰€åˆ›å»ºå­˜å‚¨æ¡¶çš„åŸºç¡€é…置页é¢ï¼Œå°† <1>存储桶åç§° 填写到上方。", + "obsBucketDes": "å‰å¾€ <0>OBS ç®¡ç†æŽ§åˆ¶å° åˆ›å»ºå­˜å‚¨æ¡¶ï¼Œå°† <1>æ¡¶åç§° å¡«å†™åˆ°ä¸Šæ–¹ã€‚å­˜å‚¨æ¡¶ç±»åˆ«åªæ”¯æŒ <2>标准存储 或 <3>低频访问存储。", + "cosPrivateRW": "ç§æœ‰è¯»å†™", + "cosPublicRW": "å…¬å…±è¯»ç§æœ‰å†™", + "cosAccessDomainDes": "在所创建 Bucket 的概况页é¢ï¼Œå¡«å†™ <0>域åä¿¡æ¯ æ ç›®ä¸‹ 给出的 <1>访问域å。你也å¯ä»¥ä½¿ç”¨è‡ªå·±ç»‘定的æºç«™åŸŸå或 CDN 加速域å。", + "obsEndpointDes": "在所创建存储桶的概览页é¢ï¼Œå¡«å†™ <0>域åä¿¡æ¯ æ ç›®ä¸‹ 给出的 <1>Endpoint(终端节点)。", + "accessDomain": "访问域å", + "cosCDNDomainDes": "å‰å¾€ <0>腾讯云 CDN ç®¡ç†æŽ§åˆ¶å° åˆ›å»º CDN 加速域å,并设定æºç«™ä¸ºåˆšåˆ›å»ºçš„ COS 存储桶。在下方填写 CDN 加速域å,并选择是å¦ä½¿ç”¨ HTTPS:", + "cosCredentialDes": "填写在腾讯云 <0>访问密钥 页é¢èŽ·å–一对访问密钥。请确ä¿è¿™å¯¹å¯†é’¥æ‹¥æœ‰ COS æœåŠ¡çš„è®¿é—®æƒé™ã€‚你也å¯ä»¥åˆ›å»ºå¸¦æœ‰ <1>编程访问 能力的<2>å­ç”¨æˆ·ï¼Œä¸ºå…¶èµ‹äºˆ COS æœåŠ¡çš„è®¿é—®æƒé™ã€‚", + "obsCredentialDes": "填写在åŽä¸ºäº‘ <0>访问密钥 页é¢èŽ·å–一对访问密钥。你也å¯ä»¥åˆ›å»ºå¸¦æœ‰ <1>编程访问 能力的<2>IAM 用户,为其赋予 <3>OBS OperateAccess æƒé™ã€‚", + "grantAccess": "è´¦å·æŽˆæƒ", + "grantAccessLater": "点击下方按钮创建存储策略åŽï¼Œè¿˜éœ€è¦åœ¨å­˜å‚¨ç­–略设置页é¢è¿›è¡Œè´¦å·æŽˆæƒã€‚", + "odHttpsWarning": "ä½ å¿…é¡»å¯ç”¨ HTTPS æ‰èƒ½ä½¿ç”¨ OneDrive/SharePoint 存储策略;å¯ç”¨åŽåŒæ­¥æ›´æ”¹ 傿•°è®¾ç½® - ç«™ç‚¹ä¿¡æ¯ - 站点URL。", + "creatAadAppDes": "å‰å¾€ <0>Microsoft Entra ID æŽ§åˆ¶å° å¹¶ç™»å½•ï¼Œç™»å½•åŽè¿›å…¥<1>Microsoft Entra ID 管ç†é¢æ¿ï¼Œè¿™é‡Œç™»å½•使用的账å·å’Œæœ€ç»ˆå­˜å‚¨ä½¿ç”¨çš„ OneDrive 所属账å·å¯ä»¥ä¸åŒã€‚", + "createAadAppDes2": "进入左侧 <0>应用注册 èœå•,并点击 <1>新注册 按钮。填写应用注册表å•。其中,åç§°å¯ä»»å–ï¼›<2>å—æ”¯æŒçš„叿ˆ·ç±»åž‹ 选择为 <3>任何组织目录(任何 Azure AD 目录 - 多租户)ä¸­çš„å¸æˆ·å’Œä¸ªäºº Microsoft 叿ˆ·(例如,Skypeã€Xbox)ï¼›<4>é‡å®šå‘ URI (å¯é€‰) 请选择 <5>Web,并填写 <6>{{url}}ï¼› å…¶ä»–ä¿æŒé»˜è®¤å³å¯ã€‚", + "aadAppIDDes": "进入应用管ç†çš„ <0>概览 页é¢ï¼Œçœ‹åˆ°çš„ <1>应用程åº(客户端) ID 的值。", + "entraIdApp": "Entra ID 应用信æ¯", + "aadAppID": "应用程åº(客户端) ID", + "addAppSecretDes": "客户端密ç çš„创建方å¼ï¼šè¿›å…¥åº”用管ç†é¡µé¢å·¦ä¾§çš„ <0>è¯ä¹¦å’Œå¯†ç  èœå•,点击 <1>æ–°å»ºå®¢æˆ·ç«¯å¯†ç  æŒ‰é’®ï¼Œ<2>æˆªæ­¢æœŸé™ é€‰æ‹©ä¸ºæœ€é•¿æ—¶é—´ã€‚å®¢æˆ·ç«¯å¯†ç è¿‡æœŸåŽï¼Œéœ€è¦é‡æ–°åˆ›å»ºå¹¶å°†å…¶å¡«å…¥å­˜å‚¨ç­–略设置中。", + "aadAppSecret": "客户端密ç ", + "aadAccountCloud": "Microsoft Graph 端点", + "aadAccountCloudDes": "请根æ®ä½ ä½¿ç”¨çš„ Microsoft 365 è´¦å·ç±»åž‹é€‰æ‹©å¯¹åº”的端点。", + "multiTenant": "公有(国际版)", + "gallatin": "世纪互è”", + "sharePointDes": "是å¦å°†æ–‡ä»¶å­˜æ”¾åœ¨ SharePoint 中?", + "saveToOneDrive": "存到账å·é»˜è®¤ OneDrive 驱动器中", + "spSiteURL": "SharePoint 站点地å€", + "odReverseProxyURLDes": "是å¦è¦åœ¨æ–‡ä»¶ä¸‹è½½æ—¶æ›¿æ¢ä¸ºä½¿ç”¨è‡ªå»ºçš„å代æœåŠ¡å™¨ï¼Ÿ", + "odReverseProxyURL": "å代æœåŠ¡å™¨åœ°å€", + "chunkSizeDesOd": "å…许范围:5 MB ~ 5GB,OneDrive è¦æ±‚必须为 320 KiB (327,680 bytes) 的整数å€ã€‚", + "limitOdTPSDes": "é™åˆ¶ OneDrive API 请求频率", + "tps": "TPS é™åˆ¶", + "tpsDes": "留空表示ä¸é™åˆ¶ã€‚é™åˆ¶æ­¤å­˜å‚¨ç­–ç•¥æ¯ç§’å‘ OneDrive å‘é€ API 请求最大数é‡ã€‚超出此频率的请求会被é™é€Ÿã€‚多个 Cloudreve 节点转存文件时,它们会å„è‡ªä½¿ç”¨è‡ªå·±çš„é™æµæ¡¶ï¼Œè¯·æ ¹æ®æƒ…况按比例调低此数值。Web 端上传请求并ä¸å—æ­¤é™åˆ¶ã€‚", + "tpsBurst": "TPS çªå‘请求", + "tpsBurstDes": "请求空闲时,Cloudreve å¯å°†æŒ‡å®šæ•°é‡çš„åé¢é¢„留给未æ¥çš„çªå‘æµé‡ä½¿ç”¨ã€‚", + "odOauthDes": "但是你需è¦ç‚¹å‡»ä¸‹æ–¹æŒ‰é’®ï¼Œå¹¶ä½¿ç”¨ OneDrive 登录授æƒä»¥å®Œæˆåˆå§‹åŒ–åŽæ‰èƒ½ä½¿ç”¨ã€‚æ—¥åŽä½ å¯ä»¥åœ¨å­˜å‚¨ç­–略列表页é¢é‡æ–°è¿›è¡ŒæŽˆæƒã€‚", + "gotoAuthPage": "转到授æƒé¡µé¢", + "s3BucketDes": "å‰å¾€ AWS S3 控制å°åˆ›å»ºå­˜å‚¨æ¡¶ï¼Œåœ¨ä¸‹æ–¹å¡«å†™ä½ åˆ›å»ºå­˜å‚¨æ¡¶æ—¶æŒ‡å®šçš„ <0>Bucket å称:", + "s3EndpointDes": "指定存储桶的 EndPoint(地域节点),填写为完整的 URL æ ¼å¼ï¼Œæ¯”如 <0>https://bucket.region.example.com。", + "selectRegionDes": "输入存储桶所在的区域代ç ï¼Œå¦‚ <0>us-east-1ã€‚å¯¹äºŽéž AWS çš„ S3 兼容存储æä¾›å•†ï¼Œè¯·åœ¨å…¶æ–‡æ¡£ä¸­æŸ¥æ‰¾å¦‚何填写此项。", + "chunkSizeLabelS3": "请指定分片上传时的分片大å°ï¼ŒèŒƒå›´ 5 MB ~ 5 GB。", + "policyEndpoint": "Endpoint", + "s3Region": "地区代ç ", + "s3EndpointPathStyle": "选择是å¦å¼ºåˆ¶ä½¿ç”¨è·¯å¾„æ ¼å¼ Endpoint。æŸäº›ç¬¬ä¸‰æ–¹ S3 兼容存储å¯èƒ½éœ€è¦å‹¾é€‰æ­¤é€‰é¡¹ã€‚å¼€å¯åŽï¼Œå°†ä¼šå¼ºåˆ¶ä½¿ç”¨è·¯å¾„æ ¼å¼åœ°å€ï¼Œæ¯”如 <0>http://s3.amazonaws.com/BUCKET/KEY。", + "usePathEndpoint": "å¼ºåˆ¶è·¯å¾„æ ¼å¼ Endpoint", + "thumbExt": "å¯ç”Ÿæˆç¼©ç•¥å›¾çš„æ–‡ä»¶æ‰©å±•å", + "thumbExtDes": "留空表示使用存储策略预定义集åˆã€‚对本机ã€S3存储策略无效", + "driverRoot": "驱动器根目录", + "driverRootDes": "选择在 OneDrive 账户中ä¿å­˜æ–‡ä»¶çš„ä½ç½®ã€‚更改此选项会导致存储策略中已有文件无法访问。", + "saveToDefaultOneDrive": "ä¿å­˜æ–‡ä»¶åˆ°é»˜è®¤ OneDrive 驱动器", + "saveToSharePoint": "ä¿å­˜æ–‡ä»¶åˆ° SharePoint", + "sharePointUrlDes": "输入 SharePoint 站点 URL。失去焦点åŽï¼Œç³»ç»Ÿå°†è‡ªåŠ¨è½¬æ¢ä¸ºæ­£ç¡®çš„驱动器标识。", + "ks3selectRegionDes": "输入存储桶所在的区域代ç ï¼Œå¦‚ <0>BEIJING。", + "ks3EndpointPathStyle": "选择是å¦å¼ºåˆ¶ä½¿ç”¨è·¯å¾„æ ¼å¼ Endpoint。", + "ossRegionDes": "输入存储桶所在的区域代ç ï¼Œå¦‚ <0>cn-hangzhou。你å¯ä»¥åœ¨ <1>OSS地域和访问域å 的表格中找到对应地域,并填写对应的 <2>地域ID。" + }, + "node": { + "slave": "从机", + "master": "主机", + "noCapabilities": "未å¯ç”¨ä»»ä½•功能", + "active": "å·²å¯ç”¨", + "suspended": "å·²ç¦ç”¨", + "deleteNodeConfirmation": "确定è¦åˆ é™¤èŠ‚ç‚¹ {{name}} å—?", + "editNode": "编辑节点 {{node}}", + "thisIsMasterNodes": "ä½ æ­£åœ¨ç¼–è¾‘ä¸€ä¸ªä¸»æœºèŠ‚ç‚¹ï¼Œå³æ­£åœ¨æœåС当å‰ç«™ç‚¹çš„ Cloudreve 实例。", + "enableNode": "å¯ç”¨èŠ‚ç‚¹", + "enableNodeDes": "å¯ç”¨èŠ‚ç‚¹åŽï¼ŒèŠ‚ç‚¹ä¼šæŽ¥å—处ç†å·²å¼€å¯çš„功能。", + "name": "åç§°", + "nameNode": "节点å称,也用于å‘用户展示。", + "type": "类型", + "server": "节点地å€", + "serverDes": "用于与节点通信的地å€ã€‚如果你è¦åœ¨æ­¤èŠ‚ç‚¹å­˜å‚¨æ–‡ä»¶ï¼Œæ­¤åœ°å€ä¹Ÿä¼šæš´éœ²ç»™ç”¨æˆ·ç«¯ç”¨äºŽä¸Šä¼ æ–‡ä»¶ã€‚", + "loadBalancerRankDes": "为此节点指定负载å‡è¡¡æƒé‡ï¼Œæ•°å€¼ä¸ºæ•´æ•°ã€‚æƒé‡è¶Šé«˜ï¼ŒèŠ‚ç‚¹è¢«é€‰ä¸­çš„æ¦‚çŽ‡è¶Šå¤§ã€‚", + "loadBalancerRank": "è´Ÿè½½å‡è¡¡æƒé‡", + "slaveSecret": "从机密钥", + "slaveSecretDes": "用于从机节点与主机节点通信的密钥。需è¦ä¸Žä»Žæœºé…置文件中 <0>Slave 下的 <1>Secret ä¿æŒä¸€è‡´ã€‚", + "testNode": "测试节点通信", + "testNodeSuccess": "节点通信æˆåŠŸ", + "createArchiveDes": "接å—创建压缩文件的任务请求。", + "extractArchiveDes": "接å—解压文件的任务请求。", + "remoteDownloadDes": "接å—离线下载的任务请求。å¯ç”¨åŽè¿˜éœ€è¦åœ¨ä¸‹æ–¹é…置离线下载相关信æ¯ã€‚", + "downloader": "下载器", + "aria2Des": "请在目标节点æœåŠ¡å™¨ä¸Šä»¥å’Œè¿è¡Œ Cloudreve 相åŒçš„用户/æƒé™å¯åЍ Aria2, 并在 Aria2 çš„é…ç½®æ–‡ä»¶ä¸­å¼€å¯ RPC æœåŠ¡ï¼Œæ›´å¤šä¿¡æ¯åŠæŒ‡å¼•请å‚考文档的“离线下载â€ç« èŠ‚ã€‚", + "qbittorrentDes": "请在目标节点æœåŠ¡å™¨ä¸Šä»¥å’Œè¿è¡Œ Cloudreve 相åŒçš„用户/æƒé™å¯åЍ qBittorrent, 并在 qBittorrent 的设置中开å¯â€œWeb UIâ€æœåŠ¡ï¼Œæ›´å¤šä¿¡æ¯åŠæŒ‡å¼•请å‚考文档的“离线下载â€ç« èŠ‚ã€‚", + "rpcServer": "RPC æœåŠ¡å™¨åœ°å€", + "rpcServerHelpDes": "包å«ç«¯å£çš„完整 RPC æœåŠ¡å™¨åœ°å€ï¼Œä¾‹å¦‚:<0>http://127.0.0.1:6800/。", + "rpcToken": "RPC 授æƒä»¤ç‰Œ", + "rpcTokenDes": "与 Aria2 é…置文件中 <0>rpc-secret ä¿æŒä¸€è‡´ï¼Œæœªè®¾ç½®è¯·ç•™ç©ºã€‚", + "downloaderOptionDes": "在创建下载任务时é¢å¤–æºå¸¦çš„下载器é…置,以 JSON 键值对格å¼ä¹¦å†™ï¼Œå…·ä½“å¯å‚考<0>下载器官方文档。", + "refreshInterval": "状æ€åˆ·æ–°é—´éš” (ç§’)", + "refreshIntervalDes": "Cloudreve å‘下载器请求刷新任务状æ€çš„间隔,实际刷新间隔也å–决于“离线下载â€é˜Ÿåˆ—çš„é…置和ç¹å¿™ç¨‹åº¦ã€‚", + "waitForSeeding": "等待åšç§å®Œæˆ", + "waitForSeedingDes": "å¯ç”¨åŽï¼Œå½“离线下载任务完æˆåŽï¼Œä¼šä¿ç•™æ­¤ä»»åŠ¡åœ¨åšç§çжæ€ï¼Œç›´åˆ°åœ¨ä¸‹è½½å™¨é…置的åšç§ç»“æŸæ¡ä»¶æ»¡è¶³ã€‚等待åšç§å‘生在离线下载任务完æˆåŽï¼Œä¸ä¼šå½±å“用户使用下载的文件。", + "webUIEndpoint": "Web UI 地å€", + "webUIEndpointDes": "qBittorrent çš„ Web UI 地å€ï¼Œæ¯”如 <0>http://127.0.0.1:8080/。", + "tempPath": "临时下载目录", + "tempPathDes": "节点上用于临时存放离线下载文件的目录,节点上的 Cloudreve è¿›ç¨‹éœ€è¦æ­¤ç›®å½•的读ã€å†™ã€æ‰§è¡Œæƒé™ï¼Œä¸‹è½½å™¨ä¹Ÿè¦èƒ½å¤Ÿè®¿é—®æ­¤ç›®å½•。留空会使用默认的临时文件路径。", + "webUIUsername": "Web UI 用户å", + "webUIPassword": "Web UI 密ç ", + "webUICredDes": "如果未å¯ç”¨è®¤è¯ï¼Œæ­¤å¤„请留空。", + "downloaderTestPass": "æˆåŠŸè¿žæŽ¥åˆ°ä¸‹è½½å™¨ï¼Œç‰ˆæœ¬ï¼š{{version}}", + "testDownloader": "测试下载器通信", + "addNewNode": "新建节点", + "nameTheNode": "为节点命å:", + "copyBinary": "", + "runCrSlave": "在节点上è¿è¡Œå’Œä¸»ç«™ç›¸åŒç‰ˆæœ¬çš„ Cloudreve,并使用以下é…置文件å¯åŠ¨ï¼š", + "keepIfUpload": "如果你未æ¥éœ€è¦ä½¿ç”¨æ­¤èŠ‚ç‚¹å­˜å‚¨ï¼Œè¯·ä¿ç•™ä¸‹é¢çš„跨域é…置。", + "storeFiles": "存储文件", + "storeFilesDes": "使用此节点存储用户文件。", + "storeFilesHint": "如果你想使用此节点存储文件,清å‰å¾€ <0>存储策略 页颿–°å»ºä»Žæœºå­˜å‚¨ç­–略,并选择此节点。", + "runCrWithConfig": "将上述文件ä¿å­˜ä¸º <0>config.ini 文件,并使用此文件å¯åЍ Cloudreve:<0>./cloudreve -c config.ini。一个从机 Cloudreve 实例å¯ä»¥å¯¹æŽ¥å¤šä¸ª Cloudreve 主节点,åªéœ€åœ¨æ‰€æœ‰ä¸»èŠ‚ç‚¹ä¸­æ·»åŠ æ­¤ä»ŽæœºèŠ‚ç‚¹å¹¶ä¿æŒå¯†é’¥ä¸€è‡´å³å¯ã€‚", + "inputServer": "输入节点的地å€ï¼š", + "testButton": "å¯ä»¥ç‚¹å‡»ä¸‹é¢æŒ‰é’®æµ‹è¯•é€šä¿¡æ˜¯å¦æ­£å¸¸ã€‚", + "hostHeaderHint": "如果有签å错误,请检查从机å‰ç½®å代是å¦å‘ˆé€’了 <0>Host 头。", + "features": "å·²å¯ç”¨åŠŸèƒ½", + "remoteDownload": "离线下载", + "refresh": "刷新" + }, + "group": { + "countUser": "统计", + "anonymous": "未登录访客用户组", + "sysGroup": "系统用户组", + "adminGroup": "管ç†å‘˜ç”¨æˆ·ç»„", + "#": "#", + "name": "åç§°", + "type": "存储策略", + "count": "下属用户数", + "size": "最大容é‡", + "nameOfGroup": "用户组å", + "nameOfGroupDes": "用户组的å称,用于å‘用户展示。", + "availablePolicies": "å¯ç”¨å­˜å‚¨ç­–ç•¥", + "availablePoliciesDes": "指定用户组å¯ç”¨çš„存储策略,修改此设置ä¸ä¼šå½±å“用户已上传的文件。", + "availablePolicyDesPro": "å¯å¤šé€‰ï¼Œç”¨æˆ·å¯åœ¨é€‰å®šèŒƒå›´å†…自由切æ¢å­˜å‚¨ç­–ç•¥.", + "initialStorageQuota": "åˆå§‹å®¹é‡", + "initialStorageQuotaDes": "用户组下的用户åˆå§‹å¯ç”¨æœ€å¤§å®¹é‡ã€‚", + "isAdmin": "管ç†å‘˜ç”¨æˆ·ç»„", + "isAdminDes": "å¼€å¯åŽï¼Œç”¨æˆ·ç»„下的用户将拥有管ç†å‘˜æƒé™ã€‚", + "share": "分享", + "allowCreateShareLink": "创建分享链接", + "allowCreateShareLinkDes": "关闭åŽï¼Œç”¨æˆ·æ— æ³•创建分享链接。", + "shareFree": "无需购买分享链接", + "shareFreeDes": "å¼€å¯åŽï¼Œç”¨æˆ·æ— éœ€è´­ä¹°å³å¯è®¿é—®æ‰€æœ‰ä»˜è´¹åˆ†äº«é“¾æŽ¥ã€‚", + "fileManagement": "文件管ç†", + "allowWabDAV": "WebDAV", + "allowWabDAVDes": "关闭åŽï¼Œç”¨æˆ·æ— æ³•通过 WebDAV å议连接至网盘。", + "allowWabDAVProxy": "WebDAV 代ç†", + "allowWabDAVProxyDes": "å¯ç”¨åŽï¼Œç”¨æˆ·å¯ä»¥é…ç½® WebDAV 下载ç»ç”± Cloudreve 中转。", + "compressTask": "压缩/解压缩任务", + "compressTaskDes": "å¼€å¯åŽï¼Œç”¨æˆ·å¯ä»¥åœ¨çº¿åŽ‹ç¼©/解压缩文件。", + "compressSize": "待压缩文件最大大å°", + "compressSizeDes": "用户å¯åˆ›å»ºçš„压缩任务的文件最大总大å°ï¼Œå¡«å†™ä¸º 0 表示ä¸é™åˆ¶ã€‚这一é™åˆ¶åœ¨åˆ›å»ºåŽ‹ç¼©ä»»åŠ¡æ—¶ä¸ä¼šæ£€æŸ¥ï¼Œå½“执行时已处ç†åŽŸå§‹æ–‡ä»¶æ€»å¤§å°è¶…过此é™åˆ¶æ—¶ï¼Œä»»åŠ¡ä¼šå¤±è´¥ã€‚", + "decompressSize": "待解压文件最大大å°", + "decompressSizeDes": "用户å¯åˆ›å»ºçš„解压缩任务的文件最大总大å°ï¼Œå¡«å†™ä¸º 0 表示ä¸é™åˆ¶ã€‚", + "allowRemoteDownload": "离线下载", + "allowRemoteDownloadDes": "是å¦å…许用户创建离线下载任务。如需使用离线下载,还需è¦åœ¨ <0>节点列表 中有开å¯ç¦»çº¿ä¸‹è½½åŠŸèƒ½çš„èŠ‚ç‚¹ã€‚", + "aria2Options": "ä¸‹è½½å™¨ä»»åŠ¡å‚æ•°", + "aria2OptionsDes": "qBittorrent 或 Aria2 下载器的任务é¢å¤–é…ç½®å‚æ•°ï¼Œä»¥ JSON ç¼–ç åŽçš„é”®-值格å¼ä¹¦å†™ï¼Œå¯ç”¨å‚数请查阅官方文档。", + "aria2BatchSize": "批é‡ç¦»çº¿ä¸‹è½½æœ€å¤§æ•°é‡", + "aria2BatchSizeDes": "批é‡åˆ›å»ºç¦»çº¿ä¸‹è½½æ—¶çš„æœ€å¤§æ•°é‡ï¼Œå¡«å†™ä¸º 0 表示ä¸é™åˆ¶ã€‚", + "migratePolicy": "存储策略转移", + "migratePolicyDes": "是å¦ç”¨æˆ·åˆ›å»ºå­˜å‚¨ç­–略转移任务。", + "advanceDelete": "高级文件删除选项", + "advanceDeleteDes": "å¼€å¯åŽï¼Œç”¨æˆ·åœ¨å‰å°åˆ é™¤æ–‡ä»¶æ—¶å¯ä»¥é€‰æ‹©æ˜¯å¦ä¿ç•™ç‰©ç†æ–‡ä»¶ï¼Œè¯·åªå¼€æ”¾ç»™å¯ä¿¡ç”¨æˆ·ç»„。", + "allowSelectNode": "å…许选择节点", + "allowSelectNodeDes": "å¼€å¯åŽï¼Œç”¨æˆ·å¯ä»¥åœ¨åˆ›å»ºä»»åŠ¡å‰é€‰æ‹©å¤„ç†èŠ‚ç‚¹ï¼›å…³é—­åŽï¼Œç³»ç»Ÿä¼šåœ¨ç”¨æˆ·ç»„å…许的节点下自动分é…节点。", + "allowedNodes": "å¯ç”¨èŠ‚ç‚¹", + "allowedNodesDes": "指定用户组å¯ç”¨çš„任务处ç†èŠ‚ç‚¹ï¼Œç•™ç©ºè¡¨ç¤ºå…¨éƒ¨èŠ‚ç‚¹éƒ½å¯ç”¨ã€‚用户åªèƒ½åœ¨æ­¤åˆ—表内选择或被负载å‡è¡¡åˆ†é…节点。目å‰è¦†ç›–çš„ä»»åŠ¡èŒƒå›´æ˜¯ï¼šç¦»çº¿ä¸‹è½½ã€æ–‡ä»¶åŽ‹ç¼©æˆ–è§£åŽ‹ç¼©ã€‚å…¶ä»–ä»»åŠ¡ä¼šåˆ†é…给主机处ç†ã€‚", + "allNodes": "所有节点", + "esclateAnonymity": "æå‡åŒ¿å用户æƒé™", + "esclateAnonymityDes": "å¼€å¯åŽï¼Œç”¨æˆ·å¯ä»¥ä¸ºåŒ¿å用户设置更高æƒé™ï¼ˆä¿®æ”¹/创建/删除);关闭åŽï¼Œç”¨æˆ·æœ€é«˜åªèƒ½èµ‹äºˆåŒ¿å用户åªè¯»æƒé™ã€‚更改此设置ä¸ä¼šå½±å“已设置的分享链接或文件。", + "allowDownloadShare": "访问分享链接", + "allowDownloadShareDes": "关闭åŽï¼Œç”¨æˆ·æ— æ³•查看别人的分享链接。此项设置优先级高于分享链接的æƒé™è®¾ç½®ã€‚", + "deletedNode": "已删除节点 #{{id}}", + "maxWalkedFiles": "最大é历文件数", + "maxWalkedFilesDes": "在æŸäº›éœ€è¦æ·±å±‚é历文件的æ“作中,最大å…许é历的文件数。", + "trashBinDuration": "回收站ä¿ç•™æ—¶é—´ï¼ˆç§’)", + "trashBinDurationDes": "回收站中文件的ä¿ç•™æ—¶é•¿ï¼Œè¶…æœŸåŽæ–‡ä»¶å°†è¢«å½»åº•删除。更改此设置ä¸ä¼šå½±å“å·²ç»åœ¨å›žæ”¶ç«™ä¸­çš„æ–‡ä»¶ã€‚", + "serverSideBatchDownload": "æœåŠ¡ç«¯æ‰“åŒ…ä¸‹è½½", + "serverSideBatchDownloadDes": "是å¦å…许用户多选文件使用æœåŠ¡ç«¯ä¸­è½¬æ‰“åŒ…ä¸‹è½½ï¼Œå…³é—­åŽï¼Œç”¨æˆ·ä»ç„¶å¯ä»¥ä½¿ç”¨çº¯ Web 端打包下载功能。", + "uploadDownload": "上传和下载", + "getDirectLink": "获å–直链", + "getDirectLinkDes": "是å¦å…è®¸ç”¨æˆ·èŽ·å–æ–‡ä»¶çš„直链。", + "bathSourceLinkLimit": "批é‡ç”Ÿæˆå¤–直链é‡é™åˆ¶", + "bathSourceLinkLimitDes": "å…è®¸ç”¨æˆ·å•æ¬¡æ‰¹é‡èŽ·å–直链的最大文件数é‡ï¼Œå¡«å†™ä¸º 0 表示ä¸å…许获å–直链。", + "redirectedSource": "使用é‡å®šå‘的直链", + "redirectedSourceDes": "推èå¼€å¯ã€‚å¼€å¯åŽï¼Œç”¨æˆ·èŽ·å–的文件直链将由 Cloudreve 中转,链接较短。关闭åŽï¼Œç”¨æˆ·èŽ·å–çš„æ–‡ä»¶ç›´é“¾ä¼šå˜æˆæ–‡ä»¶çš„原始链接,且与文件版本绑定。部分存储策略在æŸäº›è®¾ç½®ä¸‹èŽ·å–çš„éžä¸­è½¬ç›´é“¾æ— æ³•ä¿æŒæ°¸ä¹…有效,请å‚阅 Cloudreve 文档。", + "reuseDirectLink": "é‡ç”¨å·²æœ‰ç›´é“¾", + "reuseDirectLinkDes": "å¼€å¯åŽï¼Œå¤šæ¬¡è¯·æ±‚åŒä¸€ä¸ªæ–‡ä»¶çš„直链时,会é‡ç”¨å·²åˆ›å»ºçš„中转直链。", + "downloadSpeedLimit": "下载é™é€Ÿ", + "downloadSpeedLimitDes": "填写为 0 表示ä¸é™åˆ¶ã€‚å¼€å¯é™åˆ¶åŽï¼Œç”¨æˆ·ä¸‹è½½æ‰€æœ‰æ”¯æŒé™é€Ÿçš„存储策略下的文件时,下载最大速度会被é™åˆ¶ã€‚", + "anonymousHint": "æ­¤ç”¨æˆ·ç»„å¯¹åº”ç€æœªç™»å½•的匿å访客。", + "create": "新建", + "copyFromExisting": "从现有用户组å¤åˆ¶?", + "notCopy": "ä¸å¤åˆ¶", + "confirmDelete": "确认è¦åˆ é™¤ç”¨æˆ·ç»„ {{group}}?", + "new": "新建用户组", + "editGroup": "编辑 {{group}}" + }, + "user": { + "createdAt": "创建日期", + "originUserGroup": "原始用户组", + "originUserGroupDes": "ç”¨æˆ·åœ¨è´­ä¹°ç”¨æˆ·ç»„å‰æ‰€å±žçš„用户组,当å‰ç”¨æˆ·ç»„到期åŽä¼šå›žé€€åˆ°æ­¤ç”¨æˆ·ç»„。", + "noOriginUserGroup": "æ— ", + "groupExpired": "用户组过期日期", + "groupExpiredDes": "ISO8601 æ ¼å¼çš„用户组到期日期,留空表示当å‰ç”¨æˆ·ç»„永久有效。", + "openUserFiles": "打开用户文件", + "id": "ID", + "idValue": "{{id}} ({{hash_id}})", + "avatar": "头åƒ", + "removeAvatar": "移除头åƒ", + "userDialogTitle": "用户详情", + "2FAEnabled": "å·²å¯ç”¨äºŒæ­¥éªŒè¯", + "qqEnabled": "已绑定 QQ", + "logtoEnabled": "已绑定 Logto", + "deleted": "用户已删除", + "new": "新建用户", + "filter": "过滤", + "emptyNoFilter": "留空表示ä¸è¿‡æ»¤æ­¤é¡¹ã€‚", + "selectedObjects": "已选择 {{num}} 个对象", + "nick": "昵称", + "email": "Email", + "group": "用户组", + "status": "状æ€", + "usedStorage": "已用空间", + "status_active": "正常", + "status_inactive": "未激活", + "status_manual_banned": "手动å°ç¦", + "status_sys_banned": "系统å°ç¦", + "toggleBan": "å°ç¦/è§£å°", + "filterCondition": "过滤æ¡ä»¶", + "all": "全部", + "userStatus": "用户状æ€", + "apply": "应用", + "editUser": "编辑 {{nick}}", + "password": "密ç ", + "passwordDes": "留空表示ä¸ä¿®æ”¹", + "groupDes": "用户所属用户组", + "2FA": "二步验è¯", + "notEnabled": "未å¯ç”¨", + "reset2Fa": "关闭", + "reset": "é‡ç½®", + "confirmDelete": "确认è¦åˆ é™¤ç”¨æˆ· {{user}}?", + "deleteXUsers": "删除 {{num}} 个用户", + "confirmBatchDelete": "确认è¦åˆ é™¤ {{num}} 个用户?", + "calibrateStorage": "校准存储空间", + "calibrateStorageSuccess": "存储空间校准æˆåŠŸ" + }, + "file": { + "deleteXFiles": "删除 {{num}} 个文件", + "confirmBatchDelete": "确定è¦åˆ é™¤ {{num}} 个文件?", + "confirmDelete": "确认è¦åˆ é™¤æ–‡ä»¶ {{file}}?", + "haveShares": "拥有分享链接", + "haveDirectLinks": "拥有中转直链", + "directLinkId": "链接标识", + "directLinks": "中转直链", + "noRecords": "没有记录", + "speed": "é™é€Ÿ", + "downloads": "下载次数", + "shareLink": "分享链接", + "shareLinkNum": "{{num}} 个 (<0>查看)", + "blobType": "类型", + "noEntities": "没有 Blob", + "blobs": "Blobs", + "creator": "创建者", + "source": "æº", + "key": "é”®", + "value": "值", + "isPublic": "公开", + "noMetadata": "没有元数æ®", + "metadata": "元数æ®", + "id": "ID", + "primaryStoragePolicy": "首选存储策略", + "fileDialogTitle": "文件详情", + "name": "文件å", + "deleteAsync": "删除任务将在åŽå°æ‰§è¡Œ", + "forceDelete": "强制删除", + "size": "大å°", + "sizeUsed": "å ç”¨ç©ºé—´", + "uploader": "所有者", + "createdAt": "创建于", + "uploading": "上传中", + "unknownUploader": "未知", + "uploaderID": "所有者 ID", + "searchFileName": "æœç´¢æ–‡ä»¶å", + "storagePolicy": "存储策略", + "selectTargetUser": "请先选择目标用户", + "importTaskCreated": "导入任务已创建,你å¯ä»¥åœ¨â€œåŽå°ä»»åŠ¡â€ä¸­æŸ¥çœ‹æ‰§è¡Œæƒ…况", + "manuallyPathOnly": "é€‰æ‹©çš„å­˜å‚¨ç­–ç•¥åªæ”¯æŒæ‰‹åŠ¨è¾“å…¥è·¯å¾„", + "selectFolder": "选择目录", + "import": "导入", + "importExternalFolder": "导入外部目录", + "importExternalFolderDes": "ä½ å¯ä»¥å°†å­˜å‚¨ç­–略中已有文件ã€ç›®å½•结构导入到 Cloudreve 中,导入æ“作ä¸ä¼šé¢å¤–å ç”¨ç‰©ç†å­˜å‚¨ç©ºé—´ï¼Œä½†ä»ä¼šæ­£å¸¸æ‰£é™¤ç”¨æˆ·å·²ç”¨å®¹é‡ç©ºé—´ã€‚", + "storagePolicyDes": "选择è¦å¯¼å…¥æ–‡ä»¶ç›®å‰å­˜å‚¨æ‰€åœ¨çš„存储策略。", + "targetUser": "目标用户", + "targetUserDes": "选择è¦å°†æ–‡ä»¶å¯¼å…¥åˆ°å“ªä¸ªç”¨æˆ·çš„æ–‡ä»¶ç³»ç»Ÿä¸­ã€‚", + "srcFolderPath": "原始目录路径", + "select": "选择", + "selectSrcDes": "è¦å¯¼å…¥çš„目录在存储端的路径。", + "dstFolderPath": "目的目录路径", + "dstFolderPathDes": "è¦å°†ç›®å½•导入到用户文件系统中的路径。", + "recursivelyImport": "递归导入å­ç›®å½•", + "recursivelyImportDes": "是å¦å°†ç›®å½•下的所有å­ç›®å½•递归导入。", + "createImportTask": "创建导入任务", + "unlink": "解除关è”(ä¿ç•™ç‰©ç†æ–‡ä»¶ï¼‰", + "searchUser": "æœç´¢ç”¨æˆ·æ˜µç§°æˆ–邮箱...", + "extractMediaMeta": "æå–媒体信æ¯", + "extractMediaMetaDes": "是å¦åœ¨å¯¼å…¥æ–‡ä»¶çš„åŒæ—¶å°è¯•æå–æ¯ä¸ªæ–‡ä»¶çš„媒体信æ¯ã€‚", + "importWarning": "注æ„事项", + "importWarnings": [ + "导入åŽï¼Œç‰©ç†æ–‡ä»¶å°†ç”± Cloudreve 接管,åŽç»­è¯·ä¸è¦åœ¨å¤–部修改此文件;", + "ä¸è¦é‡å¤å¯¼å…¥ç›¸åŒçš„æ–‡ä»¶ï¼›", + "如果用户文件冲çªï¼Œæ­¤æ–‡ä»¶ä¼šè¢«è·³è¿‡ï¼›" + ], + "otherConditions": "å…¶ä»–æ¡ä»¶", + "shareLinkExisted": "存在分享链接", + "directLinkExisted": "存在中转直链", + "isUploading": "上传中" + }, + "entity": { + "refenenceCount": "引用次数", + "waitForRecycle": "等待回收", + "entityDialogTitle": "Blob 详情", + "uploadSessionID": "ä¸Šä¼ ä¼šè¯ ID", + "referredFiles": "å…³è”æ–‡ä»¶", + "confirmBatchDelete": "确认è¦åˆ é™¤ {{num}} 个 Blob?", + "deleteXEntities": "删除 {{num}} 个 Blob", + "forceDelete": "强制删除", + "forceDeleteDes": "æ— è®ºç‰©ç†æ–‡ä»¶æ˜¯å¦åˆ é™¤æˆåŠŸï¼Œéƒ½ä¼šåˆ é™¤ Blob 记录。" + }, + "event": { + "cleanup": "清ç†", + "cleanupAuditLog": "清ç†äº‹ä»¶", + "cleanupAuditLogDescription": "删除满足以下æ¡ä»¶çš„æ‰€æœ‰äº‹ä»¶ï¼š", + "cleanupNotAfter": "在此日期之å‰", + "cleanupEventTypes": "事件类型", + "cleanupEventTypesDes": "é€‰æ‹©è¦æ¸…ç†çš„äº‹ä»¶ç±»åž‹ï¼Œç•™ç©ºè¡¨ç¤ºæ¸…ç†æ‰€æœ‰ç±»åž‹ã€‚", + "allEventTypes": "所有事件类型", + "initiator": "å‘起者", + "event": "事件", + "userID": "用户 ID", + "ip": "IP", + "type": "类型", + "correlationId": "请求 ID", + "fileID": "文件 ID", + "emailSend": "å‘é€é‚®ä»¶ “{{title}}†到 {{email}}", + "emailFailed": "邮件队列å¯åŠ¨å¤±è´¥", + "signinFailed": "登录失败: {{reason}}", + "createDavAccount": "创建 WebDAV 账户: {{account}}", + "updateDavAccount": "æ›´æ–° WebDAV 账户: {{account}}", + "deleteDavAccount": "删除 WebDAV 账户: {{account}}", + "pointsChange": "积分å˜åŒ–: {{points}}", + "storageAdded": "购买了 {{size}} 容é‡", + "nickChange": "昵称从 {{old}} 改为 {{new}}", + "eventDialogTitle": "事件详情", + "userAgent": "用户代ç†", + "linkedUser": "å…³è”用户", + "datetime": "æ—¶é—´", + "linkedFile": "å…³è”æ–‡ä»¶", + "linkedEntity": "å…³è” Blob", + "linkedShare": "å…³è”分享", + "rawContent": "原始记录", + "confirmDelete": "确认è¦åˆ é™¤è¿™ä¸ªäº‹ä»¶?", + "deleteXEvents": "删除 {{num}} 个事件", + "confirmBatchDelete": "确认è¦åˆ é™¤ {{num}} 个事件?" + }, + "share": { + "confirmBatchDelete": "确认è¦åˆ é™¤ {{num}} 个分享?", + "confirmDelete": "确认è¦åˆ é™¤è¿™ä¸ªåˆ†äº«?", + "deleteXShares": "删除 {{num}} 个分享", + "shareDialogTitle": "分享详情", + "shareLink": "分享链接", + "deleted": "文件已删除", + "srcFileName": "æºæ–‡ä»¶", + "views": "æµè§ˆ", + "downloads": "下载", + "price": "积分", + "autoExpire": "自动过期", + "owner": "分享者", + "createdAt": "分享于", + "private": "从个人主页éšè—", + "yes": "是", + "no": "å¦", + "afterNDownloads": "{{num}} 次下载åŽ", + "none": "æ— ", + "srcType": "æºæ–‡ä»¶ç±»åž‹", + "folder": "目录", + "file": "文件" + }, + "task": { + "cleanupTasks": "清ç†ä»»åŠ¡", + "cleanupTasksDescription": "æ¸…ç†æ»¡è¶³ä»¥ä¸‹æ¡ä»¶çš„æ‰€æœ‰ä»»åŠ¡ï¼š", + "cleanupNotAfter": "在此日期之å‰", + "cleanupTaskTypes": "任务类型", + "cleanupTaskTypesDes": "é€‰æ‹©è¦æ¸…ç†çš„ä»»åŠ¡ç±»åž‹ï¼Œç•™ç©ºè¡¨ç¤ºæ¸…ç†æ‰€æœ‰ç±»åž‹ã€‚", + "cleanupTaskStatuses": "任务状æ€", + "cleanupTaskStatusesDes": "é€‰æ‹©è¦æ¸…ç†çš„任务状æ€ï¼Œç•™ç©ºè¡¨ç¤ºæ¸…ç†æ‰€æœ‰å·²å®Œæˆçжæ€çš„任务。", + "confirmDelete": "确认è¦åˆ é™¤è¿™ä¸ªä»»åŠ¡?", + "confirmBatchDelete": "确认è¦åˆ é™¤ {{num}} 个任务?", + "deleteXTasks": "删除 {{num}} 个任务", + "blobID": "Blob ID", + "retryIndex": "é‡è¯•åºå·", + "entityError": "回收失败的 Blob", + "updatedAt": "更新于", + "taskDialogTitle": "任务详情", + "explicitEntityRecycle": "显å¼å›žæ”¶æ–‡ä»¶ Blob: {{blobs}}", + "entityRecycleRoutine": "定时扫æå›žæ”¶æ–‡ä»¶ Blob", + "mediaMetadata": "æå– Blob <0>#{{entityID}} 的媒体信æ¯", + "uploadSentinelCheck": "æ£€æŸ¥ä¸Šä¼ ä¼šè¯ {{uploadSessionID}} 状æ€", + "remoteDownload": "离线下载:", + "owner": "所有者", + "content": "内容", + "status": "状æ€", + "create_archive": "创建压缩文件", + "extract_archive": "解压文件", + "relocate": "转移存储策略", + "remote_download": "离线下载", + "media_meta": "åª’ä½“ä¿¡æ¯æå–", + "entity_recycle_routine": "Blob 扫æå›žæ”¶", + "explicit_entity_recycle": "æ˜¾å¼ Blob 回收", + "upload_sentinel_check": "上传哨兵检查", + "import": "外部导入", + "type": "类型", + "node": "处ç†èŠ‚ç‚¹", + "createdBy": "创建者", + "ready": "就绪", + "downloading": "下载中", + "paused": "æš‚åœä¸­", + "seeding": "åšç§ä¸­", + "error": "出错", + "finished": "完æˆ", + "canceled": "å–æ¶ˆ/åœæ­¢", + "unknown": "未知", + "errorMsg": "错误信æ¯" + }, + "payment": { + "tradeNo": "交易å•å·", + "productType": "商å“类型", + "providerID": "支付方å¼", + "status": "状æ€", + "deleteXPayments": "删除 {{num}} 个订å•" + }, + "customProps": { + "add": "添加", + "type": "类型", + "default": "默认值", + "actions": "æ“作", + "text": "文本", + "number": "æ•°å­—", + "boolean": "勾选", + "select": "å•选", + "multiSelect": "多选", + "user": "用户", + "link": "链接", + "rating": "评分", + "addProp": "添加属性", + "editProp": "编辑属性", + "icon": "图标", + "iconDes": "<0>Iconify 图标å称,留空表示ä¸å±•示图标。", + "id": "标识", + "idDes": "属性标识,需è¦ç¡®ä¿åœ¨æ‰€æœ‰å±žæ€§ä¸­å”¯ä¸€ã€‚", + "idPatternDes": "åªèƒ½åŒ…å«å­—æ¯ã€æ•°å­—ã€ä¸‹åˆ’线和中划线。", + "minLength": "最å°é•¿åº¦", + "maxLength": "最大长度", + "emptyLimit": "留空表示ä¸é™åˆ¶ã€‚", + "minValue": "最å°å€¼", + "maxValue": "最大值", + "options": "选项", + "optionsDes": "æ¯è¡Œä¸€ä¸ªé€‰é¡¹ã€‚" + }, + "vas": { + "disableSubAddressEmail": "ç¦ç”¨å­åœ°å€é‚®ç®±", + "disableSubAddressEmailDes": "å¼€å¯åŽï¼ŒåŒ…å«åŠ å· <0>+ çš„é‚®ç®±åœ°å€æ— æ³•注册账户。", + "confirmDelete": "确认è¦åˆ é™¤è¿™äº›è®¢å•?", + "vas": "增值æœåŠ¡", + "reports": "举报", + "orders": "订å•", + "initialFiles": "åˆå§‹æ–‡ä»¶", + "initialFilesDes": "指定用户注册åŽåˆå§‹æ‹¥æœ‰çš„æ–‡ä»¶ã€‚输入文件 ID æœç´¢å¹¶æ·»åŠ çŽ°æœ‰æ–‡ä»¶ã€‚", + "filterEmailProvider": "过滤注册邮箱域", + "filterEmailProviderDisabled": "ä¸å¯ç”¨", + "filterEmailProviderWhitelist": "白åå•", + "filterEmailProviderBlacklist": "黑åå•", + "filterEmailProviderDes": "åªå…许使用特定的邮箱注册站点,第三方 SSO 登录ä¸å—æ­¤é™åˆ¶ã€‚", + "filterEmailProviderRule": "邮箱域过滤规则", + "filterEmailProviderRuleDes": "多个域请使用åŠè§’逗å·éš”开。", + "qqConnect": "QQ 互è”", + "qqConnectHint": "在 <0>QQ 互è”å¼€æ”¾å¹³å° åˆ›å»ºåº”ç”¨æ—¶ï¼Œå›žè°ƒåœ°å€è¯·å¡«å†™ï¼š{{url}}。", + "enableQQConnect": "å¼€å¯ QQ 互è”", + "enableQQConnectDes": "是å¦å…许绑定 QQã€ä½¿ç”¨ QQ 登录本站", + "loginWithoutBinding": "未绑定时å¯ç›´æŽ¥ç™»å½•", + "loginWithoutBindingDes": "å¼€å¯åŽï¼Œå¦‚æžœç”¨æˆ·ä½¿ç”¨äº†ç¬¬ä¸‰æ–¹ç™»å½•ï¼Œä½†æ˜¯æ²¡æœ‰å·²ç»‘å®šçš„æ³¨å†Œç”¨æˆ·ï¼Œç³»ç»Ÿä¼šä¸ºå…¶åˆ›å»ºç”¨æˆ·å¹¶ç™»å½•ã€‚è¿™ç§æ–¹å¼åˆ›å»ºçš„用户日åŽåªèƒ½ä½¿ç”¨ç¬¬ä¸‰æ–¹ç™»å½•。", + "appid": "APP ID", + "appidDes": "应用管ç†é¡µé¢èŽ·å–到的 APP ID。", + "appKey": "APP KEY", + "appKeyDes": "应用管ç†é¡µé¢èŽ·å–到的 APP KEY。", + "overuseReminder": "è¶…é¢æé†’", + "overuseReminderDes": "用户因增值æœåŠ¡è¿‡æœŸï¼Œå®¹é‡è¶…出é™åˆ¶åŽå‘é€çš„æé†’邮件模æ¿", + "vasSetting": "支付/æ‚项设置", + "storagePack": "容é‡åŒ…", + "purchasableGroups": "å¯è´­ç”¨æˆ·ç»„", + "giftCodes": "å…‘æ¢ç ", + "enable": "å¼€å¯", + "appID": "APP ID", + "appIDDes": "当é¢ä»˜åº”用的 APPID。", + "rsaPrivate": "RSA 应用ç§é’¥", + "rsaPrivateDes": "当é¢ä»˜åº”用的 RSA2 (SHA256) ç§é’¥ï¼Œä¸€èˆ¬æ˜¯ç”±ä½ è‡ªå·±ç”Ÿæˆã€‚详情å‚考 <0>ç”Ÿæˆ RSA 密钥。", + "alipayPublicKey": "支付å®å…¬é’¥", + "alipayPublicKeyDes": "ç”±æ”¯ä»˜å®æä¾›ï¼Œå¯åœ¨ã€åº”用管ç†ã€‘-ã€åº”用信æ¯ã€‘-ã€æŽ¥å£åŠ ç­¾æ–¹å¼ã€‘中获å–。", + "wechatPay": "å¾®ä¿¡å®˜æ–¹æ‰«ç æ”¯ä»˜", + "applicationID": "应用 ID", + "applicationIDDes": "ç›´è¿žå•†æˆ·ç”³è¯·çš„å…¬ä¼—å·æˆ–移动应用 appid。", + "merchantID": "直连商户å·", + "merchantIDDes": "直连商户的商户å·ï¼Œç”±å¾®ä¿¡æ”¯ä»˜ç”Ÿæˆå¹¶ä¸‹å‘。", + "apiV3Secret": "API v3 密钥", + "apiV3SecretDes": "商户需先在ã€å•†æˆ·å¹³å°ã€‘-ã€API 安全】的页é¢è®¾ç½®è¯¥å¯†é’¥ï¼Œè¯·æ±‚æ‰èƒ½é€šè¿‡å¾®ä¿¡æ”¯ä»˜çš„ç­¾åæ ¡éªŒã€‚密钥的长度为 32 个字节。", + "mcCertificateSerial": "商户è¯ä¹¦åºåˆ—å·", + "mcCertificateSerialDes": "登录商户平å°ã€API 安全】-ã€API è¯ä¹¦ã€‘-ã€æŸ¥çœ‹è¯ä¹¦ã€‘ï¼Œå¯æŸ¥çœ‹å•†æˆ· API è¯ä¹¦åºåˆ—å·ã€‚", + "mcAPISecret": "商户 API ç§é’¥", + "mcAPISecretDes": "ç§é’¥æ–‡ä»¶ apiclient_key.pem 的内容。", + "payjs": "PAYJS 微信支付", + "payjsWarning": "æ­¤æœåŠ¡ç”±ç¬¬ä¸‰æ–¹å¹³å° <0>PAYJS æä¾›ï¼Œäº§ç”Ÿçš„任何纠纷与 Cloudreve å¼€å‘者无关。", + "mcNumber": "商户å·", + "mcNumberDes": "å¯åœ¨ PAYJS 管ç†é¢æ¿é¦–页看到", + "communicationSecret": "通信密钥", + "otherSettings": "æ‚项设置", + "banBufferPeriod": "å°ç¦ç¼“冲期 (ç§’)", + "banBufferPeriodDes": "ç”¨æˆ·ä¿æŒå®¹é‡è¶…é¢çжæ€çš„æœ€é•¿æ—¶é•¿ï¼Œè¶…出时长该用户会被系统冻结。", + "allowSellShares": "å…许为分享定价", + "allowSellSharesDes": "å¼€å¯åŽï¼Œç”¨æˆ·å¯ä¸ºåˆ†äº«è®¾å®šç§¯åˆ†ä»·æ ¼ï¼Œä¸‹è½½éœ€è¦æ‰£é™¤ç§¯åˆ†ã€‚", + "creditPriceRatio": "积分到账比率 (%)", + "creditPriceRatioDes": "购买下载设定价格的分享,分享者实际到账的积分比率。", + "creditPrice": "积分价格 (分)", + "creditPriceDes": "充值积分时的价格", + "add": "添加", + "name": "åç§°", + "price": "å•ä»·", + "duration": "æ—¶é•¿", + "size": "大å°", + "actions": "æ“作", + "orCredits": " 或 {{num}} 积分", + "highlight": "çªå‡ºå±•示", + "yes": "是", + "no": "å¦", + "productName": "商å“å", + "qyt": "æ•°é‡", + "code": "å…‘æ¢ç ", + "status": "状æ€", + "invalidProduct": "已失效商å“", + "used": "已使用", + "notUsed": "未使用", + "generatingResult": "生æˆç»“æžœ", + "addStoragePack": "添加容é‡åŒ…", + "editStoragePack": "编辑容é‡åŒ…", + "productNameDes": "商å“展示åç§°", + "packSizeDes": "容é‡åŒ…的大å°", + "durationDay": "有效期 (天)", + "durationDayDes": "æ¯ä¸ªå®¹é‡åŒ…的有效期", + "priceYuan": "å•ä»· (å…ƒ)", + "packPriceDes": "容é‡åŒ…çš„å•ä»·", + "priceCredits": "å•ä»· (积分)", + "priceCreditsDes": "使用积分购买时的价格,填写为 0 表示ä¸èƒ½ä½¿ç”¨ç§¯åˆ†è´­ä¹°", + "editMembership": "编辑å¯è´­ç”¨æˆ·ç»„", + "addMembership": "添加å¯è´­ç”¨æˆ·ç»„", + "group": "用户组", + "groupDes": "è´­ä¹°åŽå‡çº§çš„用户组", + "durationGroupDes": "è´­ä¹°åŽå‡çº§çš„用户组å•ä½è´­ä¹°æ—¶é—´çš„æœ‰æ•ˆæœŸ", + "groupPriceDes": "用户组的å•ä»·", + "productDescription": "å•†å“æè¿° (一行一个)", + "productDescriptionDes": "购买页é¢å±•ç¤ºçš„å•†å“æè¿°", + "highlightDes": "å¼€å¯åŽï¼Œåœ¨å•†å“选择页é¢ä¼šè¢«çªå‡ºå±•示", + "generateGiftCode": "生æˆå…‘æ¢ç ", + "numberOfCodes": "ç”Ÿæˆæ•°é‡", + "numberOfCodesDes": "æ¿€æ´»ç æ‰¹é‡ç”Ÿæˆæ•°é‡", + "linkedProduct": "对应商å“", + "productQyt": "商哿•°é‡", + "productQytDes": "对于积分类商å“,此处为积分数é‡ï¼Œå…¶ä»–商å“ä¸ºæ—¶é•¿å€æ•°", + "freeDownload": "å…积分下载分享", + "freeDownloadDes": "å¼€å¯åŽï¼Œç”¨æˆ·å¯ä»¥å…费下载需付积分的分享", + "credits": "积分", + "markSuccessful": "标记æˆåŠŸ", + "markAsResolved": "标记为已处ç†", + "reportedContent": "举报对象", + "reason": "原因", + "description": "补充æè¿°", + "reportTime": "举报时间", + "invalid": "[已失效]", + "deleteShare": "删除分享", + "orderDeleted": "订å•记录已删除", + "orderName": "订å•å", + "product": "商å“", + "paymentId": "è®¢å• ID", + "orderNumber": "订å•å·", + "paidBy": "支付方å¼", + "orderOwner": "创建者", + "amount": "金é¢", + "unpaid": "未支付", + "paid": "已支付", + "shareLink": "分享链接", + "mobileApp": "移动客户端", + "showAppPromotion": "展示客户端引导页é¢", + "showAppPromotionDes": "å¼€å¯åŽï¼Œç”¨æˆ·å¯ä»¥åœ¨ “连接与挂载†页é¢ä¸­çœ‹åˆ°ç§»åŠ¨å®¢æˆ·ç«¯çš„ä½¿ç”¨å¼•å¯¼ã€‚", + "customPaymentName": "付款方å¼åç§°", + "customPaymentNameDes": "用于展示给用户的付款方å¼åç§°", + "customPaymentSecretDes": "Cloudreve 用于签å付款请求的密钥。", + "customPaymentEndpoint": "支付接å£åœ°å€", + "customPaymentEndpointDes": "åˆ›å»ºæ”¯ä»˜è®¢å•æ—¶è¯·æ±‚çš„æŽ¥å£ URL。", + "appFeedback": "åé¦ˆé¡µé¢ URL", + "appForum": "ç”¨æˆ·è®ºå› URL", + "appLinkDes": "用于在 App 设置页é¢å±•示,留空å³ä¸å±•示链接按钮,仅当 VOL æŽˆæƒæœ‰æ•ˆæ—¶æ­¤é¡¹è®¾ç½®æ‰ä¼šç”Ÿæ•ˆã€‚" + }, + "pro": { + "title": "Pro 版本专属功能", + "description": "您å°è¯•访问的功能仅在 Cloudreve Pro 版本中å¯ç”¨ï¼Œå‡çº§ä»¥è§£é”所有高级功能。", + "proInclude": "Pro 版本包å«ï¼š", + "shareLinkCollabration": "分享链接ååŒç¼–辑", + "filePermission": "文件æƒé™ç®¡ç†", + "multipleStoragePolicy": "多存储策略和目录存储策略切æ¢", + "auditAndActivity": "文件和系统活动日志", + "vasService": "增值æœåŠ¡å’Œç§¯åˆ†ç³»ç»Ÿ", + "sso": "SSO å•点登录", + "more": "......", + "later": "ç¨åŽå†è¯´", + "learnMore": "了解 Pro 版本详情", + "promotionTitle": "社区版å‡çº§ç‰¹åˆ«ä¼˜æƒ ", + "promotion": "è´­ä¹°æ—¶ä½¿ç”¨ä¼˜æƒ ç  <0>{{code}},获得 <1>-{{discount}}% 折扣。" + }, + "abuseReport": { + "deleteXAbuseReports": "删除 {{num}} 个举报", + "folderPath": "目录路径", + "reporter": "举报者", + "shareLink": "分享链接 <0>#{{id}}", + "deletedShare": "已删除的分享链接", + "deletedUser": "已删除的用户", + "confirmDelete": "确认è¦åˆ é™¤è¿™ä¸ªä¸¾æŠ¥è®°å½•?", + "confirmBatchDelete": "确认è¦åˆ é™¤ {{num}} 个举报记录?", + "reporterID": "举报者用户 ID", + "reportedUserID": "举报对象用户 ID", + "shareID": "分享 ID", + "reason": "原因" + } +} diff --git a/public/locales/zh-CN/image_editor.json b/public/locales/zh-CN/image_editor.json new file mode 100755 index 0000000..1aba4c6 --- /dev/null +++ b/public/locales/zh-CN/image_editor.json @@ -0,0 +1,113 @@ +{ + "name": "åç§°", + "save": "ä¿å­˜", + "saveAs": "å¦å­˜ä¸º", + "back": "åŽé€€", + "loading": "加载中...", + "resetOperations": "é‡ç½®/删除所有æ“作", + "changesLoseWarningHint": "如果您按下“é‡ç½®â€æŒ‰é’®ï¼Œæ‚¨çš„æ›´æ”¹å°†ä¸¢å¤±ã€‚确定è¦ç»§ç»­å—?", + "discardChangesWarningHint": "如果关闭窗å£ï¼Œæ‚¨çš„æœ€åŽæ›´æ”¹å°†ä¸ä¼šè¢«ä¿å­˜ã€‚", + "cancel": "å–æ¶ˆ", + "apply": "应用", + "warning": "警告", + "confirm": "确认", + "discardChanges": "放弃更改", + "undoTitle": "撤消上次æ“作", + "redoTitle": "é‡åšä¸Šæ¬¡æ“作", + "showImageTitle": "显示原始图åƒ", + "zoomInTitle": "放大", + "zoomOutTitle": "缩å°", + "toggleZoomMenuTitle": "切æ¢ç¼©æ”¾èœå•", + "adjustTab": "调整", + "finetuneTab": "微调", + "filtersTab": "滤镜", + "watermarkTab": "æ°´å°", + "annotateTabLabel": "注释", + "resize": "调整大å°", + "resizeTab": "调整大å°", + "imageName": "图片åç§°", + "invalidImageError": "æä¾›çš„å›¾åƒæ— æ•ˆã€‚", + "uploadImageError": "ä¸Šä¼ å›¾åƒæ—¶å‡ºé”™ã€‚", + "areNotImages": "䏿˜¯å›¾åƒ", + "isNotImage": "䏿˜¯å›¾åƒ", + "toBeUploaded": "待上传", + "cropTool": "è£å‰ª", + "original": "原始", + "custom": "自定义", + "square": "正方形", + "landscape": "横å‘", + "portrait": "纵å‘", + "ellipse": "椭圆", + "classicTv": "ç»å…¸ç”µè§†", + "cinemascope": "宽银幕电影", + "arrowTool": "箭头", + "blurTool": "模糊", + "brightnessTool": "亮度", + "contrastTool": "对比度", + "ellipseTool": "椭圆", + "unFlipX": "å–æ¶ˆç¿»è½¬ X", + "flipX": "翻转 X", + "unFlipY": "å–æ¶ˆç¿»è½¬ Y", + "flipY": "翻转 Y", + "hsvTool": "HSV", + "hue": "色调", + "brightness": "亮度", + "saturation": "饱和度", + "value": "数值", + "imageTool": "图åƒ", + "importing": "导入...", + "addImage": "+ 添加图片", + "uploadImage": "上传图片", + "fromGallery": "æ¥è‡ªç”»å»Š", + "lineTool": "线", + "penTool": "画笔", + "polygonTool": "多边形", + "sides": "ä¾§é¢", + "rectangleTool": "长方形", + "cornerRadius": "æ‹è§’åŠå¾„", + "resizeWidthTitle": "宽度(以åƒç´ ä¸ºå•ä½ï¼‰", + "resizeHeightTitle": "高度(以åƒç´ ä¸ºå•ä½ï¼‰", + "toggleRatioLockTitle": "åˆ‡æ¢æ¯”率é”定", + "resetSize": "é‡ç½®ä¸ºåŽŸå§‹å›¾åƒå¤§å°", + "rotateTool": "旋转", + "textTool": "文本", + "textSpacings": "文字间è·", + "textAlignment": "文本对é½", + "fontFamily": "字体", + "size": "尺寸", + "letterSpacing": "å­—æ¯é—´è·", + "lineHeight": "线高", + "warmthTool": "色温", + "addWatermark": "+ 添加水å°", + "addTextWatermark": "+ 添加文字水å°", + "addWatermarkTitle": "选择水å°ç±»åž‹", + "uploadWatermark": "上传水å°", + "addWatermarkAsText": "添加为文本", + "padding": "内间è·", + "paddings": "内间è·", + "shadow": "阴影", + "horizontal": "æ°´å¹³", + "vertical": "垂直", + "blur": "模糊", + "opacity": "ä¸é€æ˜Žåº¦", + "transparency": "逿˜Žåº¦", + "position": "ä½ç½®", + "stroke": "线æ¡", + "saveAsModalTitle": "å¦å­˜ä¸º", + "extension": "扩展", + "format": "æ ¼å¼", + "nameIsRequired": "文件å为必填项。", + "quality": "è´¨é‡", + "imageDimensionsHoverTitle": "ä¿å­˜çš„图åƒå°ºå¯¸ï¼ˆå®½x高)", + "cropSizeLowerThanResizedWarning": "请注æ„,所选的è£å‰ªåŒºåŸŸä½ŽäºŽåº”用的调整大å°ï¼Œè¿™å¯èƒ½ä¼šå¯¼è‡´è´¨é‡ä¸‹é™", + "actualSize": "实际尺寸(100%)", + "fitSize": "适åˆå°ºå¯¸", + "addImageTitle": "é€‰æ‹©è¦æ·»åŠ çš„å›¾åƒ...", + "mutualizedFailedToLoadImg": "加载图åƒå¤±è´¥ã€‚", + "tabsMenu": "èœå•", + "download": "下载", + "width": "宽度", + "height": "高度", + "plus": "+", + "cropItemNoEffect": "æ­¤è£å‰ªé¡¹ç›®æ²¡æœ‰å¯ç”¨çš„预览" +} \ No newline at end of file diff --git a/public/locales/zh-CN/markdown_editor.json b/public/locales/zh-CN/markdown_editor.json new file mode 100755 index 0000000..9d15f98 --- /dev/null +++ b/public/locales/zh-CN/markdown_editor.json @@ -0,0 +1,114 @@ +{ + "frontmatterEditor": { + "title": "编辑å‰ç½®å…ƒæ•°æ®", + "key": "é”®", + "value": "值", + "addEntry": "添加项目" + }, + "dialogControls": { + "save": "ä¿å­˜", + "cancel": "å–æ¶ˆ" + }, + "uploadImage": { + "dialogTitle": "上传图片", + "uploadInstructions": "从您的设备中上传图片:", + "addViaUrlInstructions": "或填写图片 URL / ç›¸å¯¹è·¯å¾„ï¼ˆç›¸å¯¹äºŽå½“å‰æ–‡ä»¶ï¼‰ï¼š", + "autoCompletePlaceholder": "选择或粘贴图片 URL", + "addViaUrlInstructionsNoUpload": "图片 URL:", + "alt": "替代文本:", + "title": "标题:" + }, + "imageEditor": { + "deleteImage": "删除图片", + "editImage": "编辑图片" + }, + "createLink": { + "url": "网å€", + "urlPlaceholder": "选择或粘贴网å€", + "title": "标题", + "saveTooltip": "设置网å€", + "cancelTooltip": "å–æ¶ˆæ›´æ”¹" + }, + "linkPreview": { + "open": "在新窗å£ä¸­æ‰“å¼€ {{url}}", + "edit": "编辑链接", + "copyToClipboard": "å¤åˆ¶åˆ°å‰ªè´´æ¿", + "copied": "å·²å¤åˆ¶ï¼", + "remove": "移除链接" + }, + "table": { + "deleteTable": "删除表格", + "columnMenu": "列èœå•", + "textAlignment": "文字对é½", + "alignLeft": "左对é½", + "alignCenter": "居中对é½", + "alignRight": "å³å¯¹é½", + "insertColumnLeft": "在当å‰åˆ—左侧æ’入一列", + "insertColumnRight": "在当å‰åˆ—å³ä¾§æ’入一列", + "deleteColumn": "删除此列", + "rowMenu": "行èœå•", + "insertRowAbove": "在当å‰è¡Œä¸Šæ–¹æ’入一行", + "insertRowBelow": "在当å‰è¡Œä¸‹æ–¹æ’入一行", + "deleteRow": "删除此行" + }, + "toolbar": { + "blockTypes": { + "paragraph": "段è½", + "quote": "引用", + "heading": "标题 {{level}}" + }, + "blockTypeSelect": { + "selectBlockTypeTooltip": "选择å—类型", + "placeholder": "å—类型" + }, + "toggleGroup": "切æ¢ç»„", + "removeBold": "移除粗体", + "bold": "粗体", + "removeItalic": "移除斜体", + "italic": "斜体", + "underline": "移除下划线", + "removeUnderline": "下划线", + "removeInlineCode": "移除内è”ä»£ç æ ·å¼", + "inlineCode": "内è”ä»£ç æ ·å¼", + "link": "创建链接", + "richText": "富文本", + "diffMode": "差异模å¼", + "source": "æºç æ¨¡å¼", + "admonition": "æ’入注释区å—", + "codeBlock": "æ’入代ç å—", + "editFrontmatter": "编辑å‰ç½®å…ƒæ•°æ®", + "insertFrontmatter": "æ’å…¥å‰ç½®å…ƒæ•°æ®", + "image": "æ’入图片", + "insertSandpack": "æ’å…¥ Sandpack", + "table": "æ’入表格", + "thematicBreak": "æ’入主题æ¢è¡Œ", + "bulletedList": "æ— åºåˆ—表", + "numberedList": "有åºåˆ—表", + "checkList": "任务列表", + "deleteSandpack": "删除 Sandpack", + "undo": "撤销 {{shortcut}}", + "redo": "é‡åš {{shortcut}}", + "superscript": "上标", + "subscript": "下标", + "strikethrough": "删除线", + "removeSubscript": "移除下标", + "removeSuperscript": "移除上标", + "removeStrikethrough": "移除删除线" + }, + "admonitions": { + "note": "注æ„", + "tip": "æç¤º", + "danger": "å±é™©", + "info": "ä¿¡æ¯", + "caution": "警告", + "changeType": "选择注释区å—类型", + "placeholder": "注释区å—类型" + }, + "codeBlock": { + "language": "代ç å—语言", + "selectLanguage": "选择代ç å—语言" + }, + "contentArea": { + "editableMarkdown": "å¯ç¼–辑的 Markdown" + } +} \ No newline at end of file diff --git a/public/locales/zh-TW/application.json b/public/locales/zh-TW/application.json new file mode 100755 index 0000000..1aac3d0 --- /dev/null +++ b/public/locales/zh-TW/application.json @@ -0,0 +1,919 @@ +{ + "login": { + "lastStep": "最後一步", + "siginToYourAccount": "登入你的賬號", + "createNewAccount": "建立新賬號", + "enterPassword": "請輸入密碼", + "enterPasswordHint": "請輸入賬號 {{email}} å°æ‡‰çš„密碼", + "paswordlessHint": "賬號 {{email}} ç‚ºç„¡å¯†ç¢¼è³¬æˆ¶ï¼Œè«‹é¸æ“‡ä¸‹åˆ—æ–¹å¼èªè­‰ï¼š", + "noAccountSignupNow": "還沒有賬號?<0>ç«‹å³æ³¨å†Š", + "haveAccountSignInNow": "已由賬號?<0>ç«‹å³ç™»å…¥", + "privacyPolicy": "éš±ç§æ”¿ç­–", + "termOfUse": "ä½¿ç”¨æ¢æ¬¾", + "signupHint": "你輸入的賬戶 {{email}} ä¸å­˜åœ¨ï¼Œæ˜¯å¦ç«‹å³æ³¨å†Šï¼Ÿ", + "accountNotFoundHint": "你輸入的賬戶 {{email}} ä¸å­˜åœ¨ã€‚", + "or": "或者", + "selectAccountToUse": "鏿“‡è¦ä½¿ç”¨çš„賬號", + "useOtherAccount": "使用其他賬號", + "email": "é›»å­éƒµç®±", + "password": "密碼", + "captcha": "驗證碼", + "captchaError": "驗證碼載入失敗: {{message}}", + "signIn": "登入", + "signUp": "注冊", + "signUpAccount": "注冊賬號", + "useFIDO2": "使用通行金鑰器登入", + "usePassword": "使用密碼登入", + "forgetPassword": "忘記密碼?", + "2FA": "二步驗證", + "input2FACode": "請輸入 6 ä½äºŒæ­¥é©—證程å¼ç¢¼", + "passwordNotMatch": "兩次密碼輸入ä¸ä¸€è‡´", + "findMyPassword": "找回密碼", + "passwordReset": "密碼已é‡è¨­", + "newPassword": "新密碼", + "repeatNewPassword": "é‡è¤‡æ–°å¯†ç¢¼", + "repeatPassword": "é‡è¤‡å¯†ç¢¼", + "resetPassword": "é‡è¨­å¯†ç¢¼", + "backToSingIn": "返回登入", + "sendMeAnEmail": "傳é€å¯†ç¢¼é‡ç½®éƒµä»¶", + "resetEmailSent": "密碼é‡ç½®éƒµä»¶å·²å‚³é€ï¼Œè«‹æ³¨æ„查收", + "browserNotSupport": "ç•¶å‰ç€è¦½å™¨æˆ–ç’°å¢ƒä¸æ”¯æ´", + "success": "登入æˆåŠŸ", + "signUpSuccess": "注冊æˆåŠŸ", + "activateSuccess": "啟用æˆåŠŸ", + "accountActivated": "您的賬號已被æˆåŠŸå•Ÿç”¨", + "title": "登入 {{title}}", + "sinUpTitle": "注冊 {{title}}", + "activateTitle": "郵件啟用", + "activateDescription": "一å°å•Ÿç”¨éƒµä»¶å·²ç¶“發é€è‡³æ‚¨çš„郵箱,請訪å•郵件中的連çµä»¥ç¹¼çºŒå®Œæˆæ³¨å†Šã€‚", + "continue": "下一步", + "back": "上一步", + "logout": "退出登入", + "signingOut": "正在退出登入...", + "loggedOut": "您已退出登入", + "clickToRefresh": "點é¸é‡æ–°æ•´ç†é©—證碼", + "switchLanguage": "切æ›èªžè¨€" + }, + "navbar": { + "notBefore": "䏿—©æ–¼", + "notAfter": "䏿™šæ–¼", + "minimum": "最å°", + "resetThumbnail": "é‡è¨­å¤±æ•—的縮圖", + "resetThumbnailRequested": "已請求é‡è¨­ç¸®åœ–。", + "noFileCanResetThumbnail": "沒有å¯é‡è¨­ç¸®åœ–的檔案。", + "maximum": "最大", + "fileSize": "檔案大å°", + "searchBase": "æœå°‹è·¯å¾‘", + "searchInBase": "æœå°‹ <0>", + "conditionDuplicate": "æ¢ä»¶å·²å­˜åœ¨", + "fileType": "檔案型別", + "addCondition": "新增æ¢ä»¶", + "notNameOpOr": "需包å«å…¨éƒ¨é—œéµè©ž", + "caseFolding": "忽略大å°å¯«", + "keywords": "é—œéµå­—", + "fileNameKeywordsHelp": "è¼¸å…¥å¾ŒæŒ‰å›žè»Šéµæ–°å¢žé—œéµå­—", + "advancedSearch": "高階æœå°‹", + "searchFilesTitle": "æœå°‹æª”案", + "searchIn": "æœå°‹ <0>{{keywords}}", + "recentlyViewed": "最近ç€è¦½", + "searchFiles": "æœå°‹æª”案...", + "showMore": "更多", + "myFiles": "我的檔案", + "hisFiles": "他的檔案", + "trash": "回收站", + "sharedWithMe": "與我共享", + "myShare": "我的分享", + "remoteDownload": "離線下載", + "connect": "連線與掛載", + "taskQueue": "後臺任務", + "setting": "設定", + "videos": "視訊", + "photos": "圖片", + "music": "音樂", + "documents": "文件", + "addATag": "新增標籤...", + "addTagDialog": { + "selectFolder": "鏿“‡ç›®éŒ„", + "fileSelector": "檔案分類", + "folderLink": "ç›®éŒ„å¿«æ·æ–¹å¼", + "tagName": "標籤å", + "matchPattern": "檔å匹é…è¦å‰‡", + "matchPatternDescription": "ä½ å¯ä»¥ä½¿ç”¨ <0>* 作為è¬ç”¨å­—元。比如 <1>*.png è¡¨ç¤ºåŒ¹é… png æ ¼å¼å½±è±¡ã€‚多行è¦å‰‡é–“會以 “或†的關系進行é‹ç®—。", + "icon": "圖示:", + "color": "é¡è‰²ï¼š", + "folderPath": "目錄路徑" + }, + "storage": "儲存空間", + "storageDetail": "已使用 {{used}}, å…± {{total}}", + "notLoginIn": "未登入", + "visitor": "éŠå®¢", + "objectsSelected": "{{num}} 個物件", + "searchPlaceholder": "按下 <0>/ é–‹å§‹æœå°‹", + "backToHomepage": "返回主é ", + "darkModeSwitch": "設定黑暗模å¼", + "toDarkMode": "黑暗", + "toLightMode": "淺色", + "myProfile": "個人主é ", + "dashboard": "管ç†é¢æ¿" + }, + "fileManager": { + "customProps": "自定義屬性", + "rating": "è©•ç´š", + "description": "æè¿°", + "add": "新增", + "clickToEdit": "點擊編輯...", + "clickToEditSelect": "é»žæ“Šé¸æ“‡...", + "enterUrl": "輸入 URL...", + "searchUser": "æœå°‹ç”¨æˆ¶...", + "typeToSearch": "輸入暱稱或郵箱...", + "searchProperty": "æœå°‹ç›¸åŒå±¬æ€§çš„æª”案", + "permissions": "權é™", + "quality": "清晰度", + "audioTrack": "音軌", + "auto": "自動", + "default": "é è¨­", + "shareWithMeEmpty": "沒有找到別人的分享", + "shareWithMeEmptyDes": "如需è¦åœ¨æ­¤çœ‹åˆ°åˆ¥äººçš„分享,請在訪å•åˆ¥äººåˆ†äº«é€£çµæ™‚,在å³ä¸Šè§’å°‡å¿«æ·æ–¹å¼ä¿å­˜åˆ°ä½ çš„æ–‡ä»¶ä¸­çš„ä»»æ„ä½ç½®ã€‚", + "selectAll": "å…¨é¸", + "selectNone": "å–æ¶ˆé¸æ“‡", + "invertSelection": "åé¸", + "imageSize": "圖片尺寸", + "focalLength": "焦è·", + "columnExisted": "列已存在", + "metadataColumn": "元資料 ({{metadata}})", + "column": "列", + "listColumnSetting": "列設定", + "addColumn": "新增列", + "failedLoadPreview": "é è¦½è¼‰å…¥å¤±æ•—", + "recursiveLimitReached": "æœå°‹æ·±åº¦é”到上é™", + "recursiveLimitReachedDes": "ç³»çµ±å·²åœæ­¢æœå°‹æ›´æ·±å±¤çš„ç›®éŒ„ï¼Œè«‹å˜—è©¦ç¸®å°æœå°‹ç›®éŒ„的範åœã€‚", + "searchConditions": "{{num}} 個æ¢ä»¶", + "createDate": "建立日期", + "updatedDate": "修改日期", + "cameraMake": "相機制造商", + "cameraModel": "相機型號", + "lensModel": "é¡é ­åž‹è™Ÿ", + "lensMake": "é¡é ­åˆ¶é€ å•†", + "metadataKey": "éµ", + "metadataValue": "值", + "metadata": "元資料", + "symbolicFile": "å¿«æ·æ–¹å¼", + "relocation": "轉移儲存策略", + "downloadingFile": "正在下載 “{{name}}â€ï¼Œ è«‹ä¸è¦é—œé–‰æœ¬é é¢...", + "mountOwner": "åªæœ‰ç•¶å‰ç›®éŒ„的所有者å¯ä»¥æŽ›è¼‰ç­–ç•¥", + "uploading": "上傳中", + "noActionsCanBeDone": "沒有å¯ä»¥é€²è¡Œçš„æ“ä½œ", + "newFileName": "新檔案.{{ext}}", + "newDocumentType": "{{display_name}} (.{{ext}})", + "text": "文字", + "diagram": "圖表", + "whiteboard": "白æ¿", + "selectApplications": "鏿“‡æ‡‰ç”¨...", + "newlyCreatedFolder": "新建資料夾", + "expandAllApp": "展開所有應用", + "epubViewer": "ePub 閱讀器", + "googledocs": "Google Docs 線上閱讀器", + "m365viewer": "Microsoft Office 線上閱讀器", + "pdfViewer": "PDF 閱讀器", + "archivePreview": "壓縮包é è¦½", + "extractSelected": "解壓縮é¸ä¸­çš„æª”案", + "viewerFileSizeWarning": "é–‹å•Ÿçš„æª”æ¡ˆå¤§å° ({{file_size}}) è¶…éŽäº† {{app}} çš„é™åˆ¶ ({{max}}),å¯èƒ½ç„¡æ³•正常工作。", + "testSubtitleStyle": "æ¸¬è©¦å­—å¹•æ¨£å¼ AaBbCc", + "color": "é¡è‰²", + "fontSize": "字型大å°", + "disableSubtitle": "ç¦ç”¨å­—幕", + "noSubtitle": "沒有在當å‰ç›®éŒ„下找到 ASS/SRT/VTT 字幕檔案。", + "subtitleStyles": "字幕樣å¼", + "subtitles": "字幕", + "markdownEditor": "Markdown 編輯器", + "saveSuccess": "在 {{time}} 儲存æˆåŠŸ", + "drawioLng": "zh", + "charset": "編碼", + "textType": "文字型別", + "fileSaved": "檔案已儲存", + "failedToLoadFile": "檔案載入失敗: {{msg}}", + "monacoEditor": "Monaco 程å¼ç¢¼ç·¨è¼¯å™¨", + "preparingOpenFile": "正在準備開啟檔案...", + "openWithDescription": "鏿“‡ä¸€å€‹æ‡‰ç”¨é–‹å•Ÿ .{{ext}} 檔案。", + "openWith": "開啟方å¼", + "readOnly": "åªè®€", + "save": "儲存", + "noMoreImages": "ç•¶å‰é é¢ç„¡å¯ç€è¦½å½±è±¡", + "imageViewer": "圖片檢視器", + "logFileDeleteShare": "刪除分享連çµ", + "logFileEditShare": "編輯分享連çµ", + "deleteShareWarning": "確定è¦åˆªé™¤æ­¤åˆ†äº«é€£çµå—Žï¼Ÿ", + "edit": "編輯", + "editAndReactivate": "ç·¨è¼¯ä¸¦é‡æ–°å•Ÿç”¨", + "yes": "是", + "no": "å¦", + "permanentValid": "永久有效", + "manageShares": "管ç†åˆ†äº«é€£çµ", + "manageDirectLinks": "管ç†ç›´éˆ", + "deleteLinkConfirm": "確定è¦åˆªé™¤æ­¤ç›´éˆå—Žï¼Ÿ", + "directLinkNotFound": "你查找的直éˆå·²ç¶“ä¸å­˜åœ¨ã€‚", + "versionNotFound": "你查找的版本已經ä¸å­˜åœ¨ã€‚", + "deleteVersionWarning": "確定è¦åˆªé™¤æ­¤ç‰ˆæœ¬å—Žï¼Ÿæ­¤æ“作無法撤銷。", + "setAsCurrent": "設為當å‰ç‰ˆæœ¬", + "current": "[ç•¶å‰ç‰ˆæœ¬]", + "createdBy": "建立者", + "manageVersions": "管ç†ç‰ˆæœ¬", + "livePhoto": "Live Photo", + "version": "版本", + "actions": "æ“作", + "versionEntity": "檔案資料和歷å²ç‰ˆæœ¬", + "data": "資料", + "owned": "æ“æœ‰æ­¤æª”案", + "ownedSymbolic": "æ“æœ‰æ­¤å¿«æ·æ–¹å¼", + "expires": "éŽæœŸæ™‚é–“", + "originalLocation": "原始ä½ç½®", + "descendant": "å­ç‰©ä»¶", + "folderChildren": "{{files}} 個檔案,{{folders}} 個資料夾", + "moreThan": "大於 {{text}}", + "calculate": "計算", + "unset": "未設定", + "folder": "資料夾", + "file": "檔案", + "symbolicLink": "å¿«æ·æ–¹å¼ ({{srcType}})", + "type": "型別", + "storageUsed": "佔用空間", + "location": "ä½ç½®", + "basicInfo": "基本資訊", + "format": "æ ¼å¼", + "duration": "時長", + "artist": "è—è¡“å®¶", + "album": "專輯", + "title": "標題", + "resolution": "è§£æžåº¦", + "takenAt": "æ‹æ”時間", + "software": "軟體", + "copyright": "作者", + "exposureBias": "æ›å…‰è£œå„Ÿ", + "flash": "閃光燈", + "copyToClipboard": "復制到剪下æ¿", + "searchSomething": "æœå°‹ \"{{text}}\"...", + "iso": "ISO", + "exposureValue": "{{num}} ç§’", + "exposure": "æ›å…‰", + "aperture": "光圈", + "address": "地å€", + "street": "è¡—é“", + "locality": "城市å€", + "place": "城市", + "district": "å€", + "region": "çœ", + "country": "國家", + "mediaInfo": "媒體資訊", + "details": "詳情", + "activity": "活動", + "goToSharedLink": "轉到分享連çµ", + "saveShortcut": "å„²å­˜åˆ†äº«ç‚ºå¿«æ·æ–¹å¼", + "customizeIcon": "自定義圖示", + "tags": "標籤", + "apply": "應用", + "customizeColor": "自定義é¡è‰²", + "folderColor": "資料夾é¡è‰²", + "restore": "還原", + "unpin": "å–æ¶ˆå›ºå®š", + "youDontHaveReadPermissionToThisFile": "ä½ æ²’æœ‰è¨±å¯æ¬Šè®€å–此內容", + "anonymousAccessDenied": "ä½ æ²’æœ‰è¨±å¯æ¬Šè®€å–此內容,請嘗試登入帳號。", + "sharedWithOthers": "與他人分享", + "new": "新建", + "open": "開啟", + "openParentFolder": "轉到所在目錄", + "download": "下載", + "batchDownload": "打包下載", + "share": "分享", + "rename": "釿–°å‘½å", + "organize": "æ•´ç†", + "pin": "固定到å´é‚Šæ¬„", + "pinAlias": "展示別å", + "optional": "å¯é¸", + "move": "移動", + "delete": "刪除", + "moreActions": "更多æ“作", + "refresh": "釿–°æ•´ç†", + "createArchive": "建立壓縮檔案", + "newFolder": "建立資料夾", + "newFile": "建立檔案", + "showFullPath": "顯示路徑", + "listView": "列表", + "gridView": "網格", + "galleryView": "畫廊", + "paginationSize": "分é å¤§å°", + "paginationOption": "{{option}} / é ", + "noPagination": "ä¸åˆ†é ", + "sortMethod": "排åº", + "sortMethods": { + "A-Z": "A-Z", + "Z-A": "Z-A", + "oldestUploaded": "最早上傳", + "newestUploaded": "最新上傳", + "oldestModified": "最早修改", + "newestModified": "最新修改", + "smallest": "最å°", + "largest": "最大" + }, + "shareCreateBy": "ç”± {{nick}} 建立", + "name": "å稱", + "size": "大å°", + "lastModified": "修改日期", + "currentFolder": "ç•¶å‰ç›®éŒ„", + "backToParentFolder": "上級目錄", + "folders": "資料夾", + "files": "檔案", + "listError": "請求時出ç¾éŒ¯èª¤", + "dropFileHere": "拖拽檔案至此", + "orClickUploadButton": "或點é¸å·¦ä¸Šæ–¹â€œæ–°å»ºâ€æŒ‰éˆ•新增檔案", + "nothingFound": "什麼都沒有找到", + "uploadFiles": "上傳檔案", + "uploadFolder": "上傳目錄", + "newRemoteDownloads": "離線下載", + "enter": "進入", + "getSourceLink": "ç²å–ç›´éˆ", + "createRemoteDownloadForTorrent": "建立離線下載任務", + "extractArchive": "解壓縮", + "createShareLink": "建立分享連çµ", + "viewDetails": "詳細資訊", + "copy": "復制", + "bytes": " ({{bytes}} ä½å…ƒçµ„)", + "storagePolicy": "儲存策略", + "childFolders": "包å«ç›®éŒ„", + "childFiles": "åŒ…å«æª”案", + "childCount": "{{num}} 個", + "parentFolder": "所在目錄", + "rootFolder": "根目錄", + "modifiedAt": "修改於", + "createdAt": "創建於", + "statisticAt": "統計於", + "musicPlayer": "音訊播放器", + "closeAndStop": "退出播放", + "playInBackground": "後臺播放", + "copyTo": "復制到", + "copyToDst": "復制到 <0>", + "moveTo": "移動到", + "moveToDst": "移動到 <0>", + "errorReadFileContent": "ç„¡æ³•è®€å–æª”案內容:{{msg}}", + "wordWrap": "自動æ›è¡Œ", + "pdfLoadingError": "PDF 載入失敗:{{msg}}", + "subtitleSwitchTo": "字幕切æ›åˆ°ï¼š{{subtitle}}", + "noSubtitleAvailable": "視訊目錄下沒有å¯ç”¨å­—幕檔案 (支æ´ï¼šASS/SRT/VTT)", + "subtitle": "鏿“‡å­—幕", + "playlist": "播放列表", + "openInExternalPlayer": "用外部播放器開啟", + "repeatMode": "循環模å¼", + "listRepeat": "清單循環", + "singleRepeat": "單曲循環", + "shuffle": "隨機播放", + "playbackSpeed": "播放速度", + "searchResult": "æœå°‹çµæžœ", + "preparingBathDownload": "正在準備打包下載...", + "preparingDownload": "正在準備下載...", + "browserDownload": "ç€è¦½å™¨ç«¯ä¸‹è¼‰åˆ°æœ¬åœ°ç›®éŒ„", + "browserDownloadDescription": "ç”±ç€è¦½å™¨é€ä¸€ä¸‹è¼‰æª”æ¡ˆçµæ§‹åˆ°ä½ æŒ‡å®šåˆ°æœ¬åœ°ç›®éŒ„。", + "browserBatchDownload": "ç€è¦½å™¨ç«¯æ‰“包", + "browserBatchDownloadDescription": "ç”±ç€è¦½å™¨å¯¦æ™‚下載並打包為 Zip 檔案,無法下載大於 4GB 的資料。", + "serverBatchDownload": "æœå‹™ç«¯ä¸­è½‰æ‰“包", + "serverBatchDownloadDescription": "ç”±æœå‹™ç«¯ä¸­è½‰æ‰“包為 Zip 檔案並實時傳é€åˆ°å®¢æˆ¶ç«¯ä¸‹è¼‰ï¼Œä¸æ”¯æ´åˆ†äº«å¿«æ·æ–¹å¼ã€‚", + "selectArchiveMethod": "鏿“‡æ‰¹é‡ä¸‹è¼‰æ–¹å¼", + "batchDownloadStarted": "打包下載已開始,請ä¸è¦é—œé–‰æ­¤é é¢...", + "batchDownloadError": "打包é‡åˆ°éŒ¯èª¤ï¼š{{msg}}", + "userDenied": "使用者拒絕", + "directoryDownloadReplace": "æ›¿æ›æ­¤æª”案", + "directoryDownloadReplaceDescription": "將會覆蓋本地的 “{{name}}â€", + "directoryDownloadSkip": "è·³éŽæ­¤æª”案", + "directoryDownloadSkipDescription": "將會跳éŽä¸‹è¼‰ “{{name}}â€", + "selectDirectoryDuplicationMethod": "檔案é‡å", + "directoryDownloadReplaceAll": "æ›¿æ›æ­¤æª”案和後續所有é‡å檔案", + "directoryDownloadReplaceAllDescription": "將會覆蓋本地的 “{{name}}â€ï¼Œä¸¦è¨˜ä½é¸æ“‡", + "directoryDownloadSkipAll": "è·³éŽæ­¤æª”案和後續所有é‡å檔案", + "directoryDownloadSkipAllDescription": "將會跳éŽä¸‹è¼‰ “{{name}}â€ï¼Œä¸¦è¨˜ä½é¸æ“‡", + "directoryDownloadStarted": "下載已開始,請ä¸è¦é—œé–‰æ­¤æ¨™ç±¤é ", + "directoryDownloadFinished": "下載完æˆï¼Œç„¡å¤±æ•—物件", + "directoryDownloadFinishedWithError": "下載完æˆ, 失敗 {{failed}} 個物件", + "directoryDownloadPermissionError": "ç„¡è¨±å¯æ¬Šæ“作,請å…許讀寫本地檔案", + "back": "後退", + "view": "檢視", + "layout": "布局", + "thumbnails": "縮圖", + "on": "開啟", + "off": "關閉", + "viewSetting": "檢視設定", + "saved": "å·²ä¿å­˜", + "notSet": "未設定", + "deleteViewSetting": "刪除檢視設定" + }, + "modals": { + "includePasswordInShareLink": "在連çµä¸­åŒ…å«å¯†ç¢¼", + "includePasswordInShareLinkDes": "勾é¸å¾Œï¼Œåˆ†äº«é€£çµä¸­æœƒåŒ…å«å¯†ç¢¼ï¼Œé€šéŽæ­¤é€£çµè¨ªå•時ä¸éœ€è¦å†è¼¸å…¥å¯†ç¢¼ã€‚", + "showFileName": "顯示檔å", + "forceDownload": "強制下載", + "archiveFile": "壓縮檔案", + "cancelDownload": "å–æ¶ˆä¸‹è¼‰", + "always": "始終", + "justOnce": "僅一次", + "quality": "質é‡", + "saveAsOtherFormat": "å¦å­˜ç‚ºå…¶ä»–æ ¼å¼", + "conflictDes1": "檔案版本發生è¡çªï¼Œå¯èƒ½çš„原因是:", + "conflictDes2": "<0>該檔案在你開啟後被從它處更新了新版本。<1>如果你å¦å­˜ç‚ºäº†æ–°æª”åæˆ–æ–°ä½ç½®ï¼Œå¯èƒ½å·²æœ‰åŒå檔案存在。", + "saveAs": "å¦å­˜ç‚º", + "versionConflict": "版本è¡çª", + "overwrite": "覆蓋", + "editShareLink": "編輯分享連çµ", + "clearPermissions": "æ¸…é™¤è¨±å¯æ¬Šè¨­å®š", + "shortcutCreated": "å¿«æ·æ–¹å¼å·²å»ºç«‹", + "createShortcut": "å»ºç«‹å¿«æ·æ–¹å¼", + "createShortcutTo": "在 <0> å»ºç«‹å¿«æ·æ–¹å¼", + "targetExisted": "目標已存在", + "users": "使用者", + "groups": "使用者組", + "noResults": "æ²’æœ‰çµæžœ", + "resetToDefault": "é‡ç½®ç‚ºé è¨­", + "duplicateTag": "標籤 \"{{tag}}\" 已存在", + "colorForTag": "自定義新標籤é¡è‰²", + "enterForNewTag": "æŒ‰å›žè»Šéµæ–°å¢žæ–°æ¨™ç±¤", + "manageTags": "ç®¡ç†æ¨™ç±¤", + "onlyOwner": "åªæœ‰æª”案所有者å¯ä»¥å¼·åˆ¶è§£éŽ–æ­¤æª”æ¡ˆ", + "forceUnlock": "強制解鎖", + "forceUnlockAll": "強制解鎖全部", + "forceUnlockDes": "強制解鎖å¯èƒ½æœƒå°Žè‡´æª”案狀態異常,推薦優先等待檔案被主動釋放。確定è¦ç¹¼çºŒè§£éŽ–å—Žï¼Ÿ", + "webdav": "WebDAV", + "soft-delete": "移至回收站", + "updateMetadata": "更新元資料", + "upload": "上傳", + "moveCopy": "移動或復制", + "view": "檢視", + "cannotPerformAction": "䏿”¯æ´ç§»å‹•或復制到此處", + "cannotMoveCopyToChild": "無法移動或復制到å­ç›®éŒ„", + "copySuccess": "æˆåŠŸå¾©åˆ¶ {{num}} 個檔案", + "moveSuccess": "æˆåŠŸç§»å‹• {{num}} 個檔案", + "unknownParent": "未知父目錄", + "unknownParentDes": "被佔用的目錄是共享目錄的父目錄,它ä¸å±¬æ–¼ä½ æ‰€æœ‰", + "lockConflictTitle": "檔案被佔用", + "lockConflictDescription": "æ“作無法完æˆï¼Œå› ç‚ºä¸‹åˆ—檔案正在被使用,請ç¨å¾Œé‡è©¦ã€‚ 如果你是檔案所有者,並且確定檔案沒有被使用,你å¯ä»¥å¼·åˆ¶è§£éŽ–æª”æ¡ˆä¸¦é‡è©¦ã€‚", + "application": "應用", + "errorDetailsTitle": "錯誤詳情", + "processingMoving": "正在移動檔案...", + "processingCopying": "正在復制檔案...", + "processingRestoring": "正在æ¢å¾©æª”案...", + "fileRestored": "å·²æ¢å¾© {{num}} 個檔案至原ä½", + "duplicatedObjectName": "æ–°å稱與已有檔案é‡è¤‡", + "newNameLengthError": "檔å長度必須在 1~255 個字元之間", + "newNameCharacterError": "檔åä¸èƒ½åŒ…å«ä»¥ä¸‹å­—元:\\ / : * ? \" < > |", + "newNameDotError": "檔åä¸èƒ½ç‚º \".\" 或 \"..\"", + "taskCreated": "任務已建立", + "taskCreateFailed": "{{failed}} 個任務建立失敗:{{details}}", + "linkCopied": "連çµå·²å¾©åˆ¶", + "getSourceLinkTitle": "ç²å–檔案直éˆ", + "sourceLink": "檔案直éˆ", + "folderName": "資料夾å稱", + "create": "建立", + "fileName": "檔å", + "renameDescription": "輸入 <0>{{name}} 的新å稱:", + "newName": "æ–°å稱", + "moveToDescription": "移動至 <0>{{name}}", + "saveToTitle": "儲存至", + "saveToTitleDescription": "儲存至 <0>{{name}}", + "deleteTitle": "刪除物件", + "deleteOneDescription": "確定è¦å°‡ <0>{{name}} 移至回收站嗎?", + "deleteMultipleDescription": "確定è¦å°‡é€™ {{num}} 個物件移至回收站嗎?", + "deleteOneDescriptionHard": "ç¢ºå®šè¦æ°¸ä¹…刪除 <0>{{name}} 嗎?", + "trashRetention": "回收站中的檔案會在 <0>{{num}} 後自動刪除。", + "deleteMultipleDescriptionHard": "ç¢ºå®šè¦æ°¸ä¹…刪除這 {{num}} 個物件嗎?", + "newRemoteDownloadTitle": "新建離線下載任務", + "remoteDownloadURL": "下載連çµ", + "remoteDownloadURLDescription": "輸入檔案下載地å€ï¼Œä¸€è¡Œä¸€å€‹", + "remoteDownloadDst": "下載至", + "processNode": "處ç†ç¯€é»ž", + "remoteDownloadNodeAuto": "自動分é…", + "createTask": "建立任務", + "downloadToDst": "下載至 <0>{{name}}", + "downloadTo": "下載至", + "decompressTo": "解壓縮至", + "decompressToDst": "解壓縮至 <0>{{name}}", + "defaultEncoding": "é è¨­", + "chineseMajorEncoding": "簡體中文常見編碼", + "selectEncoding": "ZIP 檔案編碼", + "password": "壓縮檔案密碼", + "passwordDescription": "如果壓縮檔案未加密,此處請留空。", + "noEncodingSelected": "æœªé¸æ“‡ç·¨ç¢¼æ–¹å¼", + "listingFiles": "åˆ—å–æª”案中...", + "listingFileError": "åˆ—å–æª”案時出錯:{{message}}", + "generatingSourceLinks": "生æˆå¤–éˆä¸­...", + "noFileCanGenerateSourceLink": "沒有å¯ä»¥ç”Ÿæˆå¤–éˆçš„æª”案", + "sourceBatchSizeExceeded": "ç•¶å‰ä½¿ç”¨è€…組最大å¯åŒæ™‚為 {{limit}} 個檔案生æˆå¤–éˆ", + "zipFileName": "壓縮檔å", + "shareLinkShareContent": "我å‘你分享了:{{name}} 連çµï¼š{{link}}", + "shareLinkPasswordInfo": " 密碼: {{password}}", + "createShareLink": "建立分享連çµ", + "privateShare": "使用密碼ä¿è­·é€£çµ", + "privateShareDes": "勾é¸å¾Œï¼Œéœ€è¦ä½¿ç”¨å¯†ç¢¼è¨ªå•分享連çµã€‚", + "useCustomPassword": "使用自定義密碼", + "expireAfterDownload": "ä¸‹è¼‰å¾Œè‡ªå‹•éŽæœŸ", + "sharePassword": "分享密碼", + "randomlyGenerate": "隨機生æˆ", + "expireAutomatically": "è¶…æ™‚è‡ªå‹•éŽæœŸ", + "downloadLimitOptions": "{{num}} 次下載", + "or": "或者", + "5minutes": "5 分é˜", + "1hour": "1 å°æ™‚", + "1day": "1 天", + "7days": "7 天", + "30days": "30 天", + "custom": "自定義", + "minutes": "分é˜", + "downloads": "次下載", + "expirePrefix": "", + "expireSuffix": "å¾ŒéŽæœŸ", + "allowPreview": "å…許é è¦½", + "allowPreviewDescription": "是å¦å…許在分享é é¢é è¦½æª”案內容", + "shareLink": "分享連çµ", + "sendLink": "傳é€é€£çµ", + "directoryDownloadReplaceNotifiction": "已覆蓋 {{name}}", + "directoryDownloadSkipNotifiction": "å·²è·³éŽ {{name}}", + "directoryDownloadTitle": "批é‡ä¸‹è¼‰æ—¥èªŒ", + "directoryDownloadStarted": "開始下載 “{{name}}â€", + "directoryDownloadFinished": "ä¸‹è¼‰å®Œæˆ â€œ{{name}}â€", + "directoryDownloadError": "é‡åˆ°éŒ¯èª¤ï¼š{{msg}}", + "directoryDownloadErrorNotification": "下載 {{name}} é‡åˆ°éŒ¯èª¤ï¼š{{msg}}", + "directoryDownloadAutoscroll": "自動滾動", + "directoryDownloadCancelled": "已喿¶ˆä¸‹è¼‰", + "advanceOptions": "高階é¸é …", + "skipSoftDelete": "徹底刪除檔案", + "skipSoftDeleteDes": "è·³éŽå›žæ”¶ç«™ï¼Œç›´æŽ¥åˆªé™¤æª”案", + "unlinkOnly": "ä¿ç•™ç‰©ç†æª”案", + "unlinkOnlyDes": "åƒ…åˆªé™¤æª”æ¡ˆè¨˜éŒ„ï¼Œç‰©ç†æª”æ¡ˆä¸æœƒè¢«åˆªé™¤", + "shareView": "分享視圖設定", + "shareViewDes": "勾é¸å¾Œï¼Œå…¶ä»–ä½¿ç”¨è€…å­˜å–æ­¤å…±äº«è³‡æ–™å¤¾æ™‚å¯ä»¥çœ‹åˆ°ä½ ä¿å­˜åœ¨æœå‹™å™¨çš„è¦–åœ–è¨­å®šï¼ˆä½ˆå±€ã€æŽ’åºç­‰ï¼‰ã€‚", + "showReadme": "顯示 README 文件", + "showReadmeDes": "勾é¸å¾Œï¼Œæœƒè‡ªå‹•為訪å•者展示目錄下的 <0>README.md (å€åˆ†å¤§å°å¯«) 文件。", + "viewSetting": "視圖設定", + "saved": "å·²ä¿å­˜", + "notSet": "未設定", + "deleteViewSetting": "刪除視圖設定" + }, + "uploader": { + "fileCopyName": "副本_", + "overwriteTooltip": "檔案é‡å時覆蓋已有檔案,åªé‡å°æ–°æ–°å¢žçš„任務有效", + "rename": "使用新檔åé‡è©¦", + "overwrite": "覆蓋已有檔案", + "pasteFilesHere": "將檔案貼上到此處", + "clipboardDefaultFileName": "剪貼簿 {{date}}.png", + "uploadFromClipboard": "從剪貼簿上傳", + "uploadList": "上傳列表", + "fileNotMatchError": "æ‰€é¸æ“‡æª”案與原始檔案ä¸ç¬¦", + "unknownError": "å‡ºç¾æœªçŸ¥éŒ¯èª¤ï¼š{{msg}}", + "taskListEmpty": "沒有上傳任務", + "hideTaskList": "éš±è—列表", + "uploadTasks": "上傳佇列", + "moreActions": "更多æ“作", + "addNewFiles": "新增新檔案", + "toggleTaskList": "展開/折疊佇列", + "pendingInQueue": "排隊中...", + "preparing": "準備中...", + "processing": "處ç†ä¸­...", + "progressDescription": "已上傳 {{uploaded}} , å…± {{total}} - {{percentage}}%", + "progressDescriptionFull": "{{speed}} 已上傳 {{uploaded}} , å…± {{total}} - {{percentage}}%", + "progressDescriptionPlaceHolder": "已上傳 - ", + "uploaded": "已上傳", + "rootFolder": "根目錄", + "unknownStatus": "未知", + "resumed": "斷點續傳", + "resumable": "坿¢å¾©é€²åº¦", + "retry": "é‡è©¦", + "deleteTask": "刪除任務記錄", + "cancelAndDelete": "å–æ¶ˆä¸¦åˆªé™¤", + "selectAndResume": "é¸å–åŒæ¨£æª”案並æ¢å¾©ä¸Šå‚³", + "fileName": "檔å:", + "fileSize": "檔案大å°ï¼š", + "sessionExpiredIn": "<0>éŽæœŸ", + "chunkDescription": "({{total}} 個分片, æ¯å€‹åˆ†ç‰‡ {{size}})", + "noChunks": "(無分片)", + "destination": "存放ä½ç½®ï¼š", + "storagePolicy": "儲存策略:", + "uploadSession": "上傳會話:", + "errorDetails": "錯誤資訊:", + "uploadSessionCleaned": "上傳會話已清除", + "hideCompletedTooltip": "列表中ä¸é¡¯ç¤ºå·²å®Œæˆã€å¤±æ•—ã€è¢«å–消的任務", + "hideCompleted": "éš±è—已完æˆä»»å‹™", + "addTimeAscTooltip": "最先新增的任務排在最å‰", + "addTimeAsc": "最先新增é å‰", + "addTimeDescTooltip": "最後新增的任務排在最å‰", + "addTimeDesc": "最後新增é å‰", + "showInstantSpeedTooltip": "單個任務上傳速度展示為瞬時速度", + "showInstantSpeed": "瞬時速度", + "showAvgSpeedTooltip": "單個任務上傳速度展示為平å‡é€Ÿåº¦", + "showAvgSpeed": "å¹³å‡é€Ÿåº¦", + "cleanAllSessionTooltip": "清空æœå‹™ç«¯æ‰€æœ‰æœªå®Œæˆçš„上傳會話", + "cleanAllSession": "清空所有上傳會話", + "cleanCompletedTooltip": "清除列表中已完æˆã€å¤±æ•—ã€è¢«å–消的任務", + "cleanCompleted": "清除已完æˆä»»å‹™", + "retryFailedTasks": "é‡è©¦æ‰€æœ‰å¤±æ•—任務", + "retryFailedTasksTooltip": "é‡è©¦ä½‡åˆ—中所有已失敗的任務", + "setConcurrentTooltip": "è¨­å®šåŒæ™‚進行的任務數é‡", + "setConcurrent": "設定並行數é‡", + "sizeExceedLimitError": "檔案大å°è¶…出儲存策略é™åˆ¶ï¼ˆæœ€å¤§ï¼š{{max}})", + "suffixNotAllowedError": "å„²å­˜ç­–ç•¥ä¸æ”¯æ´ä¸Šå‚³æ­¤å‰¯æª”å的檔案", + "regexpNotAllowedError": "å„²å­˜ç­–ç•¥ä¸æ”¯æ´ä¸Šå‚³æ­¤å稱的檔案", + "suffixAllowed": "(支æŒçš„副檔å:{{supported}})", + "suffixDenied": "ï¼ˆç¦æ­¢çš„副檔å:{{denied}})", + "createUploadSessionError": "無法建立上傳會話", + "deleteUploadSessionError": "無法刪除上傳會話", + "requestError": "請求失敗: {{msg}} ({{url}})", + "chunkUploadError": "分片 [{{index}}] 上傳失敗", + "conflictError": "åŒå檔案的上傳任務已經在處ç†ä¸­", + "chunkUploadErrorWithMsg": "分片上傳失敗: {{msg}}", + "chunkUploadErrorWithRetryAfter": "(請在 {{retryAfter}} 秒後é‡è©¦ï¼‰", + "emptyFileError": "æš«ä¸æ”¯æ´ä¸Šå‚³ç©ºæª”案至 OneDrive,請通éŽå»ºç«‹æª”案按鈕建立空檔案", + "finishUploadError": "ç„¡æ³•å®Œæˆæª”案上傳", + "finishUploadErrorWithMsg": "ç„¡æ³•å®Œæˆæª”案上傳: {{msg}}", + "ossFinishUploadError": "ç„¡æ³•å®Œæˆæª”案上傳: {{msg}} ({{code}})", + "cosUploadFailed": "上傳失敗: {{msg}} ({{code}})", + "upyunUploadFailed": "上傳失敗: {{msg}}", + "parseResponseError": "無法解æžéŸ¿æ‡‰: {{msg}} ({{content}})", + "concurrentTaskNumber": "åŒæ™‚上傳的任務數é‡", + "dropFileHere": "鬆開滑鼠開始上傳" + }, + "share": { + "statistics": "統計", + "expireAt": "<0>éŽæœŸ", + "expireAfterDownloads": "{{downloads}} æ¬¡ä¸‹è¼‰å¾ŒéŽæœŸ", + "somebodyShare": "{{name}} 的分享", + "expiredLink": "已失效的分享", + "sharedBy": "<0>{{nick}} 呿‚¨åˆ†äº«äº† {{num}} 個檔案", + "files": "1 file", + "files_other": "{{count}} files", + "statisticsViews": "{{views}} 次ç€è¦½", + "statisticsDownloads": "{{downloads}} 次下載 ", + "views": "{{count}} view", + "views_other": "{{count}} views", + "downloads": "{{count}} download", + "downloads_other": "{{count}} downloads", + "privateShareTitle": "{{nick}} 的加密分享", + "enterPassword": "分享密碼", + "continue": "繼續", + "shareCanceled": "分享連çµå·²åˆªé™¤", + "listLoadingError": "載入失敗", + "sharedFiles": "我的分享", + "createdAtDesc": "最新", + "createdAtAsc": "最早", + "noRecords": "沒有分享記錄.", + "sourceNotFound": "[原始物件ä¸å­˜åœ¨]", + "expired": "已失效", + "changeToPublic": "變更為公開分享", + "changeToPrivate": "變更為ç§å¯†åˆ†äº«", + "viewPassword": "檢視密碼", + "disablePreview": "ç¦æ­¢é è¦½", + "enablePreview": "å…許é è¦½", + "cancelShare": "å–æ¶ˆåˆ†äº«", + "sharePassword": "分享密碼", + "readmeError": "ç„¡æ³•è®€å– README 內容:{{msg}}", + "enterKeywords": "請輸入æœå°‹é—œéµè©ž", + "searchResult": "æœå°‹çµæžœ", + "sharedAt": "分享於 <0>", + "pleaseLogin": "請先登入", + "cannotShare": "此檔案無法é è¦½", + "preview": "é è¦½", + "incorrectPassword": "å¯†ç¢¼ä¸æ­£ç¢º", + "shareNotExist": "分享ä¸å­˜åœ¨æˆ–å·²éŽæœŸ", + "copyLinkToClipboard": "復制連çµåˆ°å‰ªä¸‹æ¿" + }, + "download": { + "noFilesFound": "沒有找到任何檔案", + "filterByName": "按åç¨±éŽæ¿¾", + "selectAll": "å…¨é¸", + "reverseSelect": "åé¸", + "cancelTaskConfirm": "確定è¦å–消此任務嗎?", + "saveChanges": "儲存更改", + "failedToLoad": "載入失敗", + "active": "進行中", + "finished": "已完æˆ", + "activeEmpty": "沒有下載中的任務", + "finishedEmpty": "沒有已完æˆçš„任務", + "loadMore": "載入更多", + "taskFileDeleted": "檔案已刪除", + "unknownTaskName": "[未知]", + "taskCanceled": "任務已喿¶ˆï¼Œç‹€æ…‹æœƒåœ¨ç¨å¾Œæ›´æ–°", + "operationSubmitted": "æ“作æˆåŠŸï¼Œç‹€æ…‹æœƒåœ¨ç¨å¾Œæ›´æ–°", + "deleteThisFile": "刪除此檔案", + "openDstFolder": "開啟存放目錄", + "selectDownloadingFile": "鏿“‡è¦ä¸‹è¼‰çš„æª”案", + "cancelTask": "å–æ¶ˆä»»å‹™", + "updatedAt": "更新於:", + "uploaded": "上傳大å°", + "uploadSpeed": "上傳速度", + "InfoHash": "InfoHash", + "seederCount": "åšç¨®è€…:", + "seeding": "åšç¨®ä¸­ï¼š", + "downloadNode": "節點:", + "isSeeding": "是", + "notSeeding": "å¦", + "chunkSize": "分片大å°ï¼š", + "chunkNumbers": "分片數é‡", + "taskDeleted": "刪除æˆåŠŸ", + "transferFailed": "檔案轉存失敗", + "downloadFailed": "下載出錯:{{msg}}", + "canceledStatus": "已喿¶ˆ", + "finishedStatus": "已完æˆ", + "pending": "已完æˆï¼Œè½‰å­˜æŽ’隊中", + "transferring": "轉存中", + "deleteRecord": "刪除記錄", + "createdAt": "建立日期:", + "unknownSize": "未知檔案大å°" + }, + "setting": { + "treeView": "樹視圖", + "autoExpandTreeView": "自動展開樹視圖", + "autoExpandTreeViewDes": "開啟後,å´é‚Šæ¬„的文件樹會隨當å‰ç›®éŒ„自動展開。", + "syncView": "視圖設置", + "syncViewDes": "是å¦è¨˜ä½å„å€‹ç›®éŒ„çš„è¦–åœ–è¨­ç½®ï¼Œä¸¦åŒæ­¥åˆ°æœå‹™å™¨ã€‚", + "syncViewOn": "åŒæ­¥åˆ°æœå‹™å™¨", + "syncViewOff": "ä¸åŒæ­¥", + "noAuthenticator": "æ–°å¢žé€šè¡Œé‡‘é‘°ä»¥ä½¿ç”¨äººè‡‰ã€æŒ‡ç´‹æˆ– USB 金鑰登入賬號", + "neverUsed": "從未使用éŽ", + "usedAt": "上次使用於 <0>", + "passkeyName": "{os} 上的 {browser}", + "versionRetentionMax": "最大版本數é‡ï¼Œ0 表示無é™åˆ¶", + "versionRetentionEnabledExt": "啟用的副檔å", + "versionRetentionEnabledExtDes": "æŒ‰å›žè»Šéµæ–°å¢žï¼Œç•™ç©ºæ™‚æœƒå°æ‰€æœ‰æª”案啟用", + "enableVersionRetention": "啟用版本ä¿ç•™", + "enableVersionRetentionDes": "å•Ÿç”¨å¾Œï¼Œå°æ–¼ç¬¦åˆæ¢ä»¶çš„æª”案,系統會ä¿ç•™å…¶çš„æ­·å²ç‰ˆæœ¬", + "versionRetention": "版本ä¿ç•™", + "languageDes": "設定應用展示語言和首é¸éƒµä»¶èªžè¨€", + "timezoneDes": "設定展示時å€ï¼Œé è¨­è·Ÿéš¨ç³»çµ±æ™‚å€", + "nickNameDes": "用於公開展示的å字,å¯ä½¿ç”¨çœŸå¯¦å§“åæˆ–暱稱", + "cropAvatar": "è£å‰ªé ­åƒ", + "preference": "å好", + "accountCreatedAt": "創建於 <0>", + "shoeQr": "顯示", + "deviceNothing": "ç•¶å‰ä½¿ç”¨è€…çµ„ä¸æ”¯æ´ WebDAV", + "connectionInfo": "連線資訊", + "proxyTooltip": "æœå‹™ç«¯ä»£ç†æ‰€æœ‰æª”案下載請求。", + "readonlyTooltip": "使用者åªèƒ½é€šéŽæ­¤è³¬è™Ÿè®€å–檔案。", + "blockSysFilesUpload": "阻止上傳系統檔案", + "blockSysFilesUploadTooltip": "開啟後,以 <0>. 開頭的檔案會被阻止上傳。", + "rootFolderIn": "鏿“‡ <0>", + "createWebDavAccount": "建立 WebDAV 賬號", + "editWebDavAccount": "編輯 {{name}}", + "seeding": "åšç¨®ä¸­", + "awaitSeeding": "等待åšç¨®", + "awaitSeedingDes": "等待下載任務åšç¨®å®Œæˆã€‚", + "downloadTransferDes": "將檔案轉存到目的地。", + "downloadDes": "下載指定的檔案。", + "retryErrorHistory": "æ­·å²é‡è©¦éŒ¯èª¤", + "retryCount": "é‡è©¦æ¬¡æ•¸", + "resumeAt": "下次æ¢å¾©åŸ·è¡Œ", + "executeDuration": "執行淨耗時", + "input": "輸入", + "output": "輸出", + "suspended": " (已掛起)", + "updatedAt": "æ›´æ–°æ–¼", + "taskDetails": "任務詳情", + "partialSuccessWarning": "有 {{num}} 個物件處ç†å¤±æ•—,已將其跳éŽã€‚", + "sendTask": "傳é€ä»»å‹™", + "sendTaskDes": "將任務傳é€åˆ°è™•ç†ç¯€é»žã€‚", + "downloaded": "已下載", + "importingFiles": "匯入檔案", + "importingFilesDes": "檢索檔案並將其匯入到指定目錄。", + "importedFiles": "已匯入檔案", + "indexedFiles": "已索引檔案", + "extractedFiles": "已解壓檔案數é‡", + "extractedFilesSize": "已解壓檔案大å°", + "extractingFiles": "解壓檔案", + "extractingFilesDes": "將所有檔案解壓到指定目錄。", + "downloadingZip": "ç²å–壓縮檔案", + "downloadingZipDes": "將壓縮檔案下載到臨時工作å€ã€‚", + "progressNotAvailable": "進度資訊尚未å¯ç”¨", + "uploadedSize": "轉存檔案", + "archivedFiles": "å·²è™•ç†æª”案數é‡", + "transferredFiles": "已轉存檔案數é‡", + "archivedFilesSize": "å·²è™•ç†æª”案大å°", + "createArchiveFinishing": "æäº¤æ–°å¢žæª”案更改。", + "indexForArchiveDes": "檢索所有待壓縮檔案。", + "prepare": "準備", + "preparingWorkspaceDes": "準備臨時工作å€ã€‚", + "compressFiles": "建立壓縮檔案", + "compressFilesDes": "將檔案壓縮到臨時工作å€ã€‚", + "uploadArchiveFileDes": "將壓縮檔案轉存到目的地。", + "uploadWorker": "上傳執行緒 #{{num}}", + "queueToStart": "排隊開始", + "indexingFiles": "檢索檔案", + "indexingFilesDes": "檢索所有待轉移檔案,並將其鎖定。", + "transferring": "轉存", + "committingChanges": "æäº¤æ›´æ”¹", + "autoRefresh": "è‡ªå‹•é‡æ–°æ•´ç†", + "avatarUpdated": "é ­åƒå·²æ›´æ–°ï¼Œæœ€æ–°é ­åƒå±•示å¯èƒ½æœ‰å»¶é²", + "nickChanged": "æš±ç¨±å·²æ›´æ”¹ï¼Œé‡æ–°æ•´ç†å¾Œç”Ÿæ•ˆ", + "settingSaved": "設定已儲存", + "themeColorChanged": "主題é…色已更æ›", + "profile": "個人資料", + "avatar": "é ­åƒ", + "uid": "UID", + "nickname": "暱稱", + "group": "使用者組", + "regTime": "注冊時間", + "security": "密碼和安全", + "profilePage": "個人主é ", + "publicShareOnly": "僅展示無密碼分享連çµ", + "publicShareOnlyDes": "僅在個人主é å±•示沒有設置密碼的分享連çµã€‚", + "allShare": "所有分享", + "allShareDes": "在個人主é å±•示所有分享連çµï¼ˆåŒ…æ‹¬æœ‰å¯†ç¢¼çš„åˆ†äº«ï¼‰ã€‚å°æ–¼æœ‰å¯†ç¢¼çš„分享,用戶還需è¦è¼¸å…¥å¯†ç¢¼æ‰èƒ½è¨ªå•。", + "hideShare": "éš±è—æ‰€æœ‰åˆ†äº«é€£çµ", + "hideShareDes": "在個人主é éš±è—所有分享連çµã€‚", + "userHideShare": "用戶隱è—了分享連çµåˆ—表", + "accountPassword": "登入密碼", + "2fa": "二步驗證", + "enabled": "已開啟", + "disabled": "未開啟", + "appearance": "個性化", + "themeColor": "主題é…色", + "darkMode": "黑暗模å¼", + "syncWithSystem": "系統", + "fileList": "檔案列表", + "timeZone": "時å€", + "webdavServer": "連線地å€", + "userName": "使用者å稱", + "manageAccount": "賬號管ç†", + "uploadImage": "從檔案上傳", + "useGravatar": "使用 Gravatar é ­åƒ ", + "changeNick": "修改暱稱", + "originalPassword": "原密碼", + "enable2FA": "啟用二步驗證", + "disable2FA": "關閉二步驗證", + "2faDescription": "請使用任æ„二步驗證 APP 或者支æ´äºŒæ­¥é©—證的密碼管ç†è»Ÿé«”掃æäºŒç¶­ç¢¼æ–°å¢žæœ¬ç«™ã€‚掃æå®Œæˆå¾Œè«‹å¡«å¯«äºŒæ­¥é©—è­‰ APP 給出的 6 ä½é©—證碼以開啟二步驗證。", + "inputCurrent2FACode": "輸入當å‰äºŒæ­¥é©—è­‰ APP 給出的 6 ä½é©—證碼:", + "timeZoneCode": "IANA 時å€å稱標識", + "authenticatorRemoved": "憑證已刪除", + "authenticatorAdded": "驗證器已新增", + "browserNotSupported": "ç•¶å‰ç€è¦½å™¨æˆ–ç’°å¢ƒä¸æ”¯æ´", + "removedAuthenticator": "刪除憑證", + "removedAuthenticatorConfirm": "確定è¦åŠéŠ·é€™å€‹æ†‘è­‰å—Žï¼Ÿ", + "addNewAuthenticator": "新增新憑證", + "hardwareAuthenticator": "通行金鑰", + "copied": "已復制到剪下æ¿", + "pleaseManuallyCopy": "ç•¶å‰ç€è¦½å™¨ä¸æ”¯æ´ï¼Œè«‹æ‰‹å‹•復制", + "webdavAccounts": "WebDAV 賬號管ç†", + "webdavHint": "WebDAV的地å€ç‚ºï¼š{{url}};登入使用者å稱統一為:{{name}} ;密碼為所建立賬號的密碼。", + "annotation": "備注å", + "rootFolder": "ç›¸å°æ ¹ç›®éŒ„", + "createdAt": "建立日期", + "action": "æ“作", + "readonlyOn": "åªè®€", + "readonlyOff": "讀寫", + "proxy": "åå‘代ç†", + "none": "ç„¡", + "proxied": "已代ç†", + "delete": "刪除", + "listEmpty": "沒有記錄", + "createNewAccount": "建立新賬號", + "taskType": "任務型別", + "taskStatus": "狀態", + "taskProgress": "任務進度", + "errorDetails": "錯誤資訊", + "queueing": "排隊中", + "processing": "處ç†ä¸­", + "failed": "失敗", + "canceled": "å–æ¶ˆ", + "finished": "已完æˆ", + "fileTransfer": "檔案中轉", + "fileRecycle": "檔案回收", + "importFiles": "匯入外部目錄", + "transferProgress": "å·²å®Œæˆ {{num}} 個檔案", + "waiting": "等待中", + "compressing": "壓縮中", + "decompressing": "解壓縮中", + "downloading": "下載中", + "indexing": "索引中", + "listing": "æ’入中", + "allShares": "全部分享", + "trendingShares": "熱門分享", + "totalShares": "分享總數", + "fileName": "檔å", + "shareDate": "分享日期", + "downloadNumber": "下載次數", + "viewNumber": "ç€è¦½æ¬¡æ•¸", + "language": "語言", + "iOSApp": "iOS/iPadOS 客戶端", + "connectByiOS": "é€šéŽ iOS/iPadOS è£ç½®é€£ç·šåˆ° <0>{{title}}", + "downloadOurApp": "ä¸‹è¼‰ä¸¦å®‰è£æˆ‘們的應用:", + "fillInEndpoint": "使用我們的應用掃æä¸‹æ–¹äºŒç¶­ç¢¼ï¼ˆå…¶ä»–掃碼應用無效):", + "loginApp": "完æˆç¹«çµï¼Œä½ å¯ä»¥é–‹å§‹ä½¿ç”¨å®¢æˆ¶ç«¯äº†ã€‚如果掃碼繫çµé‡åˆ°å•題,你也å¯ä»¥å˜—試手動輸入使用者å稱和密碼登入。", + "relocateFileTo": "å°‡ <0>{{more}} 的儲存策略轉移至 {{policy}}", + "extractFileTo": "å°‡ <0>{{more}} 解壓縮至 <1>", + "createArchiveTo": "å°‡ <0>{{more}} 打包至 <1>", + "importFileTo": "å°‡ {{policy}} 中的文件導入至 <0>" + }, + "vas": { + "points": "ç©åˆ†", + "quota": "容é‡é…é¡", + "used": "已使用 - {{size}}", + "total": "ç¸½å®¹é‡ - {{size}}", + "report": "濫用舉報", + "validDurationDays": "{{num}} 天", + "reportTarget": "舉報å°è±¡", + "reportReason": "原因", + "reportReasonOptions": ["侵權", "有害內容", "垃圾訊æ¯", "å…¶ä»–"], + "reportDescription": "補充說明", + "reportAbuseSuccess": "舉報已æäº¤" + } +} diff --git a/public/locales/zh-TW/common.json b/public/locales/zh-TW/common.json new file mode 100755 index 0000000..5fe5c21 --- /dev/null +++ b/public/locales/zh-TW/common.json @@ -0,0 +1,106 @@ +{ + "pageNotFound": "é é¢ä¸å­˜åœ¨", + "unknownError": "未知錯誤", + "errLoadingSiteConfig": "無法載入站點é…置:", + "newVersionRefresh": "ç•¶å‰é é¢æœ‰æ–°ç‰ˆæœ¬å¯ç”¨ã€‚", + "update": "æ›´æ–°", + "errorDetails": "詳情", + "renderError": "é é¢æ¸²æŸ“出ç¾éŒ¯èª¤ï¼Œè«‹å˜—è©¦é‡æ–°æ•´ç†æ­¤é é¢ã€‚", + "ok": "確定", + "cancel": "å–æ¶ˆ", + "select": "鏿“‡", + "copyToClipboard": "復制", + "close": "關閉", + "dismiss": "關閉", + "intlDateTime": "{{val, datetime}}", + "seconds": "s ç§’", + "minutes": "m 分 s ç§’", + "hours": "H å°æ™‚ m 分", + "days": "{{d}} 天", + "timeAgoLocaleCode": "zh_CN", + "forEditorLocaleCode": "zh-CN", + "artPlayerLocaleCode": "zh-cn", + "requestID": "請求 ID: {{id}}", + "object": "物件", + "error": "錯誤", + "areYouSure": "確èª", + "incorrectSizeInput": "ä¸ç¬¦åˆå°ºå¯¸é™åˆ¶", + "of": "å…±", + "rowsPerPage": "æ¯é è¡Œæ•¸", + "custom": "自定義", + "enter": "輸入", + "captcha": { + "cap": { + "human": "我是人類", + "verifying": "驗證中…", + "verified": "您已通éŽé©—è­‰" + } + }, + "errors": { + "401": "請先登入", + "403": "ä½ æ²’æœ‰è¨±å¯æ¬ŠåŸ·è¡Œæ­¤æ“作", + "404": "資æºä¸å­˜åœ¨", + "409": "發生è¡çª ({{message}})", + "40001": "輸入引數有誤 ({{message}})", + "40002": "上傳失敗", + "40003": "目錄建立失敗", + "40004": "åŒå物件已存在", + "40005": "籤åéŽæœŸ", + "40006": "䏿”¯æ´çš„儲存策略型別", + "40007": "ç•¶å‰ä½¿ç”¨è€…組無法進行此æ“作", + "40011": "上傳會話ä¸å­˜åœ¨æˆ–å·²éŽæœŸ", + "40012": "分片åºè™Ÿç„¡æ•ˆ ({{message}})", + "40013": "正文長度無效 ({{message}})", + "40014": "超出批é‡ç²å–外éˆé™åˆ¶", + "40015": "超出最大離線下載任務數é‡é™åˆ¶", + "40016": "路徑ä¸å­˜åœ¨", + "40017": "該賬號已被å°ç¦", + "40018": "該賬號未啟用", + "40019": "此功能未啟用", + "40020": "æ†‘è­‰ç„¡æ•ˆæˆ–éŽæœŸ", + "40021": "使用者ä¸å­˜åœ¨", + "40022": "驗證程å¼ç¢¼ä¸æ­£ç¢º", + "40023": "登入會話ä¸å­˜åœ¨", + "40024": "無法åˆå§‹åŒ– WebAuthn", + "40025": "驗證失敗", + "40026": "驗證碼錯誤", + "40027": "é©—è­‰å¤±æ•—ï¼Œè«‹é‡æ–°æ•´ç†ç¶²é é‡è©¦", + "40028": "郵件傳é€å¤±æ•—", + "40029": "無效的連çµ", + "40030": "此連çµå·²éŽæœŸ", + "40032": "此郵箱已被使用", + "40033": "ä½¿ç”¨è€…æœªå•Ÿç”¨ï¼Œå·²é‡æ–°ç™¼é€å•Ÿç”¨éƒµä»¶", + "40034": "該使用者無法被啟用", + "40035": "儲存策略ä¸å­˜åœ¨", + "40039": "使用者組ä¸å­˜åœ¨", + "40044": "檔案ä¸å­˜åœ¨", + "40045": "無法列å–目錄下的物件", + "40047": "無法åˆå§‹åŒ–檔案系統", + "40048": "建立任務出錯", + "40049": "檔案大å°è¶…出é™åˆ¶", + "40050": "檔案型別ä¸å…許", + "40051": "容é‡ç©ºé–“ä¸è¶³", + "40052": "æ­¤æª”æ¡ˆåæˆ–副檔åä¸è¢«å…許", + "40053": "䏿”¯æ´å°æ ¹ç›®éŒ„執行此æ“作", + "40054": "話當å‰ç›®éŒ„下已經有åŒå檔案正在上傳中,請嘗試清空上傳會話", + "40055": "檔案資訊ä¸ä¸€è‡´", + "40056": "䏿”¯æ´è©²æ ¼å¼çš„壓縮檔案", + "40057": "å¯ç”¨å„²å­˜ç­–ç•¥ç™¼ç”Ÿè®ŠåŒ–ï¼Œè«‹é‡æ–°æ•´ç†æª”æ¡ˆåˆ—è¡¨ä¸¦é‡æ–°æ–°å¢žæ­¤ä»»å‹™", + "40058": "分享ä¸å­˜åœ¨æˆ–å·²éŽæœŸ", + "40069": "å¯†ç¢¼ä¸æ­£ç¢º", + "40070": "此分享無法é è¦½", + "40071": "籤å無效", + "40073": "檔案被佔用", + "40074": "æ‰€é¸æª”案數é‡è¶…出é™åˆ¶", + "40079": "è¶…å‡ºæœ€å¤§éæ­·æª”案數é™åˆ¶ï¼Œè«‹ç¸®å°æ“作範åœ", + "40081": "æ“作未完全æˆåŠŸ", + "40082": "åªæœ‰æª”案所有者å¯ä»¥åŸ·è¡Œæ­¤æ“作", + "40080": "使用者郵箱或密碼錯誤", + "50001": "資料庫æ“作失敗 ({{message}})", + "50002": "URL 或請求籤å失敗 ({{message}})", + "50004": "I/O æ“作失敗 ({{message}})", + "50005": "內部錯誤 ({{message}})", + "50010": "目標節點ä¸å¯ç”¨", + "50011": "檔案元資訊查詢失敗" + } +} diff --git a/public/locales/zh-TW/dashboard.json b/public/locales/zh-TW/dashboard.json new file mode 100755 index 0000000..c2069dd --- /dev/null +++ b/public/locales/zh-TW/dashboard.json @@ -0,0 +1,1623 @@ +{ + "errors": { + "40036": "é è¨­å„²å­˜ç­–略無法刪除", + "40037": "有檔案 Blob ä»åœ¨ä½¿ç”¨æ­¤å„²å­˜ç­–略,請先刪除這些檔案 Blob", + "40038": "有 {{message}} 個使用者組ç¶å®šäº†æ­¤å„²å­˜ç­–略,請先解除繫çµ", + "40040": "無法å°ç³»çµ±ä½¿ç”¨è€…組執行此æ“作", + "40041": "有 {{message}} ä½ä½¿ç”¨è€…ä»å±¬æ–¼æ­¤ä½¿ç”¨è€…組,請先刪除這些使用者或者更改使用者組", + "40042": "無法更改åˆå§‹ä½¿ç”¨è€…的使用者組", + "40043": "無法å°åˆå§‹ä½¿ç”¨è€…執行此æ“作", + "40046": "無法å°ä¸»æ©Ÿç¯€é»žåŸ·è¡Œæ­¤æ“作", + "40060": "從機無法å‘主機發é€å›žèª¿è«‹æ±‚,請檢查主機端 引數設定 - 站點資訊 - 站點URL設定,並確ä¿å¾žæ©Ÿå¯ä»¥é€£ç·šåˆ°æ­¤åœ°å€ ({{message}})", + "40061": "Cloudreve 版本ä¸ä¸€è‡´ ({{message}})", + "40086": "節點正在被以下儲存策略使用:{{message}}", + "50008": "設定項更新失敗 ({{message}})", + "50009": "跨域策略新增失敗" + }, + "nav": { + "summary": "颿¿é¦–é ", + "settings": "引數設定", + "basicSetting": "站點資訊", + "email": "郵件", + "transportation": "傳輸與通訊", + "appearance": "外觀", + "image": "影象與é è¦½", + "captcha": "驗證碼", + "storagePolicy": "儲存策略", + "nodes": "節點", + "groups": "使用者組", + "users": "使用者", + "files": "檔案", + "entities": "檔案 Blob", + "shares": "分享", + "tasks": "後臺任務", + "remoteDownload": "離線下載", + "generalTasks": "常è¦ä»»å‹™", + "title": "儀表盤", + "dashboard": "Cloudreve 儀表盤", + "userSession": "使用者會話", + "fileSystem": "檔案系統", + "mediaProcessing": "媒體處ç†", + "queue": "佇列", + "events": "事件", + "server": "伺æœå™¨", + "customProps": "自定義屬性", + "abuseReport": "濫用舉報" + }, + "summary": { + "generatedAt": "ç”Ÿæˆæ–¼ <0>", + "confirmSiteURLTitle": "確定站點URL設定", + "siteURLNotMatch": "你設定的站點 URL 並未包å«ç•¶å‰çš„ {{current}},是å¦è¦æ›´æ”¹è¨­å®šï¼Ÿ", + "setAsPrimary": "設定為主è¦ç«™é»ž URL", + "setAsPrimaryDes": "å°‡ {{current}} 設定為主è¦ç«™é»ž URL,用於與外部æœå‹™é€šè¨Šå’ŒæŽ¥å—回撥,請使用能被公網訪å•çš„ URL。", + "setAsSecondary": "新增到備é¸ç«™é»ž URL", + "setAsSecondaryDes": "å°‡ {{current}} 新增到備é¸ç«™é»ž URL,Cloudreve 會根據使用者實際訪å•çš„ URL è‡ªå‹•é¸æ“‡æ˜¯å¦ä½¿ç”¨ã€‚", + "siteURLDescription": "此設定éžå¸¸é‡è¦ï¼Œè«‹ç¢ºä¿å…¶èˆ‡ä½ ç«™é»žçš„實際地å€ä¸€è‡´ã€‚ä½ å¯ä»¥åœ¨ 引數設定 - 站點資訊 中更改此設定。", + "ignore": "忽略", + "changeIt": "更改", + "trend": "趨勢", + "summary": "總計", + "totalUsers": "注冊使用者", + "totalFilesAndFolders": "檔案與目錄", + "shareLinks": "分享連çµ", + "totalBlobs": "檔案 Blob", + "homepage": "主é ", + "github": "GitHub", + "documents": "檔案", + "discordCommunity": "Discord 社群", + "telegram": "Telegram 社群", + "forum": "社å€è«–壇", + "buyPro": "å‡ç´šåˆ° Pro", + "publishedAt": "發表於 <0>", + "licenseExpireAt": "授權有效期", + "permanentLicense": "永久", + "offlineLicenseExpireAy": "é›¢ç·šæŽˆæ¬ŠéŽæœŸæ—¥æœŸ", + "offlineLicenseDes": "在連線網路的情æ³ä¸‹ï¼ŒCloudreve æœƒåœ¨éŽæœŸå‰è‡ªå‹•更新離線授權", + "licensedDomains": "授權的域å", + "renew": "更新離線授權", + "manageLicense": "ç®¡ç†æŽˆæ¬Š", + "volPurchase": "客戶端 VOL 授權需è¦å–®ç¨åœ¨ <0>授權管ç†é¢æ¿ 購買。VOL 授權å…許你的使用者å…費使用 <1>Cloudreve iOS 客戶端 連線到你的站點,無需使用者å†ä»˜è²»è¨‚é–± iOS 客戶端。購買授權後請點é¸ä¸‹æ–¹æ›´æ–°æŽˆæ¬Šã€‚", + "iosVol": "iOS å®¢æˆ¶ç«¯æ‰¹é‡æŽˆæ¬Š (VOL)", + "refreshSuccessfully": "釿–°æ•´ç†æˆåŠŸ", + "manualRefresh": "æ‰‹å‹•é‡æ–°æ•´ç†é›¢ç·šæŽˆæ¬Š", + "manualRefreshDes": "è‡ªå‹•é‡æ–°æ•´ç†é›¢ç·šæŽˆæ¬Šå¤±æ•—,請嘗試登入 <0>授權管ç†é¢æ¿ ç²å–最新的離線授權,將其貼上在下方。" + }, + "queue": { + "queueName_io_intense": "IO 密集型", + "queueName_io_intenseDes": "用於處ç†å¤§é‡ IO æ“作的佇列,包括:轉移儲存策略ã€è§£å£“縮ã€å£“縮。", + "queueName_media_meta": "媒體元資料æå–", + "queueName_media_metaDes": "用於æå–媒體檔案的元資料。", + "queueName_recycle": "Blob 回收", + "queueName_recycleDes": "ç”¨æ–¼åˆªé™¤éŽæœŸçš„æª”案 Blob。", + "queueName_thumb": "縮圖生æˆ", + "queueName_thumbDes": "用於為檔案生æˆç¸®åœ–。", + "queueName_remote_download": "離線下載", + "queueName_remote_downloadDes": "用於處ç†é›¢ç·šä¸‹è¼‰ä»»å‹™ã€‚", + "failed": "失敗 ({{count}})", + "success": "æˆåŠŸ ({{count}})", + "suspending": "掛起 ({{count}})", + "busyWorker": "處ç†ä¸­ ({{count}})", + "submited": "å·²æäº¤ ({{count}})", + "editQueueSettings": "編輯佇列設定 - {{name}}", + "workerNum": "工作執行緒數", + "workerNumDes": "任務佇列最多並行執行的任務數。", + "maxExecution": "最大執行時間", + "maxExecutionDes": "ä»»å‹™æœ€å¤§åŸ·è¡Œæ™‚é–“ï¼ˆç§’ï¼‰ï¼Œè¶…éŽæ­¤æ™‚間任務將被終止。", + "backoffFactor": "退é¿å› å­", + "backoffFactorDes": "任務é‡è©¦æ™‚間間隔的增長因å­ã€‚", + "backoffMaxDuration": "æœ€å¤§é€€é¿æ™‚é–“", + "backoffMaxDurationDes": "任務é‡è©¦çš„æœ€å¤§é€€é¿æ™‚間(秒)。", + "maxRetry": "最大é‡è©¦æ¬¡æ•¸", + "maxRetryDes": "任務失敗後的最大é‡è©¦æ¬¡æ•¸ã€‚", + "retryDelay": "é‡è©¦å»¶é²", + "retryDelayDes": "任務é‡è©¦çš„åˆå§‹å»¶é²æ™‚間(秒)。" + }, + "settings": { + "headlessFooter": "登入會話é é¢åº•部", + "headlessFooterDes": "使用者登入ã€è¨»å†Šã€å›žèª¿çµæžœç­‰é é¢åº•部展示的自訂 HTML 內容。", + "headlessBottom": "登入會話é é¢ä¸»é«”底部", + "headlessBottomDes": "使用者登入ã€è¨»å†Šã€å›žèª¿çµæžœç­‰é é¢ä¸»é«”框底部展示的自訂 HTML 內容。", + "customHTML": "自訂 HTML", + "customHTMLDes": "在站點的é è¨­ä½ç½®æ’入展示自訂的 HTML 內容。", + "sidebarBottom": "å´é‚Šæ¬„底部", + "sidebarBottomDes": "在å´é‚Šæ¬„底部展示的自訂 HTML 內容。", + "addNavItem": "新增導航æ¢ç›®", + "customNavItems": "自定義å´é‚Šå°Žèˆªæ¬„", + "customNavItemsDes": "ä½ å¯ä»¥åœ¨å´é‚Šå°Žèˆªæ¬„中新增自定義的æ¢ç›®ï¼Œç”¨æˆ¶é»žæ“Šå¾Œæœƒè·³è½‰åˆ°å°æ‡‰çš„éˆæŽ¥ã€‚", + "navItemUrl": "éˆæŽ¥", + "iconifyNamePlaceholder": "Iconify 圖標標識,比如:fluent:home-24-regular", + "imageUrl": "圖片 URL", + "iconifyName": "Iconify 圖標å", + "oidc": "OpenID Connect (OIDC)", + "oidcDes": "OpenID Connect (OIDC) 是一種開放的èªè­‰èº«ä»½é©—è­‰å”定,用於在ä¸åŒçš„系統之間進行身份驗證。在第三方身份平å°ä¸­å‰µå»ºæ‡‰ç”¨å¾Œï¼Œè«‹å°‡ <0>{{url}} 添加到 “é‡å®šå‘ URI†中。詳細請åƒé–± <1>官方文檔。", + "clientID": "客戶端 ID", + "clientIDDes": "第三方身份平å°åˆ›å»ºçš„应用的客户端 ID。", + "clientSecret": "客戶端密碼", + "clientSecretDes": "第三方身份平å°åˆ›å»ºçš„应用的客戶端密钥。", + "scope": "Scope", + "scopeDes": "需è¦é¡å¤–請求的 Scope,以逗號 <0>, 分隔。é è¨­æƒ…æ³ä¸‹ï¼ŒCloudreve 會請求 <0>openid , <0>email å’Œ <0>profile;無需在此é‡è¤‡å¡«å¯«ã€‚", + "oidcWellknown": "ç™¼ç¾æ–‡æª” (Wellknown)", + "oidcWellknownDes": "第三方身份平å°ç™¼ç¾æ–‡æª”ï¼ŒåŒ…å« OpenID Connect çš„é…置信æ¯ã€‚", + "importFromWellknown": "從 URL å°Žå…¥", + "importOidc": "å°Žå…¥ OIDC ç™¼ç¾æ–‡æª”檔", + "oidcWellknownUrl": "ç™¼ç¾æ–‡æª” URL", + "oidcWellknownUrlDes": "第三方身份平å°ç™¼ç¾æ–‡æª”çš„ URL 地å€ï¼Œæ¯”如 <0>https://accounts.google.com/.well-known/openid-configuration。", + "resetUrl": "é‡ç½®é€£çµ", + "exceedToleranceDays": "設置的å°ç¦å¯¬å®¹å¤©æ•¸", + "activateUrl": "激活連çµ", + "domainNotLicensed": "åŸŸåæœªæŽˆæ¬Š", + "domainNotLicensedDes": "你設定的站點 URL ä¸­åŒ…å«æœªæŽˆæ¬Šçš„域å,請在 <0>授權管ç†é¢æ¿ 中添加此å­åŸŸå,然後點é¸ä¸‹æ–¹æŒ‰éˆ•更新授權後é‡è©¦ã€‚", + "showSettings": "顯示設定", + "perPage": "æ¯é  {{num}} æ¢", + "noNodes": "沒有å¯ç”¨çš„節點。", + "extractMediaMeta": "媒體資訊æå–", + "extractMediaMetaDes": "æå–媒體檔案的元資料以用於展示和æœå°‹ã€‚é è¨­æƒ…æ³ä¸‹ï¼Œéžæœ¬æ©Ÿå„²å­˜ç­–ç•¥åªæœƒä½¿ç”¨â€œå„²å­˜ç­–ç•¥åŽŸç”Ÿâ€æ–¹å¼æå–。你å¯ä»¥åœ¨å„²å­˜ç­–略設定é é¢é–‹å•Ÿâ€œæå–器代ç†â€åŠŸèƒ½æ“´å……å¥—ä»¶ç¬¬ä¸‰æ–¹å„²å­˜ç­–ç•¥çš„ç¸®åœ–èƒ½åŠ›ã€‚è©³ç´°è«‹åƒé–± <0>官方檔案。", + "exif": "EXIF", + "exifDes": "從圖片檔案中æå– EXIF 元資料以用於展示和æœå°‹ã€‚", + "music": "音樂元資料", + "musicDes": "從音樂檔案中æå–元資料,包括標題ã€è—è¡“å®¶ã€å°ˆè¼¯ç­‰è³‡è¨Šã€‚", + "ffprobe": "FFprobe", + "ffprobeDes": "使用 FFprobe 從視訊和音訊檔案中æå–元資料。", + "maxSizeLocal": "最大檔案大å°ï¼ˆæœ¬åœ°å„²å­˜ï¼‰", + "maxSizeLocalDes": "當檔案儲存在本地儲存策略時,å…許æå–元資料的最大檔案大å°ï¼Œå¡«å¯«ç‚º 0 時ä¸é™åˆ¶ã€‚", + "maxSizeRemote": "最大檔案大å°ï¼ˆé ç«¯å„²å­˜ï¼‰", + "maxSizeRemoteDes": "當檔案儲存在第三方儲存策略時,å…許æå–元資料的最大檔案大å°ï¼Œå¡«å¯«ç‚º 0 時ä¸é™åˆ¶ã€‚", + "exifBruteForce": "å¿…è¦æ™‚使用暴力æœå°‹", + "exifBruteForceDes": "啟用後,如果在標準頭部ä½ç½®æ‰¾ä¸åˆ° EXIF è³‡æ–™ï¼Œå°‡æŽƒææ•´å€‹æª”案以查詢 EXIF 資料。這å¯èƒ½æœƒå¢žåŠ è™•ç†æ™‚間,但å¯ä»¥æ‰¾åˆ°éžæ¨™æº–ä½ç½®çš„ EXIF 資料。", + "musicCover": "歌曲å°é¢", + "musicCoverDes": "æå–音訊檔案中的專輯å°é¢, æ”¯æ´ ID3 (v1, 2.2, 2.3, 2.4) 元資料容器。這一生æˆå™¨ä¾è³´æ–¼ä»»ä¸€å…¶ä»–影象生æˆå™¨ï¼ˆCloudreve 內建 或 VIPS)。", + "geocoding": "地ç†ç·¨ç¢¼", + "geocodingDes": "根據媒體 EXIF 中記錄的座標資訊,使用 Mapbox æœå‹™ç²å–地å€è³‡è¨Šã€‚", + "mapboxAK": "Mapbox 密鑰", + "mapboxAKDes": "在 Mapbox 控制å°å»ºç«‹çš„密鑰。", + "geocodingDependencyWarning": "地ç†ç·¨ç¢¼ç”¢ç”Ÿå™¨ä¾è³´æ–¼ EXIF 產生器,請開啟 EXIF 產生器。", + "notAppliedToNativeGenerator": "{{prefix}}ä¸é©ç”¨æ–¼å„²å­˜ç­–略原生生æˆå™¨ã€‚", + "notAppliedToOneDriveNativeGenerator": "{{prefix}}ä¸é©ç”¨æ–¼ OneDrive 或 SharePoint 儲存策略原生生æˆå™¨ã€‚", + "fileBlobMargin": "檔案 Blob 臨時 URL å¿«å–冗餘(秒)", + "fileBlobMarginDes": "當相åŒçš„æª”案 Blob 被多次請求時,如果最åˆçš„ URL 剩餘有效期大於冗餘時長,相åŒçš„ URL 會被複用。", + "fileBlobTimeout": "檔案 Blob 臨時 URL 有效期(秒)", + "fileBlobTimeoutDes": "é™åˆ¶ä½¿ç”¨è€…開啟或下載檔案時,所ç²å¾—的臨時連çµçš„æœ‰æ•ˆæœŸï¼Œåªé‡å°æœ¬æ©Ÿå„²å­˜ç­–ç•¥ã€WebDAV 或經 Cloudreve 代ç†çš„æª”案下載。", + "wopiSessionTimeout": "WOPI 會話有效期(秒)", + "wopiSessionTimeoutDes": "é™åˆ¶ä½¿ç”¨è€…使用 WOPI ç·¨è¼¯æª”æ¡ˆæ™‚ï¼Œå–®å€‹æœƒè©±çš„æœ‰æ•ˆæœŸï¼ŒéŽæœŸå¾Œä½¿ç”¨è€…需è¦é‡æ–°å¾ž Cloudreve 開啟檔案。", + "oauthRefresh": "OAuth å„²å­˜ç­–ç•¥æ†‘è­‰é‡æ–°æ•´ç†é–“éš”", + "oauthRefreshDes": "è¨­å®šå¤šä¹…é‡æ–°æ•´ç†éœ€è¦ä½¿ç”¨ OAuth 的儲存策略(OneDrive)的憑證,å¯ä»¥é¿å…é•·æœŸæœªä½¿ç”¨å„²å­˜ç­–ç•¥å°Žè‡´çš„æ†‘è­‰éŽæœŸ", + "transitParallelNum": "中轉最大並行傳輸", + "transitParallelNumDes": "當單個æœå‹™ç«¯æª”案中轉任務包å«å¤šå€‹æª”案時,最大並行上傳的數é‡ã€‚", + "failedChunkRetry": "分片錯誤最大é‡è©¦", + "failedChunkRetryDes": "分片上傳失敗後é‡è©¦çš„æœ€å¤§æ¬¡æ•¸ï¼Œåªé©ç”¨æ–¼æœå‹™ç«¯ä¸Šå‚³æˆ–中轉分片上傳。", + "cacheChunks": "å¿«å–æµå¼åˆ†ç‰‡æª”案以用於é‡è©¦", + "cacheChunksDes": "開啟後,æµå¼ä¸­è½‰åˆ†ç‰‡ä¸Šå‚³æ™‚會將分片資料快å–在系統臨時目錄,以便用於分片上傳失敗後的é‡è©¦ï¼›\n 關閉後,æµå¼ä¸­è½‰åˆ†ç‰‡ä¸Šå‚³ä¸æœƒé¡å¤–佔用硬碟空間,但分片上傳失敗後整個上傳會立å³å¤±æ•—。", + "folderPropsTimeout": "目錄統計資訊有效期(秒)", + "folderPropsTimeoutDes": "使用者計算目錄統計資訊(大å°ï¼ŒåŒ…嫿ª”案數é‡ç­‰ï¼‰æ™‚ï¼Œçµæžœå¿«å–的有效期。", + "slaveAPIExpiration": "從機 API ç±¤åæœ‰æ•ˆæœŸï¼ˆç§’)", + "slaveAPIExpirationDes": "主機訪å•從機 API æ™‚ä½¿ç”¨çš„ç±¤åæœ‰æ•ˆæœŸã€‚", + "uploadSessionTimeout": "上傳會話有效期 (ç§’)", + "uploadSessionDes": "åœ¨ä¸Šå‚³æœƒè©±æœ‰æ•ˆæœŸå…§ï¼Œå°æ–¼æ”¯æ´çš„儲存策略,使用者å¯ä»¥æ–·é»žçºŒå‚³æœªå®Œæˆçš„任務。最大å¯è¨­å®šçš„值å—陿–¼ä¸åŒå„²å­˜ç­–ç•¥æœå‹™å•†çš„è¦å‰‡ã€‚", + "archiveTimeout": "æœå‹™ç«¯æ‰“包下載會話有效期 (ç§’)", + "advanceOptions": "高階設定", + "emojiOptions": "Emoji é¸é …", + "addCategorize": "新增分類", + "category": "分類", + "searchQuery": "檔案分類查詢", + "importWopi": "匯入 WOPI 應用設定", + "wopiEndpoint": "WOPI Discovery Endpoint", + "wopiDes": "通éŽå°æŽ¥æ”¯æ´ WOPI å”議的線上檔案處ç†ç³»çµ±ï¼Œæ“´å……套件 Cloudreve 的檔案線上é è¦½å’Œç·¨è¼¯èƒ½åŠ›ã€‚è«‹åœ¨æ­¤å¡«å¯« WOPI æœå‹™ç™¼ç¾åœ°å€ï¼Œæ¯”如 <0>https://example.com/hosting/discovery。詳細請åƒé–± <1>官方檔案。", + "embeddedWebpageViewer": "嵌入網é å¼æ‡‰ç”¨", + "wopiViewer": "WOPI å”議應用", + "ext": "副檔å", + "invalidWopiActionMapping": "WOPI Action å°æ˜ ç„¡æ•ˆ", + "woapiActionMapping": "WOPI Action å°æ˜ ", + "drawioHost": "DrawIO 例項", + "drawioHostDes": "ä½ å¯ä»¥å¡«å¯«è‡ªå»ºä¾‹é …的地å€ã€‚", + "openInNew": "在新視窗直接開啟", + "openInNewDes": "勾é¸å¾Œï¼Œæœƒç›´æŽ¥å½ˆå‡ºæ–°æ¨™ç±¤é–‹å•Ÿæ­¤æ‡‰ç”¨ã€‚", + "maxSize": "最大檔案大å°", + "maxSizeDes": "此應用支æ´çš„æœ€å¤§æª”案大å°ï¼Œå¡«å¯« 0 表示ä¸é™åˆ¶ï¼Œè¶…å‡ºå¤§å°æ™‚仿œƒå˜—試開啟檔案,但會警告使用者。", + "srcEncodedVar": "ç¶“éŽ URL 編碼後的檔案 Blob 臨時訪å•地å€", + "srcVar": "檔案 Blob 臨時訪å•地å€", + "srcBase64Var": "ç¶“éŽ Base64 編碼後的檔案 Blob 臨時訪å•地å€", + "nameEncodedVar": "ç¶“éŽ URL 編碼後的檔å", + "versionEntityVar": "開啟的檔案版本 Blob ID,為空時表示開啟的是最新版本。", + "fileIdVar": "檔案 ID", + "userIdVar": "使用者 ID,未登入時為空。", + "userDisplayNameVar": "ç¶“éŽ URL 編碼後的使用者暱稱。", + "fileViewers": "檔案ç€è¦½æ‡‰ç”¨", + "addViewer": "新增應用", + "viewerGroupTitle": "應用分組 #{{index}}", + "viewerType": "型別", + "viewerPlatform": "å¹³å°", + "viewerPlatformDes": "鏿“‡å°æ‡‰çš„å¹³å°ï¼Œè®“æ‡‰ç”¨åƒ…åœ¨å°æ‡‰å¹³å°ä¸Šå±•示。", + "viewerPlatformPC": "電腦端", + "viewerPlatformMobile": "行動端", + "viewerPlatformAll": "全平å°", + "displayName": "å稱", + "displayNameDes": "展示åç¨±ï¼Œæ”¯æ´ i18next éµå€¼ã€‚", + "viewerEnabled": "啟用", + "newFileAction": "æ–°å»ºæª”æ¡ˆå°æ˜ ", + "newFileActionDes": "æ–°å¢žå°æ˜ å¾Œï¼Œä½¿ç”¨è€…點é¸â€œæ–°å»ºâ€æŒ‰éˆ•å¾Œæœƒå‡ºç¾æ­¤æ‡‰ç”¨çš„é¸é …。", + "addNewFileAction": "æ–°å¢žå°æ˜ ", + "builtinViewerType": "內建應用", + "wopiViewerType": "WOPI", + "customViewerType": "自定義", + "nMapping": "{{num}} 個", + "editViewerTitle": "編輯 {{name}}", + "builtInIconUrlDes": "此內建應用有é è¨­åœ–示,圖示地å€ç•™ç©ºæ™‚會使用é è¨­åœ–示。", + "viewerUrl": "應用 URL", + "viewerUrlDes": "自定義應用的 URL 地å€ï¼Œæ”¯æ´ä½¿ç”¨ <0>魔法變數。", + "addIcon": "新增圖示", + "exts": "副檔å列表", + "icon": "圖示", + "iconUrl": "圖示地å€", + "iconColor": "圖示é¡è‰²", + "iconColorDark": "圖示é¡è‰²ï¼ˆé»‘暗模å¼ï¼‰", + "fileIcons": "檔案圖示", + "builtinIcon": "內建圖示", + "mimeMapping": "MIME åž‹åˆ¥å°æ˜ ", + "mimeMappingDes": "JSON æ ¼å¼çš„ MIME åž‹åˆ¥å°æ˜ è¡¨ï¼Œéµç‚ºå‰¯æª”å,值為 MIME 型別。Cloudreve 會根據副檔å和此設定判斷檔案 MIME 型別。", + "mapProvider": "地圖æä¾›å•†", + "mapProviderDes": "展示媒體ä½ç½®è³‡è¨Šæ™‚使用的地圖æä¾›å•†ã€‚", + "mapGoogle": "Google Maps (Leaflet)", + "mapOpenStreetMap": "OpenStreetMap (Leaflet)", + "mapboxMap": "OpenStreetMap (Mapbox)", + "mapboxAccessToken": "Mapbox 密鑰", + "mapboxAccessTokenDes": "在 <0>Mapbox æŽ§åˆ¶å° å‰µå»ºçš„å…¬é–‹å¯†é‘°ã€‚", + "tileType": "é è¨­åœ°åœ–型別", + "tileTypeDes": "Google Maps é è¨­åœ°åœ–型別。", + "tileTypeTerrain": "地形", + "tileTypeSatellite": "衛星", + "tileTypeGeneral": "常è¦", + "maxPageSize": "最大分é å¤§å°", + "maxPageSizeDes": "é™åˆ¶ä½¿ç”¨è€…å¯èª¿æ•´çš„æ¯é æœ€å¤§æª”案數é‡ã€‚", + "maxRecursiveSearch": "最大éžè¿´æœå°‹æ•¸é‡", + "maxRecursiveSearchDes": "使用者æœå°‹æª”案時,如果已æœå°‹çš„æª”案數é‡è¶…出此é™åˆ¶ï¼Œæœå°‹æœƒåœæ­¢ä¸¦è­¦å‘Šä½¿ç”¨è€…。", + "maxBatchSize": "最大批é‡é‹ç®—å…ƒé‡", + "maxBatchSizeDes": "ä½¿ç”¨è€…å¯æ‰¹é‡æ“作的最大檔案數é‡ï¼Œåªæœƒçµ±è¨ˆé ‚層數é‡ï¼Œå­ç›®éŒ„下的檔案數é‡ä¸æœƒè¨ˆå…¥ã€‚", + "defaultPagination": "æª”æ¡ˆåˆ—è¡¨åˆ†é æ–¹å¼", + "cursorPagination": "éŠæ¨™åˆ†é ", + "cursorPaginationDes": "ä½¿ç”¨è€…æ»¾å‹•åˆ°åº•ç«¯å¾Œæœƒè‡ªå‹•è¼‰å…¥æ›´å¤šæª”æ¡ˆï¼Œå°æ–¼å¤§é‡æª”æ¡ˆåˆ—è¡¨æ•ˆèƒ½è¼ƒå¥½ï¼Œä½†ç„¡æ³•çœ‹åˆ°ç¸½é æ•¸ã€‚", + "offsetPagination": "傳統分é ", + "offsetPaginationDes": "é é¢åº•部會展示分é å°Žèˆªï¼Œä½¿ç”¨è€…å¯ä»¥çœ‹åˆ°ç¸½é æ•¸ä¸¦è·³è½‰åˆ°æŸä¸€é ï¼Œåœ¨å°æ–¼å¤§é‡æª”案列表下效能較差。", + "defaultPaginationDes": "無論上é¢è¨­å®šå¦‚何,使用者在æœå°‹æ™‚æœƒå¼·åˆ¶ä½¿ç”¨éŠæ¨™åˆ†é ã€‚", + "publicResourceMaxAge": "éœæ…‹è³‡æºå¿«å–有效期 (ç§’)", + "publicResourceMaxAgeDes": "用於告知ç€è¦½å™¨æˆ– CDN å¿«å–éœæ…‹è³‡æºçš„æœ‰æ•ˆæœŸï¼Œå–®ä½ç‚ºç§’。影響範åœåŒ…括檔案ã€ç¸®åœ–和使用者頭åƒã€‚", + "cronDes": "{{des}},此處需è¦å¡«å¯«æ­£ç¢ºçš„ <0>Cron 表示å¼ã€‚é‡å•Ÿ Cloudreve 後生效。", + "entityCollectInterval": "檔案 Blob 回收間隔", + "entityCollectIntervalDes": "設定多久掃æä¸¦åˆªé™¤éŽæœŸçš„æª”案 Blob", + "trashBinInterval": "回收站掃æé–“éš”", + "trashBinIntervalDes": "設定多久掃æä¸¦åˆªé™¤å›žæ”¶ç«™ä¸­çš„éŽæœŸæª”案", + "logtoName": "登入方å¼å稱", + "logtoNameDes": "用於展示給使用者的登入方å¼å稱,é è¨­ç‚º “SSOâ€ï¼Œæ”¯æ´ i18next éµå€¼ã€‚", + "logtoDirectSSO": "ç›´é”第三方登入", + "logtoDirectSSODes": "如果你想è¦è·³éŽ Logto ç™»å…¥èž¢å¹•ï¼Œç›´æŽ¥è·³è½‰åˆ°å°æŽ¥çš„ç¬¬ä¸‰æ–¹ç™»å…¥æˆ– SSO,請在此填寫第三方登入è¯çµå™¨çš„æ¨™è­˜ï¼Œè©³æƒ…è«‹åƒè€ƒ <0>Logto 檔案。", + "logtoEndpoint": "Logto 端點", + "logtoEndpointDes": "應用管ç†é¢æ¿ç²å–到的 Logto 端點地å€ï¼Œå¯ä»¥ç‚ºè‡ªå·±éƒ¨ç½²çš„例項。", + "logtoKey": "應用金鑰", + "logtoKeyDes": "應用管ç†é é¢å»ºç«‹çš„æ‡‰ç”¨é‡‘鑰。", + "logtoAppIDDes": "應用管ç†é é¢å»ºç«‹çš„æ‡‰ç”¨ ID。", + "logto": "Logto", + "logtoDes": "借由 <0>Logto, ä½ å¯ä»¥å¯¦ç¾æ›´å¤šç¬¬ä¸‰æ–¹å¹³è‡ºçš„互è¯ç™»å…¥ï¼Œæ¯”如 Appleã€GitHubã€Microsoft Entra IDã€Googleã€ç°¡è¨Šç­‰ã€‚請在 Logto 管ç†é¢æ¿å»ºç«‹ä¸€å€‹ â€œå‚³çµ±ç¶²é æ‡‰ç”¨â€ï¼Œä¸¦å°‡ <1>{{url}} 加入到 “é‡å®šå‘ URIs†中。", + "thirdPartySignIn": "第三方登入", + "logo": "LOGO", + "logoDes": "LOGO 影象的地å€ï¼Œç”¨æ–¼åœ¨å·¦ä¸Šè§’展示;請分別æä¾›é»‘暗模å¼å’Œæ—¥é–“模å¼ä¸‹ä¸åŒçš„ LOGO", + "dark": "黑暗模å¼", + "light": "日間模å¼", + "tosUrl": "ä½¿ç”¨æ¢æ¬¾é€£çµ", + "tosUrlDes": "用於在使用者登入或注冊é å°¾å±•示,留空ä¸å±•示。", + "privacyUrl": "éš±ç§æ”¿ç­–連çµ", + "privacyUrlDes": "用於在使用者登入或注冊é å°¾å±•示,留空ä¸å±•示。", + "addSecondary": "新增備é¸ç«™é»ž URL", + "secondarySiteURL": "å‚™é¸", + "secondaryDes": "ä½ é‚„å¯ä»¥æ–°å¢žå…¶ä»–å‚™é¸ç«™é»ž URL,Cloudreve 會根據使用者實際訪å•çš„ URL è‡ªå‹•é¸æ“‡æ˜¯å¦ä½¿ç”¨ã€‚", + "primarySiteURL": "主è¦", + "primarySiteURLDes": "主è¦ç«™é»ž URL 用於與外部æœå‹™é€šè¨Šå’ŒæŽ¥å—回撥(比如:儲存æä¾›å•†ï¼‰ï¼Œè«‹ä½¿ç”¨èƒ½è¢«å…¬ç¶²è¨ªå•çš„ URL。", + "revert": "撤銷更改", + "saved": "設定已更改", + "save": "儲存", + "basicInformation": "基本資訊", + "mainTitle": "站點å稱", + "mainTitleDes": "站點的å稱。", + "siteDescription": "站點æè¿°", + "siteDescriptionDes": "站點æè¿°è³‡è¨Šï¼Œå¯èƒ½æœƒåœ¨åˆ†äº«é é¢æ‘˜è¦å…§å±•示。", + "siteURL": "站點 URL", + "customFooterHTML": "é å°¾ç¨‹å¼ç¢¼", + "customFooterHTMLDes": "在é é¢åº•部æ’入的自定義 HTML 程å¼ç¢¼ã€‚", + "announcement": "站點公告", + "announcementDes": "展示給已登陸使用者的公告,留空ä¸å±•ç¤ºã€‚ç•¶æ­¤é …å…§å®¹æ›´æ”¹æ™‚ï¼Œæ‰€æœ‰ä½¿ç”¨è€…æœƒé‡æ–°çœ‹åˆ°å…¬å‘Šã€‚", + "supportHTML": "æ”¯æ´ HTML 程å¼ç¢¼", + "branding": "圖示", + "smallIcon": "å°åœ–示", + "smallIconDes": "å°åœ–示地å€ï¼Œico 或 svg æ ¼å¼ã€‚此圖示還會被用於在ç€è¦½å™¨åˆ†é ã€æ›¸ç±¤åŠæ¡Œé¢æ·å¾‘ç­‰ä½ç½®é¡¯ç¤ºã€‚", + "mediumIcon": "中圖示", + "mediumIconDes": "192x192 的中圖示地å€ï¼Œpng æ ¼å¼ã€‚", + "largeIcon": "大圖示", + "largeIconDes": "512x512 的大圖示地å€ï¼Œpng æ ¼å¼ã€‚此圖示還會被用於在 iOS 客戶端切æ›ç«™é»žæ™‚顯示。", + "displayMode": "展示模å¼", + "displayModeDes": "PWA 應用新增後的展示模å¼ã€‚", + "themeColor": "主題色", + "themeColorDes": "CSS 色值,影響 PWA 啟動畫é¢ä¸Šç‹€æ…‹åˆ—ã€å…§å®¹é ä¸­ç‹€æ…‹åˆ—ã€ä½å€åˆ—çš„é¡è‰²ã€‚", + "backgroundColor": "背景色", + "backgroundColorDes": "CSS 色值", + "hint": "æç¤º", + "webauthnNoHttps": "Web Authn 需è¦ä½ çš„站點啟用 HTTPSï¼Œä¸¦ç¢ºèª å¼•æ•¸è¨­å®š - 站點資訊 - 站點URL 也使用了 HTTPS 後æ‰èƒ½é–‹å•Ÿã€‚", + "accountManagement": "注冊與登入", + "allowNewRegistrations": "å…許新使用者注冊", + "allowNewRegistrationsDes": "關閉後,無法å†é€šéŽå‰è‡ºæ³¨å†Šæ–°çš„使用者。", + "emailActivation": "郵件啟用", + "emailActivationDes": "開啟後,新使用者注冊需è¦é»žé¸éƒµä»¶ä¸­çš„å•Ÿç”¨é€£çµæ‰èƒ½å®Œæˆã€‚è«‹ç¢ºèª <0>郵件發信設定 æ˜¯å¦æ­£ç¢ºï¼Œå¦å‰‡å•Ÿç”¨éƒµä»¶ç„¡æ³•é€é”。", + "captchaForSignup": "注冊驗證碼", + "captchaForSignupDes": "是å¦å•Ÿç”¨æ³¨å†Šè¡¨å–®é©—證碼。", + "captchaForLogin": "登入驗證碼", + "captchaForLoginDes": "是å¦å•Ÿç”¨ç™»å…¥è¡¨å–®é©—證碼。", + "captchaForReset": "找回密碼驗證碼", + "captchaForResetDes": "是å¦å•Ÿç”¨æ‰¾å›žå¯†ç¢¼è¡¨å–®é©—證碼。", + "captchaForAbuseReport": "濫用舉報驗證碼", + "captchaForAbuseReportDes": "是å¦å•Ÿç”¨æ¿«ç”¨èˆ‰å ±è¡¨å–®é©—證碼。", + "webauthnDes": "是å¦å…許使用者使用繫çµçš„硬體èªè­‰è£ç½®ç™»å…¥ï¼Œæ¯”å¦‚ï¼šäººè‡‰ã€æŒ‡ç´‹æˆ– USB 金鑰;站點必須啟用 HTTPS æ‰èƒ½ä½¿ç”¨ã€‚", + "webauthn": "使用通行金鑰登入", + "defaultSymbolics": "é è¨­åˆ†äº«å¿«æ·æ–¹å¼", + "defaultSymbolicsDes": "新使用者根目錄下é è¨­å­˜åœ¨çš„分享連çµå¿«æ·æ–¹å¼ã€‚è«‹é€šéŽæ•¸å­— ID æœå°‹åˆ†äº«é€£çµï¼Œä½ å¯åœ¨ <0>分享列表 最左å´çœ‹åˆ°æ•¸å­— ID。", + "searchShare": "æœå°‹åˆ†äº« ID...", + "defaultGroup": "é è¨­ä½¿ç”¨è€…組", + "defaultGroupDes": "使用者注冊後的åˆå§‹ä½¿ç”¨è€…組。", + "testMailSent": "測試郵件已傳é€", + "testSMTPSettings": "發件測試", + "testSMTPTooltip": "Cloudreve æœƒä½¿ç”¨ä½ ç•¶å‰ SMTP è¨­å®šå‚³é€æ¸¬è©¦éƒµä»¶ï¼Œæ¸¬è©¦å‰ç„¡éœ€å„²å­˜è¨­å®šã€‚", + "recipient": "收件人地å€", + "send": "傳é€", + "smtp": "發信", + "senderName": "發件人å", + "senderNameDes": "郵件中展示的發件人姓å。", + "senderAddress": "發件人郵箱", + "senderAddressDes": "發件郵箱的地å€ã€‚", + "smtpServer": "SMTP 伺æœå™¨", + "smtpServerDes": "發件伺æœå™¨åœ°å€ï¼Œä¸å«åŸ è™Ÿã€‚", + "smtpPort": "SMTP 埠", + "smtpPortDes": "發件伺æœå™¨åŸ è™Ÿã€‚", + "smtpUsername": "SMTP 使用者å稱", + "smtpUsernameDes": "發信郵箱使用者å稱,一般與郵箱地å€ç›¸åŒã€‚", + "smtpPassword": "SMTP 密碼", + "smtpPasswordDes": "發信郵箱密碼。", + "replyToAddress": "回信郵箱", + "replyToAddressDes": "使用者回復系統傳é€çš„郵件時,用於接收回信的郵箱。", + "enforceSSL": "強制使用 SSL 連線", + "enforceSSLDes": "是å¦å¼·åˆ¶ä½¿ç”¨ SSL 加密連線。如果無法傳é€éƒµä»¶ï¼Œå¯é—œé–‰æ­¤é …,Cloudreve 會嘗試使用 STARTTLS 並決定是å¦ä½¿ç”¨åŠ å¯†é€£ç·šã€‚", + "smtpTTL": "SMTP 連線有效期 (ç§’)", + "smtpTTLDes": "有效期內建立的 SMTP 連線會被新郵件傳é€è«‹æ±‚復用。", + "emailTemplates": "郵件模æ¿", + "activateNewUser": "新使用者啟用", + "resetPassword": "é‡ç½®å¯†ç¢¼", + "sendTestEmail": "傳逿¸¬è©¦éƒµä»¶", + "transportation": "傳輸", + "workerNum": "Worker 數é‡", + "workerNumDes": "主機節點任務佇列最多並行執行的任務數,儲存後需è¦é‡å•Ÿ Cloudreve 生效", + "tempFolder": "臨時目錄", + "tempFolderDes": "用於存放解壓縮ã€å£“縮等任務產生的臨時檔案的目錄路徑", + "textEditMaxSize": "檔案線上編輯最大大å°", + "textEditMaxSizeDes": "檔案檔案å¯ç·šä¸Šç·¨è¼¯çš„æœ€å¤§å¤§å°ï¼Œè¶…出此大å°çš„æª”案無法線上編輯。此項設定é©ç”¨æ–¼ç´”文字檔案ã€ç¨‹å¼ç¢¼æª”案ã€Office 檔案(WOPI)等 Web 線上編輯器。", + "resetConnection": "上傳校驗失敗時強制é‡ç½®é€£ç·š", + "resetConnectionDes": "開啟後,如果本次策略ã€é ­åƒç­‰è³‡æ–™ä¸Šå‚³æ ¡é©—失敗,伺æœå™¨æœƒå¼·åˆ¶é‡ç½®é€£ç·š", + "batchDownload": "打包下載", + "previewURL": "é è¦½é€£çµ", + "cannotDeleteDefaultTheme": "ä¸èƒ½åˆªé™¤é è¨­é…色", + "themeConfig": "色彩é…ç½®", + "actions": "æ“作", + "wrongFormat": "æ ¼å¼ä¸æ­£ç¢º", + "avatar": "é ­åƒ", + "gravatarServer": "Gravatar 伺æœå™¨", + "gravatarServerDes": "Gravatar 伺æœå™¨åœ°å€ï¼Œå¯é¸æ“‡ä½¿ç”¨åœ‹å…§æ˜ è±¡ã€‚", + "avatarFilePath": "é ­åƒå„²å­˜è·¯å¾‘", + "avatarFilePathDes": "使用者上傳自定義頭åƒçš„å„²å­˜è·¯å¾‘ï¼Œç›¸å°æ–¼ Cloudreve 資料目錄。", + "avatarSize": "é ­åƒæª”案大å°é™åˆ¶", + "avatarSizeDes": "使用者å¯ä¸Šå‚³é ­åƒæª”案的最大大å°ã€‚", + "avatarImageSize": "影象尺寸 (px)", + "avatarImageSizeDes": "ä½¿ç”¨è€…æ‰€ä¸Šå‚³é ­åƒæœƒè¢«èª¿æ•´åˆ°çµ¦å®šçš„尺寸,單ä½ç‚ºç•«ç´ ã€‚", + "filePreview": "檔案é è¦½", + "thumbnails": "縮圖", + "thumbnailDoc": "有關é…置縮圖的更多資訊,請åƒé–± <0>官方檔案。", + "thumbnailDocLink": "https://docs.cloudreve.org/usage/thumbnails", + "thumbnailBasic": "基本設定", + "generators": "縮圖生æˆå™¨", + "thumbMaxSize": "最大原始檔案尺寸", + "thumbMaxSizeDes": "å¯ç”Ÿæˆç¸®åœ–的最大原始檔案的大å°ï¼Œè¶…出此大å°çš„æª”æ¡ˆä¸æœƒç”Ÿæˆç¸®åœ–。", + "generatorProxyWarning": "é è¨­æƒ…æ³ä¸‹ï¼Œéžæœ¬æ©Ÿå„²å­˜ç­–ç•¥åªæœƒä½¿ç”¨â€œå„²å­˜ç­–略原生â€ç”Ÿæˆå™¨ã€‚ä½ å¯ä»¥åœ¨å„²å­˜ç­–略設定é é¢é–‹å•Ÿâ€œç”Ÿæˆå™¨ä»£ç†â€åŠŸèƒ½æ“´å……å¥—ä»¶ç¬¬ä¸‰æ–¹å„²å­˜ç­–ç•¥çš„ç¸®åœ–èƒ½åŠ›ã€‚è©³ç´°è«‹åƒé–± <0>官方檔案。", + "policyBuiltin": "儲存策略原生", + "policyBuiltinDes": "使用儲存æä¾›æ–¹åŽŸç”Ÿçš„å½±è±¡è™•ç†ä»‹é¢ã€‚å°æ–¼æœ¬æ©Ÿå’Œ S3 策略,這一生æˆå™¨ä¸å¯ç”¨ï¼Œå°‡æœƒè‡ªå‹•順沿其他生æˆå™¨ã€‚å°æ–¼å…¶ä»–儲存策略,請å‰å¾€å„²å­˜ç­–略設定é é¢è¨­å®šå…許的副檔å。", + "cloudreveBuiltin": "Cloudreve 內建", + "cloudreveBuiltinDes": "使用 Cloudreve 內建的影象處ç†èƒ½åŠ›ï¼Œåƒ…æ”¯æ´ PNGã€JPEGã€GIF æ ¼å¼çš„圖片。", + "libreOffice": "LibreOffice", + "libreOfficeDes": "使用 LibreOffice ç”Ÿæˆ Office 檔案的縮圖。這一生æˆå™¨ä¾è³´æ–¼ä»»ä¸€å…¶ä»–影象生æˆå™¨ï¼ˆCloudreve 內建 或 VIPS)。", + "libraw": "LibRaw / DCRaw", + "librawDes": "使用 LibRaw 附帶的 DCRaw 模擬例程,或者原始 DCRaw 程å¼ç”Ÿæˆ RAW 影象的縮圖。", + "vips": "VIPS", + "vipsDes": "使用 libvips 處ç†ç¸®åœ–å½±è±¡ï¼Œæ”¯æ´æ›´å¤šå½±è±¡æ ¼å¼ï¼Œè³‡æºæ¶ˆè€—更低。", + "thumbDependencyWarning": "LibreOffice 或歌曲å°é¢ç”Ÿæˆå™¨ä¾è³´æ–¼ Cloudreve 內建 或 VIPS 生æˆå™¨ï¼Œè«‹é–‹å•Ÿå…¶ä¸­ä»»ä¸€ç”Ÿæˆå™¨ã€‚", + "ffmpeg": "FFmpeg", + "ffmpegDes": "使用 FFmpeg 生æˆè¦–訊縮圖。", + "executable": "å¯åŸ·è¡Œæª”案", + "executableDes": "第三方生æˆå™¨å¯åŸ·è¡Œæª”案的路徑或命令。", + "executableTest": "測試", + "executableTestSuccess": "生æˆå™¨æ­£å¸¸ï¼Œç‰ˆæœ¬ï¼š{{version}}", + "generatorExts": "å¯ç”¨å‰¯æª”å", + "generatorExtsDes": "此生æˆå™¨å¯ç”¨çš„副檔å列表,多個請使用åŠå½¢é€—號 , 隔開。", + "ffmpegSeek": "縮圖擷å–ä½ç½®", + "ffmpegSeekDes": "定義縮圖擷å–çš„æ™‚é–“ï¼ŒæŽ¨è–¦é¸æ“‡è¼ƒå°å€¼ä»¥åŠ é€Ÿç”ŸæˆéŽç¨‹ã€‚如果超出視訊實際長度,會導致縮圖擷å–失敗", + "ffmpegExtraArgs": "é¡å¤–è¼¸å…¥åƒæ•¸", + "ffmpegExtraArgsDes": "å‘¼å« FFmpeg 時é¡å¤–è¼¸å…¥çš„åƒæ•¸ã€‚", + "generatorProxy": "生æˆå™¨ä»£ç†", + "enableThumbProxy": "使用生æˆå™¨ä»£ç†", + "proxyPolicyList": "啟動代ç†çš„儲存策略", + "proxyPolicyListDes": "å¯å¤šé¸ã€‚é¸ä¸­å¾Œï¼Œå„²å­˜ç­–ç•¥ä¸æ”¯æ´åŽŸç”Ÿç”Ÿæˆç¸®åœ–的型別會由 Cloudreve 代ç†ç”Ÿæˆ", + "thumbWidth": "最大寬度", + "thumbHeight": "最大高度", + "thumbSuffix": "Blob 檔案字尾", + "thumbSuffixDes": "生æˆçš„縮圖 Blob ç›¸å°æ–¼åŽŸå§‹ Blob 增加的字尾,", + "thumbFormat": "縮圖格å¼", + "thumbFormatDes": "優先使用的縮圖格å¼ï¼Œå¦‚果生æˆå™¨ä¸æ”¯æ´ï¼Œæœƒè‡ªå‹•é™ç´šç‚º jpg æ ¼å¼ã€‚", + "thumbQuality": "影象質é‡", + "thumbQualityDes": "壓縮質é‡ç™¾åˆ†æ¯”,åªé‡å° jpg å’Œ webp æ ¼å¼æœ‰æ•ˆã€‚", + "thumbGC": "生æˆå®Œæˆå¾Œç«‹å³å›žæ”¶è¨˜æ†¶é«”", + "captcha": "驗證碼", + "captchaType": "驗證碼型別", + "captchaTypeDes": "鏿“‡é©—證碼型別和驗證碼æœå‹™æä¾›å•†ã€‚", + "plainCaptcha": "圖形", + "reCaptchaV2": "reCAPTCHA V2", + "turnstile": "Cloudflare Turnstile", + "turnstileSiteKey": "站點金鑰", + "turnstileSiteKSecret": "金鑰", + "cap": "Cap", + "capInstanceURL": "實例 URL", + "capInstanceURLDes": "自部署 Cap 伺æœå™¨çš„ URL 地å€ã€‚詳細資訊請åƒè€ƒ <0>ç¨ç«‹æ¨¡å¼æ–‡æª”。", + "capSiteKey": "站點金鑰", + "capSiteKeyDes": "從 Cap 伺æœå™¨æŽ§åˆ¶é¢æ¿ç²å–的站點金鑰。", + "capSecretKey": "ç§å¯†é‡‘é‘°", + "capSecretKeyDes": "從 Cap 伺æœå™¨æŽ§åˆ¶é¢æ¿ç²å–çš„ç§å¯†é‡‘鑰。", + "capAssetServer": "éœæ…‹è³‡æºæœå‹™æº", + "capAssetServerDes": "鏿“‡ Cap é©—è­‰ç¢¼éœæ…‹è³‡æºçš„載入æºã€‚使用自部署伺æœå™¨éœ€è¦åœ¨ä¼ºæœå™¨ç«¯è¨­å®šç’°å¢ƒè®Šæ•¸ï¼Œè«‹åƒè€ƒ <0>é–‹å•Ÿéœæ…‹è³‡æºæœå‹™ã€‚", + "capAssetServerJsdelivr": "jsDelivr CDN", + "capAssetServerUnpkg": "unpkg CDN", + "capAssetServerInstance": "自部署伺æœå™¨", + "captchaProvider": "驗證碼型別", + "captchaWidth": "寬度", + "captchaHeight": "高度", + "captchaLength": "長度", + "captchaLengthDes": "驗證碼中字符的長度。", + "captchaMode": "模å¼", + "captchaModeNumber": "數字", + "captchaModeLetter": "å­—æ¯", + "captchaModeMath": "算數", + "captchaModeNumberLetter": "數字+å­—æ¯", + "captchaElement": "驗證碼的形å¼", + "complexOfNoiseText": "加強幹擾文字", + "complexOfNoiseDot": "加強幹擾點", + "showHollowLine": "使用空心線", + "showNoiseDot": "使用噪點", + "showNoiseText": "使用幹擾文字", + "showSlimeLine": "使用波浪線", + "showSineLine": "使用正弦線", + "siteKey": "Site KEY", + "siteKeyDes": "<0>應用管ç†é é¢ ç²å–到的的 網站金鑰。", + "siteSecret": "Secret", + "siteSecretDes": "<0>應用管ç†é é¢ ç²å–到的的 祕鑰。", + "secretID": "SecretId", + "secretIDDes": "<0>訪å•金鑰é é¢ ç²å–到的的 SecretId", + "secretKey": "SecretKey", + "secretKeyDes": "<0>訪å•金鑰é é¢ ç²å–到的的 SecretKey", + "tCaptchaAppID": "APPID", + "tCaptchaAppIDDes": "<0>圖形驗證é é¢ ç²å–到的的 APPID", + "tCaptchaSecretKey": "App Secret Key", + "tCaptchaSecretKeyDes": "<0>圖形驗證é é¢ ç²å–到的的 App Secret Key", + "staticResourceCache": "éœæ…‹å…¬å…±è³‡æºå¿«å–", + "staticResourceCacheDes": "公共å¯è¨ªå•çš„éœæ…‹è³‡æºï¼ˆå¦‚:本機策略直éˆã€æª”案下載連çµï¼‰çš„å¿«å–æœ‰æ•ˆæœŸ", + "creditSystem": "ç©åˆ†ç³»çµ±", + "creditAndVAS": "ç©åˆ†èˆ‡å¢žå€¼æœå‹™", + "enableCredit": "啟用ç©åˆ†ç³»çµ±", + "enableCreditDes": "啟用ç©åˆ†ç³»çµ±ï¼Œå…許使用者為分享連çµè¨­å®šåƒ¹æ ¼ã€‚", + "creditPrice": "ç©åˆ†åƒ¹æ ¼", + "creditPriceDes": "使用貨幣充值ç©åˆ†çš„價格(以最å°è²¨å¹£å–®ä½è¨ˆï¼‰ï¼Œå¡«å¯« 0 è¡¨ç¤ºç¦æ­¢å……值ç©åˆ†ã€‚", + "shareScoreRate": "分享者傭金比例", + "shareScoreRateDes": "分享連çµè¢«è³¼è²·æ™‚,分享者ç²å¾—çš„ç©åˆ†ç™¾åˆ†æ¯”(1-100)。", + "cronNotifyUser": "通知超é¡ä½¿ç”¨è€…掃æé–“éš”", + "cronNotifyUserDes": "掃æä¸¦å‚³é€éƒµä»¶æé†’è¶…é¡ä½¿ç”¨è€…", + "cronBanUser": "使用者å°ç¦æŽƒæé–“éš”", + "cronBanUserDes": "掃æä¸¦å°ç¦è¶…å‡ºå„²å­˜ä¸”è¶…å‡ºç·©è¡æœŸçš„使用者", + "anonymousPurchase": "匿å購買", + "anonymousPurchaseDes": "å…許未登入使用者直接購買分享連çµã€‚", + "shopNavEnabled": "顯示商店導航", + "shopNavEnabledDes": "在å´é‚Šæ¬„å°Žèˆªä¸­é¡¯ç¤ºâ€œå•†åº—â€æ¢ç›®ã€‚", + "paymentSettings": "支付設定", + "currencyCode": "貨幣程å¼ç¢¼", + "currencyCodeDes": "三字æ¯è²¨å¹£ç¨‹å¼ç¢¼ï¼ˆå¦‚ USDã€CNYã€EUR)。", + "currencySymbol": "貨幣符號", + "currencySymbolDes": "顯示的貨幣符號(如 $ã€Â¥ã€â‚¬ï¼‰ã€‚", + "currencyUnit": "貨幣單ä½", + "currencyUnitDes": "最å°è²¨å¹£å–®ä½ï¼ˆå¦‚美元/分為100)。", + "paymentProviders": "支付æä¾›å•†", + "providerName": "æä¾›å•†å稱,用於展示給使用者。", + "providerType": "æä¾›å•†åž‹åˆ¥", + "providerKey": "金鑰", + "selectCurrency": "鏿“‡å¸¸ç”¨è²¨å¹£", + "addPaymentProvider": "新增支付æä¾›å•†", + "stripeProvider": "Stripe", + "weixinProvider": "微信支付", + "alipayProvider": "支付寶", + "customProvider": "自定義支付渠é“", + "customProviderDes": "通éŽå¯¦ç¾ Cloudreve 相容付款介é¢ä¾†å°æŽ¥å…¶ä»–第三方支付平臺,詳情請åƒè€ƒ <0>官方檔案。", + "providerKeyDes": "輸入 Stripe çš„ API 金鑰。", + "storageProductSettings": "儲存產å“", + "storageProductsDes": "é…置使用者å¯ä»¥è³¼è²·ä»¥æ“´å……套件儲存空間的產å“。", + "addStorageProduct": "新增產å“", + "editStorageProduct": "編輯產å“", + "storageSize": "儲存大å°", + "storageSizeBytes": "此產å“包å«çš„儲存大å°ã€‚", + "duration": "時長", + "durationSeconds": "時長(秒,例如:2592000 表示 30 天)。", + "price": "價格", + "priceInUnits": "價格(以最å°è²¨å¹£å–®ä½è¨ˆï¼‰", + "priceInUnitsDes": "價格將顯示為:", + "chipLabel": "標籤(å¯é¸ï¼‰", + "chipLabelHelp": "顯示在產å“å稱æ—邊的短文字標籤。", + "usePoints": "å…許使用ç©åˆ†", + "points": "ç©åˆ†", + "pointsHelp": "è³¼è²·æ­¤ç”¢å“æ‰€éœ€çš„ç©åˆ†æ•¸é‡ã€‚", + "pointsUnit": "ç©åˆ†", + "groupProductSettings": "使用者組產å“", + "groupProductsDes": "é…置使用者å¯ä»¥è³¼è²·ä»¥åŠ å…¥ç‰¹å®šä½¿ç”¨è€…çµ„çš„ç”¢å“。", + "addGroupProduct": "新增使用者組產å“", + "editGroupProduct": "編輯使用者組產å“", + "groupId": "使用者組 ID", + "groupIdHelp": "購買此產å“後å‡ç´šåˆ°çš„使用者組。", + "description": "æè¿°", + "descriptionHelp": "輸入特性或優勢,æ¯è¡Œä¸€é …", + "receiptEmailTemplate": "支付收據模æ¿", + "receiptEmailTemplateDes": "ç•¶æ”¯ä»˜è¢«ç¢ºèªæ™‚傳é€çµ¦ä½¿ç”¨è€…的郵件模æ¿ã€‚", + "activationEmailTemplate": "賬戶啟用模æ¿", + "activationEmailTemplateDes": "當使用者啟用賬戶時傳é€çµ¦ä½¿ç”¨è€…的郵件模æ¿ã€‚", + "quotaExceededEmailTemplate": "儲存é…é¡è¶…出模æ¿", + "quotaExceededEmailTemplateDes": "當使用者超出儲存é…顿™‚傳é€çµ¦ä½¿ç”¨è€…的郵件模æ¿ã€‚", + "resetPasswordEmailTemplate": "密碼é‡ç½®æ¨¡æ¿", + "resetPasswordEmailTemplateDes": "當使用者請求é‡ç½®å¯†ç¢¼æ™‚傳é€çµ¦ä½¿ç”¨è€…的郵件模æ¿ã€‚", + "preferredLanguage": "首é¸èªžè¨€", + "setAsPreferredLanguage": "設為首é¸èªžè¨€", + "setAsPreferredLanguageDes": "如果無法ç²å–使用者的語言å好,將使用首é¸èªžè¨€çš„郵件模æ¿ã€‚", + "alreadyAsPreferredLanguageDes": "ç•¶å‰èªžè¨€å·²è¨­ç‚ºé¦–é¸èªžè¨€ã€‚如果無法ç²å–使用者的語言å好,將使用此郵件模æ¿ã€‚", + "addLanguage": "新增語言", + "removeLanguage": "移除語言", + "removeLanguageBtn": "移除語言", + "cannotRemovePreferredLanguageDes": "無法移除首é¸èªžè¨€ã€‚請設定其他語言為首é¸èªžè¨€å¾Œé‡è©¦ã€‚", + "languageCodeDes": "è«‹é¸æ“‡è¦æ–°å¢žçš„語言。", + "emailSubject": "郵件主題", + "emailSubjectDes": "郵件的主題。你å¯ä»¥ä½¿ç”¨ <0>魔法變數 來定製郵件主題。", + "emailBody": "郵件內容", + "emailBodyDes": "郵件的內容。你å¯ä»¥ä½¿ç”¨ <0>魔法變數 來定製郵件內容。", + "orderTitle": "訂單標題", + "themeOptions": "主題é¸é …", + "themeOptionsDes": "為你的站點é…置自定義主題é¸é …。這些主題將å¯ä¾›ä½¿ç”¨è€…在其åå¥½è¨­å®šä¸­é¸æ“‡ã€‚", + "primaryColor": "主色調", + "secondaryColor": "次色調", + "primaryColorDark": "主色調(暗色模å¼ï¼‰", + "secondaryColorDark": "次色調(暗色模å¼ï¼‰", + "addThemeOption": "新增主題é¸é …", + "editThemeOption": "編輯主題é¸é …", + "invalidThemeConfig": "無效的主題é…置。請檢查你的 JSON 語法。", + "themeConfiguration": "主題é…ç½®", + "themePreview": "主題é è¦½", + "lightTheme": "亮色主題", + "darkTheme": "暗色主題", + "previewTitle": "é è¦½æ¨™é¡Œ", + "previewTextField": "輸入欄ä½", + "previewPrimary": "主色調", + "invalidThemePreview": "無效的主題é…置,無法é è¦½", + "duplicateThemeColor": "å·²å­˜åœ¨ä½¿ç”¨æ­¤ä¸»è‰²èª¿çš„ä¸»é¡Œã€‚è«‹é¸æ“‡ä¸åŒçš„é¡è‰²ã€‚", + "themeDes": "完整的å¯é…置項請åƒè€ƒ <0>Material-UI Default theme viewer。", + "defaultTheme": "é è¨­", + "auditLog": "事件", + "auditLogDes": "é…置哪些事件應該被記錄。æŸäº›äº‹ä»¶å¯èƒ½æœƒè¢«ç³»çµ±ç”¨æ–¼æä¾›é¡å¤–功能,例如檔案活動和登入活動。", + "systemEvents": "系統事件", + "systemEventsDes": "與系統æ“作和狀態相關的事件。", + "userEvents": "使用者事件", + "userEventsDes": "與使用者賬戶ã€èªè­‰å’Œé…置檔案更改相關的事件。", + "fileEvents": "檔案事件", + "fileEventsDes": "與檔案æ“作相關的事件,如上傳ã€ä¸‹è¼‰å’Œä¿®æ”¹ã€‚", + "shareEvents": "分享事件", + "shareEventsDes": "與檔案分享和連çµè¨ªå•相關的事件。", + "versionEvents": "版本事件", + "versionEventsDes": "與檔案版本管ç†ç›¸é—œçš„事件。", + "mediaEvents": "媒體事件", + "mediaEventsDes": "與媒體檔案處ç†ç›¸é—œçš„事件,如縮圖生æˆã€‚", + "filesystemEvents": "檔案系統事件", + "filesystemEventsDes": "與檔案系統æ“作相關的事件,如掛載和歸檔處ç†ã€‚", + "webdavEvents": "WebDAV 事件", + "webdavEventsDes": "與 WebDAV 賬戶管ç†å’Œè¨ªå•相關的事件。", + "paymentEvents": "支付事件", + "paymentEventsDes": "與支付交易和處ç†ç›¸é—œçš„事件。", + "emailEvents": "Email 事件", + "emailEventsDes": "與郵件傳é€å’Œé€šçŸ¥ç›¸é—œçš„事件。", + "toggleAll": "啟用/ç¦ç”¨æ‰€æœ‰äº‹ä»¶", + "toggleAllDes": "啟用或ç¦ç”¨æ­¤é¡žåˆ¥ä¸­çš„æ‰€æœ‰äº‹ä»¶ã€‚", + "event": { + "file_imported": "外部檔案導入", + "server_start": "伺æœå™¨å•Ÿå‹•", + "user_signup": "使用者注冊", + "email_sent": "郵件傳é€", + "user_activated": "使用者啟用", + "user_login_failed": "登入失敗", + "user_login": "使用者登入", + "user_token_refresh": "ä»¤ç‰Œé‡æ–°æ•´ç†", + "file_create": "檔案建立", + "file_rename": "æª”æ¡ˆé‡æ–°å‘½å", + "set_file_permission": "è¨±å¯æ¬Šæ›´æ”¹", + "entity_uploaded": "檔案上傳或更新", + "entity_downloaded": "檔案下載", + "copy_from": "復制來æº", + "copy_to": "復制到", + "move_to": "移動到", + "delete_file": "檔案刪除", + "move_to_trash": "移動到回收站", + "share": "分享建立", + "share_link_viewed": "åˆ†äº«é€£çµæª¢è¦–", + "set_current_version": "設定當å‰ç‰ˆæœ¬", + "delete_version": "刪除版本", + "thumb_generated": "縮圖生æˆ", + "live_photo_uploaded": "上傳 Live Photo", + "update_metadata": "元資料更新", + "edit_share": "分享編輯", + "delete_share": "分享刪除", + "mount": "掛載", + "relocate": "轉移儲存策略", + "create_archive": "建立歸檔", + "extract_archive": "解壓歸檔", + "webdav_login_failed": "WebDAV 登入失敗", + "webdav_account_create": "WebDAV 賬戶建立", + "webdav_account_update": "WebDAV 賬戶更新", + "webdav_account_delete": "WebDAV 賬戶刪除", + "payment_created": "支付建立", + "points_change": "ç©åˆ†æ›´æ”¹", + "payment_paid": "支付完æˆ", + "payment_fulfilled": "履行訂單", + "payment_fulfill_failed": "履行訂單失敗", + "storage_added": "儲存擴容", + "group_changed": "使用者組更改", + "user_exceed_quota_notified": "超出é…é¡é€šçŸ¥", + "user_changed": "使用者狀態更改", + "get_direct_link": "ç²å–ç›´éˆ", + "link_account": "連çµå¤–部賬戶", + "unlink_account": "å–æ¶ˆé€£çµå¤–部賬戶", + "change_nick": "更改暱稱", + "change_avatar": "更改頭åƒ", + "membership_unsubscribe": "å–æ¶ˆè¨‚é–±", + "change_password": "更改密碼", + "enable_2fa": "啟用 2FA", + "disable_2fa": "ç¦ç”¨ 2FA", + "add_passkey": "新增通行密鑰", + "remove_passkey": "移除通行密鑰", + "redeem_gift_code": "å…Œæ›ç¦®å“碼", + "update_view": "更改檢視設定", + "delete_direct_link": "刪除直éˆ", + "report_abuse": "舉報濫用" + }, + "server": "伺æœå™¨è¨­å®š", + "tempPath": "臨時路徑", + "tempPathDes": "å„²å­˜è‡¨æ™‚æª”æ¡ˆçš„ç›®éŒ„ï¼Œç›¸å°æ–¼ Cloudreve 資料目錄。修改å‰è«‹ç¢ºä¿æ²’有正在進行的佇列任務。", + "siteID": "站點 ID", + "siteIDDes": "用於標識站點的唯一 ID,一般無需修改。", + "siteSecretKey": "主金鑰", + "siteSecretKeyDes": "用於加密使用者令牌ã€ç±¤å的主金鑰。輪轉後,所有使用者令牌ã€ç±¤å都將失效。儲存後é‡å•Ÿ Cloudreve 生效。", + "rotateSecretKey": "輪轉主金鑰", + "hashidSalt": "HashID 鹽值", + "hashidSaltDes": "ç”¨æ–¼ç”Ÿæˆ HashID çš„é¹½å€¼ï¼Œè«‹è¬¹æ…Žæ›´æ”¹ï¼Œæ›´æ”¹å¾Œæœƒå°Žè‡´ç¾æœ‰çš„ç›´éˆã€åˆ†äº«é€£çµç­‰å…¨éƒ¨å¤±æ•ˆã€‚", + "accessTokenTTL": "訪å•令牌 TTL", + "accessTokenTTLDes": "訪å•令牌的有效期,單ä½ç‚ºç§’。", + "refreshTokenTTL": "釿–°æ•´ç†ä»¤ç‰Œ TTL", + "refreshTokenTTLDes": "釿–°æ•´ç†ä»¤ç‰Œçš„æœ‰æ•ˆæœŸï¼Œå–®ä½ç‚ºç§’ã€‚å½±éŸ¿ä½¿ç”¨è€…ç™»å…¥ç‹€æ…‹çš„ä¿æŒæ™‚間。", + "cronGarbageCollect": "垃圾回收掃æé–“éš”", + "cronGarbageCollectDes": "設定多久掃æä¸¦å›žæ”¶è‡¨æ™‚檔案和 KV å„²å­˜ä¸­çš„éŽæœŸè³‡æ–™", + "startWithProtocol": "必須以 http:// 或 https:// é–‹é ­", + "tlsWarning": "ç•¶å‰ç«™é»žä½¿ç”¨ https,這裡填寫 http çš„ URL å¯èƒ½æœƒå°Žè‡´ç•°å¸¸ã€‚", + "blobUrlCache": "Blob URL å¿«å–", + "clearBlobUrlCache": "清除 Blob URL å¿«å–", + "clearBlobUrlCacheDes": "為了增加快å–命中率,Cloudreve 會快å–並復用 Blob URL。當 CDN 地å€ç­‰è¨­å®šç™¼ç”Ÿè®Šæ›´æ™‚,請清除快å–。", + "cacheCleared": "å¿«å–已清除" + }, + "giftCodes": { + "giftCodesSettings": "禮å“碼", + "generateGiftCodes": "生æˆç¦®å“碼", + "giftCodeQuantity": "數é‡", + "giftCodeQuantityHelp": "è¦ç”Ÿæˆçš„禮å“碼數é‡ã€‚", + "giftCodeProductType": "產å“型別", + "giftCodeTypePoints": "ç©åˆ†", + "giftCodeTypeStorage": "儲存空間", + "giftCodeTypeGroup": "使用者組", + "giftCodePointsAmount": "ç©åˆ†æ•¸é‡", + "giftCodePointsAmountHelp": "å…Œæ›ç¢¼è¢«ä½¿ç”¨æ™‚å°‡ç²å¾—çš„ç©åˆ†æ•¸é‡ã€‚", + "giftCodeProduct": "產å“", + "selectStorageProduct": "鏿“‡å„²å­˜ç”¢å“", + "selectGroupProduct": "鏿“‡ä½¿ç”¨è€…組產å“", + "giftCodeType": "型別", + "giftCodeAmount": "數é‡", + "giftCode": "禮å“碼", + "giftCodeStatus": "狀態", + "giftCodeUsedBy": "使用者", + "giftCodeUsed": "已使用", + "giftCodeUnused": "å¯ç”¨", + "giftCodeDeleted": "禮å“碼已æˆåŠŸåˆªé™¤", + "giftCodesGenerated": "禮å“碼已æˆåŠŸç”Ÿæˆ", + "noGiftCodes": "暫無禮å“碼", + "generatedCodesTitle": "已生æˆçš„禮å“碼", + "generatedCodesDescription": "復制這些禮å“碼以分享給使用者。æ¯å€‹ç¦®å“碼åªèƒ½ä½¿ç”¨ä¸€æ¬¡ã€‚", + "copyAndClose": "復制並關閉", + "duratonTimes": "æ™‚é•·å€æ•¸", + "duratonTimesDes": "æ¯å€‹ç¦®å“碼包å«äº†å¤šå°‘份尿‡‰å•†å“。", + "unknownProduct": "未知產å“" + }, + "policy": { + "acceleratedDomainUpload": "使用傳輸加速域å上傳", + "acceleratedDomainUploadDes": "開啟後,上傳檔案時會使用七牛的 <0>傳輸加速域å。", + "compare": "å„²å­˜ç­–ç•¥å°æ¯”", + "deletePolicyConfirmation": "確定è¦åˆªé™¤å„²å­˜ç­–ç•¥ {{name}} 嗎?", + "streamSaver": "ç”±ç€è¦½å™¨è™•ç†ä¸‹è¼‰", + "streamSaverDes": "開啟後,使用者下載檔案時會強制由ç€è¦½å™¨è™•ç†ã€‚因為 OneDrive 儲存策略的é™åˆ¶ï¼Œä½¿ç”¨è€…直接下載檔案時得到的檔å無法與 Cloudreve 內檔å一致,由ç€è¦½å™¨è™•ç†ä¸‹è¼‰å¯ä»¥è§£æ±ºæ­¤å•題。", + "oauthCallbackFailed": "授權失敗", + "httpsRequired": "Entra ID 應用需è¦ä½¿ç”¨ HTTPS é‡å®šå‘ URL,但是當å‰ç«™é»žä½¿ç”¨çš„æ˜¯ HTTP,後續登入完æˆå¾Œå¯èƒ½æœƒå°Žè‡´é‡å®šå‘失敗,屆時請手動將ç€è¦½å™¨ä½å€åˆ—中的 HTTPS 替æ›ç‚º HTTP。", + "authorizeMicrosoft": "使用 Microsoft 登入", + "redirectUrl": "é‡å®šå‘ URL", + "redirectUrlDes": "ç•¶å‰å±•示的是最新的符åˆè¦æ±‚çš„é‡å®šå‘ URLï¼Œè«‹ç¢ºèªæ‡‰ç”¨è¨­å®šä¸­çš„é‡å®šå‘ URL 一致。", + "authorizeOneDrive": "ç¢ºèª Entra ID 應用設定", + "authorizeOneDriveDes": "請確èªä»¥ä¸‹ Entra ID 應用資訊是å¦ä»ç„¶æœ‰æ•ˆï¼Œå¦‚有需è¦è«‹åšå‡ºæ›´æ”¹ã€‚", + "authorizeNow": "ç«‹å³æŽˆæ¬Š", + "authorizeAgain": "釿–°æŽˆæ¬Š", + "notGranted": "無授權賬號,儲存策略無法使用。", + "granted": "å·²æŽˆæ¬Šè³¬è™Ÿï¼Œæ†‘è­‰é‡æ–°æ•´ç†æ–¼ <0>。", + "grantedNotRefresh": "å·²æŽˆæ¬Šè³¬è™Ÿï¼Œæ†‘è­‰è‡ªä¸Šæ¬¡å•Ÿå‹•å¾Œå°šæœªé‡æ–°æ•´ç†ã€‚", + "batchDeleteSize": "最大批é‡åˆªé™¤æ•¸é‡", + "batchDeleteSizeDes": "é™åˆ¶å–®æ¬¡ API 請求的最大刪除數é‡ï¼Œæ­¤è¨­å®šä¸æœƒå½±éŸ¿ä½¿ç”¨è€…åˆªé™¤æ‰¹é‡æª”案。ä¸å¡«å¯«æœƒä½¿ç”¨é è¨­å€¼ <0>1000,這是官方 S3 API 的最大å…許值。", + "bucketPolicy": "æ¡¶ç­–ç•¥", + "cdnOrCustomDomain": "CDN 或自定義æºç«™åŸŸå", + "bucketDomain": "儲存空間域å", + "bucketDomainDes": "填寫你為儲存空間繫çµçš„ CDN åŠ é€ŸåŸŸåæˆ–者自定義æºç«™åŸŸå。", + "storageNodeInternal": "儲存節點(內網 Endpoint)", + "chunkSizeDesOssObs": "å…許範åœï¼š100 KB ~ 5 GB,", + "chunkSizeDesQiniuCos": "å…許範åœï¼š1 MB ~ 1 GB,", + "chunkSizeDesS3": "å…許範åœï¼š5 MB ~ 5 GB,", + "thisIsACustomDomain": "這是一個自定義域å", + "thisIsACustomDomainDes": "如果你為 Bucket ç¶å®šäº†è‡ªå®šç¾©åŸŸå,且需è¦é€šéŽè‡ªå®šç¾©åŸŸåé€²è¡Œä¸Šå‚³ç­‰ç®¡ç†æ“ä½œï¼Œè«‹å‹¾é¸æ­¤é¸é …。勾é¸å¾Œï¼ŒCloudreve 䏿œƒåœ¨è«‹æ±‚域å中嘗試補全 Bucket å稱。", + "addedManually": "我已自行設定", + "origin": "來æº", + "allowMethods": "å…許 Methods", + "exposeHeaders": "暴露 Headers", + "allowHeaders": "å…許 Headers", + "maxAge": "å¿«å–æ™‚é–“", + "accessCredential": "è¨ªå•æ†‘è­‰", + "downloadTrafficDiagram": "下載æµé‡è·¯å¾‘演示圖", + "downloadRelay": "下載中轉", + "downloadRelayDes": "é–‹å•Ÿå¾Œï¼Œä½¿ç”¨è€…ä¸‹è¼‰æª”æ¡ˆæ™‚æœƒé€šéŽ Cloudreve 代ç†ã€‚", + "download": "下載", + "downloadCdn": "下載 CDN", + "useDownloadCdn": "使用 CDN 加速下載", + "skipSign": "ä¸ç‚º CDN ç°½åæ–‡ä»¶ URL", + "skipSignDes": "如果你在 COS 域å設置中開啟了 “回æºé‘‘權â€ï¼Œè«‹å‹¾é¸æ­¤é …。", + "cdnHost": "CDN 地å€", + "downloadCdnDes": "ä½¿ç”¨è€…è¨ªå•æª”案時的 URL 中的主機åã€å”議等部分會被替æ›ç‚ºä½ æŒ‡å®šçš„ CDN 域å。", + "mediaExtractorProxy": "ä»£ç†æå–媒體資訊", + "mediaExtractorProxyDes": "é–‹å•Ÿå¾Œï¼Œå°æ–¼å„²å­˜ç«¯æå–噍䏿”¯æ´çš„æª”案,Cloudreve 會嘗試æå–檔案媒體資訊。請在 <0>åª’é«”è™•ç† ä¸­é…ç½® Cloudreve 媒體資訊æå–器。", + "mediaExtractorNative": "原生æå–器", + "mediaExtractorOss": "智慧媒體管ç†ï¼ˆIMM)", + "mediaExtractorQiniu": "智慧多媒體æœå‹™", + "mediaExtractorCos": "騰訊雲資料永ç", + "mediaExtractorObs": "åœ–ç‰‡è™•ç†æœå‹™", + "mediaExtractorUpyun": "åœ–ç‰‡è™•ç†æœå‹™", + "nativeMediaMetaExts": "使用<0>{{name}}的副檔å", + "nativeMediaMetaExtsGeneralDes": "åŠå½¢é€—號 , 隔開,留空表示ä¸ä½¿ç”¨<0>{{name}}。", + "nativeMediaMetaExtsRemote": "å°æ–¼å¾žæ©Ÿå„²å­˜ï¼Œé è¨­æƒ…æ³ä¸‹æ”¯æ´ EXIF 和音樂元資料,你å¯ä»¥é€šéŽé…置覆寫在從機端啟用其他生æˆå™¨ã€‚", + "nativeMediaMetaExtOss": "智慧媒體管ç†ï¼ˆIMM)æœå‹™æ”¯æ´è™•ç†éŸ³è¨Šã€è¦–訊和圖片。處ç†åœ–片無需手動é…置,但如果你需è¦è™•ç†éŸ³è¨Šæˆ–è¦–è¨Šï¼Œéœ€è¦æ‰‹å‹•開通 IMM 並繫çµåˆ° Bucket, è«‹åƒè€ƒ <0>檔案 繫çµã€‚繫çµå®Œæˆå¾Œè«‹åœ¨ä¸Šé¢åŠ ä¸Šä½ æƒ³è¦è™•ç†çš„音視訊的副檔å。", + "nativeMediaMetaExtQiniu": "智慧多媒體æœå‹™æ”¯æ´è™•ç†å¸¸è¦‹éŸ³è¨Šã€è¦–訊和圖片,無需é¡å¤–é…置,在上方填寫你想è¦è™•ç†çš„媒體的副檔åå³å¯ã€‚", + "nativeMediaMetaExtCos": "é¨°è¨Šé›²è³‡æ–™æ°¸çæœå‹™æ”¯æ´è™•ç†éŸ³è¨Šã€è¦–訊和圖片。處ç†åœ–片無需手動é…置,但如果你需è¦è™•ç†éŸ³è¨Šæˆ–視訊, è«‹å…ˆå‰å¾€ <0>資料永ç 開通並繫çµå„²å­˜æ¡¶ï¼Œç„¶å¾Œå‰å¾€ 儲存桶設定 - åª’é«”è™•ç† ä¸­é–‹é€šç¾Žåœ–è™•ç†æœå‹™ã€‚繫çµå®Œæˆå¾Œè«‹åœ¨ä¸Šé¢åŠ ä¸Šä½ æƒ³è¦è™•ç†çš„音視訊的副檔å。", + "nativeMediaMetaExtObs": "åœ–ç‰‡è™•ç†æœå‹™æ”¯æ´<0>æå–圖片 EXIF。無需手動é…置,在上é¢åŠ ä¸Šä½ æƒ³è¦è™•ç†çš„圖片的副檔åå³å¯ã€‚", + "nativeMediaMetaExtUpyun": "åœ–ç‰‡è™•ç†æœå‹™æ”¯æ´<0>æå–圖片 EXIF。無需手動é…置,在上é¢åŠ ä¸Šä½ æƒ³è¦è™•ç†çš„圖片的副檔åå³å¯ã€‚", + "thumbProxy": "代ç†ç”Ÿæˆç¸®åœ–", + "thumbProxyDes": "é–‹å•Ÿå¾Œï¼Œå°æ–¼ä¸ç¬¦åˆåŽŸç”Ÿç¸®åœ–æ¢ä»¶çš„æª”案,Cloudreve 會嘗試為其生æˆç¸®åœ–檔案,並上傳到儲存端。請在 <0>åª’é«”è™•ç† ä¸­é…ç½® Cloudreve 縮圖生æˆå™¨ã€‚", + "nativeThumbnailMaxSize": "使用原生縮圖的最大檔案大å°", + "nativeThumbnailMaxSizeDes": "填寫 0 表示ä¸é™åˆ¶ï¼Œè¶…出此大å°çš„æª”æ¡ˆå°‡ä¸æœƒä½¿ç”¨åŽŸç”Ÿç¸®åœ–ã€‚", + "nativeThumbNailsSupportAllExts": "å°æ‰€æœ‰å‰¯æª”å使用", + "nativeThumbNails": "使用原生縮圖的副檔å", + "nativeThumbNailsGeneralDes": "åŠå½¢é€—號 , 隔開,留空表示ä¸ä½¿ç”¨åŽŸç”Ÿç¸®åœ–ã€‚å°æ–¼åˆ—表中列出的副檔å,Cloudreve 會使用儲存端的原生縮圖。", + "nativeThumbNailsGeneralRemote": "å°æ–¼å¾žæ©Ÿå„²å­˜ï¼Œé è¨­æƒ…æ³ä¸‹åªæ”¯æ´ç°¡å–®å½±è±¡å’Œæ­Œæ›²å°é¢ç¸®åœ–,你å¯ä»¥é€šéŽé…置覆寫在從機端啟用其他生æˆå™¨ã€‚", + "nativeThumbNailsGeneralOss": "å°æ–¼é˜¿é‡Œé›² OSS 儲存,<0>åœ–ç‰‡è™•ç†æœå‹™æœƒè¢«ç”¨ä¾†ç”Ÿæˆç¸®åœ–。", + "nativeThumbNailsGeneralQiniu": "å°æ–¼ä¸ƒç‰›é›²ç«¯å„²å­˜ï¼Œ<0>圖片基本處ç†(imageView2)æœå‹™æœƒè¢«ç”¨ä¾†ç”Ÿæˆç¸®åœ–。", + "nativeThumbNailsGeneralCos": "å°æ–¼é¨°è¨Šé›² COS 儲存,<0>é¨°è¨Šé›²è³‡æ–™æ°¸çæœå‹™æœƒè¢«ç”¨ä¾†ç”Ÿæˆç¸®åœ–。", + "nativeThumbNailsGeneralObs": "å°æ–¼è¯ç‚ºé›² OBS 儲存,<0>åœ–ç‰‡è™•ç†æœå‹™æœƒè¢«ç”¨ä¾†ç”Ÿæˆç¸®åœ–。", + "nativeThumbNailsGeneralUpyun": "å°æ–¼åˆæ‹é›²ç«¯å„²å­˜ï¼Œ<0>åœ–ç‰‡è™•ç†æœå‹™æœƒè¢«ç”¨ä¾†ç”Ÿæˆç¸®åœ–。", + "preallocate": "é åˆ†é…硬碟空間", + "preallocateDes": "開啟後,使用者上傳檔案時會é å…ˆåˆ†é…ç¡¬ç¢Ÿç©ºé–“ï¼ŒåŒæ™‚ä¹Ÿå¯æ”¯æ´ä¸¦è¡Œåˆ†ç‰‡ä¸Šå‚³ã€‚åªåœ¨ Linux 或 Darwin 下有效。", + "chunkConcurrency": "並行上傳分片數", + "chunkConcurrencyDes": "設定 Web ç«¯ç›´å‚³æ™‚ï¼ŒåŒæ™‚進行的分片上傳數é‡ã€‚", + "sourceWebEdit": "Web 線上編輯", + "uploadRelay": "中轉上傳", + "uploadRelayDes": "é–‹å•Ÿå¾Œï¼Œä½¿ç”¨è€…çš„ä¸Šå‚³è«‹æ±‚æœƒé€šéŽ Cloudreve 中轉到儲存端,因為無法進行分片上傳,請注æ„調整 Web 伺æœå™¨ç«¯æœ€å¤§ä¸Šå‚³å¤§å°é™åˆ¶ã€‚", + "customProxy": "自定義代ç†", + "storageNode": "儲存æä¾›å•†", + "sourceWeb": "Web / 官方客戶端", + "sourceDav": "WebDAV", + "uploadTrafficDiagram": "上傳æµé‡è·¯å¾‘演示圖", + "node": "儲存節點", + "nodeDes": "è«‹é¸æ“‡ä¸€å€‹å¾žæ©Ÿç¯€é»žç”¨æ–¼å„²å­˜æª”案,你å¯ä»¥åˆ° <0>儲存節點列表 中建立或管ç†å¾žæ©Ÿç¯€é»žã€‚", + "noBindedGroupWarning": "ç•¶å‰å„²å­˜ç­–略沒有被分é…給任何使用者組,請å‰å¾€ <0>使用者組列表 為當å‰å„²å­˜ç­–略繫çµä½¿ç”¨è€…組。", + "nameRuleImmutable": "ä¿®æ”¹æ­¤è¨­å®šä¸æœƒå½±éŸ¿å„²å­˜ç­–略下已有檔案。Blob 路徑在建立後固定,å³ä½¿å…¶ä¸­é­”æ³•è®Šæ•¸ç™¼ç”Ÿæ”¹è®Šï¼Œè·¯å¾‘ä¹Ÿä¸æœƒæ›´æ–°ã€‚", + "uniqueVarRequired": "請在目錄路徑或檔案å稱中至少包å«ä¸€å€‹å”¯ä¸€æ€§è®Šæ•¸ï¼š{uuid}ã€{randomkey8}ã€{randomkey16}。", + "storageAndUpload": "儲存與上傳", + "blobFolderNaming": "Blob 儲存目錄", + "blobFolderNamingDes": "檔案 Blob 的存放目錄,å¯ä»¥ä½¿ç”¨ <0>魔法變數 。", + "blobName": "Blob å稱", + "blobNameDes": "檔案 Blob çš„å稱,å¯ä»¥ä½¿ç”¨ <0>魔法變數,需è¦ç¢ºä¿ç‚ºçµ•å°å”¯ä¸€ï¼Œå³ä½¿åœ¨çŸ­æ™‚間內多次上傳åŒä¸€æª”案。", + "basicInfo": "基本資訊", + "editX": "編輯 {{name}}", + "noGroupBinded": "沒有繫çµä»»ä½•使用者組", + "create": "建立", + "addXStoragePolicy": "新增 {{type}} 儲存策略", + "loadSummary": "載入統計資料", + "policySummary": "{{count}} 個檔案 Blob ({{size}})", + "sharp": "#", + "name": "å稱", + "type": "型別", + "childFiles": "下屬檔案數", + "totalSize": "資料é‡", + "actions": "æ“作", + "authSuccess": "授權æˆåŠŸ", + "policyDeleted": "儲存策略已刪除", + "newStoragePolicy": "新增儲存策略", + "all": "全部", + "local": "本機儲存", + "remote": "從機儲存", + "qiniu": "七牛", + "upyun": "åˆæ‹é›²", + "oss": "阿里雲 OSS", + "cos": "騰訊雲 COS", + "onedrive": "OneDrive", + "s3": "S3 相容", + "ks3": "金山雲 KS3", + "obs": "è¯ç‚ºé›² OBS", + "load_balance": "負載å‡è¡¡", + "childPolicy": "å­å„²å­˜ç­–ç•¥", + "childPolicyDes": "鏿“‡ä½ è¦æ·»åŠ åˆ°è² è¼‰å‡è¡¡ä¸­çš„å­å„²å­˜ç­–略。", + "weight": "權é‡", + "addTargetPolicy": "添加å­å„²å­˜ç­–ç•¥", + "selectPolicies": "鏿“‡å„²å­˜ç­–ç•¥", + "selectPoliciesDes": "鏿“‡è¦æ·»åŠ åˆ°è² è¼‰å‡è¡¡çš„儲存策略。", + "loadBalanceDes": "使用負載å‡è¡¡å„²å­˜ç­–略時,新上傳的文件會根據權é‡éš¨æ©Ÿåˆ†é…到ä¸åŒçš„å­å„²å­˜ç­–略中。", + "xChildPolicies": "{{count}} å­å„²å­˜ç­–ç•¥", + "refresh": "釿–°æ•´ç†", + "delete": "刪除", + "edit": "編輯", + "selectAStorageProvider": "鏿“‡å„²å­˜æ–¹å¼", + "maxSizeOfSingleFile": "檔案大å°é™åˆ¶", + "maxSizeOfSingleFileDes": "單個檔案的最大大å°ï¼Œè¼¸å…¥é™åˆ¶ç‚º 0 時表示ä¸é™åˆ¶å–®æª”案大å°ã€‚", + "enterFileExt": "留空表示ä¸é™åˆ¶å‰¯æª”å,多個請以åŠå½¢é€—號 , 隔開。", + "extList": "副檔åé™åˆ¶", + "noLimit": "ç„¡é™åˆ¶", + "whitelist": "å…許", + "blacklist": "拒絕", + "fileNameRegex": "æª”åæ­£è¦è¡¨é”å¼è¦å‰‡", + "fileNameRegexDes": "ç”¨æ–¼åŒ¹é…æª”å的正è¦è¡¨é”å¼ï¼Œç•™ç©ºè¡¨ç¤ºç„¡é™åˆ¶ã€‚", + "chunkSizeDes": "請指定分片上傳時的分片大å°ï¼Œå¡«å¯«ç‚º 0 表示ä¸ä½¿ç”¨åˆ†ç‰‡ä¸Šå‚³ï¼Œä½†æœ€å¤§ä¸Šå‚³å¤§å°å¯èƒ½å—陿–¼ Web 伺æœå™¨ã€‚", + "chunkSizeDesSuffix": "{{prefix}}通éŽåˆ†ç‰‡ä¸Šå‚³ï¼Œä½¿ç”¨è€…上傳的檔案將會被切分æˆåˆ†ç‰‡é€å€‹ä¸Šå‚³åˆ°å„²å­˜ç«¯ï¼Œç•¶ä¸Šå‚³ä¸­æ–·å¾Œï¼Œä½¿ç”¨è€…å¯ä»¥é¸æ“‡å¾žä¸Šæ¬¡ä¸Šå‚³çš„分片後繼續開始上傳。", + "chunkSize": "上傳分片大å°", + "policyName": "儲存策略的展示å,也會用於å‘用戶展示。", + "magicVar": { + "fileNameMagicVar": "檔å魔法變數", + "pathMagicVar": "路徑魔法變數", + "variable": "魔法變數", + "description": "æè¿°", + "example": "示例", + "16digitsRandomString": "16 ä½éš¨æ©Ÿå­—å…ƒ", + "8digitsRandomString": "8 ä½éš¨æ©Ÿå­—å…ƒ", + "secondTimestamp": "秒級時間戳", + "nanoTimestamp": "ç´ç§’級時間戳", + "uid": "使用者 ID", + "originalFileName": "原始檔å", + "originFileNameNoext": "無副檔å的原始檔å", + "extension": "副檔å", + "uuidV4": "UUID V4", + "date": "日期", + "dateAndTime": "日期時間", + "randomNumber": "範åœå…§çš„隨機數", + "year": "年份", + "month": "月份", + "day": "æ—¥", + "hour": "å°æ™‚", + "minute": "分é˜", + "second": "ç§’", + "path": "使用者上傳檔案時的åˆå§‹è·¯å¾‘" + }, + "storageBucket": "儲存空間", + "wanSiteURLDes": "在使用此儲存策略å‰ï¼Œè«‹ç¢ºä¿ä½ åœ¨ 引數設定 - 站點資訊 - 站點URL 中填寫的 地å€èˆ‡å¯¦éš›ç›¸ç¬¦ï¼Œä¸¦ä¸” <0>能夠被外網正常訪å•。", + "enterQiniuBucket": "å‰å¾€ <0>ä¸ƒç‰›æŽ§åˆ¶é¢æ¿ 建立物件儲存資æºã€‚在填寫你在七牛建立儲存空間時指定的“儲存空間å稱â€ã€‚", + "qiniuBucketName": "儲存空間å稱", + "cosObsBucketName": "儲存桶å稱", + "bucketType": "Bucket è®€å¯«è¨±å¯æ¬Š", + "bucketTypeDes": "è«‹é¸æ“‡ä½ å»ºç«‹çš„å„²å­˜ç©ºé–“çš„è®€å¯«è¨±å¯æ¬Šåž‹åˆ¥ã€‚", + "aclType": "è¨ªå•æŽ§åˆ¶åž‹åˆ¥", + "accessTypePulic": "å…¬æœ‰è®€ç§æœ‰å¯«", + "accessTypePrivate": "ç§æœ‰è®€å¯«", + "accessType": "訪å•è¨±å¯æ¬Š", + "privateBucket": "ç§æœ‰", + "privateDes": "Cloudreve æœƒå°æª”案 URL 籤å。", + "publicBucket": "公共讀", + "publicStorage": "公開", + "publicDes": "ä¸æŽ¨è–¦é¸æ“‡ï¼ŒCloudreve 會直接返回檔案的直éˆï¼Œç„¡æ³•有效控制檔案的訪å•è¨±å¯æ¬Šã€‚", + "bucketCDNDes": "填寫你為儲存空間繫çµçš„ CDN 加速域å。", + "bucketCDNDomain": "CDN 加速域å", + "qiniuCredentialDes": "åœ¨ä¸ƒç‰›æŽ§åˆ¶é¢æ¿é€²å…¥ 個人中心 - 金鑰管ç†ï¼Œå¡«å¯«ç²å¾—到的 AKã€SK。", + "ak": "AK", + "sk": "SK", + "cannotEnableForPrivateBucket": "ç§æœ‰ç©ºé–“開啟外éˆåŠŸèƒ½å¾Œï¼Œé‚„éœ€è¦åœ¨ä½¿ç”¨è€…組裡設定開啟“使用é‡å®šå‘的外éˆâ€ï¼Œå¦å‰‡ç„¡æ³•正常生æˆå¤–éˆ", + "chunkSizeLabelQiniu": "請指定分片上傳時的分片大å°ï¼Œç¯„åœ 1 MB - 1 GB。", + "corsSettingStep": "跨域策略", + "corsPolicyAdded": "跨域策略已新增。", + "createOSSBucketDes": "ä½ å¯å‰å¾€ <0>OSS ç®¡ç†æŽ§åˆ¶æª¯ 建立 Bucketã€‚åªæ”¯æ´ <1>標準儲存 å’Œ <2>ä½Žé »è¨ªå• åž‹åˆ¥çš„ Bucket。", + "bucketName": "Bucket å稱", + "publicReadBucket": "公共讀", + "ossEndpointDes": "轉到所建立 Bucket 的概覽é é¢ï¼Œå¡«å¯« <0>訪å•åŸŸå æ¬„目下 <1>å¤–ç¶²è¨ªå• ä¸€è¡Œä¸­é–“çš„ <2>EndPoint(地域節點)。", + "ossEndpointDesInternalHint": "如需é…置內網或自定義域å Endpoint,å¯åœ¨å»ºç«‹å„²å­˜ç­–略後設定。", + "obsEndpointCnameHint": "如需é…置自定義域å Endpoint,å¯åœ¨å»ºç«‹å„²å­˜ç­–略後設定。", + "endpoint": "EndPoint", + "ossLANEndpointDes": "留空為ä¸ä½¿ç”¨ã€‚如果你的 Cloudreve 部署在阿里雲端計算æœå‹™ä¸­ï¼Œä¸¦ä¸”與 OSS 處在åŒä¸€å¯ç”¨å€ä¸‹ï¼Œä½ å¯ä»¥é¡å¤–指定使用內網 EndPoint ä»¥ç¯€çœæµé‡é–‹æ”¯, Cloudreve 會在æ¢ä»¶æ»¿è¶³æ™‚切æ›åˆ°å…§ç¶² EndPoint 傳é€è«‹æ±‚。", + "intranetEndPoint": "å…§ç¶² EndPoint", + "ossCDNDes": "是å¦è¦ä½¿ç”¨é…套的 阿里雲CDN 加速 OSS 訪å•?", + "createOSSCDNDes": "å‰å¾€ <0>阿里雲 CDN ç®¡ç†æŽ§åˆ¶æª¯ 建立 CDN 加速域å,並設定æºç«™ç‚ºå‰›å»ºç«‹çš„ OSS Bucket。在下方填寫 CDN 加速域åï¼Œä¸¦é¸æ“‡æ˜¯å¦ä½¿ç”¨ HTTPS:", + "ossAKDes": "在阿里雲 <0>å®‰å…¨è³‡è¨Šç®¡ç† é é¢ç²å– AccessKey。你也å¯ä»¥åœ¨ <1>RAM è¨ªå•æŽ§åˆ¶ ä¸­å»ºç«‹æ“æœ‰ <2>AliyunOSSFullAccess è¨±å¯æ¬Šçš„ AccessKey。", + "shouldNotContainSpace": "ä¸èƒ½å«æœ‰ç©ºæ ¼", + "nameThePolicyFirst": "為此儲存策略命å:", + "chunkSizeLabelOSS": "請指定分片上傳時的分片大å°ï¼Œç¯„åœ 100 KB ~ 5 GB。", + "ossCORSDes": "æ­¤å„²å­˜ç­–ç•¥éœ€è¦æ­£ç¢ºé…置如上跨域策略後æ‰èƒ½ä½¿ç”¨ Web 端上傳檔案,Cloudreve å¯ä»¥å¹«ä½ è‡ªå‹•設定,你也å¯ä»¥æ‰‹å‹•è¨­å®šã€‚å¦‚æžœä½ å·²è¨­å®šéŽæ­¤ Bucket 的跨域策略,此步驟å¯ä»¥è·³éŽã€‚", + "letCloudreveHelpMe": "讓 Cloudreve 幫我設定", + "skip": "è·³éŽ", + "createUpyunBucketDes": "填寫在 <0>åˆæ‹é›²é¢æ¿ 建立雲端儲存æœå‹™å稱。", + "storageServiceName": "æœå‹™å稱", + "operatorName": "æ“作員å", + "operatorPassword": "æ“作員密碼", + "tokenStatus": "Token 防盜éˆ", + "upyunTokenDes": "強烈建議開啟 Token 防盜éˆï¼Œå‰å¾€æ‰€å»ºç«‹é›²ç«¯å„²å­˜æœå‹™çš„ <0>功能é…ç½® 颿¿ï¼Œè½‰åˆ° <1>è¨ªå•æŽ§åˆ¶ é¸é …å¡ï¼Œé–‹å•Ÿ Token 防盜éˆä¸¦è¨­å®šå¯†ç¢¼ã€‚", + "tokenEnabled": "已開啟 Token 防盜éˆ", + "tokenDisabled": "未開啟 Token 防盜éˆ", + "upyunTokenSecretDes": "填寫你所設定的 Token 防盜éˆé‡‘鑰。", + "upyunTokenSecret": "Token 防盜éˆé‡‘é‘°", + "createCOSBucketDes": "å‰å¾€ <0>COS ç®¡ç†æŽ§åˆ¶æª¯ 建立儲存桶,轉到所建立儲存桶的基礎é…ç½®é é¢ï¼Œå°‡ <1>儲存桶å稱 填寫到上方。", + "obsBucketDes": "å‰å¾€ <0>OBS ç®¡ç†æŽ§åˆ¶æª¯ 建立儲存桶,將 <1>æ¡¶å稱 å¡«å¯«åˆ°ä¸Šæ–¹ã€‚å„²å­˜æ¡¶é¡žåˆ¥åªæ”¯æ´ <2>標準儲存 或 <3>低頻訪å•儲存。", + "cosPrivateRW": "ç§æœ‰è®€å¯«", + "cosPublicRW": "å…¬å…±è®€ç§æœ‰å¯«", + "cosAccessDomainDes": "在所建立 Bucket 的概æ³é é¢ï¼Œå¡«å¯« <0>域å資訊 欄目下 給出的 <1>訪å•域å。你也å¯ä»¥ä½¿ç”¨è‡ªå·±ç¹«çµçš„æºç«™åŸŸåæˆ– CDN 加速域å。", + "obsEndpointDes": "在所建立儲存桶的概覽é é¢ï¼Œå¡«å¯« <0>域å資訊 欄目下 給出的 <1>Endpoint(終端節點)。", + "accessDomain": "訪å•域å", + "cosCDNDomainDes": "å‰å¾€ <0>騰訊雲 CDN ç®¡ç†æŽ§åˆ¶æª¯ 建立 CDN 加速域å,並設定æºç«™ç‚ºå‰›å»ºç«‹çš„ COS 儲存桶。在下方填寫 CDN 加速域åï¼Œä¸¦é¸æ“‡æ˜¯å¦ä½¿ç”¨ HTTPS:", + "cosCredentialDes": "填寫在騰訊雲 <0>訪å•金鑰 é é¢ç²å–一å°è¨ªå•金鑰。請確ä¿é€™å°é‡‘é‘°æ“æœ‰ COS æœå‹™çš„訪å•è¨±å¯æ¬Šã€‚你也å¯ä»¥å»ºç«‹å¸¶æœ‰ <1>程å¼è¨­è¨ˆè¨ªå• 能力的<2>å­ä½¿ç”¨è€…,為其賦予 COS æœå‹™çš„訪å•è¨±å¯æ¬Šã€‚", + "obsCredentialDes": "填寫在è¯ç‚ºé›² <0>訪å•金鑰 é é¢ç²å–一å°è¨ªå•金鑰。你也å¯ä»¥å»ºç«‹å¸¶æœ‰ <1>程å¼è¨­è¨ˆè¨ªå• 能力的<2>IAM 使用者,為其賦予 <3>OBS OperateAccess è¨±å¯æ¬Šã€‚", + "grantAccess": "賬號授權", + "grantAccessLater": "點é¸ä¸‹æ–¹æŒ‰éˆ•建立儲存策略後,還需è¦åœ¨å„²å­˜ç­–略設定é é¢é€²è¡Œè³¬è™ŸæŽˆæ¬Šã€‚", + "odHttpsWarning": "你必須啟用 HTTPS æ‰èƒ½ä½¿ç”¨ OneDrive/SharePoint å„²å­˜ç­–ç•¥ï¼›å•Ÿç”¨å¾ŒåŒæ­¥æ›´æ”¹ 引數設定 - 站點資訊 - 站點URL。", + "creatAadAppDes": "å‰å¾€ <0>Microsoft Entra ID 控制檯 並登入,登入後進入<1>Microsoft Entra ID 管ç†é¢æ¿ï¼Œé€™è£¡ç™»å…¥ä½¿ç”¨çš„賬號和最終儲存使用的 OneDrive 所屬賬號å¯ä»¥ä¸åŒã€‚", + "createAadAppDes2": "é€²å…¥å·¦å´ <0>應用注冊 é¸å–®ï¼Œä¸¦é»žé¸ <1>新注冊 按鈕。填寫應用注冊表單。其中,å稱å¯ä»»å–ï¼›<2>å—æ”¯æ´çš„帳戶型別 鏿“‡ç‚º <3>任何組織目錄(任何 Azure AD 目錄 - 多租戶)中的帳戶和個人 Microsoft 帳戶(例如,Skypeã€Xbox)ï¼›<4>é‡å®šå‘ URI (å¯é¸) è«‹é¸æ“‡ <5>Web,並填寫 <6>{{url}}ï¼› å…¶ä»–ä¿æŒé è¨­å³å¯ã€‚", + "aadAppIDDes": "進入應用管ç†çš„ <0>概覽 é é¢ï¼Œçœ‹åˆ°çš„ <1>應用程å¼(客戶端) ID 的值。", + "entraIdApp": "Entra ID 應用資訊", + "aadAppID": "應用程å¼(客戶端) ID", + "addAppSecretDes": "客戶端密碼的建立方å¼ï¼šé€²å…¥æ‡‰ç”¨ç®¡ç†é é¢å·¦å´çš„ <0>證書和密碼 é¸å–®ï¼Œé»žé¸ <1>新建客戶端密碼 按鈕,<2>æˆªæ­¢æœŸé™ é¸æ“‡ç‚ºæœ€é•·æ™‚é–“ã€‚å®¢æˆ¶ç«¯å¯†ç¢¼éŽæœŸå¾Œï¼Œéœ€è¦é‡æ–°å»ºç«‹ä¸¦å°‡å…¶å¡«å…¥å„²å­˜ç­–略設定中。", + "aadAppSecret": "客戶端密碼", + "aadAccountCloud": "Microsoft Graph 端點", + "aadAccountCloudDes": "請根據你使用的 Microsoft 365 è³¬è™Ÿåž‹åˆ¥é¸æ“‡å°æ‡‰çš„端點。", + "multiTenant": "公有(國際版)", + "gallatin": "世紀互è¯", + "sharePointDes": "是å¦å°‡æª”案存放在 SharePoint 中?", + "saveToOneDrive": "存到賬號é è¨­ OneDrive 驅動器中", + "spSiteURL": "SharePoint 站點地å€", + "odReverseProxyURLDes": "是å¦è¦åœ¨æª”案下載時替æ›ç‚ºä½¿ç”¨è‡ªå»ºçš„å代伺æœå™¨ï¼Ÿ", + "odReverseProxyURL": "å代伺æœå™¨åœ°å€", + "chunkSizeDesOd": "å…許範åœï¼š5 MB ~ 5GB,OneDrive è¦æ±‚必須為 320 KiB (327,680 bytes) 的整數å€ã€‚", + "limitOdTPSDes": "é™åˆ¶ OneDrive API 請求頻率", + "tps": "TPS é™åˆ¶", + "tpsDes": "留空表示ä¸é™åˆ¶ã€‚é™åˆ¶æ­¤å„²å­˜ç­–ç•¥æ¯ç§’å‘ OneDrive å‚³é€ API 請求最大數é‡ã€‚超出此頻率的請求會被é™é€Ÿã€‚多個 Cloudreve 節點轉存檔案時,它們會å„è‡ªä½¿ç”¨è‡ªå·±çš„é™æµæ¡¶ï¼Œè«‹æ ¹æ“šæƒ…æ³æŒ‰æ¯”例調低此數值。Web 端上傳請求並ä¸å—æ­¤é™åˆ¶ã€‚", + "tpsBurst": "TPS çªç™¼è«‹æ±‚", + "tpsBurstDes": "請求空閒時,Cloudreve å¯å°‡æŒ‡å®šæ•¸é‡çš„åé¡é ç•™çµ¦æœªä¾†çš„çªç™¼æµé‡ä½¿ç”¨ã€‚", + "odOauthDes": "但是你需è¦é»žé¸ä¸‹æ–¹æŒ‰éˆ•,並使用 OneDrive 登入授權以完æˆåˆå§‹åŒ–後æ‰èƒ½ä½¿ç”¨ã€‚日後你å¯ä»¥åœ¨å„²å­˜ç­–略列表é é¢é‡æ–°é€²è¡ŒæŽˆæ¬Šã€‚", + "gotoAuthPage": "轉到授權é é¢", + "s3BucketDes": "å‰å¾€ AWS S3 控制檯建立儲存桶,在下方填寫你建立儲存桶時指定的 <0>Bucket å稱:", + "s3EndpointDes": "指定儲存桶的 EndPoint(地域節點),填寫為完整的 URL æ ¼å¼ï¼Œæ¯”如 <0>https://bucket.region.example.com。", + "selectRegionDes": "輸入儲存桶所在的å€åŸŸç¨‹å¼ç¢¼ï¼Œå¦‚ <0>us-east-1ã€‚å°æ–¼éž AWS çš„ S3 相容儲存æä¾›å•†ï¼Œè«‹åœ¨å…¶æª”案中查詢如何填寫此項。", + "chunkSizeLabelS3": "請指定分片上傳時的分片大å°ï¼Œç¯„åœ 5 MB ~ 5 GB。", + "policyEndpoint": "Endpoint", + "s3Region": "地å€ç¨‹å¼ç¢¼", + "s3EndpointPathStyle": "鏿“‡æ˜¯å¦å¼·åˆ¶ä½¿ç”¨è·¯å¾‘æ ¼å¼ Endpoint。æŸäº›ç¬¬ä¸‰æ–¹ S3 相容儲存å¯èƒ½éœ€è¦å‹¾é¸æ­¤é¸é …。開啟後,將會強制使用路徑格å¼åœ°å€ï¼Œæ¯”如 <0>http://s3.amazonaws.com/BUCKET/KEY。", + "usePathEndpoint": "å¼·åˆ¶è·¯å¾‘æ ¼å¼ Endpoint", + "thumbExt": "å¯ç”Ÿæˆç¸®åœ–的副檔å", + "thumbExtDes": "留空表示使用儲存策略é å®šç¾©é›†åˆã€‚å°æœ¬æ©Ÿã€S3儲存策略無效", + "driverRoot": "驅動器根目錄", + "driverRootDes": "鏿“‡åœ¨ OneDrive 賬戶中儲存檔案的ä½ç½®ã€‚更改此é¸é …會導致儲存策略中已有檔案無法訪å•。", + "saveToDefaultOneDrive": "儲存檔案到é è¨­ OneDrive 驅動器", + "saveToSharePoint": "儲存檔案到 SharePoint", + "sharePointUrlDes": "輸入 SharePoint 站點 URL。失去焦點後,系統將自動轉æ›ç‚ºæ­£ç¢ºçš„驅動器標識。", + "ks3selectRegionDes": "輸入儲存桶所在的å€åŸŸç¨‹å¼ç¢¼ï¼Œå¦‚ <0>BEIJING。", + "ks3EndpointPathStyle": "鏿“‡æ˜¯å¦å¼·åˆ¶ä½¿ç”¨è·¯å¾‘æ ¼å¼ Endpoint。", + "ossRegionDes": "輸入儲存桶所在的å€åŸŸä»£ç¢¼ï¼Œå¦‚ <0>cn-hangzhou。你å¯ä»¥åœ¨ <1>OSS地域和訪å•域å çš„è¡¨æ ¼ä¸­æ‰¾åˆ°å°æ‡‰åœ°åŸŸï¼Œä¸¦å¡«å¯«å°æ‡‰çš„ <2>地域ID。" + }, + "node": { + "slave": "從機", + "master": "主機", + "noCapabilities": "未啟用任何功能", + "active": "已啟用", + "suspended": "å·²ç¦ç”¨", + "deleteNodeConfirmation": "確定è¦åˆªé™¤ç¯€é»ž {{name}} 嗎?", + "editNode": "編輯節點 {{node}}", + "thisIsMasterNodes": "ä½ æ­£åœ¨ç·¨è¼¯ä¸€å€‹ä¸»æ©Ÿç¯€é»žï¼Œå³æ­£åœ¨æœå‹™ç•¶å‰ç«™é»žçš„ Cloudreve 例項。", + "enableNode": "啟用節點", + "enableNodeDes": "啟用節點後,節點會接å—處ç†å·²é–‹å•Ÿçš„功能。", + "name": "å稱", + "nameNode": "節點å稱,也用於å‘用戶展示。", + "type": "型別", + "server": "節點地å€", + "serverDes": "用於與節點通訊的地å€ã€‚如果你è¦åœ¨æ­¤ç¯€é»žå„²å­˜æª”案,此地å€ä¹Ÿæœƒæš´éœ²çµ¦ä½¿ç”¨è€…端用於上傳檔案。", + "loadBalancerRankDes": "為此節點指定負載å‡è¡¡æ¬Šé‡ï¼Œæ•¸å€¼ç‚ºæ•´æ•¸ã€‚權é‡è¶Šé«˜ï¼Œç¯€é»žè¢«é¸ä¸­çš„æ¦‚率越大。", + "loadBalancerRank": "負載å‡è¡¡æ¬Šé‡", + "slaveSecret": "從機金鑰", + "slaveSecretDes": "用於從機節點與主機節點通訊的金鑰。需è¦èˆ‡å¾žæ©Ÿé…置檔案中 <0>Slave 下的 <1>Secret ä¿æŒä¸€è‡´ã€‚", + "testNode": "測試節點通訊", + "testNodeSuccess": "節點通訊æˆåŠŸ", + "createArchiveDes": "接å—建立壓縮檔案的任務請求。", + "extractArchiveDes": "接å—解壓檔案的任務請求。", + "remoteDownloadDes": "接å—離線下載的任務請求。啟用後還需è¦åœ¨ä¸‹æ–¹é…置離線下載相關資訊。", + "downloader": "下載器", + "aria2Des": "請在目標節點伺æœå™¨ä¸Šä»¥å’ŒåŸ·è¡Œ Cloudreve 相åŒçš„使用者/è¨±å¯æ¬Šå•Ÿå‹• Aria2, 並在 Aria2 çš„é…置檔案中開啟 RPC æœå‹™ï¼Œæ›´å¤šè³‡è¨ŠåŠæŒ‡å¼•è«‹åƒè€ƒæª”案的“離線下載â€ç« ç¯€ã€‚", + "qbittorrentDes": "請在目標節點伺æœå™¨ä¸Šä»¥å’ŒåŸ·è¡Œ Cloudreve 相åŒçš„使用者/è¨±å¯æ¬Šå•Ÿå‹• qBittorrent, 並在 qBittorrent 的設定中開啟“Web UIâ€æœå‹™ï¼Œæ›´å¤šè³‡è¨ŠåŠæŒ‡å¼•è«‹åƒè€ƒæª”案的“離線下載â€ç« ç¯€ã€‚", + "rpcServer": "RPC 伺æœå™¨åœ°å€", + "rpcServerHelpDes": "包å«åŸ çš„完整 RPC 伺æœå™¨åœ°å€ï¼Œä¾‹å¦‚:<0>http://127.0.0.1:6800/。", + "rpcToken": "RPC 授權令牌", + "rpcTokenDes": "與 Aria2 é…置檔案中 <0>rpc-secret ä¿æŒä¸€è‡´ï¼Œæœªè¨­å®šè«‹ç•™ç©ºã€‚", + "downloaderOptionDes": "在建立下載任務時é¡å¤–攜帶的下載器é…置,以 JSON éµå€¼å°æ ¼å¼æ›¸å¯«ï¼Œå…·é«”å¯åƒè€ƒ<0>下載器官方檔案。", + "refreshInterval": "ç‹€æ…‹é‡æ–°æ•´ç†é–“éš” (ç§’)", + "refreshIntervalDes": "Cloudreve å‘ä¸‹è¼‰å™¨è«‹æ±‚é‡æ–°æ•´ç†ä»»å‹™ç‹€æ…‹çš„é–“éš”ï¼Œå¯¦éš›é‡æ–°æ•´ç†é–“éš”ä¹Ÿå–æ±ºæ–¼â€œé›¢ç·šä¸‹è¼‰â€ä½‡åˆ—çš„é…置和ç¹å¿™ç¨‹åº¦ã€‚", + "waitForSeeding": "等待åšç¨®å®Œæˆ", + "waitForSeedingDes": "啟用後,當離線下載任務完æˆå¾Œï¼Œæœƒä¿ç•™æ­¤ä»»å‹™åœ¨åšç¨®ç‹€æ…‹ï¼Œç›´åˆ°åœ¨ä¸‹è¼‰å™¨é…置的åšç¨®çµæŸæ¢ä»¶æ»¿è¶³ã€‚等待åšç¨®ç™¼ç”Ÿåœ¨é›¢ç·šä¸‹è¼‰ä»»å‹™å®Œæˆå¾Œï¼Œä¸æœƒå½±éŸ¿ä½¿ç”¨è€…使用下載的檔案。", + "webUIEndpoint": "Web UI 地å€", + "webUIEndpointDes": "qBittorrent çš„ Web UI 地å€ï¼Œæ¯”如 <0>http://127.0.0.1:8080/。", + "tempPath": "臨時下載目錄", + "tempPathDes": "節點上用於臨時存放離線下載檔案的目錄,節點上的 Cloudreve 程å¼éœ€è¦æ­¤ç›®éŒ„的讀ã€å¯«ã€åŸ·è¡Œè¨±å¯æ¬Šï¼Œä¸‹è¼‰å™¨ä¹Ÿè¦èƒ½å¤ è¨ªå•此目錄。留空會使用é è¨­çš„臨時檔案路徑。", + "webUIUsername": "Web UI 使用者å稱", + "webUIPassword": "Web UI 密碼", + "webUICredDes": "如果未啟用èªè­‰ï¼Œæ­¤è™•請留空。", + "downloaderTestPass": "æˆåŠŸé€£ç·šåˆ°ä¸‹è¼‰å™¨ï¼Œç‰ˆæœ¬ï¼š{{version}}", + "testDownloader": "測試下載器通訊", + "addNewNode": "新建節點", + "nameTheNode": "為節點命å:", + "copyBinary": "", + "runCrSlave": "在節點上執行和主站相åŒç‰ˆæœ¬çš„ Cloudreve,並使用以下é…置檔案啟動:", + "keepIfUpload": "如果你未來需è¦ä½¿ç”¨æ­¤ç¯€é»žå„²å­˜ï¼Œè«‹ä¿ç•™ä¸‹é¢çš„跨域é…置。", + "storeFiles": "儲存檔案", + "storeFilesDes": "使用此節點儲存使用者檔案。", + "storeFilesHint": "如果你想使用此節點儲存檔案,清å‰å¾€ <0>儲存策略 é é¢æ–°å»ºå¾žæ©Ÿå„²å­˜ç­–ç•¥ï¼Œä¸¦é¸æ“‡æ­¤ç¯€é»žã€‚", + "runCrWithConfig": "將上述檔案儲存為 <0>config.ini 檔案,並使用此檔案啟動 Cloudreve:<0>./cloudreve -c config.ini。一個從機 Cloudreve 例項å¯ä»¥å°æŽ¥å¤šå€‹ Cloudreve 主節點,åªéœ€åœ¨æ‰€æœ‰ä¸»ç¯€é»žä¸­æ–°å¢žæ­¤å¾žæ©Ÿç¯€é»žä¸¦ä¿æŒé‡‘鑰一致å³å¯ã€‚", + "inputServer": "輸入節點的地å€ï¼š", + "testButton": "å¯ä»¥é»žé¸ä¸‹é¢æŒ‰éˆ•æ¸¬è©¦é€šè¨Šæ˜¯å¦æ­£å¸¸ã€‚", + "hostHeaderHint": "如果有簽å錯誤,請檢查從機å‰ç½®å代是å¦å‘ˆéžäº† <0>Host 頭。", + "features": "已啟用功能", + "remoteDownload": "離線下載", + "refresh": "釿–°æ•´ç†" + }, + "group": { + "countUser": "統計", + "anonymous": "未登入訪客使用者組", + "sysGroup": "系統使用者組", + "adminGroup": "管ç†å“¡ä½¿ç”¨è€…組", + "#": "#", + "name": "å稱", + "type": "儲存策略", + "count": "下屬使用者數", + "size": "最大容é‡", + "nameOfGroup": "使用者組å", + "nameOfGroupDes": "使用者組的å稱,用於å‘用戶展示。", + "availablePolicies": "å¯ç”¨å„²å­˜ç­–ç•¥", + "availablePoliciesDes": "指定使用者組å¯ç”¨çš„å„²å­˜ç­–ç•¥ï¼Œã€‚ä¿®æ”¹æ­¤è¨­å®šä¸æœƒå½±éŸ¿ä½¿ç”¨è€…已上傳的檔案。", + "availablePolicyDesPro": "å¯å¤šé¸ï¼Œä½¿ç”¨è€…å¯åœ¨é¸å®šç¯„åœå…§è‡ªç”±åˆ‡æ›å„²å­˜ç­–略。", + "initialStorageQuota": "åˆå§‹å®¹é‡", + "initialStorageQuotaDes": "使用者組下的使用者åˆå§‹å¯ç”¨æœ€å¤§å®¹é‡ã€‚", + "isAdmin": "管ç†å“¡ä½¿ç”¨è€…組", + "isAdminDes": "é–‹å•Ÿå¾Œï¼Œä½¿ç”¨è€…çµ„ä¸‹çš„ä½¿ç”¨è€…å°‡æ“æœ‰ç®¡ç†å“¡è¨±å¯æ¬Šã€‚", + "share": "分享", + "allowCreateShareLink": "建立分享連çµ", + "allowCreateShareLinkDes": "關閉後,使用者無法建立分享連çµã€‚", + "shareFree": "無需購買分享連çµ", + "shareFreeDes": "開啟後,使用者無需購買å³å¯è¨ªå•所有付費分享連çµã€‚", + "fileManagement": "檔案管ç†", + "allowWabDAV": "WebDAV", + "allowWabDAVDes": "é—œé–‰å¾Œï¼Œä½¿ç”¨è€…ç„¡æ³•é€šéŽ WebDAV å”議連線至網盤。", + "allowWabDAVProxy": "WebDAV 代ç†", + "allowWabDAVProxyDes": "啟用後, 使用者å¯ä»¥é…ç½® WebDAV 下載經由 Cloudreve 中轉。", + "compressTask": "壓縮/解壓縮任務", + "compressTaskDes": "開啟後,使用者å¯ä»¥ç·šä¸Šå£“縮/解壓縮檔案。", + "compressSize": "待壓縮檔案最大大å°", + "compressSizeDes": "使用者å¯å»ºç«‹çš„壓縮任務的檔案最大總大å°ï¼Œå¡«å¯«ç‚º 0 表示ä¸é™åˆ¶ã€‚這一é™åˆ¶åœ¨å»ºç«‹å£“ç¸®ä»»å‹™æ™‚ä¸æœƒæª¢æŸ¥ï¼Œç•¶åŸ·è¡Œæ™‚已處ç†åŽŸå§‹æª”æ¡ˆç¸½å¤§å°è¶…éŽæ­¤é™åˆ¶æ™‚,任務會失敗。", + "decompressSize": "待解壓檔案最大大å°", + "decompressSizeDes": "使用者å¯å»ºç«‹çš„解壓縮任務的檔案最大總大å°ï¼Œå¡«å¯«ç‚º 0 表示ä¸é™åˆ¶ã€‚", + "allowRemoteDownload": "離線下載", + "allowRemoteDownloadDes": "是å¦å…許使用者建立離線下載任務。如需使用離線下載,還需è¦åœ¨ <0>節點列表 中有開啟離線下載功能的節點。", + "aria2Options": "下載器任務引數", + "aria2OptionsDes": "qBittorrent 或 Aria2 下載器的任務é¡å¤–é…置引數,以 JSON 編碼後的éµ-å€¼æ ¼å¼æ›¸å¯«ï¼Œå¯ç”¨å¼•數請查閱官方檔案。", + "aria2BatchSize": "批é‡é›¢ç·šä¸‹è¼‰æœ€å¤§æ•¸é‡", + "aria2BatchSizeDes": "批é‡å»ºç«‹é›¢ç·šä¸‹è¼‰æ™‚的最大數é‡ï¼Œå¡«å¯«ç‚º 0 表示ä¸é™åˆ¶ã€‚", + "migratePolicy": "儲存策略轉移", + "migratePolicyDes": "是å¦ä½¿ç”¨è€…建立儲存策略轉移任務。", + "advanceDelete": "高階檔案刪除é¸é …", + "advanceDeleteDes": "開啟後,使用者在å‰è‡ºåˆªé™¤æª”案時å¯ä»¥é¸æ“‡æ˜¯å¦ä¿ç•™ç‰©ç†æª”案,請åªé–‹æ”¾çµ¦å¯ä¿¡ä½¿ç”¨è€…組。", + "allowSelectNode": "å…è¨±é¸æ“‡ç¯€é»ž", + "allowSelectNodeDes": "開啟後,使用者å¯ä»¥åœ¨å»ºç«‹ä»»å‹™å‰é¸æ“‡è™•ç†ç¯€é»žï¼›é—œé–‰å¾Œï¼Œç³»çµ±æœƒåœ¨ä½¿ç”¨è€…組å…許的節點下自動分é…節點。", + "allowedNodes": "å¯ç”¨ç¯€é»ž", + "allowedNodesDes": "指定使用者組å¯ç”¨çš„任務處ç†ç¯€é»žï¼Œç•™ç©ºè¡¨ç¤ºå…¨éƒ¨ç¯€é»žéƒ½å¯ç”¨ã€‚使用者åªèƒ½åœ¨æ­¤åˆ—è¡¨å…§é¸æ“‡æˆ–被負載å‡è¡¡åˆ†é…節點。目å‰è¦†è“‹çš„ä»»å‹™ç¯„åœæ˜¯ï¼šé›¢ç·šä¸‹è¼‰ã€æª”案壓縮或解壓縮。其他任務會分é…給主機處ç†ã€‚", + "allNodes": "所有節點", + "esclateAnonymity": "æå‡åŒ¿åä½¿ç”¨è€…è¨±å¯æ¬Š", + "esclateAnonymityDes": "開啟後,使用者å¯ä»¥ç‚ºåŒ¿åä½¿ç”¨è€…è¨­å®šæ›´é«˜è¨±å¯æ¬Šï¼ˆä¿®æ”¹/建立/刪除);關閉後,使用者最高åªèƒ½è³¦äºˆåŒ¿å使用者åªè®€è¨±å¯æ¬Šã€‚æ›´æ”¹æ­¤è¨­å®šä¸æœƒå½±éŸ¿å·²è¨­å®šçš„åˆ†äº«é€£çµæˆ–檔案。", + "allowDownloadShare": "訪å•分享連çµ", + "allowDownloadShareDes": "關閉後,使用者無法檢視別人的分享連çµã€‚此項設定優先順åºé«˜æ–¼åˆ†äº«é€£çµçš„è¨±å¯æ¬Šè¨­å®šã€‚", + "deletedNode": "已刪除節點 #{{id}}", + "maxWalkedFiles": "æœ€å¤§éæ­·æª”案數", + "maxWalkedFilesDes": "在æŸäº›éœ€è¦æ·±å±¤é歷檔案的æ“作中,最大å…è¨±éæ­·çš„æª”案數。", + "trashBinDuration": "回收站ä¿ç•™æ™‚間(秒)", + "trashBinDurationDes": "回收站中檔案的ä¿ç•™æ™‚é•·ï¼Œè¶…æœŸå¾Œæª”æ¡ˆå°‡è¢«å¾¹åº•åˆªé™¤ã€‚æ›´æ”¹æ­¤è¨­å®šä¸æœƒå½±éŸ¿å·²ç¶“在回收站中的檔案。", + "serverSideBatchDownload": "æœå‹™ç«¯æ‰“包下載", + "serverSideBatchDownloadDes": "是å¦å…è¨±ä½¿ç”¨è€…å¤šé¸æª”案使用æœå‹™ç«¯ä¸­è½‰æ‰“包下載,關閉後,使用者ä»ç„¶å¯ä»¥ä½¿ç”¨ç´” Web 端打包下載功能。", + "uploadDownload": "上傳和下載", + "getDirectLink": "ç²å–ç›´éˆ", + "getDirectLinkDes": "是å¦å…許使用者ç²å–檔案的直éˆã€‚", + "bathSourceLinkLimit": "批é‡ç”Ÿæˆå¤–ç›´éˆé‡é™åˆ¶", + "bathSourceLinkLimitDes": "å…許使用者單次批é‡ç²å–ç›´éˆçš„æœ€å¤§æª”案數é‡ï¼Œå¡«å¯«ç‚º 0 表示ä¸å…許ç²å–ç›´éˆã€‚", + "redirectedSource": "使用é‡å®šå‘的直éˆ", + "redirectedSourceDes": "推薦開啟。開啟後,使用者ç²å–的檔案直éˆå°‡ç”± Cloudreve 中轉,連çµè¼ƒçŸ­ã€‚關閉後,使用者ç²å–çš„æª”æ¡ˆç›´éˆæœƒè®Šæˆæª”案的原始連çµï¼Œä¸”與檔案版本ç¶å®šã€‚部分儲存策略在æŸäº›è¨­å®šä¸‹ç²å–çš„éžä¸­è½‰ç›´éˆç„¡æ³•ä¿æŒæ°¸ä¹…有效,請åƒé–± Cloudreve 檔案。", + "reuseDirectLink": "é‡ç”¨å·²æœ‰ç›´éˆ", + "reuseDirectLinkDes": "開啟後,多次請求åŒä¸€å€‹æª”æ¡ˆçš„ç›´éˆæ™‚,會é‡ç”¨å·²å‰µå»ºçš„中轉直éˆã€‚", + "downloadSpeedLimit": "下載é™é€Ÿ", + "downloadSpeedLimitDes": "填寫為 0 表示ä¸é™åˆ¶ã€‚開啟é™åˆ¶å¾Œï¼Œä½¿ç”¨è€…下載所有支æ´é™é€Ÿçš„儲存策略下的檔案時,下載最大速度會被é™åˆ¶ã€‚", + "anonymousHint": "æ­¤ä½¿ç”¨è€…çµ„å°æ‡‰è‘—未登入的匿å訪客。", + "create": "新建", + "copyFromExisting": "å¾žç¾æœ‰ä½¿ç”¨è€…組復制?", + "notCopy": "ä¸å¾©åˆ¶", + "confirmDelete": "確èªè¦åˆªé™¤ä½¿ç”¨è€…組 {{group}}?", + "new": "新建使用者組", + "editGroup": "編輯 {{group}}" + }, + "user": { + "createdAt": "建立日期", + "originUserGroup": "原始使用者組", + "originUserGroupDes": "ä½¿ç”¨è€…åœ¨è³¼è²·ä½¿ç”¨è€…çµ„å‰æ‰€å±¬çš„使用者組,當å‰ä½¿ç”¨è€…組到期後會回退到此使用者組。", + "noOriginUserGroup": "ç„¡", + "groupExpired": "ä½¿ç”¨è€…çµ„éŽæœŸæ—¥æœŸ", + "groupExpiredDes": "ISO8601 æ ¼å¼çš„使用者組到期日期,留空表示當å‰ä½¿ç”¨è€…組永久有效。", + "openUserFiles": "開啟使用者檔案", + "id": "ID", + "idValue": "{{id}} ({{hash_id}})", + "avatar": "é ­åƒ", + "removeAvatar": "移除頭åƒ", + "userDialogTitle": "使用者詳情", + "2FAEnabled": "已啟用二步驗證", + "qqEnabled": "å·²ç¹«çµ QQ", + "logtoEnabled": "å·²ç¹«çµ Logto", + "deleted": "使用者已刪除", + "new": "新建使用者", + "filter": "éŽæ¿¾", + "emptyNoFilter": "留空表示ä¸éŽæ¿¾æ­¤é …。", + "selectedObjects": "已鏿“‡ {{num}} 個物件", + "nick": "暱稱", + "email": "Email", + "group": "使用者組", + "status": "狀態", + "usedStorage": "已用空間", + "status_active": "正常", + "status_inactive": "未啟用", + "status_manual_banned": "手動å°ç¦", + "status_sys_banned": "系統å°ç¦", + "toggleBan": "å°ç¦/è§£å°", + "filterCondition": "éŽæ¿¾æ¢ä»¶", + "all": "全部", + "userStatus": "使用者狀態", + "apply": "應用", + "editUser": "編輯 {{nick}}", + "password": "密碼", + "passwordDes": "留空表示ä¸ä¿®æ”¹", + "groupDes": "使用者所屬使用者組", + "2FA": "二步驗證", + "notEnabled": "未啟用", + "reset2Fa": "關閉", + "reset": "é‡ç½®", + "confirmDelete": "確èªè¦åˆªé™¤ä½¿ç”¨è€… {{user}}?", + "deleteXUsers": "刪除 {{num}} 個使用者", + "confirmBatchDelete": "確èªè¦åˆªé™¤ {{num}} 個使用者?", + "calibrateStorage": "校准儲存空間", + "calibrateStorageSuccess": "儲存空間校准æˆåŠŸ" + }, + "file": { + "deleteXFiles": "刪除 {{num}} 個檔案", + "confirmBatchDelete": "確定è¦åˆªé™¤ {{num}} 個檔案?", + "confirmDelete": "確èªè¦åˆªé™¤æª”案 {{file}}?", + "haveShares": "æ“æœ‰åˆ†äº«é€£çµ", + "haveDirectLinks": "æ“æœ‰ä¸­è½‰ç›´éˆ", + "directLinkId": "é€£çµæ¨™è­˜", + "directLinks": "中轉直éˆ", + "noRecords": "沒有記錄", + "speed": "é™é€Ÿ", + "downloads": "下載次數", + "shareLink": "分享連çµ", + "shareLinkNum": "{{num}} 個 (<0>檢視)", + "blobType": "型別", + "noEntities": "沒有 Blob", + "blobs": "Blobs", + "creator": "建立者", + "source": "æº", + "key": "éµ", + "value": "值", + "isPublic": "公開", + "noMetadata": "沒有元資料", + "metadata": "元資料", + "id": "ID", + "primaryStoragePolicy": "首é¸å„²å­˜ç­–ç•¥", + "fileDialogTitle": "檔案詳情", + "name": "檔å", + "deleteAsync": "刪除任務將在後臺執行", + "forceDelete": "強制刪除", + "size": "大å°", + "sizeUsed": "佔用空間", + "uploader": "所有者", + "createdAt": "建立於", + "uploading": "上傳中", + "unknownUploader": "未知", + "uploaderID": "所有者 ID", + "searchFileName": "æœå°‹æª”å", + "storagePolicy": "儲存策略", + "selectTargetUser": "è«‹å…ˆé¸æ“‡ç›®æ¨™ä½¿ç”¨è€…", + "importTaskCreated": "匯入任務已建立,你å¯ä»¥åœ¨â€œå¾Œè‡ºä»»å‹™â€ä¸­æª¢è¦–執行情æ³", + "manuallyPathOnly": "鏿“‡çš„å„²å­˜ç­–ç•¥åªæ”¯æ´æ‰‹å‹•輸入路徑", + "selectFolder": "鏿“‡ç›®éŒ„", + "import": "匯入", + "importExternalFolder": "匯入外部目錄", + "importExternalFolderDes": "ä½ å¯ä»¥å°‡å„²å­˜ç­–略中已有檔案ã€ç›®éŒ„çµæ§‹åŒ¯å…¥åˆ° Cloudreve 中,匯入æ“ä½œä¸æœƒé¡å¤–佔用物ç†å„²å­˜ç©ºé–“ï¼Œä½†ä»æœƒæ­£å¸¸æ‰£é™¤ä½¿ç”¨è€…已用容é‡ç©ºé–“。", + "storagePolicyDes": "鏿“‡è¦åŒ¯å…¥æª”案目å‰å„²å­˜æ‰€åœ¨çš„儲存策略。", + "targetUser": "目標使用者", + "targetUserDes": "鏿“‡è¦å°‡æª”案匯入到哪個使用者的檔案系統中。", + "srcFolderPath": "原始目錄路徑", + "select": "鏿“‡", + "selectSrcDes": "è¦åŒ¯å…¥çš„目錄在儲存端的路徑", + "dstFolderPath": "目的目錄路徑", + "dstFolderPathDes": "è¦å°‡ç›®éŒ„匯入到使用者檔案系統中的路徑。", + "recursivelyImport": "éžè¿´åŒ¯å…¥å­ç›®éŒ„", + "recursivelyImportDes": "是å¦å°‡ç›®éŒ„下的所有å­ç›®éŒ„éžè¿´åŒ¯å…¥ã€‚", + "createImportTask": "建立匯入任務", + "unlink": "解除關è¯ï¼ˆä¿ç•™ç‰©ç†æª”案)", + "searchUser": "æœå°‹ä½¿ç”¨è€…昵稱或郵箱...", + "extractMediaMeta": "æå–媒体信æ¯", + "extractMediaMetaDes": "是å¦åœ¨åŒ¯å…¥æª”æ¡ˆçš„åŒæ™‚嘗試æå–æ¯å€‹æª”案的媒體資訊。", + "importWarning": "注æ„事項", + "importWarnings": [ + "åŒ¯å…¥å¾Œï¼Œç‰©ç†æª”案將由 Cloudreve 接管,後續請ä¸è¦åœ¨å¤–部修改此檔案;", + "ä¸è¦é‡è¤‡åŒ¯å…¥ç›¸åŒçš„æª”案;", + "如果使用者檔案è¡çªï¼Œæ­¤æª”案會被跳éŽï¼›" + ], + "otherConditions": "å…¶ä»–æ¢ä»¶", + "shareLinkExisted": "有分享連çµ", + "directLinkExisted": "有中轉直éˆ", + "isUploading": "上傳中" + }, + "entity": { + "refenenceCount": "引用次數", + "waitForRecycle": "等待回收", + "entityDialogTitle": "Blob 詳情", + "uploadSessionID": "上傳會話 ID", + "referredFiles": "é—œè¯æª”案", + "confirmBatchDelete": "確èªè¦åˆªé™¤ {{num}} 個 Blob?", + "deleteXEntities": "刪除 {{num}} 個 Blob", + "forceDelete": "強制刪除", + "forceDeleteDes": "ç„¡è«–ç‰©ç†æª”案是å¦åˆªé™¤æˆåŠŸï¼Œéƒ½æœƒåˆªé™¤ Blob 記錄。" + }, + "event": { + "cleanup": "清ç†", + "cleanupAuditLog": "事件清ç†", + "cleanupAuditLogDescription": "刪除滿足以下æ¢ä»¶çš„æ‰€æœ‰äº‹ä»¶ï¼š", + "cleanupNotAfter": "在此日期之å‰", + "cleanupEventTypes": "事件類型", + "cleanupEventTypesDes": "鏿“‡è¦æ¸…ç†çš„äº‹ä»¶é¡žåž‹ï¼Œç•™ç©ºè¡¨ç¤ºæ¸…ç†æ‰€æœ‰é¡žåž‹ã€‚", + "allEventTypes": "所有事件類型", + "initiator": "發起者", + "event": "事件", + "userID": "使用者 ID", + "ip": "IP", + "type": "型別", + "correlationId": "請求 ID", + "fileID": "檔案 ID", + "emailSend": "傳é€éƒµä»¶ “{{title}}†到 {{email}}", + "emailFailed": "郵件佇列啟動失敗", + "signinFailed": "登入失敗: {{reason}}", + "createDavAccount": "建立 WebDAV 賬戶: {{account}}", + "updateDavAccount": "æ›´æ–° WebDAV 賬戶: {{account}}", + "deleteDavAccount": "刪除 WebDAV 賬戶: {{account}}", + "pointsChange": "ç©åˆ†è®ŠåŒ–: {{points}}", + "storageAdded": "購買了 {{size}} 容é‡", + "nickChange": "暱稱從 {{old}} 改為 {{new}}", + "eventDialogTitle": "事件詳情", + "userAgent": "使用者代ç†", + "linkedUser": "é—œè¯ä½¿ç”¨è€…", + "datetime": "時間", + "linkedFile": "é—œè¯æª”案", + "linkedEntity": "é—œè¯ Blob", + "linkedShare": "é—œè¯åˆ†äº«", + "rawContent": "原始記錄", + "confirmDelete": "確èªè¦åˆªé™¤é€™å€‹äº‹ä»¶?", + "deleteXEvents": "刪除 {{num}} 個事件", + "confirmBatchDelete": "確èªè¦åˆªé™¤ {{num}} 個事件?" + }, + "share": { + "confirmBatchDelete": "確èªè¦åˆªé™¤ {{num}} 個分享?", + "confirmDelete": "確èªè¦åˆªé™¤é€™å€‹åˆ†äº«?", + "deleteXShares": "刪除 {{num}} 個分享", + "shareDialogTitle": "分享詳情", + "shareLink": "分享連çµ", + "deleted": "檔案已刪除", + "srcFileName": "原始檔", + "views": "ç€è¦½", + "downloads": "下載", + "price": "ç©åˆ†", + "autoExpire": "è‡ªå‹•éŽæœŸ", + "owner": "分享者", + "createdAt": "分享於", + "private": "從個人主é éš±è—", + "yes": "是", + "no": "å¦", + "afterNDownloads": "{{num}} 次下載後", + "none": "ç„¡", + "srcType": "原始檔型別", + "folder": "目錄", + "file": "檔案" + }, + "task": { + "cleanupTasks": "清ç†ä»»å‹™", + "cleanupTasksDescription": "æ¸…ç†æ»¿è¶³ä»¥ä¸‹æ¢ä»¶çš„æ‰€æœ‰ä»»å‹™ï¼š", + "cleanupNotAfter": "在此日期之å‰", + "cleanupTaskTypes": "任務類型", + "cleanupTaskTypesDes": "鏿“‡è¦æ¸…ç†çš„ä»»å‹™é¡žåž‹ï¼Œç•™ç©ºè¡¨ç¤ºæ¸…ç†æ‰€æœ‰é¡žåž‹ã€‚", + "cleanupTaskStatuses": "任務狀態", + "cleanupTaskStatusesDes": "鏿“‡è¦æ¸…ç†çš„ä»»å‹™ç‹€æ…‹ï¼Œç•™ç©ºè¡¨ç¤ºæ¸…ç†æ‰€æœ‰å·²å®Œæˆç‹€æ…‹çš„任務。", + "confirmDelete": "確èªè¦åˆªé™¤é€™å€‹ä»»å‹™?", + "confirmBatchDelete": "確èªè¦åˆªé™¤ {{num}} 個任務?", + "deleteXTasks": "刪除 {{num}} 個任務", + "blobID": "Blob ID", + "retryIndex": "é‡è©¦åºè™Ÿ", + "entityError": "回收失敗的 Blob", + "updatedAt": "æ›´æ–°æ–¼", + "taskDialogTitle": "任務詳情", + "explicitEntityRecycle": "顯å¼å›žæ”¶æª”案 Blob: {{blobs}}", + "entityRecycleRoutine": "定時掃æå›žæ”¶æª”案 Blob", + "mediaMetadata": "æå– Blob <0>#{{entityID}} 的媒體資訊", + "uploadSentinelCheck": "檢查上傳會話 {{uploadSessionID}} 狀態", + "remoteDownload": "離線下載:", + "owner": "所有者", + "content": "內容", + "status": "狀態", + "create_archive": "建立壓縮檔案", + "extract_archive": "解壓檔案", + "relocate": "轉移儲存策略", + "remote_download": "離線下載", + "media_meta": "媒體資訊æå–", + "entity_recycle_routine": "Blob 掃æå›žæ”¶", + "explicit_entity_recycle": "é¡¯å¼ Blob 回收", + "upload_sentinel_check": "上傳哨兵檢查", + "import": "外部匯入", + "type": "型別", + "node": "處ç†ç¯€é»ž", + "createdBy": "建立者", + "ready": "就緒", + "downloading": "下載中", + "paused": "æš«åœä¸­", + "seeding": "åšç¨®ä¸­", + "error": "出錯", + "finished": "完æˆ", + "canceled": "å–æ¶ˆ/åœæ­¢", + "unknown": "未知", + "errorMsg": "錯誤資訊" + }, + "payment": { + "tradeNo": "交易單號", + "productType": "商å“型別", + "providerID": "支付方å¼", + "status": "狀態", + "deleteXPayments": "刪除 {{num}} 個訂單" + }, + "customProps": { + "add": "新增", + "type": "類型", + "default": "é è¨­å€¼", + "actions": "æ“作", + "text": "文本", + "number": "數字", + "boolean": "勾é¸", + "select": "å–®é¸", + "multiSelect": "多é¸", + "user": "使用者", + "link": "連çµ", + "rating": "評分", + "addProp": "新增屬性", + "editProp": "編輯屬性", + "icon": "圖標", + "iconDes": "<0>Iconify 圖標å稱,留空表示ä¸å±•示圖標。", + "id": "標識", + "idDes": "屬性標識,需è¦ç¢ºä¿åœ¨æ‰€æœ‰å±¬æ€§ä¸­å”¯ä¸€ã€‚", + "idPatternDes": "åªèƒ½åŒ…å«å­—æ¯ã€æ•¸å­—ã€ä¸‹åŠƒç·šå’Œä¸­åŠƒç·šã€‚", + "minLength": "最å°é•·åº¦", + "maxLength": "最大長度", + "emptyLimit": "留空表示ä¸é™åˆ¶ã€‚", + "minValue": "最å°å€¼", + "maxValue": "最大值", + "options": "é¸é …", + "optionsDes": "æ¯è¡Œä¸€å€‹é¸é …。" + }, + "vas": { + "disableSubAddressEmail": "ç¦ç”¨å­åœ°å€éƒµç®±", + "disableSubAddressEmailDes": "開啟後,包å«åŠ è™Ÿ <0>+ 的郵箱地å€ç„¡æ³•註冊賬戶。", + "confirmDelete": "確èªè¦åˆªé™¤é€™äº›è¨‚å–®?", + "vas": "增值æœå‹™", + "reports": "舉報", + "orders": "訂單", + "initialFiles": "åˆå§‹æª”案", + "initialFilesDes": "指定使用者注冊後åˆå§‹æ“有的檔案。輸入檔案 ID æœå°‹ä¸¦æ–°å¢žç¾æœ‰æª”案。", + "filterEmailProvider": "éŽæ¿¾æ³¨å†Šéƒµç®±åŸŸ", + "filterEmailProviderDisabled": "ä¸å•Ÿç”¨", + "filterEmailProviderWhitelist": "白åå–®", + "filterEmailProviderBlacklist": "黑åå–®", + "filterEmailProviderDes": "åªå…許使用特定的郵箱注冊站點,第三方 SSO 登入ä¸å—æ­¤é™åˆ¶ã€‚", + "filterEmailProviderRule": "éƒµç®±åŸŸéŽæ¿¾è¦å‰‡", + "filterEmailProviderRuleDes": "多個域請使用åŠå½¢é€—號隔開。", + "qqConnect": "QQ 互è¯", + "qqConnectHint": "在 <0>QQ 互è¯é–‹æ”¾å¹³è‡º 建立應用時,回撥地å€è«‹å¡«å¯«ï¼š<0>{{url}}。", + "enableQQConnect": "開啟 QQ 互è¯", + "enableQQConnectDes": "是å¦å…è¨±ç¹«çµ QQã€ä½¿ç”¨ QQ 登入本站", + "loginWithoutBinding": "æœªç¹«çµæ™‚å¯ç›´æŽ¥ç™»å…¥", + "loginWithoutBindingDes": "開啟後,如果使用者使用了第三方登入,但是沒有已繫çµçš„æ³¨å†Šä½¿ç”¨è€…,系統會為其建立使用者並登入。這種方å¼å»ºç«‹çš„使用者日後åªèƒ½ä½¿ç”¨ç¬¬ä¸‰æ–¹ç™»å…¥ã€‚", + "appid": "APP ID", + "appidDes": "應用管ç†é é¢ç²å–到的的 APP ID。", + "appKey": "APP KEY", + "appKeyDes": "應用管ç†é é¢ç²å–到的的 APP KEY。", + "overuseReminder": "è¶…é¡æé†’", + "overuseReminderDes": "使用者因增值æœå‹™éŽæœŸï¼Œå®¹é‡è¶…出é™åˆ¶å¾Œå‚³é€çš„æé†’郵件模æ¿", + "vasSetting": "支付/雜項設定", + "storagePack": "容é‡åŒ…", + "purchasableGroups": "å¯è³¼ä½¿ç”¨è€…組", + "giftCodes": "å…Œæ›ç¢¼", + "enable": "開啟", + "appID": "APP ID", + "appIDDes": "ç•¶é¢ä»˜æ‡‰ç”¨çš„ APPID。", + "rsaPrivate": "RSA 應用ç§é‘°", + "rsaPrivateDes": "ç•¶é¢ä»˜æ‡‰ç”¨çš„ RSA2 (SHA256) ç§é‘°ï¼Œä¸€èˆ¬æ˜¯ç”±ä½ è‡ªå·±ç”Ÿæˆã€‚詳情åƒè€ƒ <0>ç”Ÿæˆ RSA 金鑰。", + "alipayPublicKey": "支付寶公鑰", + "alipayPublicKeyDes": "由支付寶æä¾›ï¼Œå¯åœ¨ã€æ‡‰ç”¨ç®¡ç†ã€‘-ã€æ‡‰ç”¨è³‡è¨Šã€‘-ã€ä»‹é¢åŠ ç°½æ–¹å¼ã€‘中ç²å–。", + "wechatPay": "微信官方掃碼支付", + "applicationID": "應用 ID", + "applicationIDDes": "直連商戶申請的公眾號或移動應用 appid。", + "merchantID": "直連商戶號", + "merchantIDDes": "直連商戶的商戶號,由微信支付生æˆä¸¦ä¸‹ç™¼ã€‚", + "apiV3Secret": "API v3 金鑰", + "apiV3SecretDes": "商戶需先在ã€å•†æˆ¶å¹³è‡ºã€‘-ã€API 安全】的é é¢è¨­å®šè©²é‡‘鑰,請求æ‰èƒ½é€šéŽå¾®ä¿¡æ”¯ä»˜çš„ç±¤åæ ¡é©—。金鑰的長度為 32 個ä½å…ƒçµ„。", + "mcCertificateSerial": "商戶證書åºåˆ—號", + "mcCertificateSerialDes": "登入商戶平臺ã€API 安全】-ã€API 證書】-ã€æª¢è¦–è­‰æ›¸ã€‘ï¼Œå¯æª¢è¦–商戶 API 證書åºåˆ—號。", + "mcAPISecret": "商戶API ç§é‘°", + "mcAPISecretDes": "ç§é‘°æª”案 apiclient_key.pem 的內容。", + "payjs": "PAYJS 微信支付", + "payjsWarning": "æ­¤æœå‹™ç”±ç¬¬ä¸‰æ–¹å¹³è‡º <0>PAYJS æä¾›ï¼Œç”¢ç”Ÿçš„任何糾紛與 Cloudreve 開發者無關。", + "mcNumber": "商戶號", + "mcNumberDes": "å¯åœ¨ PAYJS 管ç†é¢æ¿é¦–é çœ‹åˆ°", + "communicationSecret": "通訊金鑰", + "otherSettings": "雜項設定", + "banBufferPeriod": "å°ç¦ç·©è¡æœŸ (ç§’)", + "banBufferPeriodDes": "ä½¿ç”¨è€…ä¿æŒå®¹é‡è¶…é¡ç‹€æ…‹çš„æœ€é•·æ™‚長,超出時長該使用者會被系統å‡çµã€‚", + "allowSellShares": "å…許為分享定價", + "allowSellSharesDes": "開啟後,使用者å¯ç‚ºåˆ†äº«è¨­å®šç©åˆ†åƒ¹æ ¼ï¼Œä¸‹è¼‰éœ€è¦æ‰£é™¤ç©åˆ†ã€‚", + "creditPriceRatio": "ç©åˆ†åˆ°è³¬æ¯”率 (%)", + "creditPriceRatioDes": "購買下載設定價格的分享,分享者實際到賬的ç©åˆ†æ¯”率。", + "creditPrice": "ç©åˆ†åƒ¹æ ¼ (分)", + "creditPriceDes": "充值ç©åˆ†æ™‚的價格", + "add": "新增", + "name": "å稱", + "price": "單價", + "duration": "時長", + "size": "大å°", + "actions": "æ“作", + "orCredits": " 或 {{num}} ç©åˆ†", + "highlight": "çªå‡ºå±•示", + "yes": "是", + "no": "å¦", + "productName": "商å“å", + "qyt": "數é‡", + "code": "å…Œæ›ç¢¼", + "status": "狀態", + "invalidProduct": "已失效商å“", + "used": "已使用", + "notUsed": "未使用", + "generatingResult": "生æˆçµæžœ", + "addStoragePack": "新增容é‡åŒ…", + "editStoragePack": "編輯容é‡åŒ…", + "productNameDes": "商å“展示å稱", + "packSizeDes": "容é‡åŒ…的大å°", + "durationDay": "有效期 (天)", + "durationDayDes": "æ¯å€‹å®¹é‡åŒ…的有效期", + "priceYuan": "單價 (å…ƒ)", + "packPriceDes": "容é‡åŒ…的單價", + "priceCredits": "單價 (ç©åˆ†)", + "priceCreditsDes": "使用ç©åˆ†è³¼è²·æ™‚的價格,填寫為 0 表示ä¸èƒ½ä½¿ç”¨ç©åˆ†è³¼è²·", + "editMembership": "編輯å¯è³¼ä½¿ç”¨è€…組", + "addMembership": "新增å¯è³¼ä½¿ç”¨è€…組", + "group": "使用者組", + "groupDes": "購買後å‡ç´šçš„使用者組", + "durationGroupDes": "購買後å‡ç´šçš„使用者組單ä½è³¼è²·æ™‚間的有效期", + "groupPriceDes": "使用者組的單價", + "productDescription": "å•†å“æè¿° (一行一個)", + "productDescriptionDes": "購買é é¢å±•ç¤ºçš„å•†å“æè¿°", + "highlightDes": "開啟後,在商å“鏿“‡é é¢æœƒè¢«çªå‡ºå±•示", + "generateGiftCode": "生æˆå…Œæ›ç¢¼", + "numberOfCodes": "ç”Ÿæˆæ•¸é‡", + "numberOfCodesDes": "啟用碼批é‡ç”Ÿæˆæ•¸é‡", + "linkedProduct": "å°æ‡‰å•†å“", + "productQyt": "商哿•¸é‡", + "productQytDes": "å°æ–¼ç©åˆ†é¡žå•†å“,此處為ç©åˆ†æ•¸é‡ï¼Œå…¶ä»–商å“ç‚ºæ™‚é•·å€æ•¸", + "freeDownload": "å…ç©åˆ†ä¸‹è¼‰åˆ†äº«", + "freeDownloadDes": "開啟後,使用者å¯ä»¥å…費下載需付ç©åˆ†çš„分享", + "credits": "ç©åˆ†", + "markSuccessful": "標記æˆåŠŸ", + "markAsResolved": "標記為已處ç†", + "reportedContent": "舉報物件", + "reason": "原因", + "description": "補充æè¿°", + "reportTime": "舉報時間", + "invalid": "[已失效]", + "deleteShare": "刪除分享", + "orderDeleted": "訂單記錄已刪除", + "orderName": "訂單å", + "product": "商å“", + "paymentId": "訂單 ID", + "orderNumber": "訂單號", + "paidBy": "支付方å¼", + "orderOwner": "建立者", + "amount": "金é¡", + "unpaid": "未支付", + "paid": "已支付", + "shareLink": "分享連çµ", + "mobileApp": "移動客戶端", + "showAppPromotion": "展示客戶端引導é é¢", + "showAppPromotionDes": "開啟後,使用者å¯ä»¥åœ¨ “連線與掛載†é é¢ä¸­çœ‹åˆ°ç§»å‹•客戶端的使用引導。", + "customPaymentName": "付款方å¼å稱", + "customPaymentNameDes": "用於展示給使用者的付款方å¼å稱", + "customPaymentSecretDes": "Cloudreve 用於籤å付款請求的金鑰。", + "customPaymentEndpoint": "支付介é¢åœ°å€", + "customPaymentEndpointDes": "å»ºç«‹æ”¯ä»˜è¨‚å–®æ™‚è«‹æ±‚çš„ä»‹é¢ URL", + "appFeedback": "å饋é é¢ URL", + "appForum": "使用者論壇 URL", + "appLinkDes": "用於在 App 設定é é¢å±•示,留空å³ä¸å±•ç¤ºé€£çµæŒ‰éˆ•,僅當 VOL æŽˆæ¬Šæœ‰æ•ˆæ™‚æ­¤é …è¨­å®šæ‰æœƒç”Ÿæ•ˆã€‚" + }, + "pro": { + "title": "Pro 版本專屬功能", + "description": "您嘗試訪å•的功能僅在 Cloudreve Pro 版本中å¯ç”¨ï¼Œå‡ç´šä»¥è§£éŽ–æ‰€æœ‰é«˜ç´šåŠŸèƒ½ã€‚", + "proInclude": "Pro 版本包å«ï¼š", + "shareLinkCollabration": "分享連çµå”åŒç·¨è¼¯", + "filePermission": "文件權é™ç®¡ç†", + "multipleStoragePolicy": "多儲存策略和目錄儲存策略切æ›", + "auditAndActivity": "文件和系統活動日誌", + "vasService": "增值æœå‹™å’Œç©åˆ†ç³»çµ±", + "sso": "SSO 單點登入", + "more": "......", + "later": "ç¨å¾Œå†èªª", + "learnMore": "了解 Pro 版本詳情", + "promotionTitle": "社å€ç‰ˆå‡ç´šç‰¹åˆ¥å„ªæƒ ", + "promotion": "購買時使用優惠碼 <0>{{code}},ç²å¾— <1>-{{discount}}% 折扣。" + }, + "abuseReport": { + "deleteXAbuseReports": "刪除 {{num}} 個舉報", + "folderPath": "目錄路徑", + "reporter": "舉報者", + "shareLink": "åˆ†äº«é€£çµ <0>#{{id}}", + "deletedShare": "已刪除的分享連çµ", + "deletedUser": "已刪除的使用者", + "confirmDelete": "確èªè¦åˆªé™¤é€™å€‹èˆ‰å ±è¨˜éŒ„?", + "confirmBatchDelete": "確èªè¦åˆªé™¤ {{num}} 個舉報記錄?", + "reporterID": "舉報者使用者 ID", + "reportedUserID": "舉報å°è±¡ä½¿ç”¨è€… ID", + "shareID": "分享 ID", + "reason": "原因" + } +} diff --git a/public/locales/zh-TW/image_editor.json b/public/locales/zh-TW/image_editor.json new file mode 100755 index 0000000..04b70c4 --- /dev/null +++ b/public/locales/zh-TW/image_editor.json @@ -0,0 +1,113 @@ +{ + "name": "å稱", + "save": "ä¿å­˜", + "saveAs": "å¦å­˜ç‚º", + "back": "返回", + "loading": "加載中...", + "resetOperations": "é‡ç½®/刪除所有æ“作", + "changesLoseWarningHint": "如果您按下\"é‡ç½®\"按鈕,您的更改將丟失。是å¦è¦ç¹¼çºŒï¼Ÿ", + "discardChangesWarningHint": "如果關閉模態,則䏿œƒä¿å­˜æœ€å¾Œçš„æ›´æ”¹ã€‚", + "cancel": "å–æ¶ˆ", + "apply": "申請", + "warning": "警告", + "confirm": "確èª", + "discardChanges": "放棄更改", + "undoTitle": "撤消上次æ“作", + "redoTitle": "é‡åšä¸Šæ¬¡æ“作", + "showImageTitle": "顯示原始圖åƒ", + "zoomInTitle": "放大", + "zoomOutTitle": "縮å°", + "toggleZoomMenuTitle": "切æ›ç¸®æ”¾èœå–®", + "adjustTab": "調整", + "finetuneTab": "微調", + "filtersTab": "濾é¡", + "watermarkTab": "æ°´å°", + "annotateTabLabel": "繪圖", + "resize": "調整大å°", + "resizeTab": "調整大å°", + "imageName": "圖åƒå稱", + "invalidImageError": "æä¾›çš„圖åƒç„¡æ•ˆã€‚", + "uploadImageError": "上傳圖片時出錯。", + "areNotImages": "冇有圖åƒ", + "isNotImage": "䏿˜¯ä¸€å€‹åœ–åƒ", + "toBeUploaded": "待上傳", + "cropTool": "è£å‰ª", + "original": "原始", + "custom": "自定義", + "square": "正方形", + "landscape": "風景", + "portrait": "è‚–åƒ", + "ellipse": "人åƒ", + "classicTv": "經典電視", + "cinemascope": "電影比例", + "arrowTool": "ç®­é ­", + "blurTool": "模糊", + "brightnessTool": "亮度", + "contrastTool": "å°æ¯”", + "ellipseTool": "橢圓", + "unFlipX": "å–æ¶ˆæ©«åš®ç¿»è½‰", + "flipX": "橫嚮翻轉", + "unFlipY": "å–æ¶ˆç¸±åš®ç¿»è½‰", + "flipY": "縱嚮翻轉", + "hsvTool": "色相/飽和度", + "hue": "色相", + "brightness": "亮度", + "saturation": "飽和度", + "value": "值", + "imageTool": "圖åƒ", + "importing": "正在導入...", + "addImage": "+ 添加圖片", + "uploadImage": "上傳圖片", + "fromGallery": "從圖庫", + "lineTool": "ç·šæ¢", + "penTool": "ç•«ç­†", + "polygonTool": "多邊形", + "sides": "å´éºµ", + "rectangleTool": "矩形", + "cornerRadius": "æ‹è§’åŠå¾‘", + "resizeWidthTitle": "寬度(以åƒç´ ç‚ºå–®ä½ï¼‰", + "resizeHeightTitle": "高度(以åƒç´ ç‚ºå–®ä½ï¼‰", + "toggleRatioLockTitle": "鎖定比例", + "resetSize": "é‡ç½®ç‚ºåŽŸå§‹åœ–åƒå¤§å°", + "rotateTool": "旋轉", + "textTool": "文本", + "textSpacings": "文本間è·", + "textAlignment": "文本å°é½Š", + "fontFamily": "字體家æ—", + "size": "尺寸", + "letterSpacing": "å­—æ¯é–“è·", + "lineHeight": "行高", + "warmthTool": "溫暖", + "addWatermark": "添加水å°", + "addTextWatermark": "添加文本水å°", + "addWatermarkTitle": "鏿“‡æ°´å°é¡žåž‹", + "uploadWatermark": "上傳水å°", + "addWatermarkAsText": "添加為文本", + "padding": "å¡«å……", + "paddings": "填充物", + "shadow": "å½±å­", + "horizontal": "æ°´å¹³", + "vertical": "垂直", + "blur": "模糊", + "opacity": "ä¸é€æ˜Žåº¦", + "transparency": "逿˜Žåº¦", + "position": "ä½ç½®", + "stroke": "中風", + "saveAsModalTitle": "å¦å­˜ç‚º", + "extension": "擴展", + "format": "æ ¼å¼", + "nameIsRequired": "å稱為必填項。", + "quality": "質é‡", + "imageDimensionsHoverTitle": "ä¿å­˜çš„圖åƒå¤§å°ï¼ˆå¯¬ x 高)", + "cropSizeLowerThanResizedWarning": "請註æ„,所é¸è£å‰ªéºµç©å°æ–¼æ‡‰ç”¨çš„調整大å°,這å¯èƒ½æœƒå°Žç·»è³ªé‡ä¸‹é™", + "actualSize": "å¯¦éš›å¤§å° ï¼ˆ100%)", + "fitSize": "é©åˆå°ºå¯¸", + "addImageTitle": "鏿“‡è¦æ·»åŠ çš„åœ–ç‰‡...", + "mutualizedFailedToLoadImg": "無法加載圖åƒã€‚", + "tabsMenu": "èœå–®", + "download": "下載", + "width": "寬度", + "height": "高度", + "plus": "+", + "cropItemNoEffect": "æ­¤è£å‰ªé …冇有å¯ç”¨çš„é è¦½" +} \ No newline at end of file diff --git a/public/locales/zh-TW/markdown_editor.json b/public/locales/zh-TW/markdown_editor.json new file mode 100755 index 0000000..58ab961 --- /dev/null +++ b/public/locales/zh-TW/markdown_editor.json @@ -0,0 +1,114 @@ +{ + "frontmatterEditor": { + "title": "編輯中繼資料", + "key": "éµ", + "value": "值", + "addEntry": "新增項目" + }, + "dialogControls": { + "save": "儲存", + "cancel": "å–æ¶ˆ" + }, + "uploadImage": { + "dialogTitle": "上傳圖片", + "uploadInstructions": "從您的è£ç½®ä¸Šå‚³åœ–片:", + "addViaUrlInstructions": "或填寫圖片 URL / 相å°è·¯å¾‘ï¼ˆç›¸å°æ–¼ç•¶å‰æ–‡ä»¶ï¼‰ï¼š", + "autoCompletePlaceholder": "鏿“‡æˆ–貼上圖片 URL", + "addViaUrlInstructionsNoUpload": "圖片 URL:", + "alt": "替代文字:", + "title": "標題:" + }, + "imageEditor": { + "deleteImage": "刪除圖片", + "editImage": "編輯圖片" + }, + "createLink": { + "url": "ç¶²å€", + "urlPlaceholder": "鏿“‡æˆ–貼上網å€", + "title": "標題", + "saveTooltip": "設定網å€", + "cancelTooltip": "å–æ¶ˆä¿®æ”¹" + }, + "linkPreview": { + "open": "在新視窗中開啟 {{url}}", + "edit": "編輯連çµç¶²å€", + "copyToClipboard": "複製到剪貼簿", + "copied": "已複製ï¼", + "remove": "移除連çµ" + }, + "table": { + "deleteTable": "刪除表格", + "columnMenu": "欄é¸å–®", + "textAlignment": "文字å°é½Š", + "alignLeft": "é å·¦å°é½Š", + "alignCenter": "置中å°é½Š", + "alignRight": "é å³å°é½Š", + "insertColumnLeft": "åœ¨æ­¤å·¦å´æ’入一欄", + "insertColumnRight": "在此å³å´æ’入一欄", + "deleteColumn": "刪除此欄", + "rowMenu": "列é¸å–®", + "insertRowAbove": "在上方æ’入一列", + "insertRowBelow": "在下方æ’入一列", + "deleteRow": "刪除此列" + }, + "toolbar": { + "blockTypes": { + "paragraph": "段è½", + "quote": "引用", + "heading": "標題 {{level}}" + }, + "blockTypeSelect": { + "selectBlockTypeTooltip": "鏿“‡å¡Šé¡žåž‹", + "placeholder": "塊類型" + }, + "toggleGroup": "切æ›ç¾¤çµ„", + "removeBold": "移除粗體", + "bold": "ç²—é«”", + "removeItalic": "移除斜體", + "italic": "斜體", + "underline": "移除底線", + "removeUnderline": "底線", + "removeInlineCode": "移除程å¼ç¢¼æ ¼å¼", + "inlineCode": "å…§è¯ç¨‹å¼ç¢¼æ ¼å¼", + "link": "建立連çµ", + "richText": "富文字", + "diffMode": "差異模å¼", + "source": "原始碼模å¼", + "admonition": "æ’入註解å€å¡Š", + "codeBlock": "æ’入程å¼ç¢¼å€å¡Š", + "editFrontmatter": "編輯中繼資料", + "insertFrontmatter": "æ’入中繼資料", + "image": "æ’入圖片", + "insertSandpack": "æ’å…¥ Sandpack", + "table": "æ’入表格", + "thematicBreak": "æ’入主題斷行", + "bulletedList": "項目清單", + "numberedList": "編號清單", + "checkList": "æ ¸å–æ¸…å–®", + "deleteSandpack": "刪除 Sandpack", + "undo": "復原 {{shortcut}}", + "redo": "é‡åš {{shortcut}}", + "superscript": "上標", + "subscript": "下標", + "strikethrough": "刪除線", + "removeSubscript": "移除下標", + "removeSuperscript": "移除上標", + "removeStrikethrough": "移除刪除線" + }, + "admonitions": { + "note": "注æ„", + "tip": "æç¤º", + "danger": "å±éšª", + "info": "資訊", + "caution": "警告", + "changeType": "鏿“‡è¨»è§£å€å¡Šé¡žåž‹", + "placeholder": "註解å€å¡Šé¡žåž‹" + }, + "codeBlock": { + "language": "程å¼ç¢¼å€å¡Šèªžè¨€", + "selectLanguage": "鏿“‡ç¨‹å¼ç¢¼å€å¡Šèªžè¨€" + }, + "contentArea": { + "editableMarkdown": "å¯ç·¨è¼¯çš„ Markdown" + } +} \ No newline at end of file diff --git a/public/pdfviewer.html b/public/pdfviewer.html new file mode 100755 index 0000000..6a7ff9d --- /dev/null +++ b/public/pdfviewer.html @@ -0,0 +1,752 @@ + + + + + + + + + PDF.js viewer + + + + + + + + + + + + +
+ +
+
+
+
+ + + + +
+
+ +
+
+
+ + +
+
+
+
+
+
+ + + +
+
+
+ +
+
+
+
+
+ +
+
+ + +
+
+ +
+ +
+
+ + + + +
+
+
+
+ +
+ +
+ + + +
+
+
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+ + + +
+ +
+ +
+ + +
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+ +
+ +
+
+ +
+
+ + +
+
+ +
+ File name: +

-

+
+
+ File size: +

-

+
+
+
+ Title: +

-

+
+
+ Author: +

-

+
+
+ Subject: +

-

+
+
+ Keywords: +

-

+
+
+ Creation Date: +

-

+
+
+ Modification Date: +

-

+
+
+ Creator: +

-

+
+
+
+ PDF Producer: +

-

+
+
+ PDF Version: +

-

+
+
+ Page Count: +

-

+
+
+ Page Size: +

-

+
+
+
+ Fast Web View: +

-

+
+
+ +
+
+ +
+
+ Choose an option + + Alt text (alternative text) helps when people can’t see the image or when it doesn’t load. + +
+
+
+
+ + +
+
+ + Aim for 1-2 sentences that describe the subject, setting, or actions. + +
+
+
+ +
+
+
+
+
+ + +
+
+ + This is used for ornamental images, like borders or watermarks. + +
+
+
+
+ + +
+
+
+ +
+
+ Edit alt text (image description) +
+
+
+
+
+
+ +
+ Short description for people who can’t see the image or when the image doesn’t load. +
This alt text was created automatically and may be inaccurate. Learn more
+
+
+ + +
+ +
+
+
+
+
+
+ Couldn’t create alt text automatically + Please write your own alt text or try again later. +
+ +
+
+
+ + + +
+
+
+ + +
+
+ Image alt text settings +
+
+ Automatic alt text +
+
+
+ + +
+
+ Suggests descriptions to help people who can’t see the image or when the image doesn’t load. Learn more +
+
+
+
+ Alt text AI model (180MB) +
+ Runs locally on your device so your data stays private. Required for automatic alt text. +
+
+ + +
+
+
+
+
+ Alt text editor +
+
+ + +
+
+ Helps you make sure all your images have alt text. +
+
+
+
+ +
+
+
+ + + + This modal allows the user to create a signature to add to a PDF document. The user can edit the name (which also serves as the alt text), and optionally save the signature for repeated use. + +
+
+ Add a signature +
+
+ + + +
+
+
+ +
+
+ + Draw your signature +
+
+ + +
+
+
+
+ +
+ Drag a file here to upload + + +
+
+
+
+
+ + + + + +
+ +
+
+ + + + You’ve reached the limit of 5 saved signatures. Remove one to save more. +
+
+ +
+ + +
+
+
+
+ + +
+
+ Edit description +
+
+
+ + + + + +
+ +
+
+ + +
+
+
+ + +
+ Preparing document for printing… +
+
+ + 0% +
+
+ +
+
+
+ + + +
+
+ + diff --git a/public/service-worker.js b/public/service-worker.js new file mode 100755 index 0000000..a3833b4 --- /dev/null +++ b/public/service-worker.js @@ -0,0 +1,14 @@ +self.addEventListener("install", function (e) { + self.skipWaiting(); +}); + +self.addEventListener("activate", function (e) { + self.registration + .unregister() + .then(function () { + return self.clients.matchAll(); + }) + .then(function (clients) { + clients.forEach((client) => client.navigate(client.url)); + }); +}); diff --git a/public/static/img/appstore.svg b/public/static/img/appstore.svg new file mode 100755 index 0000000..072b425 --- /dev/null +++ b/public/static/img/appstore.svg @@ -0,0 +1,46 @@ + + Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/static/img/cloudreve.svg b/public/static/img/cloudreve.svg new file mode 100755 index 0000000..0a2c6c1 --- /dev/null +++ b/public/static/img/cloudreve.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/static/img/cos.png b/public/static/img/cos.png new file mode 100755 index 0000000..f511b89 Binary files /dev/null and b/public/static/img/cos.png differ diff --git a/public/static/img/favicon.ico b/public/static/img/favicon.ico new file mode 100755 index 0000000..82bcc51 Binary files /dev/null and b/public/static/img/favicon.ico differ diff --git a/public/static/img/ks3.png b/public/static/img/ks3.png new file mode 100755 index 0000000..079d597 Binary files /dev/null and b/public/static/img/ks3.png differ diff --git a/public/static/img/lb.svg b/public/static/img/lb.svg new file mode 100755 index 0000000..8a9132c --- /dev/null +++ b/public/static/img/lb.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/public/static/img/local.png b/public/static/img/local.png new file mode 100755 index 0000000..13ab953 Binary files /dev/null and b/public/static/img/local.png differ diff --git a/public/static/img/logo.svg b/public/static/img/logo.svg new file mode 100755 index 0000000..de7c93d --- /dev/null +++ b/public/static/img/logo.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/static/img/logo192.png b/public/static/img/logo192.png new file mode 100755 index 0000000..28020b0 Binary files /dev/null and b/public/static/img/logo192.png differ diff --git a/public/static/img/logo512.png b/public/static/img/logo512.png new file mode 100755 index 0000000..1f14fa3 Binary files /dev/null and b/public/static/img/logo512.png differ diff --git a/public/static/img/logo_light.svg b/public/static/img/logo_light.svg new file mode 100755 index 0000000..7bdb432 --- /dev/null +++ b/public/static/img/logo_light.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/static/img/marker-icon-2x.png b/public/static/img/marker-icon-2x.png new file mode 100755 index 0000000..88f9e50 Binary files /dev/null and b/public/static/img/marker-icon-2x.png differ diff --git a/public/static/img/marker-icon.png b/public/static/img/marker-icon.png new file mode 100755 index 0000000..950edf2 Binary files /dev/null and b/public/static/img/marker-icon.png differ diff --git a/public/static/img/marker-shadow.png b/public/static/img/marker-shadow.png new file mode 100755 index 0000000..9fd2979 Binary files /dev/null and b/public/static/img/marker-shadow.png differ diff --git a/public/static/img/obs.png b/public/static/img/obs.png new file mode 100755 index 0000000..57fd7d4 Binary files /dev/null and b/public/static/img/obs.png differ diff --git a/public/static/img/onedrive.png b/public/static/img/onedrive.png new file mode 100755 index 0000000..c76920c Binary files /dev/null and b/public/static/img/onedrive.png differ diff --git a/public/static/img/oss.png b/public/static/img/oss.png new file mode 100755 index 0000000..46dca72 Binary files /dev/null and b/public/static/img/oss.png differ diff --git a/public/static/img/qiniu.png b/public/static/img/qiniu.png new file mode 100755 index 0000000..5c7574b Binary files /dev/null and b/public/static/img/qiniu.png differ diff --git a/public/static/img/remote.png b/public/static/img/remote.png new file mode 100755 index 0000000..4d648fe Binary files /dev/null and b/public/static/img/remote.png differ diff --git a/public/static/img/s3.png b/public/static/img/s3.png new file mode 100755 index 0000000..544bc6f Binary files /dev/null and b/public/static/img/s3.png differ diff --git a/public/static/img/upyun.png b/public/static/img/upyun.png new file mode 100755 index 0000000..67ff1dd Binary files /dev/null and b/public/static/img/upyun.png differ diff --git a/public/static/img/viewers/artplayer.png b/public/static/img/viewers/artplayer.png new file mode 100755 index 0000000..1d9d15e Binary files /dev/null and b/public/static/img/viewers/artplayer.png differ diff --git a/public/static/img/viewers/drawio.svg b/public/static/img/viewers/drawio.svg new file mode 100755 index 0000000..454a30d --- /dev/null +++ b/public/static/img/viewers/drawio.svg @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/public/static/img/viewers/excalidraw.svg b/public/static/img/viewers/excalidraw.svg new file mode 100755 index 0000000..ee99676 --- /dev/null +++ b/public/static/img/viewers/excalidraw.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/static/img/viewers/gdrive.png b/public/static/img/viewers/gdrive.png new file mode 100755 index 0000000..2b64d3c Binary files /dev/null and b/public/static/img/viewers/gdrive.png differ diff --git a/public/static/img/viewers/m365.svg b/public/static/img/viewers/m365.svg new file mode 100755 index 0000000..aa6e0f0 --- /dev/null +++ b/public/static/img/viewers/m365.svg @@ -0,0 +1,46 @@ + + +Microsoft 365 logo (2022) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/img/viewers/monaco.svg b/public/static/img/viewers/monaco.svg new file mode 100755 index 0000000..cc61f81 --- /dev/null +++ b/public/static/img/viewers/monaco.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/static/img/viewers/photopea.png b/public/static/img/viewers/photopea.png new file mode 100755 index 0000000..d5b0afe Binary files /dev/null and b/public/static/img/viewers/photopea.png differ diff --git a/src/App.tsx b/src/App.tsx new file mode 100755 index 0000000..83d337d --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,360 @@ +import { createTheme, CssBaseline, GlobalStyles, styled, ThemeProvider, useMediaQuery, useTheme } from "@mui/material"; +import { grey } from "@mui/material/colors"; +import { ThemeOptions } from "@mui/material/styles/createTheme"; +import i18next from "i18next"; +import { enqueueSnackbar, MaterialDesignContent, SnackbarProvider } from "notistack"; +import { Suspense, useEffect, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { Outlet } from "react-router-dom"; +import { useRegisterSW } from "virtual:pwa-register/react"; +import FileIconSnackbar from "./component/Common/Snackbar/FileIconSnackbar.tsx"; +import LoadingSnackbar from "./component/Common/Snackbar/LoadingSnackbar.tsx"; +import { ServiceWorkerUpdateAction } from "./component/Common/Snackbar/snackbar.tsx"; +import GlobalDialogs from "./component/Dialogs/GlobalDialogs.tsx"; +import { GrowDialogTransition } from "./component/FileManager/Search/SearchPopup.tsx"; +import Warning from "./component/Icons/Warning.tsx"; +import { useAppSelector } from "./redux/hooks.ts"; +import { changeThemeColor } from "./util"; + +export const applyThemeWithOverrides = (themeConfig: ThemeOptions): ThemeOptions => { + return { + ...themeConfig, + shape: { + ...themeConfig.shape, + borderRadius: 12, + }, + components: { + MuiCssBaseline: { + styleOverrides: { + body: { + overscrollBehavior: "none", + }, + }, + }, + MuiTooltip: { + defaultProps: { + enterDelay: 500, + }, + }, + MuiToggleButton: { + styleOverrides: { + root: { + textTransform: "none", + }, + }, + }, + MuiButton: { + styleOverrides: { + root: { + textTransform: "none", + }, + }, + defaultProps: { + disableElevation: true, + }, + }, + MuiAlert: { + defaultProps: { + iconMapping: { + warning: , + }, + }, + }, + MuiListItemButton: { + styleOverrides: { + root: { + borderRadius: 12, + }, + }, + }, + MuiTab: { + styleOverrides: { + root: { + textTransform: "none", + }, + }, + }, + MuiSkeleton: { + defaultProps: { + animation: "wave", + }, + }, + MuiMenu: { + styleOverrides: { + paper: { + borderRadius: "8px", + }, + list: { + padding: "4px 0", + }, + }, + defaultProps: { + slotProps: { + paper: { + elevation: 3, + }, + }, + }, + }, + MuiDialogContent: { + styleOverrides: { + root: { + paddingTop: 0, + }, + }, + }, + MuiMenuItem: { + styleOverrides: { + root: { + borderRadius: "8px", + margin: "0px 4px", + paddingLeft: "8px", + paddingRight: "8px", + }, + }, + }, + MuiDialog: { + defaultProps: { + TransitionComponent: GrowDialogTransition, + }, + }, + MuiFilledInput: { + styleOverrides: { + root: { + "&::before, &::after": { + borderBottom: "none", + }, + "&:hover:not(.Mui-disabled, .Mui-error):before": { + borderBottom: "none", + }, + borderRadius: 12, + // '&:hover:not(.Mui-disabled, .Mui-error):before': { + // borderBottom: '2px solid var(--TextField-brandBorderHoverColor)', + // }, + // '&.Mui-focused:after': { + // borderBottom: '2px solid var(--TextField-brandBorderFocusedColor)', + // }, + }, + }, + }, + }, + }; +}; + +export const useGeneratedTheme = (preferedDark?: boolean, subTheme?: boolean) => { + const themes = useAppSelector((state) => state.siteConfig.basic.config.themes); + const defaultTheme = useAppSelector((state) => state.siteConfig.basic.config.default_theme); + const preferredTheme = useAppSelector((state) => state.globalState.preferredTheme); + let darkMode = useAppSelector((state) => state.globalState.darkMode); + darkMode = darkMode; + const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)"); + const mode = + preferedDark !== undefined + ? preferedDark + ? "dark" + : "light" + : darkMode === undefined + ? prefersDarkMode + ? "dark" + : "light" + : darkMode + ? "dark" + : "light"; + const theme = useMemo(() => { + // Determine preferred theme + var themeConfig = {} as ThemeOptions; + if (themes) { + try { + const themeOptions = JSON.parse(themes) as themeOptions; + themeConfig = getPreferredTheme(themeOptions, mode, preferredTheme, defaultTheme); + } catch (e) { + console.log("failed to parse theme config, using default", e); + } + } + + themeConfig = { + ...themeConfig, + palette: { + ...themeConfig.palette, + mode: mode, + }, + }; + + const t = createTheme(applyThemeWithOverrides(themeConfig)); + if (!subTheme) { + changeThemeColor(themeConfig?.palette?.mode === "light" ? t.palette.grey[100] : t.palette.grey[900]); + } + return t; + }, [prefersDarkMode, preferredTheme, defaultTheme, themes, darkMode]); + + return theme; +}; + +const removeI18nCache = () => { + Object.keys(localStorage).forEach(function (key) { + if (key && key.startsWith("i18next_res_")) { + localStorage.removeItem(key); + } + }); +}; + +export const App = () => { + const theme = useGeneratedTheme(); + const { t } = useTranslation(); + + const { + offlineReady: [offlineReady, setOfflineReady], + needRefresh: [needRefresh, setNeedRefresh], + updateServiceWorker, + } = useRegisterSW({ + onRegisterError(error) { + console.log("SW registration error", error); + }, + }); + + useEffect(() => { + if (needRefresh) { + enqueueSnackbar({ + message: i18next.t("common:newVersionRefresh"), + variant: "default", + persist: true, + action: ServiceWorkerUpdateAction(() => { + updateServiceWorker(true); + removeI18nCache(); + }), + }); + } + }, [needRefresh, updateServiceWorker]); + + return ( + Loading...}> + + + + + ); +}; + +interface themeOptions { + [key: string]: singleThemeOption; +} + +interface singleThemeOption { + light: ThemeOptions; + dark?: ThemeOptions; +} + +const getPreferredTheme = ( + opts: themeOptions, + mode: "dark" | "light", + preferredTheme?: string, + defaultTheme?: string, +): ThemeOptions => { + let themeConfig = {} as singleThemeOption; + if (defaultTheme && opts[defaultTheme]) { + themeConfig = opts[defaultTheme]; + } + if (preferredTheme && opts[preferredTheme]) { + themeConfig = opts[preferredTheme]; + } + + if (!themeConfig?.light) { + themeConfig = Object.values(opts)[0]; + } + + if (mode === "dark" && themeConfig.dark) { + return themeConfig.dark; + } + + return themeConfig.light; +}; + +const StyledMaterialDesignContent = styled(MaterialDesignContent)(({ theme }) => ({ + "&.notistack-MuiContent": { + borderRadius: 12, + }, + "&.notistack-MuiContent-success": { + backgroundColor: theme.palette.success.main, + }, + "&.notistack-MuiContent-error": { + backgroundColor: theme.palette.error.main, + }, + "&.notistack-MuiContent-warning": { + backgroundColor: theme.palette.warning.main, + }, +})); + +const AppContent = () => { + const title = useAppSelector((state) => state.siteConfig.basic.config.title); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const scrollBar = { + "&::-webkit-scrollbar-button": { + width: 0, + height: 0, + }, + "&::-webkit-scrollbar-corner": { + background: "0 0", + }, + "&::-webkit-scrollbar-thumb": { + borderRadius: 4, + backgroundColor: "transparent", + }, + "&::-webkit-scrollbar-track": { + borderRadius: 4, + }, + "&::-webkit-scrollbar-track:hover": { + backgroundColor: theme.palette.mode == "light" ? grey[200] : grey[800], + }, + "&::-webkit-scrollbar-thumb:hover": { + backgroundColor: theme.palette.primary.main + "!important", + }, + "& :hover::-webkit-scrollbar-thumb,:hover>:first-child::-webkit-scrollbar-thumb": { + backgroundColor: theme.palette.mode == "light" ? grey[400] : grey[600], + }, + "&::-webkit-scrollbar ": { + width: 8, + height: 8, + }, + }; + + return ( + <> + + ({ + html: { + scrollbarWidth: isMobile ? "initial" : "thin", + //scrollbarColor: theme.palette.action.selected + " transparent", + }, + ...(isMobile ? undefined : scrollBar), + body: { + overflowY: isMobile ? "initial" : "hidden", + }, + ".highlight-marker": { + backgroundColor: "#ffc1079e", + borderRadius: "4px", + boxShadow: "0 0 0 2px #ffc1079e", + }, + })} + /> + + + + + + ); +}; diff --git a/src/api/api.ts b/src/api/api.ts new file mode 100755 index 0000000..534e022 --- /dev/null +++ b/src/api/api.ts @@ -0,0 +1,2025 @@ +import { AxiosProgressEvent, CancelToken } from "axios"; +import i18n from "../i18n.ts"; +import { + AdminListGroupResponse, + AdminListService, + ListShareResponse as AdminListShareResponse, + StoragePolicy as AdminStoragePolicy, + BatchIDService, + CleanupTaskService, + CreateStoragePolicyCorsService, + Entity, + FetchWOPIDiscoveryService, + File as FileEnt, + FinishOauthCallbackService, + GetOauthRedirectService, + GetSettingService, + GroupEnt, + HomepageSummary, + ListEntityResponse, + ListFileResponse, + ListNodeResponse, + ListStoragePolicyResponse, + ListTaskResponse, + ListUserResponse, + Node, + OauthCredentialStatus, + QueueMetric, + SetSettingService, + Share as ShareEnt, + Task, + TestNodeDownloaderService, + TestNodeService, + TestSMTPService, + ThumbGeneratorTestService, + UpsertFileService, + UpsertGroupService, + UpsertNodeService, + UpsertStoragePolicyService, + UpsertUserService, + User as UserEnt, +} from "./dashboard.ts"; +import { + ArchiveListFilesResponse, + ArchiveListFilesService, + CreateFileService, + CreateViewerSessionService, + DeleteFileService, + DeleteUploadSessionService, + DirectLink, + FileResponse, + FileThumbResponse, + FileUpdateService, + FileURLResponse, + FileURLService, + GetFileInfoService, + ListFileService, + ListResponse, + MoveFileService, + MultipleUriService, + PatchMetadataService, + PatchViewSyncService, + PinFileService, + RenameFileService, + Share, + ShareCreateService, + UnlockFileService, + UploadCredential, + UploadSessionRequest, + VersionControlService, + ViewerGroup, + ViewerSessionResponse, +} from "./explorer.ts"; +import { AppError, Code, CrHeaders, defaultOpts, isRequestAbortedError, send, ThunkResponse } from "./request.ts"; +import { CreateDavAccountService, DavAccount, ListDavAccountsResponse, ListDavAccountsService } from "./setting.ts"; +import { ListShareResponse, ListShareService } from "./share.ts"; +import { CaptchaResponse, SiteConfig } from "./site.ts"; +import { + Capacity, + FinishPasskeyLoginService, + FinishPasskeyRegistrationService, + LoginResponse, + Passkey, + PasskeyCredentialOption, + PasswordLoginRequest, + PatchUserSetting, + PrepareLoginResponse, + PreparePasskeyLoginResponse, + RefreshTokenRequest, + ResetPasswordService, + SendResetEmailService, + SignUpService, + Token, + TwoFALoginRequest, + User, + UserSettings, +} from "./user.ts"; +import { + ArchiveWorkflowService, + DownloadWorkflowService, + ImportWorkflowService, + ListTaskService, + SetDownloadFilesService, + TaskListResponse, + TaskProgresses, + TaskResponse, +} from "./workflow.ts"; + +export function getSiteConfig(section: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/site/config/" + section, + { + method: "GET", + }, + { + ...defaultOpts, + bypassSnackbar: (e) => isRequestAbortedError(e), + errorSnackbarMsg: (e) => i18n.t("errLoadingSiteConfig", { ns: "common" }) + e.message, + }, + ), + ); + }; +} + +export function sendPrepareLogin(email: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/session/prepare", + { + params: { + email: email, + }, + method: "GET", + }, + { + ...defaultOpts, + noCredential: true, + bypassSnackbar: (e) => e instanceof AppError && e.code == Code.NodeFound, + }, + ), + ); + }; +} + +export function getCaptcha(): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/site/captcha", + { + method: "GET", + }, + { + ...defaultOpts, + noCredential: true, + errorSnackbarMsg: (e) => i18n.t("captchaError", { ns: "common" }) + e.message, + }, + ), + ); + }; +} + +export function sendLogin(req: PasswordLoginRequest): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/session/token", + { + data: req, + method: "POST", + }, + { + ...defaultOpts, + noCredential: true, + bypassSnackbar: (e) => e instanceof AppError && e.code == Code.Continue, + }, + ), + ); + }; +} + +export function send2FALogin(req: TwoFALoginRequest): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/session/token/2fa", + { + data: req, + method: "POST", + }, + { + ...defaultOpts, + noCredential: true, + }, + ), + ); + }; +} + +export function getUserMe(): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/user/me", + { + method: "GET", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendRefreshToken(req: RefreshTokenRequest): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/session/token/refresh", + { + data: req, + method: "POST", + }, + { + ...defaultOpts, + bypassSnackbar: (_e) => true, + noCredential: true, + }, + ), + ); + }; +} + +export function sendSignout(req: RefreshTokenRequest): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/session/token", + { + data: req, + method: "DELETE", + }, + { + ...defaultOpts, + noCredential: true, + }, + ), + ); + }; +} + +export function getFileList(req: ListFileService, skipSnackbar = true): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file", + { + params: req, + method: "GET", + }, + { + ...defaultOpts, + bypassSnackbar: (_e) => true, + }, + ), + ); + }; +} + +export function getFileThumb(path: string, contextHint?: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/thumb", + { + params: { uri: path }, + method: "GET", + headers: contextHint + ? { + [CrHeaders.context_hint]: contextHint, + } + : {}, + }, + { + ...defaultOpts, + bypassSnackbar: (_e) => true, + }, + ), + ); + }; +} + +export function getUserInfo(uid: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/user/info/" + uid, + { + method: "GET", + }, + { + ...defaultOpts, + bypassSnackbar: (_e) => true, + }, + ), + ); + }; +} + +export function getUserCapacity(): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/user/capacity", + { + method: "GET", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendDeleteFiles(req: DeleteFileService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file", + { + data: req, + method: "DELETE", + }, + { + ...defaultOpts, + skipBatchError: req.uris.length == 1, + }, + ), + ); + }; +} + +export function sendUnlockFiles(req: UnlockFileService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/lock", + { + data: req, + method: "DELETE", + }, + { + ...defaultOpts, + skipLockConflict: true, + }, + ), + ); + }; +} + +export function sendRenameFile(req: RenameFileService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/rename", + { + data: req, + method: "POST", + }, + { + ...defaultOpts, + bypassSnackbar: (e) => isRequestAbortedError(e), + }, + ), + ); + }; +} + +export function sendPinFile(req: PinFileService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/pin", + { + data: req, + method: "PUT", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendUnpinFile(req: PinFileService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/pin", + { + data: req, + method: "DELETE", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendMoveFile(req: MoveFileService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/move", + { + data: req, + method: "POST", + }, + { + ...defaultOpts, + skipBatchError: req.uris.length == 1, + }, + ), + ); + }; +} + +export function sendRestoreFile(req: DeleteFileService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/restore", + { + data: req, + method: "POST", + }, + { + ...defaultOpts, + skipBatchError: req.uris.length == 1, + }, + ), + ); + }; +} + +export function sendMetadataPatch(req: PatchMetadataService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/metadata", + { + data: req, + method: "PATCH", + }, + { + ...defaultOpts, + skipBatchError: req.uris.length == 1, + }, + ), + ); + }; +} + +export function getSearchUser(keyword: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/user/search?keyword=" + encodeURIComponent(keyword), + { + method: "GET", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendCreateShare(req: ShareCreateService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/share", + { + data: req, + method: "PUT", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendUpdateShare(req: ShareCreateService, id: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/share/" + id, + { + data: req, + method: "POST", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendDeleteShare(id: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/share/" + id, + { + method: "DELETE", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getShareInfo( + id: string, + password?: string, + count_views?: boolean, + owner_extended?: boolean, +): ThunkResponse { + return async (dispatch, _getState) => { + let uri = "/share/info/" + id; + const query = new URLSearchParams(); + if (password && password != "") { + query.set("password", password); + } + if (count_views) { + query.set("count_views", "true"); + } + if (owner_extended) { + query.set("owner_extended", "true"); + } + if (query.toString() != "") { + uri += "?" + query.toString(); + } + return await dispatch( + send( + uri, + { + method: "GET", + }, + { + ...defaultOpts, + bypassSnackbar: (_e) => true, + }, + ), + ); + }; +} + +export function sendCreateFile(req: CreateFileService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/create", + { + data: req, + method: "POST", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getFileEntityUrl(req: FileURLService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/url", + { + data: req, + method: "POST", + }, + { + ...defaultOpts, + skipBatchError: req.uris.length == 1, + }, + ), + ); + }; +} + +export function getFileInfo(req: GetFileInfoService, skipError = false): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/info", + { + method: "GET", + params: req, + }, + { + ...defaultOpts, + bypassSnackbar: () => skipError, + }, + ), + ); + }; +} + +export function setCurrentVersion(req: VersionControlService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/version/current", + { + method: "POST", + data: req, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function deleteVersion(req: VersionControlService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/version", + { + method: "DELETE", + data: req, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendUpdateFile(req: FileUpdateService, data: any): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/content", + { + data, + params: req, + method: "PUT", + headers: { + "Content-Type": "application/octet-stream", + }, + }, + { + bypassSnackbar: (e) => e instanceof AppError && e.code == Code.StaleVersion, + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendCreateViewerSession(req: CreateViewerSessionService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/viewerSession", + { + data: req, + method: "PUT", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendCreateUploadSession(req: UploadSessionRequest): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/upload", + { + data: req, + method: "PUT", + }, + { + ...defaultOpts, + bypassSnackbar: (_e) => true, + }, + ), + ); + }; +} + +export function sendUploadChunk( + sessionID: string, + chunk: Blob, + index: number, + cancel?: CancelToken, + onProgress?: (progressEvent: AxiosProgressEvent) => void, +): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/file/upload/${sessionID}/${index}`, + { + data: chunk, + cancelToken: cancel, + onUploadProgress: onProgress, + method: "POST", + headers: { + "Content-Type": "application/octet-stream", + }, + }, + { + ...defaultOpts, + bypassSnackbar: (_e) => true, + }, + ), + ); + }; +} + +export function sendDeleteUploadSession(req: DeleteUploadSessionService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/file/upload`, + { + data: req, + method: "DELETE", + }, + { + ...defaultOpts, + bypassSnackbar: (_e) => true, + }, + ), + ); + }; +} + +export function sendS3LikeCompleteUpload(policyType: string, sessionId: string, sessionKey: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/callback/${policyType}/${sessionId}/${sessionKey}`, + { + method: "GET", + }, + { + ...defaultOpts, + bypassSnackbar: (_e) => true, + }, + ), + ); + }; +} + +export function sendOneDriveCompleteUpload(sessionId: string, sessionKey: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/callback/onedrive/${sessionId}/${sessionKey}`, + { + method: "POST", + }, + { + ...defaultOpts, + bypassSnackbar: (_e) => true, + }, + ), + ); + }; +} + +export function sendCreateArchive(req: ArchiveWorkflowService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/workflow/archive", + { + data: req, + method: "POST", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendExtractArchive(req: ArchiveWorkflowService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/workflow/extract", + { + data: req, + method: "POST", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getTasks(req: ListTaskService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/workflow", + { + params: req, + method: "GET", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getTasksPhaseProgress(id: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/workflow/progress/" + id, + { + method: "GET", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendCreateRemoteDownload(req: DownloadWorkflowService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/workflow/download", + { + data: req, + method: "POST", + }, + { + ...defaultOpts, + skipBatchError: (req.src?.length ?? 0) <= 1, + }, + ), + ); + }; +} + +export function sendSetDownloadTarget(id: string, req: SetDownloadFilesService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/workflow/download/" + id, + { + data: req, + method: "PATCH", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendCancelDownloadTask(id: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/workflow/download/" + id, + { + method: "DELETE", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getShares(req: ListShareService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/share", + { + method: "GET", + params: req, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getDavAccounts(req: ListDavAccountsService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/devices/dav", + { + method: "GET", + params: req, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendCreateDavAccounts(req: CreateDavAccountService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/devices/dav", + { + method: "PUT", + data: req, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendUpdateDavAccounts(id: string, req: CreateDavAccountService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/devices/dav/${id}`, + { + method: "PATCH", + data: req, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendDeleteDavAccount(id: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/devices/dav/${id}`, + { + method: "DELETE", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getFileDirectLinks(req: MultipleUriService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/file/source", + { + data: req, + method: "PUT", + }, + { + ...defaultOpts, + skipBatchError: req.uris.length == 1, + acceptBatchPartialSuccess: true, + }, + ), + ); + }; +} + +export function sendDeleteDirectLink(id: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch(send(`/file/source/${id}`, { method: "DELETE" }, { ...defaultOpts })); + }; +} + +export function getUserShares(req: ListShareService, uid: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/user/shares/${uid}`, + { + method: "GET", + params: req, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getUserSettings(): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/user/setting`, + { + method: "GET", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendUploadAvatar(avatar?: Blob, contentType?: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/user/setting/avatar`, + { + method: "PUT", + data: avatar, + headers: contentType + ? { + "Content-Type": contentType, + } + : undefined, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendUpdateUserSetting(settings: PatchUserSetting): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/user/setting`, + { + method: "PATCH", + data: settings, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function get2FAInitSecret(): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/user/setting/2fa`, + { + method: "GET", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendPreparePasskeyRegistration(): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/user/authn`, + { + method: "PUT", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendFinishPasskeyRegistration(req: FinishPasskeyRegistrationService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/user/authn`, + { + method: "POST", + data: req, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendDeletePasskey(id: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/user/authn?id=${encodeURIComponent(id)}`, + { + method: "DELETE", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendFinishPasskeyLogin(req: FinishPasskeyLoginService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/session/authn`, + { + method: "POST", + data: req, + }, + { + ...defaultOpts, + noCredential: true, + }, + ), + ); + }; +} + +export function sendPreparePasskeyLogin(): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/session/authn`, + { + method: "PUT", + }, + { + ...defaultOpts, + noCredential: true, + }, + ), + ); + }; +} + +export function sendSinUp(req: SignUpService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/user", + { + data: req, + method: "POST", + }, + { + ...defaultOpts, + noCredential: true, + bypassSnackbar: (e) => e instanceof AppError && e.code == Code.Continue, + }, + ), + ); + }; +} + +export function sendEmailActivate(id: string, sign: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/user/activate/${id}?sign=${encodeURIComponent(sign)}`, + { + method: "GET", + }, + { + ...defaultOpts, + noCredential: true, + }, + ), + ); + }; +} + +export function sendResetEmail(req: SendResetEmailService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/user/reset`, + { + method: "POST", + data: req, + }, + { + ...defaultOpts, + noCredential: true, + }, + ), + ); + }; +} + +export function sendReset(uid: string, req: ResetPasswordService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/user/reset/${uid}`, + { + method: "PATCH", + data: req, + }, + { + ...defaultOpts, + noCredential: true, + }, + ), + ); + }; +} + +export function getDashboardSummary(generateCharts?: boolean): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/summary?generate=${!!generateCharts}`, + { + method: "GET", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getSettings(keys: GetSettingService): ThunkResponse<{ + [key: string]: string; +}> { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/settings`, + { + method: "POST", + data: keys, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendSetSetting(keys: SetSettingService): ThunkResponse<{ + [key: string]: string; +}> { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/settings`, + { + method: "PATCH", + data: keys, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getGroupList(args: AdminListService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/group`, + { + method: "POST", + data: args, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getWopiDiscovery(args: FetchWOPIDiscoveryService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/tool/wopi`, + { + method: "GET", + params: args, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendTestThumbGeneratorExecutable(args: ThumbGeneratorTestService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/tool/thumbExecutable`, + { + method: "POST", + data: args, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendTestSMTP(args: TestSMTPService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/tool/mail`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getQueueMetrics(): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/queue/metrics`, + { method: "GET" }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getStoragePolicyList(args: AdminListService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/policy`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getStoragePolicyDetail(id: number, countEntity?: boolean): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/policy/${id}`, + { method: "GET", params: { countEntity: countEntity ? true : undefined } }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function upsertStoragePolicy(args: UpsertStoragePolicyService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/policy${args.policy.id ? `/${args.policy.id}` : ""}`, + { method: "PUT", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getNodeList(args: AdminListService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/node`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getNodeDetail(id: number): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/node/${id}`, + { + method: "GET", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function upsertNode(args: UpsertNodeService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/node${args.node.id ? `/${args.node.id}` : ""}`, + { + method: "PUT", + data: args, + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendClearBlobUrlCache(): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/tool/entityUrlCache`, + { method: "DELETE" }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function createStoragePolicyCors(args: CreateStoragePolicyCorsService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/policy/cors`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getPolicyOauthRedirectUrl(): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/policy/oauth/redirect`, + { method: "GET" }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getPolicyOauthCredentialRefreshTime(id: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/policy/oauth/status/${id}`, + { method: "GET" }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getPolicyOauthUrl(args: GetOauthRedirectService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/policy/oauth/signin`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function finishOauthCallback(args: FinishOauthCallbackService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/policy/oauth/callback`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getOneDriveDriverRoot(id: number, url: string): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/policy/oauth/root/${id}`, + { method: "GET", params: { url } }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function deleteStoragePolicy(id: number): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/policy/${id}`, + { method: "DELETE" }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getGroupDetail(id: number, countUser?: boolean): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/group/${id}`, + { method: "GET", params: { countUser: countUser ? true : undefined } }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function upsertGroup(args: UpsertGroupService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/group${args.group.id ? `/${args.group.id}` : ""}`, + { method: "PUT", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function deleteGroup(id: number): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/group/${id}`, + { method: "DELETE" }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function deleteNode(id: number): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/node/${id}`, + { method: "DELETE" }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function testNode(args: TestNodeService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/node/test`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function testNodeDownloader(args: TestNodeDownloaderService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/node/test/downloader`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getUserList(args: AdminListService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/user`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getUserDetail(id: number): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/user/${id}`, + { method: "GET" }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function upsertUser(args: UpsertUserService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/user${args.user.id ? `/${args.user.id}` : ""}`, + { method: "PUT", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function batchDeleteUser(args: BatchIDService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/user/batch/delete`, + { method: "POST", data: args }, + { + ...defaultOpts, + skipBatchError: args.ids.length === 1, + }, + ), + ); + }; +} + +export function getFlattenFileList(args: AdminListService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/file`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getFileDetail(id: number): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/file/${id}`, + { method: "GET" }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function upsertFile(args: UpsertFileService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/file${args.file.id ? `/${args.file.id}` : ""}`, + { method: "PUT", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getFileUrl(id: number): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/file/url/${id}`, + { method: "GET" }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function batchDeleteFiles(args: BatchIDService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/file/batch/delete`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getEntityList(args: AdminListService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/entity`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getEntityDetail(id: number): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/entity/${id}`, + { method: "GET" }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getEntityUrl(id: number): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/entity/url/${id}`, + { method: "GET" }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function batchDeleteEntities(args: BatchIDService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/entity/batch/delete`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getTaskList(args: AdminListService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/queue`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getTaskDetail(id: number): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/queue/${id}`, + { method: "GET" }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function batchDeleteTasks(args: BatchIDService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/queue/batch/delete`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getShareList(args: AdminListService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/share`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getShareDetail(id: number): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/share/${id}`, + { method: "GET" }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function batchDeleteShares(args: BatchIDService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/share/batch/delete`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendCalibrateUserStorage(id: number): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/user/${id}/calibrate`, + { method: "POST" }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendImport(req: ImportWorkflowService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + "/workflow/import", + { + data: req, + method: "POST", + }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendPatchViewSync(args: PatchViewSyncService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/file/view`, + { method: "PATCH", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function sendCleanupTask(args: CleanupTaskService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/admin/queue/cleanup`, + { method: "POST", data: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} + +export function getArchiveListFiles(args: ArchiveListFilesService): ThunkResponse { + return async (dispatch, _getState) => { + return await dispatch( + send( + `/file/archive`, + { method: "GET", params: args }, + { + ...defaultOpts, + }, + ), + ); + }; +} diff --git a/src/api/dashboard.ts b/src/api/dashboard.ts new file mode 100755 index 0000000..bed893f --- /dev/null +++ b/src/api/dashboard.ts @@ -0,0 +1,553 @@ +import { EntityType, PaginationResults, PolicyType } from "./explorer.ts"; +import { Capacity } from "./user.ts"; +import { TaskStatus, TaskSummary, TaskType } from "./workflow.ts"; + +export interface MetricsSummary { + dates: string[]; + files: number[]; + users: number[]; + shares: number[]; + file_total: number; + user_total: number; + share_total: number; + entities_total: number; + generated_at: string; +} + +export interface Version { + version: string; + pro: boolean; + commit: string; +} + +export interface HomepageSummary { + metrics_summary?: MetricsSummary; + site_urls: string[]; + version: Version; +} + +export interface ManualRefreshLicenseService { + license: string; +} + +export interface GetSettingService { + keys: string[]; +} + +export interface SetSettingService { + settings: { + [key: string]: string; + }; +} + +export interface GroupEnt extends CommonMixin { + name: string; + max_storage?: number; + speed_limit?: number; + permissions?: string; + edges: { + storage_policies?: StoragePolicy; + }; + total_users?: number; + settings?: GroupSetting; +} + +export interface GroupSetting { + compress_size?: number; + decompress_size?: number; + remote_download_options?: Record; + source_batch?: number; + aria2_batch?: number; + max_walked_files?: number; + trash_retention?: number; + redirected_source?: boolean; +} + +export interface AdminListGroupResponse { + groups: GroupEnt[]; + pagination: PaginationResults; +} + +export interface QQConnectConfig { + app_id?: string; + app_secret?: string; + direct_sign_in?: boolean; +} + +export interface LogtoConfig { + endpoint?: string; + app_id?: string; + app_secret?: string; + direct_sign_in?: boolean; + display_name?: string; + direct_sso?: string; +} + +export interface FetchWOPIDiscoveryService { + endpoint: string; +} + +export interface ThumbGeneratorTestService { + name: string; + executable: string; +} + +export interface TestSMTPService { + to: string; + settings: { + [key: string]: string; + }; +} + +export enum QueueType { + IO_INTENSE = "io_intense", + MEDIA_META = "media_meta", + RECYCLE = "recycle", + THUMB = "thumb", + REMOTE_DOWNLOAD = "remote_download", +} + +export interface QueueMetric { + name: QueueType; + busy_workers: number; + success_tasks: number; + failure_tasks: number; + submitted_tasks: number; + suspending_tasks: number; +} + +export interface CommonMixin { + id: number; + created_at?: string; + updated_at?: string; + deleted_at?: string; +} + +export interface StoragePolicy extends CommonMixin { + name: string; + type: PolicyType; + server?: string; + bucket_name?: string; + is_private?: boolean; + access_key?: string; + secret_key?: string; + max_size?: number; + auto_rename?: boolean; + dir_name_rule?: string; + file_name_rule?: string; + settings?: PolicySetting; + node_id?: number; + edges: { + users?: User[]; + groups?: GroupEnt[]; + node?: Node; + }; + entities_count?: number; + entities_size?: number; +} + +export enum NodeType { + master = "master", + slave = "slave", +} + +export interface ListNodeResponse { + nodes: Node[]; + pagination: PaginationResults; +} + +export interface Node extends CommonMixin { + name?: string; + status?: NodeStatus; + type?: NodeType; + server?: string; + slave_key?: string; + capabilities?: string; + weight?: number; + settings?: NodeSetting; + edges: { + storage_policy?: StoragePolicy[]; + }; +} + +export enum DownloaderProvider { + qbittorrent = "qbittorrent", + aria2 = "aria2", +} + +export interface QBittorrentSetting { + server?: string; + user?: string; + password?: string; + options?: Record; + temp_path?: string; +} + +export interface Aria2Setting { + server?: string; + token?: string; + options?: Record; + temp_path?: string; +} + +export interface NodeSetting { + provider?: DownloaderProvider; + qbittorrent?: QBittorrentSetting; + aria2?: Aria2Setting; + interval?: number; + wait_for_seeding?: boolean; +} + +export enum NodeStatus { + active = "active", + suspended = "suspended", +} + +export interface PolicySetting { + token?: string; + file_type?: string[]; + is_file_type_deny_list?: boolean; + file_regexp?: string; + is_name_regexp_deny_list?: boolean; + od_redirect?: string; + custom_proxy?: boolean; + proxy_server?: string; + internal_proxy?: boolean; + od_driver?: string; + region?: string; + server_side_endpoint?: string; + chunk_size?: number; + tps_limit?: number; + tps_limit_burst?: number; + s3_path_style?: boolean; + thumb_exts?: string[]; + thumb_support_all_exts?: boolean; + thumb_max_size?: number; + relay?: boolean; + pre_allocate?: boolean; + media_meta_exts?: string[]; + media_meta_generator_proxy?: boolean; + thumb_generator_proxy?: boolean; + native_media_processing?: boolean; + s3_delete_batch_size?: number; + stream_saver?: boolean; + use_cname?: boolean; + source_auth?: boolean; + qiniu_upload_cdn?: boolean; + chunk_concurrency?: number; +} + +export interface User extends CommonMixin { + email: string; + nick: string; + password?: string; + settings?: UserSetting; + status?: UserStatus; + storage?: number; + avatar?: string; + credit?: number; + group_expires?: string; + notify_date?: string; + group_users?: number; + previous_group?: number; + unmanaged_email?: boolean; + edges: { + group?: GroupEnt; + storage_policy?: StoragePolicy; + openid?: OpenID[]; + passkey?: Passkey[]; + }; + + hash_id?: string; + two_fa_enabled?: boolean; + capacity?: Capacity; +} + +export interface OpenID extends CommonMixin { + provider?: number; +} + +export interface Passkey extends CommonMixin { + name?: string; +} + +export enum UserStatus { + active = "active", + inactive = "inactive", + manual_banned = "manual_banned", + sys_banned = "sys_banned", +} + +export interface UserSetting { + profile_off?: boolean; + preferred_policy?: number; + preferred_theme?: string; + version_retention?: boolean; + version_retention_ext?: string[]; + version_retention_max?: number; + pined?: PinedFile[]; + language?: string; +} + +export interface PinedFile { + uri: string; + name?: string; +} + +export interface ListStoragePolicyResponse { + policies: StoragePolicy[]; + pagination: PaginationResults; +} + +export interface AdminListService { + page: number; + page_size: number; + order_by: string; + order_direction: string; + conditions?: Record; + searches?: Record; +} + +export interface UpsertStoragePolicyService { + policy: StoragePolicy; +} + +export interface CreateStoragePolicyCorsService { + policy: StoragePolicy; +} + +export interface OauthCredentialStatus { + last_refresh_time: string; + valid: boolean; +} + +export interface GetOauthRedirectService { + id: number; + secret: string; + app_id: string; +} + +export interface FinishOauthCallbackService { + code: string; + state: string; +} + +export interface UpsertGroupService { + group: GroupEnt; +} + +export interface TestNodeService { + node: Node; +} + +export interface TestNodeDownloaderService extends TestNodeService {} + +export interface UpsertNodeService extends TestNodeService {} + +export interface ListUserResponse { + users: User[]; + pagination: PaginationResults; +} + +export interface UpsertUserService { + user: User; + password?: string; + two_fa?: string; +} + +export interface BatchIDService { + ids: number[]; + force?: boolean; +} + +/* +FilePermissions struct { + Groups map[int]*boolset.BooleanSet `json:"groups,omitempty"` + Users map[int]*boolset.BooleanSet `json:"users,omitempty"` + } + +*/ + +export interface File extends CommonMixin { + type?: number; + name?: string; + owner_id?: number; + size?: number; + primary_entity?: number; + file_children?: number; + is_symbolic?: boolean; + storage_policy_files?: number; + edges: { + owner?: User; + storage_policies?: StoragePolicy; + metadata?: Metadata[]; + entities?: Entity[]; + direct_links?: DirectLink[]; + shares?: Share[]; + }; + + user_hash_id?: string; + file_hash_id?: string; + direct_link_map?: Record; +} + +export interface DirectLink extends CommonMixin { + name?: string; + downloads?: number; + speed?: number; +} + +export interface Entity extends CommonMixin { + type?: EntityType; + source?: string; + size?: number; + reference_count?: number; + storage_policy_entities?: number; + upload_session_id?: string; + edges: { + user?: User; + storage_policy?: StoragePolicy; + file?: File[]; + }; + + user_hash_id?: string; + user_hash_id_map?: Record; +} + +export interface Metadata extends CommonMixin { + name?: string; + value?: string; + is_public?: boolean; +} + +export interface ListFileResponse { + files: File[]; + pagination: PaginationResults; +} + +export interface UpsertFileService { + file: File; +} + +export interface ListEntityResponse { + entities: Entity[]; + pagination: PaginationResults; +} + +export interface LogEntry { + category: number; + failed?: boolean; + error?: string; + user_agent?: string; + is_system?: boolean; + reason?: string; + email_to?: string; + email_title?: string; + original_name?: string; + new_name?: string; + from?: string; + to?: string; + entity_create_time?: string; + storage_policy_id?: string; + storage_policy_name?: string; + account_id?: number; + account?: string; + account_uri?: string; + payment_id?: number; + points_change?: number; + sku?: string; + storage_size?: number; + expire?: string; + group_id?: number; + group_id_from?: number; + direct_link_id?: string; + openid_provider?: number; + sub?: string; + name?: string; + passkey_id?: number; + exts?: Record; +} + +export interface AuditLog extends CommonMixin { + type?: number; + correlation_id?: string; + ip?: string; + content?: LogEntry; + edges: { + user?: User; + file?: File; + entity?: Entity; + share?: Share; + }; + + user_hash_id?: string; +} + +export interface ListAuditLogResponse { + logs: AuditLog[]; + pagination: PaginationResults; +} + +export interface TaskPublicState { + error?: string; + error_history?: string[]; + executed_duration?: number; + retry_count?: number; + resume_time?: number; + slave_task_props?: SlaveTaskProps; +} + +export interface SlaveTaskProps { + node_id?: number; + master_site_url?: string; + master_site_id?: string; + master_site_version?: string; +} + +export interface Task extends CommonMixin { + type?: string; + status?: TaskStatus; + public_state?: TaskPublicState; + private_state?: string; + correlation_id?: string; + user_tasks?: number; + edges: { + user?: User; + }; + + user_hash_id?: string; + task_hash_id?: string; + summary?: TaskSummary; + node?: Node; +} + +export interface ListTaskResponse { + tasks: Task[]; + pagination: PaginationResults; +} + +export interface Share extends CommonMixin { + password?: string; + views?: number; + downloads?: number; + expires?: string; + remain_downloads?: number; + edges: { + user?: User; + file?: File; + }; + + user_hash_id?: string; + share_link?: string; +} + +export interface ListShareResponse { + shares: Share[]; + pagination: PaginationResults; +} + +export interface CleanupTaskService { + not_after: string; + types?: TaskType[]; + status?: TaskStatus[]; +} diff --git a/src/api/explorer.ts b/src/api/explorer.ts new file mode 100755 index 0000000..531c4ba --- /dev/null +++ b/src/api/explorer.ts @@ -0,0 +1,576 @@ +import { ListViewColumnSetting } from "../component/FileManager/Explorer/ListView/Column.tsx"; +import { User } from "./user.ts"; + +export interface PaginationArgs { + page?: number; + page_size?: number; + order_by?: string; + order_direction?: string; + next_page_token?: string; +} + +export interface ListFileService extends PaginationArgs { + uri: string; +} + +export const FileType = { + file: 0, + folder: 1, +}; + +export interface FileResponse { + type: number; + id: string; + name: string; + created_at: string; + updated_at: string; + size: number; + metadata?: { + [key: string]: string; + }; + path: string; + shared?: boolean; + capability?: string; + owned?: boolean; + folder_summary?: FolderSummary; + extended_info?: ExtendedInfo; + primary_entity?: string; +} + +export interface FolderSummary { + size: number; + files: number; + folders: number; + completed: boolean; + calculated_at: string; +} + +export interface ExtendedInfo { + storage_policy?: StoragePolicy; + storage_used: number; + shares?: Share[]; + entities?: Entity[]; + view?: ExplorerView; + direct_links?: DirectLink[]; +} + +export interface DirectLink { + id: string; + created_at: string; + url: string; + downloaded: number; +} + +export interface Entity { + id: string; + type: number; + created_at: string; + storage_policy?: StoragePolicy; + size: number; + created_by?: User; +} + +export interface Share { + id: string; + name?: string; + expires?: string; + is_private?: boolean; + share_view?: boolean; + remain_downloads?: number; + created_at?: string; + url: string; + visited: number; + downloaded: number; + expired?: boolean; + unlocked: boolean; + password_protected: boolean; + source_type?: number; + owner: User; + source_uri?: string; + password?: string; + show_readme?: boolean; +} + +export enum PolicyType { + local = "local", + remote = "remote", + oss = "oss", + qiniu = "qiniu", + onedrive = "onedrive", + cos = "cos", + upyun = "upyun", + s3 = "s3", + ks3 = "ks3", + obs = "obs", + load_balance = "load_balance", +} + +export interface StoragePolicy { + id: string; + name: string; + allowed_suffix?: string[]; + denied_suffix?: string[]; + allowed_name_regexp?: string; + denied_name_regexp?: string; + max_size: number; + type: PolicyType; + relay?: boolean; + chunk_concurrency?: number; +} + +export interface PaginationResults { + page: number; + page_size: number; + total_items?: number; + next_token?: string; + is_cursor?: boolean; +} + +export interface NavigatorProps { + capability?: string; + max_page_size: number; + order_by_options: string[]; + order_direction_options: string[]; +} + +export interface ExplorerView { + page_size: number; + order?: string; + order_direction?: string; + view?: string; + thumbnail?: boolean; + columns?: ListViewColumnSetting[]; + gallery_width?: number; +} + +export interface ListResponse { + files: FileResponse[]; + pagination: PaginationResults; + props: NavigatorProps; + context_hint?: string; + recursion_limit_reached?: boolean; + mixed_type?: boolean; + single_file_view?: boolean; + parent?: FileResponse; + storage_policy?: StoragePolicy; + view?: ExplorerView; +} + +export const Metadata = { + share_redirect: "sys:shared_redirect", + share_owner: "sys:shared_owner", + upload_session_id: "sys:upload_session_id", + icon_color: "customize:icon_color", + emoji: "customize:emoji", + live_photo: "customize:live_photo", + tag_prefix: "tag:", + thumbDisabled: "thumb:disabled", + restore_uri: "sys:restore_uri", + expected_collect_time: "sys:expected_collect_time", + + // Exif + gps_lng: "exif:longitude", + gps_lat: "exif:latitude", + gps_attitude: "exif:altitude", + artist: "exif:artist", + copy_right: "exif:copy_right", + camera_model: "exif:camera_model", + camera_make: "exif:camera_make", + camera_owner_name: "exif:camera_owner_name", + body_serial_number: "exif:body_serial_number", + lens_make: "exif:lens_make", + lens_model: "exif:lens_model", + software: "exif:software", + exposure_time: "exif:exposure_time", + f_number: "exif:f", + aperture_value: "exif:aperture_value", + focal_length: "exif:focal_length", + iso_speed_ratings: "exif:iso", + pixel_x_dimension: "exif:x", + pixel_y_dimension: "exif:y", + orientation: "exif:orientation", + taken_at: "exif:taken_at", + flash: "exif:flash", + image_description: "exif:image_description", + projection_type: "exif:projection_type", + exposure_bias_value: "exif:exposure_bias", + + // Music + music_title: "music:title", + music_artist: "music:artist", + music_album: "music:album", + + // Stream + stream_title: "stream:title", + stream_duration: "stream:duration", + stream_format_name: "stream:format", + stream_format_long: "stream:formatLong", + stream_bit_rate: "stream:bitrate", + stream_description: "stream:description", + stream_indexed_codec: "codec", + stream_indexed_bitrate: "bitrate", + stream_indexed_width: "width", + stream_indexed_height: "height", + + // Geocoding + street: "geocoding:street", + locality: "geocoding:locality", + place: "geocoding:place", + district: "geocoding:district", + region: "geocoding:region", + country: "geocoding:country", +}; + +export interface FileThumbResponse { + url: string; + expires?: string; +} + +export interface DeleteFileService { + uris: string[]; + unlink?: boolean; + skip_soft_delete?: boolean; +} + +export interface LockApplication { + type: string; + inner_xml?: string; + viewer_id?: string; +} + +export interface LockOwner { + application: LockApplication; +} + +export interface ConflictDetail { + path?: string; + token?: string; + owner?: LockOwner; + type: number; +} + +export interface UnlockFileService { + tokens: string[]; +} + +export interface RenameFileService { + uri: string; + new_name: string; +} + +export const NavigatorCapability = { + create_file: 0, + rename_file: 1, + upload_file: 6, + download_file: 7, + update_metadata: 8, + list_children: 9, + generate_thumb: 10, + delete_file: 14, + lock_file: 15, + soft_delete: 16, + restore: 17, + share: 18, + info: 19, + version_control: 20, + enter_folder: 23, +}; + +export interface PinFileService { + uri: string; + name?: string; +} + +export interface MoveFileService extends MultipleUriService { + dst: string; + copy?: boolean; +} + +export interface MetadataPatch { + key: string; + value?: string; + remove?: boolean; +} + +export interface PatchMetadataService extends MultipleUriService { + patches: MetadataPatch[]; +} + +export interface ShareCreateService { + uri: string; + downloads?: number; + is_private?: boolean; + password?: string; + expire?: number; + share_view?: boolean; + show_readme?: boolean; +} + +export interface CreateFileService { + uri: string; + type: "file" | "folder"; + err_on_conflict?: boolean; + metadata?: { + [key: string]: string; + }; +} + +export interface FileURLService extends MultipleUriService { + download?: boolean; + redirect?: boolean; + entity?: string; + no_cache?: boolean; + skip_error?: boolean; + use_primary_site_url?: boolean; + archive?: boolean; +} + +export interface FileURLResponse { + urls: EntityURLResponse[]; + expires: string; +} + +export interface EntityURLResponse { + url: string; + stream_saver_display_name?: string; +} + +export interface GetFileInfoService { + uri?: string; + id?: string; + extended?: boolean; + folder_summary?: boolean; +} + +export enum EntityType { + version = 0, + thumbnail = 1, + live_photo = 2, +} + +export interface VersionControlService { + uri: string; + version: string; +} + +export const AuditLogType = { + server_start: 0, + user_signup: 1, + email_sent: 2, + user_activated: 3, + user_login_failed: 4, + user_login: 5, + user_token_refresh: 6, + file_create: 7, + file_rename: 8, + set_file_permission: 9, + entity_uploaded: 10, + entity_downloaded: 11, + copy_from: 12, + copy_to: 13, + move_to: 14, + delete_file: 15, + move_to_trash: 16, + share: 17, + share_link_viewed: 18, + set_current_version: 19, + delete_version: 20, + thumb_generated: 21, + live_photo_uploaded: 22, + update_metadata: 23, + edit_share: 24, + delete_share: 25, + mount: 26, + relocate: 27, + create_archive: 28, + extract_archive: 29, + webdav_login_failed: 30, + webdav_account_create: 31, + webdav_account_update: 32, + webdav_account_delete: 33, + payment_created: 34, + points_change: 35, + payment_paid: 36, + payment_fulfilled: 37, + payment_fulfill_failed: 38, + storage_added: 39, + group_changed: 40, + user_exceed_quota_notified: 41, + user_changed: 42, + get_direct_link: 43, + link_account: 44, + unlink_account: 45, + change_nick: 46, + change_avatar: 47, + membership_unsubscribe: 48, + change_password: 49, + enable_2fa: 50, + disable_2fa: 51, + add_passkey: 52, + remove_passkey: 53, + redeem_gift_code: 54, + file_imported: 55, + update_view: 56, + delete_direct_link: 57, + report_abuse: 58, +}; + +export interface MultipleUriService { + uris: string[]; +} + +export const ViewerAction = { + edit: "edit", + view: "view", +}; + +export const ViewerType = { + builtin: "builtin", + wopi: "wopi", + custom: "custom", +}; + +export enum ViewerPlatform { + pc = "pc", + mobile = "mobile", + all = "all", +} + +export interface Viewer { + id: string; + type: string; + display_name: string; + exts: string[]; + icon: string; + url?: string; + max_size?: number; + disabled?: boolean; + props?: { + [key: string]: string; + }; + wopi_actions?: { + [key: string]: { + [key: string]: string; + }; + }; + templates?: NewFileTemplate[]; + platform?: ViewerPlatform; + required_group_permission?: number[]; +} + +export interface NewFileTemplate { + ext: string; + display_name: string; +} + +export interface ViewerGroup { + viewers: Viewer[]; +} + +export interface FileUpdateService { + uri: string; + previous?: string; +} + +export interface ViewerSession { + id: string; + access_token: string; + expires: number; +} + +export interface ViewerSessionResponse { + session: ViewerSession; + wopi_src?: string; +} + +export interface CreateViewerSessionService { + uri: string; + viewer_id: string; + preferred_action: string; + version?: string; +} + +export interface UploadSessionRequest { + uri: string; + size: number; + policy_id: string; + last_modified?: number; + entity_type?: string; + metadata?: { + [key: string]: string; + }; + mime_type?: string; +} + +export interface UploadCredential { + session_id: string; + expires: number; + chunk_size: number; + upload_urls: string[]; + credential: string; + uploadID: string; + callback: string; + ak: string; + keyTime: string; + path: string; + completeURL: string; + storage_policy?: StoragePolicy; + uri: string; + callback_secret: string; + mime_type?: string; + upload_policy?: string; +} + +export interface DeleteUploadSessionService { + id: string; + uri: string; +} + +export interface DirectLink { + file_url: string; + link: string; +} + +export interface PatchViewSyncService { + uri: string; + view?: ExplorerView; +} + +export interface CustomProps { + id: string; + name: string; + type: CustomPropsType; + max?: number; + min?: number; + default?: string; + options?: string[]; + icon?: string; +} + +export enum CustomPropsType { + text = "text", + number = "number", + boolean = "boolean", + select = "select", + multi_select = "multi_select", + user = "user", + link = "link", + rating = "rating", +} + +export interface ArchivedFile { + name: string; + size: number; + updated_at?: string; + is_directory: boolean; +} + +export interface ArchiveListFilesResponse { + files: ArchivedFile[]; +} + +export interface ArchiveListFilesService { + uri: string; + entity?: string; + text_encoding?: string; +} diff --git a/src/api/request.ts b/src/api/request.ts new file mode 100755 index 0000000..bbb91d9 --- /dev/null +++ b/src/api/request.ts @@ -0,0 +1,283 @@ +import axios, { AxiosRequestConfig } from "axios"; +import { enqueueSnackbar, SnackbarAction } from "notistack"; +import { DefaultCloseAction, ErrorListDetailAction } from "../component/Common/Snackbar/snackbar.tsx"; +import i18n from "../i18n.ts"; +import { AppThunk } from "../redux/store.ts"; +import { openLockConflictDialog } from "../redux/thunks/dialog.ts"; +import { router } from "../router"; +import SessionManager from "../session"; +import { ErrNames } from "../session/errors.ts"; +import { sendRefreshToken } from "./api.ts"; + +export interface requestOpts { + errorSnackbarMsg: (e: Error) => string; + bypassSnackbar?: (e: Error) => boolean; + noCredential: boolean; + skipBatchError?: boolean; + skipLockConflict?: boolean; + withHost?: boolean; + acceptBatchPartialSuccess?: boolean; +} + +export const defaultOpts: requestOpts = { + errorSnackbarMsg: (e) => e.message, + noCredential: false, + skipBatchError: false, +}; + +export const ApiPrefix = "/api/v4"; + +const instance = axios.create({ + baseURL: ApiPrefix, +}); +export interface AggregatedError { + [key: string]: Response; +} + +export interface Response { + data: T; + code: number; + msg: string; + error?: string; + correlation_id?: string; + aggregated_error?: AggregatedError; +} + +export class AppError extends Error { + public code: any; + public cid: string | undefined = undefined; + public aggregatedError: AggregatedError | undefined = undefined; + public rawMessage: string = ""; + public error?: string = undefined; + public response: Response; + + constructor(resp: Response) { + const message = resp.msg; + const code = resp.code; + super(message); + + this.response = resp; + this.code = code; + const error = resp.error; + this.cid = resp.correlation_id; + this.aggregatedError = resp.aggregated_error; + this.rawMessage = message; + this.error = error; + + if (i18n.exists(`errors.${code}`, { ns: "common" })) { + this.message = i18n.t(`errors.${code}`, { + ns: "common", + message, + }); + } else if (i18n.exists(`errors.${code}`, { ns: "dashboard" })) { + this.message = i18n.t(`errors.${code}`, { + ns: "dashboard", + message, + }); + } else { + this.message = message || i18n.t("unknownError", { ns: "common" }); + } + + this.message += error && !this.message.includes(error) ? ` (${error})` : ""; + this.stack = new Error().stack; + } + + ErrorResponse = (): Response => { + return this.response; + }; +} +// +// instance.interceptors.response.use( +// function (response: any) { +// response.rawData = response.data; +// response.data = response.data.data; +// if ( +// response.rawData.code !== undefined && +// response.rawData.code !== 0 && +// response.rawData.code !== 203 +// ) { +// // Login expired +// if (response.rawData.code === 401) { +// Auth.signout(); +// window.location.href = +// "/login?redirect=" + +// encodeURIComponent( +// window.location.pathname + window.location.search +// ); +// } +// +// // Non-admin +// if (response.rawData.code === 40008) { +// window.location.href = "/home"; +// } +// +// // Not binding mobile phone +// if (response.rawData.code === 40010) { +// window.location.href = "/setting?modal=phone"; +// } +// throw new AppError( +// response.rawData.msg, +// response.rawData.code, +// response.rawData.error +// ); +// } +// return response; +// }, +// function (error) { +// return Promise.reject(error); +// } +// ); + +export type ThunkResponse = AppThunk>; + +export const Code = { + Success: 0, + Continue: 203, + CredentialInvalid: 40020, + IncorrectPassword: 40069, + LockConflict: 40073, + StaleVersion: 40076, + BatchOperationNotFullyCompleted: 40081, + DomainNotLicensed: 40087, + AnonymouseAccessDenied: 40088, + CodeLoginRequired: 401, + PermissionDenied: 403, + NodeFound: 404, +}; + +export const CrHeaderPrefix = "X-Cr-"; +export const CrHeaders = { + context_hint: CrHeaderPrefix + "Context-Hint", +}; + +function getAccessToken(): AppThunk> { + return async (dispatch, _getState) => { + try { + const accessToken = await SessionManager.getAccessToken(); + return `Bearer ${accessToken}`; + } catch (e) { + if (!(e instanceof Error)) { + throw e; + } + switch (e.name) { + case ErrNames.ErrNoAvailableSession: + case ErrNames.ErrRefreshTokenExpired: + case ErrNames.ErrNoSesssionSelected: + return undefined; + case ErrNames.ErrAccessTokenExpired: + // try to refresh token + console.log("refresh access token"); + const newToken = await dispatch(refreshToken()); + return `Bearer ${newToken}`; + } + + throw e; + } + }; +} + +function refreshToken(): AppThunk> { + return async (dispatch, _getState) => { + const user = SessionManager.currentLogin(); + const token = await dispatch(sendRefreshToken({ refresh_token: user.token.refresh_token })); + SessionManager.refreshToken(user.user.id, token); + return token.access_token; + }; +} + +let signOutLock = false; + +export function send( + url: string, + config?: AxiosRequestConfig, + opts: requestOpts = defaultOpts, +): ThunkResponse { + return async (dispatch, _getState) => { + try { + let axiosConf: AxiosRequestConfig = { + ...config, + headers: { + ...config?.headers, + }, + url, + }; + + if (!opts.noCredential) { + const token = await dispatch(getAccessToken()); + if (token && axiosConf.headers) { + axiosConf.headers["Authorization"] = token; + } + } + + const resp = await instance.request>(axiosConf); + + if (resp.data.code !== undefined && resp.data.code !== Code.Success) { + switch (resp.data.code) { + case Code.CredentialInvalid: + case Code.CodeLoginRequired: + if (!signOutLock) { + SessionManager.signOutCurrent(); + router.navigate( + "/session?redirect=" + encodeURIComponent(window.location.pathname + window.location.search), + ); + } + signOutLock = true; + } + + throw new AppError(resp.data); + } + return resp.data.data; + } catch (e) { + let partialSuccessResponse: any = undefined; + if (e instanceof Error) { + // Handle lock conflict error + if (e instanceof AppError && e.code == Code.LockConflict && !opts.skipLockConflict) { + let rejected = false; + try { + await dispatch(openLockConflictDialog(e.response)); + } catch (e) { + rejected = true; + } + if (!rejected) { + return await dispatch(send(url, config, opts)); + } + } + + if (opts.bypassSnackbar && opts.bypassSnackbar(e)) { + throw e; + } + + let action: SnackbarAction = DefaultCloseAction; + // Handle aggregated error + if (e instanceof AppError && e.code == Code.BatchOperationNotFullyCompleted) { + if (!opts.skipBatchError) { + action = ErrorListDetailAction(e.ErrorResponse()); + if (opts.acceptBatchPartialSuccess) { + partialSuccessResponse = e.response.data; + } + } else { + const inner = e.aggregatedError?.[Object.keys(e.aggregatedError)[0]]; + e = inner?.code ? new AppError(inner) : e; + } + } + + if (e instanceof Error) { + enqueueSnackbar({ + message: opts.errorSnackbarMsg(e), + variant: "error", + action, + }); + } + } + + if (partialSuccessResponse) { + return partialSuccessResponse; + } + throw e; + } + }; +} + +export const isRequestAbortedError = (e: Error) => { + return e.message == "Request aborted"; +}; diff --git a/src/api/setting.ts b/src/api/setting.ts new file mode 100755 index 0000000..ee8928d --- /dev/null +++ b/src/api/setting.ts @@ -0,0 +1,34 @@ +import { PaginationResults } from "./explorer.ts"; + +export interface ListDavAccountsService { + page_size: number; + next_page_token?: string; +} + +export interface DavAccount { + id: string; + created_at: string; + name: string; + uri: string; + password: string; + options?: string; +} + +export interface ListDavAccountsResponse { + accounts: DavAccount[]; + pagination?: PaginationResults; +} + +export const DavAccountOption = { + readonly: 0, + proxy: 1, + disable_sys_files: 2, +}; + +export interface CreateDavAccountService { + name: string; + uri: string; + readonly?: boolean; + proxy?: boolean; + disable_sys_files?: boolean; +} diff --git a/src/api/share.ts b/src/api/share.ts new file mode 100755 index 0000000..1463672 --- /dev/null +++ b/src/api/share.ts @@ -0,0 +1,28 @@ +import { User } from "./user.ts"; +import { PaginationResults, Share } from "./explorer.ts"; + +export interface ShareInfo { + id: string; + name?: string; + source_type?: number; + remain_downloads?: number; + visited?: number; + downloaded?: number; + expires?: string; + created_at?: string; + unlocked: boolean; + owner: User; + expired?: boolean; +} + +export interface ListShareService { + page_size: number; + order_by?: string; + order_direction?: string; + next_page_token?: string; +} + +export interface ListShareResponse { + shares: Share[]; + pagination: PaginationResults; +} diff --git a/src/api/site.ts b/src/api/site.ts new file mode 100755 index 0000000..829f51d --- /dev/null +++ b/src/api/site.ts @@ -0,0 +1,66 @@ +import { CustomProps, ViewerGroup } from "./explorer.ts"; +import { User } from "./user.ts"; + +export enum CaptchaType { + NORMAL = "normal", + RECAPTCHA = "recaptcha", + // Deprecated + TCAPTCHA = "tcaptcha", + TURNSTILE = "turnstile", + CAP = "cap", +} + +export interface SiteConfig { + instance_id?: string; + title?: string; + login_captcha?: boolean; + reg_captcha?: boolean; + forget_captcha?: boolean; + themes?: string; + default_theme?: string; + authn?: boolean; + user?: User; + captcha_ReCaptchaKey?: string; + captcha_type?: CaptchaType; + turnstile_site_id?: string; + captcha_cap_instance_url?: string; + captcha_cap_site_key?: string; + captcha_cap_secret_key?: string; + captcha_cap_asset_server?: string; + register_enabled?: boolean; + logo?: string; + logo_light?: string; + tos_url?: string; + privacy_policy_url?: string; + icons?: string; + emoji_preset?: string; + map_provider?: string; + mapbox_ak?: string; + google_map_tile_type?: string; + file_viewers?: ViewerGroup[]; + max_batch_size?: number; + app_promotion?: boolean; + thumbnail_width?: number; + thumbnail_height?: number; + custom_props?: CustomProps[]; + custom_nav_items?: CustomNavItem[]; + custom_html?: CustomHTML; + thumb_exts?: string[]; +} + +export interface CaptchaResponse { + ticket: string; + image: string; +} + +export interface CustomNavItem { + name: string; + url: string; + icon: string; +} + +export interface CustomHTML { + headless_footer?: string; + headless_bottom?: string; + sidebar_bottom?: string; +} diff --git a/src/api/user.ts b/src/api/user.ts new file mode 100755 index 0000000..ffcaabb --- /dev/null +++ b/src/api/user.ts @@ -0,0 +1,202 @@ +/** + * UserLoginService 管ç†ç”¨æˆ·ç™»å½•çš„æœåŠ¡ + */ +export interface UserLoginService { + email: string; + password: string; + otp?: string; +} + +/** + * User 用户åºåˆ—化器 + */ +export interface User { + id: string; + email?: string; + nickname: string; + status?: any /* user.Status */; + avatar?: string; + created_at: any /* time.Time */; + preferred_theme?: string; + anonymous?: boolean; + group?: Group; + pined?: PinedFile[]; + language?: string; + disable_view_sync?: boolean; + share_links_in_profile?: ShareLinksInProfileLevel; +} +export interface Group { + id: string; + name: string; + permission?: string; + direct_link_batch_size?: number; + trash_retention?: number; +} + +export interface PinedFile { + uri: string; + name?: string; +} + +export interface PrepareLoginResponse { + webauthn_enabled: boolean; + sso_enabled: boolean; + password_enabled: boolean; + qq_enabled: boolean; +} + +export interface CaptchaRequest { + [key: string]: any; +} + +export interface PasswordLoginRequest extends CaptchaRequest { + email: string; + password: string; +} + +export interface Token { + access_token: string; + refresh_token: string; + access_expires: string; + refresh_expires: string; +} + +export interface LoginResponse { + user: User; + token: Token; +} + +export interface TwoFALoginRequest { + otp: string; + session_id: string; +} + +export interface RefreshTokenRequest { + refresh_token: string; +} + +export interface Capacity { + total: number; + used: number; +} + +export const GroupPermission = { + is_admin: 0, + is_anonymous: 1, + share: 2, + webdav: 3, + archive_download: 4, + archive_task: 5, + webdav_proxy: 6, + share_download: 7, + remote_download: 9, + redirected_source: 11, + advance_delete: 12, + unique_direct_link: 17, +}; + +export interface UserSettings { + version_retention_enabled: boolean; + version_retention_ext?: string[]; + version_retention_max?: number; + passwordless: boolean; + two_fa_enabled: boolean; + passkeys?: Passkey[]; + disable_view_sync: boolean; + share_links_in_profile: ShareLinksInProfileLevel; +} + +export interface PatchUserSetting { + nick?: string; + language?: string; + preferred_theme?: string; + version_retention_enabled?: boolean; + version_retention_ext?: string[]; + version_retention_max?: number; + current_password?: string; + new_password?: string; + two_fa_enabled?: boolean; + two_fa_code?: string; + disable_view_sync?: boolean; + share_links_in_profile?: ShareLinksInProfileLevel; +} + +export interface PasskeyCredentialOption { + publicKey: { + rp: { + name: string; + id: string; + }; + user: { + name: string; + displayName: string; + id: string; + }; + challenge: string; + pubKeyCredParams: { + type: "public-key"; + alg: number; + }[]; + timeout: number; + excludeCredentials: { + type: "public-key"; + id: string; + }[]; + authenticatorSelection: { + requireResidentKey: boolean; + userVerification: UserVerificationRequirement; + }; + }; +} + +export interface PasskeyCredentialLoginOption { + publicKey: { + challenge: string; + timeout: number; + rpId: string; + }; +} + +export interface PreparePasskeyLoginResponse { + options: PasskeyCredentialLoginOption; + session_id: string; +} + +export interface FinishPasskeyRegistrationService { + response: string; + name: string; + ua: string; +} + +export interface Passkey { + id: string; + name: string; + created_at: string; + used_at: string; +} + +export interface FinishPasskeyLoginService { + response: string; + session_id: string; +} + +export interface SignUpService extends CaptchaRequest { + email: string; + password: string; + language: string; +} + +export interface SendResetEmailService extends CaptchaRequest { + email: string; +} + +export interface ResetPasswordService { + password: string; + secret: string; +} + +export enum ShareLinksInProfileLevel { + public_share_only = "", + all_share = "all_share", + hide_share = "hide_share", +} diff --git a/src/api/workflow.ts b/src/api/workflow.ts new file mode 100755 index 0000000..94c7536 --- /dev/null +++ b/src/api/workflow.ts @@ -0,0 +1,163 @@ +import { PaginationResults } from "./explorer.ts"; + +export interface ArchiveWorkflowService { + src: string[]; + dst: string; + encoding?: string; + password?: string; + file_mask?: string[]; +} + +export interface TaskListResponse { + tasks: TaskResponse[]; + pagination: PaginationResults; +} + +export interface TaskResponse { + created_at: string; + updated_at: string; + id: string; + status: string; + type: string; + node?: NodeSummary; + summary?: TaskSummary; + error?: string; + error_history?: string[]; + duration?: number; + resume_time?: number; + retry_count?: number; +} + +export interface TaskSummary { + phase?: string; + props: { + src?: string; + src_str?: string; + dst?: string; + src_multiple?: string[]; + dst_policy_id?: string; + failed?: number; + download?: DownloadTaskStatus; + }; +} + +export enum DownloadTaskState { + seeding = "seeding", + downloading = "downloading", + error = "error", + completed = "completed", + unknown = "unknown", +} + +export interface DownloadTaskStatus { + name: string; + state: DownloadTaskState; + total: number; + downloaded: number; + download_speed: number; + upload_speed: number; + uploaded: number; + files?: DownloadTaskFile[]; + hash?: string; + pieces?: string; + num_pieces?: number; +} + +export interface DownloadTaskFile { + index: number; + name: string; + size: number; + progress: number; + selected: boolean; +} + +export interface NodeSummary { + id: string; + name: string; + type: NodeTypes; + capabilities: string; +} + +export enum NodeTypes { + master = "master", + slave = "slave", +} + +export const NodeCapability = { + none: 0, + create_archive: 1, + extract_archive: 2, + remote_download: 3, + //relocate: 4, +}; + +export interface RelocateWorkflowService { + src: string[]; + dst_policy_id: string; +} + +export interface DownloadWorkflowService { + src?: string[]; + src_file?: string; + dst: string; +} + +export interface ImportWorkflowService { + src: string; + dst: string; + extract_media_meta?: boolean; + user_id: string; + recursive?: boolean; + policy_id: number; +} + +export interface ListTaskService { + page_size: number; + category: ListTaskCategory; + next_page_token?: string; +} + +export enum ListTaskCategory { + general = "general", + downloading = "downloading", + downloaded = "downloaded", +} + +export enum TaskType { + create_archive = "create_archive", + extract_archive = "extract_archive", + remote_download = "remote_download", + media_metadata = "media_meta", + entity_recycle_routine = "entity_recycle_routine", + explicit_entity_recycle = "explicit_entity_recycle", + upload_sentinel_check = "upload_sentinel_check", + import = "import", +} + +export enum TaskStatus { + queued = "queued", + processing = "processing", + suspending = "suspending", + error = "error", + canceled = "canceled", + completed = "completed", +} + +export interface TaskProgress { + total: number; + current: number; + identifier: string; +} + +export interface TaskProgresses { + [key: string]: TaskProgress; +} + +export interface SetFileToDownloadArgs { + index: number; + download: boolean; +} + +export interface SetDownloadFilesService { + files: SetFileToDownloadArgs[]; +} diff --git a/src/component/Admin/AdminBundle.tsx b/src/component/Admin/AdminBundle.tsx new file mode 100755 index 0000000..8b0bef6 --- /dev/null +++ b/src/component/Admin/AdminBundle.tsx @@ -0,0 +1,33 @@ +import EntitySetting from "./Entity/EntitySetting"; +import FileSetting from "./File/FileSetting"; +import FileSystem from "./FileSystem/Filesystem"; +import EditGroup from "./Group/EditGroup/EditGroup"; +import GroupSetting from "./Group/GroupSetting"; +import Home from "./Home/Home"; +import EditNode from "./Node/EditNode"; +import NodeSetting from "./Node/NodeSetting"; +import Settings from "./Settings/Settings"; +import ShareList from "./Share/ShareList"; +import EditStoragePolicy from "./StoragePolicy/EditStoragePolicy/EditStoragePolicy"; +import OauthCallback from "./StoragePolicy/OauthCallback"; +import StoragePolicySetting from "./StoragePolicy/StoragePolicySetting"; +import TaskList from "./Task/TaskList"; +import UserSetting from "./User/UserSetting"; + +export { + EditGroup, + EditNode, + EditStoragePolicy, + EntitySetting, + FileSetting, + FileSystem, + GroupSetting, + Home, + NodeSetting, + OauthCallback, + Settings, + ShareList, + StoragePolicySetting, + TaskList, + UserSetting, +}; diff --git a/src/component/Admin/Common/AdminCard.tsx b/src/component/Admin/Common/AdminCard.tsx new file mode 100755 index 0000000..66a325a --- /dev/null +++ b/src/component/Admin/Common/AdminCard.tsx @@ -0,0 +1,41 @@ +import { Box, styled } from "@mui/material"; + +export const BorderedCard = styled(Box)(({ theme }) => ({ + border: `1px solid ${theme.palette.divider}`, + borderRadius: theme.shape.borderRadius, + padding: theme.spacing(2), + backgroundColor: theme.palette.background.paper, +})); + +export const BorderedCardClickable = styled(BorderedCard)(({ theme }) => ({ + cursor: "pointer", + "&:hover": { + backgroundColor: theme.palette.action.hover, + }, + transition: "background-color 0.3s ease", +})); + +export const BorderedCardClickableBaImg = styled(BorderedCardClickable)<{ img?: string }>(({ theme, img }) => ({ + position: "relative", + overflow: "hidden", + "&::before": { + content: '""', + position: "absolute", + top: "-20px", + right: "-20px", + width: "150px", + height: "150px", + backgroundImage: `url(${img})`, + backgroundSize: "cover", + backgroundPosition: "center", + transform: "rotate(-10deg)", + opacity: 0.1, + maskImage: "radial-gradient(circle at center, black 30%, transparent 80%)", + pointerEvents: "none", + zIndex: 0, + }, + "& > *": { + position: "relative", + zIndex: 1, + }, +})); diff --git a/src/component/Admin/Common/EndpointInput.tsx b/src/component/Admin/Common/EndpointInput.tsx new file mode 100755 index 0000000..caa6258 --- /dev/null +++ b/src/component/Admin/Common/EndpointInput.tsx @@ -0,0 +1,44 @@ +import { TextFieldProps } from "@mui/material"; +import { useCallback, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { DenseFilledTextField } from "../../Common/StyledComponents"; + +export interface EndpointInputProps extends TextFieldProps<"outlined"> { + enforceProtocol?: boolean; + enforcePrefix?: boolean; +} + +export const EndpointInput = (props: EndpointInputProps) => { + const { t } = useTranslation("dashboard"); + const { enforceProtocol, enforcePrefix = true, onChange, ...rest } = props; + const [value, setValue] = useState((props.value as string) ?? ""); + const handleChange = useCallback( + (e: React.ChangeEvent) => { + setValue(e.target.value); + onChange?.(e); + }, + [onChange], + ); + + const showError = useMemo(() => { + if (!enforceProtocol) return false; + return value.startsWith("http://") && window.location.protocol == "https:"; + }, [enforceProtocol, value]); + + return ( + + ); +}; diff --git a/src/component/Admin/Common/GroupSelectionInput.tsx b/src/component/Admin/Common/GroupSelectionInput.tsx new file mode 100755 index 0000000..08ee1de --- /dev/null +++ b/src/component/Admin/Common/GroupSelectionInput.tsx @@ -0,0 +1,94 @@ +import { ListItemText } from "@mui/material"; +import FormControl from "@mui/material/FormControl"; +import { useEffect, useState } from "react"; +import { getGroupList } from "../../../api/api"; +import { GroupEnt } from "../../../api/dashboard"; +import { useAppDispatch } from "../../../redux/hooks"; +import { DenseSelect } from "../../Common/StyledComponents"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu"; + +export interface GroupSelectionInputProps { + value: string; + onChange: (value: string) => void; + onChangeGroup?: (group?: GroupEnt) => void; + emptyValue?: string; + emptyText?: string; + fullWidth?: boolean; + required?: boolean; +} + +const AnonymousGroupId = 3; + +const GroupSelectionInput = ({ + value, + onChange, + onChangeGroup, + emptyValue, + emptyText, + fullWidth, + required, +}: GroupSelectionInputProps) => { + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(true); + const [groups, setGroups] = useState([]); + + useEffect(() => { + setLoading(true); + dispatch( + getGroupList({ + page_size: 1000, + page: 1, + order_by: "id", + order_direction: "asc", + }), + ) + .then((res) => { + setGroups(res.groups); + }) + .finally(() => { + setLoading(false); + }); + }, []); + + const handleChange = (value: string) => { + onChange(value); + onChangeGroup?.(groups.find((g) => g.id === parseInt(value))); + }; + + return ( + + handleChange(e.target.value as string)} + required={required} + > + {groups + .filter((g) => g.id != AnonymousGroupId) + .map((g) => ( + + + {g.name} + + + ))} + {emptyValue !== undefined && emptyText && ( + + {emptyText}} + slotProps={{ + primary: { variant: "body2" }, + }} + /> + + )} + + + ); +}; + +export default GroupSelectionInput; diff --git a/src/component/Admin/Common/MagicVarDialog.tsx b/src/component/Admin/Common/MagicVarDialog.tsx new file mode 100755 index 0000000..2bc2c2d --- /dev/null +++ b/src/component/Admin/Common/MagicVarDialog.tsx @@ -0,0 +1,60 @@ +import { DialogContent, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { NoWrapTableCell } from "../../Common/StyledComponents.tsx"; +import DraggableDialog from "../../Dialogs/DraggableDialog"; + +export interface MagicVar { + name: string; + value: string; + example?: string; +} + +export interface MagicVarDialogProps { + open: boolean; + onClose: () => void; + vars: MagicVar[]; +} + +const MagicVarDialog = ({ open, onClose, vars }: MagicVarDialogProps) => { + const { t } = useTranslation("dashboard"); + + return ( + + + + + + + {t("policy.magicVar.variable")} + {t("policy.magicVar.description")} + {t("policy.magicVar.example")} + + + + {vars.map((v, i) => ( + + + {v.name} + + {t(v.value)} + {v.example} + + ))} + +
+
+
+
+ ); +}; + +export default MagicVarDialog; diff --git a/src/component/Admin/Common/NodeSelectionInput.tsx b/src/component/Admin/Common/NodeSelectionInput.tsx new file mode 100755 index 0000000..613a386 --- /dev/null +++ b/src/component/Admin/Common/NodeSelectionInput.tsx @@ -0,0 +1,89 @@ +import { Alert, Box, OutlinedSelectProps, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getNodeList } from "../../../api/api"; +import { Node, NodeStatus, NodeType } from "../../../api/dashboard"; +import { useAppDispatch } from "../../../redux/hooks"; +import { DenseSelect } from "../../Common/StyledComponents"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu"; + +export interface NodeSelectionInputProps extends OutlinedSelectProps { + value: number; + onChange: (value: number) => void; +} + +export const NodeStatusCondition = "node_status"; + +const NodeSelectionInput = ({ value, onChange, ...rest }: NodeSelectionInputProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(true); + const [nodes, setNodes] = useState([]); + + useEffect(() => { + setLoading(true); + dispatch( + getNodeList({ + page_size: 1000, + page: 1, + order_by: "id", + order_direction: "desc", + conditions: { + [NodeStatusCondition]: NodeStatus.active, + }, + }), + ) + .then((res) => { + const filteredNodes = res.nodes.filter((n) => n.type != NodeType.master); + setNodes(filteredNodes); + if (!value && filteredNodes.length > 0) { + onChange(filteredNodes[0].id); + } + }) + .finally(() => { + setLoading(false); + }); + }, []); + + if (!loading && nodes.length == 0) { + return {t("settings.noNodes")}; + } + return ( + onChange(e.target.value as number)} + MenuProps={{ + PaperProps: { sx: { maxWidth: 230 } }, + MenuListProps: { + sx: { + "& .MuiMenuItem-root": { + whiteSpace: "normal", + }, + }, + }, + }} + {...rest} + > + {nodes.map((g) => ( + + + + {g.name} + + + {g.server} + + + + ))} + + ); +}; + +export default NodeSelectionInput; diff --git a/src/component/Admin/Common/ProDialog.tsx b/src/component/Admin/Common/ProDialog.tsx new file mode 100755 index 0000000..373e842 --- /dev/null +++ b/src/component/Admin/Common/ProDialog.tsx @@ -0,0 +1,141 @@ +import { + Alert, + AlertTitle, + Button, + DialogContent, + List, + ListItem, + ListItemIcon, + ListItemText, + Typography, + styled, +} from "@mui/material"; +import dayjs from "dayjs"; +import { useCallback, useMemo } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import DraggableDialog, { StyledDialogActions } from "../../Dialogs/DraggableDialog"; +import CheckmarkCircleFilled from "../../Icons/CheckmarkCircleFilled"; +import Gift from "../../Icons/Gift"; +import { Code } from "../../Common/Code.tsx"; + +export interface ProDialogProps { + open: boolean; + onClose: () => void; +} + +const features = [ + "shareLinkCollabration", + "filePermission", + "multipleStoragePolicy", + "auditAndActivity", + "vasService", + "sso", + "more", +]; + +const StyledButton = styled(Button)(({ theme }) => ({ + background: `linear-gradient(45deg, ${theme.palette.primary.main} 30%, ${theme.palette.primary.light} 90%)`, + color: theme.palette.primary.contrastText, + "&:hover": { + background: `linear-gradient(45deg, ${theme.palette.primary.dark} 30%, ${theme.palette.primary.main} 90%)`, + }, + transition: "all 300ms cubic-bezier(0.4, 0, 0.2, 1) !important", +})); + +// TODO: fetch from cloudreve.org +const currentPromotion = { + code: "FI5Q9668YV", + discount: 15, + start: "2025-08-12T00:00:00Z", + end: "2025-10-12T23:59:59Z", +}; + +const ProDialog = ({ open, onClose }: ProDialogProps) => { + const { t } = useTranslation("dashboard"); + const openMore = useCallback((e: React.MouseEvent) => { + e.stopPropagation(); + window.open("https://cloudreve.org/pro", "_blank"); + }, []); + const showPromotion = useMemo(() => { + const now = dayjs(); + return now >= dayjs(currentPromotion.start) && now <= dayjs(currentPromotion.end); + }, []); + return ( + + + + {t("pro.description")} + + + + {t("pro.proInclude")} + + + + {features.map((feature) => ( + + + + + + {t(`pro.${feature}`)} + + + ))} + + {showPromotion && ( + , + }} + severity="info" + sx={{ mt: 2 }} + > + {t("pro.promotionTitle")} + , ]} + /> + + )} + + + + + {t("pro.learnMore")} + + + + ); +}; + +export default ProDialog; diff --git a/src/component/Admin/Common/SharesInput.tsx b/src/component/Admin/Common/SharesInput.tsx new file mode 100755 index 0000000..b0b7f5e --- /dev/null +++ b/src/component/Admin/Common/SharesInput.tsx @@ -0,0 +1,45 @@ +import { Box, debounce, useTheme } from "@mui/material"; +import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getShareList } from "../../../api/api.ts"; +import { Share } from "../../../api/dashboard.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { DenseAutocomplete, DenseFilledTextField, NoWrapBox, SquareChip } from "../../Common/StyledComponents.tsx"; +import FileTypeIcon from "../../FileManager/Explorer/FileTypeIcon.tsx"; +import LinkDismiss from "../../Icons/LinkDismiss.tsx"; + +export interface SharesInputProps {} + +const SharesInput = (props: SharesInputProps) => { + const theme = useTheme(); + const { t } = useTranslation(); + const [options, setOptions] = useState([]); + + return ( + ( + + )} + /> + ); +}; + +export default SharesInput; diff --git a/src/component/Admin/Common/SinglePolicySelectionInput.tsx b/src/component/Admin/Common/SinglePolicySelectionInput.tsx new file mode 100755 index 0000000..cf7a6f3 --- /dev/null +++ b/src/component/Admin/Common/SinglePolicySelectionInput.tsx @@ -0,0 +1,122 @@ +import { Box, FormControl, ListItemText, SelectChangeEvent, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getStoragePolicyList } from "../../../api/api"; +import { StoragePolicy } from "../../../api/dashboard"; +import { useAppDispatch } from "../../../redux/hooks"; +import { DenseSelect } from "../../Common/StyledComponents"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu"; + +export interface SinglePolicySelectionInputProps { + value: number | undefined; + onChange: (value: number) => void; + emptyValue?: number; + emptyText?: string; + simplified?: boolean; +} + +const SinglePolicySelectionInput = ({ + value, + onChange, + emptyValue, + emptyText, + simplified, +}: SinglePolicySelectionInputProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [policies, setPolicies] = useState([]); + const [loading, setLoading] = useState(false); + const [policyMap, setPolicyMap] = useState>({}); + + const handleChange = (event: SelectChangeEvent) => { + const { + target: { value }, + } = event; + onChange(value as number); + }; + + useEffect(() => { + setLoading(true); + dispatch(getStoragePolicyList({ page: 1, page_size: 1000, order_by: "id", order_direction: "asc" })) + .then((res) => { + setPolicies(res.policies); + setPolicyMap( + res.policies.reduce( + (acc, policy) => { + acc[policy.id] = policy; + return acc; + }, + {} as Record, + ), + ); + }) + .finally(() => { + setLoading(false); + }); + }, []); + + return ( + + ( + + ) + : undefined + } + disabled={loading} + MenuProps={{ + PaperProps: { sx: { maxWidth: 230 } }, + MenuListProps: { + sx: { + "& .MuiMenuItem-root": { + whiteSpace: "normal", + }, + }, + }, + }} + > + {policies.length > 0 && + policies.map((policy) => ( + + + + {policy.name} + + + {t(`policy.${policy.type}`)} + + + + ))} + {emptyValue !== undefined && emptyText && ( + + {t(emptyText)}} + slotProps={{ + primary: { variant: "body2" }, + }} + /> + + )} + + + ); +}; + +export default SinglePolicySelectionInput; diff --git a/src/component/Admin/Common/TablePagination.tsx b/src/component/Admin/Common/TablePagination.tsx new file mode 100755 index 0000000..28096ff --- /dev/null +++ b/src/component/Admin/Common/TablePagination.tsx @@ -0,0 +1,86 @@ +import { + Box, + ListItemText, + Pagination, + PaginationProps, + SelectChangeEvent, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { DenseSelect } from "../../Common/StyledComponents"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu"; + +export interface TablePaginationProps extends PaginationProps { + rowsPerPageOptions?: number[]; + rowsPerPage?: number; + onRowsPerPageChange?: (pageSize: number) => void; + page: number; + totalItems: number; + onChange: (event: React.ChangeEvent, value: number) => void; +} + +export const TablePagination = ({ + rowsPerPageOptions = [5, 10, 25, 50], + rowsPerPage = 5, + onRowsPerPageChange, + page, + onChange, + totalItems, + ...props +}: TablePaginationProps) => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + + const onDenseSelectChange = (e: SelectChangeEvent) => { + onRowsPerPageChange?.(e.target.value as number); + }; + + useEffect(() => { + if ((page - 1) * rowsPerPage >= totalItems) { + onChange({} as React.ChangeEvent, Math.ceil(totalItems / rowsPerPage)); + } + }, [rowsPerPage, totalItems]); + + return ( + + + ( + + )} + > + {rowsPerPageOptions.map((option) => ( + + + + ))} + + + ); +}; + +export default TablePagination; diff --git a/src/component/Admin/Entity/EntityDeleteDialog.tsx b/src/component/Admin/Entity/EntityDeleteDialog.tsx new file mode 100755 index 0000000..2e2d56f --- /dev/null +++ b/src/component/Admin/Entity/EntityDeleteDialog.tsx @@ -0,0 +1,70 @@ +import { Checkbox, DialogContent, FormGroup, Stack, Tooltip } from "@mui/material"; +import { useCallback, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { batchDeleteEntities } from "../../../api/api.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { SmallFormControlLabel } from "../../Common/StyledComponents.tsx"; +import DialogAccordion from "../../Dialogs/DialogAccordion.tsx"; +import DraggableDialog, { StyledDialogContentText } from "../../Dialogs/DraggableDialog.tsx"; + +export interface EntityDeleteDialogProps { + entityID?: number[]; + open: boolean; + onClose?: () => void; + onDelete?: () => void; +} + +const EntityDeleteDialog = ({ entityID, open, onDelete, onClose }: EntityDeleteDialogProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + + const [force, setForce] = useState(false); + const [deleting, setDeleting] = useState(false); + + const onAccept = useCallback(() => { + if (entityID) { + setDeleting?.(true); + dispatch(batchDeleteEntities({ ids: entityID, force })) + .then(() => { + onDelete?.(); + onClose?.(); + }) + .finally(() => { + setDeleting?.(false); + }); + } + }, [entityID, force, setDeleting]); + + return ( + + + + {t("entity.confirmBatchDelete", { num: entityID?.length })} + + + + setForce(e.target.checked)} checked={force} />} + label={t("entity.forceDelete")} + /> + + + + + + + ); +}; +export default EntityDeleteDialog; diff --git a/src/component/Admin/Entity/EntityDialog/EntityDialog.tsx b/src/component/Admin/Entity/EntityDialog/EntityDialog.tsx new file mode 100755 index 0000000..0ce2457 --- /dev/null +++ b/src/component/Admin/Entity/EntityDialog/EntityDialog.tsx @@ -0,0 +1,84 @@ +import { Box, DialogContent } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { getEntityDetail } from "../../../../api/api.ts"; +import { Entity } from "../../../../api/dashboard.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import AutoHeight from "../../../Common/AutoHeight.tsx"; +import FacebookCircularProgress from "../../../Common/CircularProgress.tsx"; +import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import EntityForm from "./EntityForm.tsx"; + +export interface EntityDialogProps { + open: boolean; + onClose: () => void; + entityID?: number; +} + +const EntityDialog = ({ open, onClose, entityID }: EntityDialogProps) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation("dashboard"); + const [values, setValues] = useState({ edges: {}, id: 0 }); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (!entityID || !open) { + return; + } + setLoading(true); + dispatch(getEntityDetail(entityID)) + .then((res) => { + setValues(res); + }) + .catch(() => { + onClose(); + }) + .finally(() => { + setLoading(false); + }); + }, [open]); + + return ( + + + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${loading}`} + > + + {loading && ( + + + + )} + {!loading && } + + + + + + + ); +}; + +export default EntityDialog; diff --git a/src/component/Admin/Entity/EntityDialog/EntityFileList.tsx b/src/component/Admin/Entity/EntityDialog/EntityFileList.tsx new file mode 100755 index 0000000..6d1f7eb --- /dev/null +++ b/src/component/Admin/Entity/EntityDialog/EntityFileList.tsx @@ -0,0 +1,119 @@ +import { Box, Link, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { File } from "../../../../api/dashboard"; +import { FileType } from "../../../../api/explorer"; +import { sizeToString } from "../../../../util"; +import { + NoWrapCell, + NoWrapTableCell, + NoWrapTypography, + StyledTableContainerPaper, +} from "../../../Common/StyledComponents"; +import TimeBadge from "../../../Common/TimeBadge"; +import UserAvatar from "../../../Common/User/UserAvatar"; +import FileTypeIcon from "../../../FileManager/Explorer/FileTypeIcon"; +import FileDialog from "../../File/FileDialog/FileDialog"; +import UserDialog from "../../User/UserDialog/UserDialog"; + +const EntityFileList = ({ files, userHashIDMap }: { files: File[]; userHashIDMap: Record }) => { + const { t } = useTranslation("dashboard"); + const [userDialogOpen, setUserDialogOpen] = useState(false); + const [userDialogID, setUserDialogID] = useState(0); + const [fileDialogOpen, setFileDialogOpen] = useState(false); + const [fileDialogID, setFileDialogID] = useState(0); + + const userClicked = (uid: number) => (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + setUserDialogOpen(true); + setUserDialogID(uid); + }; + + const fileClicked = (fid: number) => (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + setFileDialogOpen(true); + setFileDialogID(fid); + }; + return ( + <> + setUserDialogOpen(false)} userID={userDialogID} /> + setFileDialogOpen(false)} fileID={fileDialogID} /> + + + + + {t("group.#")} + {t("file.name")} + {t("file.size")} + {t("file.uploader")} + {t("file.createdAt")} + + + + {files?.map((option, index) => { + return ( + + + {option.id} + + + + + {option.name} + + + + {sizeToString(option.size ?? 0)} + + + + + + + {option.edges?.owner?.nick} + + + + + + + + + + + ); + })} + {!files?.length && ( + + + {t("file.noRecords")} + + + )} + +
+
+ + ); +}; + +export default EntityFileList; diff --git a/src/component/Admin/Entity/EntityDialog/EntityForm.tsx b/src/component/Admin/Entity/EntityDialog/EntityForm.tsx new file mode 100755 index 0000000..39b2202 --- /dev/null +++ b/src/component/Admin/Entity/EntityDialog/EntityForm.tsx @@ -0,0 +1,114 @@ +import { Box, Grid2 as Grid, Link, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link as RouterLink } from "react-router-dom"; +import { Entity } from "../../../../api/dashboard"; +import { EntityType } from "../../../../api/explorer"; +import { useAppDispatch } from "../../../../redux/hooks"; +import { sizeToString } from "../../../../util"; +import { NoWrapTypography } from "../../../Common/StyledComponents"; +import UserAvatar from "../../../Common/User/UserAvatar"; +import { EntityTypeText } from "../../../FileManager/Sidebar/Data"; +import SettingForm from "../../../Pages/Setting/SettingForm"; +import UserDialog from "../../User/UserDialog/UserDialog"; +import EntityFileList from "./EntityFileList"; +const EntityForm = ({ values }: { values: Entity }) => { + const dispatch = useAppDispatch(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + const { t } = useTranslation("dashboard"); + const [userDialogOpen, setUserDialogOpen] = useState(false); + const [userDialogID, setUserDialogID] = useState(0); + + const userClicked = (e: React.MouseEvent) => { + e.preventDefault(); + setUserDialogOpen(true); + setUserDialogID(values?.edges?.user?.id ?? 0); + }; + + return ( + <> + setUserDialogOpen(false)} userID={userDialogID} /> + + + + + {values.id} + + + + + {sizeToString(values.size ?? 0)} + + + + + {t(EntityTypeText[values.type ?? EntityType.version])} + + + + + {values.reference_count ?? 0} + + + + + + + + + {values?.edges?.user?.nick} + + + + + + + + {values.upload_session_id ?? "-"} + + + + + {values.source ?? "-"} + + + + + + {values.edges?.storage_policy?.name} + + + + + + + + + + ); +}; + +export default EntityForm; diff --git a/src/component/Admin/Entity/EntityFilterPopover.tsx b/src/component/Admin/Entity/EntityFilterPopover.tsx new file mode 100755 index 0000000..cfadd43 --- /dev/null +++ b/src/component/Admin/Entity/EntityFilterPopover.tsx @@ -0,0 +1,153 @@ +import { Box, Button, ListItemText, Popover, PopoverProps, Stack } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { EntityType } from "../../../api/explorer"; +import { DenseFilledTextField, DenseSelect } from "../../Common/StyledComponents"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu"; +import { EntityTypeText } from "../../FileManager/Sidebar/Data"; +import SettingForm from "../../Pages/Setting/SettingForm"; +import SinglePolicySelectionInput from "../Common/SinglePolicySelectionInput"; +export interface EntityFilterPopoverProps extends PopoverProps { + storagePolicy: string; + setStoragePolicy: (storagePolicy: string) => void; + owner: string; + setOwner: (owner: string) => void; + type?: EntityType; + setType: (type?: EntityType) => void; + clearFilters: () => void; +} + +const EntityFilterPopover = ({ + storagePolicy, + setStoragePolicy, + owner, + setOwner, + type, + setType, + clearFilters, + onClose, + open, + ...rest +}: EntityFilterPopoverProps) => { + const { t } = useTranslation("dashboard"); + + // Create local state to track changes before applying + const [localStoragePolicy, setLocalStoragePolicy] = useState(storagePolicy); + const [localOwner, setLocalOwner] = useState(owner); + const [localType, setLocalType] = useState(type); + + // Initialize local state when popup opens + useEffect(() => { + if (open) { + setLocalStoragePolicy(storagePolicy); + setLocalOwner(owner); + setLocalType(type); + } + }, [open]); + + // Apply filters and close popover + const handleApplyFilters = () => { + setStoragePolicy(localStoragePolicy); + setOwner(localOwner); + setType(localType); + onClose?.({}, "backdropClick"); + }; + + // Reset filters and close popover + const handleResetFilters = () => { + setLocalStoragePolicy(""); + setLocalOwner(""); + setLocalType(undefined); + clearFilters(); + onClose?.({}, "backdropClick"); + }; + + return ( + + + + setLocalOwner(e.target.value)} + placeholder={t("user.emptyNoFilter")} + size="small" + /> + + + + setLocalType(e.target.value === -1 ? undefined : (e.target.value as EntityType))} + > + {[EntityType.version, EntityType.thumbnail, EntityType.live_photo].map((type) => ( + + + + ))} + + {t("user.all")}} + slotProps={{ + primary: { + variant: "body2", + }, + }} + /> + + + + + + setLocalStoragePolicy(value.toString())} + emptyValue={-1} + emptyText={t("user.all")} + /> + + + + + + + + + ); +}; + +export default EntityFilterPopover; diff --git a/src/component/Admin/Entity/EntityRow.tsx b/src/component/Admin/Entity/EntityRow.tsx new file mode 100755 index 0000000..2e84079 --- /dev/null +++ b/src/component/Admin/Entity/EntityRow.tsx @@ -0,0 +1,218 @@ +import { Box, Checkbox, IconButton, Link, Skeleton, TableCell, TableRow, Tooltip } from "@mui/material"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link as RouterLink } from "react-router-dom"; +import { getEntityUrl } from "../../../api/api"; +import { Entity } from "../../../api/dashboard"; +import { EntityType } from "../../../api/explorer"; +import { useAppDispatch } from "../../../redux/hooks"; +import { sizeToString } from "../../../util"; +import { NoWrapTableCell, NoWrapTypography, SquareChip } from "../../Common/StyledComponents"; +import TimeBadge from "../../Common/TimeBadge"; +import UserAvatar from "../../Common/User/UserAvatar"; +import { EntityTypeText } from "../../FileManager/Sidebar/Data"; +import Delete from "../../Icons/Delete"; +import Download from "../../Icons/Download"; + +export interface EntityRowProps { + entity?: Entity; + loading?: boolean; + selected?: boolean; + onDelete?: (id: number) => void; + onSelect?: (id: number) => void; + openEntityDialog?: (id: number) => void; + openUserDialog?: (id: number) => void; +} + +const EntityRow = ({ + entity, + loading, + selected, + onDelete, + onSelect, + openUserDialog, + openEntityDialog, +}: EntityRowProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [deleteLoading, setDeleteLoading] = useState(false); + const [openLoading, setOpenLoading] = useState(false); + + const onSelectClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + onSelect?.(entity?.id ?? 0); + }; + + const onOpenClick = (e: React.MouseEvent) => { + e.stopPropagation(); + setOpenLoading(true); + + dispatch(getEntityUrl(entity?.id ?? 0)) + .then((url) => { + // 直接下载文件:使用a标签的download属性强制下载 + const link = document.createElement('a'); + link.href = url; + link.download = `entity-${entity?.id}`; + link.style.display = 'none'; + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }) + .finally(() => { + setOpenLoading(false); + }) + .catch((error) => { + console.error('Failed to get entity URL:', error); + }); + }; + + const userClicked = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + openUserDialog?.(entity?.edges?.user?.id ?? 0); + }; + + const onDeleteClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + onDelete?.(entity?.id ?? 0); + }; + + if (loading) { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } + + return ( + openEntityDialog?.(entity?.id ?? 0)} + selected={selected} + > + + + + + {entity?.id} + + + {t(EntityTypeText[entity?.type ?? EntityType.version])} + + + + + {entity?.source || "-"} + + + {!entity?.reference_count && } + + + + + {sizeToString(entity?.size ?? 0)} + + + + + {entity?.edges?.storage_policy?.name || "-"} + + + + + {entity?.reference_count ?? 0} + + + + + + + + + + + + {entity?.edges?.user?.nick || "-"} + + + + + + + + + + + + + + + + ); +}; + +export default EntityRow; diff --git a/src/component/Admin/Entity/EntitySetting.tsx b/src/component/Admin/Entity/EntitySetting.tsx new file mode 100755 index 0000000..9a28ea9 --- /dev/null +++ b/src/component/Admin/Entity/EntitySetting.tsx @@ -0,0 +1,328 @@ +import { Delete } from "@mui/icons-material"; +import { + Badge, + Box, + Button, + Checkbox, + Container, + Divider, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TableSortLabel, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { bindPopover, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import { useQueryState } from "nuqs"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getEntityList } from "../../../api/api"; +import { AdminListService, Entity } from "../../../api/dashboard"; +import { useAppDispatch } from "../../../redux/hooks"; +import { NoWrapTableCell, SecondaryButton, StyledTableContainerPaper } from "../../Common/StyledComponents"; +import ArrowSync from "../../Icons/ArrowSync"; +import Filter from "../../Icons/Filter"; +import PageContainer from "../../Pages/PageContainer"; +import PageHeader from "../../Pages/PageHeader"; +import TablePagination from "../Common/TablePagination"; +import { OrderByQuery, OrderDirectionQuery, PageQuery, PageSizeQuery } from "../StoragePolicy/StoragePolicySetting"; +import UserDialog from "../User/UserDialog/UserDialog"; +import EntityDeleteDialog from "./EntityDeleteDialog"; +import EntityDialog from "./EntityDialog/EntityDialog"; +import EntityFilterPopover from "./EntityFilterPopover"; +import EntityRow from "./EntityRow"; +export const StoragePolicyQuery = "storage_policy"; +export const UserQuery = "user"; +export const TypeQuery = "type"; + +const EntitySetting = () => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(true); + const [entities, setEntities] = useState([]); + const [page, setPage] = useQueryState(PageQuery, { defaultValue: "1" }); + const [pageSize, setPageSize] = useQueryState(PageSizeQuery, { + defaultValue: "10", + }); + const [orderBy, setOrderBy] = useQueryState(OrderByQuery, { + defaultValue: "", + }); + const [orderDirection, setOrderDirection] = useQueryState(OrderDirectionQuery, { defaultValue: "desc" }); + const [storagePolicy, setStoragePolicy] = useQueryState(StoragePolicyQuery, { defaultValue: "" }); + const [user, setUser] = useQueryState(UserQuery, { defaultValue: "" }); + const [type, setType] = useQueryState(TypeQuery, { defaultValue: "" }); + const [count, setCount] = useState(0); + const [selected, setSelected] = useState([]); + const filterPopupState = usePopupState({ + variant: "popover", + popupId: "entityFilterPopover", + }); + + const [userDialogOpen, setUserDialogOpen] = useState(false); + const [userDialogID, setUserDialogID] = useState(undefined); + const [entityDialogOpen, setEntityDialogOpen] = useState(false); + const [entityDialogID, setEntityDialogID] = useState(undefined); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [deleteDialogID, setDeleteDialogID] = useState(undefined); + + const pageInt = parseInt(page) ?? 1; + const pageSizeInt = parseInt(pageSize) ?? 10; + + const clearFilters = useCallback(() => { + setStoragePolicy(""); + setUser(""); + setType(""); + }, [setStoragePolicy, setUser, setType]); + + useEffect(() => { + fetchEntities(); + }, [page, pageSize, orderBy, orderDirection, storagePolicy, user, type]); + + const fetchEntities = () => { + setLoading(true); + setSelected([]); + + const params: AdminListService = { + page: pageInt, + page_size: pageSizeInt, + order_by: orderBy ?? "", + order_direction: orderDirection ?? "desc", + conditions: {}, + }; + + if (storagePolicy) { + params.conditions!.entity_policy = storagePolicy; + } + + if (user) { + params.conditions!.entity_user = user; + } + + if (type) { + params.conditions!.entity_type = type; + } + + dispatch(getEntityList(params)) + .then((res) => { + setEntities(res.entities); + setPageSize(res.pagination.page_size.toString()); + setCount(res.pagination.total_items ?? 0); + setLoading(false); + }) + .finally(() => { + setLoading(false); + }); + }; + + const handleDelete = () => { + setDeleteDialogOpen(true); + setDeleteDialogID(Array.from(selected)); + }; + + const handleSelectAllClick = (event: React.ChangeEvent) => { + if (event.target.checked) { + const newSelected = entities.map((n) => n.id); + setSelected(newSelected); + return; + } + setSelected([]); + }; + + const handleSelect = useCallback( + (id: number) => { + const selectedIndex = selected.indexOf(id); + let newSelected: readonly number[] = []; + + if (selectedIndex === -1) { + newSelected = newSelected.concat(selected, id); + } else if (selectedIndex === 0) { + newSelected = newSelected.concat(selected.slice(1)); + } else if (selectedIndex === selected.length - 1) { + newSelected = newSelected.concat(selected.slice(0, -1)); + } else if (selectedIndex > 0) { + newSelected = newSelected.concat(selected.slice(0, selectedIndex), selected.slice(selectedIndex + 1)); + } + setSelected(newSelected); + }, + [selected], + ); + + const orderById = orderBy === "id" || orderBy === ""; + const direction = orderDirection as "asc" | "desc"; + const onSortClick = (field: string) => () => { + const alreadySorted = orderBy === field || (field === "id" && orderById); + setOrderBy(field); + setOrderDirection(alreadySorted ? (direction === "asc" ? "desc" : "asc") : "asc"); + }; + + const hasActiveFilters = useMemo(() => { + return !!(storagePolicy || user || type); + }, [storagePolicy, user, type]); + + const handleUserDialogOpen = (id: number) => { + setUserDialogID(id); + setUserDialogOpen(true); + }; + + const handleEntityDialogOpen = (id: number) => { + setEntityDialogID(id); + setEntityDialogOpen(true); + }; + + const handleSingleDelete = (id: number) => { + setDeleteDialogOpen(true); + setDeleteDialogID([id]); + }; + + return ( + + setEntityDialogOpen(false)} entityID={entityDialogID} /> + setUserDialogOpen(false)} userID={userDialogID} /> + setDeleteDialogOpen(false)} + entityID={deleteDialogID} + onDelete={fetchEntities} + /> + + + + setType(type !== undefined ? type.toString() : "")} + clearFilters={clearFilters} + /> + + }> + {t("node.refresh")} + + + + } variant="contained" {...bindTrigger(filterPopupState)}> + {t("user.filter")} + + + + {selected.length > 0 && !isMobile && ( + <> + + + + )} + + {isMobile && selected.length > 0 && ( + + + + )} + + + + + + 0 && selected.length < entities.length} + checked={entities.length > 0 && selected.length === entities.length} + onChange={handleSelectAllClick} + /> + + + + {t("group.#")} + + + {t("file.blobType")} + {t("file.source")} + + + {t("file.size")} + + + {t("file.storagePolicy")} + + + {t("entity.refenenceCount")} + + + + + {t("file.createdAt")} + + + {t("file.creator")} + + + + + {!loading && + entities.map((entity) => ( + + ))} + {loading && + entities.length > 0 && + entities.slice(0, 10).map((entity) => )} + {loading && + entities.length === 0 && + Array.from(Array(10)).map((_, index) => )} + +
+
+ {count > 0 && ( + + setPageSize(value.toString())} + onChange={(_, value) => setPage(value.toString())} + /> + + )} +
+
+ ); +}; + +export default EntitySetting; diff --git a/src/component/Admin/File/FileDialog/FileDialog.tsx b/src/component/Admin/File/FileDialog/FileDialog.tsx new file mode 100755 index 0000000..6931ed4 --- /dev/null +++ b/src/component/Admin/File/FileDialog/FileDialog.tsx @@ -0,0 +1,162 @@ +import { Box, Button, Collapse, DialogActions, DialogContent } from "@mui/material"; +import * as React from "react"; +import { createContext, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { getFileDetail, upsertFile } from "../../../../api/api.ts"; +import { File, UpsertFileService } from "../../../../api/dashboard.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import AutoHeight from "../../../Common/AutoHeight.tsx"; +import FacebookCircularProgress from "../../../Common/CircularProgress.tsx"; +import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import FileForm from "./FileForm.tsx"; + +export interface FileDialogProps { + open: boolean; + onClose: () => void; + fileID?: number; + onUpdated?: (file: File) => void; +} + +export interface FileDialogContextProps { + values: File; + setFile: (f: (p: File) => File) => void; + formRef?: React.RefObject; +} + +const defaultFile: File = { + id: 0, + name: "", + size: 0, + edges: {}, +}; + +export const FileDialogContext = createContext({ + values: { ...defaultFile }, + setFile: () => {}, +}); + +const FileDialog = ({ open, onClose, fileID, onUpdated }: FileDialogProps) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation("dashboard"); + const [values, setValues] = useState({ + ...defaultFile, + }); + const [modifiedValues, setModifiedValues] = useState({ + ...defaultFile, + }); + const [loading, setLoading] = useState(true); + const [submitting, setSubmitting] = useState(false); + const formRef = useRef(null); + + const showSaveButton = useMemo(() => { + return JSON.stringify(modifiedValues) !== JSON.stringify(values); + }, [modifiedValues, values]); + + useEffect(() => { + if (!fileID || !open) { + return; + } + setLoading(true); + dispatch(getFileDetail(fileID)) + .then((res) => { + setValues(res); + setModifiedValues(res); + }) + .catch(() => { + onClose(); + }) + .finally(() => { + setLoading(false); + }); + }, [open]); + + const revert = () => { + setModifiedValues(values); + }; + + const submit = () => { + if (formRef.current) { + if (!formRef.current.checkValidity()) { + formRef.current.reportValidity(); + return; + } + } + + const args: UpsertFileService = { + file: { ...modifiedValues }, + }; + + setSubmitting(true); + dispatch(upsertFile(args)) + .then((res) => { + setValues(res); + setModifiedValues(res); + onUpdated?.(res); + }) + .finally(() => { + setSubmitting(false); + }); + }; + + return ( + + + + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${loading}`} + > + + {loading && ( + + + + )} + {!loading && } + + + + + + + + + + + + + + ); +}; + +export default FileDialog; diff --git a/src/component/Admin/File/FileDialog/FileDirectLinks.tsx b/src/component/Admin/File/FileDialog/FileDirectLinks.tsx new file mode 100755 index 0000000..e68eb3a --- /dev/null +++ b/src/component/Admin/File/FileDialog/FileDirectLinks.tsx @@ -0,0 +1,109 @@ +import { IconButton, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip } from "@mui/material"; +import { useCallback, useContext } from "react"; +import { useTranslation } from "react-i18next"; +import { sizeToString } from "../../../../util"; +import { + NoWrapCell, + NoWrapTableCell, + NoWrapTypography, + StyledTableContainerPaper, +} from "../../../Common/StyledComponents"; +import TimeBadge from "../../../Common/TimeBadge"; +import Delete from "../../../Icons/Delete"; +import Open from "../../../Icons/Open"; +import { FileDialogContext } from "./FileDialog"; + +const FileDirectLinks = () => { + const { t } = useTranslation("dashboard"); + const { setFile, values } = useContext(FileDialogContext); + + const handleDelete = (id: number) => { + setFile((prev) => ({ + ...prev, + edges: { + ...prev.edges, + direct_links: prev.edges?.direct_links?.filter((link) => link.id !== id), + }, + })); + }; + + const handleOpen = (id: number) => { + window.open(values?.direct_link_map?.[id] ?? "", "_blank"); + }; + + const linkId = useCallback( + (id: number) => { + const url = new URL(values?.direct_link_map?.[id] ?? ""); + return url.pathname; + }, + [values?.direct_link_map], + ); + + return ( + + + + + {t("group.#")} + {t("file.name")} + {t("file.downloads")} + {t("file.speed")} + {t("file.directLinkId")} + {t("file.createdAt")} + + + + + {values?.edges?.direct_links?.map((option, index) => { + const lid = linkId(option.id); + return ( + + + {option.id} + + + {option.name ?? ""} + + + {option.downloads ?? 0} + + + + {option.speed ? `${sizeToString(option.speed)}/s` : "-"} + + + + + {lid} + + + + + + + + + handleOpen(option.id)}> + + + handleDelete(option.id)}> + + + + + ); + })} + {!values?.edges?.direct_links?.length && ( + + + {t("file.noRecords")} + + + )} + +
+
+ ); +}; + +export default FileDirectLinks; diff --git a/src/component/Admin/File/FileDialog/FileEntity.tsx b/src/component/Admin/File/FileDialog/FileEntity.tsx new file mode 100755 index 0000000..677ac40 --- /dev/null +++ b/src/component/Admin/File/FileDialog/FileEntity.tsx @@ -0,0 +1,130 @@ +import { Link, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip } from "@mui/material"; +import { useContext, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link as RouterLink } from "react-router-dom"; +import { EntityType } from "../../../../api/explorer"; +import { sizeToString } from "../../../../util"; +import { + NoWrapCell, + NoWrapTableCell, + NoWrapTypography, + StyledTableContainerPaper, +} from "../../../Common/StyledComponents"; +import TimeBadge from "../../../Common/TimeBadge"; +import { EntityTypeText } from "../../../FileManager/Sidebar/Data"; +import EntityDialog from "../../Entity/EntityDialog/EntityDialog"; +import UserDialog from "../../User/UserDialog/UserDialog"; +import { FileDialogContext } from "./FileDialog"; + +const FileEntity = () => { + const { t } = useTranslation("dashboard"); + const { setFile, values } = useContext(FileDialogContext); + const [userDialogOpen, setUserDialogOpen] = useState(false); + const [userDialogId, setUserDialogId] = useState(null); + const [entityDialogOpen, setEntityDialogOpen] = useState(false); + const [entityDialogId, setEntityDialogId] = useState(null); + + const handleUserDialogOpen = (id: number) => (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + setUserDialogId(id); + setUserDialogOpen(true); + }; + + const handleEntityDialogOpen = (id: number) => (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + setEntityDialogId(id); + setEntityDialogOpen(true); + }; + return ( + + setUserDialogOpen(false)} userID={userDialogId ?? undefined} /> + setEntityDialogOpen(false)} + entityID={entityDialogId ?? undefined} + /> + + + + {t("group.#")} + {t("file.blobType")} + {t("file.size")} + {t("file.storagePolicy")} + {t("file.source")} + {t("file.createdAt")} + {t("file.creator")} + + + + {values?.edges?.entities?.map((option, index) => ( + + + {option.id} + + + + {t(EntityTypeText[option.type ?? EntityType.version])} + + + + {sizeToString(option.size ?? 0)} + + + + + {option.edges?.storage_policy?.name ?? ""} + + + + + + {option.source ?? ""} + + + + + + + + + + + {option.edges?.user?.nick ?? ""} + + + + + ))} + {!values?.edges?.entities?.length && ( + + + {t("file.noEntities")} + + + )} + +
+
+ ); +}; + +export default FileEntity; diff --git a/src/component/Admin/File/FileDialog/FileForm.tsx b/src/component/Admin/File/FileDialog/FileForm.tsx new file mode 100755 index 0000000..b8fb4c1 --- /dev/null +++ b/src/component/Admin/File/FileDialog/FileForm.tsx @@ -0,0 +1,162 @@ +import { Box, Grid2 as Grid, Link, Skeleton, styled, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Link as RouterLink } from "react-router-dom"; +import { getFileInfo } from "../../../../api/api"; +import { FileType } from "../../../../api/explorer"; +import { useAppDispatch } from "../../../../redux/hooks"; +import { sizeToString } from "../../../../util"; +import CrUri from "../../../../util/uri"; +import { DenseFilledTextField, NoWrapTypography } from "../../../Common/StyledComponents"; +import UserAvatar from "../../../Common/User/UserAvatar"; +import FileBadge from "../../../FileManager/FileBadge"; +import SettingForm from "../../../Pages/Setting/SettingForm"; +import SinglePolicySelectionInput from "../../Common/SinglePolicySelectionInput"; +import UserDialog from "../../User/UserDialog/UserDialog"; +import { FileDialogContext } from "./FileDialog"; +import FileDirectLinks from "./FileDirectLinks"; +import FileEntity from "./FileEntity"; +import FileMetadata from "./FileMetadata"; + +const StyledFileBadge = styled(FileBadge)(({ theme }) => ({ + minHeight: "40px", + border: `1px solid ${theme.palette.mode === "light" ? "rgba(0, 0, 0, 0.23)" : "rgba(255, 255, 255, 0.23)"}`, +})); + +const FileForm = () => { + const dispatch = useAppDispatch(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + const { t } = useTranslation("dashboard"); + const { formRef, values, setFile } = useContext(FileDialogContext); + const [userDialogOpen, setUserDialogOpen] = useState(false); + const [userDialogID, setUserDialogID] = useState(0); + const [fileParentLoading, setFileParentLoading] = useState(true); + const [fileParent, setFileParent] = useState(null); + + useEffect(() => { + setFileParentLoading(true); + dispatch(getFileInfo({ id: values.file_hash_id }, true)) + .then((res) => { + const crUri = new CrUri(res.path); + setFileParent(crUri.parent().toString()); + }) + .catch(() => { + setFileParent(null); + }) + .finally(() => { + setFileParentLoading(false); + }); + }, [values.id]); + + const onNameChange = useCallback( + (e: React.ChangeEvent) => { + setFile((prev) => ({ ...prev, name: e.target.value })); + }, + [setFile], + ); + + const userClicked = (e: React.MouseEvent) => { + e.preventDefault(); + setUserDialogOpen(true); + setUserDialogID(values?.edges?.owner?.id ?? 0); + }; + + const sizeUsed = useMemo(() => { + return sizeToString(values?.edges?.entities?.reduce((acc, entity) => acc + (entity.size ?? 0), 0) ?? 0); + }, [values?.edges?.entities]); + + return ( + <> + setUserDialogOpen(false)} userID={userDialogID} /> + e.preventDefault()}> + + + + {values.id} + + + + + {sizeToString(values.size ?? 0)} + + + + + {sizeUsed} + + + + + , + ]} + ns="dashboard" + values={{ num: values.edges?.shares?.length ?? 0 }} + /> + + + + + + + + + {values?.edges?.owner?.nick} + + + + + + + + + + {!fileParentLoading && ( + <> + {fileParent && ( + + )} + {!fileParent && "-"} + + )} + {fileParentLoading && } + + + {}} /> + + + + + + + + + + + + + + ); +}; + +export default FileForm; diff --git a/src/component/Admin/File/FileDialog/FileMetadata.tsx b/src/component/Admin/File/FileDialog/FileMetadata.tsx new file mode 100755 index 0000000..b51f625 --- /dev/null +++ b/src/component/Admin/File/FileDialog/FileMetadata.tsx @@ -0,0 +1,114 @@ +import { Delete } from "@mui/icons-material"; +import { Checkbox, IconButton, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material"; +import { useContext } from "react"; +import { useTranslation } from "react-i18next"; +import { + DenseFilledTextField, + NoWrapCell, + NoWrapTableCell, + NoWrapTypography, + StyledTableContainerPaper, +} from "../../../Common/StyledComponents"; +import { FileDialogContext } from "./FileDialog"; + +const FileMetadata = () => { + const { t } = useTranslation("dashboard"); + const { setFile, values } = useContext(FileDialogContext); + const onKeyChange = (index: number, value: string) => { + setFile((prev) => ({ + ...prev, + edges: { + ...prev.edges, + metadata: prev.edges?.metadata?.map((item, i) => (i === index ? { ...item, name: value } : item)), + }, + })); + }; + const onValueChange = (index: number, value: string) => { + setFile((prev) => ({ + ...prev, + edges: { + ...prev.edges, + metadata: prev.edges?.metadata?.map((item, i) => (i === index ? { ...item, value: value } : item)), + }, + })); + }; + const onIsPublicChange = (index: number, value: boolean) => { + setFile((prev) => ({ + ...prev, + edges: { + ...prev.edges, + metadata: prev.edges?.metadata?.map((item, i) => + i === index ? { ...item, is_public: value ? true : undefined } : item, + ), + }, + })); + }; + const handleDelete = (id: number) => { + setFile((prev) => ({ + ...prev, + edges: { ...prev.edges, metadata: prev.edges?.metadata?.filter((item) => item.id !== id) }, + })); + }; + return ( + + + + + {t("file.name")} + {t("file.value")} + {t("file.isPublic")} + {t("group.#")} + + + + + {values?.edges?.metadata?.map((option, index) => ( + + + onKeyChange(index, e.target.value)} + /> + + + onValueChange(index, e.target.value)} + /> + + + onIsPublicChange(index, e.target.checked)} + /> + + + {option.id} + + + handleDelete(option.id)}> + + + + + ))} + {!values?.edges?.metadata?.length && ( + + + {t("file.noMetadata")} + + + )} + +
+
+ ); +}; + +export default FileMetadata; diff --git a/src/component/Admin/File/FileFilterPopover.tsx b/src/component/Admin/File/FileFilterPopover.tsx new file mode 100755 index 0000000..f932bba --- /dev/null +++ b/src/component/Admin/File/FileFilterPopover.tsx @@ -0,0 +1,197 @@ +import { Box, Button, Checkbox, Popover, PopoverProps, Stack, styled } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { DenseFilledTextField, SmallFormControlLabel } from "../../Common/StyledComponents"; +import SettingForm from "../../Pages/Setting/SettingForm"; +import SinglePolicySelectionInput from "../Common/SinglePolicySelectionInput"; + +export interface FileFilterPopoverProps extends PopoverProps { + storagePolicy: string; + setStoragePolicy: (storagePolicy: string) => void; + owner: string; + setOwner: (owner: string) => void; + name: string; + setName: (name: string) => void; + hasShareLink: boolean; + setHasShareLink: (hasShareLink: boolean) => void; + hasDirectLink: boolean; + setHasDirectLink: (hasDirectLink: boolean) => void; + isUploading: boolean; + setIsUploading: (isUploading: boolean) => void; + clearFilters: () => void; +} + +const StyledCheckbox = styled(Checkbox)(({ theme }) => ({ + paddingTop: 0, + paddingBottom: 0, + paddingLeft: 0, +})); + +const FileFilterPopover = ({ + storagePolicy, + setStoragePolicy, + owner, + setOwner, + name, + setName, + hasShareLink, + setHasShareLink, + hasDirectLink, + setHasDirectLink, + isUploading, + setIsUploading, + clearFilters, + onClose, + open, + ...rest +}: FileFilterPopoverProps) => { + const { t } = useTranslation("dashboard"); + + // Create local state to track changes before applying + const [localStoragePolicy, setLocalStoragePolicy] = useState(storagePolicy); + const [localOwner, setLocalOwner] = useState(owner); + const [localName, setLocalName] = useState(name); + const [localHasShareLink, setLocalHasShareLink] = useState(hasShareLink); + const [localHasDirectLink, setLocalHasDirectLink] = useState(hasDirectLink); + const [localIsUploading, setLocalIsUploading] = useState(isUploading); + + // Initialize local state when popup opens + useEffect(() => { + if (open) { + setLocalStoragePolicy(storagePolicy); + setLocalOwner(owner); + setLocalName(name); + setLocalHasShareLink(hasShareLink); + setLocalHasDirectLink(hasDirectLink); + setLocalIsUploading(isUploading); + } + }, [open]); + + // Apply filters and close popover + const handleApplyFilters = () => { + setStoragePolicy(localStoragePolicy); + setOwner(localOwner); + setName(localName); + setHasShareLink(localHasShareLink); + setHasDirectLink(localHasDirectLink); + setIsUploading(localIsUploading); + onClose?.({}, "backdropClick"); + }; + + // Reset filters and close popover + const handleResetFilters = () => { + setLocalStoragePolicy(""); + setLocalOwner(""); + setLocalName(""); + setLocalHasShareLink(false); + setLocalHasDirectLink(false); + setLocalIsUploading(false); + clearFilters(); + onClose?.({}, "backdropClick"); + }; + + return ( + + + + setLocalOwner(e.target.value)} + placeholder={t("user.emptyNoFilter")} + size="small" + /> + + + + setLocalName(e.target.value)} + placeholder={t("user.emptyNoFilter")} + size="small" + /> + + + + setLocalStoragePolicy(value.toString())} + emptyValue={-1} + emptyText={t("user.all")} + /> + + + + + setLocalHasShareLink(e.target.checked)} + /> + } + label={t("file.shareLinkExisted")} + /> + setLocalHasDirectLink(e.target.checked)} + /> + } + label={t("file.directLinkExisted")} + /> + setLocalIsUploading(e.target.checked)} + /> + } + label={t("file.isUploading")} + /> + + + + + + + + + + ); +}; + +export default FileFilterPopover; diff --git a/src/component/Admin/File/FileRow.tsx b/src/component/Admin/File/FileRow.tsx new file mode 100755 index 0000000..8104798 --- /dev/null +++ b/src/component/Admin/File/FileRow.tsx @@ -0,0 +1,261 @@ +import { Box, Checkbox, IconButton, Link, Skeleton, TableCell, TableRow, Tooltip } from "@mui/material"; +import { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { batchDeleteFiles, getFileUrl } from "../../../api/api"; +import { File } from "../../../api/dashboard"; +import { FileType, Metadata } from "../../../api/explorer"; +import { useAppDispatch } from "../../../redux/hooks"; +import { Viewers } from "../../../redux/siteConfigSlice"; +import { confirmOperation } from "../../../redux/thunks/dialog"; +import { fileExtension, sizeToString } from "../../../util"; +import { NoWrapTableCell, NoWrapTypography } from "../../Common/StyledComponents"; +import TimeBadge from "../../Common/TimeBadge"; +import UserAvatar from "../../Common/User/UserAvatar"; +import FileTypeIcon from "../../FileManager/Explorer/FileTypeIcon"; +import UploadingTag from "../../FileManager/Explorer/UploadingTag"; +import Delete from "../../Icons/Delete"; +import LinkIcon from "../../Icons/LinkOutlined"; +import Open from "../../Icons/Open"; +import Share from "../../Icons/Share"; + +export interface FileRowProps { + file?: File; + loading?: boolean; + deleting?: boolean; + selected?: boolean; + onDelete?: () => void; + onDetails?: (id: number) => void; + onSelect?: (id: number) => void; + openUserDialog?: (id: number) => void; +} + +const FileRow = ({ + file, + loading, + deleting, + selected, + onDelete, + onDetails, + onSelect, + openUserDialog, +}: FileRowProps) => { + const navigate = useNavigate(); + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [deleteLoading, setDeleteLoading] = useState(false); + const [openLoading, setOpenLoading] = useState(false); + const onRowClick = () => { + onDetails?.(file?.id ?? 0); + }; + + const onDeleteClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + dispatch(confirmOperation(t("file.confirmDelete", { file: file?.name }))).then(() => { + if (file?.id) { + setDeleteLoading(true); + dispatch(batchDeleteFiles({ ids: [file.id] })) + .then(() => { + onDelete?.(); + }) + .finally(() => { + setDeleteLoading(false); + }); + } + }); + }; + + const onOpenClick = (e: React.MouseEvent) => { + e.stopPropagation(); + setOpenLoading(true); + + dispatch(getFileUrl(file?.id ?? 0)) + .then((url) => { + const ext = fileExtension(file?.name ?? ""); + + let hasViewer = false; + try { + // check Viewers object is loaded and valid + if (ext && Viewers && typeof Viewers === 'object' && Viewers[ext]) { + hasViewer = Array.isArray(Viewers[ext]) && Viewers[ext].length > 0; + } + } catch (error) { + console.warn('Failed to check viewer availability:', error); + hasViewer = false; + } + + if (hasViewer) { + // å¯é¢„è§ˆæ–‡ä»¶ï¼šæ–°çª—å£æ‰“开预览,窗å£ä¿æŒæ˜¾ç¤ºé¢„览内容 + window.open(url, "_blank"); + } else { + // 下载文件:使用a标签的download属性强制下载 + const link = document.createElement('a'); + link.href = url; + link.download = file?.name || `file-${file?.id}`; + link.style.display = 'none'; + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + }) + .finally(() => { + setOpenLoading(false); + }) + .catch((error) => { + console.error('Failed to get file URL:', error); + }); + }; + + const onSelectClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + onSelect?.(file?.id ?? 0); + }; + + const uploading = useMemo(() => { + return ( + file?.edges?.metadata && file?.edges?.metadata.some((metadata) => metadata.name === Metadata.upload_session_id) + ); + }, [file?.edges?.metadata]); + + if (loading) { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } + + const stopPropagation = (e: React.MouseEvent) => { + e.stopPropagation(); + }; + + const userClicked = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + openUserDialog?.(file?.owner_id ?? 0); + }; + + const sizeUsed = useMemo(() => { + return sizeToString(file?.edges?.entities?.reduce((acc, entity) => acc + (entity.size ?? 0), 0) ?? 0); + }, [file?.edges?.entities]); + + return ( + + + + + + {file?.id} + + + + + {file?.name} + + {uploading && } + {file?.edges?.direct_links?.length && ( + + + + + + )} + {file?.edges?.shares?.length && ( + + + + + + )} + + + + + {sizeToString(file?.size ?? 0)} + + + {sizeUsed} + + + + + + + {file?.edges?.owner?.nick} + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default FileRow; diff --git a/src/component/Admin/File/FileSetting.tsx b/src/component/Admin/File/FileSetting.tsx new file mode 100755 index 0000000..f5385e8 --- /dev/null +++ b/src/component/Admin/File/FileSetting.tsx @@ -0,0 +1,353 @@ +import { Delete } from "@mui/icons-material"; +import { + Badge, + Box, + Button, + Checkbox, + Container, + Divider, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TableSortLabel, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { bindPopover, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import { useQueryState } from "nuqs"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { batchDeleteFiles, getFlattenFileList } from "../../../api/api"; +import { File } from "../../../api/dashboard"; +import { Metadata } from "../../../api/explorer"; +import { useAppDispatch } from "../../../redux/hooks"; +import { confirmOperation } from "../../../redux/thunks/dialog"; +import { NoWrapTableCell, SecondaryButton, StyledTableContainerPaper } from "../../Common/StyledComponents"; +import ArrowImport from "../../Icons/ArrowImport"; +import ArrowSync from "../../Icons/ArrowSync"; +import Filter from "../../Icons/Filter"; +import PageContainer from "../../Pages/PageContainer"; +import PageHeader from "../../Pages/PageHeader"; +import TablePagination from "../Common/TablePagination"; +import { OrderByQuery, OrderDirectionQuery, PageQuery, PageSizeQuery } from "../StoragePolicy/StoragePolicySetting"; +import UserDialog from "../User/UserDialog/UserDialog"; +import FileDialog from "./FileDialog/FileDialog"; +import FileFilterPopover from "./FileFilterPopover"; +import FileRow from "./FileRow"; +import { ImportFileDialog } from "./ImportFileDialog"; + +export const StoragePolicyQuery = "storage_policy"; +export const OwnerQuery = "owner"; +export const NameQuery = "name"; +export const HasDirectLinkQuery = "has_direct_link"; +export const SharedQuery = "shared"; +export const UploadingQuery = "uploading"; + +const FileSetting = () => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(true); + const [files, setFiles] = useState([]); + const [page, setPage] = useQueryState(PageQuery, { defaultValue: "1" }); + const [pageSize, setPageSize] = useQueryState(PageSizeQuery, { + defaultValue: "10", + }); + const [orderBy, setOrderBy] = useQueryState(OrderByQuery, { + defaultValue: "", + }); + const [orderDirection, setOrderDirection] = useQueryState(OrderDirectionQuery, { defaultValue: "desc" }); + const [storagePolicy, setStoragePolicy] = useQueryState(StoragePolicyQuery, { defaultValue: "" }); + const [owner, setOwner] = useQueryState(OwnerQuery, { defaultValue: "" }); + const [name, setName] = useQueryState(NameQuery, { defaultValue: "" }); + const [hasDirectLink, setHasDirectLink] = useQueryState(HasDirectLinkQuery, { defaultValue: "" }); + const [shared, setShared] = useQueryState(SharedQuery, { defaultValue: "" }); + const [uploading, setUploading] = useQueryState(UploadingQuery, { defaultValue: "" }); + const [count, setCount] = useState(0); + const [selected, setSelected] = useState([]); + const [createNewOpen, setCreateNewOpen] = useState(false); + const [importFileDialogOpen, setImportFileDialogOpen] = useState(false); + const filterPopupState = usePopupState({ + variant: "popover", + popupId: "userFilterPopover", + }); + + const [userDialogOpen, setUserDialogOpen] = useState(false); + const [userDialogID, setUserDialogID] = useState(undefined); + const [fileDialogOpen, setFileDialogOpen] = useState(false); + const [fileDialogID, setFileDialogID] = useState(undefined); + const [deleteLoading, setDeleteLoading] = useState(false); + + const pageInt = parseInt(page) ?? 1; + const pageSizeInt = parseInt(pageSize) ?? 11; + + const clearFilters = useCallback(() => { + setStoragePolicy(""); + setOwner(""); + setName(""); + setHasDirectLink(""); + setShared(""); + setUploading(""); + }, [setStoragePolicy, setOwner, setName, setHasDirectLink, setShared, setUploading]); + + useEffect(() => { + fetchFiles(); + }, [page, pageSize, orderBy, orderDirection, storagePolicy, owner, name, hasDirectLink, shared, uploading]); + + const fetchFiles = () => { + setLoading(true); + setSelected([]); + dispatch( + getFlattenFileList({ + page: pageInt, + page_size: pageSizeInt, + order_by: orderBy ?? "", + order_direction: orderDirection ?? "desc", + conditions: { + file_policy: storagePolicy, + file_user: owner, + file_name: name, + file_direct_link: hasDirectLink === "true" ? "true" : "", + file_shared: shared === "true" ? "true" : "", + file_metadata: uploading === "true" ? Metadata.upload_session_id : "", + }, + }), + ) + .then((res) => { + setFiles(res.files); + setPageSize(res.pagination.page_size.toString()); + setCount(res.pagination.total_items ?? 0); + setLoading(false); + }) + .finally(() => { + setLoading(false); + }); + }; + + const handleDelete = () => { + setDeleteLoading(true); + dispatch(confirmOperation(t("file.confirmBatchDelete", { num: selected.length }))) + .then(() => { + dispatch(batchDeleteFiles({ ids: Array.from(selected) })) + .then(() => { + fetchFiles(); + }) + .finally(() => { + setDeleteLoading(false); + }); + }) + .finally(() => { + setDeleteLoading(false); + }); + }; + + const handleSelectAllClick = (event: React.ChangeEvent) => { + if (event.target.checked) { + const newSelected = files.map((n) => n.id); + setSelected(newSelected); + return; + } + setSelected([]); + }; + + const handleSelect = useCallback( + (id: number) => { + const selectedIndex = selected.indexOf(id); + let newSelected: readonly number[] = []; + + if (selectedIndex === -1) { + newSelected = newSelected.concat(selected, id); + } else if (selectedIndex === 0) { + newSelected = newSelected.concat(selected.slice(1)); + } else if (selectedIndex === selected.length - 1) { + newSelected = newSelected.concat(selected.slice(0, -1)); + } else if (selectedIndex > 0) { + newSelected = newSelected.concat(selected.slice(0, selectedIndex), selected.slice(selectedIndex + 1)); + } + setSelected(newSelected); + }, + [selected], + ); + + const orderById = orderBy === "id" || orderBy === ""; + const direction = orderDirection as "asc" | "desc"; + const onSortClick = (field: string) => () => { + const alreadySorted = orderBy === field || (field === "id" && orderById); + setOrderBy(field); + setOrderDirection(alreadySorted ? (direction === "asc" ? "desc" : "asc") : "asc"); + }; + + const hasActiveFilters = useMemo(() => { + return !!(storagePolicy || owner || name || hasDirectLink || shared || uploading); + }, [storagePolicy, owner, name, hasDirectLink, shared, uploading]); + + const handleFileDialogOpen = (id: number) => { + setFileDialogID(id); + setFileDialogOpen(true); + }; + + const handleUserDialogOpen = (id: number) => { + setUserDialogID(id); + setUserDialogOpen(true); + }; + + return ( + + {/* setCreateNewOpen(false)} + onCreated={(user) => { + setUserDialogID(user.id); + setUserDialogOpen(true); + }} + /> */} + setFileDialogOpen(false)} + fileID={fileDialogID} + onUpdated={(file) => { + setFileDialogID(file.id); + setFileDialogOpen(true); + }} + /> + setUserDialogOpen(false)} userID={userDialogID} /> + setImportFileDialogOpen(false)} /> + + + + + + setHasDirectLink(value ? "true" : "")} + hasShareLink={shared === "true"} + setHasShareLink={(value: boolean) => setShared(value ? "true" : "")} + isUploading={uploading === "true"} + setIsUploading={(value: boolean) => setUploading(value ? "true" : "")} + /> + + }> + {t("node.refresh")} + + + + } variant="contained" {...bindTrigger(filterPopupState)}> + {t("user.filter")} + + + + {selected.length > 0 && !isMobile && ( + <> + + + + )} + + {isMobile && selected.length > 0 && ( + + + + )} + + + + + + 0 && selected.length < files.length} + checked={files.length > 0 && selected.length === files.length} + onChange={handleSelectAllClick} + /> + + + + {t("group.#")} + + + + + {t("file.name")} + + + + + {t("file.size")} + + + {t("file.sizeUsed")} + {t("file.uploader")} + + + {t("file.createdAt")} + + + + + + + {!loading && + files.map((file) => ( + + ))} + {loading && + files.length > 0 && + files.slice(0, 10).map((file) => )} + {loading && + files.length === 0 && + Array.from(Array(10)).map((_, index) => )} + +
+
+ {count > 0 && ( + + setPageSize(value.toString())} + onChange={(_, value) => setPage(value.toString())} + /> + + )} +
+
+ ); +}; + +export default FileSetting; diff --git a/src/component/Admin/File/ImportFileDialog.tsx b/src/component/Admin/File/ImportFileDialog.tsx new file mode 100755 index 0000000..2fe88a9 --- /dev/null +++ b/src/component/Admin/File/ImportFileDialog.tsx @@ -0,0 +1,190 @@ +import { Alert, AlertTitle, Checkbox, DialogContent, Stack, Typography } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { sendImport } from "../../../api/api"; +import { User } from "../../../api/user"; +import { useAppDispatch } from "../../../redux/hooks"; +import { DefaultCloseAction } from "../../Common/Snackbar/snackbar"; +import { DenseFilledTextField, SmallFormControlLabel } from "../../Common/StyledComponents"; +import DraggableDialog from "../../Dialogs/DraggableDialog"; +import SettingForm from "../../Pages/Setting/SettingForm"; +import SinglePolicySelectionInput from "../Common/SinglePolicySelectionInput"; +import { NoMarginHelperText } from "../Settings/Settings"; +import UserSearchInput from "./UserSearchInput"; +// TODO: Add API call for creating import task + +export interface ImportFileDialogProps { + open: boolean; + onClose: () => void; +} + +interface ImportTaskForm { + storagePolicyId: number | undefined; + externalPath: string; + recursive: boolean; + targetUser: User | null; + targetPath: string; + extractMediaMeta: boolean | undefined; +} + +const defaultForm: ImportTaskForm = { + storagePolicyId: undefined, + externalPath: "", + recursive: true, + targetUser: null, + targetPath: "/", + extractMediaMeta: false, +}; + +export const ImportFileDialog = ({ open, onClose }: ImportFileDialogProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + const [loading, setLoading] = useState(false); + const [formState, setFormState] = useState({ ...defaultForm }); + const formRef = useRef(null); + + useEffect(() => { + if (open) { + setFormState({ ...defaultForm }); + } + }, [open]); + + const handleSubmit = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + + if (!formState.targetUser) { + enqueueSnackbar(t("file.pleaseSelectUser"), { variant: "error", action: DefaultCloseAction }); + return; + } + + setLoading(true); + dispatch( + sendImport({ + src: formState.externalPath, + dst: formState.targetPath, + extract_media_meta: false, + user_id: formState.targetUser.id, + recursive: formState.recursive, + policy_id: formState.storagePolicyId ?? 0, + }), + ) + .then(() => { + enqueueSnackbar(t("file.importTaskCreated"), { variant: "success", action: DefaultCloseAction }); + onClose(); + }) + .finally(() => { + setLoading(false); + }); + }; + + const handlePolicyChange = (value: number) => { + setFormState({ ...formState, storagePolicyId: value }); + }; + + const handleUserSelected = (user: User) => { + setFormState({ ...formState, targetUser: user }); + }; + + return ( + + +
{ + e.preventDefault(); + handleSubmit(); + }} + > + + theme.typography.body2.fontSize }}> + {t("file.importExternalFolderDes")} + + theme.typography.body2.fontSize }}> + {t("file.importWarning")} +
    + {t("file.importWarnings", { returnObjects: true }).map((warning, index) => ( +
  • {warning.toString()}
  • + ))} +
+
+ + + {t("file.storagePolicyDes")} + + + setFormState({ ...formState, externalPath: e.target.value })} + /> + {t("file.selectSrcDes")} + + + + {t("file.targetUserDes")} + + + setFormState({ ...formState, targetPath: e.target.value })} + placeholder={"/"} + /> + {t("file.dstFolderPathDes")} + + + setFormState({ ...formState, recursive: e.target.checked })} + /> + } + labelPlacement="end" + label={{t("file.recursivelyImport")}} + /> + {t("file.recursivelyImportDes")} + + + setFormState({ ...formState, extractMediaMeta: e.target.checked })} + /> + } + labelPlacement="end" + label={{t("file.extractMediaMeta")}} + /> + {t("file.extractMediaMetaDes")} + +
+
+
+
+ ); +}; + +export default ImportFileDialog; diff --git a/src/component/Admin/File/UserSearchInput.tsx b/src/component/Admin/File/UserSearchInput.tsx new file mode 100755 index 0000000..e02c5cf --- /dev/null +++ b/src/component/Admin/File/UserSearchInput.tsx @@ -0,0 +1,142 @@ +import { Box, createFilterOptions, debounce, useTheme } from "@mui/material"; +import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getSearchUser } from "../../../api/api.ts"; +import { User } from "../../../api/user.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { + DenseAutocomplete, + DenseFilledTextField, + NoWrapBox, + NoWrapTypography, +} from "../../Common/StyledComponents.tsx"; +import UserAvatar from "../../Common/User/UserAvatar.tsx"; + +export interface UserSearchInputProps { + onUserSelected: (user: User) => void; + label?: string; + required?: boolean; +} + +const UserSearchInput = (props: UserSearchInputProps) => { + const theme = useTheme(); + const { t } = useTranslation(["dashboard", "application"]); + const [value, setValue] = useState(null); + const [inputValue, setInputValue] = useState(""); + const [options, setOptions] = useState([]); + const [loading, setLoading] = useState(false); + + const dispatch = useAppDispatch(); + + const fetch = useMemo( + () => + debounce((request: { input: string }, callback: (results?: readonly User[]) => void) => { + setLoading(true); + dispatch(getSearchUser(request.input)) + .then((results: readonly User[]) => { + callback(results); + }) + .finally(() => { + setLoading(false); + }); + }, 400), + [dispatch], + ); + + useEffect(() => { + let active = true; + + if (inputValue === "" || inputValue.length < 2) { + setOptions([]); + return undefined; + } + + fetch({ input: inputValue }, (results?: readonly User[]) => { + if (active) { + setOptions(results ?? []); + } + }); + + return () => { + active = false; + }; + }, [value, inputValue, fetch]); + + const filterOptions = useMemo(() => { + return createFilterOptions({ + stringify: (option) => option.nickname + " " + option.email, + }); + }, []); + + return ( + { + setValue(newValue); + if (newValue) { + props.onUserSelected(newValue); + } + }} + onInputChange={(_event, newInputValue) => { + setInputValue(newInputValue); + }} + getOptionLabel={(option) => (typeof option === "string" ? option : `${option.nickname} <${option.email}>`)} + noOptionsText={t("application:modals.noResults")} + renderOption={(props, option) => { + return ( +
  • + + + + + + {option.nickname} + {option.email && ( + + {option.email} + + )} + + +
  • + ); + }} + renderInput={(params) => ( + + )} + /> + ); +}; + +export default UserSearchInput; diff --git a/src/component/Admin/FileSystem/CustomProps/CustomPropsSetting.tsx b/src/component/Admin/FileSystem/CustomProps/CustomPropsSetting.tsx new file mode 100755 index 0000000..4320e09 --- /dev/null +++ b/src/component/Admin/FileSystem/CustomProps/CustomPropsSetting.tsx @@ -0,0 +1,207 @@ +import { + Box, + ListItemIcon, + Menu, + Stack, + Table, + TableBody, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; +import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import { createRef, useCallback, useContext, useEffect, useState } from "react"; +import { DndProvider } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; +import { useTranslation } from "react-i18next"; +import { CustomProps, CustomPropsType } from "../../../../api/explorer"; +import { NoWrapCell, SecondaryButton, StyledTableContainerPaper } from "../../../Common/StyledComponents"; +import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu"; +import Add from "../../../Icons/Add"; +import { ProChip } from "../../../Pages/Setting/SettingForm"; +import ProDialog from "../../Common/ProDialog"; +import { SettingContext } from "../../Settings/SettingWrapper"; +import DraggableCustomPropsRow, { FieldTypes } from "./DraggableCustomPropsRow"; +import EditPropsDialog from "./EditPropsDialog"; + +const CustomPropsSetting = () => { + const { t } = useTranslation("dashboard"); + const { formRef, setSettings, values } = useContext(SettingContext); + const [customProps, setCustomProps] = useState([]); + const [open, setOpen] = useState(false); + const [isNew, setIsNew] = useState(false); + const [editProps, setEditProps] = useState(undefined); + const [proOpen, setProOpen] = useState(false); + const newPropsPopupState = usePopupState({ + variant: "popover", + popupId: "newProp", + }); + const { onClose, ...menuProps } = bindMenu(newPropsPopupState); + + useEffect(() => { + try { + setCustomProps(JSON.parse(values.custom_props || "[]")); + } catch { + setCustomProps([]); + } + }, [values.custom_props]); + + const onChange = useCallback( + (customProps: CustomProps[]) => { + setSettings({ + custom_props: JSON.stringify(customProps), + }); + }, + [setSettings], + ); + + const handleDeleteProduct = useCallback( + (id: string) => { + const newCustomProps = customProps.filter((p) => p.id !== id); + setCustomProps(newCustomProps); + onChange(newCustomProps); + }, + [customProps, onChange], + ); + + const handleSave = useCallback( + (props: CustomProps) => { + const existingIndex = customProps.findIndex((p) => p.id === props.id); + let newCustomProps: CustomProps[]; + if (existingIndex >= 0) { + newCustomProps = [...customProps]; + newCustomProps[existingIndex] = props; + } else { + newCustomProps = [...customProps, props]; + } + setCustomProps(newCustomProps); + onChange(newCustomProps); + }, + [customProps, onChange], + ); + + const moveRow = useCallback( + (from: number, to: number) => { + if (from === to) return; + const updated = [...customProps]; + const [moved] = updated.splice(from, 1); + updated.splice(to, 0, moved); + setCustomProps(updated); + onChange(updated); + }, + [customProps, onChange], + ); + + const handleMoveUp = (idx: number) => { + if (idx <= 0) return; + moveRow(idx, idx - 1); + }; + const handleMoveDown = (idx: number) => { + if (idx >= customProps.length - 1) return; + moveRow(idx, idx + 1); + }; + + const onNewProp = (type: CustomPropsType) => { + setEditProps({ + type, + id: "", + name: "", + default: "", + }); + setIsNew(true); + setOpen(true); + onClose(); + }; + + return ( + e.preventDefault()}> + + setProOpen(false)} /> + + } {...bindTrigger(newPropsPopupState)}> + {t("customProps.add")} + + + {(Object.keys(FieldTypes) as CustomPropsType[]).map((type, index) => { + const fieldType = FieldTypes[type]; + const Icon = fieldType.icon; + return ( + (fieldType.pro ? setProOpen(true) : onNewProp(type))}> + + + + {t(fieldType.title)} + {fieldType.pro && } + + ); + })} + + + + + + + + {t("settings.displayName")} + {t("customProps.type")} + {t("customProps.default")} + {t("settings.actions")} + + + + + {customProps.map((prop, idx) => { + const rowRef = createRef(); + return ( + { + setEditProps(props); + setIsNew(false); + setOpen(true); + }} + onDelete={handleDeleteProduct} + onMoveUp={() => handleMoveUp(idx)} + onMoveDown={() => handleMoveDown(idx)} + isFirst={idx === 0} + isLast={idx === customProps.length - 1} + t={t} + /> + ); + })} + {customProps.length === 0 && ( + + + + {t("application:setting.listEmpty")} + + + + )} + +
    +
    +
    +
    + setOpen(false)} onSave={handleSave} isNew={isNew} props={editProps} /> +
    + ); +}; + +export default CustomPropsSetting; diff --git a/src/component/Admin/FileSystem/CustomProps/DraggableCustomPropsRow.tsx b/src/component/Admin/FileSystem/CustomProps/DraggableCustomPropsRow.tsx new file mode 100755 index 0000000..ec67324 --- /dev/null +++ b/src/component/Admin/FileSystem/CustomProps/DraggableCustomPropsRow.tsx @@ -0,0 +1,209 @@ +import { Icon } from "@iconify/react/dist/iconify.js"; +import { Box, IconButton, SvgIconProps, TableRow, useTheme } from "@mui/material"; +import React from "react"; +import { useDrag, useDrop } from "react-dnd"; +import { CustomProps, CustomPropsType } from "../../../../api/explorer"; +import { NoWrapCell } from "../../../Common/StyledComponents"; +import { getPropsContent } from "../../../FileManager/Sidebar/CustomProps/CustomPropsItem"; +import ArrowDown from "../../../Icons/ArrowDown"; +import CheckboxChecked from "../../../Icons/CheckboxChecked"; +import DataBarVerticalStar from "../../../Icons/DataBarVerticalStar"; +import Dismiss from "../../../Icons/Dismiss"; +import Edit from "../../../Icons/Edit"; +import LinkOutlined from "../../../Icons/LinkOutlined"; +import Numbers from "../../../Icons/Numbers"; +import PersonOutlined from "../../../Icons/PersonOutlined"; +import SlideText from "../../../Icons/SlideText"; +import TaskListRegular from "../../../Icons/TaskListRegular"; +import TextIndentIncrease from "../../../Icons/TextIndentIncrease"; + +const DND_TYPE = "storage-product-row"; + +// 拖拽item类型 +type DragItem = { index: number }; + +export interface DraggableCustomPropsRowProps { + customProps: CustomProps; + index: number; + moveRow: (from: number, to: number) => void; + onEdit: (customProps: CustomProps) => void; + onDelete: (id: string) => void; + onMoveUp: () => void; + onMoveDown: () => void; + isFirst: boolean; + isLast: boolean; + t: any; + style?: React.CSSProperties; +} + +export interface FieldTypeProps { + title: string; + icon: (props: SvgIconProps) => JSX.Element; + minTitle?: string; + minDes?: string; + maxTitle?: string; + maxDes?: string; + maxRequired?: boolean; + showOptions?: boolean; + pro?: boolean; +} + +export const FieldTypes: Record = { + [CustomPropsType.text]: { + title: "customProps.text", + icon: SlideText, + minTitle: "customProps.minLength", + minDes: "customProps.emptyLimit", + maxTitle: "customProps.maxLength", + maxDes: "customProps.emptyLimit", + }, + [CustomPropsType.number]: { + title: "customProps.number", + icon: Numbers, + minTitle: "customProps.minValue", + minDes: "customProps.emptyLimit", + maxTitle: "customProps.maxValue", + maxDes: "customProps.emptyLimit", + }, + [CustomPropsType.boolean]: { + title: "customProps.boolean", + icon: CheckboxChecked, + }, + [CustomPropsType.select]: { + title: "customProps.select", + icon: TextIndentIncrease, + showOptions: true, + }, + [CustomPropsType.multi_select]: { + title: "customProps.multiSelect", + icon: TaskListRegular, + showOptions: true, + }, + [CustomPropsType.user]: { + title: "customProps.user", + icon: PersonOutlined, + pro: true, + }, + [CustomPropsType.link]: { + title: "customProps.link", + icon: LinkOutlined, + minTitle: "customProps.minLength", + minDes: "customProps.emptyLimit", + maxTitle: "customProps.maxLength", + maxDes: "customProps.emptyLimit", + }, + [CustomPropsType.rating]: { + title: "customProps.rating", + icon: DataBarVerticalStar, + maxRequired: true, + maxTitle: "customProps.maxValue", + }, +}; + +const DraggableCustomPropsRow = React.memo( + React.forwardRef( + ( + { customProps, index, moveRow, onEdit, onDelete, onMoveUp, onMoveDown, isFirst, isLast, t, style }, + ref, + ): JSX.Element => { + const theme = useTheme(); + const [, drop] = useDrop({ + accept: DND_TYPE, + hover(item, monitor) { + if (!(ref && typeof ref !== "function" && ref.current)) return; + const dragIndex = item.index; + const hoverIndex = index; + if (dragIndex === hoverIndex) return; + const hoverBoundingRect = ref.current.getBoundingClientRect(); + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; + const clientOffset = monitor.getClientOffset(); + if (!clientOffset) return; + const hoverClientY = clientOffset.y - hoverBoundingRect.top; + if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) return; + if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) return; + moveRow(dragIndex, hoverIndex); + item.index = hoverIndex; + }, + }); + const [{ isDragging }, drag] = useDrag({ + type: DND_TYPE, + item: { index }, + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + }), + }); + // 兼容ref为function和对象 + const setRowRef = (node: HTMLTableRowElement | null) => { + if (typeof ref === "function") { + ref(node); + } else if (ref) { + (ref as React.MutableRefObject).current = node; + } + drag(drop(node)); + }; + + const fieldType = FieldTypes[customProps.type]; + const TypeIcon = fieldType.icon; + + return ( + + + + {customProps.icon && ( + + )} + {t(customProps.name, { ns: "application" })} + + + + + + {t(fieldType.title)} + + + + {getPropsContent( + { + props: customProps, + id: customProps.id, + value: customProps.default ?? "", + }, + () => {}, + false, + true, + )} + + + onEdit(customProps)}> + + + onDelete(customProps.id)}> + + + + + + + + + + + + + ); + }, + ), +); + +export default DraggableCustomPropsRow; diff --git a/src/component/Admin/FileSystem/CustomProps/EditPropsDialog.tsx b/src/component/Admin/FileSystem/CustomProps/EditPropsDialog.tsx new file mode 100755 index 0000000..48e644d --- /dev/null +++ b/src/component/Admin/FileSystem/CustomProps/EditPropsDialog.tsx @@ -0,0 +1,179 @@ +import { Box, DialogContent, FormControl, Grid2, Link } from "@mui/material"; +import { useEffect, useRef, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { CustomProps } from "../../../../api/explorer"; +import { DenseFilledTextField } from "../../../Common/StyledComponents"; +import DraggableDialog from "../../../Dialogs/DraggableDialog"; +import { getPropsContent } from "../../../FileManager/Sidebar/CustomProps/CustomPropsItem"; +import SettingForm from "../../../Pages/Setting/SettingForm"; +import { NoMarginHelperText } from "../../Settings/Settings"; +import { FieldTypes } from "./DraggableCustomPropsRow"; + +interface EditPropsDialogProps { + open: boolean; + onClose: () => void; + onSave: (props: CustomProps) => void; + isNew: boolean; + props?: CustomProps; +} + +const EditPropsDialog = ({ open, onClose, onSave, isNew, props }: EditPropsDialogProps) => { + const { t } = useTranslation("dashboard"); + const formRef = useRef(null); + const [editProps, setEditProps] = useState(props); + + const handleSave = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + + onSave({ ...editProps } as CustomProps); + onClose(); + }; + + useEffect(() => { + if (props) { + setEditProps({ ...props }); + } + + if (!open) { + setTimeout(() => { + setEditProps(undefined); + }, 100); + } + }, [open, props]); + + if (!editProps || !editProps.type) return null; + + const fieldType = FieldTypes[editProps?.type]; + return ( + + + + + + setEditProps({ ...editProps, id: e.target.value } as CustomProps)} + /> + {t("customProps.idDes")} + + + + + + setEditProps({ + ...editProps, + name: e.target.value, + } as CustomProps) + } + required + /> + {t("settings.displayNameDes")} + + + + + setEditProps({ ...editProps, icon: e.target.value } as CustomProps)} + /> + + { + ]} + /> + } + + + + {(fieldType.minTitle || fieldType.maxTitle) && ( + + {fieldType.minTitle && ( + + + setEditProps({ ...editProps, min: parseInt(e.target.value) } as CustomProps)} + /> + {fieldType.minDes && {t(fieldType.minDes)}} + + + )} + {fieldType.maxTitle && ( + + + setEditProps({ ...editProps, max: parseInt(e.target.value) } as CustomProps)} + /> + {fieldType.maxDes && {t(fieldType.maxDes)}} + + + )} + + )} + {fieldType.showOptions && ( + + + setEditProps({ ...editProps, options: e.target.value.split("\n") } as CustomProps)} + /> + {t("customProps.optionsDes")} + + + )} + + + {getPropsContent( + { + props: editProps, + id: editProps.id, + value: editProps.default ?? "", + }, + (value) => { + setEditProps({ ...editProps, default: value } as CustomProps); + }, + false, + false, + true, + )} + + + + + + ); +}; + +export default EditPropsDialog; diff --git a/src/component/Admin/FileSystem/Filesystem.tsx b/src/component/Admin/FileSystem/Filesystem.tsx new file mode 100755 index 0000000..e4d1a16 --- /dev/null +++ b/src/component/Admin/FileSystem/Filesystem.tsx @@ -0,0 +1,133 @@ +import { Box, Container } from "@mui/material"; +import { useQueryState } from "nuqs"; +import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import ResponsiveTabs, { Tab } from "../../Common/ResponsiveTabs.tsx"; +import AppGeneric from "../../Icons/AppGeneric.tsx"; +import Icons from "../../Icons/Icons.tsx"; +import SettingsOutlined from "../../Icons/SettingsOutlined.tsx"; +import TextBulletListSquareEdit from "../../Icons/TextBulletListSquareEdit.tsx"; +import PageContainer from "../../Pages/PageContainer.tsx"; +import PageHeader, { PageTabQuery } from "../../Pages/PageHeader.tsx"; +import SettingsWrapper from "../Settings/SettingWrapper.tsx"; +import CustomPropsSetting from "./CustomProps/CustomPropsSetting.tsx"; +import FileIcons from "./Icons/FileIcons.tsx"; +import Parameters from "./Parameters.tsx"; +import ViewerSetting from "./ViewerSetting/ViewerSetting.tsx"; + +export enum SettingsPageTab { + Parameters = "parameters", + CustomProps = "customProps", + Icon = "icon", + FileApp = "fileApp", +} + +const FileSystem = () => { + const { t } = useTranslation("dashboard"); + const [tab, setTab] = useQueryState(PageTabQuery); + + const tabs: Tab[] = useMemo(() => { + const res = []; + res.push( + ...[ + { + label: t("nav.settings"), + value: SettingsPageTab.Parameters, + icon: , + }, + { + label: t("settings.fileIcons"), + value: SettingsPageTab.Icon, + icon: , + }, + { + label: t("settings.fileViewers"), + value: SettingsPageTab.FileApp, + icon: , + }, + { + label: t("nav.customProps"), + value: SettingsPageTab.CustomProps, + icon: , + }, + ], + ); + return res; + }, [t]); + + return ( + + + + setTab(newValue)} + tabs={tabs} + /> + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${tab}`} + > + + {(!tab || tab === SettingsPageTab.Parameters) && ( + + + + )} + {tab === SettingsPageTab.Icon && ( + + + + )} + {tab === SettingsPageTab.FileApp && ( + + + + )} + {tab === SettingsPageTab.CustomProps && ( + + + + )} + + + + + + ); +}; + +export default FileSystem; diff --git a/src/component/Admin/FileSystem/HexColorInput.tsx b/src/component/Admin/FileSystem/HexColorInput.tsx new file mode 100755 index 0000000..f01016a --- /dev/null +++ b/src/component/Admin/FileSystem/HexColorInput.tsx @@ -0,0 +1,39 @@ +import { DenseFilledTextField } from "../../Common/StyledComponents.tsx"; +import { InputAdornment } from "@mui/material"; +import CircleColorSelector, { + customizeMagicColor, +} from "../../FileManager/FileInfo/ColorCircle/CircleColorSelector.tsx"; +import * as React from "react"; + +export interface HexColorInputProps { + currentColor: string; + onColorChange: (color: string) => void; + required?: boolean; +} + +const HexColorInput = ({ currentColor, onColorChange, ...rest }: HexColorInputProps) => { + return ( + { + onColorChange(e.target.value); + }} + type="text" + InputProps={{ + endAdornment: ( + + onColorChange(color)} + /> + + ), + }} + {...rest} + /> + ); +}; + +export default HexColorInput; diff --git a/src/component/Admin/FileSystem/Icons/EmojiList.tsx b/src/component/Admin/FileSystem/Icons/EmojiList.tsx new file mode 100755 index 0000000..da10d2b --- /dev/null +++ b/src/component/Admin/FileSystem/Icons/EmojiList.tsx @@ -0,0 +1,245 @@ +import { Box, IconButton, Stack, Table, TableBody, TableContainer, TableHead, TableRow } from "@mui/material"; +import React, { memo, useMemo, useState } from "react"; +import { DndProvider, useDrag, useDrop } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; +import { useTranslation } from "react-i18next"; +import { + DenseFilledTextField, + NoWrapCell, + NoWrapTableCell, + SecondaryButton, + StyledTableContainerPaper, +} from "../../../Common/StyledComponents.tsx"; +import Add from "../../../Icons/Add.tsx"; +import ArrowDown from "../../../Icons/ArrowDown.tsx"; +import Dismiss from "../../../Icons/Dismiss.tsx"; + +export interface EmojiListProps { + config: string; + onChange: (value: string) => void; +} + +const DND_TYPE = "emoji-row"; + +interface DraggableEmojiRowProps { + r: string; + i: number; + moveRow: (from: number, to: number) => void; + configParsed: { [key: string]: string[] }; + inputCache: { [key: number]: string | undefined }; + setInputCache: React.Dispatch>; + onChange: (value: string) => void; + isFirst: boolean; + isLast: boolean; + t: (key: string) => string; +} + +function DraggableEmojiRow({ + r, + i, + moveRow, + configParsed, + inputCache, + setInputCache, + onChange, + isFirst, + isLast, +}: DraggableEmojiRowProps) { + const ref = React.useRef(null); + const [, drop] = useDrop({ + accept: DND_TYPE, + hover(item: any, monitor) { + if (!ref.current) return; + + const dragIndex = item.index; + const hoverIndex = i; + if (dragIndex === hoverIndex) return; + + const hoverBoundingRect = ref.current.getBoundingClientRect(); + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; + const clientOffset = monitor.getClientOffset(); + if (!clientOffset) return; + + const hoverClientY = clientOffset.y - hoverBoundingRect.top; + + if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) return; + if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) return; + + moveRow(dragIndex, hoverIndex); + item.index = hoverIndex; + }, + }); + const [{ isDragging }, drag] = useDrag({ + type: DND_TYPE, + item: { index: i }, + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + }), + }); + drag(drop(ref)); + return ( + + + { + const newConfig = { + ...configParsed, + [e.target.value]: configParsed[r], + }; + delete newConfig[r]; + onChange(JSON.stringify(newConfig)); + }} + /> + + + { + onChange( + JSON.stringify({ + ...configParsed, + [r]: inputCache[i]?.split(",") ?? configParsed[r], + }), + ); + setInputCache({ + ...inputCache, + [i]: undefined, + }); + }} + onChange={(e) => + setInputCache({ + ...inputCache, + [i]: e.target.value, + }) + } + /> + + + { + const newConfig = { + ...configParsed, + }; + delete newConfig[r]; + onChange(JSON.stringify(newConfig)); + }} + size={"small"} + > + + + moveRow(i, i - 1)} disabled={isFirst}> + + + moveRow(i, i + 1)} disabled={isLast}> + + + + + ); +} + +const EmojiList = memo(({ config, onChange }: EmojiListProps) => { + const { t } = useTranslation("dashboard"); + const [render, setRender] = useState(false); + const configParsed = useMemo((): { [key: string]: string[] } => JSON.parse(config), [config]); + const [inputCache, setInputCache] = useState<{ + [key: number]: string | undefined; + }>({}); + return ( + + + {!render && ( + setRender(!render)}> + {t("settings.showSettings")} + + )} + {render && Object.keys(configParsed ?? {}).length > 0 && ( + + + + + + {t("settings.category")} + {t("settings.emojiOptions")} + {t("settings.actions")} + + + + {Object.keys(configParsed ?? {}).map((r, i, arr) => ( + { + if (from === to || to < 0 || to >= arr.length) return; + const keys = Object.keys(configParsed); + const values = Object.values(configParsed); + const [movedKey] = keys.splice(from, 1); + const [movedValue] = values.splice(from, 1); + keys.splice(to, 0, movedKey); + values.splice(to, 0, movedValue); + const newConfig: { [key: string]: string[] } = {}; + keys.forEach((k, idx) => { + newConfig[k] = values[idx]; + }); + onChange(JSON.stringify(newConfig)); + }} + configParsed={configParsed} + inputCache={inputCache} + setInputCache={setInputCache} + onChange={onChange} + isFirst={i === 0} + isLast={i === arr.length - 1} + t={t} + /> + ))} + +
    +
    +
    + )} +
    + {render && ( + + } + onClick={() => + onChange( + JSON.stringify({ + ...configParsed, + [""]: [], + }), + ) + } + > + {t("settings.addCategorize")} + + + )} +
    + ); +}); + +export default EmojiList; diff --git a/src/component/Admin/FileSystem/Icons/FileIconList.tsx b/src/component/Admin/FileSystem/Icons/FileIconList.tsx new file mode 100755 index 0000000..8a50f61 --- /dev/null +++ b/src/component/Admin/FileSystem/Icons/FileIconList.tsx @@ -0,0 +1,350 @@ +import { Icon } from "@iconify/react/dist/iconify.js"; +import { + Box, + IconButton, + InputAdornment, + ListItemText, + Table, + TableBody, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; +import { styled, useTheme } from "@mui/material/styles"; +import { memo, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + DenseFilledTextField, + DenseSelect, + NoWrapCell, + NoWrapTableCell, + SecondaryButton, + StyledTableContainerPaper, +} from "../../../Common/StyledComponents.tsx"; +import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx"; +import { builtInIcons, FileTypeIconSetting } from "../../../FileManager/Explorer/FileTypeIcon.tsx"; +import Add from "../../../Icons/Add.tsx"; +import Dismiss from "../../../Icons/Dismiss.tsx"; +import HexColorInput from "../HexColorInput.tsx"; + +export interface FileIconListProps { + config: string; + onChange: (value: string) => void; +} + +export enum IconType { + Image = "imageUrl", + Iconify = "iconifyName", +} + +const StyledDenseSelect = styled(DenseSelect)(() => ({ + "& .MuiFilledInput-input": { + "&:focus": { + backgroundColor: "initial", + }, + }, + backgroundColor: "initial", +})); + +const IconPreview = ({ icon }: { icon: FileTypeIconSetting }) => { + const theme = useTheme(); + const IconComponent = useMemo(() => { + if (icon.icon) { + return builtInIcons[icon.icon]; + } + }, [icon.icon]); + + const iconColor = useMemo(() => { + if (theme.palette.mode == "dark") { + return icon.color_dark ?? icon.color ?? theme.palette.action.active; + } else { + return icon.color ?? theme.palette.action.active; + } + }, [icon.color, icon.color_dark, theme]); + + if (IconComponent) { + return ( + + ); + } + + // Handle iconify icons + if (icon.iconify) { + return ; + } + + return ( + + ); +}; + +const FileIconList = memo(({ config, onChange }: FileIconListProps) => { + const { t } = useTranslation("dashboard"); + const configParsed = useMemo((): FileTypeIconSetting[] => JSON.parse(config), [config]); + const [inputCache, setInputCache] = useState<{ + [key: number]: string | undefined; + }>({}); + const [iconUrlCache, setIconUrlCache] = useState<{ + [key: number]: string | undefined; + }>({}); + const [iconTypeCache, setIconTypeCache] = useState<{ + [key: number]: IconType | undefined; + }>({}); + + return ( + + {configParsed?.length > 0 && ( + + + + + {t("settings.icon")} + {t("settings.iconUrl")} + {t("settings.iconColor")} + {t("settings.iconColorDark")} + {t("settings.exts")} + + + + + {configParsed.map((r, i) => { + const currentIconType = + iconTypeCache[i] ?? (r.img ? IconType.Image : r.iconify ? IconType.Iconify : IconType.Image); + const currentIconUrl = + iconUrlCache[i] ?? (currentIconType === IconType.Image ? r.img : r.iconify) ?? ""; + + return ( + + + + + + {!r.icon ? ( + { + const newConfig = [...configParsed]; + const updatedItem = { ...r }; + + if (currentIconType === IconType.Image) { + updatedItem.img = currentIconUrl; + updatedItem.iconify = undefined; + } else { + updatedItem.iconify = currentIconUrl; + updatedItem.img = undefined; + } + + newConfig[i] = updatedItem; + onChange(JSON.stringify(newConfig)); + + setIconUrlCache({ + ...iconUrlCache, + [i]: undefined, + }); + }} + onChange={(e) => + setIconUrlCache({ + ...iconUrlCache, + [i]: e.target.value, + }) + } + slotProps={{ + input: { + startAdornment: ( + + { + const newType = e.target.value as IconType; + setIconTypeCache({ + ...iconTypeCache, + [i]: newType, + }); + + // Clear the URL cache when switching types + setIconUrlCache({ + ...iconUrlCache, + [i]: "", + }); + + // Update the config immediately + const newConfig = [...configParsed]; + const updatedItem = { ...r }; + + if (newType === IconType.Image) { + updatedItem.img = ""; + updatedItem.iconify = undefined; + } else { + updatedItem.iconify = ""; + updatedItem.img = undefined; + } + + newConfig[i] = updatedItem; + onChange(JSON.stringify(newConfig)); + }} + renderValue={(value) => ( + {t(`settings.${value}`)} + )} + size={"small"} + variant="filled" + > + + + {t(`settings.${IconType.Image}`)} + + + + + {t(`settings.${IconType.Iconify}`)} + + + + + ), + }, + }} + /> + ) : ( + t("settings.builtinIcon") + )} + + + {!r.icon && !r.iconify ? ( + "-" + ) : ( + + onChange( + JSON.stringify([ + ...configParsed.slice(0, i), + { + ...r, + color: color, + }, + ...configParsed.slice(i + 1), + ]), + ) + } + /> + )} + + + {!r.icon && !r.iconify ? ( + "-" + ) : ( + + onChange( + JSON.stringify([ + ...configParsed.slice(0, i), + { + ...r, + color_dark: color, + }, + ...configParsed.slice(i + 1), + ]), + ) + } + /> + )} + + + { + onChange( + JSON.stringify([ + ...configParsed.slice(0, i), + { + ...r, + exts: inputCache[i]?.split(",") ?? r.exts, + }, + ...configParsed.slice(i + 1), + ]), + ); + setInputCache({ + ...inputCache, + [i]: undefined, + }); + }} + onChange={(e) => + setInputCache({ + ...inputCache, + [i]: e.target.value, + }) + } + /> + + + {!r.icon && ( + onChange(JSON.stringify(configParsed.filter((_, index) => index !== i)))} + size={"small"} + > + + + )} + + + ); + })} + +
    +
    + )} + } + sx={{ mt: 1 }} + onClick={() => + onChange( + JSON.stringify([ + ...configParsed, + { + img: "", + exts: [], + }, + ]), + ) + } + > + {t("settings.addIcon")} + +
    + ); +}); + +export default FileIconList; diff --git a/src/component/Admin/FileSystem/Icons/FileIcons.tsx b/src/component/Admin/FileSystem/Icons/FileIcons.tsx new file mode 100755 index 0000000..906b86a --- /dev/null +++ b/src/component/Admin/FileSystem/Icons/FileIcons.tsx @@ -0,0 +1,58 @@ +import { Box, Stack, Typography } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useCallback, useContext, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch } from "../../../../redux/hooks"; +import { SettingSection, SettingSectionContent } from "../../Settings/Settings"; +import { SettingContext } from "../../Settings/SettingWrapper"; +import EmojiList from "./EmojiList"; +import FileIconList from "./FileIconList"; + +const FileIcons = () => { + const { t } = useTranslation("dashboard"); + const { formRef, setSettings, values } = useContext(SettingContext); + const [loading, setLoading] = useState(false); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + + const iconOnChange = useCallback( + (s: string) => + setSettings({ + explorer_icons: s, + }), + [], + ); + + const onEmojiChange = useCallback( + (s: string) => + setSettings({ + emojis: s, + }), + [], + ); + + return ( + e.preventDefault()}> + + + + {t("settings.fileIcons")} + + + + + + + + {t("settings.emojiOptions")} + + + + + + + + ); +}; + +export default FileIcons; diff --git a/src/component/Admin/FileSystem/Parameters.tsx b/src/component/Admin/FileSystem/Parameters.tsx new file mode 100755 index 0000000..d0984e3 --- /dev/null +++ b/src/component/Admin/FileSystem/Parameters.tsx @@ -0,0 +1,610 @@ +import { DeleteOutline } from "@mui/icons-material"; +import { + Box, + Collapse, + FormControl, + FormControlLabel, + Link, + ListItemText, + Stack, + Switch, + Typography, +} from "@mui/material"; +import { useSnackbar } from "notistack"; +import * as React from "react"; +import { useCallback, useContext, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { sendClearBlobUrlCache } from "../../../api/api.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { isTrueVal } from "../../../session/utils.ts"; +import SizeInput from "../../Common/SizeInput.tsx"; +import { DefaultCloseAction } from "../../Common/Snackbar/snackbar.tsx"; +import { DenseFilledTextField, DenseSelect, SecondaryButton } from "../../Common/StyledComponents.tsx"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx"; +import SettingForm from "../../Pages/Setting/SettingForm.tsx"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings/Settings.tsx"; +import { SettingContext } from "../Settings/SettingWrapper.tsx"; + +const Parameters = () => { + const { t } = useTranslation("dashboard"); + const { formRef, setSettings, values } = useContext(SettingContext); + const [loading, setLoading] = useState(false); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + + const clearBlobUrlCache = () => { + setLoading(true); + dispatch(sendClearBlobUrlCache()) + .then(() => { + setLoading(false); + enqueueSnackbar(t("settings.cacheCleared"), { variant: "success", action: DefaultCloseAction }); + }) + .catch(() => { + setLoading(false); + }); + }; + + const onMimeMappingChange = useCallback((e: React.ChangeEvent) => { + setSettings({ + mime_mapping: e.target.value, + }); + }, []); + + return ( + e.preventDefault()}> + + + + {t("nav.fileSystem")} + + + + + + setSettings({ + maxEditSize: e.toString(), + }) + } + /> + {t("settings.textEditMaxSizeDes")} + + + + + + setSettings({ + cron_trash_bin_collect: e.target.value, + }) + } + required + /> + + ]} + /> + + + + + + + setSettings({ + cron_entity_collect: e.target.value, + }) + } + required + /> + + ]} + /> + + + + + + + setSettings({ + public_resource_maxage: e.target.value, + }) + } + required + /> + {t("settings.publicResourceMaxAgeDes")} + + + + + ( + + {v == "0" ? t("settings.offsetPagination") : t("settings.cursorPagination")} + + )} + onChange={(e) => + setSettings({ + use_cursor_pagination: e.target.value as string, + }) + } + MenuProps={{ + PaperProps: { sx: { maxWidth: 230 } }, + MenuListProps: { + sx: { + "& .MuiMenuItem-root": { + whiteSpace: "normal", + }, + }, + }, + }} + value={values.use_cursor_pagination} + > + + + + {t("settings.offsetPagination")} + + + {t("settings.offsetPaginationDes")} + + + + + + + {t("settings.cursorPagination")} + + + {t("settings.cursorPaginationDes")} + + + + + {t("settings.defaultPaginationDes")} + + + + + + setSettings({ + max_page_size: e.target.value, + }) + } + required + /> + {t("settings.maxPageSizeDes")} + + + + + + setSettings({ + max_batched_file: e.target.value, + }) + } + required + /> + {t("settings.maxBatchSizeDes")} + + + + + + setSettings({ + max_recursive_searched_folder: e.target.value, + }) + } + required + /> + {t("settings.maxRecursiveSearchDes")} + + + + + + setSettings({ + map_provider: e.target.value as string, + }) + } + value={values.map_provider} + > + + + {t("settings.mapGoogle")} + + + + + {t("settings.mapOpenStreetMap")} + + + + + {t("settings.mapboxMap")} + + + + {t("settings.mapProviderDes")} + + + + + + setSettings({ map_mapbox_ak: e.target.value })} + /> + + ]} + /> + + + + + + + + + setSettings({ + map_google_tile_type: e.target.value as string, + }) + } + value={values.map_google_tile_type} + > + + + {t("settings.tileTypeTerrain")} + + + + + {t("settings.tileTypeSatellite")} + + + + + {t("settings.tileTypeGeneral")} + + + + {t("settings.tileTypeDes")} + + + + + + + {t("settings.mimeMappingDes")} + + + + + + + {t("settings.searchQuery")} + + + + + setSettings({ + explorer_category_image_query: e.target.value, + }) + } + required + /> + + + + setSettings({ + explorer_category_video_query: e.target.value, + }) + } + required + /> + + + + setSettings({ + explorer_category_audio_query: e.target.value, + }) + } + required + /> + + + + setSettings({ + explorer_category_document_query: e.target.value, + }) + } + required + /> + + + + + + {t("settings.advanceOptions")} + + + + + setSettings({ + archive_timeout: e.target.value, + }) + } + required + /> + + + + setSettings({ + upload_session_timeout: e.target.value, + }) + } + required + /> + {t("settings.uploadSessionDes")} + + + + setSettings({ + slave_api_timeout: e.target.value, + }) + } + required + /> + {t("settings.slaveAPIExpirationDes")} + + + + setSettings({ + folder_props_timeout: e.target.value, + }) + } + required + /> + {t("settings.folderPropsTimeoutDes")} + + + + setSettings({ + chunk_retries: e.target.value, + }) + } + required + /> + {t("settings.failedChunkRetryDes")} + + + + + setSettings({ + use_temp_chunk_buffer: e.target.checked ? "1" : "0", + }) + } + /> + } + label={t("settings.cacheChunks")} + /> + {t("settings.cacheChunksDes")} + + + + + setSettings({ + max_parallel_transfer: e.target.value, + }) + } + required + /> + {t("settings.transitParallelNumDes")} + + + + setSettings({ + cron_oauth_cred_refresh: e.target.value, + }) + } + /> + + ]} + /> + + + + + setSettings({ + viewer_session_timeout: e.target.value, + }) + } + required + /> + {t("settings.wopiSessionTimeoutDes")} + + + + setSettings({ + entity_url_default_ttl: e.target.value, + }) + } + required + /> + {t("settings.fileBlobTimeoutDes")} + + + + setSettings({ + entity_url_cache_margin: e.target.value, + }) + } + required + /> + {t("settings.fileBlobMarginDes")} + + + + + } + variant="contained" + loading={loading} + color="primary" + onClick={clearBlobUrlCache} + > + {t("settings.clearBlobUrlCache")} + + + {t("settings.clearBlobUrlCacheDes")} + + + + + + + ); +}; + +export default Parameters; diff --git a/src/component/Admin/FileSystem/ViewerSetting/FileViewerEditDialog.tsx b/src/component/Admin/FileSystem/ViewerSetting/FileViewerEditDialog.tsx new file mode 100755 index 0000000..d6f30e2 --- /dev/null +++ b/src/component/Admin/FileSystem/ViewerSetting/FileViewerEditDialog.tsx @@ -0,0 +1,543 @@ +import { + DialogContent, + FormControlLabel, + IconButton, + Link, + ListItemText, + SelectChangeEvent, + Switch, + Table, + TableBody, + TableContainer, + TableHead, + TableRow, + useTheme, +} from "@mui/material"; +import FormControl from "@mui/material/FormControl"; +import Grid from "@mui/material/Grid2"; +import { useSnackbar } from "notistack"; +import React, { lazy, Suspense, useCallback, useEffect, useMemo, useState } from "react"; +import { DndProvider, useDrag, useDrop } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; +import { Trans, useTranslation } from "react-i18next"; +import { Viewer, ViewerPlatform, ViewerType } from "../../../../api/explorer.ts"; +import { builtInViewers } from "../../../../redux/thunks/viewer.ts"; +import { isTrueVal } from "../../../../session/utils.ts"; +import CircularProgress from "../../../Common/CircularProgress.tsx"; +import SizeInput from "../../../Common/SizeInput.tsx"; +import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx"; +import { + DenseFilledTextField, + DenseSelect, + NoWrapTableCell, + SecondaryButton, + StyledTableContainerPaper, +} from "../../../Common/StyledComponents.tsx"; +import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx"; +import { ViewerIDWithDefaultIcons } from "../../../FileManager/Dialogs/OpenWith.tsx"; +import Add from "../../../Icons/Add.tsx"; +import ArrowDown from "../../../Icons/ArrowDown.tsx"; +import Dismiss from "../../../Icons/Dismiss.tsx"; +import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; +import MagicVarDialog, { MagicVar } from "../../Common/MagicVarDialog.tsx"; +import { NoMarginHelperText } from "../../Settings/Settings.tsx"; + +const MonacoEditor = lazy(() => import("../../../Viewers/CodeViewer/MonacoEditor.tsx")); + +export interface FileViewerEditDialogProps { + viewer: Viewer; + onChange: (viewer: Viewer) => void; + open: boolean; + onClose: () => void; +} + +const magicVars: MagicVar[] = [ + { + name: "{$src}", + value: "settings.srcEncodedVar", + example: "https%3A%2F%2Fcloudreve.org%2Fapi%2Fv4%2Ffile%2Fcontent%2FzOie%2F0%2Ftext.txt%3Fsign%3Dxxx", + }, + { + name: "{$src_raw}", + value: "settings.srcVar", + example: "https://cloudreve.org/api/v4/file/content/zOie/0/text.txt?sign=xxx", + }, + { + name: "{$src_raw_base64}", + value: "settings.srcBase64Var", + example: "aHR0cHM6Ly9jbG91ZHJldmUub3JnL2FwaS92NC9maWxlL2NvbnRlbnQvek9pZS8wL3RleHQudHh0P3NpZ249eHh4", + }, + { + name: "{$name}", + value: "settings.nameEncodedVar", + example: "sampleFile%5B1%5D.txt", + }, + { + name: "{$version}", + value: "settings.versionEntityVar", + example: "zOie", + }, + { + name: "{$id}", + value: "settings.fileIdVar", + example: "jm8AF8", + }, + { + name: "{$user_id}", + value: "settings.userIdVar", + example: "lpua", + }, + { + name: "{$user_display_name}", + value: "settings.userDisplayNameVar", + example: "Aaron%20Liu", + }, +]; + +const DND_TYPE = "template-row"; + +interface DraggableTemplateRowProps { + t: any; + i: number; + moveRow: (from: number, to: number) => void; + onExtChange: (e: SelectChangeEvent, child: React.ReactNode) => void; + onNameChange: (e: React.ChangeEvent) => void; + onDelete: () => void; + isFirst: boolean; + isLast: boolean; + extList: string[]; + template: any; +} + +function DraggableTemplateRow({ + i, + moveRow, + onExtChange, + onNameChange, + onDelete, + isFirst, + isLast, + extList, + template, +}: DraggableTemplateRowProps) { + const ref = React.useRef(null); + const [, drop] = useDrop({ + accept: DND_TYPE, + hover(item: any, monitor) { + if (!ref.current) return; + + const dragIndex = item.index; + const hoverIndex = i; + if (dragIndex === hoverIndex) return; + + const hoverBoundingRect = ref.current.getBoundingClientRect(); + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; + const clientOffset = monitor.getClientOffset(); + if (!clientOffset) return; + + const hoverClientY = clientOffset.y - hoverBoundingRect.top; + + if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) return; + if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) return; + + moveRow(dragIndex, hoverIndex); + item.index = hoverIndex; + }, + }); + const [{ isDragging }, drag] = useDrag({ + type: DND_TYPE, + item: { index: i }, + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + }), + }); + drag(drop(ref)); + return ( + + + + {extList.map((ext) => ( + + {ext} + + ))} + + + + + + + + + + moveRow(i, i - 1)} disabled={isFirst}> + + + moveRow(i, i + 1)} disabled={isLast}> + + + + + ); +} + +const FileViewerEditDialog = ({ viewer, onChange, open, onClose }: FileViewerEditDialogProps) => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const { enqueueSnackbar } = useSnackbar(); + const [viewerShadowed, setViewerShadowed] = useState(undefined); + const formRef = React.useRef(null); + const [magicVarOpen, setMagicVarOpen] = useState(false); + const [wopiCached, setWopiCached] = useState(""); + const withDefaultIcon = useMemo(() => { + return ViewerIDWithDefaultIcons.includes(viewer.id); + }, [viewer.id]); + + useEffect(() => { + setViewerShadowed({ ...viewer }); + setWopiCached(""); + }, [viewer, setWopiCached, setViewerShadowed]); + + const onSubmit = useCallback(() => { + if (formRef.current && !formRef.current.checkValidity()) { + formRef.current.reportValidity(); + return; + } + + let changed = viewerShadowed; + if (!viewerShadowed || !changed) { + return; + } + + if (wopiCached != "") { + try { + const parsed = JSON.parse(wopiCached); + changed = { ...viewerShadowed, wopi_actions: parsed }; + setViewerShadowed({ ...changed }); + } catch (e) { + enqueueSnackbar({ + message: t("settings.invalidWopiActionMapping"), + variant: "warning", + action: DefaultCloseAction, + }); + return; + } + } + + onChange(changed); + onClose(); + }, [viewerShadowed, wopiCached, formRef]); + + const openMagicVar = useCallback((e: React.MouseEvent) => { + setMagicVarOpen(true); + e.stopPropagation(); + e.preventDefault(); + }, []); + + if (!viewerShadowed) { + return null; + } + + return ( + + + setMagicVarOpen(false)} /> +
    + + + { + setViewerShadowed((v) => ({ + ...(v as Viewer), + icon: e.target.value, + })); + }} + /> + {withDefaultIcon && {t("settings.builtInIconUrlDes")}} + + + { + setViewerShadowed((v) => ({ + ...(v as Viewer), + display_name: e.target.value, + })); + }} + /> + {t("settings.displayNameDes")} + + + + setViewerShadowed((v) => ({ + ...(v as Viewer), + exts: e.target.value.split(",").map((ext) => ext.trim()), + })) + } + /> + + {viewer.type == ViewerType.custom && ( + + + setViewerShadowed((v) => ({ + ...(v as Viewer), + url: e.target.value, + })) + } + /> + + ]} + /> + + + )} + + + + setViewerShadowed((v) => ({ + ...(v as Viewer), + max_size: e ? e : undefined, + })) + } + /> + {t("settings.maxSizeDes")} + + + + + + setViewerShadowed((v) => ({ + ...(v as Viewer), + platform: e.target.value as ViewerPlatform, + })) + } + > + + + {t("settings.viewerPlatformPC")} + + + + + {t("settings.viewerPlatformMobile")} + + + + + {t("settings.viewerPlatformAll")} + + + + {t("settings.viewerPlatformDes")} + + + + + setViewerShadowed((v) => ({ + ...(v as Viewer), + props: { + ...(v?.props ?? {}), + openInNew: e.target.checked.toString(), + }, + })) + } + /> + } + label={t("settings.openInNew")} + /> + {t("settings.openInNewDes")} + + {viewer.id == builtInViewers.drawio && ( + + + setViewerShadowed((v) => ({ + ...(v as Viewer), + props: { + ...(v?.props ?? {}), + host: e.target.value, + }, + })) + } + /> + {t("settings.drawioHostDes")} + + )} + {viewer.type == ViewerType.wopi && ( + + }> + setWopiCached(e as string)} + /> + + + )} + + {viewerShadowed?.templates && viewerShadowed.templates.length > 0 && ( + + + + + + {t("settings.ext")} + {t("settings.displayName")} + {t("settings.actions")} + + + + {viewerShadowed.templates?.map((template, i) => ( + { + if (from === to || to < 0 || to >= (viewerShadowed.templates?.length ?? 0)) return; + setViewerShadowed((v) => { + const arr = [...(v?.templates ?? [])]; + const [moved] = arr.splice(from, 1); + arr.splice(to, 0, moved); + return { ...(v as Viewer), templates: arr }; + }); + }} + onExtChange={(e) => { + const newExt = e.target.value as string; + setViewerShadowed((v) => ({ + ...(v as Viewer), + templates: (v?.templates ?? []).map((template, index) => + index == i ? { ...template, ext: newExt } : template, + ), + })); + }} + onNameChange={(e) => { + setViewerShadowed((v) => ({ + ...(v as Viewer), + templates: (v?.templates ?? []).map((template, index) => + index == i ? { ...template, display_name: e.target.value } : template, + ), + })); + }} + onDelete={() => { + setViewerShadowed((v) => ({ + ...(v as Viewer), + templates: (v?.templates ?? []).filter((_, index) => index != i), + })); + }} + isFirst={i === 0} + isLast={i === (viewerShadowed.templates?.length ?? 0) - 1} + extList={viewerShadowed.exts} + template={template} + /> + ))} + +
    +
    +
    + )} + } + onClick={() => + setViewerShadowed((v) => ({ + ...(v as Viewer), + templates: [...(v?.templates ?? []), { ext: viewerShadowed.exts?.[0] ?? "", display_name: "" }], + })) + } + > + {t("settings.addNewFileAction")} + + {t("settings.newFileActionDes")} +
    +
    +
    +
    +
    + ); +}; + +export default FileViewerEditDialog; diff --git a/src/component/Admin/FileSystem/ViewerSetting/FileViewerList.tsx b/src/component/Admin/FileSystem/ViewerSetting/FileViewerList.tsx new file mode 100755 index 0000000..c0e02b7 --- /dev/null +++ b/src/component/Admin/FileSystem/ViewerSetting/FileViewerList.tsx @@ -0,0 +1,343 @@ +import { ExpandMoreRounded } from "@mui/icons-material"; +import { + AccordionDetails, + Box, + Link, + ListItemIcon, + Menu, + Table, + TableBody, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; +import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import * as React from "react"; +import { memo, useCallback, useMemo, useState } from "react"; +import { DndProvider, useDrag, useDrop } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; +import { useTranslation } from "react-i18next"; +import { Viewer, ViewerGroup, ViewerType } from "../../../../api/explorer.ts"; +import { uuidv4 } from "../../../../util"; +import { NoWrapTableCell, SecondaryButton } from "../../../Common/StyledComponents.tsx"; +import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx"; +import Add from "../../../Icons/Add.tsx"; +import DesktopFlow from "../../../Icons/DesktopFlow.tsx"; +import DocumentDataLink from "../../../Icons/DocumentDataLink.tsx"; +import { AccordionSummary, StyledAccordion } from "../../Settings/UserSession/SSOSettings.tsx"; +import FileViewerEditDialog from "./FileViewerEditDialog.tsx"; +import FileViewerRow from "./FileViewerRow.tsx"; +import ImportWopiDialog from "./ImportWopiDialog.tsx"; + +interface ViewerGroupProps { + group: ViewerGroup; + index: number; + onDelete: (e: React.MouseEvent) => void; + onGroupChange: (g: ViewerGroup) => void; + dndType: string; +} + +const DND_TYPE = "viewer-row"; + +const DraggableViewerRow = memo(function DraggableViewerRow({ + viewer, + index, + moveRow, + onChange, + onDelete, + onMoveUp, + onMoveDown, + isLast, + isFirst, + dndType, +}: any) { + const ref = React.useRef(null); + const [, drop] = useDrop({ + accept: dndType, + hover(item: any, monitor) { + if (!ref.current) return; + + const dragIndex = item.index; + const hoverIndex = index; + if (dragIndex === hoverIndex) return; + + const hoverBoundingRect = ref.current.getBoundingClientRect(); + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; + const clientOffset = monitor.getClientOffset(); + if (!clientOffset) return; + + const hoverClientY = clientOffset.y - hoverBoundingRect.top; + + if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) return; + if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) return; + + moveRow(dragIndex, hoverIndex); + item.index = hoverIndex; + }, + }); + const [{ isDragging }, drag] = useDrag({ + type: dndType, + item: { index }, + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + }), + }); + drag(drop(ref)); + return ( + + ); +}); + +const ViewerGroupRow = memo(({ group, index, onDelete, onGroupChange, dndType }: ViewerGroupProps) => { + const { t } = useTranslation("dashboard"); + + const onViewerChange = useMemo(() => { + return group.viewers.map((_, index) => (vChanged: Viewer) => { + onGroupChange({ + viewers: group.viewers.map((v, i) => (i == index ? vChanged : v)), + }); + }); + }, [group.viewers]); + + const onViewerDeleted = useMemo(() => { + return group.viewers.map((_, index) => (e: React.MouseEvent) => { + onGroupChange({ + viewers: group.viewers.filter((_, i) => i != index), + }); + e.preventDefault(); + e.stopPropagation(); + }); + }, [group.viewers]); + + const [viewers, setViewers] = useState(group.viewers); + React.useEffect(() => { + setViewers(group.viewers); + }, [group.viewers]); + const moveRow = useCallback( + (from: number, to: number) => { + if (from === to) return; + const updated = [...viewers]; + const [moved] = updated.splice(from, 1); + updated.splice(to, 0, moved); + setViewers(updated); + onGroupChange({ viewers: updated }); + }, + [viewers, onGroupChange], + ); + const handleMoveUp = (idx: number) => { + if (idx <= 0) return; + moveRow(idx, idx - 1); + }; + const handleMoveDown = (idx: number) => { + if (idx >= viewers.length - 1) return; + moveRow(idx, idx + 1); + }; + + return ( + + }> + + {t("settings.viewerGroupTitle", { index: index + 1 })} + {index > 0 && ( + + {t("policy.delete")} + + )} + + + + + + + + + {t("settings.icon")} + {t("settings.viewerType")} + {t("settings.displayName")} + {t("settings.exts")} + {t("settings.viewerPlatform")} + {t("settings.newFileAction")} + {t("settings.viewerEnabled")} + {t("settings.actions")} + + + + + {viewers.map((viewer, idx) => ( + handleMoveUp(idx)} + onMoveDown={() => handleMoveDown(idx)} + isFirst={idx === 0} + isLast={idx === viewers.length - 1} + dndType={dndType} + /> + ))} + +
    +
    +
    +
    +
    + ); +}); + +export interface FileViewerListProps { + config: string; + onChange: (value: string) => void; +} + +const FileViewerList = memo(({ config, onChange }: FileViewerListProps) => { + const { t } = useTranslation("dashboard"); + const addNewPopupState = usePopupState({ + variant: "popover", + popupId: "addNewViewer", + }); + const [createNewOpen, setCreateNewOpen] = useState(false); + const [newViewer, setNewViewer] = useState(undefined); + const [importOpen, setImportOpen] = useState(false); + + const configParsed = useMemo((): ViewerGroup[] => JSON.parse(config), [config]); + + const onNewViewerChange = useCallback( + (v: Viewer) => { + setNewViewer(v); + const newViewerSetting = [...configParsed]; + newViewerSetting[0].viewers.push(v); + onChange(JSON.stringify(newViewerSetting)); + }, + [configParsed], + ); + + const onGroupDelete = useMemo(() => { + return configParsed.map((_, index) => (e: React.MouseEvent) => { + onChange(JSON.stringify([...configParsed].filter((_, i) => i != index))); + e.preventDefault(); + e.stopPropagation(); + }); + }, [configParsed]); + + const onGroupChange = useMemo(() => { + return configParsed.map((_, index) => (g: ViewerGroup) => { + onChange(JSON.stringify([...configParsed].map((item, i) => (i == index ? g : item)))); + }); + }, [configParsed]); + + const { onClose, ...menuProps } = bindMenu(addNewPopupState); + + const onCreateNewClosed = useCallback(() => { + setCreateNewOpen(false); + }, []); + + const openCreateNew = useCallback(() => { + setNewViewer({ + id: uuidv4(), + icon: "", + type: ViewerType.custom, + display_name: "", + exts: [], + }); + setCreateNewOpen(true); + onClose(); + }, [onClose, setNewViewer]); + + const openImportNew = useCallback(() => { + setImportOpen(true); + onClose(); + }, [onClose, setImportOpen]); + + const onImportedNew = useCallback( + (v: ViewerGroup) => { + const newViewerSetting = [...configParsed]; + newViewerSetting.push(v); + onChange(JSON.stringify(newViewerSetting)); + }, + [configParsed], + ); + + return ( + + } sx={{ mb: 1 }}> + {t("settings.addViewer")} + + {configParsed?.length > 0 && + configParsed.map((item: ViewerGroup, index) => ( + + ))} + + + + + + {t("settings.embeddedWebpageViewer")} + + + + + + {t("settings.wopiViewer")} + + + {newViewer && ( + + )} + setImportOpen(false)} open={importOpen} /> + + ); +}); + +export default FileViewerList; diff --git a/src/component/Admin/FileSystem/ViewerSetting/FileViewerRow.tsx b/src/component/Admin/FileSystem/ViewerSetting/FileViewerRow.tsx new file mode 100755 index 0000000..68780ff --- /dev/null +++ b/src/component/Admin/FileSystem/ViewerSetting/FileViewerRow.tsx @@ -0,0 +1,151 @@ +import * as React from "react"; +import { useCallback, useState } from "react"; +import { Viewer, ViewerPlatform, ViewerType } from "../../../../api/explorer.ts"; +import { useTranslation } from "react-i18next"; +import { IconButton, ListItemText, TableRow } from "@mui/material"; +import { DenseFilledTextField, DenseSelect, NoWrapCell, StyledCheckbox } from "../../../Common/StyledComponents.tsx"; +import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx"; +import { ViewerIcon } from "../../../FileManager/Dialogs/OpenWith.tsx"; +import Dismiss from "../../../Icons/Dismiss.tsx"; +import Edit from "../../../Icons/Edit.tsx"; +import FileViewerEditDialog from "./FileViewerEditDialog.tsx"; +import ArrowDown from "../../../Icons/ArrowDown.tsx"; + +export interface FileViewerRowProps { + viewer: Viewer; + onChange: (viewer: Viewer) => void; + onDelete: (e: React.MouseEvent) => void; + onMoveUp?: () => void; + onMoveDown?: () => void; + isFirst?: boolean; + isLast?: boolean; + style?: React.CSSProperties; +} + +const FileViewerRow = React.memo( + React.forwardRef( + ({ viewer, onChange, onDelete, onMoveUp, onMoveDown, isFirst, isLast, style }, ref) => { + const { t } = useTranslation("dashboard"); + const [extCached, setExtCached] = useState(""); + const [editOpen, setEditOpen] = useState(false); + const onClose = useCallback(() => { + setEditOpen(false); + }, [setEditOpen]); + return ( + + + + + + {t(`settings.${viewer.type}ViewerType`)} + + {t(viewer.display_name, { + ns: "application", + })} + + + { + onChange({ + ...viewer, + exts: extCached == "" ? viewer.exts : extCached?.split(",")?.map((ext) => ext.trim()), + }); + setExtCached(""); + }} + onChange={(e) => setExtCached(e.target.value)} + /> + + + + onChange({ + ...viewer, + platform: e.target.value as ViewerPlatform, + }) + } + > + + + {t("settings.viewerPlatformPC")} + + + + + {t("settings.viewerPlatformMobile")} + + + + + {t("settings.viewerPlatformAll")} + + + + + + {viewer.templates?.length ? t("settings.nMapping", { num: viewer.templates?.length }) : t("share.none")} + + + + onChange({ + ...viewer, + disabled: !e.target.checked, + }) + } + /> + + + setEditOpen(true)}> + + + {viewer.type != ViewerType.builtin && ( + + + + )} + + + + + + + + + + + ); + }, + ), +); + +export default FileViewerRow; diff --git a/src/component/Admin/FileSystem/ViewerSetting/ImportWopiDialog.tsx b/src/component/Admin/FileSystem/ViewerSetting/ImportWopiDialog.tsx new file mode 100755 index 0000000..7701fa3 --- /dev/null +++ b/src/component/Admin/FileSystem/ViewerSetting/ImportWopiDialog.tsx @@ -0,0 +1,72 @@ +import { DialogContent, Link } from "@mui/material"; +import { useCallback, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { getWopiDiscovery } from "../../../../api/api.ts"; +import { ViewerGroup } from "../../../../api/explorer.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import { DenseFilledTextField } from "../../../Common/StyledComponents.tsx"; +import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; +import { Code } from "../../../Common/Code.tsx"; +import { NoMarginHelperText } from "../../Settings/Settings.tsx"; + +export interface ImportWopiDialogProps { + open: boolean; + onClose: () => void; + onImported: (v: ViewerGroup) => void; +} + +const ImportWopiDialog = ({ open, onClose, onImported }: ImportWopiDialogProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [endpoint, setEndpoint] = useState(""); + const [loading, setLoading] = useState(false); + + const onSubmit = useCallback(() => { + setLoading(true); + dispatch( + getWopiDiscovery({ + endpoint, + }), + ) + .then((res) => { + onImported(res); + onClose(); + }) + .finally(() => { + setLoading(false); + }); + }, [endpoint, onClose, onImported]); + + return ( + + + + setEndpoint(e.target.value)} /> + + , ]} + /> + + + + + ); +}; + +export default ImportWopiDialog; diff --git a/src/component/Admin/FileSystem/ViewerSetting/ViewerSetting.tsx b/src/component/Admin/FileSystem/ViewerSetting/ViewerSetting.tsx new file mode 100755 index 0000000..b4f10cf --- /dev/null +++ b/src/component/Admin/FileSystem/ViewerSetting/ViewerSetting.tsx @@ -0,0 +1,33 @@ +import { Box, Stack } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useCallback, useContext, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch } from "../../../../redux/hooks"; +import { SettingContext } from "../../Settings/SettingWrapper"; +import FileViewerList from "./FileViewerList"; + +const ViewerSetting = () => { + const { t } = useTranslation("dashboard"); + const { formRef, setSettings, values } = useContext(SettingContext); + const [loading, setLoading] = useState(false); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + + const viewerOnChange = useCallback( + (s: string) => + setSettings({ + file_viewers: s, + }), + [], + ); + + return ( + e.preventDefault()}> + + + + + ); +}; + +export default ViewerSetting; diff --git a/src/component/Admin/Group/EditGroup/BasicInfoSection.tsx b/src/component/Admin/Group/EditGroup/BasicInfoSection.tsx new file mode 100755 index 0000000..e760653 --- /dev/null +++ b/src/component/Admin/Group/EditGroup/BasicInfoSection.tsx @@ -0,0 +1,116 @@ +import { Alert, FormControl, FormControlLabel, Switch, Typography } from "@mui/material"; +import { useCallback, useContext, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { GroupEnt, StoragePolicy } from "../../../../api/dashboard"; +import { GroupPermission } from "../../../../api/user"; +import Boolset from "../../../../util/boolset"; +import SizeInput from "../../../Common/SizeInput"; +import { DenseFilledTextField } from "../../../Common/StyledComponents"; +import InPrivate from "../../../Icons/InPrivate"; +import SettingForm, { ProChip } from "../../../Pages/Setting/SettingForm"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../../Settings/Settings"; +import { AnonymousGroupID } from "../GroupRow"; +import { GroupSettingContext } from "./GroupSettingWrapper"; +import PolicySelectionInput from "./PolicySelectionInput"; +const BasicInfoSection = () => { + const { t } = useTranslation("dashboard"); + const { values, setGroup } = useContext(GroupSettingContext); + + const permission = useMemo(() => { + return new Boolset(values.permissions ?? ""); + }, [values.permissions]); + + const onNameChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ ...p, name: e.target.value })); + }, + [setGroup], + ); + + const onPolicyChange = useCallback( + (value: number) => { + setGroup((p: GroupEnt) => ({ + ...p, + edges: { ...p.edges, storage_policies: { id: value } as StoragePolicy }, + })); + }, + [setGroup], + ); + + const onMaxStorageChange = useCallback( + (size: number) => { + setGroup((p: GroupEnt) => ({ + ...p, + max_storage: size ? size : undefined, + })); + }, + [setGroup], + ); + + const onIsAdminChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + permissions: new Boolset(p.permissions).set(GroupPermission.is_admin, e.target.checked).toString(), + })); + }, + [setGroup], + ); + + return ( + + + {t("policy.basicInfo")} + + + {values?.id == AnonymousGroupID && ( + + } severity="info"> + {t("group.anonymousHint")} + + + )} + + + + {t("group.nameOfGroupDes")} + + + {values?.id != AnonymousGroupID && ( + <> + + + {t("group.availablePoliciesDes")} + + {t("group.availablePolicyDesPro")} + + + + + + {t("group.initialStorageQuotaDes")} + + + + + } + label={t("group.isAdmin")} + /> + {t("group.isAdminDes")} + + + + )} + + + ); +}; + +export default BasicInfoSection; diff --git a/src/component/Admin/Group/EditGroup/EditGroup.tsx b/src/component/Admin/Group/EditGroup/EditGroup.tsx new file mode 100755 index 0000000..cf8307e --- /dev/null +++ b/src/component/Admin/Group/EditGroup/EditGroup.tsx @@ -0,0 +1,27 @@ +import { Container } from "@mui/material"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useParams } from "react-router-dom"; +import PageContainer from "../../../Pages/PageContainer"; +import PageHeader from "../../../Pages/PageHeader"; +import GroupForm from "./GroupForm"; +import GroupSettingWrapper from "./GroupSettingWrapper"; + +const EditGroup = () => { + const { t } = useTranslation("dashboard"); + const { id } = useParams(); + const [name, setName] = useState(""); + + return ( + + + + setName(p.name)}> + + + + + ); +}; + +export default EditGroup; diff --git a/src/component/Admin/Group/EditGroup/FileManagementSection.tsx b/src/component/Admin/Group/EditGroup/FileManagementSection.tsx new file mode 100755 index 0000000..d0a5574 --- /dev/null +++ b/src/component/Admin/Group/EditGroup/FileManagementSection.tsx @@ -0,0 +1,388 @@ +import { + Box, + CircularProgress, + Collapse, + FormControl, + FormControlLabel, + Link, + Stack, + Switch, + Typography, + useTheme, +} from "@mui/material"; +import { lazy, Suspense, useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Link as RouterLink } from "react-router-dom"; +import { GroupEnt } from "../../../../api/dashboard"; +import { GroupPermission } from "../../../../api/user"; +import Boolset from "../../../../util/boolset"; +import SizeInput from "../../../Common/SizeInput"; +import { DenseFilledTextField } from "../../../Common/StyledComponents"; +import SettingForm, { ProChip } from "../../../Pages/Setting/SettingForm"; +import ProDialog from "../../Common/ProDialog"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../../Settings/Settings"; +import { AnonymousGroupID } from "../GroupRow"; +import { GroupSettingContext } from "./GroupSettingWrapper"; +import MultipleNodeSelectionInput from "./MultipleNodeSelectionInput"; + +const MonacoEditor = lazy(() => import("../../../Viewers/CodeViewer/MonacoEditor")); + +const FileManagementSection = () => { + const { t } = useTranslation("dashboard"); + const { values, setGroup } = useContext(GroupSettingContext); + const [proOpen, setProOpen] = useState(false); + const theme = useTheme(); + + const [editedConfig, setEditedConfig] = useState(""); + + const permission = useMemo(() => { + return new Boolset(values.permissions ?? ""); + }, [values.permissions]); + + useEffect(() => { + setEditedConfig( + values.settings?.remote_download_options ? JSON.stringify(values.settings?.remote_download_options, null, 2) : "", + ); + }, [values.settings?.remote_download_options]); + + const onAllowWabDAVChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + permissions: new Boolset(p.permissions).set(GroupPermission.webdav, e.target.checked).toString(), + })); + }, + [setGroup], + ); + + const onAllowWabDAVProxyChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + permissions: new Boolset(p.permissions).set(GroupPermission.webdav_proxy, e.target.checked).toString(), + })); + }, + [setGroup], + ); + + const onAllowCompressTaskChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + permissions: new Boolset(p.permissions).set(GroupPermission.archive_task, e.target.checked).toString(), + })); + }, + [setGroup], + ); + + const onCompressSizeChange = useCallback( + (e: number) => { + setGroup((p: GroupEnt) => ({ ...p, settings: { ...p.settings, compress_size: e ? e : undefined } })); + }, + [setGroup], + ); + + const onDecompressSizeChange = useCallback( + (e: number) => { + setGroup((p: GroupEnt) => ({ ...p, settings: { ...p.settings, decompress_size: e ? e : undefined } })); + }, + [setGroup], + ); + + const onAllowRemoteDownloadChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + permissions: new Boolset(p.permissions).set(GroupPermission.remote_download, e.target.checked).toString(), + })); + }, + [setGroup], + ); + + const onEditedConfigBlur = useCallback( + (value: string) => { + var res: Record | undefined = undefined; + if (value) { + try { + res = JSON.parse(value); + } catch (e) { + console.error(e); + } + } + setGroup((p: GroupEnt) => ({ ...p, settings: { ...p.settings, remote_download_options: res } })); + }, + [editedConfig, setGroup], + ); + + const onAria2BatchSizeChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + settings: { ...p.settings, aria2_batch: parseInt(e.target.value) ? parseInt(e.target.value) : undefined }, + })); + }, + [setGroup], + ); + + const onAllowAdvanceDeleteChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + permissions: new Boolset(p.permissions).set(GroupPermission.advance_delete, e.target.checked).toString(), + })); + }, + [setGroup], + ); + + const onMaxWalkedFilesChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + settings: { ...p.settings, max_walked_files: parseInt(e.target.value) ? parseInt(e.target.value) : undefined }, + })); + }, + [setGroup], + ); + + const onTrashBinDurationChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + settings: { ...p.settings, trash_retention: parseInt(e.target.value) ? parseInt(e.target.value) : undefined }, + })); + }, + [setGroup], + ); + + const onProClick = useCallback((e: React.MouseEvent) => { + e.stopPropagation(); + setProOpen(true); + }, []); + + return ( + + setProOpen(false)} /> + + {t("group.fileManagement")} + + + {values?.id != AnonymousGroupID && ( + <> + + + + } + label={t("group.allowWabDAV")} + /> + {t("group.allowWabDAVDes")} + + + + + + + } + label={t("group.allowWabDAVProxy")} + /> + {t("group.allowWabDAVProxyDes")} + + + + + + } + label={ + + {t("group.migratePolicy")} + + + } + /> + {t("group.migratePolicyDes")} + + + + + + } + label={t("group.compressTask")} + /> + {t("group.compressTaskDes")} + + + + + + + + {t("group.compressSizeDes")} + + + + + + {t("group.decompressSizeDes")} + + + + + + + + } + label={t("group.allowRemoteDownload")} + /> + + ]} + /> + + + + + + + + }> + setEditedConfig(value || "")} + onBlur={onEditedConfigBlur} + height="200px" + minHeight="200px" + options={{ + wordWrap: "on", + minimap: { enabled: false }, + scrollBeyondLastLine: false, + }} + /> + + {t("group.aria2OptionsDes")} + + + + + + {t("group.aria2BatchSizeDes")} + + + + + + + + } + label={t("group.advanceDelete")} + /> + {t("group.advanceDeleteDes")} + + + + + + {t("group.allowedNodesDes")} + + + + + } + label={ + + {t("group.allowSelectNode")} + + + } + /> + {t("group.allowSelectNodeDes")} + + + + )} + + + + + {t("group.maxWalkedFilesDes")} + + + {values?.id != AnonymousGroupID && ( + + + + {t("group.trashBinDurationDes")} + + + )} + + + ); +}; + +export default FileManagementSection; diff --git a/src/component/Admin/Group/EditGroup/GroupForm.tsx b/src/component/Admin/Group/EditGroup/GroupForm.tsx new file mode 100755 index 0000000..5fbfce3 --- /dev/null +++ b/src/component/Admin/Group/EditGroup/GroupForm.tsx @@ -0,0 +1,25 @@ +import { Box, Stack } from "@mui/material"; +import { useContext } from "react"; +import { useTranslation } from "react-i18next"; +import BasicInfoSection from "./BasicInfoSection"; +import FileManagementSection from "./FileManagementSection"; +import { GroupSettingContext } from "./GroupSettingWrapper"; +import ShareSection from "./ShareSection"; +import UploadDownloadSection from "./UploadDownloadSection"; + +const GroupForm = () => { + const { t } = useTranslation("dashboard"); + const { formRef, values } = useContext(GroupSettingContext); + return ( + e.preventDefault()}> + + + + + + + + ); +}; + +export default GroupForm; diff --git a/src/component/Admin/Group/EditGroup/GroupSettingWrapper.tsx b/src/component/Admin/Group/EditGroup/GroupSettingWrapper.tsx new file mode 100755 index 0000000..1658f81 --- /dev/null +++ b/src/component/Admin/Group/EditGroup/GroupSettingWrapper.tsx @@ -0,0 +1,145 @@ +import { Box } from "@mui/material"; +import * as React from "react"; +import { createContext, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { getGroupDetail, upsertGroup } from "../../../../api/api.ts"; +import { GroupEnt, StoragePolicy } from "../../../../api/dashboard.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import FacebookCircularProgress from "../../../Common/CircularProgress.tsx"; +import { SavingFloat } from "../../Settings/SettingWrapper.tsx"; + +export interface GroupSettingWrapperProps { + groupID: number; + children: React.ReactNode; + onGroupChange: (group: GroupEnt) => void; +} + +export interface GroupSettingContextProps { + values: GroupEnt; + setGroup: (f: (p: GroupEnt) => GroupEnt) => void; + formRef?: React.RefObject; +} + +const defaultGroup: GroupEnt = { + id: 0, + name: "", + edges: {}, +}; + +export const GroupSettingContext = createContext({ + values: { ...defaultGroup }, + setGroup: () => {}, +}); + +const groupValueFilter = (group: GroupEnt): GroupEnt => { + return { + ...group, + edges: { + storage_policies: { + id: group.edges.storage_policies?.id ?? 0, + } as StoragePolicy, + }, + }; +}; + +const GroupSettingWrapper = ({ groupID, children, onGroupChange }: GroupSettingWrapperProps) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation("dashboard"); + const [values, setValues] = useState({ + ...defaultGroup, + }); + const [modifiedValues, setModifiedValues] = useState({ + ...defaultGroup, + }); + const [loading, setLoading] = useState(true); + const [submitting, setSubmitting] = useState(false); + const formRef = useRef(null); + + const showSaveButton = useMemo(() => { + return JSON.stringify(modifiedValues) !== JSON.stringify(values); + }, [modifiedValues, values]); + + useEffect(() => { + setLoading(true); + dispatch(getGroupDetail(groupID)) + .then((res) => { + setValues(groupValueFilter(res)); + setModifiedValues(groupValueFilter(res)); + onGroupChange(groupValueFilter(res)); + }) + .finally(() => { + setLoading(false); + }); + }, [groupID]); + + const revert = () => { + setModifiedValues(values); + }; + + const submit = () => { + if (formRef.current) { + if (!formRef.current.checkValidity()) { + formRef.current.reportValidity(); + return; + } + } + + setSubmitting(true); + dispatch( + upsertGroup({ + group: { ...modifiedValues }, + }), + ) + .then((res) => { + setValues(groupValueFilter(res)); + setModifiedValues(groupValueFilter(res)); + onGroupChange(groupValueFilter(res)); + }) + .finally(() => { + setSubmitting(false); + }); + }; + + return ( + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${loading}`} + > + + {loading && ( + + + + )} + {!loading && ( + + {children} + + + )} + + + + + ); +}; + +export default GroupSettingWrapper; diff --git a/src/component/Admin/Group/EditGroup/MultipleNodeSelectionInput.tsx b/src/component/Admin/Group/EditGroup/MultipleNodeSelectionInput.tsx new file mode 100755 index 0000000..2be4da1 --- /dev/null +++ b/src/component/Admin/Group/EditGroup/MultipleNodeSelectionInput.tsx @@ -0,0 +1,41 @@ +import { ListItemText } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch } from "../../../../redux/hooks"; +import { DenseSelect } from "../../../Common/StyledComponents"; + +const MultipleNodeSelectionInput = () => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + + return ( + { + return ( + {t("group.allNodes")}} + slotProps={{ + primary: { color: "textSecondary", variant: "body2" }, + }} + /> + ); + }} + > + ); +}; + +export default MultipleNodeSelectionInput; diff --git a/src/component/Admin/Group/EditGroup/PolicySelectionInput.tsx b/src/component/Admin/Group/EditGroup/PolicySelectionInput.tsx new file mode 100755 index 0000000..ad85832 --- /dev/null +++ b/src/component/Admin/Group/EditGroup/PolicySelectionInput.tsx @@ -0,0 +1,102 @@ +import { Box, FormControl, SelectChangeEvent, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getStoragePolicyList } from "../../../../api/api"; +import { StoragePolicy } from "../../../../api/dashboard"; +import { useAppDispatch } from "../../../../redux/hooks"; +import FacebookCircularProgress from "../../../Common/CircularProgress"; +import { DenseSelect, SquareChip } from "../../../Common/StyledComponents"; +import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu"; +export interface PolicySelectionInputProps { + value: number; + onChange: (value: number) => void; +} + +const PolicySelectionInput = ({ value, onChange }: PolicySelectionInputProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [policies, setPolicies] = useState([]); + const [loading, setLoading] = useState(false); + const [policyMap, setPolicyMap] = useState>({}); + + const handleChange = (event: SelectChangeEvent) => { + const { + target: { value }, + } = event; + onChange(value as number); + }; + + useEffect(() => { + setLoading(true); + dispatch(getStoragePolicyList({ page: 1, page_size: 1000, order_by: "id", order_direction: "asc" })) + .then((res) => { + setPolicies(res.policies); + setPolicyMap( + res.policies.reduce( + (acc, policy) => { + acc[policy.id] = policy; + return acc; + }, + {} as Record, + ), + ); + }) + .finally(() => { + setLoading(false); + }); + }, []); + + return ( + + ( + + {!loading ? ( + + ) : ( + + )} + + )} + > + {policies.length > 0 && + policies.map((policy) => ( + + + + {policy.name} + + + {t(`policy.${policy.type}`)} + + + + ))} + + + ); +}; + +export default PolicySelectionInput; diff --git a/src/component/Admin/Group/EditGroup/ShareSection.tsx b/src/component/Admin/Group/EditGroup/ShareSection.tsx new file mode 100755 index 0000000..840cd5f --- /dev/null +++ b/src/component/Admin/Group/EditGroup/ShareSection.tsx @@ -0,0 +1,113 @@ +import { Box, FormControl, FormControlLabel, Switch, Typography } from "@mui/material"; +import { useCallback, useContext, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { GroupEnt } from "../../../../api/dashboard"; +import { GroupPermission } from "../../../../api/user"; +import Boolset from "../../../../util/boolset"; +import SettingForm, { ProChip } from "../../../Pages/Setting/SettingForm"; +import ProDialog from "../../Common/ProDialog"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../../Settings/Settings"; +import { AnonymousGroupID } from "../GroupRow"; +import { GroupSettingContext } from "./GroupSettingWrapper"; + +const ShareSection = () => { + const { t } = useTranslation("dashboard"); + const { values, setGroup } = useContext(GroupSettingContext); + const [proOpen, setProOpen] = useState(false); + + const permission = useMemo(() => { + return new Boolset(values.permissions ?? ""); + }, [values.permissions]); + + const onAllowCreateShareLinkChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + permissions: new Boolset(p.permissions).set(GroupPermission.share, e.target.checked).toString(), + })); + }, + [setGroup], + ); + + const onShareDownloadChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + permissions: new Boolset(p.permissions).set(GroupPermission.share_download, e.target.checked).toString(), + })); + }, + [setGroup], + ); + + const onProClick = (e: React.MouseEvent) => { + e.stopPropagation(); + setProOpen(true); + }; + + return ( + + setProOpen(false)} /> + + {t("group.share")} + + + {values?.id != AnonymousGroupID && ( + + + + } + label={t("group.allowCreateShareLink")} + /> + {t("group.allowCreateShareLinkDes")} + + + )} + + + } + label={ + + {t("group.shareFree")} + + + } + /> + {t("group.shareFreeDes")} + + + + + + } + label={t("group.allowDownloadShare")} + /> + {t("group.allowDownloadShareDes")} + + + {values?.id != AnonymousGroupID && ( + + + } + label={ + + {t("group.esclateAnonymity")} + + + } + /> + {t("group.esclateAnonymityDes")} + + + )} + + + ); +}; + +export default ShareSection; diff --git a/src/component/Admin/Group/EditGroup/UploadDownloadSection.tsx b/src/component/Admin/Group/EditGroup/UploadDownloadSection.tsx new file mode 100755 index 0000000..113eb40 --- /dev/null +++ b/src/component/Admin/Group/EditGroup/UploadDownloadSection.tsx @@ -0,0 +1,182 @@ +import { Collapse, FormControl, FormControlLabel, Stack, Switch, Typography } from "@mui/material"; +import { useCallback, useContext, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { GroupEnt } from "../../../../api/dashboard"; +import { GroupPermission } from "../../../../api/user"; +import Boolset from "../../../../util/boolset"; +import SizeInput from "../../../Common/SizeInput"; +import { DenseFilledTextField } from "../../../Common/StyledComponents"; +import SettingForm from "../../../Pages/Setting/SettingForm"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../../Settings/Settings"; +import { AnonymousGroupID } from "../GroupRow"; +import { GroupSettingContext } from "./GroupSettingWrapper"; + +const UploadDownloadSection = () => { + const { t } = useTranslation("dashboard"); + const { values, setGroup } = useContext(GroupSettingContext); + + const permission = useMemo(() => { + return new Boolset(values.permissions ?? ""); + }, [values.permissions]); + + const onAllowArchiveDownloadChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + permissions: new Boolset(p.permissions).set(GroupPermission.archive_download, e.target.checked).toString(), + })); + }, + [setGroup], + ); + + const onAllowDirectLinkChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + settings: { + ...p.settings, + source_batch: e.target.checked ? 1 : 0, + }, + })); + }, + [setGroup], + ); + + const onSourceBatchChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + settings: { ...p.settings, source_batch: parseInt(e.target.value) ? parseInt(e.target.value) : undefined }, + })); + }, + [setGroup], + ); + + const onRedirectedSourceChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + settings: { ...p.settings, redirected_source: e.target.checked ? true : undefined }, + })); + }, + [setGroup], + ); + + const onDownloadSpeedLimitChange = useCallback( + (e: number) => { + setGroup((p: GroupEnt) => ({ ...p, speed_limit: e ? e : undefined })); + }, + [setGroup], + ); + + const onReuseDirectLinkChange = useCallback( + (e: React.ChangeEvent) => { + setGroup((p: GroupEnt) => ({ + ...p, + permissions: new Boolset(p.permissions).set(GroupPermission.unique_direct_link, !e.target.checked).toString(), + })); + }, + [setGroup], + ); + + return ( + + + {t("group.uploadDownload")} + + + + + + } + label={t("group.serverSideBatchDownload")} + /> + {t("group.serverSideBatchDownloadDes")} + + + {values?.id != AnonymousGroupID && ( + <> + + + 0} onChange={onAllowDirectLinkChange} /> + } + label={t("group.getDirectLink")} + /> + {t("group.getDirectLinkDes")} + + + 0} unmountOnExit> + + + + + {t("group.bathSourceLinkLimitDes")} + + + + + + } + label={t("group.redirectedSource")} + /> + {t("group.redirectedSourceDes")} + + + + + + + + } + label={t("group.reuseDirectLink")} + /> + {t("group.reuseDirectLinkDes")} + + + + + + )} + + + + {t("group.downloadSpeedLimitDes")} + + + + + ); +}; + +export default UploadDownloadSection; diff --git a/src/component/Admin/Group/GroupRow.tsx b/src/component/Admin/Group/GroupRow.tsx new file mode 100755 index 0000000..be51218 --- /dev/null +++ b/src/component/Admin/Group/GroupRow.tsx @@ -0,0 +1,179 @@ +import { Box, IconButton, Link, Skeleton, TableRow, Tooltip } from "@mui/material"; +import { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link as RouterLink, useNavigate } from "react-router-dom"; +import { deleteGroup, getGroupDetail } from "../../../api/api"; +import { GroupEnt } from "../../../api/dashboard"; +import { GroupPermission } from "../../../api/user"; +import { useAppDispatch } from "../../../redux/hooks"; +import { confirmOperation } from "../../../redux/thunks/dialog"; +import { sizeToString } from "../../../util"; +import Boolset from "../../../util/boolset"; +import { NoWrapBox, NoWrapTableCell, NoWrapTypography, SquareChip } from "../../Common/StyledComponents"; +import Delete from "../../Icons/Delete"; +import InPrivate from "../../Icons/InPrivate"; +import PersonPasskey from "../../Icons/PersonPasskey"; +import Shield from "../../Icons/Shield"; + +export interface GroupRowProps { + group?: GroupEnt; + loading?: boolean; + onDelete?: () => void; +} + +export const AnonymousGroupID = 3; + +const GroupRow = ({ group, loading, onDelete }: GroupRowProps) => { + const navigate = useNavigate(); + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [userCount, setUserCount] = useState(undefined); + const [countLoading, setCountLoading] = useState(false); + const [deleteLoading, setDeleteLoading] = useState(false); + + const onPolicyClick = + (policyId: number): ((e: React.MouseEvent) => void) => + (e) => { + e.stopPropagation(); + navigate(`/admin/policy/${policyId}`); + }; + + const onRowClick = () => { + navigate(`/admin/group/${group?.id}`); + }; + + const groupBs = useMemo(() => { + return new Boolset(group?.permissions); + }, [group?.permissions]); + + const onCountClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + setCountLoading(true); + dispatch(getGroupDetail(group?.id ?? 0, true)) + .then((res) => { + setUserCount(res.total_users); + setCountLoading(false); + }) + .finally(() => { + setCountLoading(false); + }); + }; + + const onDeleteClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + dispatch(confirmOperation(t("group.confirmDelete", { group: group?.name }))).then(() => { + if (group?.id) { + setDeleteLoading(true); + dispatch(deleteGroup(group.id)) + .then(() => { + onDelete?.(); + }) + .finally(() => { + setDeleteLoading(false); + }); + } + }); + }; + + const stopPropagation = (e: React.MouseEvent) => { + e.stopPropagation(); + }; + + if (loading) { + return ( + + + + + + + + + + + + + + + + + + + + + ); + } + return ( + + {group?.id} + + + {group?.name} + + {(group?.id ?? 0) <= 3 && ( + + + + + + )} + {(group?.id ?? 0) == AnonymousGroupID && ( + + + + + + )} + {groupBs.enabled(GroupPermission.is_admin) && ( + + + + + + )} + + + + + + {group?.edges.storage_policies && ( + + )} + + + + {countLoading ? ( + + ) : userCount != undefined ? ( + + {userCount} + + ) : ( + + {t("group.countUser")} + + )} + + {sizeToString(group?.max_storage ?? 0)} + + + + + + + ); +}; + +export default GroupRow; diff --git a/src/component/Admin/Group/GroupSetting.tsx b/src/component/Admin/Group/GroupSetting.tsx new file mode 100755 index 0000000..ed42724 --- /dev/null +++ b/src/component/Admin/Group/GroupSetting.tsx @@ -0,0 +1,152 @@ +import { useTheme } from "@emotion/react"; +import { + Box, + Button, + Container, + Stack, + Table, + TableBody, + TableContainer, + TableHead, + TableRow, + TableSortLabel, +} from "@mui/material"; +import { useQueryState } from "nuqs"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getGroupList } from "../../../api/api"; +import { GroupEnt } from "../../../api/dashboard"; +import { useAppDispatch } from "../../../redux/hooks"; +import { NoWrapTableCell, SecondaryButton, StyledTableContainerPaper } from "../../Common/StyledComponents"; +import Add from "../../Icons/Add"; +import ArrowSync from "../../Icons/ArrowSync"; +import PageContainer from "../../Pages/PageContainer"; +import PageHeader from "../../Pages/PageHeader"; +import TablePagination from "../Common/TablePagination"; +import { OrderByQuery, OrderDirectionQuery, PageQuery, PageSizeQuery } from "../StoragePolicy/StoragePolicySetting"; +import GroupRow from "./GroupRow"; +import NewGroupDialog from "./NewGroupDIalog"; + +const GroupSetting = () => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(true); + const [groups, setGroups] = useState([]); + const [page, setPage] = useQueryState(PageQuery, { defaultValue: "1" }); + const [pageSize, setPageSize] = useQueryState(PageSizeQuery, { + defaultValue: "10", + }); + const [orderBy, setOrderBy] = useQueryState(OrderByQuery, { + defaultValue: "", + }); + const [orderDirection, setOrderDirection] = useQueryState(OrderDirectionQuery, { defaultValue: "desc" }); + const [count, setCount] = useState(0); + const [createNewOpen, setCreateNewOpen] = useState(false); + + const pageInt = parseInt(page) ?? 1; + const pageSizeInt = parseInt(pageSize) ?? 11; + + useEffect(() => { + fetchGroups(); + }, [page, pageSize, orderBy, orderDirection]); + + const fetchGroups = () => { + setLoading(true); + dispatch( + getGroupList({ + page: pageInt, + page_size: pageSizeInt, + order_by: orderBy ?? "", + order_direction: orderDirection ?? "desc", + }), + ) + .then((res) => { + setGroups(res.groups); + setPageSize(res.pagination.page_size.toString()); + setCount(res.pagination.total_items ?? 0); + setLoading(false); + }) + .finally(() => { + setLoading(false); + }); + }; + + const orderById = orderBy === "id" || orderBy === ""; + const direction = orderDirection as "asc" | "desc"; + const onSortClick = (field: string) => () => { + const alreadySorted = orderBy === field || (field === "id" && orderById); + setOrderBy(field); + setOrderDirection(alreadySorted ? (direction === "asc" ? "desc" : "asc") : "asc"); + }; + + return ( + + setCreateNewOpen(false)} /> + + + + + }> + {t("node.refresh")} + + + + + + + + + {t("group.#")} + + + + + {t("group.name")} + + + {t("group.type")} + {t("group.count")} + + + {t("group.size")} + + + + + + + {!loading && groups.map((group) => )} + {loading && + groups.length > 0 && + groups.map((group) => )} + {loading && + groups.length === 0 && + Array.from(Array(5)).map((_, index) => )} + +
    +
    + {count > 0 && ( + + setPageSize(value.toString())} + onChange={(_, value) => setPage(value.toString())} + /> + + )} +
    +
    + ); +}; + +export default GroupSetting; diff --git a/src/component/Admin/Group/NewGroupDIalog.tsx b/src/component/Admin/Group/NewGroupDIalog.tsx new file mode 100755 index 0000000..56a1cf6 --- /dev/null +++ b/src/component/Admin/Group/NewGroupDIalog.tsx @@ -0,0 +1,132 @@ +import { DialogContent, FormControl, Stack } from "@mui/material"; +import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { upsertGroup } from "../../../api/api"; +import { GroupEnt } from "../../../api/dashboard"; +import { GroupPermission } from "../../../api/user"; +import { useAppDispatch } from "../../../redux/hooks"; +import Boolset from "../../../util/boolset"; +import { DenseFilledTextField } from "../../Common/StyledComponents"; +import DraggableDialog from "../../Dialogs/DraggableDialog"; +import SettingForm from "../../Pages/Setting/SettingForm"; +import GroupSelectionInput from "../Common/GroupSelectionInput"; +import { NoMarginHelperText } from "../Settings/Settings"; + +export interface NewGroupDialogProps { + open: boolean; + onClose: () => void; +} + +const defaultGroupBs = new Boolset(""); +defaultGroupBs.sets({ + [GroupPermission.share]: true, + [GroupPermission.share_download]: true, + [GroupPermission.set_anonymous_permission]: true, +}); +const defaultGroup: GroupEnt = { + name: "", + permissions: defaultGroupBs.toString(), + max_storage: 1024 << 20, // 1GB + settings: { + compress_size: 1024 << 20, // 1MB + decompress_size: 1024 << 20, // 1MB + max_walked_files: 100000, + trash_retention: 7 * 24 * 3600, + source_batch: 10, + aria2_batch: 1, + redirected_source: true, + }, + edges: { + storage_policies: { id: 1 }, + }, + id: 0, +}; + +const NewGroupDialog = ({ open, onClose }: NewGroupDialogProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + const [copyFrom, setCopyFrom] = useState("0"); + const [loading, setLoading] = useState(false); + const [group, setGroup] = useState({ ...defaultGroup }); + const formRef = useRef(null); + const copyFromSrc = useRef(undefined); + + useEffect(() => { + if (open) { + setGroup({ ...defaultGroup }); + setCopyFrom("0"); + copyFromSrc.current = undefined; + } + }, [open]); + + const handleSubmit = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + + let newGroup = { ...group }; + if (copyFrom != "0" && copyFromSrc.current) { + newGroup = { ...copyFromSrc.current, id: 0, name: group.name }; + } + + setLoading(true); + dispatch(upsertGroup({ group: newGroup })) + .then((r) => { + navigate(`/admin/group/${r.id}`); + onClose(); + }) + .finally(() => { + setLoading(false); + }); + }; + + return ( + + +
    + + + setGroup({ ...group, name: e.target.value })} + /> + {t("group.nameOfGroupDes")} + + + + { + copyFromSrc.current = g; + }} + emptyValue={"0"} + emptyText={"group.notCopy"} + /> + + + +
    +
    +
    + ); +}; + +export default NewGroupDialog; diff --git a/src/component/Admin/Home/Home.tsx b/src/component/Admin/Home/Home.tsx new file mode 100755 index 0000000..f9ae8cc --- /dev/null +++ b/src/component/Admin/Home/Home.tsx @@ -0,0 +1,425 @@ +import Giscus from "@giscus/react"; +import { GitHub } from "@mui/icons-material"; +import { + Avatar, + Box, + Container, + Divider, + List, + ListItem, + ListItemAvatar, + ListItemButton, + ListItemIcon, + ListItemText, + Paper, + Skeleton, + styled, + Typography, +} from "@mui/material"; +import { blue, green, red, yellow } from "@mui/material/colors"; +import Grid from "@mui/material/Grid"; +import { useTheme } from "@mui/material/styles"; +import dayjs from "dayjs"; +import i18next from "i18next"; +import { useCallback, useEffect, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { CartesianGrid, Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"; +import { getDashboardSummary } from "../../../api/api.ts"; +import { HomepageSummary } from "../../../api/dashboard.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import FacebookCircularProgress from "../../Common/CircularProgress.tsx"; +import { SecondaryButton, SquareChip } from "../../Common/StyledComponents.tsx"; +import TimeBadge from "../../Common/TimeBadge.tsx"; +import Book from "../../Icons/Book.tsx"; +import BoxMultipleFilled from "../../Icons/BoxMultipleFilled.tsx"; +import Discord from "../../Icons/Discord.tsx"; +import DocumentCopyFilled from "../../Icons/DocumentCopyFilled.tsx"; +import HomeIcon from "../../Icons/Home.tsx"; +import OpenFilled from "../../Icons/OpenFilled.tsx"; +import PeopleFilled from "../../Icons/PeopleFilled.tsx"; +import ShareFilled from "../../Icons/ShareFilled.tsx"; +import SparkleFilled from "../../Icons/SparkleFilled.tsx"; +import Telegram from "../../Icons/Telegram.tsx"; +import PageContainer from "../../Pages/PageContainer.tsx"; +import PageHeader from "../../Pages/PageHeader.tsx"; +import ProDialog from "../Common/ProDialog.tsx"; +import SiteUrlWarning from "./SiteUrlWarning.tsx"; +import CommentMultiple from "../../Icons/CommentMultiple.tsx"; + +const StyledPaper = styled(Paper)(({ theme }) => ({ + padding: theme.spacing(3), + boxShadow: "initial", + border: "1px solid " + theme.palette.divider, +})); + +const StyledListItemIcon = styled(ListItemIcon)(() => ({ + minWidth: 0, +})); + +const Home = () => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const dispatch = useAppDispatch(); + const [summary, setSummary] = useState(); + const [chartLoading, setChartLoading] = useState(false); + const [siteUrlWarning, setSiteUrlWarning] = useState(false); + const [proDialogOpen, setProDialogOpen] = useState(false); + useEffect(() => { + loadSummary(false); + }, []); + + const loadSummary = useCallback((loadChart?: boolean) => { + if (loadChart) { + setChartLoading(true); + } + dispatch(getDashboardSummary(loadChart)) + .then((r) => { + setSummary(r); + if (!loadChart) { + const target = r.site_urls.find((site) => site == window.location.origin); + if (!target) { + setSiteUrlWarning(true); + } + } + }) + .finally(() => { + setChartLoading(false); + }); + }, []); + + return ( + + setProDialogOpen(false)} /> + setSiteUrlWarning(false)} + existingUrls={summary?.site_urls ?? []} + /> + + + + + + + {t("summary.trend")} + + {summary?.metrics_summary?.generated_at && ( + ]} + /> + )} + + + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${!!summary?.metrics_summary}-${!!chartLoading}`} + > + + {summary?.metrics_summary && ( + + ({ + name: dayjs(i).format("MM-DD"), + user: summary?.metrics_summary?.users[d] ?? 0, + file: summary?.metrics_summary?.files[d] ?? 0, + share: summary?.metrics_summary?.shares[d] ?? 0, + }))} + > + + + { + const yAxisValue = [ + ...(summary?.metrics_summary?.users ?? []), + ...(summary?.metrics_summary?.files ?? []), + ...(summary?.metrics_summary?.shares ?? []), + ]; + const yAxisUpperLimit = yAxisValue.length ? Math.max(...yAxisValue) / 0.8 - 1 : 0; + const yAxisDigits = yAxisUpperLimit > 1 ? Math.floor(Math.log10(yAxisUpperLimit)) + 1 : 1; + return 3 + yAxisDigits * 9; + })()} + /> + + + + + + + + )} + {chartLoading && ( + + + + )} + {!summary?.metrics_summary?.generated_at && !chartLoading && ( + + loadSummary(true)}> + {t("application:fileManager.calculate")} + + + )} + + + + + + + + + {t("summary.summary")} + + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${!!summary?.metrics_summary}-${chartLoading}`} + > + + {summary?.metrics_summary && ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} + {chartLoading && ( + + + + )} + {!summary?.metrics_summary?.generated_at && !chartLoading && ( + + loadSummary(true)}> + {t("application:fileManager.calculate")} + + + )} + + + + + + + + + + + + Cloudreve + {summary && summary.version.pro && ( + + )} + + + {summary ? summary.version.version : } + {summary && ( + t.palette.action.disabled }}> + #{summary.version.commit} + + )} + + + + + + window.open("https://cloudreve.org")}> + + + + + + + + + window.open("https://github.com/cloudreve/cloudreve")}> + + + + + + + + + window.open("https://docs.cloudreve.org/")}> + + + + + + + + + window.open("https://discord.gg/WTpMFpZT76")}> + + + + + + + + + window.open("https://t.me/cloudreve_official")}> + + + + + + + + + window.open("https://github.com/cloudreve/cloudreve/discussions")}> + + + + + + + + + {summary && !summary.version.pro && ( + setProDialogOpen(true)}> + + + + + + )} + + + + + + + + 公告 + + + + + + + + + ); +}; + +export default Home; diff --git a/src/component/Admin/Home/SiteUrlWarning.tsx b/src/component/Admin/Home/SiteUrlWarning.tsx new file mode 100755 index 0000000..954bac0 --- /dev/null +++ b/src/component/Admin/Home/SiteUrlWarning.tsx @@ -0,0 +1,81 @@ +import { DialogContent, List, ListItemButton, Stack, Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { sendSetSetting } from "../../../api/api.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { StyledListItemText } from "../../Common/StyledComponents.tsx"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; + +export interface SiteUrlWarningProps { + open: boolean; + onClose: () => void; + existingUrls: string[]; +} + +const SiteUrlWarning = ({ open, onClose, existingUrls }: SiteUrlWarningProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + + const setSiteUrl = (isPrimary: boolean) => () => { + const urls = [...existingUrls]; + if (isPrimary) { + urls.unshift(window.location.origin); + } else { + urls.push(window.location.origin); + } + onClose(); + dispatch( + sendSetSetting({ + settings: { + siteURL: urls.join(","), + }, + }), + ); + }; + + return ( + <> + + + + + {t("summary.siteURLNotMatch", { + current: window.location.origin, + })} + + + + + + + + + + + {t("summary.siteURLDescription")} + + + + + + ); +}; + +export default SiteUrlWarning; diff --git a/src/component/Admin/Node/EditNode/BasicInfoSection.tsx b/src/component/Admin/Node/EditNode/BasicInfoSection.tsx new file mode 100755 index 0000000..a343872 --- /dev/null +++ b/src/component/Admin/Node/EditNode/BasicInfoSection.tsx @@ -0,0 +1,167 @@ +import { Alert, FormControl, FormControlLabel, Switch, Typography } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useCallback, useContext, useMemo, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { testNode } from "../../../../api/api"; +import { Node, NodeStatus, NodeType } from "../../../../api/dashboard"; +import { useAppDispatch } from "../../../../redux/hooks"; +import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar"; +import { DenseFilledTextField, SecondaryButton } from "../../../Common/StyledComponents"; +import SettingForm from "../../../Pages/Setting/SettingForm"; +import { Code } from "../../../Common/Code.tsx"; +import { EndpointInput } from "../../Common/EndpointInput"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../../Settings/Settings"; +import { NodeSettingContext } from "./NodeSettingWrapper"; +const BasicInfoSection = () => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + const { values, setNode } = useContext(NodeSettingContext); + const [testNodeLoading, setTestNodeLoading] = useState(false); + + const onNameChange = useCallback( + (e: React.ChangeEvent) => { + setNode((p: Node) => ({ ...p, name: e.target.value })); + }, + [setNode], + ); + + const onServerChange = useCallback( + (e: React.ChangeEvent) => { + setNode((p: Node) => ({ ...p, server: e.target.value })); + }, + [setNode], + ); + + const onSlaveKeyChange = useCallback( + (e: React.ChangeEvent) => { + setNode((p: Node) => ({ ...p, slave_key: e.target.value })); + }, + [setNode], + ); + + const onWeightChange = useCallback( + (e: React.ChangeEvent) => { + const weight = parseInt(e.target.value); + setNode((p: Node) => ({ ...p, weight: isNaN(weight) ? 1 : weight })); + }, + [setNode], + ); + + const onStatusChange = useCallback( + (e: React.ChangeEvent) => { + setNode((p: Node) => ({ + ...p, + status: e.target.checked ? NodeStatus.active : NodeStatus.suspended, + })); + }, + [setNode], + ); + + const isActive = useMemo(() => { + return values.status === NodeStatus.active; + }, [values.status]); + + const nodeTypeText = useMemo(() => { + return values.type === NodeType.master ? t("node.master") : t("node.slave"); + }, [values.type, t]); + + const onTestNode = useCallback(() => { + setTestNodeLoading(true); + dispatch(testNode({ node: values })) + .then((res) => { + enqueueSnackbar(t("node.testNodeSuccess"), { variant: "success", action: DefaultCloseAction }); + }) + .finally(() => { + setTestNodeLoading(false); + }); + }, [dispatch, values]); + + return ( + + + {t("policy.basicInfo")} + + + {values.type === NodeType.master && ( + + {t("node.thisIsMasterNodes")} + + )} + + + } + label={t("node.enableNode")} + /> + {t("node.enableNodeDes")} + + + + + + {t("node.nameNode")} + + + + + + + + {values.type === NodeType.slave && ( + <> + + + + {t("node.serverDes")} + + + + + + + , ]} /> + + + + + )} + + + + {t("node.loadBalancerRankDes")} + + + {values.type === NodeType.slave && ( + + + {t("node.testNode")} + + + )} + + + ); +}; + +export default BasicInfoSection; diff --git a/src/component/Admin/Node/EditNode/CapabilitiesSection.tsx b/src/component/Admin/Node/EditNode/CapabilitiesSection.tsx new file mode 100755 index 0000000..261474c --- /dev/null +++ b/src/component/Admin/Node/EditNode/CapabilitiesSection.tsx @@ -0,0 +1,568 @@ +import { + CircularProgress, + Collapse, + FormControl, + FormControlLabel, + IconButton, + Link, + ListItemText, + SelectChangeEvent, + Switch, + Typography, + useTheme, +} from "@mui/material"; +import { useSnackbar } from "notistack"; +import { lazy, Suspense, useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { testNodeDownloader } from "../../../../api/api"; +import { DownloaderProvider, Node, NodeType } from "../../../../api/dashboard"; +import { NodeCapability } from "../../../../api/workflow"; +import { useAppDispatch } from "../../../../redux/hooks"; +import Boolset from "../../../../util/boolset"; +import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar"; +import { DenseFilledTextField, DenseSelect, SecondaryButton } from "../../../Common/StyledComponents"; +import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu"; +import QuestionCircle from "../../../Icons/QuestionCircle"; +import SettingForm from "../../../Pages/Setting/SettingForm"; +import { Code } from "../../../Common/Code.tsx"; +import { EndpointInput } from "../../Common/EndpointInput"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../../Settings/Settings"; +import { NodeSettingContext } from "./NodeSettingWrapper"; +import StoreFilesHintDialog from "./StoreFilesHintDialog"; +const MonacoEditor = lazy(() => import("../../../Viewers/CodeViewer/MonacoEditor")); + +const CapabilitiesSection = () => { + const { t } = useTranslation("dashboard"); + const { values, setNode } = useContext(NodeSettingContext); + const theme = useTheme(); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + + const [editedConfigAria2, setEditedConfigAria2] = useState(""); + const [editedConfigQbittorrent, setEditedConfigQbittorrent] = useState(""); + const [testDownloaderLoading, setTestDownloaderLoading] = useState(false); + const [storeFilesHintDialogOpen, setStoreFilesHintDialogOpen] = useState(false); + + const capabilities = useMemo(() => { + return new Boolset(values.capabilities ?? ""); + }, [values.capabilities]); + + const hasRemoteDownload = useMemo(() => { + return capabilities.enabled(NodeCapability.remote_download); + }, [capabilities]); + + useEffect(() => { + setEditedConfigAria2( + values.settings?.aria2?.options ? JSON.stringify(values.settings?.aria2?.options, null, 2) : "", + ); + }, [values.settings?.aria2?.options]); + + useEffect(() => { + setEditedConfigQbittorrent( + values.settings?.qbittorrent?.options ? JSON.stringify(values.settings?.qbittorrent?.options, null, 2) : "", + ); + }, [values.settings?.qbittorrent?.options]); + + const onCapabilityChange = useCallback( + (capability: number) => (e: React.ChangeEvent) => { + setNode((p: Node) => ({ + ...p, + capabilities: new Boolset(p.capabilities).set(capability, e.target.checked).toString(), + })); + }, + [setNode], + ); + + const onProviderChange = useCallback( + (e: SelectChangeEvent) => { + const provider = e.target.value as DownloaderProvider; + setNode((p: Node) => ({ + ...p, + settings: { + ...p.settings, + provider, + }, + })); + }, + [setNode], + ); + + const onAria2ServerChange = useCallback( + (e: React.ChangeEvent) => { + setNode((p: Node) => ({ + ...p, + settings: { + ...p.settings, + aria2: { + ...p.settings?.aria2, + server: e.target.value, + }, + }, + })); + }, + [setNode], + ); + + const onAria2TokenChange = useCallback( + (e: React.ChangeEvent) => { + setNode((p: Node) => ({ + ...p, + settings: { + ...p.settings, + aria2: { + ...p.settings?.aria2, + token: e.target.value, + }, + }, + })); + }, + [setNode], + ); + + const onAria2TempPathChange = useCallback( + (e: React.ChangeEvent) => { + setNode((p: Node) => ({ + ...p, + settings: { + ...p.settings, + aria2: { + ...p.settings?.aria2, + temp_path: e.target.value ? e.target.value : undefined, + }, + }, + })); + }, + [setNode], + ); + + const onQBittorrentServerChange = useCallback( + (e: React.ChangeEvent) => { + setNode((p: Node) => ({ + ...p, + settings: { + ...p.settings, + qbittorrent: { + ...p.settings?.qbittorrent, + server: e.target.value, + }, + }, + })); + }, + [setNode], + ); + + const onQBittorrentUserChange = useCallback( + (e: React.ChangeEvent) => { + setNode((p: Node) => ({ + ...p, + settings: { + ...p.settings, + qbittorrent: { + ...p.settings?.qbittorrent, + user: e.target.value ? e.target.value : undefined, + }, + }, + })); + }, + [setNode], + ); + + const onQBittorrentPasswordChange = useCallback( + (e: React.ChangeEvent) => { + setNode((p: Node) => ({ + ...p, + settings: { + ...p.settings, + qbittorrent: { + ...p.settings?.qbittorrent, + password: e.target.value ? e.target.value : undefined, + }, + }, + })); + }, + [setNode], + ); + + const onQBittorrentTempPathChange = useCallback( + (e: React.ChangeEvent) => { + setNode((p: Node) => ({ + ...p, + settings: { + ...p.settings, + qbittorrent: { + ...p.settings?.qbittorrent, + temp_path: e.target.value ? e.target.value : undefined, + }, + }, + })); + }, + [setNode], + ); + + const onIntervalChange = useCallback( + (e: React.ChangeEvent) => { + const interval = parseInt(e.target.value); + setNode((p: Node) => ({ + ...p, + settings: { + ...p.settings, + interval: isNaN(interval) ? undefined : interval, + }, + })); + }, + [setNode], + ); + + const onWaitForSeedingChange = useCallback( + (e: React.ChangeEvent) => { + setNode((p: Node) => ({ + ...p, + settings: { + ...p.settings, + wait_for_seeding: e.target.checked ? true : undefined, + }, + })); + }, + [setNode], + ); + + const onEditedConfigAria2Blur = useCallback( + (value: string) => { + var res: Record | undefined = undefined; + if (value) { + try { + res = JSON.parse(value); + } catch (e) { + console.error(e); + } + } + setNode((p: Node) => ({ ...p, settings: { ...p.settings, aria2: { ...p.settings?.aria2, options: res } } })); + }, + [editedConfigAria2, setNode], + ); + + const onEditedConfigQbittorrentBlur = useCallback( + (value: string) => { + var res: Record | undefined = undefined; + if (value) { + try { + res = JSON.parse(value); + } catch (e) { + console.error(e); + } + } + setNode((p: Node) => ({ + ...p, + settings: { ...p.settings, qbittorrent: { ...p.settings?.qbittorrent, options: res } }, + })); + }, + [editedConfigQbittorrent, setNode], + ); + + const onTestDownloaderClick = useCallback(() => { + setTestDownloaderLoading(true); + dispatch(testNodeDownloader({ node: values })) + .then((res) => { + enqueueSnackbar({ + variant: "success", + message: t("node.downloaderTestPass", { version: res }), + action: DefaultCloseAction, + }); + }) + .finally(() => { + setTestDownloaderLoading(false); + }); + }, [values]); + + const onStoreFilesClick = useCallback(() => { + setStoreFilesHintDialogOpen(true); + }, []); + + return ( + <> + setStoreFilesHintDialogOpen(false)} /> + + + {t("node.features")} + + + + + + } + label={t("application:fileManager.createArchive")} + /> + {t("node.createArchiveDes")} + + + + + + } + label={t("application:fileManager.extractArchive")} + /> + {t("node.extractArchiveDes")} + + + + + + } + label={t("application:navbar.remoteDownload")} + /> + {t("node.remoteDownloadDes")} + + + {values.type === NodeType.slave && ( + + + 0} + checked={(values.edges?.storage_policy?.length ?? 0) > 0} + /> + } + label={t("node.storeFiles")} + /> + {t("node.storeFilesDes")} + + + )} + + + + + + + {t("node.remoteDownload")} + { + window.open("https://docs.cloudreve.org/usage/remote-download", "_blank"); + }} + > + + + + + + + + + + + + + + + + {values.settings?.provider === DownloaderProvider.qbittorrent + ? t("node.qbittorrentDes") + : t("node.aria2Des")} + + + + + {values.settings?.provider === DownloaderProvider.aria2 && ( + <> + + + + + ]} /> + + + + + + + + ]} /> + + + + + + }> + setEditedConfigAria2(value || "")} + onBlur={onEditedConfigAria2Blur} + height="200px" + minHeight="200px" + options={{ + wordWrap: "on", + minimap: { enabled: false }, + scrollBeyondLastLine: false, + }} + /> + + + , + ]} + /> + + + + + + + {t("node.tempPathDes")} + + + + )} + + {values.settings?.provider === DownloaderProvider.qbittorrent && ( + <> + + + + + ]} /> + + + + + + + + {t("node.webUICredDes")} + + + + + }> + setEditedConfigQbittorrent(value || "")} + onBlur={onEditedConfigQbittorrentBlur} + height="200px" + minHeight="200px" + options={{ + wordWrap: "on", + minimap: { enabled: false }, + scrollBeyondLastLine: false, + }} + /> + + + , + ]} + /> + + + + + + + {t("node.tempPathDes")} + + + + )} + + + + + {t("node.refreshIntervalDes")} + + + + + + + } + label={t("node.waitForSeeding")} + /> + {t("node.waitForSeedingDes")} + + + + + {t("node.testDownloader")} + + + + + + + ); +}; + +export default CapabilitiesSection; diff --git a/src/component/Admin/Node/EditNode/EditNode.tsx b/src/component/Admin/Node/EditNode/EditNode.tsx new file mode 100755 index 0000000..46d764d --- /dev/null +++ b/src/component/Admin/Node/EditNode/EditNode.tsx @@ -0,0 +1,34 @@ +import { Container } from "@mui/material"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useParams } from "react-router-dom"; +import { Node } from "../../../../api/dashboard"; +import PageContainer from "../../../Pages/PageContainer"; +import PageHeader from "../../../Pages/PageHeader"; +import BasicInfoSection from "./BasicInfoSection"; +import CapabilitiesSection from "./CapabilitiesSection"; +import NodeForm from "./NodeForm"; +import NodeSettingWrapper from "./NodeSettingWrapper"; + +const EditNode = () => { + const { t } = useTranslation("dashboard"); + const { id } = useParams<{ id: string }>(); + const [node, setNode] = useState(null); + const nodeID = parseInt(id ?? "0"); + + return ( + + + + + + + + + + + + ); +}; + +export default EditNode; diff --git a/src/component/Admin/Node/EditNode/NodeForm.tsx b/src/component/Admin/Node/EditNode/NodeForm.tsx new file mode 100755 index 0000000..4da51c9 --- /dev/null +++ b/src/component/Admin/Node/EditNode/NodeForm.tsx @@ -0,0 +1,19 @@ +import { Box, Stack } from "@mui/material"; +import { useContext } from "react"; +import { NodeSettingContext } from "./NodeSettingWrapper"; + +export interface NodeFormProps { + children: React.ReactNode; +} + +const NodeForm = ({ children }: NodeFormProps) => { + const { formRef } = useContext(NodeSettingContext); + + return ( + + {children} + + ); +}; + +export default NodeForm; diff --git a/src/component/Admin/Node/EditNode/NodeSettingWrapper.tsx b/src/component/Admin/Node/EditNode/NodeSettingWrapper.tsx new file mode 100755 index 0000000..098511d --- /dev/null +++ b/src/component/Admin/Node/EditNode/NodeSettingWrapper.tsx @@ -0,0 +1,157 @@ +import { Box } from "@mui/material"; +import * as React from "react"; +import { createContext, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { getNodeDetail, upsertNode } from "../../../../api/api.ts"; +import { Node, StoragePolicy } from "../../../../api/dashboard.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import FacebookCircularProgress from "../../../Common/CircularProgress.tsx"; +import { SavingFloat } from "../../Settings/SettingWrapper.tsx"; + +export interface NodeSettingWrapperProps { + nodeID: number; + children: React.ReactNode; + onNodeChange: (node: Node) => void; +} + +export interface NodeSettingContextProps { + values: Node; + setNode: (f: (p: Node) => Node) => void; + formRef?: React.RefObject; +} + +const defaultNode: Node = { + id: 0, + name: "", + status: undefined, + type: undefined, + server: "", + slave_key: "", + capabilities: "", + weight: 1, + settings: {}, + edges: { + storage_policy: [], + }, +}; + +export const NodeSettingContext = createContext({ + values: { ...defaultNode }, + setNode: () => {}, +}); + +const nodeValueFilter = (node: Node): Node => { + return { + ...node, + edges: { + storage_policy: node.edges.storage_policy?.map( + (p): StoragePolicy => + ({ + id: p.id, + }) as StoragePolicy, + ), + }, + }; +}; + +const NodeSettingWrapper = ({ nodeID, children, onNodeChange }: NodeSettingWrapperProps) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation("dashboard"); + const [values, setValues] = useState({ + ...defaultNode, + }); + const [modifiedValues, setModifiedValues] = useState({ + ...defaultNode, + }); + const [loading, setLoading] = useState(true); + const [submitting, setSubmitting] = useState(false); + const formRef = useRef(null); + + const showSaveButton = useMemo(() => { + return JSON.stringify(modifiedValues) !== JSON.stringify(values); + }, [modifiedValues, values]); + + useEffect(() => { + setLoading(true); + dispatch(getNodeDetail(nodeID)) + .then((res) => { + setValues(nodeValueFilter(res)); + setModifiedValues(nodeValueFilter(res)); + onNodeChange(nodeValueFilter(res)); + }) + .finally(() => { + setLoading(false); + }); + }, [nodeID]); + + const revert = () => { + setModifiedValues(values); + }; + + const submit = () => { + if (formRef.current) { + if (!formRef.current.checkValidity()) { + formRef.current.reportValidity(); + return; + } + } + + setSubmitting(true); + dispatch( + upsertNode({ + node: { ...modifiedValues }, + }), + ) + .then((res) => { + setValues(nodeValueFilter(res)); + setModifiedValues(nodeValueFilter(res)); + onNodeChange(nodeValueFilter(res)); + }) + .finally(() => { + setSubmitting(false); + }); + }; + + return ( + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${loading}`} + > + + {loading && ( + + + + )} + {!loading && ( + + {children} + + + )} + + + + + ); +}; + +export default NodeSettingWrapper; diff --git a/src/component/Admin/Node/EditNode/StoreFilesHintDialog.tsx b/src/component/Admin/Node/EditNode/StoreFilesHintDialog.tsx new file mode 100755 index 0000000..6628dee --- /dev/null +++ b/src/component/Admin/Node/EditNode/StoreFilesHintDialog.tsx @@ -0,0 +1,35 @@ +import { DialogContent, Link, Typography } from "@mui/material"; +import { Trans, useTranslation } from "react-i18next"; +import { Link as RouterLink } from "react-router-dom"; +import DraggableDialog from "../../../Dialogs/DraggableDialog"; +export interface StoreFilesHintDialogProps { + open: boolean; + onClose: () => void; +} + +const StoreFilesHintDialog = ({ open, onClose }: StoreFilesHintDialogProps) => { + const { t } = useTranslation("dashboard"); + return ( + + + + ]} + /> + + + + ); +}; + +export default StoreFilesHintDialog; diff --git a/src/component/Admin/Node/EditNode/index.tsx b/src/component/Admin/Node/EditNode/index.tsx new file mode 100755 index 0000000..fef0766 --- /dev/null +++ b/src/component/Admin/Node/EditNode/index.tsx @@ -0,0 +1,3 @@ +import EditNode from "./EditNode"; + +export default EditNode; diff --git a/src/component/Admin/Node/NewNode/NewNodeDialog.tsx b/src/component/Admin/Node/NewNode/NewNodeDialog.tsx new file mode 100755 index 0000000..40d4018 --- /dev/null +++ b/src/component/Admin/Node/NewNode/NewNodeDialog.tsx @@ -0,0 +1,234 @@ +import { Box, Stack, Typography, useTheme } from "@mui/material"; +import { grey } from "@mui/material/colors"; +import { useSnackbar } from "notistack"; +import { lazy, Suspense, useEffect, useMemo, useRef, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { testNode, upsertNode } from "../../../../api/api"; +import { DownloaderProvider, Node, NodeStatus, NodeType } from "../../../../api/dashboard"; +import { useAppDispatch } from "../../../../redux/hooks"; +import { randomString } from "../../../../util"; +import FacebookCircularProgress from "../../../Common/CircularProgress"; +import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar"; +import { DenseFilledTextField, SecondaryButton } from "../../../Common/StyledComponents"; +import DraggableDialog from "../../../Dialogs/DraggableDialog"; +import SettingForm from "../../../Pages/Setting/SettingForm"; +import { Code } from "../../../Common/Code.tsx"; +import { EndpointInput } from "../../Common/EndpointInput"; +import { NoMarginHelperText } from "../../Settings/Settings"; +const MonacoEditor = lazy(() => import("../../../Viewers/CodeViewer/MonacoEditor")); + +export interface NewNodeDialogProps { + open: boolean; + onClose: () => void; +} + +const defaultNode: Node = { + id: 0, + name: "", + type: NodeType.slave, + status: NodeStatus.active, + server: "", + slave_key: "", + capabilities: "", + weight: 1, + settings: { + provider: DownloaderProvider.aria2, + qbittorrent: {}, + aria2: {}, + interval: 5, + }, + edges: { + storage_policy: [], + }, +}; + +export const Step = ({ step, children }: { step: number; children: React.ReactNode }) => { + return ( + + theme.transitions.create("background-color", { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + "&:focus-within": { + backgroundColor: (theme) => (theme.palette.mode == "dark" ? grey[900] : grey[100]), + }, + }} + > + + t.typography.body2.fontSize, + height: "20px", + backgroundColor: (theme) => theme.palette.primary.light, + color: (theme) => theme.palette.primary.contrastText, + textAlign: "center", + borderRadius: " 50%", + }} + > + {step} + + + {children} + + ); +}; + +export const NewNodeDialog = ({ open, onClose }: NewNodeDialogProps) => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + const { enqueueSnackbar } = useSnackbar(); + const [loading, setLoading] = useState(false); + const [node, setNode] = useState({ ...defaultNode }); + const formRef = useRef(null); + + useEffect(() => { + if (open) { + setNode({ ...defaultNode, slave_key: randomString(64) }); + } + }, [open]); + + const handleSubmit = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + + setLoading(true); + dispatch(upsertNode({ node })) + .then((r) => { + navigate(`/admin/node/${r.id}`); + onClose(); + }) + .finally(() => { + setLoading(false); + }); + }; + + const config = useMemo(() => { + return `[System] +Mode = slave +Listen = :5212 + +[Slave] +Secret = ${node.slave_key} + +; ${t("node.keepIfUpload")} +[CORS] +AllowOrigins = * +AllowMethods = OPTIONS,GET,POST +AllowHeaders = * +`; + }, [t, node.slave_key]); + + const handleTest = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + + setLoading(true); + dispatch(testNode({ node })) + .then(() => { + setLoading(false); + enqueueSnackbar(t("node.testNodeSuccess"), { variant: "success", action: DefaultCloseAction }); + }) + .finally(() => setLoading(false)); + }; + + return ( + +
    + + + + + {t("node.nameTheNode")} + + setNode({ ...node, name: e.target.value })} + /> + {t("node.nameNode")} + + + + + + {t("node.runCrSlave")} + + }> + + + + ]} /> + + + + + + + {t("node.inputServer")} + + setNode({ ...node, server: e.target.value })} + /> + {t("node.serverDes")} + + + + + + {t("node.testButton")} + + + {t("node.testNode")} + + + ]} /> + + + + +
    +
    + ); +}; diff --git a/src/component/Admin/Node/NodeCard.tsx b/src/component/Admin/Node/NodeCard.tsx new file mode 100755 index 0000000..9011254 --- /dev/null +++ b/src/component/Admin/Node/NodeCard.tsx @@ -0,0 +1,200 @@ +import { Box, Divider, IconButton, Skeleton, Typography } from "@mui/material"; +import Grid from "@mui/material/Grid2"; +import { useCallback, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { deleteNode } from "../../../api/api"; +import { Node, NodeStatus, NodeType } from "../../../api/dashboard"; +import { NodeCapability } from "../../../api/workflow"; +import { useAppDispatch } from "../../../redux/hooks"; +import { confirmOperation } from "../../../redux/thunks/dialog"; +import Boolset from "../../../util/boolset"; +import { NoWrapBox, SquareChip } from "../../Common/StyledComponents"; +import CheckCircleFilled from "../../Icons/CheckCircleFilled"; +import Delete from "../../Icons/Delete"; +import DismissCircleFilled from "../../Icons/DismissCircleFilled"; +import Info from "../../Icons/Info"; +import StarFilled from "../../Icons/StarFilled"; +import { BorderedCardClickable } from "../Common/AdminCard"; + +export interface NodeCardProps { + node?: Node; + onRefresh?: () => void; + loading?: boolean; +} + +const NodeCard = ({ node, onRefresh, loading }: NodeCardProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + const [deleteLoading, setDeleteLoading] = useState(false); + + const handleDelete = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + dispatch(confirmOperation(t("node.deleteNodeConfirmation", { name: node?.name ?? "" }))).then(() => { + setDeleteLoading(true); + dispatch(deleteNode(node?.id ?? 0)) + .then(() => { + onRefresh?.(); + }) + .finally(() => { + setDeleteLoading(false); + }); + }); + }, + [node, dispatch, onRefresh], + ); + + const handleEdit = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + navigate(`/admin/node/${node?.id}`); + }, + [node, navigate], + ); + + // Decode node capabilities + const getCapabilities = useCallback(() => { + if (!node?.capabilities) return []; + + const boolset = new Boolset(node.capabilities); + const capabilities = []; + + if (boolset.enabled(NodeCapability.create_archive)) { + capabilities.push({ id: NodeCapability.create_archive, name: t("application:fileManager.createArchive") }); + } + if (boolset.enabled(NodeCapability.extract_archive)) { + capabilities.push({ id: NodeCapability.extract_archive, name: t("application:fileManager.extractArchive") }); + } + if (boolset.enabled(NodeCapability.remote_download)) { + capabilities.push({ id: NodeCapability.remote_download, name: t("application:navbar.remoteDownload") }); + } + + return capabilities; + }, [node, t]); + + // If loading is true, render a skeleton placeholder + if (loading) { + return ( + + + + + + + + + + + + + + + + + + + + + + + ); + } + + const capabilities = getCapabilities(); + + return ( + + + + + {node?.name} + + + {node?.type === NodeType.master && } + + {node?.type === NodeType.master ? t("node.master") : t("node.slave")} + + + + + {capabilities.length > 0 ? ( + capabilities.map((capability) => ( + + )) + ) : ( + + + {t("node.noCapabilities")} + + )} + + + + + {node?.status === NodeStatus.active ? ( + <> + + + {t("node.active")} + + + ) : ( + <> + + + {t("node.suspended")} + + + )} + + + + + + + + + + + ); +}; + +export default NodeCard; diff --git a/src/component/Admin/Node/NodeSetting.tsx b/src/component/Admin/Node/NodeSetting.tsx new file mode 100755 index 0000000..358d3d3 --- /dev/null +++ b/src/component/Admin/Node/NodeSetting.tsx @@ -0,0 +1,131 @@ +import { Add } from "@mui/icons-material"; +import { Box, Container, Grid2 as Grid, IconButton, Stack, Typography } from "@mui/material"; +import { useQueryState } from "nuqs"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getNodeList } from "../../../api/api"; +import { Node } from "../../../api/dashboard"; +import { useAppDispatch } from "../../../redux/hooks"; +import { SecondaryButton } from "../../Common/StyledComponents"; +import ArrowSync from "../../Icons/ArrowSync"; +import QuestionCircle from "../../Icons/QuestionCircle"; +import PageContainer from "../../Pages/PageContainer"; +import PageHeader from "../../Pages/PageHeader"; +import { BorderedCardClickable } from "../Common/AdminCard"; +import TablePagination from "../Common/TablePagination"; +import { OrderByQuery, OrderDirectionQuery, PageQuery, PageSizeQuery } from "../StoragePolicy/StoragePolicySetting"; +import { NewNodeDialog } from "./NewNode/NewNodeDialog"; +import NodeCard from "./NodeCard"; + +const NodeSetting = () => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(true); + const [nodes, setNodes] = useState([]); + const [page, setPage] = useQueryState(PageQuery, { defaultValue: "1" }); + const [pageSize, setPageSize] = useQueryState(PageSizeQuery, { + defaultValue: "11", + }); + const [orderBy, setOrderBy] = useQueryState(OrderByQuery, { + defaultValue: "", + }); + const [orderDirection, setOrderDirection] = useQueryState(OrderDirectionQuery, { defaultValue: "desc" }); + const [count, setCount] = useState(0); + const [selectProviderOpen, setSelectProviderOpen] = useState(false); + const [createNewOpen, setCreateNewOpen] = useState(false); + + const pageInt = parseInt(page) ?? 1; + const pageSizeInt = parseInt(pageSize) ?? 11; + + useEffect(() => { + fetchNodes(); + }, [page, pageSize, orderBy, orderDirection]); + + const fetchNodes = () => { + setLoading(true); + dispatch( + getNodeList({ + page: pageInt, + page_size: pageSizeInt, + order_by: orderBy ?? "", + order_direction: orderDirection ?? "desc", + conditions: {}, + }), + ) + .then((res) => { + setNodes(res.nodes); + setPage((res.pagination.page + 1).toString()); + setPageSize(res.pagination.page_size.toString()); + setCount(res.pagination.total_items ?? 0); + setLoading(false); + }) + .finally(() => { + setLoading(false); + }); + }; + + return ( + + setCreateNewOpen(false)} /> + + window.open("https://docs.cloudreve.org/usage/slave-node", "_blank")}> + + + } + /> + + }> + {t("node.refresh")} + + + + + setCreateNewOpen(true)} + sx={{ + height: "100%", + borderStyle: "dashed", + display: "flex", + alignItems: "center", + gap: 1, + justifyContent: "center", + color: (t) => t.palette.text.secondary, + }} + > + + {t("node.addNewNode")} + + + {!loading && nodes.map((n) => )} + {loading && nodes.length > 0 && nodes.map((n) => )} + {loading && + nodes.length === 0 && + Array.from(Array(5)).map((_, index) => )} + + {count > 0 && ( + + setPageSize(value.toString())} + onChange={(_, value) => setPage(value.toString())} + /> + + )} + + + ); +}; + +export default NodeSetting; diff --git a/src/component/Admin/Settings/Appearance/Appearance.tsx b/src/component/Admin/Settings/Appearance/Appearance.tsx new file mode 100755 index 0000000..f9a8d29 --- /dev/null +++ b/src/component/Admin/Settings/Appearance/Appearance.tsx @@ -0,0 +1,39 @@ +import { Box, Stack } from "@mui/material"; +import { useContext } from "react"; +import { useTranslation } from "react-i18next"; +import { SettingSection } from "../Settings"; +import { SettingContext } from "../SettingWrapper"; +import CustomHTML from "./CustomHTML"; +import CustomNavItems from "./CustomNavItems"; +import ThemeOptions from "./ThemeOptions"; + +const Appearance = () => { + const { t } = useTranslation("dashboard"); + const { formRef, setSettings, values } = useContext(SettingContext); + + return ( + e.preventDefault()}> + + + setSettings({ theme_options: value })} + defaultTheme={values.defaultTheme || ""} + onDefaultThemeChange={(value: string) => setSettings({ defaultTheme: value })} + /> + + + setSettings({ custom_nav_items: value })} + /> + + + + + + + ); +}; + +export default Appearance; diff --git a/src/component/Admin/Settings/Appearance/CustomHTML.tsx b/src/component/Admin/Settings/Appearance/CustomHTML.tsx new file mode 100755 index 0000000..e8bef55 --- /dev/null +++ b/src/component/Admin/Settings/Appearance/CustomHTML.tsx @@ -0,0 +1,249 @@ +import { LoadingButton } from "@mui/lab"; +import { + Box, + CircularProgress, + Container, + FormControl, + Grid, + Grid2, + Paper, + Stack, + Typography, + useTheme, +} from "@mui/material"; +import { Suspense, useContext } from "react"; +import { useTranslation } from "react-i18next"; +import { OutlineIconTextField } from "../../../Common/Form/OutlineIconTextField"; +import Logo from "../../../Common/Logo"; +import DrawerHeader from "../../../Frame/NavBar/DrawerHeader"; +import { SideNavItemComponent } from "../../../Frame/NavBar/PageNavigation"; +import StorageSummary from "../../../Frame/NavBar/StorageSummary"; +import PoweredBy from "../../../Frame/PoweredBy"; +import CloudDownload from "../../../Icons/CloudDownload"; +import CloudDownloadOutlined from "../../../Icons/CloudDownloadOutlined"; +import CubeSync from "../../../Icons/CubeSync"; +import CubeSyncFilled from "../../../Icons/CubeSyncFilled"; +import MailOutlined from "../../../Icons/MailOutlined"; +import PhoneLaptop from "../../../Icons/PhoneLaptop"; +import PhoneLaptopOutlined from "../../../Icons/PhoneLaptopOutlined"; +import SettingForm from "../../../Pages/Setting/SettingForm"; +import MonacoEditor from "../../../Viewers/CodeViewer/MonacoEditor"; +import { SettingContext } from "../SettingWrapper"; +import { NoMarginHelperText } from "../Settings"; + +export interface CustomHTMLProps {} + +const HeadlessFooterPreview = ({ footer, bottom }: { footer?: string; bottom?: string }) => { + const { t } = useTranslation("application"); + return ( + + theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900], + }} + > + + + + `${theme.spacing(2)} ${theme.spacing(3)} ${theme.spacing(3)}`, + }} + > + +
    + + {t("login.siginToYourAccount")} + + } /> + + + {t("login.continue")} + + {bottom && ( + +
    + + )} + +
    + +
    + + {footer && ( + +
    + + )} + + + + ); +}; + +const SidebarBottomPreview = ({ bottom }: { bottom?: string }) => { + return ( + + theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900], + }} + > + + + + + + + + + {bottom && ( + +
    + + )} + + + ); +}; + +const CustomHTML = ({}: CustomHTMLProps) => { + const { t } = useTranslation("dashboard"); + const { formRef, setSettings, values } = useContext(SettingContext); + const theme = useTheme(); + + return ( + + + {t("settings.customHTML")} + + + {t("settings.customHTMLDes")} + + + + + + } + > + + }> + setSettings({ headless_footer_html: e as string })} + /> + + {t("settings.headlessFooterDes")} + + + + + + } + > + + }> + setSettings({ headless_bottom_html: e as string })} + /> + + {t("settings.headlessBottomDes")} + + + + + + } + > + + }> + setSettings({ sidebar_bottom_html: e as string })} + /> + + {t("settings.sidebarBottomDes")} + + + + + ); +}; + +export default CustomHTML; diff --git a/src/component/Admin/Settings/Appearance/CustomNavItems.tsx b/src/component/Admin/Settings/Appearance/CustomNavItems.tsx new file mode 100755 index 0000000..6aa863a --- /dev/null +++ b/src/component/Admin/Settings/Appearance/CustomNavItems.tsx @@ -0,0 +1,378 @@ +import { Icon } from "@iconify/react/dist/iconify.js"; +import { + Box, + IconButton, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, + useTheme, +} from "@mui/material"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { DndProvider, useDrag, useDrop } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; +import { useTranslation } from "react-i18next"; +import { CustomNavItem } from "../../../../api/site"; +import { + DenseFilledTextField, + NoWrapCell, + NoWrapTableCell, + SecondaryButton, + StyledTableContainerPaper, +} from "../../../Common/StyledComponents"; +import Add from "../../../Icons/Add"; +import ArrowDown from "../../../Icons/ArrowDown"; +import Delete from "../../../Icons/Delete"; + +export interface CustomNavItemsProps { + value: string; + onChange: (value: string) => void; +} + +const DND_TYPE = "custom-nav-item-row"; + +// 拖拽item类型 +type DragItem = { index: number }; + +interface DraggableNavItemRowProps { + item: CustomNavItem; + index: number; + moveRow: (from: number, to: number) => void; + onDelete: (index: number) => void; + onMoveUp: () => void; + onMoveDown: () => void; + isFirst: boolean; + isLast: boolean; + inputCache: { [key: number]: { [field: string]: string | undefined } }; + onInputChange: (index: number, field: string, value: string) => void; + onInputBlur: (index: number, field: keyof CustomNavItem) => void; + IconPreview: React.ComponentType<{ iconName: string }>; + t: any; + style?: React.CSSProperties; +} + +const DraggableNavItemRow = React.memo( + React.forwardRef( + ( + { + item, + index, + moveRow, + onDelete, + onMoveUp, + onMoveDown, + isFirst, + isLast, + inputCache, + onInputChange, + onInputBlur, + IconPreview, + t, + style, + }, + ref, + ): JSX.Element => { + const [, drop] = useDrop({ + accept: DND_TYPE, + hover(dragItem, monitor) { + if (!(ref && typeof ref !== "function" && ref.current)) return; + const dragIndex = dragItem.index; + const hoverIndex = index; + if (dragIndex === hoverIndex) return; + const hoverBoundingRect = ref.current.getBoundingClientRect(); + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; + const clientOffset = monitor.getClientOffset(); + if (!clientOffset) return; + const hoverClientY = clientOffset.y - hoverBoundingRect.top; + if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) return; + if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) return; + moveRow(dragIndex, hoverIndex); + dragItem.index = hoverIndex; + }, + }); + const [{ isDragging }, drag] = useDrag({ + type: DND_TYPE, + item: { index }, + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + }), + }); + // 兼容ref为function和对象 + const setRowRef = (node: HTMLTableRowElement | null) => { + if (typeof ref === "function") { + ref(node); + } else if (ref) { + (ref as React.MutableRefObject).current = node; + } + drag(drop(node)); + }; + return ( + + + + + + onInputChange(index, "icon", e.target.value)} + onBlur={() => onInputBlur(index, "icon")} + placeholder={t("settings.iconifyNamePlaceholder")} + /> + + + onInputChange(index, "name", e.target.value)} + onBlur={() => onInputBlur(index, "name")} + placeholder={t("settings.displayNameDes")} + /> + + + onInputChange(index, "url", e.target.value)} + onBlur={() => onInputBlur(index, "url")} + placeholder="https://example.com" + /> + + + onDelete(index)}> + + + + + + + + + + + + + ); + }, + ), +); + +const CustomNavItems = ({ value, onChange }: CustomNavItemsProps) => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const [items, setItems] = useState([]); + const [inputCache, setInputCache] = useState<{ + [key: number]: { [field: string]: string | undefined }; + }>({}); + + useEffect(() => { + try { + const parsedItems = JSON.parse(value); + setItems(Array.isArray(parsedItems) ? parsedItems : []); + } catch (e) { + setItems([]); + } + }, [value]); + + const handleSave = useCallback( + (newItems: CustomNavItem[]) => { + onChange(JSON.stringify(newItems)); + }, + [onChange], + ); + + const handleDelete = useCallback( + (index: number) => { + const newItems = items.filter((_, i) => i !== index); + handleSave(newItems); + }, + [items, handleSave], + ); + + const handleAdd = useCallback(() => { + const newItems = [ + ...items, + { + name: "", + url: "", + icon: "fluent:home-24-regular", + }, + ]; + handleSave(newItems); + }, [items, handleSave]); + + const handleFieldChange = useCallback( + (index: number, field: keyof CustomNavItem, value: string) => { + const newItems = [...items]; + newItems[index] = { + ...newItems[index], + [field]: value, + }; + handleSave(newItems); + }, + [items, handleSave], + ); + + const handleInputChange = useCallback((index: number, field: string, value: string) => { + setInputCache((prev) => ({ + ...prev, + [index]: { + ...prev[index], + [field]: value, + }, + })); + }, []); + + const handleInputBlur = useCallback( + (index: number, field: keyof CustomNavItem) => { + const cachedValue = inputCache[index]?.[field]; + if (cachedValue !== undefined) { + handleFieldChange(index, field, cachedValue); + setInputCache((prev) => ({ + ...prev, + [index]: { + ...prev[index], + [field]: undefined, + }, + })); + } + }, + [inputCache, handleFieldChange], + ); + + // 拖拽排åºé€»è¾‘ + const moveRow = useCallback( + (from: number, to: number) => { + if (from === to) return; + const updated = [...items]; + const [moved] = updated.splice(from, 1); + updated.splice(to, 0, moved); + setItems(updated); + handleSave(updated); + }, + [items, handleSave], + ); + + const handleMoveUp = (idx: number) => { + if (idx <= 0) return; + moveRow(idx, idx - 1); + }; + const handleMoveDown = (idx: number) => { + if (idx >= items.length - 1) return; + moveRow(idx, idx + 1); + }; + + const IconPreview = useMemo( + () => + ({ iconName }: { iconName: string }) => { + if (!iconName) { + return ( + + ); + } + + return ( + + ); + }, + [], + ); + + return ( + + + {t("settings.customNavItems")} + + + {t("settings.customNavItemsDes")} + + + + + + + + {t("settings.icon")} + {t("settings.iconifyName")} + {t("settings.displayName")} + {t("settings.navItemUrl")} + + + + + + {items.map((item, index) => { + const rowRef = React.createRef(); + return ( + handleMoveUp(index)} + onMoveDown={() => handleMoveDown(index)} + isFirst={index === 0} + isLast={index === items.length - 1} + inputCache={inputCache} + onInputChange={handleInputChange} + onInputBlur={handleInputBlur} + IconPreview={IconPreview} + t={t} + /> + ); + })} + {items.length === 0 && ( + + + + {t("application:setting.listEmpty")} + + + + )} + +
    +
    +
    + + } onClick={handleAdd} sx={{ mt: 2 }}> + {t("settings.addNavItem")} + +
    + ); +}; + +export default CustomNavItems; diff --git a/src/component/Admin/Settings/Appearance/ThemeOptionEditDialog.tsx b/src/component/Admin/Settings/Appearance/ThemeOptionEditDialog.tsx new file mode 100755 index 0000000..da9e42c --- /dev/null +++ b/src/component/Admin/Settings/Appearance/ThemeOptionEditDialog.tsx @@ -0,0 +1,220 @@ +import { + Badge, + Box, + Button, + createTheme, + DialogContent, + Divider, + Grid, + Link, + Paper, + Stack, + TextField, + ThemeProvider, + Typography, + useTheme, +} from "@mui/material"; +import { useSnackbar } from "notistack"; +import { lazy, Suspense, useCallback, useEffect, useMemo, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { applyThemeWithOverrides } from "../../../../App"; +import CircularProgress from "../../../Common/CircularProgress"; +import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar"; +import DraggableDialog from "../../../Dialogs/DraggableDialog"; +import SideNavItem from "../../../Frame/NavBar/SideNavItem"; +import Setting from "../../../Icons/Setting"; + +const MonacoEditor = lazy(() => import("../../../Viewers/CodeViewer/MonacoEditor")); + +export interface ThemeOptionEditDialogProps { + open: boolean; + onClose: () => void; + id: string; + config: string; + onSave: (id: string, newId: string, config: string) => void; +} + +const ThemeOptionEditDialog = ({ open, onClose, id, config, onSave }: ThemeOptionEditDialogProps) => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const { enqueueSnackbar } = useSnackbar(); + const [editedConfig, setEditedConfig] = useState(config); + const [parsedConfig, setParsedConfig] = useState(null); + + useEffect(() => { + try { + setParsedConfig(JSON.parse(config)); + } catch (e) { + setParsedConfig(null); + } + }, [config]); + + useEffect(() => { + try { + setParsedConfig(JSON.parse(editedConfig)); + } catch (e) { + // Don't update parsedConfig if JSON is invalid + } + }, [editedConfig]); + + const handleSave = useCallback(() => { + try { + // Validate JSON + const parsed = JSON.parse(editedConfig); + // make sure minimum config is provided + if (!parsed.light?.palette?.primary?.main) { + throw new Error("Invalid theme config"); + } + // Get the new primary color (ID) + const newId = parsed.light.palette.primary.main; + onSave(id, newId, editedConfig); + } catch (e) { + enqueueSnackbar({ + message: t("settings.invalidThemeConfig"), + variant: "warning", + action: DefaultCloseAction, + }); + } + }, [editedConfig, id, onSave, enqueueSnackbar, t]); + + // Create preview themes + const lightTheme = useMemo(() => { + if (!parsedConfig) return null; + try { + return createTheme({ + palette: { + mode: "light", + ...parsedConfig.light.palette, + }, + }); + } catch (e) { + return null; + } + }, [parsedConfig]); + + const darkTheme = useMemo(() => { + if (!parsedConfig) return null; + try { + return createTheme({ + palette: { + mode: "dark", + ...parsedConfig.dark.palette, + }, + }); + } catch (e) { + return null; + } + }, [parsedConfig]); + + return ( + + + + + + {t("settings.themeConfiguration")} + + }> + setEditedConfig(e as string)} + /> + + + , + ]} + /> + + + + + {t("settings.themePreview")} + + + + {t("settings.lightTheme")} + + {lightTheme ? ( + + + + {t("settings.previewTitle")} + } + /> + + + + + + + + + + ) : ( + {t("settings.invalidThemePreview")} + )} + + + + + + + {t("settings.darkTheme")} + + {darkTheme ? ( + + + + {t("settings.previewTitle")} + } + /> + + + + + + + + + + ) : ( + {t("settings.invalidThemePreview")} + )} + + + + + + ); +}; + +export default ThemeOptionEditDialog; diff --git a/src/component/Admin/Settings/Appearance/ThemeOptions.tsx b/src/component/Admin/Settings/Appearance/ThemeOptions.tsx new file mode 100755 index 0000000..e61c166 --- /dev/null +++ b/src/component/Admin/Settings/Appearance/ThemeOptions.tsx @@ -0,0 +1,337 @@ +import { + Box, + IconButton, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, + useTheme, +} from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar"; +import { + NoWrapTableCell, + SecondaryButton, + StyledCheckbox, + StyledTableContainerPaper, +} from "../../../Common/StyledComponents"; +import Add from "../../../Icons/Add"; +import Delete from "../../../Icons/Delete"; +import Edit from "../../../Icons/Edit"; +import HexColorInput from "../../FileSystem/HexColorInput.tsx"; +import ThemeOptionEditDialog from "./ThemeOptionEditDialog"; + +export interface ThemeOptionsProps { + value: string; + onChange: (value: string) => void; + defaultTheme: string; + onDefaultThemeChange: (value: string) => void; +} + +interface ThemeOption { + id: string; + config: { + light: { + palette: { + primary: { + main: string; + light?: string; + dark?: string; + }; + secondary: { + main: string; + light?: string; + dark?: string; + }; + }; + }; + dark: { + palette: { + primary: { + main: string; + light?: string; + dark?: string; + }; + secondary: { + main: string; + light?: string; + dark?: string; + }; + }; + }; + }; +} + +const ThemeOptions = ({ value, onChange, defaultTheme, onDefaultThemeChange }: ThemeOptionsProps) => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const { enqueueSnackbar } = useSnackbar(); + const [options, setOptions] = useState>({}); + const [editingOption, setEditingOption] = useState<{ id: string; config: ThemeOption["config"] } | null>(null); + const [isDialogOpen, setIsDialogOpen] = useState(false); + + useEffect(() => { + try { + const parsedOptions = JSON.parse(value); + setOptions(parsedOptions); + } catch (e) { + setOptions({}); + } + }, [value]); + + const handleSave = useCallback( + (newOptions: Record) => { + onChange(JSON.stringify(newOptions)); + }, + [onChange], + ); + + const handleDelete = useCallback( + (id: string) => { + // Prevent deleting the default theme + if (id === defaultTheme) { + enqueueSnackbar({ + message: t("settings.cannotDeleteDefaultTheme"), + variant: "warning", + action: DefaultCloseAction, + }); + return; + } + + const newOptions = { ...options }; + delete newOptions[id]; + handleSave(newOptions); + }, + [options, handleSave, defaultTheme, enqueueSnackbar, t], + ); + + const handleEdit = useCallback( + (id: string) => { + setEditingOption({ id, config: options[id] }); + setIsDialogOpen(true); + }, + [options], + ); + + const handleAdd = useCallback(() => { + // Generate a new default theme option with a random color + const randomColor = `#${Math.floor(Math.random() * 16777215).toString(16)}`; + setEditingOption({ + id: randomColor, + config: { + light: { + palette: { + primary: { main: randomColor }, + secondary: { main: "#f50057" }, + }, + }, + dark: { + palette: { + primary: { main: randomColor }, + secondary: { main: "#f50057" }, + }, + }, + }, + }); + setIsDialogOpen(true); + }, []); + + const handleDialogClose = useCallback(() => { + setIsDialogOpen(false); + setEditingOption(null); + }, []); + + const handleDialogSave = useCallback( + (id: string, newId: string, config: string) => { + try { + const parsedConfig = JSON.parse(config); + const newOptions = { ...options }; + + // If ID has changed (primary color changed), delete the old entry and create a new one + if (id !== newId) { + // Check if the new ID already exists + if (newOptions[newId]) { + enqueueSnackbar({ + message: t("settings.duplicateThemeColor"), + variant: "warning", + action: DefaultCloseAction, + }); + return; + } + + // If we're changing the ID of the default theme, update the default theme reference + if (id === defaultTheme) { + onDefaultThemeChange(newId); + } + + delete newOptions[id]; + } + + newOptions[newId] = parsedConfig; + handleSave(newOptions); + setIsDialogOpen(false); + setEditingOption(null); + } catch (e) { + // Handle error + enqueueSnackbar({ + message: t("settings.invalidThemeConfig"), + variant: "warning", + action: DefaultCloseAction, + }); + } + }, + [options, handleSave, enqueueSnackbar, defaultTheme, onDefaultThemeChange, t], + ); + + const handleColorChange = useCallback( + (id: string, type: "primary" | "secondary", mode: "light" | "dark", color: string) => { + const newOptions = { ...options }; + + if (type === "primary" && mode === "light") { + // If changing the primary color (which is the ID), we need to create a new entry + const newId = color; + + // Check if the new ID already exists + if (newOptions[newId] && newId !== id) { + enqueueSnackbar({ + message: t("settings.duplicateThemeColor"), + variant: "warning", + action: DefaultCloseAction, + }); + return; + } + + const config = { ...newOptions[id] }; + config[mode].palette[type].main = color; + + // Delete old entry and create new one with the updated ID + delete newOptions[id]; + newOptions[newId] = config; + + // If we're changing the ID of the default theme, update the default theme reference + if (id === defaultTheme) { + onDefaultThemeChange(newId); + } + } else { + // For other colors, just update the value + newOptions[id][mode].palette[type].main = color; + } + + handleSave(newOptions); + }, + [options, handleSave, enqueueSnackbar, t, defaultTheme, onDefaultThemeChange], + ); + + const handleDefaultThemeChange = useCallback( + (id: string) => { + onDefaultThemeChange(id); + }, + [onDefaultThemeChange], + ); + + const optionsArray = useMemo(() => { + return Object.entries(options).map(([id, config]) => ({ + id, + config, + })); + }, [options]); + + return ( + + + {t("settings.themeOptions")} + + + {t("settings.themeOptionsDes")} + + + {optionsArray.length > 0 && ( + + + + + {t("settings.defaultTheme")} + {t("settings.primaryColor")} + {t("settings.secondaryColor")} + {t("settings.primaryColorDark")} + {t("settings.secondaryColorDark")} + + + + + {optionsArray.map((option) => ( + + + handleDefaultThemeChange(option.id)} + /> + + + handleColorChange(option.id, "primary", "light", color)} + /> + + + handleColorChange(option.id, "secondary", "light", color)} + /> + + + handleColorChange(option.id, "primary", "dark", color)} + /> + + + handleColorChange(option.id, "secondary", "dark", color)} + /> + + + handleEdit(option.id)}> + + + handleDelete(option.id)} + disabled={optionsArray.length === 1 || option.id === defaultTheme} + > + + + + + ))} + +
    +
    + )} + + } onClick={handleAdd} sx={{ mt: 2 }}> + {t("settings.addThemeOption")} + + + {editingOption && ( + + )} +
    + ); +}; + +export default ThemeOptions; diff --git a/src/component/Admin/Settings/Captcha/CapCaptcha.tsx b/src/component/Admin/Settings/Captcha/CapCaptcha.tsx new file mode 100755 index 0000000..eb839f5 --- /dev/null +++ b/src/component/Admin/Settings/Captcha/CapCaptcha.tsx @@ -0,0 +1,139 @@ +import { Trans, useTranslation } from "react-i18next"; +import { FormControl, Link, Stack, ListItemText } from "@mui/material"; +import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; +import { DenseFilledTextField, DenseSelect } from "../../../Common/StyledComponents.tsx"; +import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx"; +import * as React from "react"; +import { NoMarginHelperText } from "../Settings.tsx"; + +export interface CapCaptchaProps { + values: { + [key: string]: string; + }; + setSettings: (settings: { [key: string]: string }) => void; +} + +const CapCaptcha = ({ values, setSettings }: CapCaptchaProps) => { + const { t } = useTranslation("dashboard"); + return ( + + + + + setSettings({ + captcha_cap_instance_url: e.target.value, + }) + } + placeholder="https://cap.example.com" + required + /> + + ]} + /> + + + + + + + setSettings({ + captcha_cap_site_key: e.target.value, + }) + } + required + /> + + ]} + /> + + + + + + + setSettings({ + captcha_cap_secret_key: e.target.value, + }) + } + type="password" + required + /> + + ]} + /> + + + + + + + setSettings({ + captcha_cap_asset_server: e.target.value, + }) + } + > + + + {t("settings.capAssetServerJsdelivr")} + + + + + {t("settings.capAssetServerUnpkg")} + + + + + {t("settings.capAssetServerInstance")} + + + + + , + ]} + /> + + + + + ); +}; + +export default CapCaptcha; diff --git a/src/component/Admin/Settings/Captcha/Captcha.tsx b/src/component/Admin/Settings/Captcha/Captcha.tsx new file mode 100755 index 0000000..e14a011 --- /dev/null +++ b/src/component/Admin/Settings/Captcha/Captcha.tsx @@ -0,0 +1,158 @@ +import { Box, Collapse, FormControl, FormControlLabel, ListItemText, Stack, Switch, Typography } from "@mui/material"; +import { useContext } from "react"; +import { useTranslation } from "react-i18next"; +import { CaptchaType } from "../../../../api/site.ts"; +import { isTrueVal } from "../../../../session/utils.ts"; +import { DenseSelect } from "../../../Common/StyledComponents.tsx"; +import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx"; +import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings.tsx"; +import { SettingContext } from "../SettingWrapper.tsx"; +import CapCaptcha from "./CapCaptcha.tsx"; +import GraphicCaptcha from "./GraphicCaptcha.tsx"; +import ReCaptcha from "./ReCaptcha.tsx"; +import TurnstileCaptcha from "./TurnstileCaptcha.tsx"; + +const Captcha = () => { + const { t } = useTranslation("dashboard"); + const { formRef, setSettings, values } = useContext(SettingContext); + + return ( + e.preventDefault()}> + + + + {t("nav.captcha")} + + + + + + setSettings({ + login_captcha: e.target.checked ? "1" : "0", + }) + } + /> + } + label={t("settings.captchaForLogin")} + /> + {t("settings.captchaForLoginDes")} + + + + + + setSettings({ + reg_captcha: e.target.checked ? "1" : "0", + }) + } + /> + } + label={t("settings.captchaForSignup")} + /> + {t("settings.captchaForSignupDes")} + + + + + + setSettings({ + forget_captcha: e.target.checked ? "1" : "0", + }) + } + /> + } + label={t("settings.captchaForReset")} + /> + {t("settings.captchaForResetDes")} + + + + + + + {t("settings.captchaType")} + + + + + + setSettings({ + captcha_type: e.target.value as string, + }) + } + value={values.captcha_type} + > + + + {t("settings.plainCaptcha")} + + + + + {t("settings.reCaptchaV2")} + + + + + {t("settings.turnstile")} + + + + + {t("settings.cap")} + + + + {t("settings.captchaTypeDes")} + + + + + + + + + + + + + + + + + + + ); +}; + +export default Captcha; diff --git a/src/component/Admin/Settings/Captcha/GraphicCaptcha.tsx b/src/component/Admin/Settings/Captcha/GraphicCaptcha.tsx new file mode 100755 index 0000000..9bc43c0 --- /dev/null +++ b/src/component/Admin/Settings/Captcha/GraphicCaptcha.tsx @@ -0,0 +1,118 @@ +import { Collapse, FormControl, FormControlLabel, ListItemText, Stack, Switch } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { isTrueVal } from "../../../../session/utils.ts"; +import { DenseFilledTextField, DenseSelect } from "../../../Common/StyledComponents.tsx"; +import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx"; +import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; +import { NoMarginHelperText } from "../Settings.tsx"; + +export interface GraphicCaptchaProps { + values: { + [key: string]: string; + }; + setSettings: (settings: { [key: string]: string }) => void; +} + +const GraphicCaptcha = ({ values, setSettings }: GraphicCaptchaProps) => { + const { t } = useTranslation("dashboard"); + return ( + + + + + setSettings({ + captcha_mode: e.target.value as string, + }) + } + value={values.captcha_mode} + > + {["captchaModeNumber", "captchaModeLetter", "captchaModeMath", "captchaModeNumberLetter"].map((k, i) => ( + + + {t(`settings.${k}`)} + + + ))} + + + + + + + + setSettings({ + captcha_CaptchaLen: e.target.value, + }) + } + /> + {t("settings.captchaLengthDes")} + + + + {[ + { + name: "complexOfNoiseText", + field: "captcha_ComplexOfNoiseText", + }, + { + name: "complexOfNoiseDot", + field: "captcha_ComplexOfNoiseDot", + }, + { + name: "showHollowLine", + field: "captcha_IsShowHollowLine", + }, + { + name: "showNoiseDot", + field: "captcha_IsShowNoiseDot", + }, + { + name: "showNoiseText", + field: "captcha_IsShowNoiseText", + }, + { + name: "showSlimeLine", + field: "captcha_IsShowSlimeLine", + }, + { + name: "showSineLine", + field: "captcha_IsShowSineLine", + }, + ].map((v) => ( + + + + setSettings({ + [v.field]: e.target.checked ? "1" : "0", + }) + } + /> + } + label={t(`settings.${v.name}`)} + /> + + + ))} + + ); +}; + +export default GraphicCaptcha; diff --git a/src/component/Admin/Settings/Captcha/ReCaptcha.tsx b/src/component/Admin/Settings/Captcha/ReCaptcha.tsx new file mode 100755 index 0000000..5a22d76 --- /dev/null +++ b/src/component/Admin/Settings/Captcha/ReCaptcha.tsx @@ -0,0 +1,63 @@ +import { Trans, useTranslation } from "react-i18next"; +import { FormControl, Link, Stack } from "@mui/material"; +import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; +import { DenseFilledTextField } from "../../../Common/StyledComponents.tsx"; +import * as React from "react"; +import { NoMarginHelperText } from "../Settings.tsx"; + +export interface ReCaptchaProps { + values: { + [key: string]: string; + }; + setSettings: (settings: { [key: string]: string }) => void; +} + +const ReCaptcha = ({ values, setSettings }: ReCaptchaProps) => { + const { t } = useTranslation("dashboard"); + return ( + + + + + setSettings({ + captcha_ReCaptchaKey: e.target.value, + }) + } + required + /> + + ]} + /> + + + + + + + setSettings({ + captcha_ReCaptchaSecret: e.target.value, + }) + } + required + /> + + ]} + /> + + + + + ); +}; + +export default ReCaptcha; diff --git a/src/component/Admin/Settings/Captcha/TurnstileCaptcha.tsx b/src/component/Admin/Settings/Captcha/TurnstileCaptcha.tsx new file mode 100755 index 0000000..d1ced86 --- /dev/null +++ b/src/component/Admin/Settings/Captcha/TurnstileCaptcha.tsx @@ -0,0 +1,63 @@ +import { Trans, useTranslation } from "react-i18next"; +import { FormControl, Link, Stack } from "@mui/material"; +import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; +import { DenseFilledTextField } from "../../../Common/StyledComponents.tsx"; +import * as React from "react"; +import { NoMarginHelperText } from "../Settings.tsx"; + +export interface TurnstileCaptchaProps { + values: { + [key: string]: string; + }; + setSettings: (settings: { [key: string]: string }) => void; +} + +const Turnstile = ({ values, setSettings }: TurnstileCaptchaProps) => { + const { t } = useTranslation("dashboard"); + return ( + + + + + setSettings({ + captcha_turnstile_site_key: e.target.value, + }) + } + required + /> + + ]} + /> + + + + + + + setSettings({ + captcha_turnstile_site_secret: e.target.value, + }) + } + required + /> + + ]} + /> + + + + + ); +}; + +export default Turnstile; diff --git a/src/component/Admin/Settings/Email/Email.tsx b/src/component/Admin/Settings/Email/Email.tsx new file mode 100755 index 0000000..0dc38f4 --- /dev/null +++ b/src/component/Admin/Settings/Email/Email.tsx @@ -0,0 +1,205 @@ +import { Box, DialogContent, FormControl, FormControlLabel, Stack, Switch, Typography } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useContext, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { sendTestSMTP } from "../../../../api/api.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import { isTrueVal } from "../../../../session/utils.ts"; +import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx"; +import { DenseFilledTextField, SecondaryButton } from "../../../Common/StyledComponents.tsx"; +import DraggableDialog, { StyledDialogContentText } from "../../../Dialogs/DraggableDialog.tsx"; +import MailOutlined from "../../../Icons/MailOutlined.tsx"; +import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings.tsx"; +import { SettingContext } from "../SettingWrapper.tsx"; +import EmailTemplates from "./EmailTemplates.tsx"; + +const Email = () => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + const { formRef, setSettings, values } = useContext(SettingContext); + const [testEmailOpen, setTestEmailOpen] = useState(false); + const [testEmailAddress, setTestEmailAddress] = useState(""); + const [sending, setSending] = useState(false); + + const handleTestEmail = async () => { + setSending(true); + try { + await dispatch( + sendTestSMTP({ + to: testEmailAddress, + settings: values, + }), + ); + enqueueSnackbar({ + message: t("settings.testMailSent"), + variant: "success", + action: DefaultCloseAction, + }); + setTestEmailOpen(false); + } catch (error) { + } finally { + setSending(false); + } + }; + + return ( + e.preventDefault()}> + + setTestEmailOpen(false), + }} + loading={sending} + showActions + showCancel + onAccept={handleTestEmail} + title={t("settings.testSMTPSettings")} + > + + {t("settings.testSMTPTooltip")} + + setTestEmailAddress(e.target.value)} + type="email" + fullWidth + /> + + + + + + + {t("settings.smtp")} + + + + + setSettings({ fromName: e.target.value })} + /> + {t("settings.senderNameDes")} + + + + + + setSettings({ fromAdress: e.target.value })} + /> + {t("settings.senderAddressDes")} + + + + + + setSettings({ smtpHost: e.target.value })} + /> + {t("settings.smtpServerDes")} + + + + + + setSettings({ smtpPort: e.target.value })} + /> + {t("settings.smtpPortDes")} + + + + + + setSettings({ smtpUser: e.target.value })} + /> + {t("settings.smtpUsernameDes")} + + + + + + setSettings({ smtpPass: e.target.value })} + /> + {t("settings.smtpPasswordDes")} + + + + + + setSettings({ replyTo: e.target.value })} + /> + {t("settings.replyToAddressDes")} + + + + + + setSettings({ smtpEncryption: e.target.checked ? "1" : "0" })} + /> + } + label={t("settings.enforceSSL")} + /> + {t("settings.enforceSSLDes")} + + + + + + setSettings({ mail_keepalive: e.target.value })} + /> + {t("settings.smtpTTLDes")} + + + + + } onClick={() => setTestEmailOpen(true)}> + {t("settings.sendTestEmail")} + + + + + + {/* Email Templates Section */} + + + + ); +}; + +export default Email; diff --git a/src/component/Admin/Settings/Email/EmailTemplateEditor.tsx b/src/component/Admin/Settings/Email/EmailTemplateEditor.tsx new file mode 100755 index 0000000..f9e2e23 --- /dev/null +++ b/src/component/Admin/Settings/Email/EmailTemplateEditor.tsx @@ -0,0 +1,307 @@ +import { Delete } from "@mui/icons-material"; +import { + Box, + Button, + DialogContent, + FormControl, + Link, + ListItemText, + Tab, + Tabs, + Typography, + useTheme, +} from "@mui/material"; +import React, { lazy, Suspense, useCallback, useEffect, useRef, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { languages } from "../../../../i18n.ts"; +import CircularProgress from "../../../Common/CircularProgress.tsx"; +import { DenseFilledTextField, DenseSelect, SecondaryButton } from "../../../Common/StyledComponents.tsx"; +import Add from "../../../Icons/Add"; +import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx"; +import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; +import MagicVarDialog from "../../Common/MagicVarDialog.tsx"; +import { NoMarginHelperText } from "../Settings.tsx"; + +const MonacoEditor = lazy(() => import("../../../Viewers/CodeViewer/MonacoEditor.tsx")); + +interface TemplateItem { + language: string; + title: string; + body: string; +} + +interface EmailTemplateEditorProps { + value: string; + onChange: (value: string) => void; + templateType: string; + magicVars: MagicVar[]; +} + +const EmailTemplateEditor: React.FC = ({ value, onChange, templateType, magicVars }) => { + const theme = useTheme(); + const { t } = useTranslation("dashboard"); + const [templates, setTemplates] = useState([]); + const [currentTab, setCurrentTab] = useState(0); + const [addLanguageOpen, setAddLanguageOpen] = useState(false); + const [newLanguageCode, setNewLanguageCode] = useState(""); + const isUpdatingFromProp = useRef(false); + const [magicVarOpen, setMagicVarOpen] = useState(false); + + // Parse templates when component mounts or value changes + useEffect(() => { + try { + isUpdatingFromProp.current = true; + const parsedTemplates = value ? JSON.parse(value) : []; + setTemplates(parsedTemplates); + // If no templates, create a default English one + if (parsedTemplates.length === 0) { + setTemplates([{ language: "en-US", title: "", body: "" }]); + } + if (currentTab > parsedTemplates.length) { + setCurrentTab(0); + } + } catch (e) { + console.error("Failed to parse email template:", e); + setTemplates([{ language: "en-US", title: "", body: "" }]); + } finally { + // Use setTimeout to ensure this runs after React finishes the update + setTimeout(() => { + isUpdatingFromProp.current = true; // Prevent infinite loop + }, 0); + } + }, [value]); + + // Update the parent component when templates change, but only from user interaction + useEffect(() => { + if (templates.length > 0 && !isUpdatingFromProp.current) { + onChange(JSON.stringify(templates)); + } + }, [templates, onChange]); + + const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { + setCurrentTab(newValue); + }; + + const updateTemplate = (index: number, field: keyof TemplateItem, newValue: string) => { + isUpdatingFromProp.current = false; // Ensure this is a user interaction + const updatedTemplates = [...templates]; + updatedTemplates[index] = { + ...updatedTemplates[index], + [field]: newValue, + }; + setTemplates(updatedTemplates); + }; + + const addNewLanguage = () => { + if (!newLanguageCode.trim()) return; + + // Check if language already exists + const langTemplateIndex = templates.findIndex((l) => l.language === newLanguageCode); + if (langTemplateIndex !== -1) { + setNewLanguageCode(""); + setAddLanguageOpen(false); + + setCurrentTab(langTemplateIndex); + return; + } + + // Add new language template + isUpdatingFromProp.current = false; // Ensure this is a user interaction + setTemplates([...templates, { language: newLanguageCode, title: "", body: "" }]); + + // Reset and close dialog + setNewLanguageCode(""); + setAddLanguageOpen(false); + + // Switch to the new tab + setCurrentTab(templates.length); + }; + + const removeLanguage = (index: number) => { + isUpdatingFromProp.current = false; // Ensure this is a user interaction + const updatedTemplates = templates.filter((_, i) => i !== index); + setTemplates(updatedTemplates); + + if (currentTab >= updatedTemplates.length) { + setCurrentTab(updatedTemplates.length - 1); // Move to the last tab if current is out of range + } + }; + + const setPreferredLanguage = (index: number) => { + isUpdatingFromProp.current = false; // Ensure this is a user interaction + setTemplates([templates[index], ...templates.filter((_, i) => i !== index)]); + setCurrentTab(0); // Switch to the first tab as the preferred language is now at the top + }; + + const openMagicVar = useCallback((e: React.MouseEvent) => { + setMagicVarOpen(true); + e.stopPropagation(); + e.preventDefault(); + }, []); + + return ( + + + + {templates.map((template, index) => { + const lang = languages.find((l) => l.code === template.language); + return ; + })} + + + + {templates.map((template, index) => ( + + ))} + {/* Add Language Dialog */} + setAddLanguageOpen(false), + }} + > + + + + setNewLanguageCode(e.target.value as string)}> + {languages.map((l) => ( + + + {l.displayName} + + + ))} + + {t("settings.languageCodeDes")} + + + + + setMagicVarOpen(false)} /> + + ); +}; + +export default EmailTemplateEditor; diff --git a/src/component/Admin/Settings/Email/EmailTemplates.tsx b/src/component/Admin/Settings/Email/EmailTemplates.tsx new file mode 100755 index 0000000..b05f75e --- /dev/null +++ b/src/component/Admin/Settings/Email/EmailTemplates.tsx @@ -0,0 +1,176 @@ +import { ExpandMoreRounded } from "@mui/icons-material"; +import { Box, Typography } from "@mui/material"; +import AccordionDetails from "@mui/material/AccordionDetails"; +import React, { useContext, useState } from "react"; +import { useTranslation } from "react-i18next"; +import SettingForm, { ProChip } from "../../../Pages/Setting/SettingForm.tsx"; +import { MagicVar } from "../../Common/MagicVarDialog.tsx"; +import ProDialog from "../../Common/ProDialog.tsx"; +import { SettingContext } from "../SettingWrapper.tsx"; +import { SettingSection, SettingSectionContent } from "../Settings.tsx"; +import { AccordionSummary, StyledAccordion } from "../UserSession/SSOSettings.tsx"; +import EmailTemplateEditor from "./EmailTemplateEditor.tsx"; +interface EmailTemplate { + key: string; + title: string; + description: string; + magicVars: MagicVar[]; + pro: boolean; +} + +const commonMagicVars: MagicVar[] = [ + { + value: "settings.mainTitle", + name: "{{ .CommonContext.SiteBasic.Name }}", + example: "Cloudreve", + }, + { + value: "settings.siteDescription", + name: "{{ .CommonContext.SiteBasic.Description }}", + example: "Another Cloudreve instance", + }, + { + value: "settings.siteID", + name: "{{ .CommonContext.SiteBasic.ID }}", + example: "123e4567-e89b-12d3-a456-426614174000", + }, + { + value: "settings.logo", + name: "{{ .CommonContext.Logo.Normal }}", + example: "https://cloudreve.org/logo.svg", + }, + { + value: "settings.logo", + name: "{{ .CommonContext.Logo.Light }}", + example: "https://cloudreve.org/logo_light.svg", + }, + { + value: "settings.siteURL", + name: "{{ .CommonContext.SiteUrl }}", + example: "https://cloudreve.org", + }, +]; + +const userMagicVars: MagicVar[] = [ + { + value: "policy.magicVar.uid", + name: "{{ .User.ID }}", + example: "2534", + }, + { + value: "application:login.email", + name: "{{ .User.Email }}", + example: "example@cloudreve.org", + }, + { + value: "application:setting.nickname", + name: "{{ .User.Nick }}", + example: "Aaron Liu", + }, + { + value: "user.usedStorage", + name: "{{ .User.Storage }}", + example: "123221000", + }, +]; + +const EmailTemplates: React.FC = () => { + const { t } = useTranslation("dashboard"); + const { setSettings, values } = useContext(SettingContext); + const [proOpen, setProOpen] = useState(false); + + // Template setting keys + const templateSettings = [ + { + key: "mail_receipt_template", + title: "receiptEmailTemplate", + description: "receiptEmailTemplateDes", + pro: true, + }, + { + key: "mail_activation_template", + title: "activationEmailTemplate", + description: "activationEmailTemplateDes", + magicVars: [ + ...commonMagicVars, + ...userMagicVars, + { + value: "settings.activateUrl", + name: "{{ .Url }}", + example: "https://cloudreve.org/activate", + }, + ], + }, + { + key: "mail_exceed_quota_template", + title: "quotaExceededEmailTemplate", + description: "quotaExceededEmailTemplateDes", + pro: true, + }, + { + key: "mail_reset_template", + title: "resetPasswordEmailTemplate", + description: "resetPasswordEmailTemplateDes", + magicVars: [ + ...commonMagicVars, + ...userMagicVars, + { + value: "settings.resetUrl", + name: "{{ .Url }}", + example: "https://cloudreve.org/reset", + }, + ], + }, + ]; + + const handleProClick = (e: React.MouseEvent) => { + e.preventDefault(); + setProOpen(true); + }; + + return ( + + setProOpen(false)} /> + + {t("settings.emailTemplates")} + + + + {templateSettings.map((template) => ( + + }> + + {t("settings." + template.title)} + {template.pro && } + + + + + {t("settings." + template.description)}{" "} + + + + setSettings({ [template.key]: value })} + templateType={template.key} + /> + + + + + ))} + + + + ); +}; + +export default EmailTemplates; diff --git a/src/component/Admin/Settings/Event/Events.tsx b/src/component/Admin/Settings/Event/Events.tsx new file mode 100755 index 0000000..dcb300b --- /dev/null +++ b/src/component/Admin/Settings/Event/Events.tsx @@ -0,0 +1,199 @@ +import { + Box, + Checkbox, + Divider, + FormControl, + FormControlLabel, + FormGroup, + Grid, + Stack, + Typography, +} from "@mui/material"; +import { useContext, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { AuditLogType } from "../../../../api/explorer"; +import { ProChip } from "../../../Pages/Setting/SettingForm"; +import ProDialog from "../../Common/ProDialog"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings"; +import { SettingContext } from "../SettingWrapper"; + +// Categorize audit events +export const eventCategories = { + system: { + title: "settings.systemEvents", + description: "settings.systemEventsDes", + events: [AuditLogType.server_start], + }, + user: { + title: "settings.userEvents", + description: "settings.userEventsDes", + events: [ + AuditLogType.user_signup, + AuditLogType.user_activated, + AuditLogType.user_login, + AuditLogType.user_login_failed, + AuditLogType.user_token_refresh, + AuditLogType.user_changed, + AuditLogType.user_exceed_quota_notified, + AuditLogType.change_nick, + AuditLogType.change_avatar, + AuditLogType.change_password, + AuditLogType.enable_2fa, + AuditLogType.disable_2fa, + AuditLogType.add_passkey, + AuditLogType.remove_passkey, + AuditLogType.link_account, + AuditLogType.unlink_account, + AuditLogType.report_abuse, + ], + }, + file: { + title: "settings.fileEvents", + description: "settings.fileEventsDes", + events: [ + AuditLogType.file_create, + AuditLogType.file_imported, + AuditLogType.file_rename, + AuditLogType.set_file_permission, + AuditLogType.entity_uploaded, + AuditLogType.entity_downloaded, + AuditLogType.copy_from, + AuditLogType.copy_to, + AuditLogType.move_to, + AuditLogType.delete_file, + AuditLogType.move_to_trash, + AuditLogType.update_metadata, + AuditLogType.get_direct_link, + AuditLogType.delete_direct_link, + AuditLogType.update_view, + ], + }, + share: { + title: "settings.shareEvents", + description: "settings.shareEventsDes", + events: [AuditLogType.share, AuditLogType.share_link_viewed, AuditLogType.edit_share, AuditLogType.delete_share], + }, + version: { + title: "settings.versionEvents", + description: "settings.versionEventsDes", + events: [AuditLogType.set_current_version, AuditLogType.delete_version], + }, + media: { + title: "settings.mediaEvents", + description: "settings.mediaEventsDes", + events: [AuditLogType.thumb_generated, AuditLogType.live_photo_uploaded], + }, + filesystem: { + title: "settings.filesystemEvents", + description: "settings.filesystemEventsDes", + events: [AuditLogType.mount, AuditLogType.relocate, AuditLogType.create_archive, AuditLogType.extract_archive], + }, + webdav: { + title: "settings.webdavEvents", + description: "settings.webdavEventsDes", + events: [ + AuditLogType.webdav_login_failed, + AuditLogType.webdav_account_create, + AuditLogType.webdav_account_update, + AuditLogType.webdav_account_delete, + ], + }, + payment: { + title: "settings.paymentEvents", + description: "settings.paymentEventsDes", + events: [ + AuditLogType.payment_created, + AuditLogType.points_change, + AuditLogType.payment_paid, + AuditLogType.payment_fulfilled, + AuditLogType.payment_fulfill_failed, + AuditLogType.storage_added, + AuditLogType.group_changed, + AuditLogType.membership_unsubscribe, + AuditLogType.redeem_gift_code, + ], + }, + email: { + title: "settings.emailEvents", + description: "settings.emailEventsDes", + events: [AuditLogType.email_sent], + }, +}; + +// Get event name from AuditLogType +export const getEventName = (eventType: number): string => { + return Object.entries(AuditLogType).find(([_, value]) => value === eventType)?.[0] || `event_${eventType}`; +}; + +const Events = () => { + const { t } = useTranslation("dashboard"); + const { formRef, setSettings, values } = useContext(SettingContext); + const [proOpen, setProOpen] = useState(false); + + const handleProClick = (e: React.MouseEvent) => { + e.preventDefault(); + setProOpen(true); + }; + + return ( + e.preventDefault()}> + setProOpen(false)} /> + + + + {t("settings.auditLog")} + + + {t("settings.auditLogDes")} + + + {Object.entries(eventCategories).map(([categoryKey, category]) => ( + + + {t(category.title)} + + {t(category.description)} + + + + + + + } + label={t("settings.toggleAll")} + /> + {t("settings.toggleAllDes")} + + + + {category.events.map((eventType) => ( + + } + label={t(`settings.event.${getEventName(eventType)}`, getEventName(eventType))} + /> + + ))} + + + + + ))} + + + + ); +}; + +export default Events; diff --git a/src/component/Admin/Settings/Media/Extractors.tsx b/src/component/Admin/Settings/Media/Extractors.tsx new file mode 100755 index 0000000..6a07332 --- /dev/null +++ b/src/component/Admin/Settings/Media/Extractors.tsx @@ -0,0 +1,261 @@ +import { ExpandMoreRounded } from "@mui/icons-material"; +import { LoadingButton } from "@mui/lab"; +import { + AccordionDetails, + Box, + FormControl, + FormControlLabel, + InputAdornment, + Switch, + Typography, +} from "@mui/material"; +import { useSnackbar } from "notistack"; +import * as React from "react"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { sendTestThumbGeneratorExecutable } from "../../../../api/api.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import { isTrueVal } from "../../../../session/utils.ts"; +import SizeInput from "../../../Common/SizeInput.tsx"; +import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx"; +import { DenseFilledTextField, StyledCheckbox } from "../../../Common/StyledComponents.tsx"; +import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; +import { NoMarginHelperText, SettingSectionContent } from "../Settings.tsx"; +import { AccordionSummary, StyledAccordion } from "../UserSession/SSOSettings.tsx"; + +export interface ExtractorsProps { + values: { + [key: string]: any; + }; + setSetting: (v: { [key: string]: any }) => void; +} + +interface ExtractorRenderProps { + name: string; + des: string; + enableFlag?: string; + executableSetting?: string; + maxSizeLocalSetting?: string; + maxSizeRemoteSetting?: string; + additionalSettings?: { + name: string; + label: string; + des: string; + type?: "switch"; + }[]; +} + +const extractors: ExtractorRenderProps[] = [ + { + name: "exif", + des: "exifDes", + enableFlag: "media_meta_exif", + maxSizeLocalSetting: "media_meta_exif_size_local", + maxSizeRemoteSetting: "media_meta_exif_size_remote", + additionalSettings: [ + { + name: "media_meta_exif_brute_force", + label: "exifBruteForce", + des: "exifBruteForceDes", + type: "switch", + }, + ], + }, + { + name: "music", + des: "musicDes", + enableFlag: "media_meta_music", + maxSizeLocalSetting: "media_meta_music_size_local", + maxSizeRemoteSetting: "media_exif_music_size_remote", + }, + { + name: "ffprobe", + des: "ffprobeDes", + enableFlag: "media_meta_ffprobe", + executableSetting: "media_meta_ffprobe_path", + maxSizeLocalSetting: "media_meta_ffprobe_size_local", + maxSizeRemoteSetting: "media_meta_ffprobe_size_remote", + }, + { + name: "geocoding", + des: "geocodingDes", + enableFlag: "media_meta_geocoding", + additionalSettings: [ + { + name: "media_meta_geocoding_mapbox_ak", + label: "mapboxAK", + des: "mapboxAKDes", + }, + ], + }, +]; + +const Extractors = ({ values, setSetting }: ExtractorsProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [testing, setTesting] = useState(false); + const { enqueueSnackbar } = useSnackbar(); + + const handleEnableChange = (name: string) => (e: React.ChangeEvent) => { + setSetting({ + [name]: e.target.checked ? "1" : "0", + }); + + const newValues = { ...values, [name]: e.target.checked ? "1" : "0" }; + if (isTrueVal(newValues["media_meta_geocoding"]) && !isTrueVal(newValues["media_meta_exif"])) { + enqueueSnackbar({ + message: t("settings.geocodingDependencyWarning"), + variant: "warning", + action: DefaultCloseAction, + }); + } + }; + + const doTest = (name: string, executable: string) => { + setTesting(true); + dispatch( + sendTestThumbGeneratorExecutable({ + name, + executable, + }), + ) + .then((res) => { + enqueueSnackbar({ + message: t("settings.executableTestSuccess", { version: res }), + variant: "success", + action: DefaultCloseAction, + }); + }) + .finally(() => { + setTesting(false); + }); + }; + + return ( + + {extractors.map((e) => ( + + }> + event.stopPropagation()} + onFocus={(event) => event.stopPropagation()} + control={ + + } + label={t(`settings.${e.name}`)} + /> + + + + {t(`settings.${e.des}`)} + + + {e.executableSetting && ( + + + + doTest(e.name, values[e.executableSetting ?? ""])} + loading={testing} + color="primary" + > + {t("settings.executableTest")} + + + ), + }} + onChange={(ev) => + setSetting({ + [e.executableSetting ?? ""]: ev.target.value, + }) + } + /> + {t("settings.executableDes")} + + + )} + {e.maxSizeLocalSetting && ( + + + + setSetting({ + [e.maxSizeLocalSetting ?? ""]: v.toString(), + }) + } + /> + {t("settings.maxSizeLocalDes")} + + + )} + {e.maxSizeRemoteSetting && ( + + + + setSetting({ + [e.maxSizeRemoteSetting ?? ""]: v.toString(), + }) + } + /> + {t("settings.maxSizeRemoteDes")} + + + )} + {e.additionalSettings?.map((setting) => ( + + + {setting.type === "switch" ? ( + + setSetting({ + [setting.name]: ev.target.checked ? "1" : "0", + }) + } + /> + } + label={t(`settings.${setting.label}`)} + /> + ) : ( + + setSetting({ + [setting.name]: ev.target.value, + }) + } + /> + )} + {t(`settings.${setting.des}`)} + + + ))} + + + + ))} + + ); +}; + +export default Extractors; diff --git a/src/component/Admin/Settings/Media/Generators.tsx b/src/component/Admin/Settings/Media/Generators.tsx new file mode 100755 index 0000000..908520d --- /dev/null +++ b/src/component/Admin/Settings/Media/Generators.tsx @@ -0,0 +1,273 @@ +import { ExpandMoreRounded } from "@mui/icons-material"; +import { LoadingButton } from "@mui/lab"; +import { AccordionDetails, Box, FormControl, FormControlLabel, InputAdornment, Typography } from "@mui/material"; +import { useSnackbar } from "notistack"; +import * as React from "react"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { sendTestThumbGeneratorExecutable } from "../../../../api/api.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import { isTrueVal } from "../../../../session/utils.ts"; +import SizeInput from "../../../Common/SizeInput.tsx"; +import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx"; +import { DenseFilledTextField, StyledCheckbox } from "../../../Common/StyledComponents.tsx"; +import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; +import { NoMarginHelperText, SettingSectionContent } from "../Settings.tsx"; +import { AccordionSummary, StyledAccordion } from "../UserSession/SSOSettings.tsx"; + +export interface GeneratorsProps { + values: { + [key: string]: any; + }; + setSetting: (v: { [key: string]: any }) => void; +} + +interface GeneratorRenderProps { + name: string; + des: string; + enableFlag?: string; + executableSetting?: string; + maxSizeSetting?: string; + readOnly?: boolean; + inputs?: { + name: string; + label: string; + des: string; + required?: boolean; + }[]; +} + +const generators: GeneratorRenderProps[] = [ + { + name: "policyBuiltin", + des: "policyBuiltinDes", + readOnly: true, + }, + { + name: "musicCover", + des: "musicCoverDes", + enableFlag: "thumb_music_cover_enabled", + maxSizeSetting: "thumb_music_cover_max_size", + inputs: [ + { + name: "thumb_music_cover_exts", + label: "generatorExts", + des: "generatorExtsDes", + }, + ], + }, + { + name: "libreOffice", + des: "libreOfficeDes", + enableFlag: "thumb_libreoffice_enabled", + maxSizeSetting: "thumb_libreoffice_max_size", + executableSetting: "thumb_libreoffice_path", + inputs: [ + { + name: "thumb_libreoffice_exts", + label: "generatorExts", + des: "generatorExtsDes", + }, + ], + }, + { + name: "libraw", + des: "librawDes", + enableFlag: "thumb_libraw_enabled", + maxSizeSetting: "thumb_libraw_max_size", + executableSetting: "thumb_libraw_path", + inputs: [ + { + name: "thumb_libraw_exts", + label: "generatorExts", + des: "generatorExtsDes", + }, + ], + }, + { + name: "vips", + des: "vipsDes", + enableFlag: "thumb_vips_enabled", + maxSizeSetting: "thumb_vips_max_size", + executableSetting: "thumb_vips_path", + inputs: [ + { + name: "thumb_vips_exts", + label: "generatorExts", + des: "generatorExtsDes", + }, + ], + }, + { + name: "ffmpeg", + des: "ffmpegDes", + enableFlag: "thumb_ffmpeg_enabled", + maxSizeSetting: "thumb_ffmpeg_max_size", + executableSetting: "thumb_ffmpeg_path", + inputs: [ + { + name: "thumb_ffmpeg_exts", + label: "generatorExts", + des: "generatorExtsDes", + }, + { + name: "thumb_ffmpeg_seek", + label: "ffmpegSeek", + des: "ffmpegSeekDes", + required: true, + }, + { + name: "thumb_ffmpeg_extra_args", + label: "ffmpegExtraArgs", + des: "ffmpegExtraArgsDes", + }, + ], + }, + { + name: "cloudreveBuiltin", + maxSizeSetting: "thumb_builtin_max_size", + des: "cloudreveBuiltinDes", + enableFlag: "thumb_builtin_enabled", + }, +]; + +const Generators = ({ values, setSetting }: GeneratorsProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [testing, setTesting] = useState(false); + const { enqueueSnackbar } = useSnackbar(); + + const handleEnableChange = (name: string) => (e: React.ChangeEvent) => { + setSetting({ + [name]: e.target.checked ? "1" : "0", + }); + const newValues = { ...values, [name]: e.target.checked ? "1" : "0" }; + if ( + (newValues["thumb_libreoffice_enabled"] === "1" || newValues["thumb_music_cover_enabled"] === "1") && + newValues["thumb_builtin_enabled"] === "0" && + newValues["thumb_vips_enabled"] === "0" + ) { + enqueueSnackbar({ + message: t("settings.thumbDependencyWarning"), + variant: "warning", + action: DefaultCloseAction, + }); + } + }; + + const doTest = (name: string, executable: string) => { + setTesting(true); + dispatch( + sendTestThumbGeneratorExecutable({ + name, + executable, + }), + ) + .then((res) => { + enqueueSnackbar({ + message: t("settings.executableTestSuccess", { version: res }), + variant: "success", + action: DefaultCloseAction, + }); + }) + .finally(() => { + setTesting(false); + }); + }; + + return ( + + {generators.map((g) => ( + + }> + event.stopPropagation()} + onFocus={(event) => event.stopPropagation()} + control={ + + } + label={t(`settings.${g.name}`)} + disabled={g.readOnly} + /> + + + + {t(`settings.${g.des}`)} + + + {g.executableSetting && ( + + + + doTest(g.name, values[g.executableSetting ?? ""])} + loading={testing} + color="primary" + > + {t("settings.executableTest")} + + + ), + }} + onChange={(e) => + setSetting({ + [g.executableSetting ?? ""]: e.target.value, + }) + } + /> + {t("settings.executableDes")} + + + )} + {g.maxSizeSetting && ( + + + + setSetting({ + [g.maxSizeSetting ?? ""]: e.toString(), + }) + } + /> + {t("settings.thumbMaxSizeDes")} + + + )} + {g.inputs?.map((input) => ( + + + + setSetting({ + [input.name]: e.target.value, + }) + } + required={!!input.required} + /> + {t(`settings.${input.des}`)} + + + ))} + + + + ))} + + ); +}; + +export default Generators; diff --git a/src/component/Admin/Settings/Media/Media.tsx b/src/component/Admin/Settings/Media/Media.tsx new file mode 100755 index 0000000..11e0d88 --- /dev/null +++ b/src/component/Admin/Settings/Media/Media.tsx @@ -0,0 +1,184 @@ +import { Alert, Box, Collapse, FormControlLabel, Link, ListItemText, Stack, Switch, Typography } from "@mui/material"; +import FormControl from "@mui/material/FormControl"; +import { useContext } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { isTrueVal } from "../../../../session/utils.ts"; +import { DenseFilledTextField, DenseSelect } from "../../../Common/StyledComponents.tsx"; +import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx"; +import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings.tsx"; +import { SettingContext } from "../SettingWrapper.tsx"; +import Extractors from "./Extractors.tsx"; +import Generators from "./Generators.tsx"; + +const Media = () => { + const { t } = useTranslation("dashboard"); + const { formRef, setSettings, values } = useContext(SettingContext); + + return ( + e.preventDefault()}> + + + + {t("settings.thumbnails")} + + + {t("settings.thumbnailBasic")} + + + + + { + setSettings({ + thumb_width: e.target.value, + }); + }} + /> + + + + + { + setSettings({ + thumb_height: e.target.value, + }); + }} + /> + + + + + { + setSettings({ + thumb_entity_suffix: e.target.value, + }); + }} + /> + + {t("settings.notAppliedToNativeGenerator", { + prefix: t("settings.thumbSuffixDes"), + })} + + + + + + { + setSettings({ + thumb_encode_method: e.target.value as string, + }); + }} + required + > + {["jpg", "png", "webp"].map((f) => ( + + + {f} + + + ))} + + + {t("settings.notAppliedToOneDriveNativeGenerator", { prefix: t("settings.thumbFormatDes") })} + + + + + + + { + setSettings({ + thumb_encode_quality: e.target.value, + }); + }} + /> + + {t("settings.notAppliedToOneDriveNativeGenerator", { + prefix: t("settings.thumbQualityDes"), + })} + + + + + + + + setSettings({ + thumb_gc_after_gen: e.target.checked ? "1" : "0", + }) + } + /> + } + label={t("settings.thumbGC")} + /> + {t("settings.notAppliedToNativeGenerator", { prefix: "" })} + + + + + {t("settings.generators")} + + + + + ]} + /> + + + + + + + + {t("settings.extractMediaMeta")} + + + + + ]} + /> + + + + + + + + ); +}; + +export default Media; diff --git a/src/component/Admin/Settings/Queue/Queue.tsx b/src/component/Admin/Settings/Queue/Queue.tsx new file mode 100755 index 0000000..3495104 --- /dev/null +++ b/src/component/Admin/Settings/Queue/Queue.tsx @@ -0,0 +1,62 @@ +import { Box, Grid, Stack } from "@mui/material"; +import { useContext, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getQueueMetrics } from "../../../../api/api.ts"; +import { QueueMetric } from "../../../../api/dashboard.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import { SecondaryButton } from "../../../Common/StyledComponents.tsx"; +import ArrowSync from "../../../Icons/ArrowSync.tsx"; +import { SettingContext } from "../SettingWrapper.tsx"; +import QueueCard from "./QueueCard.tsx"; + +const Queue = () => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const { formRef, setSettings, values } = useContext(SettingContext); + const [metrics, setMetrics] = useState([]); + const [loading, setLoading] = useState(true); + + const fetchQueueMetrics = () => { + setLoading(true); + dispatch(getQueueMetrics()) + .then((res) => { + setMetrics(res); + }) + .finally(() => { + setLoading(false); + }); + }; + + useEffect(() => { + fetchQueueMetrics(); + }, []); + + return ( + + + }> + {t("node.refresh")} + + + + {!loading && + metrics.map((metric) => ( + + ))} + {loading && + Array.from(Array(5)).map((_, index) => ( + + ))} + + + ); +}; + +export default Queue; diff --git a/src/component/Admin/Settings/Queue/QueueCard.tsx b/src/component/Admin/Settings/Queue/QueueCard.tsx new file mode 100755 index 0000000..9b0da7a --- /dev/null +++ b/src/component/Admin/Settings/Queue/QueueCard.tsx @@ -0,0 +1,159 @@ +import { Box, Divider, Grid, IconButton, Skeleton, Stack, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { QueueMetric, QueueType } from "../../../../api/dashboard.ts"; +import Setting from "../../../Icons/Setting.tsx"; +import { StorageBar, StorageBlock, StoragePart } from "../../../Pages/Setting/StorageSetting.tsx"; +import { BorderedCard } from "../../Common/AdminCard.tsx"; +import QueueSettingDialog from "./QueueSettingDialog.tsx"; + +export interface QueueCardProps { + queue?: QueueType; + settings: { + [key: string]: string; + }; + setSettings: (settings: { [key: string]: string }) => void; + metrics?: QueueMetric; + loading: boolean; +} + +export const QueueCard = ({ queue, settings, metrics, setSettings, loading }: QueueCardProps) => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const [settingDialogOpen, setSettingDialogOpen] = useState(false); + + if (loading) { + return ( + + + + + + + + + + + {Array.from(Array(5)).map((_, index) => ( + + ))} + + + + ); + } + + return ( + + + + + {t(`queue.queueName_${queue}`)} + + setSettingDialogOpen(true)}> + + + + + {t(`queue.queueName_${queue}Des`)} + + + {metrics && ( + <> + + theme.palette.success.light, + width: `${(metrics.success_tasks / metrics.submitted_tasks) * 100}%`, + }} + /> + theme.palette.error.light, + width: `${(metrics.failure_tasks / metrics.submitted_tasks) * 100}%`, + }} + /> + theme.palette.action.active, + width: `${(metrics.suspending_tasks / metrics.submitted_tasks) * 100}%`, + }} + /> + theme.palette.info.light, + width: `${(metrics.busy_workers / metrics.submitted_tasks) * 100}%`, + }} + /> + + + + theme.palette.success.light, + }} + /> + {t("queue.success", { + count: metrics.success_tasks, + })} + + + theme.palette.error.light, + }} + /> + {t("queue.failed", { + count: metrics.failure_tasks, + })} + + + theme.palette.info.light, + }} + /> + {t("queue.busyWorker", { + count: metrics.busy_workers, + })} + + + theme.palette.action.active, + }} + /> + {t("queue.suspending", { + count: metrics.suspending_tasks, + })} + + + theme.palette.grey[theme.palette.mode === "light" ? 200 : 800], + }} + /> + {t("queue.submited", { + count: metrics.submitted_tasks, + })} + + + + )} + + {queue && ( + setSettingDialogOpen(false)} + queue={queue} + settings={settings} + setSettings={setSettings} + /> + )} + + + ); +}; + +export default QueueCard; diff --git a/src/component/Admin/Settings/Queue/QueueSettingDialog.tsx b/src/component/Admin/Settings/Queue/QueueSettingDialog.tsx new file mode 100755 index 0000000..5d21ea7 --- /dev/null +++ b/src/component/Admin/Settings/Queue/QueueSettingDialog.tsx @@ -0,0 +1,202 @@ +import { Box, FormControl, FormHelperText } from "@mui/material"; +import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { QueueType } from "../../../../api/dashboard.ts"; +import { DenseFilledTextField } from "../../../Common/StyledComponents.tsx"; +import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; + +export interface QueueSettingDialogProps { + open: boolean; + onClose: () => void; + queue: QueueType; + settings: { + [key: string]: string; + }; + setSettings: (settings: { [key: string]: string }) => void; +} + +const NoMarginHelperText = (props: any) => ( + +); + +const QueueSettingDialog = ({ open, onClose, queue, settings, setSettings }: QueueSettingDialogProps) => { + const { t } = useTranslation("dashboard"); + const formRef = useRef(null); + const [localSettings, setLocalSettings] = useState<{ [key: string]: string }>({}); + + // Initialize local settings when dialog opens or queue changes + useEffect(() => { + if (open) { + const queueSettings: { [key: string]: string } = {}; + const settingKeys = [ + "worker_num", + "max_execution", + "backoff_factor", + "backoff_max_duration", + "max_retry", + "retry_delay", + ]; + + settingKeys.forEach((key) => { + const fullKey = `queue_${queue}_${key}`; + queueSettings[key] = settings[fullKey] || ""; + }); + + setLocalSettings(queueSettings); + } + }, [open, queue, settings]); + + const handleSave = () => { + if (formRef.current?.reportValidity()) { + // Apply all settings at once + const updatedSettings: { [key: string]: string } = {}; + Object.entries(localSettings).forEach(([key, value]) => { + updatedSettings[`queue_${queue}_${key}`] = value as string; + }); + + setSettings(updatedSettings); + onClose(); + } + }; + + const updateLocalSetting = (key: string, value: string) => { + setLocalSettings((prev: { [key: string]: string }) => ({ + ...prev, + [key]: value, + })); + }; + + return ( + + + + + updateLocalSetting("worker_num", e.target.value)} + type="number" + slotProps={{ + htmlInput: { + min: 1, + }, + }} + required + /> + {t("queue.workerNumDes")} + + + + + + updateLocalSetting("max_execution", e.target.value)} + type="number" + inputProps={{ + min: 1, + }} + required + /> + {t("queue.maxExecutionDes")} + + + + + + updateLocalSetting("backoff_factor", e.target.value)} + type="number" + slotProps={{ + htmlInput: { + min: 1, + step: 0.1, + }, + }} + required + /> + {t("queue.backoffFactorDes")} + + + + + + updateLocalSetting("backoff_max_duration", e.target.value)} + type="number" + slotProps={{ + htmlInput: { + min: 1, + }, + }} + required + /> + {t("queue.backoffMaxDurationDes")} + + + + + + updateLocalSetting("max_retry", e.target.value)} + type="number" + slotProps={{ + htmlInput: { + min: 0, + }, + }} + required + /> + {t("queue.maxRetryDes")} + + + + + + updateLocalSetting("retry_delay", e.target.value)} + type="number" + slotProps={{ + htmlInput: { + min: 0, + }, + }} + required + /> + {t("queue.retryDelayDes")} + + + + + ); +}; + +export default QueueSettingDialog; diff --git a/src/component/Admin/Settings/Server/ServerSetting.tsx b/src/component/Admin/Settings/Server/ServerSetting.tsx new file mode 100755 index 0000000..47799e9 --- /dev/null +++ b/src/component/Admin/Settings/Server/ServerSetting.tsx @@ -0,0 +1,124 @@ +import { Box, FormControl, Link, Stack, Typography } from "@mui/material"; +import { useContext } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { DenseFilledTextField, SecondaryButton } from "../../../Common/StyledComponents"; +import ArrowSync from "../../../Icons/ArrowSync"; +import SettingForm from "../../../Pages/Setting/SettingForm"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings"; +import { SettingContext } from "../SettingWrapper"; + +const ServerSetting = () => { + const { t } = useTranslation("dashboard"); + const { formRef, setSettings, values } = useContext(SettingContext); + + const rotateSecretKey = () => { + setSettings({ secret_key: "[Placeholder]" }); + }; + + return ( + e.preventDefault()}> + + + + {t("settings.server")} + + + + + setSettings({ temp_path: e.target.value })} + /> + {t("settings.tempPathDes")} + + + + + setSettings({ siteID: e.target.value })} + /> + {t("settings.siteIDDes")} + + + + } variant="contained"> + {t("settings.rotateSecretKey")} + + {t("settings.siteSecretKeyDes")} + + + + setSettings({ hash_id_salt: e.target.value })} + /> + {t("settings.hashidSaltDes")} + + + + + setSettings({ access_token_ttl: e.target.value })} + /> + {t("settings.accessTokenTTLDes")} + + + + + setSettings({ refresh_token_ttl: e.target.value })} + /> + {t("settings.refreshTokenTTLDes")} + + + + + setSettings({ cron_garbage_collect: e.target.value })} + /> + + ]} + /> + + + + + + + + ); +}; + +export default ServerSetting; diff --git a/src/component/Admin/Settings/SettingWrapper.tsx b/src/component/Admin/Settings/SettingWrapper.tsx new file mode 100755 index 0000000..a8a262a --- /dev/null +++ b/src/component/Admin/Settings/SettingWrapper.tsx @@ -0,0 +1,191 @@ +import { LoadingButton } from "@mui/lab"; +import { Box, Grow, styled } from "@mui/material"; +import * as React from "react"; +import { createContext, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { getSettings, sendSetSetting } from "../../../api/api.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import FacebookCircularProgress from "../../Common/CircularProgress.tsx"; +import { SecondaryButton } from "../../Common/StyledComponents.tsx"; +import ArrowHookUpRight from "../../Icons/ArrowHookUpRight.tsx"; +import Save from "../../Icons/Save.tsx"; + +export interface SettingsWrapperProps { + settings: string[]; + children: React.ReactNode; +} + +export interface SettingContextProps { + values: { + [key: string]: string; + }; + setSettings: (settings: { [key: string]: string }) => void; + formRef?: React.RefObject; +} + +const SavingFloatContainer = styled(Box)(({ theme }) => ({ + padding: theme.spacing(2), + border: `1px solid ${theme.palette.divider}`, + borderRadius: theme.shape.borderRadius, + marginTop: theme.spacing(2), + position: "fixed", + backgroundColor: theme.palette.background.paper, + bottom: 23, + zIndex: theme.zIndex.modal, +})); + +export interface SavingFloatProps { + disabled?: boolean; + in: boolean; + submitting: boolean; + revert: () => void; + submit: () => void; +} + +export const SavingFloat = ({ in: inProp, submitting, revert, submit, disabled }: SavingFloatProps) => { + const { t } = useTranslation("dashboard"); + return ( + <> + + + + } + disabled={disabled} + > + {t("settings.save")} + + } + > + {t("settings.revert")} + + + + + ); +}; + +export const SettingContext = createContext({ + values: {}, + setSettings: () => {}, +}); + +const SettingsWrapper = ({ settings, children }: SettingsWrapperProps) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation("dashboard"); + const [values, setValues] = useState<{ [key: string]: string }>({}); + const [modifiedValues, setModifiedValues] = useState<{ + [key: string]: string; + }>({}); + const [loading, setLoading] = useState(true); + const [submitting, setSubmitting] = useState(false); + const setSettings = (settings: { [key: string]: string }) => { + setModifiedValues((prev) => ({ ...prev, ...settings })); + }; + const formRef = useRef(null); + + const showSaveButton = useMemo(() => { + return JSON.stringify(modifiedValues) !== JSON.stringify(values); + }, [modifiedValues, values]); + + useEffect(() => { + setLoading(true); + dispatch( + getSettings({ + keys: settings, + }), + ) + .then((res) => { + setValues(res); + setModifiedValues(res); + }) + .finally(() => { + setLoading(false); + }); + }, [settings]); + + const revert = () => { + setModifiedValues(values); + }; + + const submit = () => { + if (formRef.current) { + if (!formRef.current.checkValidity()) { + formRef.current.reportValidity(); + return; + } + } + + const modified: { [key: string]: string } = {}; + Object.keys(modifiedValues).forEach((key) => { + if (modifiedValues[key] !== values[key]) { + modified[key] = modifiedValues[key]; + } + }); + + setSubmitting(true); + dispatch( + sendSetSetting({ + settings: modified, + }), + ) + .then((res) => { + setValues((s) => ({ ...s, ...res })); + setModifiedValues((s) => ({ ...s, ...res })); + }) + .finally(() => { + setSubmitting(false); + }); + }; + + return ( + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${loading}`} + > + + {loading && ( + + + + )} + {!loading && ( + + {children} + + + )} + + + + + ); +}; + +export default SettingsWrapper; diff --git a/src/component/Admin/Settings/Settings.tsx b/src/component/Admin/Settings/Settings.tsx new file mode 100755 index 0000000..9629300 --- /dev/null +++ b/src/component/Admin/Settings/Settings.tsx @@ -0,0 +1,345 @@ +import { Box, Container, FormHelperText, InputAdornment, styled } from "@mui/material"; +import { useQueryState } from "nuqs"; +import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { QueueType } from "../../../api/dashboard.ts"; +import ResponsiveTabs, { Tab } from "../../Common/ResponsiveTabs.tsx"; +import Bot from "../../Icons/Bot.tsx"; +import Color from "../../Icons/Color.tsx"; +import CubeSync from "../../Icons/CubeSync.tsx"; +import Currency from "../../Icons/Currency.tsx"; +import FilmstripImage from "../../Icons/FilmstripImage.tsx"; +import Globe from "../../Icons/Globe.tsx"; +import MailOutlined from "../../Icons/MailOutlined.tsx"; +import PersonPasskey from "../../Icons/PersonPasskey.tsx"; +import SendLogging from "../../Icons/SendLogging.tsx"; +import Server from "../../Icons/Server.tsx"; +import PageContainer from "../../Pages/PageContainer.tsx"; +import PageHeader, { PageTabQuery } from "../../Pages/PageHeader.tsx"; +import Appearance from "./Appearance/Appearance.tsx"; +import Captcha from "./Captcha/Captcha.tsx"; +import Email from "./Email/Email.tsx"; +import Events from "./Event/Events.tsx"; +import Media from "./Media/Media.tsx"; +import Queue from "./Queue/Queue.tsx"; +import ServerSetting from "./Server/ServerSetting.tsx"; +import SettingsWrapper from "./SettingWrapper.tsx"; +import SiteInformation from "./SiteInformation/SiteInformation.tsx"; +import UserSession from "./UserSession/UserSession.tsx"; +import VAS from "./VAS/VAS.tsx"; + +export const StyledInputAdornment = styled(InputAdornment)(({ theme }) => ({ + fontSize: theme.typography.body2.fontSize, +})); + +export const SettingSection = styled(Box)(({ theme }) => ({ + display: "flex", + flexDirection: "column", + gap: theme.spacing(1), + [theme.breakpoints.up("md")]: { + padding: theme.spacing(0, 4), + }, +})); +export const SettingSectionContent = styled(Box)(({ theme }) => ({ + [theme.breakpoints.up("md")]: { + padding: theme.spacing(0, 4), + }, + display: "flex", + flexDirection: "column", + gap: theme.spacing(3), +})); +export const NoMarginHelperText = styled(FormHelperText)(() => ({ + marginLeft: 0, + marginRight: 0, +})); + +const allQueueSettings = Object.values(QueueType) + .map((queue) => [ + `queue_${queue}_worker_num`, + `queue_${queue}_max_execution`, + `queue_${queue}_backoff_factor`, + `queue_${queue}_backoff_max_duration`, + `queue_${queue}_max_retry`, + `queue_${queue}_retry_delay`, + ]) + .flat(); + +export enum SettingsPageTab { + SiteInformation = "siteInformation", + UserSession = "userSession", + Captcha = "captcha", + FileSystem = "fileSystem", + MediaProcessing = "mediaProcessing", + VAS = "vas", + Email = "email", + Queue = "queue", + Appearance = "appearance", + Events = "events", + Server = "server", +} + +const Settings = () => { + const { t } = useTranslation("dashboard"); + const [tab, setTab] = useQueryState(PageTabQuery); + + const tabs: Tab[] = useMemo(() => { + const res = []; + res.push( + ...[ + { + label: t("nav.basicSetting"), + value: SettingsPageTab.SiteInformation, + icon: , + }, + { + label: t("nav.userSession"), + value: SettingsPageTab.UserSession, + icon: , + }, + { + label: t("nav.captcha"), + value: SettingsPageTab.Captcha, + icon: , + }, + { + label: t("nav.mediaProcessing"), + value: SettingsPageTab.MediaProcessing, + icon: , + }, + { + label: t("vas.vas"), + value: SettingsPageTab.VAS, + icon: , + }, + { + label: t("nav.email"), + value: SettingsPageTab.Email, + icon: , + }, + { + label: t("nav.queue"), + value: SettingsPageTab.Queue, + icon: , + }, + { + label: t("nav.appearance"), + value: SettingsPageTab.Appearance, + icon: , + }, + { + label: t("nav.events"), + value: SettingsPageTab.Events, + icon: , + }, + { + label: t("nav.server"), + value: SettingsPageTab.Server, + icon: , + }, + ], + ); + return res; + }, [t]); + + return ( + + + + setTab(newValue)} + tabs={tabs} + /> + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${tab}`} + > + + {(!tab || tab === SettingsPageTab.SiteInformation) && ( + + + + )} + {tab === SettingsPageTab.UserSession && ( + + + + )} + {tab === SettingsPageTab.Captcha && ( + + + + )} + {tab === SettingsPageTab.MediaProcessing && ( + + + + )} + {tab === SettingsPageTab.VAS && ( + + + + )} + {tab === SettingsPageTab.Email && ( + + + + )} + {tab === SettingsPageTab.Queue && ( + + + + )} + {tab === SettingsPageTab.Appearance && ( + + + + )} + {tab === SettingsPageTab.Events && ( + + + + )} + {tab === SettingsPageTab.Server && ( + + + + )} + + + + + + ); +}; + +export default Settings; diff --git a/src/component/Admin/Settings/SiteInformation/GeneralImagePreview.tsx b/src/component/Admin/Settings/SiteInformation/GeneralImagePreview.tsx new file mode 100755 index 0000000..96f38a0 --- /dev/null +++ b/src/component/Admin/Settings/SiteInformation/GeneralImagePreview.tsx @@ -0,0 +1,50 @@ +import { Box } from "@mui/material"; +import { useState, useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import { useTheme } from "@mui/material/styles"; + +export interface GeneralImagePreviewProps { + src: string; + debounce?: number; // (å¯é€‰) 防抖时间 +} + +const GeneralImagePreview = ({ src, debounce = 0 }: GeneralImagePreviewProps) => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + + const [debouncedSrc, setDebouncedSrc] = useState(src); + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedSrc(src); + }, debounce); + + return () => clearTimeout(handler); + }, [src, debounce]); + + return ( + + `1px solid ${t.palette.divider}`, + p: 1, + display: "inline-block", + borderRadius: (theme) => `${theme.shape.borderRadius}px`, + }} + > + + + + ); +}; + +export default GeneralImagePreview; diff --git a/src/component/Admin/Settings/SiteInformation/LogoPreview.tsx b/src/component/Admin/Settings/SiteInformation/LogoPreview.tsx new file mode 100755 index 0000000..b2d9a0f --- /dev/null +++ b/src/component/Admin/Settings/SiteInformation/LogoPreview.tsx @@ -0,0 +1,65 @@ +import { Box, Stack } from "@mui/material"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import { useTheme } from "@mui/material/styles"; + +export interface LogoPreviewProps { + logoLight: string; + logoDark: string; +} + +const LogoPreview = ({ logoLight, logoDark }: LogoPreviewProps) => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + + return ( + + theme.palette.grey[100], + p: 1, + borderRadius: (theme) => `${theme.shape.borderRadius}px`, + }} + > + + + theme.palette.grey[900], + p: 1, + borderRadius: (theme) => `${theme.shape.borderRadius}px`, + }} + > + + + + ); +}; + +export default LogoPreview; diff --git a/src/component/Admin/Settings/SiteInformation/SiteInformation.tsx b/src/component/Admin/Settings/SiteInformation/SiteInformation.tsx new file mode 100755 index 0000000..9db5596 --- /dev/null +++ b/src/component/Admin/Settings/SiteInformation/SiteInformation.tsx @@ -0,0 +1,258 @@ +import { Box, FormControl, FormControlLabel, Stack, Switch, Typography } from "@mui/material"; +import Grid from "@mui/material/Grid"; +import { useContext } from "react"; +import { useTranslation } from "react-i18next"; +import { isTrueVal } from "../../../../session/utils.ts"; +import { DenseFilledTextField } from "../../../Common/StyledComponents.tsx"; +import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; +import { NoMarginHelperText, SettingSection, SettingSectionContent, StyledInputAdornment } from "../Settings.tsx"; +import { SettingContext } from "../SettingWrapper.tsx"; +import GeneralImagePreview from "./GeneralImagePreview.tsx"; +import LogoPreview from "./LogoPreview.tsx"; +import SiteURLInput from "./SiteURLInput.tsx"; + +const SiteInformation = () => { + const { t } = useTranslation("dashboard"); + const { formRef, setSettings, values } = useContext(SettingContext); + + return ( + e.preventDefault()}> + + + + {t("settings.basicInformation")} + + + + + setSettings({ siteName: e.target.value })} + value={values.siteName} + required + inputProps={{ maxLength: 255 }} + /> + {t("settings.mainTitleDes")} + + + + + setSettings({ siteDes: e.target.value })} + value={values.siteDes} + multiline + rows={4} + /> + {t("settings.siteDescriptionDes")} + + + + + setSettings({ siteURL: v })} /> + + + + + setSettings({ siteScript: e.target.value })} + value={values.siteScript} + multiline + rows={4} + /> + {t("settings.customFooterHTMLDes")} + + + + + + {t("settings.announcementDes")} + + + + + setSettings({ tos_url: e.target.value })} + value={values.tos_url} + /> + {t("settings.tosUrlDes")} + + + + + setSettings({ privacy_policy_url: e.target.value })} + value={values.privacy_policy_url} + /> + {t("settings.privacyUrlDes")} + + + + + + + {t("settings.branding")} + + + + + + } + > + + setSettings({ site_logo: e.target.value })} + value={values.site_logo} + required + InputProps={{ + startAdornment: ( + + {t("settings.light")} + + ), + }} + /> + setSettings({ site_logo_light: e.target.value })} + value={values.site_logo_light} + required + InputProps={{ + startAdornment: ( + + {t("settings.dark")} + + ), + }} + /> + {t("settings.logoDes")} + + + + + + + + } + > + + setSettings({ pwa_small_icon: e.target.value })} + value={values.pwa_small_icon} + required + /> + {t("settings.smallIconDes")} + + + + + + + + } + > + + setSettings({ pwa_medium_icon: e.target.value })} + value={values.pwa_medium_icon} + required + /> + {t("settings.mediumIconDes")} + + + + + + + + } + > + + setSettings({ pwa_large_icon: e.target.value })} + value={values.pwa_large_icon} + required + /> + {t("settings.largeIconDes")} + + + + + + + {t("vas.mobileApp")} + + + + + + setSettings({ + show_app_promotion: e.target.checked ? "1" : "0", + }) + } + /> + } + label={t("vas.showAppPromotion")} + /> + {t("vas.showAppPromotionDes")} + + + + + + {t("vas.appLinkDes")} + + + + + + {t("vas.appLinkDes")} + + + + + + + ); +}; + +export default SiteInformation; diff --git a/src/component/Admin/Settings/SiteInformation/SiteURLInput.tsx b/src/component/Admin/Settings/SiteInformation/SiteURLInput.tsx new file mode 100755 index 0000000..9bb734d --- /dev/null +++ b/src/component/Admin/Settings/SiteInformation/SiteURLInput.tsx @@ -0,0 +1,91 @@ +import { useTranslation } from "react-i18next"; +import { useMemo } from "react"; +import { Box, Collapse, Divider, IconButton, InputAdornment, Stack } from "@mui/material"; +import { DenseFilledTextField, SecondaryButton } from "../../../Common/StyledComponents.tsx"; +import FormControl from "@mui/material/FormControl"; +import Dismiss from "../../../Icons/Dismiss.tsx"; +import Add from "../../../Icons/Add.tsx"; +import { TransitionGroup } from "react-transition-group"; +import { NoMarginHelperText, StyledInputAdornment } from "../Settings.tsx"; + +export interface SiteURLInputProps { + urls: string; + onChange: (url: string) => void; +} + +const SiteURLInput = ({ urls, onChange }: SiteURLInputProps) => { + const { t } = useTranslation("dashboard"); + const urlSplit = useMemo(() => { + return urls.split(",").map((url) => url); + }, [urls]); + + const onUrlChange = (index: number) => (e: React.ChangeEvent) => { + const newUrls = [...urlSplit]; + newUrls[index] = e.target.value; + onChange(newUrls.join(",")); + }; + + const removeUrl = (index: number) => () => { + const newUrls = [...urlSplit]; + newUrls.splice(index, 1); + onChange(newUrls.join(",")); + }; + + return ( + + + + {t("settings.primarySiteURL")} + + ), + }} + required + /> + {t("settings.primarySiteURLDes")} + + + {t("settings.secondaryDes")} + + {urlSplit.slice(1).map((url, index) => ( + + + + {t("settings.secondarySiteURL")} + + ), + endAdornment: ( + + + + + + ), + }} + required + /> + + + ))} + + + } onClick={() => onChange(`${urls},`)}> + {t("settings.addSecondary")} + + + + ); +}; + +export default SiteURLInput; diff --git a/src/component/Admin/Settings/UserSession/SSOSettings.tsx b/src/component/Admin/Settings/UserSession/SSOSettings.tsx new file mode 100755 index 0000000..4492f18 --- /dev/null +++ b/src/component/Admin/Settings/UserSession/SSOSettings.tsx @@ -0,0 +1,66 @@ +import { ExpandMoreRounded } from "@mui/icons-material"; +import { Accordion, AccordionDetails, FormControlLabel, styled } from "@mui/material"; +import MuiAccordionSummary, { AccordionSummaryProps } from "@mui/material/AccordionSummary"; +import { useCallback, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { StyledCheckbox } from "../../../Common/StyledComponents.tsx"; +import ProDialog from "../../Common/ProDialog.tsx"; + +export const AccordionSummary = styled((props: AccordionSummaryProps) => )( + ({ theme }) => ({ + fontSize: theme.typography.body2.fontSize, + paddingLeft: theme.spacing(4), + "& .MuiFormControlLabel-label": { + fontSize: theme.typography.body2.fontSize, + }, + "& .MuiCheckbox-root": { + marginRight: theme.spacing(2), + }, + }), +); + +export const StyledAccordion = styled(Accordion)(({ theme }) => ({ + boxShadow: "none", + border: `1px solid ${theme.palette.divider}`, + "&::before": { + display: "none", + }, +})); + +export interface SettingSectionProps {} + +const SSOSettings = () => { + const [open, setOpen] = useState(false); + const { t } = useTranslation("dashboard"); + const onClick = useCallback((event: React.MouseEvent) => { + event.stopPropagation(); + setOpen(true); + }, []); + return ( + <> + setOpen(false)} /> +
    + + }> + } label={t("vas.qqConnect")} /> + + + + + }> + } label={t("settings.logto")} /> + + + + + }> + } label={t("settings.oidc")} /> + + + +
    + + ); +}; + +export default SSOSettings; diff --git a/src/component/Admin/Settings/UserSession/UserSession.tsx b/src/component/Admin/Settings/UserSession/UserSession.tsx new file mode 100755 index 0000000..5a1b99d --- /dev/null +++ b/src/component/Admin/Settings/UserSession/UserSession.tsx @@ -0,0 +1,244 @@ +import { Box, FormControl, FormControlLabel, Link, ListItemText, Stack, Switch, Typography } from "@mui/material"; +import { useContext, useMemo } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Link as RouterLink } from "react-router-dom"; +import { isTrueVal } from "../../../../session/utils.ts"; +import SizeInput from "../../../Common/SizeInput.tsx"; +import { DenseFilledTextField, DenseSelect } from "../../../Common/StyledComponents.tsx"; +import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx"; +import SettingForm, { ProChip } from "../../../Pages/Setting/SettingForm.tsx"; +import { Code } from "../../../Common/Code.tsx"; +import GroupSelectionInput from "../../Common/GroupSelectionInput.tsx"; +import SharesInput from "../../Common/SharesInput.tsx"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings.tsx"; +import { SettingContext } from "../SettingWrapper.tsx"; +import SSOSettings from "./SSOSettings.tsx"; + +const UserSession = () => { + const { t } = useTranslation("dashboard"); + const { formRef, setSettings, values } = useContext(SettingContext); + + const defaultSymbolics = useMemo(() => { + let result: number[] = []; + try { + result = JSON.parse(values?.default_symbolics ?? "[]"); + } catch (e) { + console.error(e); + } + return result; + }, [values?.default_symbolics]); + + return ( + e.preventDefault()}> + + + + {t("settings.accountManagement")} + + + + + + setSettings({ + register_enabled: e.target.checked ? "1" : "0", + }) + } + /> + } + label={t("settings.allowNewRegistrations")} + /> + {t("settings.allowNewRegistrationsDes")} + + + + + + setSettings({ + email_active: e.target.checked ? "1" : "0", + }) + } + /> + } + label={t("settings.emailActivation")} + /> + + ]} + /> + + + + + + + setSettings({ + authn_enabled: e.target.checked ? "1" : "0", + }) + } + /> + } + label={t("settings.webauthn")} + /> + {t("settings.webauthnDes")} + + + + + + setSettings({ + default_group: g, + }) + } + /> + {t("settings.defaultGroupDes")} + + + + + + + ]} + /> + + + + + + + {["filterEmailProviderDisabled", "filterEmailProviderWhitelist", "filterEmailProviderBlacklist"].map( + (v, i) => ( + + + {t(`vas.${v}`)} + + + ), + )} + + {t("vas.filterEmailProviderDes")} + + + + + } + label={ + <> + {t("vas.disableSubAddressEmail")} + + + } + /> + + ]} /> + + + + + + + + {t("settings.thirdPartySignIn")} + + + + + + + + + + {t("settings.avatar")} + + + + + + setSettings({ + avatar_path: e.target.value, + }) + } + required + /> + {t("settings.avatarFilePathDes")} + + + + + + setSettings({ + avatar_size: e.toString(), + }) + } + /> + {t("settings.avatarSizeDes")} + + + + + + setSettings({ + avatar_size_l: e.target.value, + }) + } + type={"number"} + inputProps={{ step: 1, min: 1 }} + required + /> + {t("settings.avatarImageSizeDes")} + + + + + + setSettings({ + gravatar_server: e.target.value, + }) + } + required + /> + {t("settings.gravatarServerDes")} + + + + + + + ); +}; + +export default UserSession; diff --git a/src/component/Admin/Settings/VAS/GiftCodes.tsx b/src/component/Admin/Settings/VAS/GiftCodes.tsx new file mode 100755 index 0000000..7002996 --- /dev/null +++ b/src/component/Admin/Settings/VAS/GiftCodes.tsx @@ -0,0 +1,117 @@ +import { Box, Chip, Stack, Table, TableBody, TableContainer, TableHead, TableRow } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useDispatch } from "react-redux"; +import { AnyAction } from "redux"; +import { ThunkDispatch } from "redux-thunk"; +import { NoWrapCell, SecondaryButton, StyledTableContainerPaper } from "../../../Common/StyledComponents.tsx"; +import Add from "../../../Icons/Add.tsx"; +import TablePagination from "../../Common/TablePagination.tsx"; + +interface GiftCodesProps { + storageProductsConfig: string; + groupProductsConfig: string; +} + +// Simplified GiftCode interface for our component use +interface GiftCode { + id: number; + code: string; + used: boolean; + qyt: number; +} + +// Pagination params +interface PaginationParams { + page: number; + perPage: number; + total: number; +} + +const GiftCodeStatusChip = ({ used }: { used: boolean }) => { + const { t } = useTranslation("dashboard"); + + return ( + + ); +}; + +const GiftCodes = ({ storageProductsConfig, groupProductsConfig }: GiftCodesProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useDispatch>(); + const { enqueueSnackbar } = useSnackbar(); + const [dialogOpen, setDialogOpen] = useState(false); + const [giftCodes, setGiftCodes] = useState([]); + const [loading, setLoading] = useState(false); + + // Pagination state + const [pagination, setPagination] = useState({ + page: 1, + perPage: 10, + total: 0, + }); + + const handleChangeRowsPerPage = (pageSize: number) => { + setPagination({ + page: 1, + perPage: pageSize, + total: pagination.total, + }); + }; + + return ( + + + } onClick={() => setDialogOpen(true)}> + {t("giftCodes.generateGiftCodes")} + + + + + + + + + # + {t("giftCodes.giftCodeProduct")} + {t("giftCodes.giftCodeAmount")} + {t("giftCodes.giftCode")} + {t("giftCodes.giftCodeStatus")} + {t("giftCodes.giftCodeUsedBy")} + + + + + {giftCodes.length === 0 && !loading && ( + + + {t("giftCodes.noGiftCodes")} + + + )} + +
    +
    + {pagination?.total > 0 && ( + + setPagination({ ...pagination, page })} + /> + + )} +
    +
    + ); +}; + +export default GiftCodes; diff --git a/src/component/Admin/Settings/VAS/GroupProducts.tsx b/src/component/Admin/Settings/VAS/GroupProducts.tsx new file mode 100755 index 0000000..ca1f214 --- /dev/null +++ b/src/component/Admin/Settings/VAS/GroupProducts.tsx @@ -0,0 +1,43 @@ +import { Box, Table, TableBody, TableContainer, TableHead, TableRow, Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { NoWrapCell, SecondaryButton, StyledTableContainerPaper } from "../../../Common/StyledComponents.tsx"; +import Add from "../../../Icons/Add.tsx"; + +const GroupProducts = () => { + const { t } = useTranslation("dashboard"); + + return ( + + + }> + {t("settings.addGroupProduct")} + + + + + + + + {t("settings.displayName")} + {t("settings.price")} + {t("settings.duration")} + {t("settings.description")} + {t("settings.actions")} + + + + + + + {t("application:setting.listEmpty")} + + + + +
    +
    +
    + ); +}; + +export default GroupProducts; diff --git a/src/component/Admin/Settings/VAS/PaymentProviders.tsx b/src/component/Admin/Settings/VAS/PaymentProviders.tsx new file mode 100755 index 0000000..917004e --- /dev/null +++ b/src/component/Admin/Settings/VAS/PaymentProviders.tsx @@ -0,0 +1,22 @@ +import { Add } from "@mui/icons-material"; +import { Box, Stack } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { SecondaryButton } from "../../../Common/StyledComponents"; + +export interface PaymentProviderProps {} + +const PaymentProviders: React.FC = ({}) => { + const { t } = useTranslation("dashboard"); + + return ( + + + }> + {t("settings.addPaymentProvider")} + + + + ); +}; + +export default PaymentProviders; diff --git a/src/component/Admin/Settings/VAS/StorageProducts.tsx b/src/component/Admin/Settings/VAS/StorageProducts.tsx new file mode 100755 index 0000000..2898a0a --- /dev/null +++ b/src/component/Admin/Settings/VAS/StorageProducts.tsx @@ -0,0 +1,43 @@ +import { Box, Table, TableBody, TableContainer, TableHead, TableRow, Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { NoWrapCell, SecondaryButton, StyledTableContainerPaper } from "../../../Common/StyledComponents.tsx"; +import Add from "../../../Icons/Add.tsx"; + +const StorageProducts = () => { + const { t } = useTranslation("dashboard"); + + return ( + + + }> + {t("settings.addStorageProduct")} + + + + + + + + {t("settings.displayName")} + {t("settings.price")} + {t("settings.duration")} + {t("settings.storageSize")} + {t("settings.actions")} + + + + + + + {t("application:setting.listEmpty")} + + + + +
    +
    +
    + ); +}; + +export default StorageProducts; diff --git a/src/component/Admin/Settings/VAS/VAS.tsx b/src/component/Admin/Settings/VAS/VAS.tsx new file mode 100755 index 0000000..be19599 --- /dev/null +++ b/src/component/Admin/Settings/VAS/VAS.tsx @@ -0,0 +1,225 @@ +import { + Box, + Button, + FormControl, + FormControlLabel, + InputAdornment, + Link, + Stack, + Switch, + Typography, +} from "@mui/material"; +import { bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import { useContext, useMemo, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { DenseFilledTextField } from "../../../Common/StyledComponents.tsx"; +import SettingForm, { ProChip } from "../../../Pages/Setting/SettingForm.tsx"; +import ProDialog from "../../Common/ProDialog.tsx"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../Settings.tsx"; +import { SettingContext } from "../SettingWrapper.tsx"; +import GiftCodes from "./GiftCodes.tsx"; +import GroupProducts from "./GroupProducts.tsx"; +import PaymentProviders from "./PaymentProviders.tsx"; +import StorageProducts from "./StorageProducts.tsx"; +interface CurrencyOption { + code: string; + symbol: string; + unit: number; + label: string; +} + +const VAS = () => { + const { t } = useTranslation("dashboard"); + const [proOpen, setProOpen] = useState(false); + const { formRef, setSettings, values } = useContext(SettingContext); + const currencyPopupState = usePopupState({ + variant: "popover", + popupId: "currencySelector", + }); + const paymentConfig = useMemo(() => JSON.parse(values.payment || "{}"), [values.payment]); + const storageProducts = useMemo(() => values.storage_products || "[]", [values.storage_products]); + const groupSellData = useMemo(() => values.group_sell_data || "[]", [values.group_sell_data]); + + const onProClick = (e: React.MouseEvent) => { + e.preventDefault(); + setProOpen(true); + }; + + return ( + + setProOpen(false)} /> + + + + {t("settings.creditAndVAS")} + + + + + } label={t("settings.enableCredit")} /> + {t("settings.enableCreditDes")} + + + + + + + + {t("settings.creditPriceDes")} + + + + + + + {t("settings.shareScoreRateDes")} + + + + + + + + {t("vas.banBufferPeriodDes")} + + + + + + + + ]} + /> + + + + + + + + + ]} + /> + + + + + + + } label={t("settings.anonymousPurchase")} /> + {t("settings.anonymousPurchaseDes")} + + + + + + } label={t("settings.shopNavEnabled")} /> + {t("settings.shopNavEnabledDes")} + + + + + + + + {t("settings.paymentSettings")} + + + + + + + + ), + }, + }} + /> + {t("settings.currencyCodeDes")} + + + + + + + {t("settings.currencySymbolDes")} + + + + + + + {t("settings.currencyUnitDes")} + + + + + + {t("settings.paymentProviders")} + + + + + + + + + + + {t("settings.storageProductSettings")} + + + + + + {t("settings.storageProductsDes")} + + + + + + + + {t("settings.groupProductSettings")} + + + + + + {t("settings.groupProductsDes")} + + + + + + + + {t("giftCodes.giftCodesSettings")} + + + + + + + + ); +}; + +export default VAS; diff --git a/src/component/Admin/Share/ShareDialog/ShareDialog.tsx b/src/component/Admin/Share/ShareDialog/ShareDialog.tsx new file mode 100755 index 0000000..5bc0fa6 --- /dev/null +++ b/src/component/Admin/Share/ShareDialog/ShareDialog.tsx @@ -0,0 +1,84 @@ +import { Box, DialogContent } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { getShareDetail } from "../../../../api/api.ts"; +import { Share } from "../../../../api/dashboard.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import AutoHeight from "../../../Common/AutoHeight.tsx"; +import FacebookCircularProgress from "../../../Common/CircularProgress.tsx"; +import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import ShareForm from "./ShareForm.tsx"; + +export interface ShareDialogProps { + open: boolean; + onClose: () => void; + shareID?: number; +} + +const ShareDialog = ({ open, onClose, shareID }: ShareDialogProps) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation("dashboard"); + const [values, setValues] = useState({ edges: {}, id: 0 }); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (!shareID || !open) { + return; + } + setLoading(true); + dispatch(getShareDetail(shareID)) + .then((res) => { + setValues(res); + }) + .catch(() => { + onClose(); + }) + .finally(() => { + setLoading(false); + }); + }, [open]); + + return ( + + + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${loading}`} + > + + {loading && ( + + + + )} + {!loading && } + + + + + + + ); +}; + +export default ShareDialog; diff --git a/src/component/Admin/Share/ShareDialog/ShareForm.tsx b/src/component/Admin/Share/ShareDialog/ShareForm.tsx new file mode 100755 index 0000000..79e1181 --- /dev/null +++ b/src/component/Admin/Share/ShareDialog/ShareForm.tsx @@ -0,0 +1,122 @@ +import { Box, Grid2 as Grid, Link, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Share } from "../../../../api/dashboard"; +import { NoWrapTypography } from "../../../Common/StyledComponents"; +import TimeBadge from "../../../Common/TimeBadge"; +import UserAvatar from "../../../Common/User/UserAvatar"; +import FileTypeIcon from "../../../FileManager/Explorer/FileTypeIcon"; +import SettingForm from "../../../Pages/Setting/SettingForm"; +import FileDialog from "../../File/FileDialog/FileDialog"; +import UserDialog from "../../User/UserDialog/UserDialog"; + +const ShareForm = ({ values }: { values: Share }) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + const { t } = useTranslation("dashboard"); + const [userDialogOpen, setUserDialogOpen] = useState(false); + const [userDialogID, setUserDialogID] = useState(0); + const [fileDialogOpen, setFileDialogOpen] = useState(false); + const [fileDialogID, setFileDialogID] = useState(0); + + const userClicked = (e: React.MouseEvent) => { + e.preventDefault(); + setUserDialogOpen(true); + setUserDialogID(values?.edges?.user?.id ?? 0); + }; + + const fileClicked = (e: React.MouseEvent) => { + e.preventDefault(); + setFileDialogOpen(true); + setFileDialogID(values?.edges?.file?.id ?? 0); + }; + + return ( + <> + setUserDialogOpen(false)} userID={userDialogID} /> + setFileDialogOpen(false)} fileID={fileDialogID} /> + + + + + {values.id} + + + + + + + {values.share_link} + + + + + + + + + + {values?.edges?.user?.nick} + + + + + + + + + {values.views ?? 0} + + + + + + {values.downloads ?? 0} + + + + + + {values?.edges?.file ? ( + + + + + {values?.edges?.file?.name} + + + + ) : ( + {t("share.deleted")} + )} + + + + + + + + + + + ); +}; + +export default ShareForm; diff --git a/src/component/Admin/Share/ShareFilterPopover.tsx b/src/component/Admin/Share/ShareFilterPopover.tsx new file mode 100755 index 0000000..9304719 --- /dev/null +++ b/src/component/Admin/Share/ShareFilterPopover.tsx @@ -0,0 +1,111 @@ +import { Box, Button, Popover, PopoverProps, Stack } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { DenseFilledTextField } from "../../Common/StyledComponents"; +import SettingForm from "../../Pages/Setting/SettingForm"; + +export interface ShareFilterPopoverProps extends PopoverProps { + user: string; + setUser: (user: string) => void; + file: string; + setFile: (file: string) => void; + clearFilters: () => void; +} + +const ShareFilterPopover = ({ + user, + setUser, + file, + setFile, + clearFilters, + onClose, + open, + ...rest +}: ShareFilterPopoverProps) => { + const { t } = useTranslation("dashboard"); + + // Create local state to track changes before applying + const [localUser, setLocalUser] = useState(user); + const [localFile, setLocalFile] = useState(file); + + // Initialize local state when popup opens + useEffect(() => { + if (open) { + setLocalUser(user); + setLocalFile(file); + } + }, [open]); + + // Apply filters and close popover + const handleApplyFilters = () => { + setUser(localUser); + setFile(localFile); + onClose?.({}, "backdropClick"); + }; + + // Reset filters and close popover + const handleResetFilters = () => { + setLocalUser(""); + setLocalFile(""); + clearFilters(); + onClose?.({}, "backdropClick"); + }; + + return ( + + + + setLocalUser(e.target.value)} + placeholder={t("user.emptyNoFilter")} + size="small" + /> + + + + setLocalFile(e.target.value)} + placeholder={t("user.emptyNoFilter")} + size="small" + /> + + + + + + + + + ); +}; + +export default ShareFilterPopover; diff --git a/src/component/Admin/Share/ShareList.tsx b/src/component/Admin/Share/ShareList.tsx new file mode 100755 index 0000000..e8bbf1b --- /dev/null +++ b/src/component/Admin/Share/ShareList.tsx @@ -0,0 +1,336 @@ +import { Delete } from "@mui/icons-material"; +import { + Badge, + Box, + Button, + Checkbox, + Container, + Divider, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TableSortLabel, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { bindPopover, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import { useQueryState } from "nuqs"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { batchDeleteShares, getShareList } from "../../../api/api"; +import { AdminListService, Share } from "../../../api/dashboard"; +import { useAppDispatch } from "../../../redux/hooks"; +import { confirmOperation } from "../../../redux/thunks/dialog"; +import { NoWrapTableCell, SecondaryButton, StyledTableContainerPaper } from "../../Common/StyledComponents"; +import ArrowSync from "../../Icons/ArrowSync"; +import Filter from "../../Icons/Filter"; +import PageContainer from "../../Pages/PageContainer"; +import PageHeader from "../../Pages/PageHeader"; +import TablePagination from "../Common/TablePagination"; +import FileDialog from "../File/FileDialog/FileDialog"; +import { OrderByQuery, OrderDirectionQuery, PageQuery, PageSizeQuery } from "../StoragePolicy/StoragePolicySetting"; +import UserDialog from "../User/UserDialog/UserDialog"; +import ShareDialog from "./ShareDialog/ShareDialog"; +import ShareFilterPopover from "./ShareFilterPopover"; +import ShareRow from "./ShareRow"; + +export const UserQuery = "user"; +export const FileQuery = "file"; + +const ShareList = () => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(true); + const [shares, setShares] = useState([]); + const [page, setPage] = useQueryState(PageQuery, { defaultValue: "1" }); + const [pageSize, setPageSize] = useQueryState(PageSizeQuery, { + defaultValue: "10", + }); + const [orderBy, setOrderBy] = useQueryState(OrderByQuery, { + defaultValue: "", + }); + const [orderDirection, setOrderDirection] = useQueryState(OrderDirectionQuery, { defaultValue: "desc" }); + const [user, setUser] = useQueryState(UserQuery, { defaultValue: "" }); + const [file, setFile] = useQueryState(FileQuery, { defaultValue: "" }); + + const [count, setCount] = useState(0); + const [selected, setSelected] = useState([]); + const filterPopupState = usePopupState({ + variant: "popover", + popupId: "shareFilterPopover", + }); + + const [userDialogOpen, setUserDialogOpen] = useState(false); + const [userDialogID, setUserDialogID] = useState(undefined); + const [openFile, setOpenFile] = useState(undefined); + const [openFileDialogOpen, setOpenFileDialogOpen] = useState(false); + const [openShare, setOpenShare] = useState(undefined); + const [openShareDialogOpen, setOpenShareDialogOpen] = useState(false); + const [deleteLoading, setDeleteLoading] = useState(false); + + const pageInt = parseInt(page) ?? 1; + const pageSizeInt = parseInt(pageSize) ?? 10; + + const clearFilters = useCallback(() => { + setUser(""); + setFile(""); + }, [setUser, setFile]); + + useEffect(() => { + fetchShares(); + }, [page, pageSize, orderBy, orderDirection, user, file]); + + const fetchShares = () => { + setLoading(true); + setSelected([]); + + const params: AdminListService = { + page: pageInt, + page_size: pageSizeInt, + order_by: orderBy ?? "", + order_direction: orderDirection ?? "desc", + conditions: { + share_file_id: file, + share_user_id: user, + }, + }; + + dispatch(getShareList(params)) + .then((res) => { + setShares(res.shares); + setPageSize(res.pagination.page_size.toString()); + setCount(res.pagination.total_items ?? 0); + setLoading(false); + }) + .finally(() => { + setLoading(false); + }); + }; + + const handleDelete = () => { + setDeleteLoading(true); + dispatch(confirmOperation(t("share.confirmBatchDelete", { num: selected.length }))) + .then(() => { + dispatch(batchDeleteShares({ ids: Array.from(selected) })) + .then(() => { + fetchShares(); + }) + .finally(() => { + setDeleteLoading(false); + }); + setDeleteLoading(false); + }) + .finally(() => { + setDeleteLoading(false); + }); + }; + + const handleSelectAllClick = (event: React.ChangeEvent) => { + if (event.target.checked) { + const newSelected = shares.map((n) => n.id); + setSelected(newSelected); + return; + } + setSelected([]); + }; + + const handleSelect = useCallback( + (id: number) => { + const selectedIndex = selected.indexOf(id); + let newSelected: readonly number[] = []; + + if (selectedIndex === -1) { + newSelected = newSelected.concat(selected, id); + } else if (selectedIndex === 0) { + newSelected = newSelected.concat(selected.slice(1)); + } else if (selectedIndex === selected.length - 1) { + newSelected = newSelected.concat(selected.slice(0, -1)); + } else if (selectedIndex > 0) { + newSelected = newSelected.concat(selected.slice(0, selectedIndex), selected.slice(selectedIndex + 1)); + } + setSelected(newSelected); + }, + [selected], + ); + + const orderById = orderBy === "id" || orderBy === ""; + const direction = orderDirection as "asc" | "desc"; + const onSortClick = (field: string) => () => { + const alreadySorted = orderBy === field || (field === "id" && orderById); + setOrderBy(field); + setOrderDirection(alreadySorted ? (direction === "asc" ? "desc" : "asc") : "asc"); + }; + + const hasActiveFilters = useMemo(() => { + return !!(user || file); + }, [user, file]); + + const handleUserDialogOpen = (id: number) => { + setUserDialogID(id); + setUserDialogOpen(true); + }; + + const handleOpenFile = (fileID: number) => { + setOpenFile(fileID); + setOpenFileDialogOpen(true); + }; + + const handleOpenShare = (shareID: number) => { + setOpenShare(shareID); + setOpenShareDialogOpen(true); + }; + + return ( + + setUserDialogOpen(false)} userID={userDialogID} /> + setOpenFileDialogOpen(false)} fileID={openFile} /> + setOpenShareDialogOpen(false)} shareID={openShare} /> + + + + + + }> + {t("node.refresh")} + + + + } variant="contained" {...bindTrigger(filterPopupState)}> + {t("user.filter")} + + + + {selected.length > 0 && !isMobile && ( + <> + + + + )} + + {isMobile && selected.length > 0 && ( + + + + )} + + + + + + 0 && selected.length < shares.length} + checked={shares.length > 0 && selected.length === shares.length} + onChange={handleSelectAllClick} + /> + + + + {t("group.#")} + + + {t("share.srcFileName")} + + + {t("share.views")} + + + + + {t("share.downloads")} + + + + + {t("share.price")} + + + {t("share.autoExpire")} + {t("share.owner")} + + + {t("share.createdAt")} + + + + + + + {!loading && + shares.map((share) => ( + + ))} + {loading && + shares.length > 0 && + shares.slice(0, 10).map((share) => )} + {loading && + shares.length === 0 && + Array.from(Array(10)).map((_, index) => )} + +
    +
    + {count > 0 && ( + + setPageSize(value.toString())} + onChange={(_, value) => setPage(value.toString())} + /> + + )} +
    +
    + ); +}; + +export default ShareList; diff --git a/src/component/Admin/Share/ShareRow.tsx b/src/component/Admin/Share/ShareRow.tsx new file mode 100755 index 0000000..db70b5e --- /dev/null +++ b/src/component/Admin/Share/ShareRow.tsx @@ -0,0 +1,224 @@ +import { Box, Checkbox, IconButton, Link, Skeleton, TableCell, TableRow } from "@mui/material"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { Share } from "../../../api/dashboard"; +import { useAppDispatch } from "../../../redux/hooks"; +import { NoWrapTableCell, NoWrapTypography } from "../../Common/StyledComponents"; +import TimeBadge from "../../Common/TimeBadge"; +import UserAvatar from "../../Common/User/UserAvatar"; +import FileTypeIcon from "../../FileManager/Explorer/FileTypeIcon"; +import { ShareExpires } from "../../FileManager/TopBar/ShareInfoPopover"; +import Delete from "../../Icons/Delete"; +import Open from "../../Icons/Open"; +import { confirmOperation } from "../../../redux/thunks/dialog"; +import { batchDeleteShares } from "../../../api/api"; + +export interface ShareRowProps { + share?: Share; + loading?: boolean; + deleting?: boolean; + selected?: boolean; + onDelete?: () => void; + onDetails?: (id: number) => void; + onSelect?: (id: number) => void; + openUserDialog?: (id: number) => void; + openFileDialog?: (id: number) => void; +} + +const ShareRow = ({ + share, + loading, + deleting, + selected, + onDelete, + onDetails, + onSelect, + openUserDialog, + openFileDialog, +}: ShareRowProps) => { + const navigate = useNavigate(); + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [deleteLoading, setDeleteLoading] = useState(false); + const [openLoading, setOpenLoading] = useState(false); + const onRowClick = () => { + onDetails?.(share?.id ?? 0); + }; + + const onDeleteClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + dispatch(confirmOperation(t("share.confirmDelete"))).then(() => { + if (share?.id) { + setDeleteLoading(true); + dispatch(batchDeleteShares({ ids: [share.id] })) + .then(() => { + onDelete?.(); + }) + .finally(() => { + setDeleteLoading(false); + }); + } + }); + }; + + const onSelectClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + onSelect?.(share?.id ?? 0); + }; + + if (loading) { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } + + const stopPropagation = (e: React.MouseEvent) => { + e.stopPropagation(); + }; + + const userClicked = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + openUserDialog?.(share?.edges?.user?.id ?? 0); + }; + + const fileClicked = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + openFileDialog?.(share?.edges?.file?.id ?? 0); + }; + + const onOpenLink = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + window.open(share?.share_link ?? "", "_blank"); + }; + + return ( + + + + + + {share?.id} + + + {share?.edges?.file ? ( + + + + + {share?.edges?.file?.name} + + + + ) : ( + {t("share.deleted")} + )} + + + {share?.views ?? 0} + + + {share?.downloads ?? 0} + + + {share?.price} + + + + + + {share?.edges?.user && ( + + + + + {share?.edges?.user?.nick} + + + + )} + + + + + + + + + + + + + + + + ); +}; + +export default ShareRow; diff --git a/src/component/Admin/StoragePolicy/AddWizardDialog.tsx b/src/component/Admin/StoragePolicy/AddWizardDialog.tsx new file mode 100755 index 0000000..98d921f --- /dev/null +++ b/src/component/Admin/StoragePolicy/AddWizardDialog.tsx @@ -0,0 +1,87 @@ +import { Box, DialogContent } from "@mui/material"; +import { useCallback, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { upsertStoragePolicy } from "../../../api/api"; +import { StoragePolicy } from "../../../api/dashboard"; +import { PolicyType } from "../../../api/explorer"; +import { useAppDispatch } from "../../../redux/hooks"; +import AutoHeight from "../../Common/AutoHeight"; +import FacebookCircularProgress from "../../Common/CircularProgress"; +import DraggableDialog from "../../Dialogs/DraggableDialog"; +import { PolicyPropsMap } from "./StoragePolicySetting"; + +export interface AddWizardDialogProps { + open: boolean; + onClose: () => void; + type: PolicyType; +} + +export interface AddWizardProps { + onSubmit: (data: StoragePolicy) => void; +} + +const AddWizardDialog = ({ open, onClose, type }: AddWizardDialogProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + const Wizard = PolicyPropsMap[type].wizard; + + const onSubmit = useCallback( + (data: StoragePolicy) => { + setLoading(true); + dispatch(upsertStoragePolicy({ policy: data })) + .then((res) => { + onClose(); + navigate(`/admin/policy/${res.id}`); + setLoading(false); + }) + .finally(() => { + setLoading(false); + }); + }, + [dispatch], + ); + + return ( + + + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${loading}`} + > + + {!loading && Wizard && } + {loading && ( + + + + )} + + + + + + + ); +}; + +export default AddWizardDialog; diff --git a/src/component/Admin/StoragePolicy/EditStoragePolicy/BucketACLInput.tsx b/src/component/Admin/StoragePolicy/EditStoragePolicy/BucketACLInput.tsx new file mode 100755 index 0000000..6dc6835 --- /dev/null +++ b/src/component/Admin/StoragePolicy/EditStoragePolicy/BucketACLInput.tsx @@ -0,0 +1,88 @@ +import { Box, ListItemText, OutlinedSelectProps, Typography } from "@mui/material"; +import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { PolicyType } from "../../../../api/explorer"; +import { DenseSelect } from "../../../Common/StyledComponents"; +import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu"; + +export interface BucketACLInputProps extends OutlinedSelectProps { + value?: boolean; + phraseVariant?: PolicyType; + onChange: (value: boolean) => void; +} + +const BucketACLInput = ({ value, phraseVariant = PolicyType.oss, onChange, ...props }: BucketACLInputProps) => { + const { t } = useTranslation("dashboard"); + const { privateLocale, publicLocale } = useMemo(() => { + switch (phraseVariant) { + case PolicyType.oss: + case PolicyType.obs: + return { privateLocale: "policy.privateBucket", publicLocale: "policy.publicBucket" }; + case PolicyType.cos: + return { privateLocale: "policy.accessTypePrivate", publicLocale: "policy.accessTypePulic" }; + case PolicyType.upyun: + return { privateLocale: "policy.tokenEnabled", publicLocale: "policy.tokenDisabled" }; + default: + return { privateLocale: "policy.privateBucket", publicLocale: "policy.publicBucket" }; + } + }, [phraseVariant]); + return ( + ( + + {value === "1" ? t(privateLocale) : t(publicLocale)} + + )} + onChange={(e) => onChange(e.target.value === "1")} + {...props} + MenuProps={{ + PaperProps: { sx: { maxWidth: 230 } }, + MenuListProps: { + sx: { + "& .MuiMenuItem-root": { + whiteSpace: "normal", + }, + }, + }, + }} + > + + + + {t(privateLocale)} + + + {t("policy.privateDes")} + + + + + + + {t(publicLocale)} + + + {t("policy.publicDes")} + + + + + ); +}; + +export default BucketACLInput; diff --git a/src/component/Admin/StoragePolicy/EditStoragePolicy/BucketCorsTable.tsx b/src/component/Admin/StoragePolicy/EditStoragePolicy/BucketCorsTable.tsx new file mode 100755 index 0000000..d1cf2aa --- /dev/null +++ b/src/component/Admin/StoragePolicy/EditStoragePolicy/BucketCorsTable.tsx @@ -0,0 +1,41 @@ +import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { NoWrapTableCell, StyledTableContainerPaper } from "../../../Common/StyledComponents"; + +export interface BucketCorsTableProps { + exposedHeaders?: string[]; +} + +const BucketCorsTable = ({ exposedHeaders }: BucketCorsTableProps) => { + const { t } = useTranslation("dashboard"); + return ( + + + + + {t("policy.origin")} + {t("policy.allowMethods")} + {t("policy.allowHeaders")} + {t("policy.exposeHeaders")} + {t("policy.maxAge")} + + + + + * + + {["GET", "POST", "PUT", "DELETE", "HEAD"].map((t) => ( +
    {t}
    + ))} +
    + * + {exposedHeaders?.map((h) =>
    {h}
    )}
    + 3600 +
    +
    +
    +
    + ); +}; + +export default BucketCorsTable; diff --git a/src/component/Admin/StoragePolicy/EditStoragePolicy/EditStoragePolicy.tsx b/src/component/Admin/StoragePolicy/EditStoragePolicy/EditStoragePolicy.tsx new file mode 100755 index 0000000..0c73f7a --- /dev/null +++ b/src/component/Admin/StoragePolicy/EditStoragePolicy/EditStoragePolicy.tsx @@ -0,0 +1,26 @@ +import { Container } from "@mui/material"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useParams } from "react-router-dom"; +import PageContainer from "../../../Pages/PageContainer"; +import PageHeader from "../../../Pages/PageHeader"; +import StoragePolicyForm from "./StoragePolicyForm"; +import StoragePolicySettingWrapper from "./StoragePolicySettingWrapper"; + +const EditStoragePolicy = () => { + const { t } = useTranslation("dashboard"); + const { id } = useParams(); + const [name, setName] = useState(""); + return ( + + + + setName(p.name)}> + + + + + ); +}; + +export default EditStoragePolicy; diff --git a/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/BasicInfoSection.tsx b/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/BasicInfoSection.tsx new file mode 100755 index 0000000..33675fe --- /dev/null +++ b/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/BasicInfoSection.tsx @@ -0,0 +1,596 @@ +import { + Checkbox, + CircularProgress, + Collapse, + FormControl, + FormControlLabel, + Link, + ListItemText, + SelectChangeEvent, + Typography, +} from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useCallback, useContext, useMemo, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Link as RouterLink } from "react-router-dom"; +import { createStoragePolicyCors, getOneDriveDriverRoot } from "../../../../../api/api"; +import { StoragePolicy } from "../../../../../api/dashboard"; +import { PolicyType } from "../../../../../api/explorer"; +import { useAppDispatch } from "../../../../../redux/hooks"; +import { DefaultCloseAction } from "../../../../Common/Snackbar/snackbar"; +import { DenseFilledTextField, DenseSelect, SecondaryButton } from "../../../../Common/StyledComponents"; +import { SquareMenuItem } from "../../../../FileManager/ContextMenu/ContextMenu"; +import SettingForm from "../../../../Pages/Setting/SettingForm"; +import { Code } from "../../../../Common/Code.tsx"; +import { EndpointInput } from "../../../Common/EndpointInput"; +import NodeSelectionInput from "../../../Common/NodeSelectionInput"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../../../Settings/Settings"; +import { PolicyPropsMap } from "../../StoragePolicySetting"; +import GraphEndpointSelection from "../../Wizards/OneDrive/GraphEndpointSelection"; +import BucketACLInput from "../BucketACLInput"; +import BucketCorsTable from "../BucketCorsTable"; +import { StoragePolicySettingContext } from "../StoragePolicySettingWrapper"; +import OdSignInStatus from "./OdSignInStatus"; + +export const SharePointDriverPending = "sharepoint_pending"; + +const BasicInfoSection = () => { + const { t } = useTranslation("dashboard"); + const { values, setPolicy } = useContext(StoragePolicySettingContext); + const [corsLoading, setCorsLoading] = useState(false); + const { enqueueSnackbar } = useSnackbar(); + const dispatch = useAppDispatch(); + + // Extract sharepoint URL from od_driver if it exists and is not the default + const initialSharepointUrl = useMemo(() => { + if (values.settings?.od_driver && values.settings.od_driver !== "me/drive") { + return values.settings.od_driver; + } + return ""; + }, [values.settings?.od_driver]); + + const [sharepointUrl, setSharepointUrl] = useState(initialSharepointUrl); + const [sharepointLoading, setSharepointLoading] = useState(false); + + const onNameChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ ...p, name: e.target.value })); + }, + [setPolicy], + ); + + const onNodeChange = useCallback( + (value: number) => { + setPolicy((p: StoragePolicy) => ({ ...p, node_id: value > 0 ? value : undefined })); + }, + [setPolicy], + ); + + const onBucketNameChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ ...p, bucket_name: e.target.value })); + }, + [setPolicy], + ); + + const showBucket = useMemo(() => { + return ( + values.type === PolicyType.oss || + values.type === PolicyType.cos || + values.type === PolicyType.obs || + values.type === PolicyType.qiniu || + values.type === PolicyType.s3 || + values.type === PolicyType.ks3 || + values.type === PolicyType.upyun + ); + }, [values.type]); + + const showEndpoint = useMemo(() => { + return values.type === PolicyType.cos || values.type === PolicyType.obs || values.type === PolicyType.s3; + }, [values.type]); + + const showCors = useMemo(() => { + return ( + values.type === PolicyType.oss || + values.type === PolicyType.cos || + values.type === PolicyType.obs || + values.type === PolicyType.s3 || + values.type === PolicyType.ks3 + ); + }, [values.type]); + + const policyProps = useMemo(() => { + return PolicyPropsMap[values.type]; + }, [values.type]); + + const onBucketTypeChange = useCallback( + (value: boolean) => { + setPolicy((p: StoragePolicy) => ({ ...p, is_private: value ? true : undefined })); + }, + [setPolicy], + ); + + const onEndpointChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ ...p, server: e.target.value })); + }, + [setPolicy], + ); + + const onIntranetEndpointChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, server_side_endpoint: e.target.value ? e.target.value : undefined }, + })); + }, + [setPolicy], + ); + + const onUseCnameChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, use_cname: e.target.checked ? true : undefined }, + })); + }, + [setPolicy], + ); + + const onAccessKeyChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ ...p, access_key: e.target.value })); + }, + [setPolicy], + ); + + const onSecretKeyChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ ...p, secret_key: e.target.value })); + }, + [setPolicy], + ); + + const onS3ForcePathStyleChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, s3_path_style: e.target.checked ? true : undefined }, + })); + }, + [setPolicy], + ); + + const onS3RegionChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ ...p, settings: { ...p.settings, region: e.target.value } })); + }, + [setPolicy], + ); + + const onS3DeleteBatchSizeChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { + ...p.settings, + s3_delete_batch_size: parseInt(e.target.value) ? parseInt(e.target.value) : undefined, + }, + })); + }, + [setPolicy], + ); + + const onGraphEndpointChange = useCallback( + (value: string) => { + setPolicy((p: StoragePolicy) => ({ ...p, server: value })); + }, + [setPolicy], + ); + + const onDriverTypeChange = useCallback( + (e: SelectChangeEvent) => { + const value = e.target.value as string; + if (value === "default") { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, od_driver: "me/drive" }, + })); + } else { + // When switching to SharePoint, set an empty URL initially + setSharepointUrl(""); + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, od_driver: SharePointDriverPending }, // Temporary value to trigger the collapse + })); + } + }, + [setPolicy, setSharepointUrl], + ); + + const onSharepointUrlChange = useCallback((e: React.ChangeEvent) => { + setSharepointUrl(e.target.value); + }, []); + + const handleSharepointUrlBlur = useCallback(() => { + if (!sharepointUrl || sharepointUrl.startsWith("sites/")) return; + + setSharepointLoading(true); + dispatch(getOneDriveDriverRoot(values.id, sharepointUrl)) + .then((res) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, od_driver: res }, + })); + setSharepointUrl(res); + }) + .finally(() => { + setSharepointLoading(false); + }); + }, [sharepointUrl, setPolicy]); + + const handleCreateCors = useCallback(() => { + setCorsLoading(true); + dispatch( + createStoragePolicyCors({ + policy: values, + }), + ) + .then(() => { + enqueueSnackbar(t("policy.corsPolicyAdded"), { + variant: "success", + action: DefaultCloseAction, + }); + }) + .finally(() => { + setCorsLoading(false); + }); + }, [dispatch, values, t]); + + const onOdTpsLimitChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, tps_limit: parseFloat(e.target.value) ? parseFloat(e.target.value) : undefined }, + })); + }, + [setPolicy], + ); + + const onOdTpsLimitBurstChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { + ...p.settings, + tps_limit_burst: parseInt(e.target.value) ? parseInt(e.target.value) : undefined, + }, + })); + }, + [setPolicy], + ); + + const onTokenChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, token: e.target.value ? e.target.value : undefined }, + })); + }, + [setPolicy], + ); + return ( + + + {t("policy.basicInfo")} + + + + + + {t("policy.policyName")} + + + {showBucket && ( + <> + + + {policyProps.bucketNameDes} + + + + + {t("policy.bucketTypeDes")} + + + {values.type === PolicyType.upyun && ( + + + + {t("policy.upyunTokenSecretDes")} + + + )} + {showEndpoint && ( + + + {policyProps.endpointDes} + {values.type == PolicyType.obs && ( + <> + + } + label={t("policy.thisIsACustomDomain")} + /> + {t("policy.thisIsACustomDomainDes")} + + )} + {(values.type === PolicyType.s3 || values.type === PolicyType.ks3) && ( + <> + + } + label={t("policy.usePathEndpoint")} + /> + + ]} + /> + + + )} + + )} + {values.type === PolicyType.oss && ( + <> + + + + , , ]} /> + + + } + label={t("policy.thisIsACustomDomain")} + /> + {t("policy.thisIsACustomDomainDes")} + + + + {t("policy.ossLANEndpointDes")} + + + )} + {policyProps.regionCode && ( + + + {policyProps.regionCodeDes} + + )} + + + + + {policyProps.credentialDes} + + + + )} + {values.type === PolicyType.remote && ( + + + + ]} + /> + + + )} + {showCors && ( + + + + {t("policy.ossCORSDes")} + + + {t("policy.letCloudreveHelpMe")} + + + )} + {(values.type === PolicyType.s3 || values.type === PolicyType.ks3) && ( + + + + ]} /> + + + )} + {values.type == PolicyType.onedrive && ( + <> + + + {t("policy.aadAccountCloudDes")} + + + + + + + + + + {t("policy.saveToDefaultOneDrive")} + + + + + {t("policy.saveToSharePoint")} + + + + {t("policy.driverRootDes")} + + + + : null, + }, + }} + /> + {t("policy.sharePointUrlDes")} + + + + + + + {t("policy.tpsDes")} + + {t("policy.tpsBurstDes")} + + + + )} + + + ); +}; + +export default BasicInfoSection; diff --git a/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/DownloadSection.tsx b/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/DownloadSection.tsx new file mode 100755 index 0000000..30c105c --- /dev/null +++ b/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/DownloadSection.tsx @@ -0,0 +1,168 @@ +import { Box, Checkbox, Collapse, FormControl, FormControlLabel, Switch, Typography } from "@mui/material"; +import { useCallback, useContext } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { StoragePolicy } from "../../../../../api/dashboard"; +import { PolicyType } from "../../../../../api/explorer"; +import SettingForm from "../../../../Pages/Setting/SettingForm"; +import { EndpointInput } from "../../../Common/EndpointInput"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../../../Settings/Settings"; +import { TrafficDiagram } from "../../TrafficDiagram"; +import { StoragePolicySettingContext } from "../StoragePolicySettingWrapper"; + +const DownloadSection = () => { + const { t } = useTranslation("dashboard"); + const { values, setPolicy } = useContext(StoragePolicySettingContext); + + const onDownloadCdnChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, custom_proxy: e.target.checked ? true : undefined }, + })); + }, + [setPolicy], + ); + + const onProxyServerChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, proxy_server: e.target.value }, + })); + }, + [setPolicy], + ); + + const onInternalProxyChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, internal_proxy: e.target.checked ? true : undefined }, + })); + }, + [setPolicy], + ); + + const onStreamSaverChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, stream_saver: e.target.checked ? true : undefined }, + })); + }, + [setPolicy], + ); + + const onSkipSignChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, source_auth: e.target.checked ? true : undefined }, + })); + }, + [setPolicy], + ); + + return ( + + + {t("policy.download")} + + + + + + } + label={t("policy.useDownloadCdn")} + /> + + + + {t("policy.downloadCdnDes")} + {values.type == PolicyType.cos && values.is_private && ( + + + } + label={t("policy.skipSign")} + /> + {t("policy.skipSignDes")} + + )} + + + + + {values.type !== PolicyType.local && ( + + + } + label={t("policy.downloadRelay")} + /> + + + + + + )} + {values.type === PolicyType.onedrive && ( + + + } + label={t("policy.streamSaver")} + /> + + + + + + )} + {values.type !== PolicyType.local && ( + + + + )} + + + ); +}; + +export default DownloadSection; diff --git a/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/MediaMetadataSection.tsx b/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/MediaMetadataSection.tsx new file mode 100755 index 0000000..b7eeb0a --- /dev/null +++ b/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/MediaMetadataSection.tsx @@ -0,0 +1,124 @@ +import { FormControl, FormControlLabel, Link, Switch, Typography } from "@mui/material"; +import { useCallback, useContext, useMemo } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Link as RouterLink } from "react-router-dom"; +import { StoragePolicy } from "../../../../../api/dashboard"; +import { PolicyType } from "../../../../../api/explorer"; +import { DenseFilledTextField } from "../../../../Common/StyledComponents"; +import SettingForm from "../../../../Pages/Setting/SettingForm"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../../../Settings/Settings"; +import { PolicyPropsMap } from "../../StoragePolicySetting"; +import { StoragePolicySettingContext } from "../StoragePolicySettingWrapper"; + +const MediaMetadataSection = () => { + const { t } = useTranslation("dashboard"); + const { values, setPolicy } = useContext(StoragePolicySettingContext); + + const policyProps = useMemo(() => { + return PolicyPropsMap[values.type]; + }, [values.type]); + + const onNativeMediaMetaExtsChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { + ...p.settings, + media_meta_exts: e.target.value === "" ? undefined : e.target.value.split(",").map((ext) => ext.trim()), + }, + })); + }, + [setPolicy], + ); + + const onMediaMetaGeneratorProxyChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, media_meta_generator_proxy: e.target.checked ? true : undefined }, + })); + }, + [setPolicy], + ); + + const noNativeExtractor = useMemo(() => { + return values.type === PolicyType.s3 || values.type === PolicyType.ks3 || values.type === PolicyType.onedrive; + }, [values.type]); + + if (values.type === PolicyType.local) { + return null; + } + + return ( + + + {t("settings.extractMediaMeta")} + + + {!noNativeExtractor && ( + ]} + /> + } + lgWidth={5} + > + + + + ] + : [] + } + /> + {policyProps.nativeExtractorDes && ( + ]} + /> + )} + + + + )} + + + + } + label={t("policy.mediaExtractorProxy")} + /> + + ]} + /> + + + + + + ); +}; + +export default MediaMetadataSection; diff --git a/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/OdSignInStatus.tsx b/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/OdSignInStatus.tsx new file mode 100755 index 0000000..b4abd32 --- /dev/null +++ b/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/OdSignInStatus.tsx @@ -0,0 +1,98 @@ +import { Box, Typography } from "@mui/material"; +import { useContext, useEffect, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { getPolicyOauthCredentialRefreshTime, getPolicyOauthUrl } from "../../../../../api/api"; +import { OauthCredentialStatus } from "../../../../../api/dashboard"; +import { useAppDispatch } from "../../../../../redux/hooks"; +import FacebookCircularProgress from "../../../../Common/CircularProgress"; +import { SecondaryButton } from "../../../../Common/StyledComponents"; +import TimeBadge from "../../../../Common/TimeBadge"; +import CheckCircleFilled from "../../../../Icons/CheckCircleFilled"; +import DismissCircleFilled from "../../../../Icons/DismissCircleFilled"; +import OneDriveAuthDialog from "../../Wizards/OneDrive/OneDriveAuthDialog"; +import { StoragePolicySettingContext } from "../StoragePolicySettingWrapper"; + +const OdSignInStatus = () => { + const { t } = useTranslation("dashboard"); + const { values, setPolicy } = useContext(StoragePolicySettingContext); + const dispatch = useAppDispatch(); + const [status, setStatus] = useState(undefined); + const [loading, setLoading] = useState(false); + const [authorizing, setAuthorizing] = useState(false); + const [authDialogOpen, setAuthDialogOpen] = useState(false); + + useEffect(() => { + if (!values.access_key) { + setStatus({ valid: false, last_refresh_time: "" }); + return; + } + setLoading(true); + dispatch(getPolicyOauthCredentialRefreshTime(values.id.toString())) + .then((res) => { + setStatus(res); + }) + .finally(() => { + setLoading(false); + }); + }, [values.id, values.secret_key]); + + const authorized = !loading && status && !!status.last_refresh_time; + + const handleOpenAuthDialog = () => { + setAuthDialogOpen(true); + }; + + const handleCloseAuthDialog = () => { + setAuthDialogOpen(false); + }; + + const handleConfirmAuth = (appId: string, appSecret: string) => { + setAuthorizing(true); + dispatch(getPolicyOauthUrl({ id: values.id, secret: appSecret, app_id: appId })) + .then((res) => { + window.location.href = res; + }) + .catch(() => { + setAuthorizing(false); + }); + }; + + return ( + + {loading && } + {!loading && ( + <> + {!authorized && ( + + + {t("policy.notGranted")} + + )} + {authorized && ( + + + ]} + /> + + )} + + {t(authorized ? "policy.authorizeAgain" : "policy.authorizeNow")} + + + )} + + + + ); +}; + +export default OdSignInStatus; diff --git a/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/StorageAndUploadSection.tsx b/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/StorageAndUploadSection.tsx new file mode 100755 index 0000000..6706871 --- /dev/null +++ b/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/StorageAndUploadSection.tsx @@ -0,0 +1,438 @@ +import { + Collapse, + FormControl, + FormControlLabel, + InputAdornment, + Link, + MenuItem, + Switch, + Typography, +} from "@mui/material"; +import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { StoragePolicy } from "../../../../../api/dashboard"; +import { PolicyType } from "../../../../../api/explorer"; +import SizeInput, { StyleOutlinedSelect } from "../../../../Common/SizeInput"; +import { DenseFilledTextField } from "../../../../Common/StyledComponents"; +import SettingForm from "../../../../Pages/Setting/SettingForm"; +import MagicVarDialog from "../../../Common/MagicVarDialog"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../../../Settings/Settings"; +import { PolicyPropsMap } from "../../StoragePolicySetting"; +import { TrafficDiagram } from "../../TrafficDiagram"; +import { StoragePolicySettingContext } from "../StoragePolicySettingWrapper"; +import { fileMagicVars, pathMagicVars } from "./magicVars"; + +const StorageAndUploadSection = () => { + const { t } = useTranslation("dashboard"); + const { values, setPolicy, formRef } = useContext(StoragePolicySettingContext); + const [magicVarDialogOpen, setMagicVarDialogOpen] = useState(false); + const [dialogType, setDialogType] = useState<"path" | "file">("path"); + + const fileNameInputRef = useRef(null); + + const policyProps = useMemo(() => { + return PolicyPropsMap[values.type]; + }, [values.type]); + + const showPreallocate = useMemo(() => { + return values.type === PolicyType.local || values.type === PolicyType.remote; + }, [values.type]); + + const showChunkConcurrency = useMemo(() => { + return ( + values.type === PolicyType.s3 || + values.type === PolicyType.local || + values.type === PolicyType.remote || + values.type === PolicyType.ks3 || + values.type === PolicyType.cos || + values.type === PolicyType.obs || + values.type === PolicyType.oss || + values.type === PolicyType.qiniu + ); + }, [values.type]); + + const onDirNameChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ ...p, dir_name_rule: e.target.value })); + }, + [setPolicy], + ); + + const onFileNameChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ ...p, file_name_rule: e.target.value })); + }, + [setPolicy], + ); + + // Ensures at least one random placeholder exists + useEffect(() => { + if (!formRef?.current || !fileNameInputRef.current) return; + + const form = formRef.current; + const originalCheckValidity = form.checkValidity.bind(form); + + const hasUniqueVar = (value: string) => /(\{randomkey8\}|\{randomkey16\}|\{uuid\})/.test(value); + + form.checkValidity = () => { + const dirHasUnique = hasUniqueVar(values.dir_name_rule || ""); + const fileHasUnique = hasUniqueVar(values.file_name_rule || ""); + + fileNameInputRef.current!.setCustomValidity(""); + if (!dirHasUnique && !fileHasUnique && (values.dir_name_rule || values.file_name_rule)) { + fileNameInputRef.current!.setCustomValidity(t("policy.uniqueVarRequired")); + } + + return originalCheckValidity(); + }; + + return () => { + form.checkValidity = originalCheckValidity; + }; + }, [formRef, values.dir_name_rule, values.file_name_rule, t]); + + const handlePathMagicVarClick = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + setDialogType("path"); + setMagicVarDialogOpen(true); + }, []); + + const handleFileMagicVarClick = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + setDialogType("file"); + setMagicVarDialogOpen(true); + }, []); + + const onMaxSizeChange = useCallback( + (e: number) => { + setPolicy((p: StoragePolicy) => ({ ...p, max_size: e ? e : undefined })); + }, + [setPolicy], + ); + + const fileExts = useMemo(() => { + return values.settings?.file_type?.join() ?? ""; + }, [values.settings?.file_type]); + + const onFileExtsChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { + ...(p.settings ?? {}), + file_type: e.target.value === "" ? undefined : e.target.value.split(",").map((ext) => ext.trim()), + }, + })); + }, + [setPolicy], + ); + + const onFileExtsModeChange = useCallback( + (mode: boolean | undefined) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { + ...(p.settings ?? {}), + is_file_type_deny_list: mode, + }, + })); + }, + [setPolicy], + ); + + const fileRegexp = useMemo(() => { + return values.settings?.file_regexp ?? ""; + }, [values.settings?.file_regexp]); + + const onFileRegexpChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { + ...(p.settings ?? {}), + file_regexp: e.target.value === "" ? undefined : e.target.value, + }, + })); + }, + [setPolicy], + ); + + const onFileRegexpModeChange = useCallback( + (mode: boolean | undefined) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { + ...(p.settings ?? {}), + is_name_regexp_deny_list: mode, + }, + })); + }, + [setPolicy], + ); + + const onChunkSizeChange = useCallback( + (size: number) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, chunk_size: size === 0 ? undefined : size }, + })); + }, + [setPolicy], + ); + + const onPreallocateChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, pre_allocate: e.target.checked ? true : undefined }, + })); + }, + [setPolicy], + ); + + const onUploadRelayChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, relay: e.target.checked ? true : undefined }, + })); + }, + [setPolicy], + ); + + const onAcceleratedDomainUploadChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, qiniu_upload_cdn: e.target.checked ? true : undefined }, + })); + }, + [setPolicy], + ); + + const onChunkConcurrencyChange = useCallback( + (e: React.ChangeEvent) => { + let value: number | undefined = parseInt(e.target.value) ?? 1; + if (value <= 1) { + value = undefined; + } + + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, chunk_concurrency: value }, + })); + }, + [setPolicy], + ); + + return ( + + + {t("policy.storageAndUpload")} + + + + + + + ]} + /> + {t("policy.nameRuleImmutable")} + + + + + + + + ]} + /> + {t("policy.nameRuleImmutable")} + + + + + + + {t("policy.maxSizeOfSingleFileDes")} + + + + + + onFileExtsModeChange(e.target.value === "blacklist" ? true : undefined)} + sx={{ minWidth: 80, mr: 1 }} + > + + {t("policy.whitelist")} + + + {t("policy.blacklist")} + + + + ), + }} + /> + {t("policy.enterFileExt")} + + + + + + onFileRegexpModeChange(e.target.value === "blacklist" ? true : undefined)} + sx={{ minWidth: 80, mr: 1 }} + > + + {t("policy.whitelist")} + + + {t("policy.blacklist")} + + + + ), + }} + /> + {t("policy.fileNameRegexDes")} + + + {values.type !== PolicyType.upyun && ( + + + + + {t("policy.chunkSizeDesSuffix", { + prefix: t(policyProps.chunkSizeDes ?? ""), + })} + + + + )} + {showPreallocate && ( + + + } + label={t("policy.preallocate")} + /> + {t("policy.preallocateDes")} + + + )} + {values.type === PolicyType.qiniu && ( + + + + } + label={t("policy.acceleratedDomainUpload")} + /> + + , + ]} + /> + + + + )} + + {showChunkConcurrency && ( + + + + {t("policy.chunkConcurrencyDes")} + + + )} + + {values.type !== PolicyType.local && ( + <> + + + } + label={t("policy.uploadRelay")} + /> + {t("policy.uploadRelayDes")} + + + + + + + )} + + setMagicVarDialogOpen(false)} + vars={dialogType === "path" ? pathMagicVars : fileMagicVars} + /> + + ); +}; + +export default StorageAndUploadSection; diff --git a/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/ThumbnailsSection.tsx b/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/ThumbnailsSection.tsx new file mode 100755 index 0000000..13d8ca3 --- /dev/null +++ b/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/ThumbnailsSection.tsx @@ -0,0 +1,154 @@ +import { Checkbox, FormControl, FormControlLabel, Link, Switch, Typography } from "@mui/material"; +import { useCallback, useContext, useMemo } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Link as RouterLink } from "react-router-dom"; +import { StoragePolicy } from "../../../../../api/dashboard"; +import { PolicyType } from "../../../../../api/explorer"; +import SizeInput from "../../../../Common/SizeInput"; +import { DenseFilledTextField } from "../../../../Common/StyledComponents"; +import SettingForm from "../../../../Pages/Setting/SettingForm"; +import { NoMarginHelperText, SettingSection, SettingSectionContent } from "../../../Settings/Settings"; +import { PolicyPropsMap } from "../../StoragePolicySetting"; +import { StoragePolicySettingContext } from "../StoragePolicySettingWrapper"; + +const ThumbnailsSection = () => { + const { t } = useTranslation("dashboard"); + const { values, setPolicy } = useContext(StoragePolicySettingContext); + + const policyProps = useMemo(() => { + return PolicyPropsMap[values.type]; + }, [values.type]); + + const noNativeThumbnail = useMemo(() => { + return values.type === PolicyType.local || values.type === PolicyType.s3; + }, [values.type]); + + const onNativeThumbnailChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { + ...p.settings, + thumb_exts: e.target.value === "" ? undefined : e.target.value.split(",").map((ext) => ext.trim()), + }, + })); + }, + [setPolicy], + ); + + const onNativeThumbnailSupportAllExtsChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, thumb_support_all_exts: e.target.checked ? true : undefined }, + })); + }, + [setPolicy], + ); + + const onNativeThumbnailMaxSizeChange = useCallback( + (size: number) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, thumb_max_size: size === 0 ? undefined : size }, + })); + }, + [setPolicy], + ); + + const onThumbProxyChange = useCallback( + (e: React.ChangeEvent) => { + setPolicy((p: StoragePolicy) => ({ + ...p, + settings: { ...p.settings, thumb_generator_proxy: e.target.checked ? true : undefined }, + })); + }, + [setPolicy], + ); + + if (values.type === PolicyType.local) { + return null; + } + + return ( + + + {t("settings.thumbnails")} + + + {!noNativeThumbnail && ( + <> + + + + + } + label={t("policy.nativeThumbNailsSupportAllExts")} + /> + + {t("policy.nativeThumbNailsGeneralDes")} + {policyProps.nativeThumbDes && ( + ] : [] + } + /> + )} + + + + + + + {t("policy.nativeThumbnailMaxSizeDes")} + + + + )} + + + + } + label={t("policy.thumbProxy")} + /> + + ]} + /> + + + + + + ); +}; + +export default ThumbnailsSection; diff --git a/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/index.ts b/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/index.ts new file mode 100755 index 0000000..d8ddb7d --- /dev/null +++ b/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/index.ts @@ -0,0 +1,6 @@ +export { default as BasicInfoSection } from "./BasicInfoSection"; +export { default as DownloadSection } from "./DownloadSection"; +export * from "./magicVars"; +export { default as MediaMetadataSection } from "./MediaMetadataSection"; +export { default as StorageAndUploadSection } from "./StorageAndUploadSection"; +export { default as ThumbnailsSection } from "./ThumbnailsSection"; diff --git a/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/magicVars.ts b/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/magicVars.ts new file mode 100755 index 0000000..364544f --- /dev/null +++ b/src/component/Admin/StoragePolicy/EditStoragePolicy/FormSections/magicVars.ts @@ -0,0 +1,32 @@ +import { MagicVar } from "../../../Common/MagicVarDialog"; + +export const commonMagicVars: MagicVar[] = [ + { name: "{randomkey16}", value: "policy.magicVar.16digitsRandomString", example: "a1b2c3d4e5f6g7h8" }, + { name: "{randomkey8}", value: "policy.magicVar.8digitsRandomString", example: "a1b2c3d4" }, + { name: "{timestamp}", value: "policy.magicVar.secondTimestamp", example: "1609459200" }, + { name: "{timestamp_nano}", value: "policy.magicVar.nanoTimestamp", example: "1609459200000000000" }, + { name: "{randomnum2}", value: "policy.magicVar.randomNumber", example: "0-1" }, + { name: "{randomnum3}", value: "policy.magicVar.randomNumber", example: "0-2" }, + { name: "{randomnum4}", value: "policy.magicVar.randomNumber", example: "0-3" }, + { name: "{randomnum8}", value: "policy.magicVar.randomNumber", example: "0-7" }, + { name: "{uid}", value: "policy.magicVar.uid", example: "1" }, + { name: "{datetime}", value: "policy.magicVar.dateAndTime", example: "20220101120000" }, + { name: "{date}", value: "policy.magicVar.date", example: "20220101" }, + { name: "{year}", value: "policy.magicVar.year", example: "2022" }, + { name: "{month}", value: "policy.magicVar.month", example: "01" }, + { name: "{day}", value: "policy.magicVar.day", example: "01" }, + { name: "{hour}", value: "policy.magicVar.hour", example: "12" }, + { name: "{minute}", value: "policy.magicVar.minute", example: "00" }, + { name: "{second}", value: "policy.magicVar.second", example: "00" }, + { name: "{originname}", value: "policy.magicVar.originalFileName", example: "example.jpg" }, + { name: "{ext}", value: "policy.magicVar.extension", example: ".jpg" }, + { name: "{originname_without_ext}", value: "policy.magicVar.originFileNameNoext", example: "example" }, + { name: "{uuid}", value: "policy.magicVar.uuidV4", example: "550e8400-e29b-41d4-a716-446655440000" }, +]; + +export const pathMagicVars: MagicVar[] = [ + ...commonMagicVars, + { name: "{path}", value: "policy.magicVar.path", example: "/path/to/" }, +]; + +export const fileMagicVars: MagicVar[] = [...commonMagicVars]; diff --git a/src/component/Admin/StoragePolicy/EditStoragePolicy/StoragePolicyForm.tsx b/src/component/Admin/StoragePolicy/EditStoragePolicy/StoragePolicyForm.tsx new file mode 100755 index 0000000..d6873e6 --- /dev/null +++ b/src/component/Admin/StoragePolicy/EditStoragePolicy/StoragePolicyForm.tsx @@ -0,0 +1,40 @@ +import { Alert, Box, Link, Stack } from "@mui/material"; +import { useContext } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Link as RouterLink } from "react-router-dom"; +import { + BasicInfoSection, + DownloadSection, + MediaMetadataSection, + StorageAndUploadSection, + ThumbnailsSection, +} from "./FormSections"; +import { StoragePolicySettingContext } from "./StoragePolicySettingWrapper"; + +const StoragePolicyForm = () => { + const { t } = useTranslation("dashboard"); + const { formRef, values } = useContext(StoragePolicySettingContext); + + return ( + e.preventDefault()}> + {!values.edges?.groups?.length && ( + + ]} + /> + + )} + + + + + + + + + ); +}; + +export default StoragePolicyForm; diff --git a/src/component/Admin/StoragePolicy/EditStoragePolicy/StoragePolicySettingWrapper.tsx b/src/component/Admin/StoragePolicy/EditStoragePolicy/StoragePolicySettingWrapper.tsx new file mode 100755 index 0000000..abc032f --- /dev/null +++ b/src/component/Admin/StoragePolicy/EditStoragePolicy/StoragePolicySettingWrapper.tsx @@ -0,0 +1,143 @@ +import { Box } from "@mui/material"; +import * as React from "react"; +import { createContext, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { getStoragePolicyDetail, upsertStoragePolicy } from "../../../../api/api.ts"; +import { StoragePolicy } from "../../../../api/dashboard.ts"; +import { PolicyType } from "../../../../api/explorer.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import FacebookCircularProgress from "../../../Common/CircularProgress.tsx"; +import { SavingFloat } from "../../Settings/SettingWrapper.tsx"; +import { SharePointDriverPending } from "./FormSections/BasicInfoSection.tsx"; + +export interface StoragePolicySettingWrapperProps { + policyID: number; + children: React.ReactNode; + onPolicyChange: (policy: StoragePolicy) => void; +} + +export interface StoragePolicySettingContextProps { + values: StoragePolicy; + setPolicy: (f: (p: StoragePolicy) => StoragePolicy) => void; + formRef?: React.RefObject; +} + +const defaultPolicy: StoragePolicy = { + id: 0, + type: PolicyType.local, + name: "", + edges: {}, +}; + +export const StoragePolicySettingContext = createContext({ + values: { ...defaultPolicy }, + setPolicy: () => {}, +}); + +const StoragePolicySettingWrapper = ({ policyID, children, onPolicyChange }: StoragePolicySettingWrapperProps) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation("dashboard"); + const [values, setValues] = useState({ + ...defaultPolicy, + }); + const [modifiedValues, setModifiedValues] = useState({ + ...defaultPolicy, + }); + const [loading, setLoading] = useState(true); + const [submitting, setSubmitting] = useState(false); + const formRef = useRef(null); + + const showSaveButton = useMemo(() => { + return JSON.stringify(modifiedValues) !== JSON.stringify(values); + }, [modifiedValues, values]); + + useEffect(() => { + setLoading(true); + dispatch(getStoragePolicyDetail(policyID)) + .then((res) => { + setValues(res); + setModifiedValues(res); + onPolicyChange(res); + }) + .finally(() => { + setLoading(false); + }); + }, [policyID]); + + const revert = () => { + setModifiedValues(values); + }; + + const submit = () => { + if (formRef.current) { + if (!formRef.current.checkValidity()) { + formRef.current.reportValidity(); + return; + } + } + + setSubmitting(true); + dispatch( + upsertStoragePolicy({ + policy: { ...modifiedValues, edges: {} }, + }), + ) + .then((res) => { + setValues(res); + setModifiedValues(res); + onPolicyChange(res); + }) + .finally(() => { + setSubmitting(false); + }); + }; + + return ( + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${loading}`} + > + + {loading && ( + + + + )} + {!loading && ( + + {children} + + + )} + + + + + ); +}; + +export default StoragePolicySettingWrapper; diff --git a/src/component/Admin/StoragePolicy/OauthCallback.tsx b/src/component/Admin/StoragePolicy/OauthCallback.tsx new file mode 100755 index 0000000..0ef5482 --- /dev/null +++ b/src/component/Admin/StoragePolicy/OauthCallback.tsx @@ -0,0 +1,56 @@ +import { Box, Container } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { finishOauthCallback } from "../../../api/api"; +import { useAppDispatch } from "../../../redux/hooks"; +import { useQuery } from "../../../util"; +import FacebookCircularProgress from "../../Common/CircularProgress"; +import { DefaultCloseAction } from "../../Common/Snackbar/snackbar"; +import PageContainer from "../../Pages/PageContainer"; +import PageHeader from "../../Pages/PageHeader"; +const OauthCallback = () => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + const navigate = useNavigate(); + + const query = useQuery(); + + useEffect(() => { + const code = query.get("code"); + const state = query.get("state"); + if (code && state) { + dispatch(finishOauthCallback({ code, state })).finally(() => { + navigate(`/admin/policy/${state}`); + }); + } else { + enqueueSnackbar(t("policy.oauthCallbackFailed"), { + variant: "error", + action: DefaultCloseAction, + }); + } + }, []); + + return ( + + + + + + + + + + ); +}; + +export default OauthCallback; diff --git a/src/component/Admin/StoragePolicy/SelectProvider.tsx b/src/component/Admin/StoragePolicy/SelectProvider.tsx new file mode 100755 index 0000000..d339985 --- /dev/null +++ b/src/component/Admin/StoragePolicy/SelectProvider.tsx @@ -0,0 +1,83 @@ +import { + Card, + CardActionArea, + CardContent, + CardMedia, + DialogActions, + DialogContent, + Grid2, + styled, + Typography, +} from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { PolicyType } from "../../../api/explorer"; +import { SecondaryButton } from "../../Common/StyledComponents"; +import DraggableDialog from "../../Dialogs/DraggableDialog"; +import Open from "../../Icons/Open"; +import { PolicyPropsMap } from "./StoragePolicySetting"; +import { useState } from "react"; +import ProDialog from "../Common/ProDialog.tsx"; +import { ProChip } from "../../Pages/Setting/SettingForm.tsx"; + +export interface SelectProviderProps { + open: boolean; + onClose: () => void; + onSelect: (provider: PolicyType) => void; +} + +const StyledCard = styled(Card)(({ theme }) => ({ + display: "flex", + boxShadow: "none", + border: `1px solid ${theme.palette.divider}`, +})); + +const SelectProvider = ({ open, onClose, onSelect }: SelectProviderProps) => { + const { t } = useTranslation("dashboard"); + const [proOpen, setProOpen] = useState(false); + return ( + + setProOpen(false)} /> + + + {Object.values(PolicyType).map((type) => ( + + + (PolicyPropsMap[type].pro ? setProOpen(true) : onSelect(type))} + > + + + + {t(PolicyPropsMap[type].name)} + {PolicyPropsMap[type].pro && } + + + + + + ))} + + + + } + variant="contained" + onClick={() => window.open("https://docs.cloudreve.org/usage/storage/", "_blank")} + > + {t("policy.compare")} + + + + ); +}; + +export default SelectProvider; diff --git a/src/component/Admin/StoragePolicy/StoragePolicyCard.tsx b/src/component/Admin/StoragePolicy/StoragePolicyCard.tsx new file mode 100755 index 0000000..63e9cab --- /dev/null +++ b/src/component/Admin/StoragePolicy/StoragePolicyCard.tsx @@ -0,0 +1,178 @@ +import { Box, Divider, IconButton, Link, Skeleton, Typography } from "@mui/material"; +import Grid from "@mui/material/Grid2"; +import { useCallback, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { deleteStoragePolicy, getStoragePolicyDetail } from "../../../api/api"; +import { StoragePolicy } from "../../../api/dashboard"; +import { useAppDispatch } from "../../../redux/hooks"; +import { confirmOperation } from "../../../redux/thunks/dialog"; +import { sizeToString } from "../../../util"; +import { NoWrapBox, SquareChip } from "../../Common/StyledComponents"; +import Delete from "../../Icons/Delete"; +import Info from "../../Icons/Info"; +import { BorderedCardClickableBaImg } from "../Common/AdminCard"; +import { PolicyPropsMap } from "./StoragePolicySetting"; + +export interface StoragePolicyCardProps { + policy?: StoragePolicy; + onRefresh?: () => void; + loading?: boolean; +} + +const StoragePolicyCard = ({ policy, onRefresh, loading }: StoragePolicyCardProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [detail, setDetail] = useState(undefined); + const [deleteLoading, setDeleteLoading] = useState(false); + const [detailLoading, setDetailLoading] = useState(false); + const navigate = useNavigate(); + + const loadDetail = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (policy) { + setDetailLoading(true); + dispatch(getStoragePolicyDetail(policy.id, true)).then((res) => { + setDetail(res); + setDetailLoading(false); + }); + } + }, + [policy, dispatch], + ); + + const handleDelete = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + dispatch(confirmOperation(t("policy.deletePolicyConfirmation", { name: policy?.name ?? "" }))).then(() => { + setDeleteLoading(true); + dispatch(deleteStoragePolicy(policy?.id ?? 0)) + .then(() => { + onRefresh?.(); + }) + .finally(() => { + setDeleteLoading(false); + }); + }); + }, + [policy, dispatch, onRefresh], + ); + + // If loading is true, render a skeleton placeholder + if (loading) { + return ( + + + + + + + + + + + + + + + + + + + + + ); + } + + return ( + + navigate(`/admin/policy/${policy?.id}`)} + img={policy ? PolicyPropsMap[policy.type].img : undefined} + > + + + {policy?.name} + + {policy && ( + + {t(PolicyPropsMap[policy.type].name)} + + )} + + + {policy?.edges.groups?.map((group) => ( + + ))} + {!policy?.edges.groups && ( + + + {t("policy.noGroupBinded")} + + )} + + + + + {detailLoading ? ( + + ) : detail ? ( + t("policy.policySummary", { + count: detail.entities_count ?? 0, + size: sizeToString(detail.entities_size ?? 0), + }) + ) : ( + + {t("policy.loadSummary")} + + )} + + + + + + + + + ); +}; + +export default StoragePolicyCard; diff --git a/src/component/Admin/StoragePolicy/StoragePolicySetting.tsx b/src/component/Admin/StoragePolicy/StoragePolicySetting.tsx new file mode 100755 index 0000000..40d73e6 --- /dev/null +++ b/src/component/Admin/StoragePolicy/StoragePolicySetting.tsx @@ -0,0 +1,493 @@ +import { Box, Container, Link, ListItemText, Stack, Typography } from "@mui/material"; +import Grid from "@mui/material/Grid2"; +import { SelectChangeEvent } from "@mui/material/Select"; +import { useQueryState } from "nuqs"; +import { useCallback, useEffect, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { getStoragePolicyList } from "../../../api/api"; +import { StoragePolicy } from "../../../api/dashboard"; +import { PolicyType } from "../../../api/explorer"; +import { useAppDispatch } from "../../../redux/hooks"; +import { DenseSelect, SecondaryButton } from "../../Common/StyledComponents"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu"; +import Add from "../../Icons/Add"; +import ArrowSync from "../../Icons/ArrowSync"; +import PageContainer from "../../Pages/PageContainer"; +import PageHeader from "../../Pages/PageHeader"; +import { BorderedCardClickable } from "../Common/AdminCard"; +import { Code } from "../../Common/Code.tsx"; +import TablePagination from "../Common/TablePagination"; +import AddWizardDialog, { AddWizardProps } from "./AddWizardDialog"; +import SelectProvider from "./SelectProvider"; +import StoragePolicyCard from "./StoragePolicyCard"; +import CosWizard from "./Wizards/COS/CosWizard"; +import LocalWizard from "./Wizards/Local/LocalWizard"; +import ObsWizard from "./Wizards/OBS/ObsWizard"; +import OneDriveWizard from "./Wizards/OneDrive/OneDriveWizard"; +import OssWizard from "./Wizards/OSS/OssWizard"; +import QiniuWizard from "./Wizards/Qiniu/QiniuWizard"; +import RemoteWizard from "./Wizards/Remote/RemoteWizard"; +import S3Wizard from "./Wizards/S3/S3Wizard"; +import KS3Wizard from "./Wizards/KS3/KS3Wizard"; +import UpyunWizard from "./Wizards/Upyun/UpyunWizard"; + +export const PageQuery = "page"; +export const PageSizeQuery = "page_size"; +export const OrderByQuery = "order_by"; +export const OrderDirectionQuery = "order_direction"; +export const PolicyTypeQuery = "policy_type"; + +export interface PolicyProps { + name: string; + img: string; + wizardSize?: "sm" | "md" | "lg"; + wizard?: (props: AddWizardProps) => React.ReactNode; + chunkSizeDes?: string; + chunkSizeMin?: number; + chunkSizeMax?: number; + nativeThumbDes?: string; + nativeThumbDoc?: string; + nativeExtractorName?: string; + nativeExtractorDoc?: string; + nativeExtractorDes?: string; + nativeExtractorDesDoc?: string; + bucketName?: string; + bucketNameDes?: React.ReactNode; + bucketType?: string; + endpointName?: string; + endpointDes?: React.ReactNode; + akName?: string; + skName?: string; + credentialDes?: React.ReactNode; + corsExposedHeaders?: string[]; + endpointNotEnforcePrefix?: boolean; + pro?: boolean; + regionCode?: string; + regionCodeDes?: React.ReactNode; +} + +export const PolicyPropsMap: Record = { + [PolicyType.local]: { + name: "policy.local", + img: "/static/img/local.png", + wizardSize: "sm", + wizard: LocalWizard, + chunkSizeDes: "policy.chunkSizeDes", + }, + [PolicyType.load_balance]: { + name: "policy.load_balance", + img: "/static/img/lb.svg", + wizardSize: "sm", + pro: true, + }, + [PolicyType.remote]: { + name: "policy.remote", + img: "/static/img/remote.png", + wizardSize: "sm", + wizard: RemoteWizard, + nativeThumbDes: "policy.nativeThumbNailsGeneralRemote", + nativeExtractorName: "policy.mediaExtractorNative", + nativeExtractorDes: "policy.nativeMediaMetaExtsRemote", + chunkSizeDes: "policy.chunkSizeDes", + }, + [PolicyType.s3]: { + name: "policy.s3", + img: "/static/img/s3.png", + wizardSize: "sm", + wizard: S3Wizard, + bucketName: "policy.bucketName", + bucketType: "policy.bucketType", + endpointName: "policy.policyEndpoint", + endpointDes: ]} />, + akName: "Access Key", + skName: "Secret Key", + chunkSizeMin: 5 * 1024 * 1024, //5MB + chunkSizeMax: 5 * 1024 * 1024 * 1024, //5GB + chunkSizeDes: "policy.chunkSizeDesS3", + regionCode: "policy.s3Region", + regionCodeDes: ]} />, + }, + [PolicyType.ks3]: { + name: "policy.ks3", + img: "/static/img/ks3.png", + wizardSize: "sm", + wizard: KS3Wizard, + bucketName: "policy.bucketName", + bucketType: "policy.bucketType", + endpointName: "policy.policyEndpoint", + endpointDes: ]} />, + akName: "Access Key", + skName: "Secret Key", + corsExposedHeaders: ["ETag"], + chunkSizeMin: 5 * 1024 * 1024, //5MB + chunkSizeMax: 5 * 1024 * 1024 * 1024, //5GB + chunkSizeDes: "policy.chunkSizeDesS3", + regionCode: "policy.s3Region", + regionCodeDes: ]} />, + }, + [PolicyType.cos]: { + name: "policy.cos", + img: "/static/img/cos.png", + wizardSize: "sm", + wizard: CosWizard, + bucketName: "policy.cosObsBucketName", + bucketNameDes: ( + , ]} + /> + ), + bucketType: "policy.accessType", + endpointName: "policy.accessDomain", + endpointDes: , ]} />, + chunkSizeMin: 1024 * 1024, //1MB + chunkSizeMax: 5 * 1024 * 1024 * 1024, //5GB + chunkSizeDes: "policy.chunkSizeDesQiniuCos", + nativeThumbDes: "policy.nativeThumbNailsGeneralCos", + nativeThumbDoc: "https://cloud.tencent.com/document/product/436/113312", + nativeExtractorName: "policy.mediaExtractorCos", + nativeExtractorDes: "policy.nativeMediaMetaExtCos", + nativeExtractorDesDoc: "https://console.cloud.tencent.com/ci", + nativeExtractorDoc: "https://console.cloud.tencent.com/ci", + akName: "SecretId", + skName: "SecretKey", + corsExposedHeaders: ["ETag"], + credentialDes: ( + , + , + , + ]} + /> + ), + }, + [PolicyType.oss]: { + name: "policy.oss", + img: "/static/img/oss.png", + wizardSize: "sm", + wizard: OssWizard, + chunkSizeMin: 100 * 1024, //100KB + chunkSizeMax: 5 * 1024 * 1024 * 1024, //5GB + chunkSizeDes: "policy.chunkSizeDesOssObs", + nativeThumbDes: "policy.nativeThumbNailsGeneralOss", + nativeThumbDoc: "https://help.aliyun.com/zh/oss/user-guide/overview-17/", + nativeExtractorName: "policy.mediaExtractorOss", + nativeExtractorDes: "policy.nativeMediaMetaExtOss", + nativeExtractorDoc: "https://help.aliyun.com/zh/oss/user-guide/quick-start-2326698", + nativeExtractorDesDoc: "https://help.aliyun.com/zh/oss/user-guide/quick-start-2326698", + bucketName: "policy.bucketName", + bucketNameDes: ( + , , ]} + /> + ), + bucketType: "policy.bucketType", + akName: "AccessKey ID", + skName: "AccessKey Secret", + credentialDes: ( + , + , + , + ]} + /> + ), + regionCode: "policy.s3Region", + regionCodeDes: ( + , + , + , + ]} + /> + ), + }, + [PolicyType.obs]: { + name: "policy.obs", + img: "/static/img/obs.png", + wizardSize: "sm", + wizard: ObsWizard, + bucketName: "policy.cosObsBucketName", + bucketNameDes: ( + , + , + , + , + ]} + /> + ), + bucketType: "policy.bucketPolicy", + endpointName: "policy.policyEndpoint", + endpointDes: , ]} />, + endpointNotEnforcePrefix: true, + akName: "Access Key Id", + skName: "Secret Access Key", + credentialDes: ( + , + , + , + , + ]} + /> + ), + corsExposedHeaders: ["ETag"], + chunkSizeMin: 100 * 1024, //100KB + chunkSizeMax: 5 * 1024 * 1024 * 1024, //5GB + chunkSizeDes: "policy.chunkSizeDesOssObs", + nativeThumbDes: "policy.nativeThumbNailsGeneralObs", + nativeThumbDoc: "https://support.huaweicloud.com/intl/zh-cn/usermanual-obs/obs_01_0001.html", + nativeExtractorName: "policy.mediaExtractorObs", + nativeExtractorDes: "policy.nativeMediaMetaExtObs", + nativeExtractorDoc: "https://support.huaweicloud.com/intl/zh-cn/usermanual-obs/obs_01_0410.html", + nativeExtractorDesDoc: "https://support.huaweicloud.com/intl/zh-cn/usermanual-obs/obs_01_0410.html", + }, + [PolicyType.qiniu]: { + name: "policy.qiniu", + img: "/static/img/qiniu.png", + wizardSize: "sm", + wizard: QiniuWizard, + bucketName: "policy.qiniuBucketName", + bucketNameDes: ( + ]} + /> + ), + bucketType: "policy.aclType", + akName: "AK", + skName: "SK", + credentialDes: , + nativeThumbDes: "policy.nativeThumbNailsGeneralQiniu", + nativeThumbDoc: "https://developer.qiniu.com/dora/api/basic-processing-images-imageview2", + nativeExtractorName: "policy.mediaExtractorQiniu", + nativeExtractorDes: "policy.nativeMediaMetaExtQiniu", + nativeExtractorDoc: "https://www.qiniu.com/products/dora", + chunkSizeMin: 1024 * 1024, //1 MB + chunkSizeMax: 1024 * 1024 * 1024, //1GB + chunkSizeDes: "policy.chunkSizeDesQiniuCos", + }, + [PolicyType.upyun]: { + name: "policy.upyun", + img: "/static/img/upyun.png", + wizardSize: "sm", + wizard: UpyunWizard, + bucketName: "policy.storageServiceName", + bucketNameDes: ( + ]} + /> + ), + bucketType: "policy.tokenStatus", + akName: "policy.operatorName", + skName: "policy.operatorPassword", + nativeThumbDes: "policy.nativeThumbNailsGeneralUpyun", + nativeThumbDoc: "https://help.upyun.com/knowledge-base/image/", + nativeExtractorName: "policy.mediaExtractorUpyun", + nativeExtractorDes: "policy.nativeMediaMetaExtUpyun", + nativeExtractorDesDoc: "https://help.upyun.com/knowledge-base/image/#e58583e695b0e68daee88eb7e58f96", + nativeExtractorDoc: "https://help.upyun.com/knowledge-base/image/", + }, + [PolicyType.onedrive]: { + name: "policy.onedrive", + img: "/static/img/onedrive.png", + wizardSize: "sm", + wizard: OneDriveWizard, + chunkSizeMin: 5 * 1024 * 1024, //5MB + chunkSizeMax: 5 * 1024 * 1024 * 1024, //5GB + chunkSizeDes: "policy.chunkSizeDesOd", + bucketName: "policy.storageServiceName", + bucketNameDes: ( + ]} + /> + ), + bucketType: "policy.tokenStatus", + }, +}; + +const StoragePolicySetting = () => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(true); + const [policies, setPolicies] = useState([]); + const [page, setPage] = useQueryState(PageQuery, { defaultValue: "1" }); + const [pageSize, setPageSize] = useQueryState(PageSizeQuery, { + defaultValue: "11", + }); + const [orderBy, setOrderBy] = useQueryState(OrderByQuery, { + defaultValue: "", + }); + const [orderDirection, setOrderDirection] = useQueryState(OrderDirectionQuery, { defaultValue: "desc" }); + const [policyType, setPolicyType] = useQueryState(PolicyTypeQuery, { + defaultValue: " ", + }); + const [count, setCount] = useState(0); + const [selectProviderOpen, setSelectProviderOpen] = useState(false); + const [newPolicyType, setNewPolicyType] = useState(null); + const [createNewOpen, setCreateNewOpen] = useState(false); + + const [open, setOpen] = useState(false); + const pageInt = parseInt(page) ?? 1; + const pageSizeInt = parseInt(pageSize) ?? 11; + + useEffect(() => { + fetchPolicies(); + }, [page, pageSize, orderBy, orderDirection, policyType]); + + const fetchPolicies = () => { + setLoading(true); + dispatch( + getStoragePolicyList({ + page: pageInt, + page_size: pageSizeInt, + order_by: orderBy ?? "", + order_direction: orderDirection ?? "desc", + conditions: { + policy_type: policyType == " " ? "" : policyType, + }, + }), + ) + .then((res) => { + setPolicies(res.policies); + setPageSize(res.pagination.page_size.toString()); + setCount(res.pagination.total_items ?? 0); + setLoading(false); + }) + .finally(() => { + setLoading(false); + }); + }; + + const onAddNewPolcyWithType = useCallback((type: PolicyType) => { + setNewPolicyType(type); + setSelectProviderOpen(false); + setCreateNewOpen(true); + }, []); + + const onPolicyTypeChange = useCallback((e: SelectChangeEvent) => { + setPolicyType(e.target.value as PolicyType); + setPage("1"); + }, []); + + return ( + + setSelectProviderOpen(false)} + onSelect={onAddNewPolcyWithType} + /> + {newPolicyType && ( + setCreateNewOpen(false)} type={newPolicyType} /> + )} + + + + }> + {t("node.refresh")} + + ( + + {v == " " ? t("policy.all") : t(PolicyPropsMap[v as PolicyType].name)} + + )} + > + + + {t("policy.all")} + + + {Object.values(PolicyType).map((type) => ( + + + {t(PolicyPropsMap[type].name)} + + + ))} + + + + + setSelectProviderOpen(true)} + sx={{ + height: "100%", + borderStyle: "dashed", + display: "flex", + alignItems: "center", + gap: 1, + justifyContent: "center", + color: (t) => t.palette.text.secondary, + }} + > + + {t("policy.newStoragePolicy")} + + + {!loading && policies.map((p) => )} + {loading && + policies.length > 0 && + policies.map((p) => )} + {loading && + policies.length === 0 && + Array.from(Array(5)).map((_, index) => )} + + {count > 0 && ( + + setPageSize(value.toString())} + onChange={(_, value) => setPage(value.toString())} + /> + + )} + + + ); +}; + +export default StoragePolicySetting; diff --git a/src/component/Admin/StoragePolicy/TrafficDiagram.tsx b/src/component/Admin/StoragePolicy/TrafficDiagram.tsx new file mode 100755 index 0000000..b205f49 --- /dev/null +++ b/src/component/Admin/StoragePolicy/TrafficDiagram.tsx @@ -0,0 +1,222 @@ +import { Box, ListItemText, useMediaQuery, useTheme } from "@mui/material"; +import { forwardRef, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { DenseSelect, NoWrapTypography } from "../../Common/StyledComponents"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu"; +import ArrowLeft from "../../Icons/ArrowLeft"; +import GlobeFilled from "../../Icons/GlobeFilled"; +import Home from "../../Icons/Home"; +import Person from "../../Icons/Person"; +import Storage from "../../Icons/Storage"; +import WindowApps from "../../Icons/WindowApps"; +import { BorderedCard } from "../Common/AdminCard"; + +export interface TrafficDiagramProps { + variant: "upload" | "download"; + proxyed?: boolean; + cdn?: boolean; + storageNodeTitle?: string; + internalEndpoint?: boolean; + proxyNodeTitle?: string; +} + +enum Source { + web = "web", + dav = "dav", + web_edit = "web_edit", + wopi = "wopi", +} + +enum Node { + cloudreve = "cloudreve", + proxy = "proxy", + storage_node = "storage_node", + storage_node_internal = "storage_node_internal", + user = "user", + arrow = "arrow", + wopi = "wopi", +} + +interface NodeIconProps extends TrafficDiagramProps { + type: Node; + size?: number; +} + +const NodeIcon = forwardRef(({ type, size = 40, storageNodeTitle, proxyNodeTitle, variant }: NodeIconProps, ref) => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const icon = useMemo(() => { + switch (type) { + case Node.cloudreve: + return t.palette.action.active }} />; + case Node.proxy: + return t.palette.action.active }} />; + case Node.storage_node: + case Node.storage_node_internal: + return t.palette.action.active }} />; + case Node.user: + return t.palette.action.active }} />; + case Node.wopi: + return t.palette.action.active }} />; + case Node.arrow: + return ( + t.palette.action.disabled, + transform: + variant == "upload" + ? isMobile + ? "rotate(-90deg)" + : "rotate(180deg)" + : isMobile + ? "rotate(90deg)" + : "rotate(0deg)", + }} + /> + ); + } + }, [type, isMobile]); + + const title = useMemo(() => { + switch (type) { + case Node.cloudreve: + return "Cloudreve"; + case Node.proxy: + return proxyNodeTitle ?? t("policy.customProxy"); + case Node.storage_node: + return storageNodeTitle ?? t("policy.storageNode"); + case Node.storage_node_internal: + return t("policy.storageNodeInternal"); + case Node.user: + return t("nav.users"); + case Node.wopi: + return t("settings.wopiViewer"); + } + }, [type]); + + return ( + + {icon} + {title && ( + + {title} + + )} + + ); +}); + +export const TrafficDiagram = ({ + variant, + cdn, + proxyed, + internalEndpoint, + storageNodeTitle, + proxyNodeTitle, +}: TrafficDiagramProps) => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const [source, setSource] = useState(Source.web); + const nodes = useMemo(() => { + const res: Node[] = []; + if (source == Source.wopi) { + res.push(Node.wopi); + } else { + res.push(Node.user); + } + if (variant == "upload") { + if (proxyed || source == Source.dav || source == Source.web_edit) { + res.push(Node.cloudreve); + } + } else { + if (proxyed || source == Source.wopi) { + res.push(Node.cloudreve); + } + + if (cdn) { + res.push(Node.proxy); + } + } + + if (variant == "upload" && internalEndpoint && (source == Source.dav || source == Source.web_edit || proxyed)) { + res.push(Node.storage_node_internal); + } else if (variant == "download" && internalEndpoint && (source == Source.wopi || proxyed) && !cdn) { + res.push(Node.storage_node_internal); + } else { + res.push(Node.storage_node); + } + + // join arrow between existing nodes + const joinedNodes: Node[] = []; + for (const node of res) { + joinedNodes.push(node); + joinedNodes.push(Node.arrow); + } + return joinedNodes.slice(0, -1); + }, [proxyed, source, cdn, variant, internalEndpoint]); + return ( + + setSource(e.target.value as Source)}> + + + {t("policy.sourceWeb")} + + + + + {t("policy.sourceDav")} + + + {variant == "upload" && ( + + + {t("policy.sourceWebEdit")} + + + )} + {variant == "download" && ( + + + {t("settings.wopiViewer")} + + + )} + + + {nodes.map((node) => ( + + ))} + + + ); +}; diff --git a/src/component/Admin/StoragePolicy/Wizards/COS/CosWizard.tsx b/src/component/Admin/StoragePolicy/Wizards/COS/CosWizard.tsx new file mode 100755 index 0000000..2ede564 --- /dev/null +++ b/src/component/Admin/StoragePolicy/Wizards/COS/CosWizard.tsx @@ -0,0 +1,172 @@ +import { Button, Collapse, FormControl, Link, Stack } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useRef, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { createStoragePolicyCors } from "../../../../../api/api"; +import { StoragePolicy } from "../../../../../api/dashboard"; +import { PolicyType } from "../../../../../api/explorer"; +import { useAppDispatch } from "../../../../../redux/hooks"; +import { DefaultCloseAction } from "../../../../Common/Snackbar/snackbar"; +import { DenseFilledTextField, SecondaryButton } from "../../../../Common/StyledComponents"; +import SettingForm from "../../../../Pages/Setting/SettingForm"; +import { Code } from "../../../../Common/Code.tsx"; +import { EndpointInput } from "../../../Common/EndpointInput"; +import { NoMarginHelperText } from "../../../Settings/Settings"; +import { AddWizardProps } from "../../AddWizardDialog"; +import BucketACLInput from "../../EditStoragePolicy/BucketACLInput"; +import BucketCorsTable from "../../EditStoragePolicy/BucketCorsTable"; +const CosWizard = ({ onSubmit }: AddWizardProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(false); + const { enqueueSnackbar } = useSnackbar(); + const [corsAdded, setCorsAdded] = useState(false); + const formRef = useRef(null); + const [policy, setPolicy] = useState({ + id: 0, + node_id: 0, + name: "", + type: PolicyType.cos, + is_private: true, + dir_name_rule: "uploads/{uid}/{path}", + settings: { + chunk_size: 25 << 20, + thumb_exts: ["png", "jpg", "jpeg", "gif", "bmp", "webp"], + media_meta_exts: ["jpg", "jpeg", "png", "bmp", "webp", "tiff", "avif", "heif"], + media_meta_generator_proxy: true, + thumb_generator_proxy: true, + chunk_concurrency: 3, + }, + file_name_rule: "{uuid}_{originname}", + edges: {}, + }); + + const hamdleCreateCors = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + setLoading(true); + dispatch(createStoragePolicyCors({ policy })) + .then(() => { + enqueueSnackbar(t("policy.corsPolicyAdded"), { variant: "success", action: DefaultCloseAction }); + setCorsAdded(true); + }) + .finally(() => { + setLoading(false); + }); + }; + + const handleSubmit = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + onSubmit(policy); + }; + + return ( +
    + + + setPolicy({ ...policy, name: e.target.value })} + /> + {t("policy.policyName")} + + + setPolicy({ ...policy, bucket_name: e.target.value })} + /> + + , ]} + /> + + + + + setPolicy({ ...policy, is_private: value })} + /> + {t("policy.bucketTypeDes")} + + + + setPolicy({ ...policy, server: e.target.value })} + variant={"outlined"} + /> + + , ]} /> + + + + + setPolicy({ ...policy, access_key: e.target.value })} + /> + setPolicy({ ...policy, secret_key: e.target.value })} + /> + + , + , + , + ]} + /> + + + + + + + {t("policy.ossCORSDes")} + + + + setCorsAdded(true)}> + {t("policy.addedManually")} + + + + + + + + + ); +}; + +export default CosWizard; diff --git a/src/component/Admin/StoragePolicy/Wizards/KS3/KS3Wizard.tsx b/src/component/Admin/StoragePolicy/Wizards/KS3/KS3Wizard.tsx new file mode 100755 index 0000000..de2cba6 --- /dev/null +++ b/src/component/Admin/StoragePolicy/Wizards/KS3/KS3Wizard.tsx @@ -0,0 +1,191 @@ +import { Button, Checkbox, Collapse, FormControl, FormControlLabel, Stack } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useMemo, useRef, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { createStoragePolicyCors } from "../../../../../api/api"; +import { StoragePolicy } from "../../../../../api/dashboard"; +import { PolicyType } from "../../../../../api/explorer"; +import { useAppDispatch } from "../../../../../redux/hooks"; +import { DefaultCloseAction } from "../../../../Common/Snackbar/snackbar"; +import { DenseFilledTextField, SecondaryButton } from "../../../../Common/StyledComponents"; +import SettingForm from "../../../../Pages/Setting/SettingForm"; +import { Code } from "../../../../Common/Code.tsx"; +import { EndpointInput } from "../../../Common/EndpointInput"; +import { NoMarginHelperText } from "../../../Settings/Settings"; +import { AddWizardProps } from "../../AddWizardDialog"; +import BucketACLInput from "../../EditStoragePolicy/BucketACLInput"; +import BucketCorsTable from "../../EditStoragePolicy/BucketCorsTable"; +import { PolicyPropsMap } from "../../StoragePolicySetting"; + +const KS3Wizard = ({ onSubmit }: AddWizardProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(false); + const { enqueueSnackbar } = useSnackbar(); + const [corsAdded, setCorsAdded] = useState(false); + const formRef = useRef(null); + const [policy, setPolicy] = useState({ + id: 0, + node_id: 0, + name: "", + type: PolicyType.ks3, + is_private: true, + dir_name_rule: "uploads/{uid}/{path}", + settings: { + chunk_size: 25 << 20, + media_meta_generator_proxy: true, + thumb_generator_proxy: true, + chunk_concurrency: 3, + }, + file_name_rule: "{uuid}_{originname}", + edges: {}, + }); + + const policyProps = useMemo(() => { + return PolicyPropsMap[PolicyType.ks3]; + }, []); + + const hamdleCreateCors = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + setLoading(true); + dispatch(createStoragePolicyCors({ policy })) + .then(() => { + enqueueSnackbar(t("policy.corsPolicyAdded"), { variant: "success", action: DefaultCloseAction }); + setCorsAdded(true); + }) + .finally(() => { + setLoading(false); + }); + }; + + const handleSubmit = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + onSubmit(policy); + }; + + return ( +
    + + + setPolicy({ ...policy, name: e.target.value })} + /> + {t("policy.policyName")} + + + setPolicy({ ...policy, bucket_name: e.target.value })} + /> + + + + setPolicy({ ...policy, is_private: value })} + /> + {t("policy.bucketTypeDes")} + + + + setPolicy({ ...policy, server: e.target.value })} + variant={"outlined"} + /> + + ]} /> + + + setPolicy({ + ...policy, + settings: { ...policy.settings, s3_path_style: e.target.checked ? true : undefined }, + }) + } + /> + } + label={t("policy.usePathEndpoint")} + /> + + ]} /> + + + + setPolicy({ ...policy, settings: { ...policy.settings, region: e.target.value } })} + /> + {policyProps.regionCodeDes} + + + + setPolicy({ ...policy, access_key: e.target.value })} + /> + setPolicy({ ...policy, secret_key: e.target.value })} + /> + + + + + + {t("policy.ossCORSDes")} + + + + setCorsAdded(true)}> + {t("policy.addedManually")} + + + + + + + +
    + ); +}; + +export default KS3Wizard; diff --git a/src/component/Admin/StoragePolicy/Wizards/Local/LocalWizard.tsx b/src/component/Admin/StoragePolicy/Wizards/Local/LocalWizard.tsx new file mode 100755 index 0000000..d211ecc --- /dev/null +++ b/src/component/Admin/StoragePolicy/Wizards/Local/LocalWizard.tsx @@ -0,0 +1,52 @@ +import { Button } from "@mui/material"; +import { useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { StoragePolicy } from "../../../../../api/dashboard"; +import { PolicyType } from "../../../../../api/explorer"; +import { DenseFilledTextField } from "../../../../Common/StyledComponents"; +import SettingForm from "../../../../Pages/Setting/SettingForm"; +import { NoMarginHelperText } from "../../../Settings/Settings"; +import { AddWizardProps } from "../../AddWizardDialog"; + +const LocalWizard = ({ onSubmit }: AddWizardProps) => { + const { t } = useTranslation("dashboard"); + const formRef = useRef(null); + const [policy, setPolicy] = useState({ + id: 0, + name: "", + type: PolicyType.local, + file_name_rule: "{uuid}_{originname}", + settings: { + chunk_size: 25 << 20, + pre_allocate: true, + }, + edges: {}, + }); + + const handleSubmit = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + onSubmit(policy); + }; + + return ( +
    + + setPolicy({ ...policy, name: e.target.value })} + /> + {t("policy.policyName")} + + +
    + ); +}; + +export default LocalWizard; diff --git a/src/component/Admin/StoragePolicy/Wizards/OBS/ObsWizard.tsx b/src/component/Admin/StoragePolicy/Wizards/OBS/ObsWizard.tsx new file mode 100755 index 0000000..de2df10 --- /dev/null +++ b/src/component/Admin/StoragePolicy/Wizards/OBS/ObsWizard.tsx @@ -0,0 +1,180 @@ +import { Button, Collapse, FormControl, Link, Stack } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useRef, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { createStoragePolicyCors } from "../../../../../api/api"; +import { StoragePolicy } from "../../../../../api/dashboard"; +import { PolicyType } from "../../../../../api/explorer"; +import { useAppDispatch } from "../../../../../redux/hooks"; +import { DefaultCloseAction } from "../../../../Common/Snackbar/snackbar"; +import { DenseFilledTextField, SecondaryButton } from "../../../../Common/StyledComponents"; +import SettingForm from "../../../../Pages/Setting/SettingForm"; +import { Code } from "../../../../Common/Code.tsx"; +import { EndpointInput } from "../../../Common/EndpointInput"; +import { NoMarginHelperText } from "../../../Settings/Settings"; +import { AddWizardProps } from "../../AddWizardDialog"; +import BucketACLInput from "../../EditStoragePolicy/BucketACLInput"; +import BucketCorsTable from "../../EditStoragePolicy/BucketCorsTable"; + +const ObsWizard = ({ onSubmit }: AddWizardProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(false); + const { enqueueSnackbar } = useSnackbar(); + const [corsAdded, setCorsAdded] = useState(false); + const formRef = useRef(null); + const [policy, setPolicy] = useState({ + id: 0, + node_id: 0, + name: "", + type: PolicyType.obs, + is_private: true, + dir_name_rule: "uploads/{uid}/{path}", + settings: { + chunk_size: 25 << 20, + thumb_exts: ["png", "jpg", "jpeg", "gif", "bmp", "webp", "tiff"], + media_meta_exts: ["png", "jpg", "jpeg", "gif", "bmp", "webp", "tiff"], + media_meta_generator_proxy: true, + thumb_generator_proxy: true, + chunk_concurrency: 3, + }, + file_name_rule: "{uuid}_{originname}", + edges: {}, + }); + + const hamdleCreateCors = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + setLoading(true); + dispatch(createStoragePolicyCors({ policy })) + .then(() => { + enqueueSnackbar(t("policy.corsPolicyAdded"), { variant: "success", action: DefaultCloseAction }); + setCorsAdded(true); + }) + .finally(() => { + setLoading(false); + }); + }; + + const handleSubmit = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + onSubmit(policy); + }; + + return ( +
    + + + setPolicy({ ...policy, name: e.target.value })} + /> + {t("policy.policyName")} + + + setPolicy({ ...policy, bucket_name: e.target.value })} + /> + + , + , + , + , + ]} + /> + + + + + setPolicy({ ...policy, is_private: value })} + /> + {t("policy.bucketTypeDes")} + + + + setPolicy({ ...policy, server: e.target.value })} + variant={"outlined"} + /> + + , ]} /> + {t("policy.obsEndpointCnameHint")} + + + + + setPolicy({ ...policy, access_key: e.target.value })} + /> + setPolicy({ ...policy, secret_key: e.target.value })} + /> + + , + , + , + ]} + /> + + + + + + + {t("policy.ossCORSDes")} + + + + setCorsAdded(true)}> + {t("policy.addedManually")} + + + + + + + + + ); +}; + +export default ObsWizard; diff --git a/src/component/Admin/StoragePolicy/Wizards/OSS/OssWizard.tsx b/src/component/Admin/StoragePolicy/Wizards/OSS/OssWizard.tsx new file mode 100755 index 0000000..22417c1 --- /dev/null +++ b/src/component/Admin/StoragePolicy/Wizards/OSS/OssWizard.tsx @@ -0,0 +1,183 @@ +import { Button, Collapse, FormControl, Link, Stack } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useMemo, useRef, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { createStoragePolicyCors } from "../../../../../api/api"; +import { StoragePolicy } from "../../../../../api/dashboard"; +import { PolicyType } from "../../../../../api/explorer"; +import { useAppDispatch } from "../../../../../redux/hooks"; +import { DefaultCloseAction } from "../../../../Common/Snackbar/snackbar"; +import { DenseFilledTextField, SecondaryButton } from "../../../../Common/StyledComponents"; +import SettingForm from "../../../../Pages/Setting/SettingForm"; +import { Code } from "../../../../Common/Code.tsx"; +import { NoMarginHelperText } from "../../../Settings/Settings"; +import { AddWizardProps } from "../../AddWizardDialog"; +import BucketACLInput from "../../EditStoragePolicy/BucketACLInput"; +import BucketCorsTable from "../../EditStoragePolicy/BucketCorsTable"; +import { PolicyPropsMap } from "../../StoragePolicySetting"; +const OssWizard = ({ onSubmit }: AddWizardProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(false); + const { enqueueSnackbar } = useSnackbar(); + const [corsAdded, setCorsAdded] = useState(false); + const formRef = useRef(null); + const [policy, setPolicy] = useState({ + id: 0, + node_id: 0, + name: "", + type: PolicyType.oss, + is_private: true, + dir_name_rule: "uploads/{uid}/{path}", + settings: { + chunk_size: 25 << 20, + thumb_exts: ["png", "jpg", "jpeg", "gif", "bmp", "webp", "heic", "tiff", "avif"], + media_meta_exts: ["jpg", "jpeg", "png", "gif", "bmp", "webp", "tiff", "heic", "heif"], + media_meta_generator_proxy: true, + thumb_generator_proxy: true, + chunk_concurrency: 3, + }, + file_name_rule: "{uuid}_{originname}", + edges: {}, + }); + + const policyProps = useMemo(() => { + return PolicyPropsMap[PolicyType.oss]; + }, []); + + const hamdleCreateCors = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + setLoading(true); + dispatch(createStoragePolicyCors({ policy })) + .then(() => { + enqueueSnackbar(t("policy.corsPolicyAdded"), { variant: "success", action: DefaultCloseAction }); + setCorsAdded(true); + }) + .finally(() => { + setLoading(false); + }); + }; + + const handleSubmit = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + onSubmit(policy); + }; + + return ( +
    + + + setPolicy({ ...policy, name: e.target.value })} + /> + {t("policy.policyName")} + + + setPolicy({ ...policy, bucket_name: e.target.value })} + /> + + , , ]} + /> + + + + + setPolicy({ ...policy, is_private: value })} + /> + {t("policy.bucketTypeDes")} + + + + setPolicy({ ...policy, server: e.target.value })} + /> + + , , ]} /> + {t("policy.ossEndpointDesInternalHint")} + + + + setPolicy({ ...policy, settings: { ...policy.settings, region: e.target.value } })} + /> + {policyProps.regionCodeDes} + + + + setPolicy({ ...policy, access_key: e.target.value })} + /> + setPolicy({ ...policy, secret_key: e.target.value })} + /> + + , + , + , + ]} + /> + + + + + + + {t("policy.ossCORSDes")} + + + + setCorsAdded(true)}> + {t("policy.addedManually")} + + + + + + + + + ); +}; + +export default OssWizard; diff --git a/src/component/Admin/StoragePolicy/Wizards/OneDrive/GraphEndpointSelection.tsx b/src/component/Admin/StoragePolicy/Wizards/OneDrive/GraphEndpointSelection.tsx new file mode 100755 index 0000000..f417cea --- /dev/null +++ b/src/component/Admin/StoragePolicy/Wizards/OneDrive/GraphEndpointSelection.tsx @@ -0,0 +1,61 @@ +import { Box, OutlinedSelectProps, Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { DenseSelect } from "../../../../Common/StyledComponents"; +import { SquareMenuItem } from "../../../../FileManager/ContextMenu/ContextMenu"; + +export interface GraphEndpointSelectionProps extends OutlinedSelectProps { + value: string; + onChange: (value: string) => void; +} + +interface GraphEndpoint { + name: string; + endpoint: string; +} + +const graphEndpoints: GraphEndpoint[] = [ + { name: "policy.multiTenant", endpoint: "https://graph.microsoft.com/v1.0" }, + { name: "policy.gallatin", endpoint: "https://microsoftgraph.chinacloudapi.cn/v1.0" }, +]; + +const GraphEndpointSelection = ({ value, onChange, ...rest }: GraphEndpointSelectionProps) => { + const { t } = useTranslation("dashboard"); + + return ( + onChange(e.target.value as string)} + MenuProps={{ + PaperProps: { sx: { maxWidth: 230 } }, + MenuListProps: { + sx: { + "& .MuiMenuItem-root": { + whiteSpace: "normal", + }, + }, + }, + }} + {...rest} + > + {graphEndpoints.map((endpoint) => ( + + + + {t(endpoint.name)} + + + {endpoint.endpoint} + + + + ))} + + ); +}; + +export default GraphEndpointSelection; diff --git a/src/component/Admin/StoragePolicy/Wizards/OneDrive/OneDriveAuthDialog.tsx b/src/component/Admin/StoragePolicy/Wizards/OneDrive/OneDriveAuthDialog.tsx new file mode 100755 index 0000000..f8a0de0 --- /dev/null +++ b/src/component/Admin/StoragePolicy/Wizards/OneDrive/OneDriveAuthDialog.tsx @@ -0,0 +1,104 @@ +import { Alert, Box, Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { getPolicyOauthRedirectUrl } from "../../../../../api/api"; +import { useAppDispatch } from "../../../../../redux/hooks"; +import { DenseFilledTextField } from "../../../../Common/StyledComponents"; +import DraggableDialog from "../../../../Dialogs/DraggableDialog"; +import { Code } from "../../../../Common/Code.tsx"; +import { NoMarginHelperText } from "../../../Settings/Settings"; + +interface OneDriveAuthDialogProps { + open: boolean; + onClose: () => void; + onConfirm: (appId: string, appSecret: string) => void; + initialAppId?: string; + initialAppSecret?: string; +} + +const OneDriveAuthDialog = ({ + open, + onClose, + onConfirm, + initialAppId = "", + initialAppSecret = "", +}: OneDriveAuthDialogProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(false); + const [redirectUrl, setRedirectUrl] = useState(""); + const [appId, setAppId] = useState(initialAppId); + const [appSecret, setAppSecret] = useState(initialAppSecret); + const isHttps = window.location.protocol === "https:"; + + useEffect(() => { + if (open) { + setLoading(true); + dispatch(getPolicyOauthRedirectUrl()).then((res) => { + setRedirectUrl(res); + setLoading(false); + }); + } + }, [open, dispatch]); + + const handleConfirm = () => { + onConfirm(appId, appSecret); + onClose(); + }; + + return ( + + + + + {t("policy.authorizeOneDriveDes")} + + + + + {t("policy.redirectUrl")} + + + {redirectUrl} + + + {t("policy.redirectUrlDes")} + + + + + + {t("policy.aadAppID")} + + setAppId(e.target.value)} /> + + , ]} /> + + + + + + {t("policy.aadAppSecret")} + + setAppSecret(e.target.value)} /> + + , , ]} /> + + + {!isHttps && {t("policy.httpsRequired")}} + + + + + + + + ); +}; + +export default OneDriveAuthDialog; diff --git a/src/component/Admin/StoragePolicy/Wizards/OneDrive/OneDriveWizard.tsx b/src/component/Admin/StoragePolicy/Wizards/OneDrive/OneDriveWizard.tsx new file mode 100755 index 0000000..5ad304e --- /dev/null +++ b/src/component/Admin/StoragePolicy/Wizards/OneDrive/OneDriveWizard.tsx @@ -0,0 +1,138 @@ +import { Box, Button, Link, Stack, Typography } from "@mui/material"; +import { useEffect, useRef, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { getPolicyOauthRedirectUrl } from "../../../../../api/api"; +import { StoragePolicy } from "../../../../../api/dashboard"; +import { PolicyType } from "../../../../../api/explorer"; +import { useAppDispatch } from "../../../../../redux/hooks"; +import { DenseFilledTextField } from "../../../../Common/StyledComponents"; +import SettingForm from "../../../../Pages/Setting/SettingForm"; +import { Code } from "../../../../Common/Code.tsx"; +import { NoMarginHelperText } from "../../../Settings/Settings"; +import { AddWizardProps } from "../../AddWizardDialog"; +import GraphEndpointSelection from "./GraphEndpointSelection"; + +const wwGraph = "https://graph.microsoft.com/v1.0"; + +const OneDriveWizard = ({ onSubmit }: AddWizardProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [redirectUrl, setRedirectUrl] = useState("Loading..."); + const formRef = useRef(null); + const [policy, setPolicy] = useState({ + id: 0, + node_id: 0, + name: "", + type: PolicyType.onedrive, + server: wwGraph, + is_private: true, + dir_name_rule: "uploads/{uid}/{path}", + settings: { + chunk_size: 50 << 20, + thumb_support_all_exts: true, + media_meta_generator_proxy: true, + }, + file_name_rule: "{uuid}_{originname}", + edges: {}, + }); + + const handleSubmit = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + onSubmit(policy); + }; + + useEffect(() => { + dispatch(getPolicyOauthRedirectUrl()).then((res) => { + setRedirectUrl(res); + }); + }, []); + + return ( +
    + + + setPolicy({ ...policy, name: e.target.value })} + /> + {t("policy.policyName")} + + + setPolicy({ ...policy, server: value })} + /> + {t("policy.aadAccountCloudDes")} + + + + , + , + ]} + /> + + + , , , , , , ]} + /> + + setPolicy({ ...policy, bucket_name: e.target.value })} + /> + + , ]} /> + + setPolicy({ ...policy, secret_key: e.target.value })} + /> + + , , ]} /> + + + + + {t("policy.grantAccessLater")} + + + + + + + + ); +}; + +export default OneDriveWizard; diff --git a/src/component/Admin/StoragePolicy/Wizards/Qiniu/QiniuWizard.tsx b/src/component/Admin/StoragePolicy/Wizards/Qiniu/QiniuWizard.tsx new file mode 100755 index 0000000..50dbd80 --- /dev/null +++ b/src/component/Admin/StoragePolicy/Wizards/Qiniu/QiniuWizard.tsx @@ -0,0 +1,147 @@ +import { Button, FormControl, Link, Stack } from "@mui/material"; +import { useRef, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { StoragePolicy } from "../../../../../api/dashboard"; +import { PolicyType } from "../../../../../api/explorer"; +import { useAppDispatch } from "../../../../../redux/hooks"; +import { DenseFilledTextField } from "../../../../Common/StyledComponents"; +import SettingForm from "../../../../Pages/Setting/SettingForm"; +import { EndpointInput } from "../../../Common/EndpointInput"; +import { NoMarginHelperText } from "../../../Settings/Settings"; +import { AddWizardProps } from "../../AddWizardDialog"; +import BucketACLInput from "../../EditStoragePolicy/BucketACLInput"; +const QiniuWizard = ({ onSubmit }: AddWizardProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const formRef = useRef(null); + const [policy, setPolicy] = useState({ + id: 0, + node_id: 0, + name: "", + type: PolicyType.qiniu, + is_private: true, + dir_name_rule: "uploads/{uid}/{path}", + settings: { + chunk_size: 25 << 20, + thumb_exts: ["psd", "jpeg", "png", "gif", "webp", "tiff", "bmp", "avif", "heic"], + thumb_max_size: 20 << 20, + media_meta_exts: [ + "avi", + "mp4", + "mkv", + "mov", + "webm", + "opus", + "flv", + "hls", + "ts", + "dash", + "mp3", + "aac", + "flac", + "wav", + "amr", + "jpg", + "jpeg", + "png", + "gif", + "bmp", + "webp", + "tiff", + "heic", + "heif", + ], + media_meta_generator_proxy: true, + thumb_generator_proxy: true, + custom_proxy: true, + chunk_concurrency: 3, + }, + file_name_rule: "{uuid}_{originname}", + edges: {}, + }); + + const handleSubmit = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + onSubmit(policy); + }; + + return ( +
    + + + setPolicy({ ...policy, name: e.target.value })} + /> + {t("policy.policyName")} + + + setPolicy({ ...policy, bucket_name: e.target.value })} + /> + + ]} + /> + + + + + setPolicy({ ...policy, is_private: value })} + /> + {t("policy.bucketTypeDes")} + + + + setPolicy({ ...policy, settings: { ...policy.settings, proxy_server: e.target.value } })} + variant={"outlined"} + /> + {t("policy.bucketDomainDes")} + + + + setPolicy({ ...policy, access_key: e.target.value })} + /> + setPolicy({ ...policy, secret_key: e.target.value })} + /> + {t("policy.qiniuCredentialDes")} + + + + +
    + ); +}; + +export default QiniuWizard; diff --git a/src/component/Admin/StoragePolicy/Wizards/Remote/RemoteWizard.tsx b/src/component/Admin/StoragePolicy/Wizards/Remote/RemoteWizard.tsx new file mode 100755 index 0000000..4e70c63 --- /dev/null +++ b/src/component/Admin/StoragePolicy/Wizards/Remote/RemoteWizard.tsx @@ -0,0 +1,130 @@ +import { Button, Link, Stack } from "@mui/material"; +import { useRef, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Link as RouterLink } from "react-router-dom"; +import { StoragePolicy } from "../../../../../api/dashboard"; +import { PolicyType } from "../../../../../api/explorer"; +import { DenseFilledTextField } from "../../../../Common/StyledComponents"; +import SettingForm from "../../../../Pages/Setting/SettingForm"; +import NodeSelectionInput from "../../../Common/NodeSelectionInput"; +import { NoMarginHelperText } from "../../../Settings/Settings"; +import { AddWizardProps } from "../../AddWizardDialog"; +const RemoteWizard = ({ onSubmit }: AddWizardProps) => { + const { t } = useTranslation("dashboard"); + const formRef = useRef(null); + const [policy, setPolicy] = useState({ + id: 0, + node_id: 0, + name: "", + type: PolicyType.remote, + dir_name_rule: "data/uploads/{uid}/{path}", + settings: { + chunk_size: 25 << 20, + pre_allocate: true, + thumb_exts: ["jpg", "png", "gif", "jpeg", "mp3", "m4a", "ogg", "flag"], + thumb_generator_proxy: true, + media_meta_exts: [ + "mp3", + "m4a", + "ogg", + "flac", + "jpg", + "jpeg", + "png", + "heic", + "heif", + "tiff", + "avif", + "3fr", + "ari", + "arw", + "bay", + "braw", + "crw", + "cr2", + "cr3", + "cap", + "data", + "dcs", + "dcr", + "dng", + "drf", + "eip", + "erf", + "fff", + "gpr", + "iiq", + "k25", + "kdc", + "mdc", + "mef", + "mos", + "mrw", + "nef", + "nrw", + "obm", + "orf", + "pef", + "ptx", + "pxn", + "r3d", + "raf", + "raw", + "rwl", + "rw2", + "rwz", + "sr2", + "srf", + "srw", + "tif", + "x3f", + ], + }, + file_name_rule: "{uuid}_{originname}", + edges: {}, + }); + + const handleSubmit = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + onSubmit(policy); + }; + + return ( +
    + + + setPolicy({ ...policy, name: e.target.value })} + /> + {t("policy.policyName")} + + + setPolicy({ ...policy, node_id: value })} + /> + + ]} + /> + + + + +
    + ); +}; + +export default RemoteWizard; diff --git a/src/component/Admin/StoragePolicy/Wizards/S3/S3Wizard.tsx b/src/component/Admin/StoragePolicy/Wizards/S3/S3Wizard.tsx new file mode 100755 index 0000000..c52d643 --- /dev/null +++ b/src/component/Admin/StoragePolicy/Wizards/S3/S3Wizard.tsx @@ -0,0 +1,190 @@ +import { Button, Checkbox, Collapse, FormControl, FormControlLabel, Stack } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useMemo, useRef, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { createStoragePolicyCors } from "../../../../../api/api"; +import { StoragePolicy } from "../../../../../api/dashboard"; +import { PolicyType } from "../../../../../api/explorer"; +import { useAppDispatch } from "../../../../../redux/hooks"; +import { DefaultCloseAction } from "../../../../Common/Snackbar/snackbar"; +import { DenseFilledTextField, SecondaryButton } from "../../../../Common/StyledComponents"; +import SettingForm from "../../../../Pages/Setting/SettingForm"; +import { Code } from "../../../../Common/Code.tsx"; +import { EndpointInput } from "../../../Common/EndpointInput"; +import { NoMarginHelperText } from "../../../Settings/Settings"; +import { AddWizardProps } from "../../AddWizardDialog"; +import BucketACLInput from "../../EditStoragePolicy/BucketACLInput"; +import BucketCorsTable from "../../EditStoragePolicy/BucketCorsTable"; +import { PolicyPropsMap } from "../../StoragePolicySetting"; + +const S3Wizard = ({ onSubmit }: AddWizardProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(false); + const { enqueueSnackbar } = useSnackbar(); + const [corsAdded, setCorsAdded] = useState(false); + const formRef = useRef(null); + const [policy, setPolicy] = useState({ + id: 0, + node_id: 0, + name: "", + type: PolicyType.s3, + is_private: true, + dir_name_rule: "uploads/{uid}/{path}", + settings: { + chunk_size: 25 << 20, + media_meta_generator_proxy: true, + thumb_generator_proxy: true, + }, + file_name_rule: "{uuid}_{originname}", + edges: {}, + }); + + const policyProps = useMemo(() => { + return PolicyPropsMap[PolicyType.s3]; + }, []); + + const hamdleCreateCors = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + setLoading(true); + dispatch(createStoragePolicyCors({ policy })) + .then(() => { + enqueueSnackbar(t("policy.corsPolicyAdded"), { variant: "success", action: DefaultCloseAction }); + setCorsAdded(true); + }) + .finally(() => { + setLoading(false); + }); + }; + + const handleSubmit = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + onSubmit(policy); + }; + + return ( +
    + + + setPolicy({ ...policy, name: e.target.value })} + /> + {t("policy.policyName")} + + + setPolicy({ ...policy, bucket_name: e.target.value })} + /> + + + + setPolicy({ ...policy, is_private: value })} + /> + {t("policy.bucketTypeDes")} + + + + setPolicy({ ...policy, server: e.target.value })} + variant={"outlined"} + /> + + ]} /> + + + setPolicy({ + ...policy, + settings: { ...policy.settings, s3_path_style: e.target.checked ? true : undefined }, + }) + } + /> + } + label={t("policy.usePathEndpoint")} + /> + + ]} /> + + + + setPolicy({ ...policy, settings: { ...policy.settings, region: e.target.value } })} + /> + {policyProps.regionCodeDes} + + + + setPolicy({ ...policy, access_key: e.target.value })} + /> + setPolicy({ ...policy, secret_key: e.target.value })} + /> + + + + + + {t("policy.ossCORSDes")} + + + + setCorsAdded(true)}> + {t("policy.addedManually")} + + + + + + + +
    + ); +}; + +export default S3Wizard; diff --git a/src/component/Admin/StoragePolicy/Wizards/Upyun/UpyunWizard.tsx b/src/component/Admin/StoragePolicy/Wizards/Upyun/UpyunWizard.tsx new file mode 100755 index 0000000..49fe751 --- /dev/null +++ b/src/component/Admin/StoragePolicy/Wizards/Upyun/UpyunWizard.tsx @@ -0,0 +1,137 @@ +import { Button, Collapse, FormControl, Link, Stack } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useRef, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { StoragePolicy } from "../../../../../api/dashboard"; +import { PolicyType } from "../../../../../api/explorer"; +import { useAppDispatch } from "../../../../../redux/hooks"; +import { DenseFilledTextField } from "../../../../Common/StyledComponents"; +import SettingForm from "../../../../Pages/Setting/SettingForm"; +import { Code } from "../../../../Common/Code.tsx"; +import { EndpointInput } from "../../../Common/EndpointInput"; +import { NoMarginHelperText } from "../../../Settings/Settings"; +import { AddWizardProps } from "../../AddWizardDialog"; +import BucketACLInput from "../../EditStoragePolicy/BucketACLInput"; + +const UpyunWizard = ({ onSubmit }: AddWizardProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(false); + const { enqueueSnackbar } = useSnackbar(); + const [corsAdded, setCorsAdded] = useState(false); + const formRef = useRef(null); + const [policy, setPolicy] = useState({ + id: 0, + node_id: 0, + name: "", + type: PolicyType.upyun, + is_private: true, + dir_name_rule: "uploads/{uid}/{path}", + settings: { + thumb_exts: ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg"], + media_meta_exts: ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg"], + media_meta_generator_proxy: true, + thumb_generator_proxy: true, + custom_proxy: true, + }, + file_name_rule: "{uuid}_{originname}", + edges: {}, + }); + + const handleSubmit = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + onSubmit(policy); + }; + + return ( +
    + + + setPolicy({ ...policy, name: e.target.value })} + /> + {t("policy.policyName")} + + + setPolicy({ ...policy, bucket_name: e.target.value })} + /> + + ]} + /> + + + + + setPolicy({ ...policy, is_private: value })} + /> + + , ]} /> + + + + + + setPolicy({ ...policy, settings: { ...policy.settings, token: e.target.value } })} + /> + {t("policy.upyunTokenSecretDes")} + + + + setPolicy({ ...policy, settings: { ...policy.settings, proxy_server: e.target.value } })} + variant={"outlined"} + /> + {t("policy.bucketCDNDes")} + + + + setPolicy({ ...policy, access_key: e.target.value })} + /> + setPolicy({ ...policy, secret_key: e.target.value })} + /> + + + + + + ); +}; + +export default UpyunWizard; diff --git a/src/component/Admin/Task/TaskCleanupDialog.tsx b/src/component/Admin/Task/TaskCleanupDialog.tsx new file mode 100755 index 0000000..b449f2a --- /dev/null +++ b/src/component/Admin/Task/TaskCleanupDialog.tsx @@ -0,0 +1,124 @@ +import { Button, DialogContent, SelectChangeEvent, Stack, Typography } from "@mui/material"; +import { DateTimePicker, LocalizationProvider } from "@mui/x-date-pickers"; +import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; +import dayjs from "dayjs"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { sendCleanupTask } from "../../../api/api"; +import { CleanupTaskService } from "../../../api/dashboard"; +import { TaskStatus, TaskType } from "../../../api/workflow"; +import { useAppDispatch } from "../../../redux/hooks"; +import DraggableDialog from "../../Dialogs/DraggableDialog"; +import SettingForm from "../../Pages/Setting/SettingForm"; +import TaskStatusSelector from "./TaskStatusSelector"; +import TaskTypeSelector from "./TaskTypeSelector"; + +export interface TaskCleanupDialogProps { + open: boolean; + onClose: () => void; + onCleanupComplete?: () => void; +} + +const TaskCleanupDialog = ({ open, onClose, onCleanupComplete }: TaskCleanupDialogProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [notAfter, setNotAfter] = useState(null); + const [selectedTypes, setSelectedTypes] = useState([]); + const [selectedStatuses, setSelectedStatuses] = useState([]); + const [loading, setLoading] = useState(false); + + const handleCleanup = async () => { + if (!notAfter) { + return; + } + + setLoading(true); + try { + const args: CleanupTaskService = { + not_after: notAfter.toISOString(), + types: selectedTypes.length > 0 ? selectedTypes : undefined, + status: selectedStatuses.length > 0 ? selectedStatuses : undefined, + }; + + await dispatch(sendCleanupTask(args)); + onCleanupComplete?.(); + onClose(); + } finally { + setLoading(false); + } + }; + + const handleReset = () => { + setNotAfter(null); + setSelectedTypes([]); + setSelectedStatuses([]); + }; + + return ( + + {t("user.reset")} + + } + > + + + + {t("task.cleanupTasksDescription")} + + + + + setNotAfter(newValue)} + slotProps={{ + textField: { + fullWidth: true, + size: "small", + }, + }} + /> + + + + + ) => setSelectedTypes(e.target.value as TaskType[])} + helperText={t("task.cleanupTaskTypesDes")} + showAllOption={false} + displayEmpty={true} + /> + + + + ) => setSelectedStatuses(e.target.value as TaskStatus[])} + helperText={t("task.cleanupTaskStatusesDes")} + showAllOption={false} + displayEmpty={true} + /> + + + + + ); +}; + +export default TaskCleanupDialog; diff --git a/src/component/Admin/Task/TaskContent.tsx b/src/component/Admin/Task/TaskContent.tsx new file mode 100755 index 0000000..c9485a1 --- /dev/null +++ b/src/component/Admin/Task/TaskContent.tsx @@ -0,0 +1,98 @@ +import { Link, Typography } from "@mui/material"; +import { memo, useCallback, useMemo } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Task } from "../../../api/dashboard"; +import { TaskSummary, TaskType } from "../../../api/workflow"; +import CrUri, { Filesystem } from "../../../util/uri"; +import TaskSummaryTitle from "../../Pages/Tasks/TaskSummaryTitle"; + +export const userTaskTypes: string[] = [ + TaskType.relocate, + TaskType.create_archive, + TaskType.extract_archive, + TaskType.remote_download, + TaskType.import, +]; + +export interface TaskContentProps { + task: Task; + openEntity?: (entityID: number) => void; +} + +const processUrl = (url: string, userHashId: string) => { + const crUrl = new CrUri(url); + if (crUrl.fs() == Filesystem.my && !crUrl.id()) { + crUrl.setUsername(userHashId); + } + return crUrl.toString(); +}; + +export const processTaskContent = (summary: TaskSummary, userHashId: string): TaskSummary => { + if (summary.props?.src) { + summary.props.src = processUrl(summary.props.src, userHashId); + } + if (summary.props?.dst) { + summary.props.dst = processUrl(summary.props.dst, userHashId); + } + if (summary.props?.src_multiple) { + summary.props.src_multiple = summary.props.src_multiple.map((url) => processUrl(url, userHashId)); + } + + return summary; +}; + +export const TaskContent = memo(({ task, openEntity }: TaskContentProps) => { + const { t } = useTranslation("dashboard"); + + if (userTaskTypes.includes(task.type ?? "")) { + const processedSummary = processTaskContent({ ...task.summary } as TaskSummary, task?.user_hash_id ?? ""); + return ( + + + + ); + } + + const entityLinkClick = useCallback( + (entityID: number) => (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (openEntity) { + openEntity(entityID); + } + }, + [openEntity], + ); + + const content = useMemo(() => { + var privateState: any = {}; + try { + privateState = JSON.parse(task.private_state ?? "{}"); + } catch (error) { + console.error(error); + } + switch (task.type) { + case TaskType.upload_sentinel_check: + return t("task.uploadSentinelCheck", { uploadSessionID: privateState?.session?.Props?.UploadSessionID }); + case TaskType.media_metadata: + return ( + ]} + /> + ); + case TaskType.entity_recycle_routine: + return t("task.entityRecycleRoutine"); + case TaskType.explicit_entity_recycle: + return t("task.explicitEntityRecycle", { + blobs: privateState?.entity_ids?.map((id: number) => `#${id}`).join(", "), + }); + default: + return ""; + } + }, [task, t]); + + return {content}; +}); diff --git a/src/component/Admin/Task/TaskDialog/BlobErrors.tsx b/src/component/Admin/Task/TaskDialog/BlobErrors.tsx new file mode 100755 index 0000000..87932a0 --- /dev/null +++ b/src/component/Admin/Task/TaskDialog/BlobErrors.tsx @@ -0,0 +1,63 @@ +import { Link, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { StyledTableContainerPaper } from "../../../Common/StyledComponents"; +import EntityDialog from "../../Entity/EntityDialog/EntityDialog"; + +export interface BlobErrorsProps { + privateState: any; +} +export const BlobErrors = ({ privateState }: BlobErrorsProps) => { + const { t } = useTranslation("dashboard"); + const [openEntityDialogOpen, setOpenEntityDialogOpen] = useState(false); + const [openTask, setOpenTask] = useState(undefined); + + const openEntityDialog = (entityID: number) => (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + setOpenEntityDialogOpen(true); + setOpenTask(entityID); + }; + + return ( + + setOpenEntityDialogOpen(false)} entityID={openTask} /> + + + + {t("task.blobID")} + {t("task.retryIndex")} + {t("common:error")} + + + + {privateState?.errors?.map((error: any, retryIndex: number) => [ + ...error.map((e: any, index: number) => [ + + + + + #{e.id} + + + + + + {retryIndex} + + + + + {e.error} + + + , + ]), + ])} + +
    +
    + ); +}; + +export default BlobErrors; diff --git a/src/component/Admin/Task/TaskDialog/TaskDialog.tsx b/src/component/Admin/Task/TaskDialog/TaskDialog.tsx new file mode 100755 index 0000000..96c8c81 --- /dev/null +++ b/src/component/Admin/Task/TaskDialog/TaskDialog.tsx @@ -0,0 +1,84 @@ +import { Box, DialogContent } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { getTaskDetail } from "../../../../api/api.ts"; +import { Task } from "../../../../api/dashboard.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import AutoHeight from "../../../Common/AutoHeight.tsx"; +import FacebookCircularProgress from "../../../Common/CircularProgress.tsx"; +import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import TaskForm from "./TaskForm.tsx"; + +export interface TaskDialogProps { + open: boolean; + onClose: () => void; + taskID?: number; +} + +const TaskDialog = ({ open, onClose, taskID }: TaskDialogProps) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation("dashboard"); + const [values, setValues] = useState({ edges: {}, id: 0 }); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (!taskID || !open) { + return; + } + setLoading(true); + dispatch(getTaskDetail(taskID)) + .then((res) => { + setValues(res); + }) + .catch(() => { + onClose(); + }) + .finally(() => { + setLoading(false); + }); + }, [open]); + + return ( + + + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${loading}`} + > + + {loading && ( + + + + )} + {!loading && } + + + + + + + ); +}; + +export default TaskDialog; diff --git a/src/component/Admin/Task/TaskDialog/TaskForm.tsx b/src/component/Admin/Task/TaskDialog/TaskForm.tsx new file mode 100755 index 0000000..b4a2913 --- /dev/null +++ b/src/component/Admin/Task/TaskDialog/TaskForm.tsx @@ -0,0 +1,299 @@ +import { + Box, + Grid2 as Grid, + Link, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, + useMediaQuery, + useTheme, +} from "@mui/material"; +import dayjs from "dayjs"; +import duration from "dayjs/plugin/duration"; +import { lazy, Suspense, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link as RouterLink } from "react-router-dom"; +import { Task } from "../../../../api/dashboard"; +import { FileType } from "../../../../api/explorer"; +import { TaskStatus, TaskSummary, TaskType } from "../../../../api/workflow"; +import { formatDuration } from "../../../../util/datetime"; +import FacebookCircularProgress from "../../../Common/CircularProgress"; +import { NoWrapTypography, StyledTableContainerPaper } from "../../../Common/StyledComponents"; +import TimeBadge from "../../../Common/TimeBadge"; +import UserAvatar from "../../../Common/User/UserAvatar"; +import FileBadge from "../../../FileManager/FileBadge"; +import SettingForm from "../../../Pages/Setting/SettingForm"; +import DownloadFileList from "../../../Pages/Tasks/DownloadFileList"; +import TaskProgress from "../../../Pages/Tasks/TaskProgress"; +import { getTaskStatusText } from "../../../Pages/Tasks/TaskProps"; +import UserDialog from "../../User/UserDialog/UserDialog"; +import { processTaskContent, userTaskTypes } from "../TaskContent"; +import BlobErrors from "./BlobErrors"; +dayjs.extend(duration); + +const MonacoEditor = lazy(() => import("../../../Viewers/CodeViewer/MonacoEditor")); + +const TaskForm = ({ values }: { values: Task }) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + const { t } = useTranslation("dashboard"); + const [userDialogOpen, setUserDialogOpen] = useState(false); + const [userDialogID, setUserDialogID] = useState(0); + + const userClicked = (e: React.MouseEvent) => { + e.preventDefault(); + setUserDialogOpen(true); + setUserDialogID(values?.edges?.user?.id ?? 0); + }; + + const processedSummary = useMemo(() => { + return processTaskContent({ ...values.summary } as TaskSummary, values?.user_hash_id ?? ""); + }, [values]); + + const privateState = useMemo((): any => { + let res: any = {}; + if (values.private_state) { + try { + res = JSON.parse(values.private_state); + } catch (error) { + console.error(error); + } + } + return res; + }, [values]); + + const isUserTask = useMemo(() => { + return userTaskTypes.includes(values.type ?? ""); + }, [values]); + + return ( + <> + setUserDialogOpen(false)} userID={userDialogID} /> + + + + + {values.id} + + + + + + {t(`task.${values.type}`)} + + + + + + {getTaskStatusText(values?.status ?? TaskStatus.queued, t)} + + + + + + {values?.node?.name ? ( + + {values?.node?.name} + + ) : ( + "-" + )} + + + + + + {values?.edges?.user ? ( + + + + + {values?.edges?.user?.nick} + + + + ) : ( + "-" + )} + + + + + + + + + + + + + + + + + {values?.correlation_id ?? "-"} + + + + + + {formatDuration(dayjs.duration((values?.public_state?.executed_duration ?? 0) / 1e6))} + + + + + + {values?.public_state?.retry_count ?? "-"} + + + + {processedSummary?.props?.src && ( + + + + )} + + {processedSummary?.props?.src_multiple && ( + + + {processedSummary?.props.src_multiple.map((src, index) => ( + + ))} + + + )} + + {processedSummary?.props?.dst && ( + + + + )} + + {values?.public_state?.error && ( + + + {values?.public_state?.error} + + + )} + + {processedSummary?.props?.download && ( + + + + )} + + {isUserTask && ( + + + + )} + + {values?.public_state?.error_history && ( + + + + + + # + {t("common:error")} + + + + {values?.public_state?.error_history.map((error, index) => ( + + + {index + 1} + + {error} + + ))} + +
    +
    +
    + )} + + {values.type == TaskType.entity_recycle_routine && privateState?.errors && ( + + + + )} + + + }> + + + +
    +
    + + ); +}; + +export default TaskForm; diff --git a/src/component/Admin/Task/TaskFilterPopover.tsx b/src/component/Admin/Task/TaskFilterPopover.tsx new file mode 100755 index 0000000..db760a8 --- /dev/null +++ b/src/component/Admin/Task/TaskFilterPopover.tsx @@ -0,0 +1,208 @@ +import { Box, Button, ListItemText, Popover, PopoverProps, Stack } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { TaskStatus, TaskType } from "../../../api/workflow"; +import { DenseFilledTextField, DenseSelect } from "../../Common/StyledComponents"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu"; +import SettingForm from "../../Pages/Setting/SettingForm"; +import { getTaskStatusText } from "../../Pages/Tasks/TaskProps"; + +export interface TaskFilterPopoverProps extends PopoverProps { + status: string; + setStatus: (status: string) => void; + user: string; + setUser: (user: string) => void; + correlationID: string; + setCorrelationID: (correlationID: string) => void; + type: string; + setType: (type: string) => void; + clearFilters: () => void; +} + +const TaskFilterPopover = ({ + status, + setStatus, + user, + setUser, + correlationID, + setCorrelationID, + type, + setType, + clearFilters, + onClose, + open, + ...rest +}: TaskFilterPopoverProps) => { + const { t } = useTranslation("dashboard"); + + // Create local state to track changes before applying + const [localStatus, setLocalStatus] = useState(status); + const [localUser, setLocalUser] = useState(user); + const [localCorrelationID, setLocalCorrelationID] = useState(correlationID); + const [localType, setLocalType] = useState(type); + + // Initialize local state when popup opens + useEffect(() => { + if (open) { + setLocalStatus(status); + setLocalUser(user); + setLocalCorrelationID(correlationID); + setLocalType(type); + } + }, [open]); + + // Apply filters and close popover + const handleApplyFilters = () => { + setStatus(localStatus); + setUser(localUser); + setCorrelationID(localCorrelationID); + setType(localType); + onClose?.({}, "backdropClick"); + }; + + // Reset filters and close popover + const handleResetFilters = () => { + setLocalStatus(""); + setLocalUser(""); + setLocalCorrelationID(""); + setLocalType(""); + clearFilters(); + onClose?.({}, "backdropClick"); + }; + + return ( + + + + setLocalUser(e.target.value)} + placeholder={t("user.emptyNoFilter")} + size="small" + /> + + + + setLocalCorrelationID(e.target.value)} + placeholder={t("user.emptyNoFilter")} + size="small" + /> + + + + setLocalType(e.target.value === " " ? "" : (e.target.value as string))} + > + {[ + TaskType.create_archive, + TaskType.extract_archive, + TaskType.remote_download, + TaskType.media_metadata, + TaskType.entity_recycle_routine, + TaskType.explicit_entity_recycle, + TaskType.upload_sentinel_check, + TaskType.import, + ].map((type) => ( + + + + ))} + + {t("user.all")}} + slotProps={{ + primary: { + variant: "body2", + }, + }} + /> + + + + + + setLocalStatus(e.target.value === " " ? "" : (e.target.value as string))} + > + {[ + TaskStatus.queued, + TaskStatus.processing, + TaskStatus.suspending, + TaskStatus.error, + TaskStatus.completed, + ].map((status) => ( + + + + ))} + + {t("user.all")}} + slotProps={{ + primary: { + variant: "body2", + }, + }} + /> + + + + + + + + + + ); +}; + +export default TaskFilterPopover; diff --git a/src/component/Admin/Task/TaskList.tsx b/src/component/Admin/Task/TaskList.tsx new file mode 100755 index 0000000..ebd7031 --- /dev/null +++ b/src/component/Admin/Task/TaskList.tsx @@ -0,0 +1,334 @@ +import { Delete } from "@mui/icons-material"; +import { + Badge, + Box, + Button, + Checkbox, + Container, + Divider, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TableSortLabel, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { bindPopover, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import { useQueryState } from "nuqs"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { batchDeleteTasks, getTaskList } from "../../../api/api"; +import { AdminListService, Task } from "../../../api/dashboard"; +import { useAppDispatch } from "../../../redux/hooks"; +import { confirmOperation } from "../../../redux/thunks/dialog"; +import { NoWrapTableCell, SecondaryButton, StyledTableContainerPaper } from "../../Common/StyledComponents"; +import ArrowSync from "../../Icons/ArrowSync"; +import Broom from "../../Icons/Broom"; +import Filter from "../../Icons/Filter"; +import PageContainer from "../../Pages/PageContainer"; +import PageHeader from "../../Pages/PageHeader"; +import TablePagination from "../Common/TablePagination"; +import EntityDialog from "../Entity/EntityDialog/EntityDialog"; +import { OrderByQuery, OrderDirectionQuery, PageQuery, PageSizeQuery } from "../StoragePolicy/StoragePolicySetting"; +import UserDialog from "../User/UserDialog/UserDialog"; +import TaskCleanupDialog from "./TaskCleanupDialog"; +import TaskDialog from "./TaskDialog/TaskDialog"; +import TaskFilterPopover from "./TaskFilterPopover"; +import TaskRow from "./TaskRow"; + +export const UserQuery = "user"; +export const TypeQuery = "type"; +export const StatusQuery = "status"; +export const CorrelationIDQuery = "correlation_id"; + +const TaskList = () => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(true); + const [tasks, setTasks] = useState([]); + const [page, setPage] = useQueryState(PageQuery, { defaultValue: "1" }); + const [pageSize, setPageSize] = useQueryState(PageSizeQuery, { + defaultValue: "10", + }); + const [orderBy, setOrderBy] = useQueryState(OrderByQuery, { + defaultValue: "", + }); + const [orderDirection, setOrderDirection] = useQueryState(OrderDirectionQuery, { defaultValue: "desc" }); + const [user, setUser] = useQueryState(UserQuery, { defaultValue: "" }); + const [type, setType] = useQueryState(TypeQuery, { defaultValue: "" }); + const [status, setStatus] = useQueryState(StatusQuery, { defaultValue: "" }); + const [correlationID, setCorrelationID] = useQueryState(CorrelationIDQuery, { defaultValue: "" }); + + const [count, setCount] = useState(0); + const [selected, setSelected] = useState([]); + const filterPopupState = usePopupState({ + variant: "popover", + popupId: "taskFilterPopover", + }); + + const [userDialogOpen, setUserDialogOpen] = useState(false); + const [userDialogID, setUserDialogID] = useState(undefined); + const [openEntity, setOpenEntity] = useState(undefined); + const [openEntityDialogOpen, setOpenEntityDialogOpen] = useState(false); + const [openTask, setOpenTask] = useState(undefined); + const [openTaskDialogOpen, setOpenTaskDialogOpen] = useState(false); + const [deleteLoading, setDeleteLoading] = useState(false); + const [cleanupDialogOpen, setCleanupDialogOpen] = useState(false); + + const pageInt = parseInt(page) ?? 1; + const pageSizeInt = parseInt(pageSize) ?? 10; + + const clearFilters = useCallback(() => { + setUser(""); + setType(""); + setStatus(""); + setCorrelationID(""); + }, [setUser, setType, setStatus, setCorrelationID]); + + useEffect(() => { + fetchTasks(); + }, [page, pageSize, orderBy, orderDirection, user, type, status, correlationID]); + + const fetchTasks = () => { + setLoading(true); + setSelected([]); + + const params: AdminListService = { + page: pageInt, + page_size: pageSizeInt, + order_by: orderBy ?? "", + order_direction: orderDirection ?? "desc", + conditions: { + task_status: status, + task_user_id: user, + task_type: type, + task_correlation_id: correlationID, + }, + }; + + dispatch(getTaskList(params)) + .then((res) => { + setTasks(res.tasks); + setPageSize(res.pagination.page_size.toString()); + setCount(res.pagination.total_items ?? 0); + setLoading(false); + }) + .finally(() => { + setLoading(false); + }); + }; + + const handleDelete = () => { + setDeleteLoading(true); + dispatch(confirmOperation(t("task.confirmBatchDelete", { num: selected.length }))) + .then(() => { + dispatch(batchDeleteTasks({ ids: Array.from(selected) })) + .then(() => { + fetchTasks(); + }) + .finally(() => { + setDeleteLoading(false); + }); + setDeleteLoading(false); + }) + .finally(() => { + setDeleteLoading(false); + }); + }; + + const handleSelectAllClick = (event: React.ChangeEvent) => { + if (event.target.checked) { + const newSelected = tasks.map((n) => n.id); + setSelected(newSelected); + return; + } + setSelected([]); + }; + + const handleSelect = useCallback( + (id: number) => { + const selectedIndex = selected.indexOf(id); + let newSelected: readonly number[] = []; + + if (selectedIndex === -1) { + newSelected = newSelected.concat(selected, id); + } else if (selectedIndex === 0) { + newSelected = newSelected.concat(selected.slice(1)); + } else if (selectedIndex === selected.length - 1) { + newSelected = newSelected.concat(selected.slice(0, -1)); + } else if (selectedIndex > 0) { + newSelected = newSelected.concat(selected.slice(0, selectedIndex), selected.slice(selectedIndex + 1)); + } + setSelected(newSelected); + }, + [selected], + ); + + const orderById = orderBy === "id" || orderBy === ""; + const direction = orderDirection as "asc" | "desc"; + const onSortClick = (field: string) => () => { + const alreadySorted = orderBy === field || (field === "id" && orderById); + setOrderBy(field); + setOrderDirection(alreadySorted ? (direction === "asc" ? "desc" : "asc") : "asc"); + }; + + const hasActiveFilters = useMemo(() => { + return !!(status || user || type); + }, [status, user, type]); + + const handleUserDialogOpen = (id: number) => { + setUserDialogID(id); + setUserDialogOpen(true); + }; + + const handleOpenEntity = (entityID: number) => { + setOpenEntity(entityID); + setOpenEntityDialogOpen(true); + }; + + const handleOpenTask = (taskID: number) => { + setOpenTask(taskID); + setOpenTaskDialogOpen(true); + }; + + return ( + + setUserDialogOpen(false)} userID={userDialogID} /> + setOpenEntityDialogOpen(false)} entityID={openEntity} /> + setOpenTaskDialogOpen(false)} taskID={openTask} /> + setCleanupDialogOpen(false)} + onCleanupComplete={fetchTasks} + /> + + + + + + }> + {t("node.refresh")} + + + + } variant="contained" {...bindTrigger(filterPopupState)}> + {t("user.filter")} + + + + } variant="contained" onClick={() => setCleanupDialogOpen(true)}> + {t("event.cleanup")} + + + {selected.length > 0 && !isMobile && ( + <> + + + + )} + + {isMobile && selected.length > 0 && ( + + + + )} + + + + + + 0 && selected.length < tasks.length} + checked={tasks.length > 0 && selected.length === tasks.length} + onChange={handleSelectAllClick} + /> + + + + {t("group.#")} + + + {t("task.content")} + {t("task.status")} + {t("file.creator")} + {t("task.node")} + + + {t("file.createdAt")} + + + + + + + {!loading && + tasks.map((task) => ( + + ))} + {loading && + tasks.length > 0 && + tasks.slice(0, 10).map((task) => )} + {loading && + tasks.length === 0 && + Array.from(Array(10)).map((_, index) => )} + +
    +
    + {count > 0 && ( + + setPageSize(value.toString())} + onChange={(_, value) => setPage(value.toString())} + /> + + )} +
    +
    + ); +}; + +export default TaskList; diff --git a/src/component/Admin/Task/TaskRow.tsx b/src/component/Admin/Task/TaskRow.tsx new file mode 100755 index 0000000..e706365 --- /dev/null +++ b/src/component/Admin/Task/TaskRow.tsx @@ -0,0 +1,192 @@ +import { Box, Checkbox, IconButton, Link, Skeleton, TableCell, TableRow } from "@mui/material"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link as RouterLink, useNavigate } from "react-router-dom"; +import { batchDeleteTasks } from "../../../api/api"; +import { Task } from "../../../api/dashboard"; +import { useAppDispatch } from "../../../redux/hooks"; +import { confirmOperation } from "../../../redux/thunks/dialog"; +import { NoWrapTableCell, NoWrapTypography } from "../../Common/StyledComponents"; +import TimeBadge from "../../Common/TimeBadge"; +import UserAvatar from "../../Common/User/UserAvatar"; +import Delete from "../../Icons/Delete"; +import TaskSummaryStatus from "../../Pages/Tasks/TaskSummaryStatus"; +import { TaskContent } from "./TaskContent"; + +export interface TaskRowProps { + task?: Task; + loading?: boolean; + deleting?: boolean; + selected?: boolean; + onDelete?: () => void; + onDetails?: (id: number) => void; + onSelect?: (id: number) => void; + openUserDialog?: (id: number) => void; + openEntity?: (entityID: number) => void; + openTask?: (taskID: number) => void; +} + +const TaskRow = ({ + task, + loading, + deleting, + selected, + onDelete, + onDetails, + onSelect, + openUserDialog, + openEntity, +}: TaskRowProps) => { + const navigate = useNavigate(); + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [deleteLoading, setDeleteLoading] = useState(false); + const [openLoading, setOpenLoading] = useState(false); + const onRowClick = () => { + onDetails?.(task?.id ?? 0); + }; + + const onDeleteClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + dispatch(confirmOperation(t("task.confirmDelete"))).then(() => { + if (task?.id) { + setDeleteLoading(true); + dispatch(batchDeleteTasks({ ids: [task.id] })) + .then(() => { + onDelete?.(); + }) + .finally(() => { + setDeleteLoading(false); + }); + } + }); + }; + + const onSelectClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + onSelect?.(task?.id ?? 0); + }; + + if (loading) { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } + + const stopPropagation = (e: React.MouseEvent) => { + e.stopPropagation(); + }; + + const userClicked = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + openUserDialog?.(task?.edges?.user?.id ?? 0); + }; + + return ( + + + + + + {task?.id} + + {task && } + + + + + {task?.edges?.user && ( + + + + + {task?.edges?.user?.nick} + + + + )} + + + + {task?.node?.name ? ( + + {task?.node?.name} + + ) : ( + "-" + )} + + + + + + + + + + + + + + ); +}; + +export default TaskRow; diff --git a/src/component/Admin/Task/TaskStatusSelector.tsx b/src/component/Admin/Task/TaskStatusSelector.tsx new file mode 100755 index 0000000..742a31c --- /dev/null +++ b/src/component/Admin/Task/TaskStatusSelector.tsx @@ -0,0 +1,71 @@ +import { Box, FormHelperText, ListItemText, SelectChangeEvent } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { TaskStatus } from "../../../api/workflow"; +import { DenseSelect, SquareChip } from "../../Common/StyledComponents"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu"; +import { getTaskStatusText } from "../../Pages/Tasks/TaskProps"; + +interface TaskStatusSelectorProps { + value: TaskStatus[]; + onChange: (event: SelectChangeEvent) => void; + renderValue?: (selected: unknown) => React.ReactNode; + helperText?: string; + showAllOption?: boolean; + allOptionText?: string; + fullWidth?: boolean; + displayEmpty?: boolean; +} + +const TaskStatusSelector = ({ + value, + onChange, + renderValue, + helperText, + showAllOption = false, + allOptionText, + fullWidth = true, + displayEmpty = false, +}: TaskStatusSelectorProps) => { + const { t } = useTranslation("dashboard"); + + const defaultRenderValue = (selected: unknown) => { + const values = Array.isArray(selected) ? selected : []; + return ( + + {values.map((val) => ( + + ))} + + ); + }; + + return ( + <> + + {showAllOption && ( + + + + )} + {Object.values(TaskStatus).map((status) => ( + + + + ))} + + {helperText && {helperText}} + + ); +}; + +export default TaskStatusSelector; diff --git a/src/component/Admin/Task/TaskTypeSelector.tsx b/src/component/Admin/Task/TaskTypeSelector.tsx new file mode 100755 index 0000000..e84508a --- /dev/null +++ b/src/component/Admin/Task/TaskTypeSelector.tsx @@ -0,0 +1,70 @@ +import { Box, FormHelperText, ListItemText, SelectChangeEvent } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { TaskType } from "../../../api/workflow"; +import { DenseSelect, SquareChip } from "../../Common/StyledComponents"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu"; + +interface TaskTypeSelectorProps { + value: TaskType[]; + onChange: (event: SelectChangeEvent) => void; + renderValue?: (selected: unknown) => React.ReactNode; + helperText?: string; + showAllOption?: boolean; + allOptionText?: string; + fullWidth?: boolean; + displayEmpty?: boolean; +} + +const TaskTypeSelector = ({ + value, + onChange, + renderValue, + helperText, + showAllOption = false, + allOptionText, + fullWidth = true, + displayEmpty = false, +}: TaskTypeSelectorProps) => { + const { t } = useTranslation("dashboard"); + + const defaultRenderValue = (selected: unknown) => { + const values = Array.isArray(selected) ? selected : []; + return ( + + {values.map((val) => ( + + ))} + + ); + }; + + return ( + <> + + {showAllOption && ( + + + + )} + {Object.values(TaskType).map((type) => ( + + + + ))} + + {helperText && {helperText}} + + ); +}; + +export default TaskTypeSelector; diff --git a/src/component/Admin/User/NewUserDialog.tsx b/src/component/Admin/User/NewUserDialog.tsx new file mode 100755 index 0000000..4a8020c --- /dev/null +++ b/src/component/Admin/User/NewUserDialog.tsx @@ -0,0 +1,117 @@ +import { DialogContent, Stack } from "@mui/material"; +import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { upsertUser } from "../../../api/api"; +import { User, UserStatus } from "../../../api/dashboard"; +import { useAppDispatch } from "../../../redux/hooks"; +import { DenseFilledTextField } from "../../Common/StyledComponents"; +import DraggableDialog from "../../Dialogs/DraggableDialog"; +import SettingForm from "../../Pages/Setting/SettingForm"; +import GroupSelectionInput from "../Common/GroupSelectionInput"; + +export interface NewUserDialogProps { + open: boolean; + onClose: () => void; + onCreated: (user: User) => void; +} + +const defaultUser: User = { + edges: {}, + id: 0, + email: "", + nick: "", + password: "", + status: UserStatus.active, + group_users: 2, +}; + +const NewUserDialog = ({ open, onClose, onCreated }: NewUserDialogProps) => { + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + const [loading, setLoading] = useState(false); + const [user, setUser] = useState({ ...defaultUser }); + const formRef = useRef(null); + + useEffect(() => { + if (open) { + setUser({ ...defaultUser }); + } + }, [open]); + + const handleSubmit = () => { + if (!formRef.current?.checkValidity()) { + formRef.current?.reportValidity(); + return; + } + + let newUser = { ...user, nick: user.email.split("@")[0] }; + + setLoading(true); + dispatch(upsertUser({ user: newUser, password: user.password })) + .then((r) => { + onCreated(r); + onClose(); + }) + .finally(() => { + setLoading(false); + }); + }; + + return ( + + +
    + + + setUser({ ...user, email: e.target.value })} + /> + + + setUser({ ...user, password: e.target.value })} + /> + + + setUser({ ...user, group_users: parseInt(e) })} + fullWidth + /> + + +
    +
    +
    + ); +}; + +export default NewUserDialog; diff --git a/src/component/Admin/User/UserDialog/UserDialog.tsx b/src/component/Admin/User/UserDialog/UserDialog.tsx new file mode 100755 index 0000000..78cb248 --- /dev/null +++ b/src/component/Admin/User/UserDialog/UserDialog.tsx @@ -0,0 +1,174 @@ +import { Box, Button, Collapse, DialogActions, DialogContent } from "@mui/material"; +import * as React from "react"; +import { createContext, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { getUserDetail, upsertUser } from "../../../../api/api.ts"; +import { UpsertUserService, User } from "../../../../api/dashboard.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import AutoHeight from "../../../Common/AutoHeight.tsx"; +import FacebookCircularProgress from "../../../Common/CircularProgress.tsx"; +import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import UserForm from "./UserForm.tsx"; + +export interface UserDialogProps { + open: boolean; + onClose: () => void; + userID?: number; + onUpdated?: (user: User) => void; +} + +export interface UserDialogContextProps { + values: User; + setUser: (f: (p: User) => User) => void; + formRef?: React.RefObject; +} + +const defaultUser: User = { + id: 0, + nick: "", + email: "", + edges: {}, +}; + +export const UserDialogContext = createContext({ + values: { ...defaultUser }, + setUser: () => {}, +}); + +const UserDialog = ({ open, onClose, userID, onUpdated }: UserDialogProps) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation("dashboard"); + const [values, setValues] = useState({ + ...defaultUser, + }); + const [modifiedValues, setModifiedValues] = useState({ + ...defaultUser, + }); + const [loading, setLoading] = useState(true); + const [submitting, setSubmitting] = useState(false); + const formRef = useRef(null); + + const showSaveButton = useMemo(() => { + return JSON.stringify(modifiedValues) !== JSON.stringify(values); + }, [modifiedValues, values]); + + const loadUser = useCallback(() => { + if (!userID) { + return; + } + setLoading(true); + dispatch(getUserDetail(userID)) + .then((res) => { + setValues(res); + setModifiedValues(res); + }) + .catch(() => { + onClose(); + }) + .finally(() => { + setLoading(false); + }); + }, [userID]); + + useEffect(() => { + loadUser(); + }, [userID]); + + const revert = () => { + setModifiedValues(values); + }; + + const submit = () => { + if (formRef.current) { + if (!formRef.current.checkValidity()) { + formRef.current.reportValidity(); + return; + } + } + + const args: UpsertUserService = { + user: { ...modifiedValues }, + }; + + if (!args.user.two_fa_enabled) { + args.two_fa = "clear"; + } + + if (args.user.password) { + args.password = args.user.password; + } + + setSubmitting(true); + dispatch(upsertUser(args)) + .then((res) => { + setValues(res); + setModifiedValues(res); + onUpdated?.(res); + }) + .finally(() => { + setSubmitting(false); + }); + }; + + return ( + + + + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${loading}`} + > + + {loading && ( + + + + )} + {!loading && } + + + + + + + + + + + + + + ); +}; + +export default UserDialog; diff --git a/src/component/Admin/User/UserDialog/UserForm.tsx b/src/component/Admin/User/UserDialog/UserForm.tsx new file mode 100755 index 0000000..a5eff88 --- /dev/null +++ b/src/component/Admin/User/UserDialog/UserForm.tsx @@ -0,0 +1,251 @@ +import { OpenInNew } from "@mui/icons-material"; +import { + Box, + Collapse, + Divider, + FormControl, + Grid2 as Grid, + Link, + ListItemText, + SelectChangeEvent, + Stack, + Typography, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useCallback, useContext, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { sendCalibrateUserStorage } from "../../../../api/api"; +import { UserStatus } from "../../../../api/dashboard"; +import { useAppDispatch } from "../../../../redux/hooks"; +import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar"; +import { DenseFilledTextField, DenseSelect, SecondaryButton } from "../../../Common/StyledComponents"; +import UserAvatar from "../../../Common/User/UserAvatar"; +import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu"; +import Delete from "../../../Icons/Delete"; +import SettingForm, { ProChip } from "../../../Pages/Setting/SettingForm"; +import { CapacityBar } from "../../../Pages/Setting/StorageSetting"; +import GroupSelectionInput from "../../Common/GroupSelectionInput"; +import ProDialog from "../../Common/ProDialog"; +import { NoMarginHelperText } from "../../Settings/Settings"; +import { UserDialogContext } from "./UserDialog"; + +const UserForm = ({ reload, setLoading }: { reload: () => void; setLoading: (loading: boolean) => void }) => { + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + const { t } = useTranslation("dashboard"); + const { formRef, values, setUser } = useContext(UserDialogContext); + const [proOpen, setProOpen] = useState(false); + + const removeAvatar = useCallback(() => { + setUser((prev) => ({ ...prev, avatar: undefined })); + }, [setUser]); + + const onEmailChange = useCallback( + (e: React.ChangeEvent) => { + setUser((prev) => ({ ...prev, email: e.target.value })); + }, + [setUser], + ); + + const onNickChange = useCallback( + (e: React.ChangeEvent) => { + setUser((prev) => ({ ...prev, nick: e.target.value })); + }, + [setUser], + ); + + const onStatusChange = useCallback( + (e: SelectChangeEvent) => { + setUser((prev) => ({ ...prev, status: e.target.value as UserStatus })); + }, + [setUser], + ); + + const onGroupChange = useCallback( + (value: string) => { + setUser((prev) => ({ ...prev, group_users: parseInt(value) })); + }, + [setUser], + ); + + const onPasswordChange = useCallback( + (e: React.ChangeEvent) => { + setUser((prev) => ({ ...prev, password: e.target.value ? e.target.value : undefined })); + }, + [setUser], + ); + + const onReset2FA = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + setUser((prev) => ({ ...prev, two_fa_enabled: false })); + }, + [setUser], + ); + + const onCalibrateStorage = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + setLoading(true); + dispatch(sendCalibrateUserStorage(values.id)) + .then(() => { + reload(); + enqueueSnackbar({ + message: t("user.calibrateStorageSuccess"), + variant: "success", + action: DefaultCloseAction, + }); + }) + .finally(() => { + setLoading(false); + }); + }, + [dispatch, values.id], + ); + + return ( + e.preventDefault()}> + setProOpen(false)} /> + + + + + + }> + {t("user.removeAvatar")} + + + + + + {t("user.idValue", { id: values.id, hash_id: values.hash_id })} + + + + + + + + + {t("user.calibrateStorage")} + + + + + setProOpen(true)} + variant="contained" + startIcon={} + > + {t("user.openUserFiles")} + + + + + + + + + + + + + + + + {Object.values(UserStatus).map((value) => ( + + + {t(`user.status_${value}`)} + + + ))} + + + + + + + + + + + + + + {}} + emptyText={t("user.noOriginUserGroup")} + emptyValue={" "} + fullWidth + /> + {t("user.originUserGroupDes")} + + + + {t("user.groupExpiredDes")} + + + + + + + {values.two_fa_enabled ? t("user.2FAEnabled") : t("user.notEnabled")} + {values.two_fa_enabled && ( + + {t("user.reset2Fa")} + + )} + + + + + + + ); +}; + +export default UserForm; diff --git a/src/component/Admin/User/UserFilterPopover.tsx b/src/component/Admin/User/UserFilterPopover.tsx new file mode 100755 index 0000000..9eef54e --- /dev/null +++ b/src/component/Admin/User/UserFilterPopover.tsx @@ -0,0 +1,160 @@ +import { Box, Button, FormControl, ListItemText, Popover, PopoverProps, Stack } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { UserStatus } from "../../../api/dashboard"; +import { DenseFilledTextField, DenseSelect } from "../../Common/StyledComponents"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu"; +import SettingForm from "../../Pages/Setting/SettingForm"; +import GroupSelectionInput from "../Common/GroupSelectionInput"; + +export interface UserFilterPopoverProps extends PopoverProps { + email: string; + setEmail: (email: string) => void; + nick: string; + setNick: (nick: string) => void; + group: string; + setGroup: (group: string) => void; + status: string; + setStatus: (status: string) => void; + clearFilters: () => void; +} + +const UserFilterPopover = ({ + email, + setEmail, + nick, + setNick, + group, + setGroup, + status, + setStatus, + clearFilters, + onClose, + open, + ...rest +}: UserFilterPopoverProps) => { + const { t } = useTranslation("dashboard"); + + // Create local state to track changes before applying + const [localEmail, setLocalEmail] = useState(email); + const [localNick, setLocalNick] = useState(nick); + const [localGroup, setLocalGroup] = useState(group); + const [localStatus, setLocalStatus] = useState(status); + + // Initialize local state when popup opens + useEffect(() => { + if (open) { + setLocalEmail(email); + setLocalNick(nick); + setLocalGroup(group); + setLocalStatus(status); + } + }, [open]); + + // Apply filters and close popover + const handleApplyFilters = () => { + setEmail(localEmail); + setNick(localNick); + setGroup(localGroup == " " ? "" : localGroup); + setStatus(localStatus == " " ? "" : localStatus); + onClose?.({}, "backdropClick"); + }; + + // Reset filters and close popover + const handleResetFilters = () => { + setLocalEmail(""); + setLocalNick(""); + setLocalGroup(""); + setLocalStatus(""); + clearFilters(); + onClose?.({}, "backdropClick"); + }; + + return ( + + + + setLocalEmail(e.target.value)} + placeholder={t("user.emptyNoFilter")} + size="small" + /> + + + + setLocalNick(e.target.value)} + placeholder={t("user.emptyNoFilter")} + size="small" + /> + + + + + + + + + setLocalStatus(e.target.value as string)} + > + + + {t("user.all")} + + + {Object.values(UserStatus).map((value) => ( + + {t(`user.status_${value}`)} + + ))} + + + + + + + + + + + ); +}; + +export default UserFilterPopover; diff --git a/src/component/Admin/User/UserRow.tsx b/src/component/Admin/User/UserRow.tsx new file mode 100755 index 0000000..4d60e96 --- /dev/null +++ b/src/component/Admin/User/UserRow.tsx @@ -0,0 +1,176 @@ +import { Box, Checkbox, IconButton, Link, Skeleton, TableCell, TableRow, Tooltip } from "@mui/material"; +import { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link as RouterLink, useNavigate } from "react-router-dom"; +import { batchDeleteUser } from "../../../api/api"; +import { User, UserStatus } from "../../../api/dashboard"; +import { useAppDispatch } from "../../../redux/hooks"; +import { confirmOperation } from "../../../redux/thunks/dialog"; +import { sizeToString } from "../../../util"; +import { NoWrapTableCell, NoWrapTypography, SquareChip } from "../../Common/StyledComponents"; +import UserAvatar from "../../Common/User/UserAvatar"; +import Delete from "../../Icons/Delete"; +import PersonPasskey from "../../Icons/PersonPasskey"; + +export interface UserRowProps { + user?: User; + loading?: boolean; + deleting?: boolean; + selected?: boolean; + onDelete?: () => void; + onDetails?: (id: number) => void; + onSelect?: (id: number) => void; +} + +const UserRow = ({ user, loading, deleting, selected, onDelete, onDetails, onSelect }: UserRowProps) => { + const navigate = useNavigate(); + const { t } = useTranslation("dashboard"); + const dispatch = useAppDispatch(); + const [deleteLoading, setDeleteLoading] = useState(false); + + const onRowClick = () => { + onDetails?.(user?.id ?? 0); + }; + + const onDeleteClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + dispatch(confirmOperation(t("user.confirmDelete", { user: user?.email }))).then(() => { + if (user?.id) { + setDeleteLoading(true); + dispatch(batchDeleteUser({ ids: [user.id] })) + .then(() => { + onDelete?.(); + }) + .finally(() => { + setDeleteLoading(false); + }); + } + }); + }; + + const onSelectClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + onSelect?.(user?.id ?? 0); + }; + + const userProps = useMemo(() => { + const res = { + passkey: false, + twoFa: false, + }; + + if (user?.edges?.passkey) { + res.passkey = true; + } + + if (user?.two_fa_enabled) { + res.twoFa = true; + } + + return res; + }, [user]); + + if (loading) { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } + + const stopPropagation = (e: React.MouseEvent) => { + e.stopPropagation(); + }; + + return ( + + + + + + {user?.id} + + + + + {user?.nick} + + {user?.status == UserStatus.inactive && } + {user?.status == UserStatus.sys_banned && ( + + )} + {user?.status == UserStatus.manual_banned && ( + + )} + {user?.two_fa_enabled && ( + + + + + + )} + + + + + {user?.email} + + + + + {user?.edges?.group?.name} + + + + {sizeToString(user?.storage ?? 0)} + + + + + + + ); +}; + +export default UserRow; diff --git a/src/component/Admin/User/UserSetting.tsx b/src/component/Admin/User/UserSetting.tsx new file mode 100755 index 0000000..7f3a14d --- /dev/null +++ b/src/component/Admin/User/UserSetting.tsx @@ -0,0 +1,319 @@ +import { Delete } from "@mui/icons-material"; +import { + Badge, + Box, + Button, + Checkbox, + Container, + Divider, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TableSortLabel, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { bindPopover, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import { useQueryState } from "nuqs"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { batchDeleteUser, getUserList } from "../../../api/api"; +import { User } from "../../../api/dashboard"; +import { useAppDispatch } from "../../../redux/hooks"; +import { confirmOperation } from "../../../redux/thunks/dialog"; +import { NoWrapTableCell, SecondaryButton, StyledTableContainerPaper } from "../../Common/StyledComponents"; +import Add from "../../Icons/Add"; +import ArrowSync from "../../Icons/ArrowSync"; +import Filter from "../../Icons/Filter"; +import PageContainer from "../../Pages/PageContainer"; +import PageHeader from "../../Pages/PageHeader"; +import TablePagination from "../Common/TablePagination"; +import { OrderByQuery, OrderDirectionQuery, PageQuery, PageSizeQuery } from "../StoragePolicy/StoragePolicySetting"; +import NewUserDialog from "./NewUserDialog"; +import UserDialog from "./UserDialog/UserDialog"; +import UserFilterPopover from "./UserFilterPopover"; +import UserRow from "./UserRow"; +export const EmailQuery = "email"; +export const NickQuery = "nick"; +export const GroupQuery = "group"; +export const StatusQuery = "status"; + +const UserSetting = () => { + const { t } = useTranslation("dashboard"); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(true); + const [users, setUsers] = useState([]); + const [page, setPage] = useQueryState(PageQuery, { defaultValue: "1" }); + const [pageSize, setPageSize] = useQueryState(PageSizeQuery, { + defaultValue: "10", + }); + const [orderBy, setOrderBy] = useQueryState(OrderByQuery, { + defaultValue: "", + }); + const [orderDirection, setOrderDirection] = useQueryState(OrderDirectionQuery, { defaultValue: "desc" }); + const [email, setEmail] = useQueryState(EmailQuery, { defaultValue: "" }); + const [nick, setNick] = useQueryState(NickQuery, { defaultValue: "" }); + const [group, setGroup] = useQueryState(GroupQuery, { defaultValue: "" }); + const [status, setStatus] = useQueryState(StatusQuery, { defaultValue: "" }); + const [count, setCount] = useState(0); + const [selected, setSelected] = useState([]); + const [createNewOpen, setCreateNewOpen] = useState(false); + const filterPopupState = usePopupState({ + variant: "popover", + popupId: "userFilterPopover", + }); + + const [userDialogOpen, setUserDialogOpen] = useState(false); + const [userDialogID, setUserDialogID] = useState(undefined); + const [deleteLoading, setDeleteLoading] = useState(false); + + const pageInt = parseInt(page) ?? 1; + const pageSizeInt = parseInt(pageSize) ?? 11; + + const clearFilters = useCallback(() => { + setEmail(""); + setNick(""); + setGroup(""); + setStatus(""); + }, [setEmail, setNick, setGroup, setStatus]); + + useEffect(() => { + fetchUsers(); + }, [page, pageSize, orderBy, orderDirection, email, nick, group, status]); + + const fetchUsers = () => { + setLoading(true); + setSelected([]); + dispatch( + getUserList({ + page: pageInt, + page_size: pageSizeInt, + order_by: orderBy ?? "", + order_direction: orderDirection ?? "desc", + conditions: { + user_email: email, + user_nick: nick, + user_group: group, + user_status: status, + }, + }), + ) + .then((res) => { + setUsers(res.users); + setPageSize(res.pagination.page_size.toString()); + setCount(res.pagination.total_items ?? 0); + setLoading(false); + }) + .finally(() => { + setLoading(false); + }); + }; + + const handleDelete = () => { + setDeleteLoading(true); + dispatch(confirmOperation(t("user.confirmBatchDelete", { num: selected.length }))) + .then(() => { + dispatch(batchDeleteUser({ ids: Array.from(selected) })) + .then(() => { + fetchUsers(); + }) + .finally(() => { + setDeleteLoading(false); + }); + }) + .finally(() => { + setDeleteLoading(false); + }); + }; + + const handleSelectAllClick = (event: React.ChangeEvent) => { + if (event.target.checked) { + const newSelected = users.map((n) => n.id); + setSelected(newSelected); + return; + } + setSelected([]); + }; + + const handleSelect = useCallback( + (id: number) => { + const selectedIndex = selected.indexOf(id); + let newSelected: readonly number[] = []; + + if (selectedIndex === -1) { + newSelected = newSelected.concat(selected, id); + } else if (selectedIndex === 0) { + newSelected = newSelected.concat(selected.slice(1)); + } else if (selectedIndex === selected.length - 1) { + newSelected = newSelected.concat(selected.slice(0, -1)); + } else if (selectedIndex > 0) { + newSelected = newSelected.concat(selected.slice(0, selectedIndex), selected.slice(selectedIndex + 1)); + } + setSelected(newSelected); + }, + [selected], + ); + + const orderById = orderBy === "id" || orderBy === ""; + const direction = orderDirection as "asc" | "desc"; + const onSortClick = (field: string) => () => { + const alreadySorted = orderBy === field || (field === "id" && orderById); + setOrderBy(field); + setOrderDirection(alreadySorted ? (direction === "asc" ? "desc" : "asc") : "asc"); + }; + + const hasActiveFilters = useMemo(() => { + return !!(email || nick || group || status); + }, [email, nick, group, status]); + + const handleUserDialogOpen = (id: number) => { + setUserDialogID(id); + setUserDialogOpen(true); + }; + + return ( + + setCreateNewOpen(false)} + onCreated={(user) => { + setUserDialogID(user.id); + setUserDialogOpen(true); + }} + /> + setUserDialogOpen(false)} + userID={userDialogID} + onUpdated={() => fetchUsers()} + /> + + + + + + + + }> + {t("node.refresh")} + + + + } variant="contained" {...bindTrigger(filterPopupState)}> + {t("user.filter")} + + + + {selected.length > 0 && !isMobile && ( + <> + + + + )} + + {isMobile && selected.length > 0 && ( + + + + )} + + + + + + 0 && selected.length < users.length} + checked={users.length > 0 && selected.length === users.length} + onChange={handleSelectAllClick} + /> + + + + {t("group.#")} + + + + + {t("user.nick")} + + + + + {t("user.email")} + + + {t("user.group")} + + + {t("user.usedStorage")} + + + + + + + {!loading && + users.map((user) => ( + + ))} + {loading && + users.length > 0 && + users.slice(0, 10).map((user) => )} + {loading && + users.length === 0 && + Array.from(Array(5)).map((_, index) => )} + +
    +
    + {count > 0 && ( + + setPageSize(value.toString())} + onChange={(_, value) => setPage(value.toString())} + /> + + )} +
    +
    + ); +}; + +export default UserSetting; diff --git a/src/component/Common/AutoHeight.tsx b/src/component/Common/AutoHeight.tsx new file mode 100755 index 0000000..b155270 --- /dev/null +++ b/src/component/Common/AutoHeight.tsx @@ -0,0 +1,38 @@ +import React, { useEffect, useRef, useState } from "react"; +import AnimateHeight, { Height } from "react-animate-height"; +import { useTheme } from "@mui/material"; + +// @ts-ignore +const AutoHeight = ({ children, ...props }) => { + const [height, setHeight] = useState("auto"); + const contentDiv = useRef(null); + const theme = useTheme(); + + useEffect(() => { + const element = contentDiv.current as HTMLDivElement; + + const resizeObserver = new ResizeObserver(() => { + setHeight(element.clientHeight); + }); + + resizeObserver.observe(element); + + return () => resizeObserver.disconnect(); + }, [contentDiv]); + + return ( + + {children} + + ); +}; + +export default AutoHeight; diff --git a/src/component/Common/BorderLinearProgress.tsx b/src/component/Common/BorderLinearProgress.tsx new file mode 100755 index 0000000..b6cf226 --- /dev/null +++ b/src/component/Common/BorderLinearProgress.tsx @@ -0,0 +1,16 @@ +import { LinearProgress, linearProgressClasses } from "@mui/material"; +import { styled } from "@mui/material/styles"; + +const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({ + height: 8, + borderRadius: 5, + [`&.${linearProgressClasses.colorPrimary}`]: { + backgroundColor: theme.palette.grey[theme.palette.mode === "light" ? 200 : 800], + }, + [`& .${linearProgressClasses.bar}`]: { + borderRadius: 5, + backgroundColor: theme.palette.mode === "light" ? "#1a90ff" : "#308fe8", + }, +})); + +export default BorderLinearProgress; diff --git a/src/component/Common/Captcha/CapCaptcha.tsx b/src/component/Common/Captcha/CapCaptcha.tsx new file mode 100755 index 0000000..6320b7a --- /dev/null +++ b/src/component/Common/Captcha/CapCaptcha.tsx @@ -0,0 +1,243 @@ +import React, { useEffect, useRef } from "react"; +import { useTranslation } from "react-i18next"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import { CaptchaParams } from "./Captcha.tsx"; +import { Box, useTheme } from "@mui/material"; + +// Cap Widget URLs +const CAP_WASM_UNPKG_URL = "https://unpkg.com/@cap.js/wasm@0.0.4/browser/cap_wasm.js"; +const CAP_WASM_JSDELIVR_URL = "https://cdn.jsdelivr.net/npm/@cap.js/wasm@0.0.4/browser/cap_wasm.min.js"; +const CAP_WIDGET_UNPKG_URL = "https://unpkg.com/@cap.js/widget"; +const CAP_WIDGET_JSDELIVR_URL = "https://cdn.jsdelivr.net/npm/@cap.js/widget"; + +export interface CapProps { + onStateChange: (state: CaptchaParams) => void; + generation: number; +} + +// Standard input height +const STANDARD_INPUT_HEIGHT = "56px"; + +const CapCaptcha = ({ onStateChange, generation, fullWidth, ...rest }: CapProps & { fullWidth?: boolean }) => { + const captchaRef = useRef(null); + const widgetRef = useRef(null); + const onStateChangeRef = useRef(onStateChange); + const scriptLoadedRef = useRef(false); + const theme = useTheme(); + const { t } = useTranslation("common"); + + const capInstanceURL = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_instance_url); + const capSiteKey = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_site_key); + const capAssetServer = useAppSelector((state) => state.siteConfig.basic.config.captcha_cap_asset_server); + + // Keep callback reference up to date + useEffect(() => { + onStateChangeRef.current = onStateChange; + }, [onStateChange]); + + // Apply responsive styles for fullWidth mode + const applyFullWidthStyles = (widget: HTMLElement) => { + const applyStyles = () => { + // Style widget container + widget.style.width = "100%"; + widget.style.display = "block"; + widget.style.boxSizing = "border-box"; + + // Style internal captcha element + const captchaElement = widget.shadowRoot?.querySelector(".captcha") || widget.querySelector(".captcha"); + if (captchaElement) { + const captchaEl = captchaElement as HTMLElement; + captchaEl.style.width = "100%"; + captchaEl.style.maxWidth = "none"; + captchaEl.style.minWidth = "0"; + captchaEl.style.boxSizing = "border-box"; + return true; + } + return false; + }; + + // Apply immediately or wait for DOM changes + if (!applyStyles()) { + const observer = new MutationObserver(() => { + if (applyStyles()) { + observer.disconnect(); + } + }); + + observer.observe(widget, { + childList: true, + subtree: true, + attributes: true, + }); + + // Fallback timeout + setTimeout(() => { + applyStyles(); + observer.disconnect(); + }, 500); + } + }; + + const createWidget = () => { + if (!captchaRef.current || !capInstanceURL || !capSiteKey) { + return; + } + + // Clean up existing widget + if (widgetRef.current) { + widgetRef.current.remove?.(); + widgetRef.current = null; + } + + // Clear container + captchaRef.current.innerHTML = ""; + + if (typeof window !== "undefined" && (window as any).Cap) { + const widget = document.createElement("cap-widget"); + + // Cap 2.0 API format: {instanceURL}/{siteKey}/ + const apiEndpoint = `${capInstanceURL.replace(/\/$/, "")}/${capSiteKey}/`; + widget.setAttribute("data-cap-api-endpoint", apiEndpoint); + widget.id = "cap-widget"; + + // Set internationalization attributes (Cap official i18n format) + widget.setAttribute("data-cap-i18n-initial-state", t("captcha.cap.human")); + widget.setAttribute("data-cap-i18n-verifying-label", t("captcha.cap.verifying")); + widget.setAttribute("data-cap-i18n-solved-label", t("captcha.cap.verified")); + + captchaRef.current.appendChild(widget); + + widget.addEventListener("solve", (e: any) => { + const token = e.detail.token; + if (token) { + onStateChangeRef.current({ ticket: token }); + } + }); + + // Apply fullWidth styles if needed + if (fullWidth) { + applyFullWidthStyles(widget); + } + + widgetRef.current = widget; + } + }; + + useEffect(() => { + if (generation > 0) { + createWidget(); + } + }, [generation, t]); + + useEffect(() => { + if (!capInstanceURL || !capSiteKey) { + return; + } + + // 在加载 widget 脚本之å‰è®¾ç½® WASM URL + if (capAssetServer === "instance") { + (window as any).CAP_CUSTOM_WASM_URL = `${capInstanceURL.replace(/\/$/, "")}/assets/cap_wasm.js`; + } else if (capAssetServer === "unpkg") { + (window as any).CAP_CUSTOM_WASM_URL = CAP_WASM_UNPKG_URL; + } else { + // jsdelivr - 默认CDN + (window as any).CAP_CUSTOM_WASM_URL = CAP_WASM_JSDELIVR_URL; + } + + const scriptId = "cap-widget-script"; + let script = document.getElementById(scriptId) as HTMLScriptElement; + + const initWidget = () => { + scriptLoadedRef.current = true; + + // Add a small delay to ensure DOM is ready + setTimeout(() => { + createWidget(); + }, 100); + }; + + if (!script) { + script = document.createElement("script"); + script.id = scriptId; + + // æ ¹æ®é…ç½®é€‰æ‹©é™æ€èµ„æºæº + let assetSource; + if (capAssetServer === "instance") { + assetSource = `${capInstanceURL.replace(/\/$/, "")}/assets/widget.js`; + } else if (capAssetServer === "unpkg") { + assetSource = CAP_WIDGET_UNPKG_URL; + } else { + // jsdelivr - 默认CDN + assetSource = CAP_WIDGET_JSDELIVR_URL; + } + + script.src = assetSource; + script.async = true; + script.onload = initWidget; + script.onerror = () => { + if (capAssetServer === "instance") { + console.error("Failed to load Cap widget script from instance server"); + } else if (capAssetServer === "unpkg") { + console.error("Failed to load Cap widget script from unpkg CDN"); + } else { + console.error("Failed to load Cap widget script from jsDelivr CDN"); + } + }; + document.head.appendChild(script); + } else if (scriptLoadedRef.current || (window as any).Cap) { + // Script already loaded + initWidget(); + } else { + // Script exists but not loaded yet + script.onload = initWidget; + } + + return () => { + // Cleanup widget (keep script for reuse) + if (widgetRef.current) { + widgetRef.current.remove?.(); + widgetRef.current = null; + } + if (captchaRef.current) { + captchaRef.current.innerHTML = ""; + } + }; + }, [capInstanceURL, capSiteKey, capAssetServer, t]); + + if (!capInstanceURL || !capSiteKey) { + return null; + } + + return ( + +
    + + ); +}; + +export default CapCaptcha; diff --git a/src/component/Common/Captcha/Captcha.tsx b/src/component/Common/Captcha/Captcha.tsx new file mode 100755 index 0000000..4932565 --- /dev/null +++ b/src/component/Common/Captcha/Captcha.tsx @@ -0,0 +1,37 @@ +import { CaptchaType } from "../../../api/site.ts"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import CapCaptcha from "./CapCaptcha.tsx"; +import DefaultCaptcha from "./DefaultCaptcha.tsx"; +import ReCaptchaV2 from "./ReCaptchaV2.tsx"; +import TurnstileCaptcha from "./TurnstileCaptcha.tsx"; + +export interface CaptchaProps { + onStateChange: (state: CaptchaParams) => void; + generation: number; + noLabel?: boolean; + [x: string]: any; +} + +export interface CaptchaParams { + [x: string]: any; +} + +export const Captcha = (props: CaptchaProps) => { + const captchaType = useAppSelector((state) => state.siteConfig.basic.config.captcha_type); + + // const recaptcha = useRecaptcha(setCaptchaLoading); + // const tcaptcha = useTCaptcha(setCaptchaLoading); + + switch (captchaType) { + case CaptchaType.RECAPTCHA: + return ; + case CaptchaType.TURNSTILE: + return ; + case CaptchaType.CAP: + return ; + // case "tcaptcha": + // return { ...tcaptcha, captchaRefreshRef, captchaLoading }; + default: + return ; + } +}; diff --git a/src/component/Common/Captcha/DefaultCaptcha.tsx b/src/component/Common/Captcha/DefaultCaptcha.tsx new file mode 100755 index 0000000..5354075 --- /dev/null +++ b/src/component/Common/Captcha/DefaultCaptcha.tsx @@ -0,0 +1,98 @@ +import { Box, InputAdornment, Skeleton, TextField } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getCaptcha } from "../../../api/api.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { CaptchaParams } from "./Captcha.tsx"; + +export interface DefaultCaptchaProps { + onStateChange: (state: CaptchaParams) => void; + generation: number; + noLabel?: boolean; +} + +const DefaultCaptcha = ({ onStateChange, generation, noLabel, ...rest }: DefaultCaptchaProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const [captcha, setCaptcha] = useState(""); + const [sessionId, setSessionID] = useState(""); + const [captchaData, setCaptchaData] = useState(); + + const refreshCaptcha = async () => { + setCaptchaData(undefined); + const captchaResponse = await dispatch(getCaptcha()); + setCaptchaData(captchaResponse.image); + setSessionID(captchaResponse.ticket); + }; + + useEffect(() => { + refreshCaptcha(); + }, [generation]); + + useEffect(() => { + onStateChange({ captcha, ticket: sessionId }); + }, [captcha, sessionId]); + + return ( + setCaptcha(e.target.value)} + value={captcha} + autoComplete={"true"} + {...rest} + slotProps={{ + input: { + endAdornment: ( + + + {!captchaData && ( + `${theme.shape.borderRadius}px`, + }} + variant="rounded" + width={192} + height={48} + /> + )} + {captchaData && ( + `${theme.shape.borderRadius}px`, + height: 48, + }} + src={captchaData} + alt="captcha" + onClick={refreshCaptcha} + /> + )} + + + ), + }, + + htmlInput: { + name: "captcha", + id: "captcha", + }, + }} + /> + ); +}; + +export default DefaultCaptcha; diff --git a/src/component/Common/Captcha/ReCaptchaV2.tsx b/src/component/Common/Captcha/ReCaptchaV2.tsx new file mode 100755 index 0000000..6c8e875 --- /dev/null +++ b/src/component/Common/Captcha/ReCaptchaV2.tsx @@ -0,0 +1,60 @@ +import { useEffect, useRef } from "react"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import { CaptchaParams } from "./Captcha.tsx"; +import ReCAPTCHA from "react-google-recaptcha"; +import { Box, useTheme } from "@mui/material"; + +export interface ReCaptchaV2Props { + onStateChange: (state: CaptchaParams) => void; + generation: number; +} + +declare global { + interface Window { + subTitle: string; + recaptchaOptions: { + useRecaptchaNet: boolean; + }; + } +} + +window.recaptchaOptions = { + useRecaptchaNet: true, +}; + +const ReCaptchaV2 = ({ onStateChange, generation, ...rest }: ReCaptchaV2Props) => { + const theme = useTheme(); + + const captchaRef = useRef(); + const reCaptchaKey = useAppSelector((state) => state.siteConfig.basic.config.captcha_ReCaptchaKey); + + const refreshCaptcha = async () => { + captchaRef.current?.reset(); + }; + + useEffect(() => { + refreshCaptcha(); + }, [generation]); + + const onCompleted = () => { + const recaptchaValue = captchaRef.current?.getValue(); + if (recaptchaValue) { + onStateChange({ captcha: recaptchaValue }); + } + }; + + return ( + + + + ); +}; + +export default ReCaptchaV2; diff --git a/src/component/Common/Captcha/TurnstileCaptcha.tsx b/src/component/Common/Captcha/TurnstileCaptcha.tsx new file mode 100755 index 0000000..3fbf848 --- /dev/null +++ b/src/component/Common/Captcha/TurnstileCaptcha.tsx @@ -0,0 +1,50 @@ +import { Turnstile } from "@marsidev/react-turnstile"; +import { Box, useTheme } from "@mui/material"; +import i18next from "i18next"; +import { useEffect, useRef } from "react"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import { CaptchaParams } from "./Captcha.tsx"; + +export interface TurnstileProps { + onStateChange: (state: CaptchaParams) => void; + generation: number; +} + +const TurnstileCaptcha = ({ onStateChange, generation, ...rest }: TurnstileProps) => { + const theme = useTheme(); + + const captchaRef = useRef(); + const turnstileKey = useAppSelector((state) => state.siteConfig.basic.config.turnstile_site_id); + + const refreshCaptcha = async () => { + captchaRef.current?.reset(); + }; + + useEffect(() => { + refreshCaptcha(); + }, [generation]); + + const onCompleted = (t: string) => { + onStateChange({ ticket: t }); + }; + + return ( + + {turnstileKey && ( + + )} + + ); +}; + +export default TurnstileCaptcha; diff --git a/src/component/Common/CircularProgress.tsx b/src/component/Common/CircularProgress.tsx new file mode 100755 index 0000000..670ad1e --- /dev/null +++ b/src/component/Common/CircularProgress.tsx @@ -0,0 +1,39 @@ +import { Box, CircularProgress, circularProgressClasses, CircularProgressProps } from "@mui/material"; +import { forwardRef } from "react"; + +export interface FacebookCircularProgressProps extends CircularProgressProps { + bgColor?: string; + fgColor?: string; +} + +const FacebookCircularProgress = forwardRef(({ sx, bgColor, fgColor, ...rest }: FacebookCircularProgressProps, ref) => { + return ( + + bgColor ?? theme.palette.grey[theme.palette.mode === "light" ? 200 : 800], + }} + size={40} + thickness={4} + {...rest} + value={100} + /> + fgColor ?? theme.palette.primary.main, + position: "absolute", + left: 0, + [`& .${circularProgressClasses.circle}`]: { + strokeLinecap: "round", + }, + }} + size={40} + thickness={4} + {...rest} + /> + + ); +}); + +export default FacebookCircularProgress; diff --git a/src/component/Common/Code.tsx b/src/component/Common/Code.tsx new file mode 100755 index 0000000..e5d04fa --- /dev/null +++ b/src/component/Common/Code.tsx @@ -0,0 +1,18 @@ +import { Box, styled } from "@mui/material"; +import { grey } from "@mui/material/colors"; + +const StyledCode = styled(Box)(({ theme }) => ({ + backgroundColor: grey[100], + ...theme.applyStyles("dark", { + backgroundColor: grey[900], + }), + border: `1px solid ${theme.palette.divider}`, + borderRadius: "4px", + padding: "1px", + paddingLeft: "4px", + paddingRight: "4px", +})); + +export const Code = ({ children }: { children?: React.ReactNode }) => { + return {children}; +}; diff --git a/src/component/Common/ErrorBoundary.tsx b/src/component/Common/ErrorBoundary.tsx new file mode 100755 index 0000000..ff8a5dd --- /dev/null +++ b/src/component/Common/ErrorBoundary.tsx @@ -0,0 +1,30 @@ +import { useRouteError } from "react-router-dom"; +import { useTranslation } from "react-i18next"; + +function ErrorBoundary() { + let error = useRouteError(); + const { t } = useTranslation(); + console.log(error); + // Uncaught ReferenceError: path is not defined + return ( +
    +

    :(

    +

    {t("common:renderError")}

    + {!!error && ( +
    + {t("common:errorDetails")} +
    +            {error.toString()}
    +          
    + {error.stack && ( +
    +              {error.stack}
    +            
    + )} +
    + )} +
    + ); +} + +export default ErrorBoundary; diff --git a/src/component/Common/FadeTransition.css b/src/component/Common/FadeTransition.css new file mode 100755 index 0000000..2d973af --- /dev/null +++ b/src/component/Common/FadeTransition.css @@ -0,0 +1,17 @@ +.fade-enter { + opacity: 0; +} +.fade-enter-active { + opacity: 1; +} +.fade-exit { + opacity: 1; +} +.fade-exit-active { + opacity: 0; +} +.fade-enter-active, +.fade-exit-active { + transition: opacity 150ms; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} diff --git a/src/component/Common/Form/EncodingSelector.tsx b/src/component/Common/Form/EncodingSelector.tsx new file mode 100755 index 0000000..08bccfd --- /dev/null +++ b/src/component/Common/Form/EncodingSelector.tsx @@ -0,0 +1,127 @@ +import { + FormControl, + InputAdornment, + InputLabel, + MenuItem, + Select, + SelectProps, + styled, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { NoLabelFilledSelect } from "../../FileManager/Sidebar/CustomProps/MultiSelectPropsContent.tsx"; +import Translate from "../../Icons/Translate.tsx"; + +const encodings = [ + "ibm866", + "iso8859_2", + "iso8859_3", + "iso8859_4", + "iso8859_5", + "iso8859_6", + "iso8859_7", + "iso8859_8", + "iso8859_8I", + "iso8859_10", + "iso8859_13", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "koi8r", + "koi8u", + "macintosh", + "windows874", + "windows1250", + "windows1251", + "windows1252", + "windows1253", + "windows1254", + "windows1255", + "windows1256", + "windows1257", + "windows1258", + "macintoshcyrillic", + "gbk", + "gb18030", + "big5", + "eucjp", + "iso2022jp", + "shiftjis", + "euckr", + "utf16be", + "utf16le", +]; + +const defaultEncodingValue = " "; + +export interface EncodingSelectorProps { + value: string; + onChange: (value: string) => void; + label?: string; + size?: "small" | "medium"; + variant?: "outlined" | "standard" | "filled"; + fullWidth?: boolean; + showIcon?: boolean; + SelectProps?: Partial; +} + +export const StyledInputAdornment = styled(InputAdornment)(({ theme }) => ({ + "&.MuiInputAdornment-positionStart": { + marginTop: "0!important", + }, +})); + +const EncodingSelector = ({ + value, + onChange, + label, + size = "medium", + variant = "outlined", + fullWidth = false, + showIcon = true, + SelectProps, +}: EncodingSelectorProps) => { + const { t } = useTranslation(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + + const displayLabel = label || t("modals.selectEncoding"); + + const SelectComponent = size == "small" ? NoLabelFilledSelect : Select; + const InputAdornmentComponent = size == "small" ? StyledInputAdornment : InputAdornment; + + return ( + + {size != "small" && {displayLabel}} + + + + ) + } + label={displayLabel} + value={value} + onChange={(e) => onChange(e.target.value as string)} + {...SelectProps} + > + + {t("modals.defaultEncoding")} + + {encodings.map((enc) => ( + + {enc} + + ))} + + + ); +}; + +export { defaultEncodingValue }; +export default EncodingSelector; diff --git a/src/component/Common/Form/FileDisplayForm.tsx b/src/component/Common/Form/FileDisplayForm.tsx new file mode 100755 index 0000000..a2e8f22 --- /dev/null +++ b/src/component/Common/Form/FileDisplayForm.tsx @@ -0,0 +1,22 @@ +import FileBadge from "../../FileManager/FileBadge.tsx"; +import { FileResponse } from "../../../api/explorer.ts"; +import { StyledTextField } from "./PathSelectorForm.tsx"; + +export interface FileDisplayFormProps { + file: FileResponse; + label: string; +} + +export const FileDisplayForm = ({ file, label }: FileDisplayFormProps) => { + return ( + , + }} + label={label} + fullWidth + /> + ); +}; diff --git a/src/component/Common/Form/OutlineIconTextField.tsx b/src/component/Common/Form/OutlineIconTextField.tsx new file mode 100755 index 0000000..04ebb7c --- /dev/null +++ b/src/component/Common/Form/OutlineIconTextField.tsx @@ -0,0 +1,21 @@ +import { InputAdornment, TextField, TextFieldProps, useMediaQuery, useTheme } from "@mui/material"; + +export interface OutlineIconTextFieldProps extends TextFieldProps<"outlined"> { + icon: React.ReactNode; +} + +export const OutlineIconTextField = ({ icon, ...rest }: OutlineIconTextFieldProps) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + return ( + {icon}, + ...rest.InputProps, + }, + }} + /> + ); +}; diff --git a/src/component/Common/Form/PathSelectorForm.tsx b/src/component/Common/Form/PathSelectorForm.tsx new file mode 100755 index 0000000..546b36c --- /dev/null +++ b/src/component/Common/Form/PathSelectorForm.tsx @@ -0,0 +1,48 @@ +import { styled, TextField, TextFieldProps } from "@mui/material"; +import { useCallback } from "react"; +import { FileType } from "../../../api/explorer.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { selectPath } from "../../../redux/thunks/dialog.ts"; +import FileBadge from "../../FileManager/FileBadge.tsx"; + +export interface PathSelectorFormProps { + path: string; + label?: string; + onChange: (path: string) => void; + variant?: string; + textFieldProps?: TextFieldProps; + allowedFs?: string[]; +} + +export const StyledTextField = styled(TextField)({ + "& .MuiInputBase-root": { + paddingLeft: "8px", + cursor: "pointer", + }, + "& .MuiOutlinedInput-input": { + paddingLeft: "8px", + cursor: "pointer", + }, +}); + +export const PathSelectorForm = ({ path, onChange, label, variant, textFieldProps }: PathSelectorFormProps) => { + const dispatch = useAppDispatch(); + const onClick = useCallback(() => { + dispatch(selectPath(variant ?? "saveTo", path)).then((path) => { + onChange(path); + }); + }, [dispatch]); + return ( + , + }} + label={label} + fullWidth + {...textFieldProps} + /> + ); +}; diff --git a/src/component/Common/LanguageSwitcher.tsx b/src/component/Common/LanguageSwitcher.tsx new file mode 100755 index 0000000..cab07e9 --- /dev/null +++ b/src/component/Common/LanguageSwitcher.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import i18next from "i18next"; +import { languages } from "../../i18n"; +import { useTranslation } from "react-i18next"; +import { IconButton, Menu, MenuItem, Tooltip } from "@mui/material"; +import Translate from "../Icons/Translate.tsx"; + +const LanguageSwitcher: React.FC = () => { + const { t } = useTranslation(); + const [anchorEl, setAnchorEl] = React.useState(null); + const open = Boolean(anchorEl); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + return ( + <> + + + + + + + {languages.map((lang) => ( + { + i18next.changeLanguage(lang.code); + handleClose(); + }} + sx={{ fontSize: 14 }} + > + {lang.displayName} + + ))} + + + ); +}; + +export default LanguageSwitcher; diff --git a/src/component/Common/Logo.tsx b/src/component/Common/Logo.tsx new file mode 100755 index 0000000..e3dbce7 --- /dev/null +++ b/src/component/Common/Logo.tsx @@ -0,0 +1,48 @@ +import { Box, Skeleton, useTheme } from "@mui/material"; +import React, { useEffect, useRef } from "react"; +import { useAppSelector } from "../../redux/hooks.ts"; + +const Logo = (props: any) => { + const theme = useTheme(); + const imageRef = useRef(); + const [loaded, setLoaded] = React.useState(false); + const { mode } = theme.palette; + const logo = useAppSelector((state) => state.siteConfig.basic.config.logo); + const logo_light = useAppSelector((state) => state.siteConfig.basic.config.logo_light); + useEffect(() => { + setLoaded(logo == logo_light); + }, [mode]); + + useEffect(() => { + if (imageRef.current?.complete) { + setLoaded(true); + } + }, []); + + return ( + <> + {(!logo || !loaded) && } + {logo && ( + setLoaded(true)} + src={mode === "light" ? logo : logo_light} + alt="Logo" + {...props} + sx={{ + display: loaded ? "block" : "none", + // disable drag + userSelect: "none", + WebkitUserDrag: "none", + MozUserDrag: "none", + msUserDrag: "none", + ...props.sx, + }} + /> + )} + + ); +}; + +export default Logo; diff --git a/src/component/Common/Nothing.tsx b/src/component/Common/Nothing.tsx new file mode 100755 index 0000000..10c7099 --- /dev/null +++ b/src/component/Common/Nothing.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import { Box, Typography } from "@mui/material"; +import PackageOpen from "../Icons/PackageOpen.tsx"; + +export interface NothingProps { + primary: string; + secondary?: string; + top?: number; + size?: number; +} + +export default function Nothing({ primary, secondary, top = 20, size = 1 }: NothingProps) { + return ( + theme.palette.action.disabled, + textAlign: "center", + }} + > + + theme.palette.action.disabled, + }} + > + {primary} + + {secondary && ( + theme.palette.action.disabled }}> + {secondary} + + )} + + ); +} diff --git a/src/component/Common/ResponsiveTabs.tsx b/src/component/Common/ResponsiveTabs.tsx new file mode 100755 index 0000000..27d9435 --- /dev/null +++ b/src/component/Common/ResponsiveTabs.tsx @@ -0,0 +1,104 @@ +import * as React from "react"; +import { useLayoutEffect, useRef, useState } from "react"; +import { Box, ListItemIcon, ListItemText, Menu, MenuItem, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { StyledTab, StyledTabs } from "./StyledComponents.tsx"; +import CaretDown from "../Icons/CaretDown.tsx"; +import { useTranslation } from "react-i18next"; +import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; + +export interface Tab { + label: React.ReactNode; + value: T; + icon?: React.ReactElement; +} + +export interface ResponsiveTabsProps { + tabs: Tab[]; + value: T; + onChange: (event: React.SyntheticEvent, value: T) => void; +} + +const ResponsiveTabs = ({ tabs, value, onChange }: ResponsiveTabsProps) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const [hideTabs, setHideTabs] = useState(false); + const tabsRef = useRef(null); + const { t } = useTranslation(); + const moreOptionState = usePopupState({ + variant: "popover", + popupId: "tabMore", + }); + const { onClose, ...menuProps } = bindMenu(moreOptionState); + useLayoutEffect(() => { + const checkOverflow = () => { + if (tabsRef.current?.children[0]?.children[0]) { + setHideTabs((e) => + e + ? true + : (tabsRef.current?.children[0]?.children[0]?.scrollWidth ?? 0) > + (tabsRef.current?.children[0]?.children[0]?.clientWidth ?? 0), + ); + } + }; + + checkOverflow(); + window.addEventListener("resize", checkOverflow); + return () => window.removeEventListener("resize", checkOverflow); + }, []); + + return ( + + + {tabs + .filter((tab) => (isMobile || hideTabs ? tab.value == value : true)) + .map((tab) => ( + + ))} + {(isMobile || hideTabs) && tabs.length > 1 && ( + <> + + {t("application:navbar.showMore")} + + + } + {...bindTrigger(moreOptionState)} + /> + + {tabs + .filter((tab) => tab.value != value) + .map((option, index) => ( + { + onClose(); + onChange(e, option.value); + }} + > + {option.icon && {option.icon}} + {option.label} + + ))} + + + )} + + + ); +}; + +export default ResponsiveTabs; diff --git a/src/component/Common/SizeInput.tsx b/src/component/Common/SizeInput.tsx new file mode 100755 index 0000000..158e197 --- /dev/null +++ b/src/component/Common/SizeInput.tsx @@ -0,0 +1,186 @@ +import { + FilledInput, + FilledInputProps, + FormControl, + FormHelperText, + InputAdornment, + InputLabel, + MenuItem, + Select, + styled, +} from "@mui/material"; +import { parseInt } from "lodash"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch } from "../../redux/hooks.ts"; +import { DenseFilledTextField } from "./StyledComponents.tsx"; + +const unitTransform = (v?: number): number[] => { + if (!v || v.toString() === "0") { + return [0, 1024 * 1024]; + } + for (let i = 4; i >= 0; i--) { + const base = Math.pow(1024, i); + if (v % base === 0) { + return [v / base, base]; + } + } + + return [0, 1024 * 1024]; +}; + +export interface SizeInputProps { + onChange: (size: number) => void; + min?: number; + value: number; + required?: boolean; + label?: string; + max?: number; + suffix?: string; + inputProps?: FilledInputProps; + variant?: "filled" | "outlined"; + allowZero?: boolean; +} + +export const StyledSelect = styled(Select)(() => ({ + "& .MuiFilledInput-input": { + paddingTop: "5px", + "&:focus": { + backgroundColor: "initial", + }, + }, + minWidth: "70px", + marginTop: "14px", + backgroundColor: "initial", +})); + +export const StyleOutlinedSelect = styled(Select)(({ theme }) => ({ + "& .MuiFilledInput-input": { + paddingTop: "5px", + "&:focus": { + backgroundColor: "initial", + }, + }, + minWidth: "70px", + backgroundColor: "initial", + fontSize: theme.typography.body2.fontSize, +})); + +export default function SizeInput({ + onChange, + min, + value, + required, + label, + max, + inputProps, + allowZero = true, + suffix, + variant = "filled", +}: SizeInputProps) { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + + const [unit, setUnit] = useState(1); + const [val, setVal] = useState(value); + const [err, setError] = useState(""); + + useEffect(() => { + onChange(val * unit); + if ((max && val * unit > max) || (min && val * unit < min)) { + setError(t("common:incorrectSizeInput")); + } else { + setError(""); + } + }, [val, unit, max, min]); + + useEffect(() => { + const res = unitTransform(value); + setUnit(res[1]); + setVal(res[0]); + }, [value]); + + if (variant === "outlined") { + return ( + ) => setVal(parseInt(e.target.value) ?? 0)} + error={err !== ""} + helperText={err} + required={required} + InputProps={{ + endAdornment: ( + + setUnit(e.target.value as number)} + > + + B{suffix && suffix} + + + KB{suffix && suffix} + + + MB{suffix && suffix} + + + GB{suffix && suffix} + + + TB{suffix && suffix} + + + + ), + ...inputProps, + }} + /> + ); + } + + return ( + + {label} + ) => setVal(parseInt(e.target.value) ?? 0)} + required={required} + endAdornment={ + + setUnit(e.target.value as number)} + > + + B{suffix && suffix} + + + KB{suffix && suffix} + + + MB{suffix && suffix} + + + GB{suffix && suffix} + + + TB{suffix && suffix} + + + + } + {...inputProps} + /> + {err !== "" && {err}} + + ); +} diff --git a/src/component/Common/Snackbar/FileIconSnackbar.tsx b/src/component/Common/Snackbar/FileIconSnackbar.tsx new file mode 100755 index 0000000..c3ffe9c --- /dev/null +++ b/src/component/Common/Snackbar/FileIconSnackbar.tsx @@ -0,0 +1,82 @@ +import { Box } from "@mui/material"; +import MuiSnackbarContent from "@mui/material/SnackbarContent"; +import { CustomContentProps } from "notistack"; +import * as React from "react"; +import { forwardRef, useState } from "react"; +import { FileResponse } from "../../../api/explorer.ts"; +import FileTypeIcon from "../../FileManager/Explorer/FileTypeIcon.tsx"; + +declare module "notistack" { + interface VariantOverrides { + file: { + file: FileResponse; + }; + } +} + +interface FileIconSnackbarProps extends CustomContentProps { + file: FileResponse; +} + +const FileIconSnackbar = forwardRef((props, ref) => { + const [progress, setProgress] = useState(0); + const { + // You have access to notistack props and options 👇🼠+ message, + action, + id, + file, + // as well as your own custom props 👇🼠+ ...other + } = props; + + let componentOrFunctionAction: React.ReactNode = undefined; + if (typeof action === "function") { + componentOrFunctionAction = action(id); + } else { + componentOrFunctionAction = action; + } + + return ( + + + + {message} + + + {componentOrFunctionAction && ( + + {componentOrFunctionAction} + + )} + + } + /> + ); +}); + +export default FileIconSnackbar; diff --git a/src/component/Common/Snackbar/LoadingSnackbar.tsx b/src/component/Common/Snackbar/LoadingSnackbar.tsx new file mode 100755 index 0000000..261b42d --- /dev/null +++ b/src/component/Common/Snackbar/LoadingSnackbar.tsx @@ -0,0 +1,91 @@ +import { Box } from "@mui/material"; +import MuiSnackbarContent from "@mui/material/SnackbarContent"; +import { CustomContentProps } from "notistack"; +import * as React from "react"; +import { forwardRef, useEffect, useState } from "react"; +import CircularProgress from "../CircularProgress.tsx"; + +declare module "notistack" { + interface VariantOverrides { + loading: { + getProgress?: () => number; + }; + } +} + +interface LoadingSnackbarProps extends CustomContentProps { + getProgress?: () => number; +} + +const LoadingSnackbar = forwardRef((props, ref) => { + const [progress, setProgress] = useState(0); + const { + // You have access to notistack props and options 👇🼠+ message, + action, + id, + getProgress, + // as well as your own custom props 👇🼠+ ...other + } = props; + + useEffect(() => { + var intervalId: NodeJS.Timeout; + if (getProgress) { + intervalId = setInterval(() => { + setProgress(getProgress()); + }, 1000); + } + + return () => { + clearInterval(intervalId); + }; + }, [getProgress]); + + let componentOrFunctionAction: React.ReactNode = undefined; + if (typeof action === "function") { + componentOrFunctionAction = action(id); + } else { + componentOrFunctionAction = action; + } + + return ( + + + + + {message} + {componentOrFunctionAction && ( + + {componentOrFunctionAction} + + )} + + } + /> + ); +}); + +export default LoadingSnackbar; diff --git a/src/component/Common/Snackbar/snackbar.tsx b/src/component/Common/Snackbar/snackbar.tsx new file mode 100755 index 0000000..42263f4 --- /dev/null +++ b/src/component/Common/Snackbar/snackbar.tsx @@ -0,0 +1,148 @@ +import { Button } from "@mui/material"; +import { closeSnackbar, SnackbarKey } from "notistack"; +import { useCallback } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { FileResponse } from "../../../api/explorer.ts"; +import { Response } from "../../../api/request.ts"; +import { setBatchDownloadLogDialog, setShareReadmeOpen } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { showAggregatedErrorDialog } from "../../../redux/thunks/dialog.ts"; +import { navigateToPath } from "../../../redux/thunks/filemanager.ts"; +import { FileManagerIndex } from "../../FileManager/FileManager.tsx"; + +export const DefaultCloseAction = (snackbarId: SnackbarKey | undefined) => { + const { t } = useTranslation(); + return ( + <> + + + ); +}; + +export const ErrorListDetailAction = (error: Response) => (snackbarId: SnackbarKey | undefined) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const Close = DefaultCloseAction(snackbarId); + + const showDetails = useCallback(() => { + dispatch(showAggregatedErrorDialog(error)); + closeSnackbar(snackbarId); + }, [dispatch, error, snackbarId]); + + return ( + <> + + {Close} + + ); +}; + +export const OpenReadMeAction = (file: FileResponse) => (snackbarId: SnackbarKey | undefined) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + + const Close = DefaultCloseAction(snackbarId); + + const openReadMe = useCallback(() => { + dispatch(setShareReadmeOpen({ open: true, target: file })); + closeSnackbar(snackbarId); + }, [dispatch, file, snackbarId]); + + return ( + <> + + {Close} + + ); +}; + +export const ViewDstAction = (dst: string) => (snackbarId: SnackbarKey | undefined) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const Close = DefaultCloseAction(snackbarId); + + const viewDst = useCallback(() => { + dispatch(navigateToPath(FileManagerIndex.main, dst)); + closeSnackbar(snackbarId); + }, [dispatch, snackbarId]); + + return ( + <> + + {Close} + + ); +}; + +export const ViewDownloadLogAction = (downloadId: string) => (_snackbarId: SnackbarKey | undefined) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const viewLogs = useCallback(() => { + dispatch(setBatchDownloadLogDialog({ open: true, id: downloadId })); + }, [dispatch, downloadId]); + + return ( + <> + + + ); +}; + +export const ViewTaskAction = + (path: string = "/tasks") => + (snackbarId: SnackbarKey | undefined) => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + const Close = DefaultCloseAction(snackbarId); + + const viewDst = useCallback(() => { + navigate(path); + closeSnackbar(snackbarId); + }, [navigate, snackbarId]); + + return ( + <> + + {Close} + + ); + }; + +export const ServiceWorkerUpdateAction = (updateServiceWorker: () => void) => (snackbarId: SnackbarKey | undefined) => { + const { t } = useTranslation(); + + const Close = DefaultCloseAction(snackbarId); + + const handleUpdate = useCallback(() => { + // Update service worker and reload + updateServiceWorker(); + closeSnackbar(snackbarId); + }, [updateServiceWorker, snackbarId]); + + return ( + <> + + {Close} + + ); +}; diff --git a/src/component/Common/StyledComponents.tsx b/src/component/Common/StyledComponents.tsx new file mode 100755 index 0000000..f5c2689 --- /dev/null +++ b/src/component/Common/StyledComponents.tsx @@ -0,0 +1,264 @@ +import { LoadingButton } from "@mui/lab"; +import { + alpha, + Autocomplete, + Box, + Button, + ButtonProps, + Checkbox, + Chip, + FormControlLabel, + FormControlLabelProps, + ListItemText, + ListItemTextProps, + Paper, + Select, + styled, + Tab, + TableCell, + Tabs, + TextField, + Typography, +} from "@mui/material"; + +export const DefaultButton = styled(({ variant, ...rest }: ButtonProps) => + } + showActions + hideOk + dialogProps={{ + open: open ?? false, + onClose: onClose, + fullWidth: true, + }} + > + + theme.typography.body2.fontSize, + }, + }} + sx={{ pt: 0.5 }} + minRows={10} + maxRows={10} + variant="outlined" + value={logs} + multiline + fullWidth + id="standard-basic" + /> + + + ); +}; +export default BatchDownloadLog; diff --git a/src/component/Dialogs/Confirmation.tsx b/src/component/Dialogs/Confirmation.tsx new file mode 100755 index 0000000..9953e6f --- /dev/null +++ b/src/component/Dialogs/Confirmation.tsx @@ -0,0 +1,50 @@ +import { useTranslation } from "react-i18next"; +import { DialogContent, Stack } from "@mui/material"; +import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts"; +import { useCallback } from "react"; +import DraggableDialog, { StyledDialogContentText } from "./DraggableDialog.tsx"; +import { generalDialogPromisePool } from "../../redux/thunks/dialog.ts"; +import { closeConfirmDialog } from "../../redux/globalStateSlice.ts"; + +const Confirmation = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const open = useAppSelector((state) => state.globalState.confirmDialogOpen); + const message = useAppSelector((state) => state.globalState.confirmDialogMessage); + const promiseId = useAppSelector((state) => state.globalState.confirmPromiseId); + + const onClose = useCallback(() => { + dispatch(closeConfirmDialog()); + if (promiseId) { + generalDialogPromisePool[promiseId]?.reject("cancel"); + } + }, [dispatch, promiseId]); + + const onAccept = useCallback(() => { + dispatch(closeConfirmDialog()); + if (promiseId) { + generalDialogPromisePool[promiseId]?.resolve(); + } + }, [promiseId]); + + return ( + + + + {message} + + + + ); +}; +export default Confirmation; diff --git a/src/component/Dialogs/DialogAccordion.tsx b/src/component/Dialogs/DialogAccordion.tsx new file mode 100755 index 0000000..70e1f32 --- /dev/null +++ b/src/component/Dialogs/DialogAccordion.tsx @@ -0,0 +1,77 @@ +import { AccordionDetailsProps, Box, styled } from "@mui/material"; +import MuiAccordion, { AccordionProps } from "@mui/material/Accordion"; +import MuiAccordionSummary, { AccordionSummaryProps } from "@mui/material/AccordionSummary"; +import MuiAccordionDetails from "@mui/material/AccordionDetails"; +import { useState } from "react"; +import { CaretDownIcon } from "../FileManager/TreeView/TreeFile.tsx"; +import { DefaultButton } from "../Common/StyledComponents.tsx"; + +const Accordion = styled((props: AccordionProps) => )( + ({ theme, expanded }) => ({ + borderRadius: theme.shape.borderRadius, + backgroundColor: expanded + ? theme.palette.mode == "light" + ? "rgba(0, 0, 0, 0.06)" + : "rgba(255, 255, 255, 0.09)" + : "initial", + "&:not(:last-child)": { + borderBottom: 0, + }, + "&::before": { + display: "none", + }, + }), +); + +const AccordionSummary = styled((props: AccordionSummaryProps) => )(() => ({ + flexDirection: "row-reverse", + minHeight: 0, + padding: 0, + "& .MuiAccordionSummary-content": { + margin: 0, + }, +})); + +const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({ + padding: theme.spacing(2), +})); + +const SummaryButton = styled(DefaultButton)<{ expanded: boolean }>(({ theme, expanded }) => ({ + justifyContent: "flex-start", + backgroundColor: expanded + ? "initial" + : theme.palette.mode == "light" + ? "rgba(0, 0, 0, 0.06)" + : "rgba(255, 255, 255, 0.09)", + "&:hover": { + backgroundColor: theme.palette.mode == "light" ? "rgba(0, 0, 0, 0.09)" : "rgba(255, 255, 255, 0.13)", + }, +})); + +export interface DialogAccordionProps { + children?: React.ReactNode; + defaultExpanded?: boolean; + title: string; + accordionDetailProps?: AccordionDetailsProps; +} + +const DialogAccordion = (props: DialogAccordionProps) => { + const [expanded, setExpanded] = useState(!!props.defaultExpanded); + const handleChange = (_event: React.SyntheticEvent, newExpanded: boolean) => { + setExpanded(newExpanded); + }; + return ( + + + + }> + {props.title} + + + {props.children} + + + ); +}; + +export default DialogAccordion; diff --git a/src/component/Dialogs/DraggableDialog.tsx b/src/component/Dialogs/DraggableDialog.tsx new file mode 100755 index 0000000..482436d --- /dev/null +++ b/src/component/Dialogs/DraggableDialog.tsx @@ -0,0 +1,117 @@ +import { + Box, + Button, + Dialog, + DialogActions, + DialogContentText, + DialogProps, + DialogTitle, + IconButton, + Paper, + PaperProps, + Stack, + styled, + useMediaQuery, +} from "@mui/material"; + +import { LoadingButton } from "@mui/lab"; +import { useCallback } from "react"; +import Draggable from "react-draggable"; +import { useTranslation } from "react-i18next"; +import Dismiss from "../Icons/Dismiss.tsx"; + +function PaperComponent(props: PaperProps) { + return ( + + + + ); +} + +export const StyledDialogActions = styled(DialogActions)<{ + denseAction?: boolean; +}>(({ theme, denseAction }) => ({ + padding: `${theme.spacing(denseAction ? 0.5 : 2)} ${theme.spacing(3)}`, + justifyContent: "space-between", +})); + +export const StyledDialogContentText = styled(DialogContentText)(({ theme }) => ({ + fontSize: theme.typography.body2.fontSize, + wordBreak: "break-all", +})); + +export const StyledDialogTitle = styled(DialogTitle)<{ moveable?: boolean }>(({ moveable }) => ({ + display: "flex", + justifyContent: "space-between", + alignItems: "center", + cursor: moveable ? "move" : "initial", +})); + +export interface DraggableDialogProps { + dialogProps: DialogProps; + children?: React.ReactNode; + secondaryAction?: React.ReactNode; + showActions?: boolean; + showCancel?: boolean; + hideOk?: boolean; + okText?: string; + cancelText?: string; + title?: string | React.ReactNode; + onAccept?: () => void; + loading?: boolean; + disabled?: boolean; + denseAction?: boolean; + secondaryFullWidth?: boolean; +} + +const DraggableDialog = (props: DraggableDialogProps) => { + const { t } = useTranslation(); + const isTouch = useMediaQuery("(pointer: coarse)"); + const onClose = useCallback(() => { + props.dialogProps.onClose && props.dialogProps.onClose({}, "backdropClick"); + }, [props.dialogProps.onClose]); + return ( + + {props.title != undefined && ( + + + {props.title} + + + + + + )} + {props.children} + {props.showActions && ( + + {props.secondaryAction} + + {props.showCancel && ( + + )} + {!props.hideOk && ( + + {props.okText ?? t("common:ok")} + + )} + + + )} + + ); +}; + +export default DraggableDialog; diff --git a/src/component/Dialogs/GlobalDialogs.tsx b/src/component/Dialogs/GlobalDialogs.tsx new file mode 100755 index 0000000..1a53e91 --- /dev/null +++ b/src/component/Dialogs/GlobalDialogs.tsx @@ -0,0 +1,20 @@ +import { useAppSelector } from "../../redux/hooks.ts"; +import PinToSidebar from "../FileManager/Dialogs/PinToSidebar.tsx"; +import BatchDownloadLog from "./BatchDownloadLog.tsx"; +import Confirmation from "./Confirmation.tsx"; +import SelectOption from "./SelectOption.tsx"; + +const GlobalDialogs = () => { + const selectOptionOpen = useAppSelector((state) => state.globalState.selectOptionDialogOpen); + const batchDownloadLogOpen = useAppSelector((state) => state.globalState.batchDownloadLogDialogOpen); + return ( + <> + + + {batchDownloadLogOpen != undefined && } + {selectOptionOpen != undefined && } + + ); +}; + +export default GlobalDialogs; diff --git a/src/component/Dialogs/SelectOption.tsx b/src/component/Dialogs/SelectOption.tsx new file mode 100755 index 0000000..74ccf89 --- /dev/null +++ b/src/component/Dialogs/SelectOption.tsx @@ -0,0 +1,67 @@ +import { useTranslation } from "react-i18next"; +import { DialogContent, List, ListItemButton, ListItemText } from "@mui/material"; +import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts"; +import React, { useCallback } from "react"; +import DraggableDialog from "./DraggableDialog.tsx"; +import { selectOptionDialogPromisePool } from "../../redux/thunks/dialog.ts"; +import { closeSelectOptionDialog } from "../../redux/globalStateSlice.ts"; + +const SelectOption = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const open = useAppSelector((state) => state.globalState.selectOptionDialogOpen); + const title = useAppSelector((state) => state.globalState.selectOptionTitle); + const promiseId = useAppSelector((state) => state.globalState.selectOptionPromiseId); + const options = useAppSelector((state) => state.globalState.selectOptionDialogOptions); + + const onClose = useCallback(() => { + dispatch(closeSelectOptionDialog()); + if (promiseId) { + selectOptionDialogPromisePool[promiseId]?.reject("cancel"); + } + }, [dispatch, promiseId]); + + const onAccept = useCallback( + (v: any) => { + dispatch(closeSelectOptionDialog()); + if (promiseId) { + selectOptionDialogPromisePool[promiseId]?.resolve(v); + } + }, + [promiseId], + ); + + return ( + + + + {options?.map((o) => ( + onAccept(o.value)}> + + + ))} + + + + ); +}; +export default SelectOption; diff --git a/src/component/FileManager/ContextMenu/CascadingMenu.tsx b/src/component/FileManager/ContextMenu/CascadingMenu.tsx new file mode 100755 index 0000000..22de6c7 --- /dev/null +++ b/src/component/FileManager/ContextMenu/CascadingMenu.tsx @@ -0,0 +1,118 @@ +import { Box, ListItemIcon, ListItemText, Menu, MenuItemProps, styled, useMediaQuery, useTheme } from "@mui/material"; +import { bindFocus, bindHover } from "material-ui-popup-state"; +import { bindMenu, bindTrigger, PopupState, usePopupState } from "material-ui-popup-state/hooks"; +import { createContext, useCallback, useContext, useMemo } from "react"; +import CaretRight from "../../Icons/CaretRight.tsx"; +import { SquareMenuItem } from "./ContextMenu.tsx"; +import HoverMenu from "./HoverMenu.tsx"; + +export const CascadingContext = createContext<{ + parentPopupState?: PopupState; + rootPopupState?: PopupState; +}>({}); + +export const SquareHoverMenu = styled(HoverMenu)(() => ({ + "& .MuiPaper-root": { + minWidth: "200px", + }, +})); + +export const SquareMenu = styled(Menu)(() => ({ + "& .MuiPaper-root": { + minWidth: "200px", + }, +})); + +export interface CascadingMenuItem { + onClick?: (event: React.MouseEvent) => void; + [key: string]: any; +} + +export function CascadingMenuItem({ onClick, ...props }: CascadingMenuItem) { + const { rootPopupState } = useContext(CascadingContext); + if (!rootPopupState) throw new Error("must be used inside a CascadingMenu"); + const handleClick = useCallback( + (event: React.MouseEvent) => { + rootPopupState.close(); + if (onClick) onClick(event); + }, + [rootPopupState, onClick], + ); + + return ; +} + +export interface CascadingMenuProps { + popupState: PopupState; + isMobile?: boolean; + [key: string]: any; +} + +export function CascadingMenu({ popupState, isMobile, ...props }: CascadingMenuProps) { + const { rootPopupState } = useContext(CascadingContext); + const context = useMemo( + () => ({ + rootPopupState: rootPopupState || popupState, + parentPopupState: popupState, + }), + [rootPopupState, popupState], + ); + + const MenuComponent = isMobile ? SquareMenu : SquareHoverMenu; + + return ( + + + + ); +} + +export interface CascadingSubmenu { + title: string; + icon?: JSX.Element; + popupId: string; + menuItemProps?: MenuItemProps; + [key: string]: any; +} + +export function CascadingSubmenu({ title, popupId, menuItemProps, icon, ...props }: CascadingSubmenu) { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + const { parentPopupState } = useContext(CascadingContext); + const popupState = usePopupState({ + popupId, + variant: "popover", + parentPopupState, + }); + return ( + <> + + {icon && {icon}} + + + + + + + + ); +} diff --git a/src/component/FileManager/ContextMenu/ContextMenu.tsx b/src/component/FileManager/ContextMenu/ContextMenu.tsx new file mode 100755 index 0000000..d7e74c9 --- /dev/null +++ b/src/component/FileManager/ContextMenu/ContextMenu.tsx @@ -0,0 +1,409 @@ +import { Box, Divider, ListItemIcon, ListItemText, Menu, MenuItem, styled, Typography, useTheme } from "@mui/material"; +import { useCallback, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { closeContextMenu } from "../../../redux/fileManagerSlice.ts"; +import { CreateNewDialogType } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { downloadFiles } from "../../../redux/thunks/download.ts"; +import { + batchGetDirectLinks, + createNew, + deleteFile, + dialogBasedMoveCopy, + enterFolder, + extractArchive, + goToParent, + goToSharedLink, + newRemoteDownload, + openShareDialog, + openSidebar, + renameFile, + restoreFile, +} from "../../../redux/thunks/file.ts"; +import { refreshFileList, uploadClicked, uploadFromClipboard } from "../../../redux/thunks/filemanager.ts"; +import { openViewers } from "../../../redux/thunks/viewer.ts"; +import AppFolder from "../../Icons/AppFolder.tsx"; +import ArchiveArrow from "../../Icons/ArchiveArrow.tsx"; +import ArrowSync from "../../Icons/ArrowSync.tsx"; +import BinFullOutlined from "../../Icons/BinFullOutlined.tsx"; +import Clipboard from "../../Icons/Clipboard.tsx"; +import CloudDownloadOutlined from "../../Icons/CloudDownloadOutlined.tsx"; +import CopyOutlined from "../../Icons/CopyOutlined.tsx"; +import DeleteOutlined from "../../Icons/DeleteOutlined.tsx"; +import Download from "../../Icons/Download.tsx"; +import Enter from "../../Icons/Enter.tsx"; +import FileAdd from "../../Icons/FileAdd.tsx"; +import FolderAdd from "../../Icons/FolderAdd.tsx"; +import FolderArrowUp from "../../Icons/FolderArrowUp.tsx"; +import FolderLink from "../../Icons/FolderLink.tsx"; +import FolderOutlined from "../../Icons/FolderOutlined.tsx"; +import HistoryOutlined from "../../Icons/HistoryOutlined.tsx"; +import Info from "../../Icons/Info.tsx"; +import LinkOutlined from "../../Icons/LinkOutlined.tsx"; +import Open from "../../Icons/Open.tsx"; +import RenameOutlined from "../../Icons/RenameOutlined.tsx"; +import ShareOutlined from "../../Icons/ShareOutlined.tsx"; +import Tag from "../../Icons/Tag.tsx"; +import Upload from "../../Icons/Upload.tsx"; +import WrenchSettings from "../../Icons/WrenchSettings.tsx"; +import { SelectType } from "../../Uploader/core"; +import { CascadingSubmenu } from "./CascadingMenu.tsx"; +import MoreMenuItems from "./MoreMenuItems.tsx"; +import NewFileTemplateMenuItems from "./NewFileTemplateMenuItems.tsx"; +import OpenWithMenuItems from "./OpenWithMenuItems.tsx"; +import OrganizeMenuItems from "./OrganizeMenuItems.tsx"; +import TagMenuItems from "./TagMenuItems.tsx"; +import useActionDisplayOpt from "./useActionDisplayOpt.ts"; + +export const SquareMenu = styled(Menu)(() => ({ + "& .MuiPaper-root": { + minWidth: "200px", + }, +})); + +export const SquareMenuItem = styled(MenuItem)<{ hoverColor?: string }>(({ theme, hoverColor }) => ({ + "&:hover .MuiListItemIcon-root": { + color: hoverColor ?? theme.palette.primary.main, + }, +})); + +export const DenseDivider = styled(Divider)(() => ({ + margin: "4px 0 !important", +})); + +export const EmptyMenu = () => { + const { t } = useTranslation(); + return ( + + + {t("fileManager.noActionsCanBeDone")} + + ); +}; + +export interface ContextMenuProps { + fmIndex: number; +} + +const ContextMenu = ({ fmIndex = 0 }: ContextMenuProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + const contextMenuOpen = useAppSelector((state) => state.fileManager[fmIndex].contextMenuOpen); + const contextMenuType = useAppSelector((state) => state.fileManager[fmIndex].contextMenuType); + const contextMenuPos = useAppSelector((state) => state.fileManager[fmIndex].contextMenuPos); + const selected = useAppSelector((state) => state.fileManager[fmIndex].selected); + const targetOverwrite = useAppSelector((state) => state.fileManager[fmIndex].contextMenuTargets); + + const targets = useMemo(() => { + const targetsMap = targetOverwrite ?? selected; + return Object.keys(targetsMap).map((key) => targetsMap[key]); + }, [targetOverwrite, selected]); + + const parent = useAppSelector((state) => state.fileManager[fmIndex].list?.parent); + + const displayOpt = useActionDisplayOpt(targets, contextMenuType, parent, fmIndex); + const onClose = useCallback(() => { + dispatch(closeContextMenu({ index: fmIndex, value: undefined })); + }, [dispatch]); + + const showOpenWithCascading = displayOpt.showOpenWithCascading && displayOpt.showOpenWithCascading(); + const showOpenWith = displayOpt.showOpenWith && displayOpt.showOpenWith(); + let part1 = + displayOpt.showOpen || + showOpenWithCascading || + showOpenWith || + displayOpt.showEnter || + displayOpt.showDownload || + displayOpt.showRemoteDownload || + displayOpt.showTorrentRemoteDownload || + displayOpt.showExtractArchive || + displayOpt.showUpload; + let part2 = + displayOpt.showCreateFolder || + displayOpt.showCreateFile || + displayOpt.showShare || + displayOpt.showRename || + displayOpt.showCopy || + displayOpt.showDirectLink; + let part3 = + displayOpt.showTags || displayOpt.showOrganize || displayOpt.showMore || displayOpt.showNewFileFromTemplate; + let part4 = displayOpt.showInfo || displayOpt.showGoToParent || displayOpt.showGoToSharedLink; + let part5 = displayOpt.showRestore || displayOpt.showDelete || displayOpt.showRefresh; + const showDivider1 = part1 && part2; + const showDivider2 = part2 && part3; + const showDivider3 = part3 && part4; + const showDivider4 = part4 && part5; + + const part1Elements = part1 ? ( + <> + {displayOpt.showUpload && ( + dispatch(uploadClicked(0, SelectType.File))}> + + + + {t("application:fileManager.uploadFiles")} + + )} + {displayOpt.showEnter && ( + dispatch(enterFolder(0, targets[0]))}> + + + + {t("application:fileManager.enter")} + + )} + {displayOpt.showUpload && ( + dispatch(uploadClicked(0, SelectType.Directory))}> + + + + {t("application:fileManager.uploadFolder")} + + )} + {displayOpt.showUpload && ( + dispatch(uploadFromClipboard(0))}> + + + + {t("application:uploader.uploadFromClipboard")} + + )} + {displayOpt.showRemoteDownload && ( + dispatch(newRemoteDownload(0))}> + + + + {t("application:fileManager.newRemoteDownloads")} + + )} + {displayOpt.showOpen && ( + dispatch(openViewers(fmIndex, targets[0]))}> + + + + {t("application:fileManager.open")} + + )} + {showOpenWithCascading && ( + } + > + + + )} + {showOpenWith && ( + dispatch(openViewers(fmIndex, targets[0], targets[0].size, undefined, true))}> + + + + {t("application:fileManager.openWith")} + + )} + {displayOpt.showDownload && ( + dispatch(downloadFiles(fmIndex, targets))}> + + + + {t("application:fileManager.download")} + + )} + {displayOpt.showExtractArchive && ( + dispatch(extractArchive(fmIndex, targets[0]))}> + + + + {t("application:fileManager.extractArchive")} + + )} + {displayOpt.showTorrentRemoteDownload && ( + dispatch(newRemoteDownload(fmIndex, targets[0]))}> + + + + {t("application:fileManager.createRemoteDownloadForTorrent")} + + )} + + ) : undefined; + + const part2Elements = part2 ? ( + <> + {displayOpt.showCreateFolder && ( + dispatch(createNew(fmIndex, CreateNewDialogType.folder))}> + + + + {t("application:fileManager.newFolder")} + + )} + {displayOpt.showCreateFile && ( + dispatch(createNew(fmIndex, CreateNewDialogType.file))}> + + + + {t("application:fileManager.newFile")} + + )} + {displayOpt.showShare && ( + dispatch(openShareDialog(fmIndex, targets[0]))}> + + + + {t("application:fileManager.share")} + + )} + {displayOpt.showRename && ( + dispatch(renameFile(fmIndex, targets[0]))}> + + + + {t("application:fileManager.rename")} + + )} + {displayOpt.showCopy && ( + dispatch(dialogBasedMoveCopy(fmIndex, targets, true))}> + + + + {t("application:fileManager.copy")} + + )} + {displayOpt.showDirectLink && ( + dispatch(batchGetDirectLinks(fmIndex, targets))}> + + + + {t("application:fileManager.getSourceLink")} + + )} + + ) : undefined; + + const part3Elements = part3 ? ( + <> + {displayOpt.showTags && ( + }> + + + )} + {displayOpt.showOrganize && ( + } + > + + + )} + {displayOpt.showMore && ( + } + > + + + )} + {displayOpt.showNewFileFromTemplate && } + + ) : undefined; + + const part4Elements = part4 ? ( + <> + {displayOpt.showGoToSharedLink && ( + dispatch(goToSharedLink(fmIndex, targets[0]))}> + + + + {t("application:fileManager.goToSharedLink")} + + )} + {displayOpt.showGoToParent && ( + dispatch(goToParent(0, targets[0]))}> + + + + {t("application:fileManager.openParentFolder")} + + )} + {displayOpt.showInfo && ( + dispatch(openSidebar(fmIndex, targets[0]))}> + + + + {t("application:fileManager.viewDetails")} + + )} + + ) : undefined; + + const part5Elements = part5 ? ( + <> + {displayOpt.showRestore && ( + dispatch(restoreFile(fmIndex, targets))}> + + + + {t("application:fileManager.restore")} + + )} + {displayOpt.showDelete && ( + dispatch(deleteFile(fmIndex, targets))}> + + + + {t("application:fileManager.delete")} + + )} + {displayOpt.showRefresh && ( + dispatch(refreshFileList(fmIndex))}> + + + + {t("application:fileManager.refresh")} + + )} + + ) : undefined; + + const allParts = [part1Elements, part2Elements, part3Elements, part4Elements, part5Elements].filter( + (p) => p != undefined, + ); + + return ( + { + e.preventDefault(); + }, + }, + }} + > + {allParts.map((part, index) => ( + <> + {part} + {index < allParts.length - 1 && } + + ))} + {allParts.length == 0 && } + + ); +}; + +export default ContextMenu; diff --git a/src/component/FileManager/ContextMenu/HoverMenu.tsx b/src/component/FileManager/ContextMenu/HoverMenu.tsx new file mode 100755 index 0000000..321bd86 --- /dev/null +++ b/src/component/FileManager/ContextMenu/HoverMenu.tsx @@ -0,0 +1,23 @@ +import * as React from "react"; +import { Menu, type MenuProps } from "@mui/material"; + +const HoverMenu: React.ComponentType = React.forwardRef(function HoverMenu(props: MenuProps, ref): any { + return ( + + ); +}); + +export default HoverMenu; diff --git a/src/component/FileManager/ContextMenu/MoreMenuItems.tsx b/src/component/FileManager/ContextMenu/MoreMenuItems.tsx new file mode 100755 index 0000000..75b4909 --- /dev/null +++ b/src/component/FileManager/ContextMenu/MoreMenuItems.tsx @@ -0,0 +1,122 @@ +import { ListItemIcon, ListItemText } from "@mui/material"; +import { useCallback, useContext } from "react"; +import { useTranslation } from "react-i18next"; +import { closeContextMenu } from "../../../redux/fileManagerSlice.ts"; +import { + setCreateArchiveDialog, + setDirectLinkManagementDialog, + setManageShareDialog, + setVersionControlDialog, +} from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { resetThumbnails } from "../../../redux/thunks/file.ts"; +import Archive from "../../Icons/Archive.tsx"; +import BranchForkLink from "../../Icons/BranchForkLink.tsx"; +import HistoryOutlined from "../../Icons/HistoryOutlined.tsx"; +import ImageArrowCounterclockwise from "../../Icons/ImageAarowCounterclockwise.tsx"; +import LinkSetting from "../../Icons/LinkSetting.tsx"; +import { CascadingContext, CascadingMenuItem } from "./CascadingMenu.tsx"; +import { SubMenuItemsProps } from "./OrganizeMenuItems.tsx"; + +const MoreMenuItems = ({ displayOpt, targets }: SubMenuItemsProps) => { + const { rootPopupState } = useContext(CascadingContext); + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const onClick = useCallback( + (f: () => any) => () => { + f(); + if (rootPopupState) { + rootPopupState.close(); + } + dispatch( + closeContextMenu({ + index: 0, + value: undefined, + }), + ); + }, + [dispatch, targets], + ); + return ( + <> + {displayOpt.showVersionControl && ( + + dispatch( + setVersionControlDialog({ + open: true, + file: targets[0], + }), + ), + )} + > + + + + {t("application:fileManager.manageVersions")} + + )} + {displayOpt.showManageShares && ( + + dispatch( + setManageShareDialog({ + open: true, + file: targets[0], + }), + ), + )} + > + + + + {t("application:fileManager.manageShares")} + + )} + {displayOpt.showDirectLinkManagement && ( + + dispatch( + setDirectLinkManagementDialog({ + open: true, + file: targets[0], + }), + ), + )} + > + + + + {t("application:fileManager.manageDirectLinks")} + + )} + {displayOpt.showCreateArchive && ( + + dispatch( + setCreateArchiveDialog({ + open: true, + files: targets, + }), + ), + )} + > + + + + {t("application:fileManager.createArchive")} + + )} + {displayOpt.showResetThumb && ( + dispatch(resetThumbnails(targets)))}> + + + + {t("application:fileManager.resetThumbnail")} + + )} + + ); +}; + +export default MoreMenuItems; diff --git a/src/component/FileManager/ContextMenu/NewFileTemplateMenuItems.tsx b/src/component/FileManager/ContextMenu/NewFileTemplateMenuItems.tsx new file mode 100755 index 0000000..1527037 --- /dev/null +++ b/src/component/FileManager/ContextMenu/NewFileTemplateMenuItems.tsx @@ -0,0 +1,99 @@ +import React, { useCallback, useContext } from "react"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { SubMenuItemsProps } from "./OrganizeMenuItems.tsx"; +import { ViewersByID } from "../../../redux/siteConfigSlice.ts"; +import { ListItemIcon, ListItemText } from "@mui/material"; +import { ViewerIcon } from "../Dialogs/OpenWith.tsx"; +import { SquareMenuItem } from "./ContextMenu.tsx"; +import { CascadingContext, CascadingMenuItem, CascadingSubmenu } from "./CascadingMenu.tsx"; +import { NewFileTemplate, Viewer } from "../../../api/explorer.ts"; +import { createNew } from "../../../redux/thunks/file.ts"; +import { CreateNewDialogType } from "../../../redux/globalStateSlice.ts"; + +interface MultiTemplatesMenuItemsProps { + viewer: Viewer; +} + +const MultiTemplatesMenuItems = ({ viewer }: MultiTemplatesMenuItemsProps) => { + const { rootPopupState } = useContext(CascadingContext); + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const onClick = useCallback( + (f: NewFileTemplate) => () => { + if (rootPopupState) { + rootPopupState.close(); + } + + dispatch(createNew(0, CreateNewDialogType.file, viewer, f)); + }, + [dispatch], + ); + + return ( + <> + {viewer.templates?.map((template) => ( + + + {t("fileManager.newDocumentType", { + display_name: t(template.display_name), + ext: template.ext, + })} + + + ))} + + ); +}; + +const NewFileTemplateMenuItems = (props: SubMenuItemsProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const onClick = useCallback( + (viewer: Viewer, template: NewFileTemplate) => () => { + dispatch(createNew(0, CreateNewDialogType.file, viewer, template)); + }, + [dispatch], + ); + + return ( + <> + {Object.values(ViewersByID) + .filter((viewer) => viewer.templates) + .map((viewer) => { + if (!viewer.templates) { + return null; + } + + if (viewer.templates.length == 1) { + return ( + + + + + + {t("fileManager.newDocumentType", { + display_name: t(viewer.templates[0].display_name), + ext: viewer.templates[0].ext, + })} + + + ); + } else { + return ( + } + > + + + ); + } + })} + + ); +}; + +export default NewFileTemplateMenuItems; diff --git a/src/component/FileManager/ContextMenu/OpenWithMenuItems.tsx b/src/component/FileManager/ContextMenu/OpenWithMenuItems.tsx new file mode 100755 index 0000000..886d54d --- /dev/null +++ b/src/component/FileManager/ContextMenu/OpenWithMenuItems.tsx @@ -0,0 +1,65 @@ +import React, { useCallback, useContext, useMemo } from "react"; +import { CascadingContext, CascadingMenuItem } from "./CascadingMenu.tsx"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { closeContextMenu } from "../../../redux/fileManagerSlice.ts"; +import { ListItemIcon, ListItemText } from "@mui/material"; +import { SubMenuItemsProps } from "./OrganizeMenuItems.tsx"; +import { fileExtension } from "../../../util"; +import { Viewers } from "../../../redux/siteConfigSlice.ts"; +import { Viewer } from "../../../api/explorer.ts"; +import { ViewerIcon } from "../Dialogs/OpenWith.tsx"; +import { openViewer, openViewers } from "../../../redux/thunks/viewer.ts"; + +const OpenWithMenuItems = ({ targets }: SubMenuItemsProps) => { + const { rootPopupState } = useContext(CascadingContext); + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const onClick = useCallback( + (v: Viewer) => () => { + dispatch(openViewer(targets[0], v, targets[0].size)); + if (rootPopupState) { + rootPopupState.close(); + } + dispatch( + closeContextMenu({ + index: 0, + value: undefined, + }), + ); + }, + [dispatch, targets], + ); + + const openSelector = useCallback(() => { + dispatch(openViewers(0, targets[0], targets[0].size, undefined, true)); + }, [targets]); + + const viewers = useMemo(() => { + if (targets.length == 0) { + return []; + } + + const firstFileSuffix = fileExtension(targets[0].name); + return Viewers[firstFileSuffix ?? ""]; + }, [targets]); + + return ( + <> + {viewers.map((viewer) => ( + + + + + {t(viewer.display_name)} + + ))} + + + {t("fileManager.selectApplications")} + + + ); +}; + +export default OpenWithMenuItems; diff --git a/src/component/FileManager/ContextMenu/OrganizeMenuItems.tsx b/src/component/FileManager/ContextMenu/OrganizeMenuItems.tsx new file mode 100755 index 0000000..b52fb3c --- /dev/null +++ b/src/component/FileManager/ContextMenu/OrganizeMenuItems.tsx @@ -0,0 +1,103 @@ +import { DisplayOption } from "./useActionDisplayOpt.ts"; +import { FileResponse } from "../../../api/explorer.ts"; +import { useCallback, useContext } from "react"; +import { CascadingContext, CascadingMenuItem } from "./CascadingMenu.tsx"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { closeContextMenu } from "../../../redux/fileManagerSlice.ts"; +import { applyIconColor, dialogBasedMoveCopy } from "../../../redux/thunks/file.ts"; +import { ListItemIcon, ListItemText } from "@mui/material"; +import FolderArrowRightOutlined from "../../Icons/FolderArrowRightOutlined.tsx"; +import { setChangeIconDialog, setPinFileDialog } from "../../../redux/globalStateSlice.ts"; +import { getFileLinkedUri } from "../../../util"; +import PinOutlined from "../../Icons/PinOutlined.tsx"; +import EmojiEdit from "../../Icons/EmojiEdit.tsx"; +import FolderColorQuickAction from "../FileInfo/FolderColorQuickAction.tsx"; +import { DenseDivider } from "./ContextMenu.tsx"; + +export interface SubMenuItemsProps { + displayOpt: DisplayOption; + targets: FileResponse[]; +} +const OrganizeMenuItems = ({ displayOpt, targets }: SubMenuItemsProps) => { + const { rootPopupState } = useContext(CascadingContext); + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const onClick = useCallback( + (f: () => any) => () => { + f(); + if (rootPopupState) { + rootPopupState.close(); + } + dispatch( + closeContextMenu({ + index: 0, + value: undefined, + }), + ); + }, + [dispatch, targets], + ); + const showDivider = + (displayOpt.showMove || displayOpt.showPin || displayOpt.showChangeIcon) && displayOpt.showChangeFolderColor; + return ( + <> + {displayOpt.showMove && ( + dispatch(dialogBasedMoveCopy(0, targets, false)))}> + + + + {t("application:fileManager.move")} + + )} + {displayOpt.showPin && ( + + dispatch( + setPinFileDialog({ + open: true, + uri: getFileLinkedUri(targets[0]), + }), + ), + )} + > + + + + {t("application:fileManager.pin")} + + )} + {displayOpt.showChangeIcon && ( + + dispatch( + setChangeIconDialog({ + open: true, + file: targets, + }), + ), + )} + > + + + + {t("application:fileManager.customizeIcon")} + + )} + {showDivider && } + {displayOpt.showChangeFolderColor && ( + onClick(() => dispatch(applyIconColor(0, targets, color, true)))()} + sx={{ + maxWidth: "204px", + margin: (theme) => `0 ${theme.spacing(0.5)}`, + padding: (theme) => `${theme.spacing(0.5)} ${theme.spacing(1)}`, + }} + /> + )} + + ); +}; + +export default OrganizeMenuItems; diff --git a/src/component/FileManager/ContextMenu/TagMenuItems.tsx b/src/component/FileManager/ContextMenu/TagMenuItems.tsx new file mode 100755 index 0000000..bc19ab7 --- /dev/null +++ b/src/component/FileManager/ContextMenu/TagMenuItems.tsx @@ -0,0 +1,179 @@ +import { Box, IconButton, ListItemIcon, ListItemText } from "@mui/material"; +import React, { useCallback, useContext, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { FileResponse, Metadata } from "../../../api/explorer.ts"; +import { closeContextMenu } from "../../../redux/fileManagerSlice.ts"; +import { setTagsDialog } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { patchFileMetadata } from "../../../redux/thunks/file.ts"; +import SessionManager, { UserSettings } from "../../../session"; +import { UsedTags } from "../../../session/utils.ts"; +import Checkmark from "../../Icons/Checkmark.tsx"; +import DeleteOutlined from "../../Icons/DeleteOutlined.tsx"; +import Tags from "../../Icons/Tags.tsx"; +import { getUniqueTagsFromFiles, Tag as TagItem } from "../Dialogs/Tags.tsx"; +import FileTag from "../Explorer/FileTag.tsx"; +import { FileManagerIndex } from "../FileManager.tsx"; +import { CascadingContext, CascadingMenuItem } from "./CascadingMenu.tsx"; +import { DenseDivider, SquareMenuItem } from "./ContextMenu.tsx"; +import { SubMenuItemsProps } from "./OrganizeMenuItems.tsx"; + +interface TagOption extends TagItem { + selected?: boolean; +} + +const getTagOptions = (targets: FileResponse[]): TagOption[] => { + const tags: { + [key: string]: TagOption; + } = {}; + getUniqueTagsFromFiles(targets).forEach((tag) => { + tags[tag.key] = { ...tag, selected: true }; + }); + + const existing = SessionManager.get(UserSettings.UsedTags) as UsedTags; + if (existing) { + Object.keys(existing).forEach((key) => { + if (!tags[key]) { + tags[key] = { key, color: existing[key] ?? undefined, selected: false }; + } + }); + } + + return Object.values(tags); +}; + +const TagMenuItems = ({ displayOpt, targets }: SubMenuItemsProps) => { + const { rootPopupState } = useContext(CascadingContext); + const [tags, setTags] = useState(getTagOptions(targets)); + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const onClick = useCallback( + (f: () => any) => () => { + f(); + if (rootPopupState) { + rootPopupState.close(); + } + dispatch( + closeContextMenu({ + index: 0, + value: undefined, + }), + ); + }, + [dispatch, targets], + ); + + const onTagChange = useCallback( + async (tag: TagOption, selected: boolean) => { + setTags((tags) => + tags.map((t) => { + if (t.key == tag.key) { + return { ...t, selected }; + } + return t; + }), + ); + try { + await dispatch( + patchFileMetadata(FileManagerIndex.main, targets, [ + { + key: Metadata.tag_prefix + tag.key, + value: tag.color, + remove: !selected, + }, + ]), + ); + } catch (e) { + return; + } + }, + [targets, setTags], + ); + + const onTagDelete = useCallback( + (tag: TagOption, event: React.MouseEvent) => { + event.stopPropagation(); + + // Remove tag from session cache + const existing = SessionManager.get(UserSettings.UsedTags) as UsedTags; + if (existing && existing[tag.key] !== undefined) { + delete existing[tag.key]; + SessionManager.set(UserSettings.UsedTags, existing); + } + + // Remove tag from local state + setTags((tags) => tags.filter((t) => t.key !== tag.key)); + }, + [setTags], + ); + + return ( + <> + + dispatch( + setTagsDialog({ + open: true, + file: targets, + }), + ), + )} + > + + + + {t("application:modals.manageTags")} + + {tags.length > 0 && } + {tags.map((tag) => ( + onTagChange(tag, !tag.selected)}> + {tag.selected && ( + <> + + + + + + + + )} + {!tag.selected && ( + + + + + onTagDelete(tag, event)} + sx={{ + opacity: 0, + transition: "opacity 0.2s", + marginLeft: "auto", + marginRight: 1, + padding: "2px", + "&:hover": { + backgroundColor: "action.hover", + }, + }} + > + + + + )} + + ))} + + ); +}; + +export default TagMenuItems; diff --git a/src/component/FileManager/ContextMenu/useActionDisplayOpt.ts b/src/component/FileManager/ContextMenu/useActionDisplayOpt.ts new file mode 100755 index 0000000..a2e1e62 --- /dev/null +++ b/src/component/FileManager/ContextMenu/useActionDisplayOpt.ts @@ -0,0 +1,325 @@ +import { useMemo } from "react"; +import { FileResponse, FileType, Metadata, NavigatorCapability } from "../../../api/explorer.ts"; +import { GroupPermission } from "../../../api/user.ts"; +import { defaultPath } from "../../../hooks/useNavigation.tsx"; +import { ContextMenuTypes } from "../../../redux/fileManagerSlice.ts"; +import { Viewers, ViewersByID } from "../../../redux/siteConfigSlice.ts"; +import { ExpandedViewerSetting } from "../../../redux/thunks/viewer.ts"; +import SessionManager from "../../../session"; +import { fileExtension } from "../../../util"; +import Boolset from "../../../util/boolset.ts"; +import CrUri, { Filesystem } from "../../../util/uri.ts"; +import { FileManagerIndex } from "../FileManager.tsx"; + +const supportedArchiveTypes = ["zip", "gz", "xz", "tar", "rar", "7z", "bz2"]; + +export const canManageVersion = (file: FileResponse, bs: Boolset) => { + return ( + file.type == FileType.file && + (!file.metadata || !file.metadata[Metadata.share_redirect]) && + bs.enabled(NavigatorCapability.version_control) + ); +}; + +export const canShowInfo = (cap: Boolset) => { + return cap.enabled(NavigatorCapability.info); +}; + +export const canUpdate = (opt: DisplayOption) => { + return !!( + opt.allUpdatable && + opt.hasFile && + opt.orCapability?.enabled(NavigatorCapability.upload_file) && + opt.allUpdatable + ); +}; + +export interface DisplayOption { + allReadable: boolean; + allUpdatable: boolean; + + hasReadable?: boolean; + hasUpdatable?: boolean; + + hasTrashFile?: boolean; + hasFile?: boolean; + hasFolder?: boolean; + hasOwned?: boolean; + hasFailedThumb?: boolean; + + showEnter?: boolean; + showOpen?: boolean; + showOpenWithCascading?: () => boolean; + showOpenWith?: () => boolean; + showDownload?: boolean; + showGoToSharedLink?: boolean; + showExtractArchive?: boolean; + showTorrentRemoteDownload?: boolean; + showGoToParent?: boolean; + + showDelete?: boolean; + showRestore?: boolean; + showRename?: boolean; + showPin?: boolean; + showOrganize?: boolean; + showCopy?: boolean; + showShare?: boolean; + showInfo?: boolean; + showDirectLink?: boolean; + + showMove?: boolean; + showTags?: boolean; + showChangeFolderColor?: boolean; + showChangeIcon?: boolean; + showCustomProps?: boolean; + + showMore?: boolean; + showVersionControl?: boolean; + showDirectLinkManagement?: boolean; + showManageShares?: boolean; + showCreateArchive?: boolean; + showResetThumb?: boolean; + + andCapability?: Boolset; + orCapability?: Boolset; + + showCreateFolder?: boolean; + showCreateFile?: boolean; + showRefresh?: boolean; + showNewFileFromTemplate?: boolean; + showUpload?: boolean; + showRemoteDownload?: boolean; +} + +const capabilityMap: { [key: string]: Boolset } = {}; + +export const getActionOpt = ( + targets: FileResponse[], + viewerSetting?: ExpandedViewerSetting, + type?: string, + parent?: FileResponse, + fmIndex: number = 0, +): DisplayOption => { + const currentUser = SessionManager.currentLoginOrNull(); + const currentUserAnonymous = SessionManager.currentUser(); + const groupBs = SessionManager.currentUserGroupPermission(); + const display: DisplayOption = { + allReadable: true, + allUpdatable: true, + }; + if (type == ContextMenuTypes.empty || type == ContextMenuTypes.new) { + display.showRefresh = type == ContextMenuTypes.empty; + display.showRemoteDownload = groupBs.enabled(GroupPermission.remote_download) && !!currentUser; + + if (!parent || parent.type != FileType.folder) { + display.showRemoteDownload = display.showRemoteDownload && type == ContextMenuTypes.new; + return display; + } + + const parentCap = new Boolset(parent.capability); + display.showCreateFolder = parentCap.enabled(NavigatorCapability.create_file) && parent.owned; + display.showCreateFile = display.showCreateFolder && fmIndex == FileManagerIndex.main; + display.showUpload = display.showCreateFile; + if (display.showCreateFile) { + const allViewers = Object.entries(ViewersByID); + for (let i = 0; i < allViewers.length; i++) { + if (allViewers[i][1] && allViewers[i][1].templates) { + display.showNewFileFromTemplate = true; + break; + } + } + } + + return display; + } + + if (type == ContextMenuTypes.searchResult) { + display.showGoToParent = true; + } + + const parentUrl = new CrUri(targets?.[0]?.path ?? defaultPath); + targets.forEach((target) => { + let readable = true; + let updatable = target.owned && parentUrl.fs() != Filesystem.share; + + if (display.allReadable && !readable) { + display.allReadable = false; + } + if (display.allUpdatable && !updatable) { + display.allUpdatable = false; + } + + if (!display.hasReadable && readable) { + display.hasReadable = true; + } + if (!display.hasUpdatable && updatable) { + display.hasUpdatable = true; + } + + if (target.metadata) { + if (target.metadata[Metadata.restore_uri]) { + display.hasTrashFile = true; + } + if (target.metadata[Metadata.thumbDisabled] !== undefined) { + display.hasFailedThumb = true; + } + } + + if (target.type == FileType.file) { + display.hasFile = true; + } + + if (target.type == FileType.folder) { + display.hasFolder = true; + } + + if (target.owned) { + display.hasOwned = true; + } + + if (target.capability) { + let bs = capabilityMap[target.capability]; + if (!bs) { + bs = new Boolset(target.capability); + capabilityMap[target.capability] = bs; + } + + if (!display.andCapability) { + display.andCapability = bs; + } + + display.andCapability = display.andCapability.and(bs); + + if (!display.orCapability) { + display.orCapability = bs; + } + display.orCapability = display.orCapability.or(bs); + } + }); + + const firstFileSuffix = fileExtension(targets[0]?.name ?? ""); + display.showPin = !display.hasTrashFile && targets.length == 1 && display.hasFolder; + display.showDelete = + display.hasUpdatable && + display.orCapability && + (display.orCapability.enabled(NavigatorCapability.soft_delete) || + display.orCapability.enabled(NavigatorCapability.delete_file)); + display.showRestore = display.andCapability?.enabled(NavigatorCapability.restore); + display.showRename = + targets.length == 1 && + display.allUpdatable && + display.orCapability && + display.orCapability.enabled(NavigatorCapability.rename_file); + display.showCopy = display.hasUpdatable && !!display.orCapability; + display.showShare = + targets.length == 1 && + !!currentUser && + groupBs.enabled(GroupPermission.share) && + display.allUpdatable && + (targets[0].owned || groupBs.enabled(GroupPermission.is_admin)) && + display.orCapability && + display.orCapability.enabled(NavigatorCapability.share) && + (!targets[0].metadata || + (!targets[0].metadata[Metadata.share_redirect] && !targets[0].metadata[Metadata.restore_uri])); + display.showMove = display.hasUpdatable && !!display.orCapability; + display.showTags = + display.hasUpdatable && display.orCapability && display.orCapability.enabled(NavigatorCapability.update_metadata); + display.showChangeFolderColor = + display.hasUpdatable && + !display.hasFile && + display.orCapability && + display.orCapability.enabled(NavigatorCapability.update_metadata); + display.showChangeIcon = + display.hasUpdatable && display.orCapability && display.orCapability.enabled(NavigatorCapability.update_metadata); + display.showCustomProps = display.showChangeIcon; + display.showDownload = + display.hasReadable && display.orCapability && display.orCapability.enabled(NavigatorCapability.download_file); + display.showDirectLink = + (display.hasOwned || groupBs.enabled(GroupPermission.is_admin)) && + display.orCapability && + (currentUserAnonymous?.group?.direct_link_batch_size ?? 0) >= targets.length && + display.orCapability.enabled(NavigatorCapability.download_file); + display.showDirectLinkManagement = display.showDirectLink && targets.length == 1 && display.hasFile; + display.showOpen = + targets.length == 1 && + display.hasFile && + display.showDownload && + !!viewerSetting && + !!firstFileSuffix && + !!viewerSetting?.[firstFileSuffix]; + display.showEnter = + targets.length == 1 && + display.hasFolder && + display.orCapability?.enabled(NavigatorCapability.enter_folder) && + display.allReadable; + display.showExtractArchive = + targets.length == 1 && + display.hasFile && + display.showDownload && + !!currentUser && + groupBs.enabled(GroupPermission.archive_task) && + supportedArchiveTypes.includes(firstFileSuffix ?? ""); + display.showTorrentRemoteDownload = + targets.length == 1 && + display.hasFile && + display.showDownload && + !!currentUser && + groupBs.enabled(GroupPermission.remote_download) && + firstFileSuffix == "torrent"; + + display.showOpenWithCascading = () => false; + display.showOpenWith = () => targets.length == 1 && !!display.hasFile && !!display.showDownload; + if (display.showOpen) { + display.showOpenWithCascading = () => + !!(display.showOpen && viewerSetting && viewerSetting[firstFileSuffix ?? ""]?.length >= 1); + display.showOpenWith = () => + !!(display.showOpen && viewerSetting && viewerSetting[firstFileSuffix ?? ""]?.length < 1); + } + display.showOrganize = display.showPin || display.showMove || display.showChangeFolderColor || display.showChangeIcon; + display.showGoToSharedLink = + targets.length == 1 && display.hasFile && targets[0].metadata && !!targets[0].metadata[Metadata.share_redirect]; + display.showInfo = targets.length == 1 && display.orCapability && canShowInfo(display.orCapability); + display.showVersionControl = + targets.length == 1 && + display.orCapability && + display.hasReadable && + canManageVersion(targets[0], display.orCapability); + display.showManageShares = + targets.length == 1 && + targets[0].shared && + display.orCapability && + !!currentUser && + groupBs.enabled(GroupPermission.share) && + display.orCapability.enabled(NavigatorCapability.share); + display.showCreateArchive = + display.hasReadable && + !!currentUser && + groupBs.enabled(GroupPermission.archive_task) && + display.orCapability && + display.orCapability.enabled(NavigatorCapability.download_file); + display.showResetThumb = + display.hasFile && + !display.hasFolder && + display.hasFailedThumb && + display.allUpdatable && + display.orCapability && + display.orCapability.enabled(NavigatorCapability.update_metadata); + + display.showMore = + display.showVersionControl || + display.showManageShares || + display.showCreateArchive || + display.showDirectLinkManagement || + display.showResetThumb; + return display; +}; + +const useActionDisplayOpt = (targets: FileResponse[], type?: string, parent?: FileResponse, fmIndex: number = 0) => { + const opt = useMemo(() => { + return getActionOpt(targets, Viewers, type, parent, fmIndex); + }, [targets, type, parent, fmIndex]); + + return opt; +}; + +export default useActionDisplayOpt; diff --git a/src/component/FileManager/Dialogs/ChangeIcon.tsx b/src/component/FileManager/Dialogs/ChangeIcon.tsx new file mode 100755 index 0000000..1cd17ad --- /dev/null +++ b/src/component/FileManager/Dialogs/ChangeIcon.tsx @@ -0,0 +1,184 @@ +import { useTranslation } from "react-i18next"; +import { Box, Button, DialogContent, Skeleton, styled, Tab, Tabs, useTheme } from "@mui/material"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; +import { closeChangeIconDialog } from "../../../redux/globalStateSlice.ts"; +import { LoadingButton } from "@mui/lab"; +import { loadSiteConfig } from "../../../redux/thunks/site.ts"; +import AutoHeight from "../../Common/AutoHeight.tsx"; +import { ConfigLoadState } from "../../../redux/siteConfigSlice.ts"; +import { applyIcon } from "../../../redux/thunks/file.ts"; +import { FileManagerIndex } from "../FileManager.tsx"; + +interface EmojiSetting { + [key: string]: string[]; +} + +interface TabPanelProps { + children?: React.ReactNode; + index: number; + value: number; + loading?: boolean; +} + +function CustomTabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props; + + return ( + + ); +} + +const StyledTab = styled(Tab)(({ theme }) => ({ + minWidth: 0, + minHeight: 0, + fontSize: theme.typography.h6.fontSize, + padding: "8px 10px", +})); + +const EmojiButton = styled(Button)(({ theme }) => ({ + minWidth: 0, + padding: "0px 4px", + fontSize: theme.typography.h6.fontSize, +})); + +const SelectorBox = styled(Box)({ + display: "flex", + flexWrap: "wrap", +}); + +const ChangeIcon = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + + const [tabValue, setTabValue] = useState(0); + const [loading, setLoading] = useState(false); + + const open = useAppSelector((state) => state.globalState.changeIconDialogOpen); + const targets = useAppSelector((state) => state.globalState.changeIconDialogFile); + const emojiStr = useAppSelector((state) => state.siteConfig.emojis.config.emoji_preset); + const emojiStrLoaded = useAppSelector((state) => state.siteConfig.emojis.loaded); + + const emojiSetting = useMemo((): EmojiSetting => { + if (!emojiStr) return {}; + try { + return JSON.parse(emojiStr) as EmojiSetting; + } catch (e) { + console.warn("failed to parse emoji setting", e); + } + return {}; + }, [emojiStr]); + + const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => { + setTabValue(newValue); + }; + + useEffect(() => { + if (open && emojiStrLoaded != ConfigLoadState.Loaded) { + dispatch(loadSiteConfig("emojis")); + } + }, [open]); + + const onClose = useCallback(() => { + if (!loading) { + dispatch(closeChangeIconDialog()); + } + }, [dispatch, loading]); + + const onAccept = useCallback( + (icon?: string) => async (e?: React.MouseEvent) => { + if (e) { + e.preventDefault(); + } + + if (!targets) return; + + setLoading(true); + try { + await dispatch(applyIcon(FileManagerIndex.main, targets, icon)); + } catch (e) { + } finally { + setLoading(false); + dispatch(closeChangeIconDialog()); + } + }, + [dispatch, targets, setLoading], + ); + + return ( + + {t("application:modals.resetToDefault")} + + } + > + + + + + + {emojiStrLoaded ? ( + Object.keys(emojiSetting).map((key) => ) + ) : ( + } /> + )} + + + + {emojiStrLoaded ? ( + Object.keys(emojiSetting).map((key, index) => ( + + + {emojiSetting[key].map((emoji) => ( + {emoji} + ))} + + + )) + ) : ( + + + {[...Array(50).keys()].map(() => ( + + + + ))} + + + )} + + + + + + ); +}; +export default ChangeIcon; diff --git a/src/component/FileManager/Dialogs/CreateArchive.tsx b/src/component/FileManager/Dialogs/CreateArchive.tsx new file mode 100755 index 0000000..6227ea0 --- /dev/null +++ b/src/component/FileManager/Dialogs/CreateArchive.tsx @@ -0,0 +1,102 @@ +import { DialogContent, Stack, useMediaQuery, useTheme } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { sendCreateArchive } from "../../../api/api.ts"; +import { closeCreateArchiveDialog } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { getFileLinkedUri } from "../../../util"; +import CrUri from "../../../util/uri.ts"; +import { OutlineIconTextField } from "../../Common/Form/OutlineIconTextField.tsx"; +import { PathSelectorForm } from "../../Common/Form/PathSelectorForm.tsx"; +import { ViewTaskAction } from "../../Common/Snackbar/snackbar.tsx"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; +import Archive from "../../Icons/Archive.tsx"; +import { FileManagerIndex } from "../FileManager.tsx"; + +const CreateArchive = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const [loading, setLoading] = useState(false); + const [fileName, setFileName] = useState("archive.zip"); + const [path, setPath] = useState(""); + + const open = useAppSelector((state) => state.globalState.createArchiveDialogOpen); + const targets = useAppSelector((state) => state.globalState.createArchiveDialogFiles); + const current = useAppSelector((state) => state.fileManager[FileManagerIndex.main].pure_path); + + useEffect(() => { + if (open) { + setPath(current ?? ""); + } + }, [open]); + + const onClose = useCallback(() => { + dispatch(closeCreateArchiveDialog()); + }, [dispatch]); + + const onAccept = useCallback(() => { + if (!targets) { + return; + } + + setLoading(true); + const dst = new CrUri(path); + dispatch( + sendCreateArchive({ + src: targets?.map((t) => getFileLinkedUri(t)), + dst: dst.join(fileName).toString(), + }), + ) + .then(() => { + dispatch(closeCreateArchiveDialog()); + enqueueSnackbar({ + message: t("modals.taskCreated"), + variant: "success", + action: ViewTaskAction(), + }); + }) + .finally(() => { + setLoading(false); + }); + }, [targets, fileName, path]); + + return ( + + + + } + variant="outlined" + value={fileName} + onChange={(e) => setFileName(e.target.value)} + label={t("application:modals.zipFileName")} + fullWidth + /> + + + + + + + ); +}; +export default CreateArchive; diff --git a/src/component/FileManager/Dialogs/CreateNew.tsx b/src/component/FileManager/Dialogs/CreateNew.tsx new file mode 100755 index 0000000..87e1b8b --- /dev/null +++ b/src/component/FileManager/Dialogs/CreateNew.tsx @@ -0,0 +1,133 @@ +import { useTranslation } from "react-i18next"; +import { DialogContent, Stack } from "@mui/material"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { ChangeEvent, useCallback, useEffect, useRef, useState } from "react"; +import { setRenameFileModalError } from "../../../redux/fileManagerSlice.ts"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; +import { createNewDialogPromisePool } from "../../../redux/thunks/dialog.ts"; +import { FilledTextField } from "../../Common/StyledComponents.tsx"; +import { closeCreateNewDialog, CreateNewDialogType } from "../../../redux/globalStateSlice.ts"; +import { submitCreateNew } from "../../../redux/thunks/file.ts"; +import { FileType } from "../../../api/explorer.ts"; + +const CreateNew = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const [name, setName] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + const formRef = useRef(null); + const inputRef = useRef(null); + + const open = useAppSelector((state) => state.globalState.createNewDialogOpen); + const promiseId = useAppSelector((state) => state.globalState.createNewPromiseId); + const type = useAppSelector((state) => state.globalState.createNewDialogType); + const defaultName = useAppSelector((state) => state.globalState.createNewDialogDefault); + const fmIndex = useAppSelector((state) => state.globalState.createNewDialogFmIndex) ?? 0; + + useEffect(() => { + if (open) { + setName(defaultName ?? ""); + } + }, [open]); + + const onClose = useCallback(() => { + dispatch(closeCreateNewDialog()); + if (promiseId) { + createNewDialogPromisePool[promiseId]?.reject("cancel"); + } + }, [dispatch, promiseId]); + + const onAccept = useCallback( + (e?: React.FormEvent) => { + if (e) { + e.preventDefault(); + } + + setLoading(true); + dispatch(submitCreateNew(fmIndex, name, type == CreateNewDialogType.folder ? FileType.folder : FileType.file)) + .then((f) => { + if (promiseId) { + createNewDialogPromisePool[promiseId]?.resolve(f); + } + dispatch(closeCreateNewDialog()); + }) + .finally(() => { + setLoading(false); + }); + }, + [promiseId, name], + ); + + const onOkClicked = useCallback(() => { + if (formRef.current) { + if (formRef.current.reportValidity()) { + onAccept(); + } + } + }, [formRef, onAccept]); + + useEffect(() => { + if (open) { + const lastDot = name.lastIndexOf("."); + setTimeout( + () => inputRef.current && inputRef.current.setSelectionRange(0, lastDot > 0 ? lastDot : name.length), + 200, + ); + } + }, [open, inputRef.current]); + + const onNameChange = useCallback( + (e: ChangeEvent) => { + setName(e.target.value); + if (error) { + dispatch(setRenameFileModalError({ index: 0, value: undefined })); + } + }, + [dispatch, setName, error], + ); + + return ( + + + +
    + + +
    +
    +
    + ); +}; +export default CreateNew; diff --git a/src/component/FileManager/Dialogs/CreateRemoteDownload.tsx b/src/component/FileManager/Dialogs/CreateRemoteDownload.tsx new file mode 100755 index 0000000..a8a9c10 --- /dev/null +++ b/src/component/FileManager/Dialogs/CreateRemoteDownload.tsx @@ -0,0 +1,119 @@ +import { DialogContent, Stack, useMediaQuery, useTheme } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { sendCreateRemoteDownload } from "../../../api/api.ts"; +import { defaultPath } from "../../../hooks/useNavigation.tsx"; +import { closeRemoteDownloadDialog } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { getFileLinkedUri } from "../../../util"; +import CrUri, { Filesystem } from "../../../util/uri.ts"; +import { FileDisplayForm } from "../../Common/Form/FileDisplayForm.tsx"; +import { OutlineIconTextField } from "../../Common/Form/OutlineIconTextField.tsx"; +import { PathSelectorForm } from "../../Common/Form/PathSelectorForm.tsx"; +import { ViewTaskAction } from "../../Common/Snackbar/snackbar.tsx"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; +import Link from "../../Icons/Link.tsx"; +import { FileManagerIndex } from "../FileManager.tsx"; + +const CreateRemoteDownload = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const [loading, setLoading] = useState(false); + const [path, setPath] = useState(""); + const [url, setUrl] = useState(""); + + const open = useAppSelector((state) => state.globalState.remoteDownloadDialogOpen); + const target = useAppSelector((state) => state.globalState.remoteDownloadDialogFile); + const current = useAppSelector((state) => state.fileManager[FileManagerIndex.main].pure_path); + + useEffect(() => { + if (open) { + const initialPath = new CrUri(current ?? defaultPath); + const fs = initialPath.fs(); + setPath(fs == Filesystem.shared_with_me || fs == Filesystem.trash ? defaultPath : initialPath.toString()); + setUrl(""); + } + }, [open]); + + const onClose = useCallback(() => { + dispatch(closeRemoteDownloadDialog()); + }, [dispatch]); + + const onAccept = useCallback(() => { + if (!target && !url) { + return; + } + + setLoading(true); + dispatch( + sendCreateRemoteDownload({ + src_file: target ? getFileLinkedUri(target) : undefined, + dst: path, + src: url ? url.split("\n") : undefined, + }), + ) + .then(() => { + dispatch(closeRemoteDownloadDialog()); + enqueueSnackbar({ + message: t("modals.taskCreated"), + variant: "success", + action: ViewTaskAction("/downloads"), + }); + }) + .finally(() => { + setLoading(false); + }); + }, [target, url, path]); + + return ( + + + + + {target && } + {!target && ( + } + variant="outlined" + value={url} + multiline + onChange={(e) => setUrl(e.target.value)} + placeholder={t("modals.remoteDownloadURLDescription")} + label={t("application:modals.remoteDownloadURL")} + fullWidth + /> + )} + + + + + + + + ); +}; +export default CreateRemoteDownload; diff --git a/src/component/FileManager/Dialogs/DeleteConfirmation.tsx b/src/component/FileManager/Dialogs/DeleteConfirmation.tsx new file mode 100755 index 0000000..c00d229 --- /dev/null +++ b/src/component/FileManager/Dialogs/DeleteConfirmation.tsx @@ -0,0 +1,156 @@ +import { Alert, Checkbox, Collapse, DialogContent, FormGroup, Stack, Tooltip } from "@mui/material"; +import dayjs from "dayjs"; +import duration from "dayjs/plugin/duration"; +import { useCallback, useMemo, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Metadata } from "../../../api/explorer.ts"; +import { GroupPermission } from "../../../api/user.ts"; +import { setFileDeleteModal } from "../../../redux/fileManagerSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { deleteDialogPromisePool } from "../../../redux/thunks/dialog.ts"; +import SessionManager from "../../../session"; +import { formatDuration } from "../../../util/datetime.ts"; +import { SmallFormControlLabel } from "../../Common/StyledComponents.tsx"; +import DialogAccordion from "../../Dialogs/DialogAccordion.tsx"; +import DraggableDialog, { StyledDialogContentText } from "../../Dialogs/DraggableDialog.tsx"; + +dayjs.extend(duration); + +export interface DeleteOption { + unlink?: boolean; + skip_soft_delete?: boolean; +} +const DeleteConfirmation = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const [unlink, setUnlink] = useState(false); + const [skipSoftDelete, setSkipSoftDelete] = useState(false); + + const open = useAppSelector((state) => state.fileManager[0].deleteFileModalOpen); + const targets = useAppSelector((state) => state.fileManager[0].deleteFileModalSelected); + const promiseId = useAppSelector((state) => state.fileManager[0].deleteFileModalPromiseId); + const loading = useAppSelector((state) => state.fileManager[0].deleteFileModalLoading); + + const hasTrashFiles = useMemo(() => { + if (targets) { + return targets.some((target) => target.metadata && target.metadata[Metadata.restore_uri]); + } + + return false; + }, [targets]); + + const onClose = useCallback(() => { + dispatch( + setFileDeleteModal({ + index: 0, + value: [false, targets, undefined, false], + }), + ); + if (promiseId) { + deleteDialogPromisePool[promiseId]?.reject("cancel"); + } + }, [dispatch, targets, promiseId]); + + const singleFileToTrash = targets && targets.length == 1 && !hasTrashFiles && !skipSoftDelete; + const multipleFilesToTrash = targets && targets.length > 1 && !hasTrashFiles && !skipSoftDelete; + const singleFilePermanently = targets && targets.length == 1 && (hasTrashFiles || skipSoftDelete); + const multipleFilesPermanently = targets && targets.length > 1 && (hasTrashFiles || skipSoftDelete); + + const onAccept = useCallback(() => { + if (promiseId) { + deleteDialogPromisePool[promiseId]?.resolve({ + unlink, + skip_soft_delete: singleFilePermanently || multipleFilesPermanently ? true : skipSoftDelete, + }); + } + }, [promiseId, unlink, skipSoftDelete, singleFilePermanently, multipleFilesPermanently]); + + const permission = SessionManager.currentUserGroupPermission(); + const showSkipSoftDeleteOption = !hasTrashFiles; + const showUnlinkOption = (skipSoftDelete || hasTrashFiles) && permission.enabled(GroupPermission.advance_delete); + const showAdvanceOptions = showUnlinkOption || showSkipSoftDeleteOption; + + const group = useMemo(() => SessionManager.currentUserGroup(), [open]); + + return ( + + + + + {(singleFileToTrash || singleFilePermanently) && ( + ]} + /> + )} + {(multipleFilesToTrash || multipleFilesPermanently) && + t( + multipleFilesToTrash + ? "application:modals.deleteMultipleDescription" + : "application:modals.deleteMultipleDescriptionHard", + { + num: targets.length, + }, + )} + + + ]} + /> + + + + {showAdvanceOptions && ( + + + + + setSkipSoftDelete(e.target.checked)} + checked={skipSoftDelete} + /> + } + label={t("application:modals.skipSoftDelete")} + /> + + + + + setUnlink(e.target.checked)} checked={unlink} />} + label={t("application:modals.unlinkOnly")} + /> + + + + + )} + + + + ); +}; +export default DeleteConfirmation; diff --git a/src/component/FileManager/Dialogs/Dialogs.tsx b/src/component/FileManager/Dialogs/Dialogs.tsx new file mode 100755 index 0000000..a836765 --- /dev/null +++ b/src/component/FileManager/Dialogs/Dialogs.tsx @@ -0,0 +1,85 @@ +import DeleteConfirmation from "./DeleteConfirmation.tsx"; +import AggregatedErrorDetail from "../../Dialogs/AggregatedErrorDetail.tsx"; +import LockConflictDetails from "./LockConflictDetails.tsx"; +import Rename from "./Rename.tsx"; +import PathSelection from "./PathSelection.tsx"; +import Tags from "./Tags.tsx"; +import ChangeIcon from "./ChangeIcon.tsx"; +import ShareDialog from "./Share/ShareDialog.tsx"; +import VersionControl from "./VersionControl.tsx"; +import ManageShares from "./Share/ManageShares.tsx"; +import StaleVersionConfirm from "./StaleVersionConfirm.tsx"; +import SaveAs from "./SaveAs.tsx"; +import Photopea from "../../Viewers/Photopea/Photopea.tsx"; +import OpenWith from "./OpenWith.tsx"; +import Wopi from "../../Viewers/Wopi.tsx"; +import ArchivePreview from "../../Viewers/ArchivePreview/ArchivePreview.tsx"; +import CodeViewer from "../../Viewers/CodeViewer/CodeViewer.tsx"; +import DrawIOViewer from "../../Viewers/DrawIO/DrawIOViewer.tsx"; +import MarkdownViewer from "../../Viewers/MarkdownEditor/MarkdownViewer.tsx"; +import VideoViewer from "../../Viewers/Video/VideoViewer.tsx"; +import PdfViewer from "../../Viewers/PdfViewer.tsx"; +import CustomViewer from "../../Viewers/CustomViewer.tsx"; +import EpubViewer from "../../Viewers/EpubViewer/EpubViewer.tsx"; +import ExcalidrawViewer from "../../Viewers/Excalidraw/ExcalidrawViewer.tsx"; +import CreateNew from "./CreateNew.tsx"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import CreateArchive from "./CreateArchive.tsx"; +import ExtractArchive from "./ExtractArchive.tsx"; +import CreateRemoteDownload from "./CreateRemoteDownload.tsx"; +import AdvanceSearch from "../Search/AdvanceSearch/AdvanceSearch.tsx"; +import React from "react"; +import ColumnSetting from "../Explorer/ListView/ColumnSetting.tsx"; +import DirectLinks from "./DirectLinks.tsx"; +import DirectLinksControl from "./DirectLinksControl.tsx"; + +const Dialogs = () => { + const showCreateArchive = useAppSelector((state) => state.globalState.createArchiveDialogOpen); + const showExtractArchive = useAppSelector((state) => state.globalState.extractArchiveDialogOpen); + const showRemoteDownload = useAppSelector((state) => state.globalState.remoteDownloadDialogOpen); + const showAdvancedSearch = useAppSelector((state) => state.globalState.advanceSearchOpen); + const showListViewColumnSetting = useAppSelector((state) => state.globalState.listViewColumnSettingDialogOpen); + const directLink = useAppSelector((state) => state.globalState.directLinkDialogOpen); + const excalidrawViewer = useAppSelector((state) => state.globalState.excalidrawViewer); + const directLinkManagement = useAppSelector((state) => state.globalState.directLinkManagementDialogOpen); + const archivePreview = useAppSelector((state) => state.globalState.archiveViewer); + + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + {showCreateArchive != undefined && } + {showExtractArchive != undefined && } + {showRemoteDownload != undefined && } + {showAdvancedSearch != undefined && } + {showListViewColumnSetting != undefined && } + {directLink != undefined && } + {excalidrawViewer != undefined && } + {directLinkManagement != undefined && } + {archivePreview != undefined && } + + ); +}; + +export default Dialogs; diff --git a/src/component/FileManager/Dialogs/DirectLinks.tsx b/src/component/FileManager/Dialogs/DirectLinks.tsx new file mode 100755 index 0000000..e841991 --- /dev/null +++ b/src/component/FileManager/Dialogs/DirectLinks.tsx @@ -0,0 +1,128 @@ +import { DialogContent, FormControlLabel, Stack, TextField, useMediaQuery, useTheme } from "@mui/material"; +import { useCallback, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { closeDirectLinkDialog } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import CrUri from "../../../util/uri.ts"; +import { StyledCheckbox } from "../../Common/StyledComponents.tsx"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; + +const DirectLinks = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + + const [showFileName, setShowFileName] = useState(false); + const [forceDownload, setForceDownload] = useState(false); + + const open = useAppSelector((state) => state.globalState.directLinkDialogOpen); + const targets = useAppSelector((state) => state.globalState.directLinkRes); + + const contents = useMemo(() => { + if (!targets) { + return ""; + } + + return targets + .map((link) => { + let finalLink = link.link; + + if (forceDownload) { + finalLink = finalLink.replace("/f/", "/f/d/"); + } + + if (!showFileName) { + return finalLink; + } + + const crUri = new CrUri(link.file_url); + const elements = crUri.elements(); + return `[${elements.pop()}] ${finalLink}`; + }) + .join("\n"); + }, [targets, showFileName, forceDownload]); + + const onClose = useCallback(() => { + dispatch(closeDirectLinkDialog()); + }, [dispatch]); + + return ( + + { + setShowFileName(!showFileName); + }} + disableRipple + checked={showFileName} + size="small" + /> + } + label={t("application:modals.showFileName")} + /> + { + setForceDownload(!forceDownload); + }} + disableRipple + checked={forceDownload} + size="small" + /> + } + label={t("application:modals.forceDownload")} + /> + + } + dialogProps={{ + open: open ?? false, + onClose: onClose, + fullWidth: true, + maxWidth: "sm", + }} + > + + + + + ); +}; +export default DirectLinks; diff --git a/src/component/FileManager/Dialogs/DirectLinksControl.tsx b/src/component/FileManager/Dialogs/DirectLinksControl.tsx new file mode 100755 index 0000000..6ced5ee --- /dev/null +++ b/src/component/FileManager/Dialogs/DirectLinksControl.tsx @@ -0,0 +1,254 @@ +import { + Alert, + Box, + DialogContent, + FormControlLabel, + IconButton, + Link, + Skeleton, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getFileInfo, sendDeleteDirectLink } from "../../../api/api.ts"; +import { DirectLink, FileResponse } from "../../../api/explorer.ts"; +import { closeDirectLinkManagementDialog } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { confirmOperation } from "../../../redux/thunks/dialog.ts"; +import { copyToClipboard } from "../../../util/index.ts"; +import AutoHeight from "../../Common/AutoHeight.tsx"; +import { NoWrapTableCell, StyledCheckbox, StyledTableContainerPaper } from "../../Common/StyledComponents.tsx"; +import TimeBadge from "../../Common/TimeBadge.tsx"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; +import CopyOutlined from "../../Icons/CopyOutlined.tsx"; +import DeleteOutlined from "../../Icons/DeleteOutlined.tsx"; + +const DirectLinksControl = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const [fileExtended, setFileExtended] = useState(undefined); + const [loading, setLoading] = useState(false); + const [forceDownload, setForceDownload] = useState(false); + + const open = useAppSelector((state) => state.globalState.directLinkManagementDialogOpen); + const target = useAppSelector((state) => state.globalState.directLinkManagementDialogFile); + const highlight = useAppSelector((state) => state.globalState.directLinkHighlight); + + const hilightButNotFound = useMemo(() => { + return ( + highlight && + fileExtended?.extended_info && + !fileExtended?.extended_info?.direct_links?.some((link) => link.id == highlight) + ); + }, [highlight, fileExtended?.extended_info?.direct_links]); + + const onClose = useCallback(() => { + if (!loading) { + dispatch(closeDirectLinkManagementDialog()); + } + }, [dispatch, loading]); + + useEffect(() => { + if (target && open) { + setFileExtended(undefined); + dispatch( + getFileInfo({ + uri: target.path, + extended: true, + }), + ).then((res) => setFileExtended(res)); + } + }, [target, open]); + + const directLinks = useMemo(() => { + return fileExtended?.extended_info?.direct_links?.map((link) => { + return { + ...link, + url: forceDownload ? link.url.replace("/f/", "/f/d/") : link.url, + }; + }); + }, [fileExtended?.extended_info?.direct_links, forceDownload]); + + const handleRowClick = useCallback((directLink: DirectLink) => { + window.open(directLink.url, "_blank"); + }, []); + + const copyURL = useCallback((actionTarget: DirectLink) => { + if (!actionTarget) { + return; + } + + copyToClipboard(actionTarget.url); + }, []); + + const deleteDirectLink = useCallback( + (actionTarget: DirectLink) => { + if (!target || !actionTarget) { + return; + } + + dispatch(confirmOperation(t("fileManager.deleteLinkConfirm"))).then(() => { + setLoading(true); + dispatch(sendDeleteDirectLink(actionTarget.id)) + .then(() => { + setFileExtended((prev) => + prev + ? { + ...prev, + extended_info: prev.extended_info + ? { + ...prev.extended_info, + direct_links: prev.extended_info.direct_links?.filter((link) => link.id !== actionTarget.id), + } + : undefined, + } + : undefined, + ); + }) + .finally(() => { + setLoading(false); + }); + }); + }, + [t, target, dispatch], + ); + + return ( + + + + {hilightButNotFound && ( + + {t("application:fileManager.directLinkNotFound")} + + )} + + + + + {t("fileManager.actions")} + {t("modals.sourceLink")} + {t("setting.viewNumber")} + {t("fileManager.createdAt")} + + + + {!fileExtended && ( + + + + + + + + + + + + + + + )} + {directLinks && + directLinks.map((link) => ( + + highlight == link.id ? `inset 0 0 0 2px ${theme.palette.primary.light}` : "none", + "&:last-child td, &:last-child th": { border: 0 }, + }} + > + + copyURL(link)} size={"small"}> + + + deleteDirectLink(link)} size={"small"}> + + + + event.stopPropagation()} + > + + + {link.url} + + + + {link.downloaded} + + + + + ))} + +
    + {!directLinks && fileExtended && ( + + + {t("application:setting.listEmpty")} + + + )} +
    +
    + { + setForceDownload(!forceDownload); + }} + disableRipple + checked={forceDownload} + size="small" + /> + } + label={t("application:modals.forceDownload")} + /> +
    +
    + ); +}; + +export default DirectLinksControl; diff --git a/src/component/FileManager/Dialogs/ExtractArchive.tsx b/src/component/FileManager/Dialogs/ExtractArchive.tsx new file mode 100755 index 0000000..286c1ba --- /dev/null +++ b/src/component/FileManager/Dialogs/ExtractArchive.tsx @@ -0,0 +1,167 @@ +import { DialogContent, Grid2, InputAdornment, TextField, useMediaQuery, useTheme } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { sendExtractArchive } from "../../../api/api.ts"; +import { closeExtractArchiveDialog } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { fileExtension, getFileLinkedUri } from "../../../util"; +import EncodingSelector, { defaultEncodingValue } from "../../Common/Form/EncodingSelector.tsx"; +import { FileDisplayForm } from "../../Common/Form/FileDisplayForm.tsx"; +import { PathSelectorForm } from "../../Common/Form/PathSelectorForm.tsx"; +import { ViewTaskAction } from "../../Common/Snackbar/snackbar.tsx"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; +import Password from "../../Icons/Password.tsx"; +import { FileManagerIndex } from "../FileManager.tsx"; + +const ExtractArchive = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const [loading, setLoading] = useState(false); + const [path, setPath] = useState(""); + const [encoding, setEncoding] = useState(defaultEncodingValue); + const [password, setPassword] = useState(""); + const [showPassword, setShowPassword] = useState(false); + + const open = useAppSelector((state) => state.globalState.extractArchiveDialogOpen); + const target = useAppSelector((state) => state.globalState.extractArchiveDialogFile); + const current = useAppSelector((state) => state.fileManager[FileManagerIndex.main].pure_path); + const mask = useAppSelector((state) => state.globalState.extractArchiveDialogMask); + const predefinedEncoding = useAppSelector((state) => state.globalState.extractArchiveDialogEncoding); + + useEffect(() => { + setEncoding(predefinedEncoding ?? defaultEncodingValue); + }, [predefinedEncoding]); + + const showEncodingOption = useMemo(() => { + const ext = fileExtension(target?.name ?? ""); + return ext === "zip"; + }, [target?.name]); + + const showPasswordOption = useMemo(() => { + const ext = fileExtension(target?.name ?? ""); + return ext === "zip" || ext === "7z"; + }, [target?.name]); + + useEffect(() => { + if (open) { + setPath(current ?? ""); + } + }, [open]); + + const onClose = useCallback(() => { + dispatch(closeExtractArchiveDialog()); + }, [dispatch]); + + const onAccept = useCallback(() => { + if (!target) { + return; + } + + setLoading(true); + dispatch( + sendExtractArchive({ + src: [getFileLinkedUri(target)], + dst: path, + encoding: showEncodingOption && encoding != defaultEncodingValue ? encoding : undefined, + password: showPasswordOption && password ? password : undefined, + file_mask: mask ?? undefined, + }), + ) + .then(() => { + dispatch(closeExtractArchiveDialog()); + enqueueSnackbar({ + message: t("modals.taskCreated"), + variant: "success", + action: ViewTaskAction(), + }); + }) + .finally(() => { + setLoading(false); + }); + }, [target, encoding, path, showPasswordOption, showEncodingOption, password, mask]); + + return ( + + + + {target && ( + + + + )} + {showEncodingOption && ( + + + + )} + + + + {showPasswordOption && ( + + + + + ), + }, + }} + fullWidth + placeholder={t("application:modals.passwordDescription")} + label={t("modals.password")} + value={password} + onChange={(e) => setPassword(e.target.value)} + /> + + )} + + + + ); +}; +export default ExtractArchive; diff --git a/src/component/FileManager/Dialogs/LockConflictDetails.tsx b/src/component/FileManager/Dialogs/LockConflictDetails.tsx new file mode 100755 index 0000000..1f9c07a --- /dev/null +++ b/src/component/FileManager/Dialogs/LockConflictDetails.tsx @@ -0,0 +1,234 @@ +import { + Box, + Button, + DialogContent, + Stack, + styled, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Tooltip, + Typography, +} from "@mui/material"; +import { useCallback, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { ConflictDetail, FileResponse, LockApplication } from "../../../api/explorer.ts"; +import { closeLockConflictDialog } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { ViewersByID } from "../../../redux/siteConfigSlice.ts"; +import { generalDialogPromisePool } from "../../../redux/thunks/dialog.ts"; +import { forceUnlockFiles } from "../../../redux/thunks/file.ts"; +import { NoWrapTableCell, StyledTableContainerPaper } from "../../Common/StyledComponents.tsx"; +import DraggableDialog, { StyledDialogActions, StyledDialogContentText } from "../../Dialogs/DraggableDialog.tsx"; +import FileBadge from "../FileBadge.tsx"; +import { ViewerIcon } from "./OpenWith.tsx"; + +interface ErrorTableProps { + data: ConflictDetail[]; + loading?: boolean; + files: { + [key: string]: FileResponse; + }; + unlock: (tokens: string[]) => Promise; +} + +export const CellHeaderWithPadding = styled(Box)({ + paddingLeft: "8px", +}); + +const ErrorTable = (props: ErrorTableProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + return ( + + + + + + {t("common:object")} + + {t("application:modals.application")} + + {t("application:setting.action")} + + + + + {props.data.map((conflict, i) => ( + + + {conflict.path && ( + + )} + {!conflict.path && } + + + {conflict.owner?.application && } + + + + + + + + + + ))} + +
    + {(!props.data || props.data.length === 0) && ( + + + {t("application:setting.listEmpty")} + + + )} +
    + ); +}; + +interface ApplicationProps { + app: LockApplication; +} + +const ApplicationNameMap: { + [key: string]: string; +} = { + rename: "application:fileManager.rename", + moveCopy: "application:modals.moveCopy", + upload: "application:modals.upload", + updateMetadata: "application:modals.updateMetadata", + delete: "application:fileManager.delete", + softDelete: "application:fileManager.delete", + dav: "application:modals.webdav", + versionControl: "fileManager.manageVersions", +}; + +const viewerType = "viewer"; + +const Application = ({ app }: ApplicationProps) => { + const { t } = useTranslation(); + const title = ApplicationNameMap[app.type] ?? app.type; + if (app.type == "viewer" && ViewersByID[app.viewer_id ?? ""]) { + const viewer = ViewersByID[app.viewer_id ?? ""]; + if (viewer) { + return ( + + + + + {viewer?.display_name} + + ); + } + } + return {t(title)}; +}; + +const LockConflictDetails = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const open = useAppSelector((state) => state.globalState.lockConflictDialogOpen); + const files = useAppSelector((state) => state.globalState.lockConflictFile); + const error = useAppSelector((state) => state.globalState.lockConflictError); + const promiseId = useAppSelector((state) => state.globalState.lockConflictPromiseId); + + const [loading, setLoading] = useState(false); + + const onClose = useCallback(() => { + dispatch(closeLockConflictDialog()); + if (promiseId) { + generalDialogPromisePool[promiseId]?.reject("cancel"); + } + }, [dispatch, promiseId]); + + const onRetry = useCallback(() => { + if (promiseId) { + dispatch(closeLockConflictDialog()); + generalDialogPromisePool[promiseId]?.resolve(); + } + }, [promiseId]); + + const showUnlockAll = useMemo(() => { + if (error && error.data) { + for (const conflict of error.data) { + if (conflict.token) { + return true; + } + } + } + return false; + }, [error]); + + const forceUnlockByToken = useCallback( + async (tokens: string[]) => { + setLoading(true); + try { + await dispatch(forceUnlockFiles(tokens)); + } finally { + setLoading(false); + } + }, + [dispatch, setLoading], + ); + + const unlockAll = useCallback(async () => { + const tokens = error?.data?.filter((c) => c.token).map((c) => c.token ?? ""); + if (tokens) { + await forceUnlockByToken(tokens); + } + }, [forceUnlockByToken, error]); + + return ( + + + + {t("application:modals.lockConflictDescription")} + {files && error && error.data && ( + + )} + {showUnlockAll && ( + + + + )} + + + + + + + + ); +}; + +export default LockConflictDetails; diff --git a/src/component/FileManager/Dialogs/OpenWith.tsx b/src/component/FileManager/Dialogs/OpenWith.tsx new file mode 100755 index 0000000..add4c67 --- /dev/null +++ b/src/component/FileManager/Dialogs/OpenWith.tsx @@ -0,0 +1,231 @@ +import { + Avatar, + Box, + DialogContent, + Divider, + Grid, + List, + ListItem, + ListItemAvatar, + ListItemButton, + ListItemText, + Stack, +} from "@mui/material"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Viewer, ViewerType } from "../../../api/explorer.ts"; +import { closeViewerSelector } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { ViewersByID } from "../../../redux/siteConfigSlice.ts"; +import { builtInViewers, openViewer } from "../../../redux/thunks/viewer.ts"; +import SessionManager, { UserSettings } from "../../../session"; +import { fileExtension } from "../../../util"; +import AutoHeight from "../../Common/AutoHeight.tsx"; +import { SecondaryButton } from "../../Common/StyledComponents.tsx"; +import DraggableDialog, { StyledDialogContentText } from "../../Dialogs/DraggableDialog.tsx"; +import Book from "../../Icons/Book.tsx"; +import DocumentPDF from "../../Icons/DocumentPDF.tsx"; +import FolderZip from "../../Icons/FolderZip.tsx"; +import Image from "../../Icons/Image.tsx"; +import Markdown from "../../Icons/Markdown.tsx"; +import MoreHorizontal from "../../Icons/MoreHorizontal.tsx"; +import MusicNote1 from "../../Icons/MusicNote1.tsx"; + +export interface ViewerIconProps { + viewer: Viewer; + size?: number; + py?: number; +} + +const emptyViewer: Viewer[] = []; + +export const ViewerIDWithDefaultIcons = [ + builtInViewers.image, + builtInViewers.pdf, + builtInViewers.epub, + builtInViewers.music, + builtInViewers.markdown, + builtInViewers.archive, +]; + +export const ViewerIcon = ({ viewer, size = 32, py = 0.5 }: ViewerIconProps) => { + const BuiltinIcons = useMemo(() => { + if (viewer.icon) { + return undefined; + } + + if (viewer.type == ViewerType.builtin) { + switch (viewer.id) { + case builtInViewers.image: + return ; + case builtInViewers.pdf: + return ; + case builtInViewers.epub: + return ; + case builtInViewers.music: + return ; + case builtInViewers.archive: + return ; + case builtInViewers.markdown: + return ( + (theme.palette.mode == "dark" ? "#cbcbcb" : "#383838"), + }} + /> + ); + } + } + }, [viewer]); + return ( + + {BuiltinIcons && BuiltinIcons} + {viewer.icon && ( + + )} + + ); +}; + +const OpenWith = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const [selectedViewer, setSelectedViewer] = React.useState(null); + const [expanded, setExpanded] = useState(false); + + const selectorState = useAppSelector((state) => state.globalState.viewerSelector); + + useEffect(() => { + if (selectorState?.open) { + setExpanded(!selectorState.viewers); + setSelectedViewer(null); + } + }, [selectorState]); + + const ext = useMemo(() => { + if (!selectorState?.file) { + return ""; + } + + return fileExtension(selectorState.file.name) ?? ""; + }, [selectorState?.file]); + + const onClose = useCallback(() => { + dispatch(closeViewerSelector()); + }, [dispatch]); + + const openWith = (always: boolean, viewer?: Viewer) => { + if (!selectorState || (!selectedViewer && !viewer)) { + return; + } + + if (always) { + SessionManager.set(UserSettings.OpenWithPrefix + ext, viewer?.id ?? selectedViewer?.id); + } + + dispatch( + openViewer( + selectorState.file, + viewer ?? (selectedViewer as Viewer), + selectorState.entitySize, + selectorState.version, + ), + ); + dispatch(closeViewerSelector()); + }; + + const onViewerClick = (viewer: Viewer) => { + if (selectorState?.viewers) { + setSelectedViewer(viewer); + } else { + // For files without matching viewers, open the selected viewer without asking for preference + openWith(false, viewer); + } + }; + + return ( + + + + + + {t("fileManager.openWithDescription", { + ext, + })} + + + + {((expanded ? Object.values(ViewersByID) : selectorState?.viewers) ?? emptyViewer).map((viewer) => ( + openWith(false, viewer)} + onClick={() => onViewerClick(viewer)} + > + + + + + + + + ))} + {!expanded && ( + setExpanded(true)} disablePadding> + + + + + + + + + + )} + + + {!!selectedViewer && ( + <> + + + + openWith(true)}> + {t("modals.always")} + + + + openWith(false)}> + {t("modals.justOnce")} + + + + + )} + + + ); +}; +export default OpenWith; diff --git a/src/component/FileManager/Dialogs/PathSelection.tsx b/src/component/FileManager/Dialogs/PathSelection.tsx new file mode 100755 index 0000000..00f72ba --- /dev/null +++ b/src/component/FileManager/Dialogs/PathSelection.tsx @@ -0,0 +1,198 @@ +import { DialogContent, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { useCallback, useMemo, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { FileResponse, FileType } from "../../../api/explorer.ts"; +import { closePathSelectionDialog } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { pathSelectionDialogPromisePool } from "../../../redux/thunks/dialog.ts"; +import CrUri, { Filesystem } from "../../../util/uri.ts"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; +import FileBadge from "../FileBadge.tsx"; +import FolderPicker, { useFolderSelector } from "../FolderPicker.tsx"; + +export const PathSelectionVariantOptions = { + copy: "copy", + move: "move", + shortcut: "shortcut", +}; + +interface SelectedFolderIndicatorProps { + selectedFile?: FileResponse; + selectedPath?: string; + variant: PathSelectionVariant; +} + +interface PathSelectionVariant { + indicator: string; + title: string; + disableSharedWithMe?: boolean; + disableTrash?: boolean; +} + +export const PathSelectionVariants: Record = { + copy: { + indicator: "fileManager.copyToDst", + title: "application:fileManager.copyTo", + disableSharedWithMe: true, + disableTrash: true, + }, + move: { + indicator: "fileManager.moveToDst", + title: "application:fileManager.moveTo", + disableSharedWithMe: true, + disableTrash: true, + }, + shortcut: { + indicator: "application:modals.createShortcutTo", + title: "application:modals.createShortcut", + disableSharedWithMe: true, + disableTrash: true, + }, + saveAs: { + indicator: "application:modals.saveToTitleDescription", + title: "application:modals.saveAs", + disableSharedWithMe: true, + disableTrash: true, + }, + saveTo: { + indicator: "application:modals.saveToTitleDescription", + title: "application:modals.saveToTitle", + disableSharedWithMe: true, + disableTrash: true, + }, + extractTo: { + indicator: "application:modals.decompressToDst", + title: "application:modals.decompressTo", + disableSharedWithMe: true, + disableTrash: true, + }, + downloadTo: { + indicator: "application:modals.downloadToDst", + title: "application:modals.downloadTo", + disableSharedWithMe: true, + disableTrash: true, + }, + searchIn: { + indicator: "application:navbar.searchInBase", + title: "application:navbar.searchBase", + }, + davAccountRoot: { + indicator: "application:setting.rootFolderIn", + title: "application:setting.rootFolder", + disableSharedWithMe: true, + disableTrash: true, + }, +}; + +export const SelectedFolderIndicator = ({ selectedFile, selectedPath, variant }: SelectedFolderIndicatorProps) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + if (!selectedFile && !selectedPath) { + return null; + } + + const badge = ( + + ); + return ( + + {isMobile ? badge : } + + ); +}; + +const PathSelection = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const [loading, setLoading] = useState(false); + + const open = useAppSelector((state) => state.globalState.pathSelectDialogOpen); + const variant = useAppSelector((state) => state.globalState.pathSelectDialogVariant); + const promiseId = useAppSelector((state) => state.globalState.pathSelectPromiseId); + const initialPath = useAppSelector((state) => state.globalState.pathSelectInitialPath); + + const variantProps = useMemo( + () => (variant ? PathSelectionVariants[variant] : PathSelectionVariants["copy"]), + [variant], + ); + + const [selectedFile, selectedPath] = useFolderSelector(); + + const onClose = useCallback(() => { + dispatch(closePathSelectionDialog()); + if (promiseId) { + pathSelectionDialogPromisePool[promiseId]?.reject("cancel"); + } + }, [dispatch, promiseId]); + + const onAccept = useCallback(async () => { + const dst = selectedPath; + + dispatch(closePathSelectionDialog()); + if (promiseId && dst) { + pathSelectionDialogPromisePool[promiseId]?.resolve(dst); + } + }, [dispatch, selectedPath, promiseId]); + + const disabled = useMemo(() => { + const dst = selectedPath; + if (dst) { + const crUri = new CrUri(dst); + if (variantProps.disableSharedWithMe && crUri.fs() == Filesystem.shared_with_me) { + return true; + } + if (variantProps.disableTrash && crUri.fs() == Filesystem.trash) { + return true; + } + } + + return !selectedPath; + }, [selectedPath, variantProps]); + + return ( + + } + onAccept={onAccept} + dialogProps={{ + open: open ?? false, + onClose: onClose, + fullWidth: true, + maxWidth: "lg", + disableRestoreFocus: true, + PaperProps: { + sx: { + height: "100%", + }, + }, + }} + > + + + + + ); +}; +export default PathSelection; diff --git a/src/component/FileManager/Dialogs/PinToSidebar.tsx b/src/component/FileManager/Dialogs/PinToSidebar.tsx new file mode 100755 index 0000000..d098f0c --- /dev/null +++ b/src/component/FileManager/Dialogs/PinToSidebar.tsx @@ -0,0 +1,92 @@ +import { useTranslation } from "react-i18next"; +import { DialogContent } from "@mui/material"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { ChangeEvent, useCallback, useEffect, useState } from "react"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; +import { closePinFileDialog } from "../../../redux/globalStateSlice.ts"; +import { pinToSidebar } from "../../../redux/thunks/settings.ts"; +import { FilledTextField } from "../../Common/StyledComponents.tsx"; + +const PinToSidebar = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const [name, setName] = useState(""); + const [loading, setLoading] = useState(false); + + const open = useAppSelector((state) => state.globalState.pinFileDialogOpen); + const uri = useAppSelector((state) => state.globalState.pinFileUri); + + const onClose = useCallback(() => { + if (!loading) { + dispatch(closePinFileDialog()); + } + }, [dispatch, loading]); + + const onAccept = useCallback( + async (e?: React.FormEvent) => { + if (e) { + e.preventDefault(); + } + + if (!uri) { + return; + } + + setLoading(true); + try { + await dispatch(pinToSidebar(uri, name)); + } catch (e) { + } finally { + setLoading(false); + dispatch(closePinFileDialog()); + } + }, + [name, dispatch, uri, setLoading], + ); + + useEffect(() => { + if (uri && open) { + setName(""); + } + }, [uri]); + + const onNameChange = useCallback( + (e: ChangeEvent) => { + setName(e.target.value); + }, + [dispatch, setName], + ); + + return ( + + + + + + ); +}; +export default PinToSidebar; diff --git a/src/component/FileManager/Dialogs/Rename.tsx b/src/component/FileManager/Dialogs/Rename.tsx new file mode 100755 index 0000000..10137e3 --- /dev/null +++ b/src/component/FileManager/Dialogs/Rename.tsx @@ -0,0 +1,133 @@ +import { Trans, useTranslation } from "react-i18next"; +import { DialogContent, Stack } from "@mui/material"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { ChangeEvent, useCallback, useContext, useEffect, useRef, useState } from "react"; +import { closeRenameFileModal, setRenameFileModalError } from "../../../redux/fileManagerSlice.ts"; +import DraggableDialog, { StyledDialogContentText } from "../../Dialogs/DraggableDialog.tsx"; +import { renameDialogPromisePool } from "../../../redux/thunks/dialog.ts"; +import { validateFileName } from "../../../redux/thunks/file.ts"; +import { FileType } from "../../../api/explorer.ts"; + +import { FmIndexContext } from "../FmIndexContext.tsx"; +import { FilledTextField } from "../../Common/StyledComponents.tsx"; + +const Rename = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const [name, setName] = useState(""); + const formRef = useRef(null); + const inputRef = useRef(null); + + const fmIndex = useContext(FmIndexContext); + + const open = useAppSelector((state) => state.fileManager[0].renameFileModalOpen); + const targets = useAppSelector((state) => state.fileManager[0].renameFileModalSelected); + const promiseId = useAppSelector((state) => state.fileManager[0].renameFileModalPromiseId); + const loading = useAppSelector((state) => state.fileManager[0].renameFileModalLoading); + const error = useAppSelector((state) => state.fileManager[0].renameFileModalError); + + const onClose = useCallback(() => { + dispatch( + closeRenameFileModal({ + index: 0, + value: undefined, + }), + ); + if (promiseId) { + renameDialogPromisePool[promiseId]?.reject("cancel"); + } + }, [dispatch, targets, promiseId]); + + const onAccept = useCallback( + (e?: React.FormEvent) => { + if (e) { + e.preventDefault(); + } + if (promiseId) { + dispatch(validateFileName(0, renameDialogPromisePool[promiseId]?.resolve, name)); + } + }, + [promiseId, name], + ); + + const onOkClicked = useCallback(() => { + if (formRef.current) { + if (formRef.current.reportValidity()) { + onAccept(); + } + } + }, [formRef, onAccept]); + + useEffect(() => { + if (targets && open) { + setName(targets.name); + } + }, [targets, open]); + + useEffect(() => { + if (targets && open && inputRef.current) { + const lastDot = targets.type == FileType.folder ? 0 : targets.name.lastIndexOf("."); + inputRef.current.setSelectionRange(0, lastDot > 0 ? lastDot : targets.name.length); + } + }, [inputRef.current, open]); + + const onNameChange = useCallback( + (e: ChangeEvent) => { + setName(e.target.value); + if (error) { + dispatch(setRenameFileModalError({ index: 0, value: undefined })); + } + }, + [dispatch, setName, error], + ); + + return ( + + + + + ]} + /> + +
    + + +
    +
    +
    + ); +}; +export default Rename; diff --git a/src/component/FileManager/Dialogs/SaveAs.tsx b/src/component/FileManager/Dialogs/SaveAs.tsx new file mode 100755 index 0000000..9c0ce3a --- /dev/null +++ b/src/component/FileManager/Dialogs/SaveAs.tsx @@ -0,0 +1,94 @@ +import { Box, DialogContent, Divider } from "@mui/material"; +import { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { closeSaveAsDialog } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { saveAsDialogPromisePool } from "../../../redux/thunks/dialog.ts"; +import { FilledTextField } from "../../Common/StyledComponents.tsx"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; +import FolderPicker, { useFolderSelector } from "../FolderPicker.tsx"; + +const SaveAs = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const [selectedFile, selectedPath] = useFolderSelector(); + const open = useAppSelector((state) => state.globalState.saveAsDialogOpen); + const initialName = useAppSelector((state) => state.globalState.saveAsInitialName); + const promiseId = useAppSelector((state) => state.globalState.saveAsPromiseId); + const [name, setName] = useState(""); + + useEffect(() => { + if (open) { + setName(initialName ?? ""); + } + }, [open]); + + const onClose = useCallback(() => { + dispatch(closeSaveAsDialog()); + if (promiseId) { + saveAsDialogPromisePool[promiseId]?.reject("cancel"); + } + }, [dispatch, promiseId]); + + const onAccept = useCallback( + (e?: React.FormEvent) => { + if (e) { + e.preventDefault(); + } + + const dst = selectedFile && selectedFile.path ? selectedFile.path : selectedPath; + dispatch(closeSaveAsDialog()); + if (promiseId && dst) { + saveAsDialogPromisePool[promiseId]?.resolve({ + uri: dst, + name: name, + }); + } + }, + [promiseId, selectedFile, name, selectedPath], + ); + + return ( + + setName(e.target.value)} + label={t("modals.fileName")} + type="text" + value={name} + fullWidth + required + /> +
    + } + denseAction + dialogProps={{ + open: open ?? false, + onClose: onClose, + fullWidth: true, + maxWidth: "lg", + disableRestoreFocus: true, + PaperProps: { + sx: { + height: "100%", + }, + }, + }} + > + + + + + + ); +}; +export default SaveAs; diff --git a/src/component/FileManager/Dialogs/Share/ManageShares.tsx b/src/component/FileManager/Dialogs/Share/ManageShares.tsx new file mode 100755 index 0000000..59cbf87 --- /dev/null +++ b/src/component/FileManager/Dialogs/Share/ManageShares.tsx @@ -0,0 +1,238 @@ +import { + Box, + DialogContent, + IconButton, + ListItemText, + Menu, + Skeleton, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; +import React, { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getFileInfo, sendDeleteShare } from "../../../../api/api.ts"; +import { FileResponse, Share } from "../../../../api/explorer.ts"; +import { closeManageShareDialog, setShareLinkDialog } from "../../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; +import { confirmOperation } from "../../../../redux/thunks/dialog.ts"; +import AutoHeight from "../../../Common/AutoHeight.tsx"; +import { NoWrapTableCell, StyledTableContainerPaper } from "../../../Common/StyledComponents.tsx"; +import TimeBadge from "../../../Common/TimeBadge.tsx"; +import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import MoreVertical from "../../../Icons/MoreVertical.tsx"; +import { SquareMenuItem } from "../../ContextMenu/ContextMenu.tsx"; +import { ShareExpires, ShareStatistics } from "../../TopBar/ShareInfoPopover.tsx"; + +const ManageShares = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const [anchorEl, setAnchorEl] = useState(null); + const [actionTarget, setActionTarget] = useState(null); + const [fileExtended, setFileExtended] = useState(undefined); + const [loading, setLoading] = useState(false); + + const open = useAppSelector((state) => state.globalState.manageShareDialogOpen); + const target = useAppSelector((state) => state.globalState.manageShareDialogFile); + + const onClose = useCallback(() => { + if (!loading) { + dispatch(closeManageShareDialog()); + } + }, [dispatch, loading]); + + useEffect(() => { + if (target && open) { + if (target.extended_info) { + setFileExtended(target); + } else { + setFileExtended(undefined); + dispatch( + getFileInfo({ + uri: target.path, + extended: true, + }), + ).then((res) => setFileExtended(res)); + } + } + }, [target, open]); + + const handleActionClose = () => { + setAnchorEl(null); + }; + + const handleOpenAction = (event: React.MouseEvent, element: Share) => { + event.stopPropagation(); + setAnchorEl(event.currentTarget); + setActionTarget(element); + }; + + const openEditDialog = () => { + dispatch( + setShareLinkDialog({ + open: true, + file: target, + share: actionTarget ?? undefined, + }), + ); + setAnchorEl(null); + }; + + const openLink = useCallback((s: Share) => { + window.open(s.url, "_blank"); + }, []); + + const deleteShare = useCallback(() => { + if (!target || !actionTarget) { + return; + } + + dispatch(confirmOperation(t("fileManager.deleteShareWarning"))).then(() => { + setLoading(true); + dispatch(sendDeleteShare(actionTarget.id)) + .then(() => { + setFileExtended((prev) => + prev + ? { + ...prev, + extended_info: prev.extended_info + ? { + ...prev.extended_info, + shares: prev.extended_info.shares?.filter((e) => e.id !== actionTarget.id), + } + : undefined, + } + : undefined, + ); + }) + .finally(() => { + setLoading(false); + }); + }); + setAnchorEl(null); + }, [t, target, actionTarget, setLoading, dispatch]); + + return ( + <> + + + + {t(`fileManager.${actionTarget?.expired ? "editAndReactivate" : "edit"}`)} + + + + {t(`fileManager.delete`)} + + + + + + + + + + {t("fileManager.actions")} + {t("fileManager.createdAt")} + {t("fileManager.expires")} + {t("application:share.statistics")} + {t("modals.privateShare")} + + + + {!fileExtended && ( + + + + + + + + + )} + {fileExtended?.extended_info?.shares && + fileExtended?.extended_info?.shares.map((e) => ( + (e.expired ? theme.palette.text.disabled : undefined), + }, + }} + onClick={() => openLink(e)} + hover + > + + handleOpenAction(event, e)} size={"small"}> + + + + + + + + {e.expired ? ( + t("application:share.expired") + ) : ( + <> + {e.remain_downloads != undefined || e.expires ? ( + + ) : ( + t("application:fileManager.permanentValid") + )} + + )} + + + + + {t(`fileManager.${e.is_private ? "yes" : "no"}`)} + + ))} + +
    + {fileExtended && !fileExtended?.extended_info?.shares && ( + + + {t("application:setting.listEmpty")} + + + )} +
    +
    +
    +
    + + ); +}; +export default ManageShares; diff --git a/src/component/FileManager/Dialogs/Share/ShareDialog.tsx b/src/component/FileManager/Dialogs/Share/ShareDialog.tsx new file mode 100755 index 0000000..6a82ef2 --- /dev/null +++ b/src/component/FileManager/Dialogs/Share/ShareDialog.tsx @@ -0,0 +1,278 @@ +import { Box, Checkbox, Collapse, DialogContent, IconButton, Stack, Tooltip, useTheme } from "@mui/material"; +import dayjs from "dayjs"; +import { TFunction } from "i18next"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { Share as ShareModel } from "../../../../api/explorer.ts"; +import { closeShareLinkDialog } from "../../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; +import { createOrUpdateShareLink } from "../../../../redux/thunks/share.ts"; +import { copyToClipboard, sendLink } from "../../../../util"; +import AutoHeight from "../../../Common/AutoHeight.tsx"; +import { FilledTextField, SmallFormControlLabel } from "../../../Common/StyledComponents.tsx"; +import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import CopyOutlined from "../../../Icons/CopyOutlined.tsx"; +import Share from "../../../Icons/Share.tsx"; +import { FileManagerIndex } from "../../FileManager.tsx"; +import ShareSettingContent, { downloadOptions, expireOptions, ShareSetting } from "./ShareSetting.tsx"; + +const initialSetting: ShareSetting = { + expires_val: expireOptions[2], + downloads_val: downloadOptions[0], +}; + +interface ShareLinkPassword { + shareLink: string; + password?: string; +} + +const shareToSetting = (share: ShareModel, t: TFunction): ShareSetting => { + const res: ShareSetting = { + is_private: share.is_private, + password: share.password, + use_custom_password: true, + share_view: share.share_view, + show_readme: share.show_readme, + downloads: share.remain_downloads != undefined && share.remain_downloads > 0, + + expires_val: expireOptions[2], + downloads_val: downloadOptions[0], + }; + + if (res.downloads) { + res.downloads_val = { + value: share.remain_downloads ?? 0, + label: (share.remain_downloads ?? 0).toString(), + }; + } + + if (share.expires != undefined) { + const expires = dayjs(share.expires); + const isExpired = expires.isBefore(dayjs()); + if (!isExpired) { + res.expires = true; + const secondsTtl = dayjs(share.expires).diff(dayjs(), "second"); + res.expires_val = { + value: secondsTtl, + label: Math.round(secondsTtl / 60) + " " + t("application:modals.minutes"), + }; + } else { + res.expires = false; + } + } + + return res; +}; + +const ShareDialog = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + + const [loading, setLoading] = useState(false); + const [setting, setSetting] = useState(initialSetting); + const [shareLink, setShareLink] = useState(""); + const [includePassword, setIncludePassword] = useState(true); + const shareLinkPassword = useMemo(() => { + const start = shareLink.lastIndexOf("/s/"); + const shareLinkParts = shareLink.substring(start + 3).split("/"); + const password = shareLinkParts.length == 2 ? shareLinkParts[1] : undefined; + return { + shareLink: password ? shareLink.substring(0, shareLink.lastIndexOf("/")) : shareLink, + password: password, + } as ShareLinkPassword; + }, [shareLink]); + + const open = useAppSelector((state) => state.globalState.shareLinkDialogOpen); + const target = useAppSelector((state) => state.globalState.shareLinkDialogFile); + const editTarget = useAppSelector((state) => state.globalState.shareLinkDialogShare); + + useEffect(() => { + if (open) { + if (editTarget) { + setSetting(shareToSetting(editTarget, t)); + } else { + setSetting(initialSetting); + } + setShareLink(""); + setIncludePassword(true); + } + }, [open]); + + const onClose = useCallback(() => { + if (!loading) { + dispatch(closeShareLinkDialog()); + } + }, [dispatch, loading]); + + const onAccept = useCallback( + async (e?: React.MouseEvent) => { + if (e) { + e.preventDefault(); + } + + if (!target) return; + + if (shareLink) { + copyToClipboard(shareLink); + return; + } + + setLoading(true); + try { + const shareLink = await dispatch( + createOrUpdateShareLink(FileManagerIndex.main, target, setting, editTarget?.id), + ); + setShareLink(shareLink); + } catch (e) { + } finally { + setLoading(false); + } + }, + [dispatch, target, shareLink, editTarget, setLoading, setting, setShareLink], + ); + + const finalShareLink = useMemo(() => { + if (includePassword) { + return shareLink; + } + return shareLink.substring(0, shareLink.lastIndexOf("/")); + }, [includePassword, shareLink]); + + const finalShareLinkPassword = useMemo(() => { + if (!includePassword) { + return shareLink.substring(shareLink.lastIndexOf("/") + 1); + } + return undefined; + }, [includePassword, shareLink]); + + return ( + <> + + sendLink(target?.name ?? "", finalShareLink)}> + + + + ) + : undefined + } + > + + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${shareLink}`} + > + + {!shareLink && ( + + )} + {shareLink && ( + + e.target.select()} + slotProps={{ + input: { + endAdornment: ( + copyToClipboard(finalShareLink)} + size="small" + sx={{ marginRight: -1 }} + > + + + ), + }, + }} + /> + {shareLinkPassword.password && ( + <> + + e.target.select()} + slotProps={{ + input: { + endAdornment: ( + copyToClipboard(finalShareLinkPassword ?? "")} + size="small" + sx={{ marginRight: -1 }} + > + + + ), + }, + }} + /> + + + { + setIncludePassword(!includePassword); + }} + /> + } + label={t("application:modals.includePasswordInShareLink")} + /> + + + )} + + )} + + + + + + + + ); +}; +export default ShareDialog; diff --git a/src/component/FileManager/Dialogs/Share/ShareSetting.tsx b/src/component/FileManager/Dialogs/Share/ShareSetting.tsx new file mode 100755 index 0000000..2ac9526 --- /dev/null +++ b/src/component/FileManager/Dialogs/Share/ShareSetting.tsx @@ -0,0 +1,382 @@ +import { + Autocomplete, + Checkbox, + Collapse, + createFilterOptions, + FormControl, + List, + ListItemButton, + ListItemIcon, + ListItemSecondaryAction, + ListItemText, + Stack, + styled, + TextField, + Typography, +} from "@mui/material"; +import MuiAccordion from "@mui/material/Accordion"; +import MuiAccordionDetails from "@mui/material/AccordionDetails"; +import MuiAccordionSummary from "@mui/material/AccordionSummary"; +import { useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { FileResponse, FileType } from "../../../../api/explorer.ts"; +import { Code } from "../../../Common/Code.tsx"; +import { FilledTextField, SmallFormControlLabel } from "../../../Common/StyledComponents.tsx"; +import BookInformation from "../../../Icons/BookInformation.tsx"; +import ClockArrowDownload from "../../../Icons/ClockArrowDownload.tsx"; +import Eye from "../../../Icons/Eye.tsx"; +import TableSettingsOutlined from "../../../Icons/TableSettings.tsx"; +import Timer from "../../../Icons/Timer.tsx"; + +const Accordion = styled(MuiAccordion)(() => ({ + border: "0px solid rgba(0, 0, 0, .125)", + boxShadow: "none", + "&:not(:last-child)": { + borderBottom: 0, + }, + "&:before": { + display: "none", + }, + ".Mui-expanded": { + margin: "0 0", + minHeight: 0, + }, + "&.Mui-expanded": { + margin: "0 0", + minHeight: 0, + }, +})); + +const AccordionSummary = styled(MuiAccordionSummary)(({ theme }) => ({ + padding: 0, + "& .MuiAccordionSummary-content": { + margin: 0, + display: "initial", + "&.Mui-expanded": { + margin: "0 0", + }, + }, + "&.Mui-expanded": { + borderRadius: "12px 12px 0 0", + backgroundColor: theme.palette.mode == "light" ? "rgba(0, 0, 0, 0.06)" : "rgba(255, 255, 255, 0.09)", + minHeight: "0px!important", + }, +})); + +const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({ + padding: 24, + backgroundColor: theme.palette.mode == "light" ? "rgba(0, 0, 0, 0.06)" : "rgba(255, 255, 255, 0.09)", + borderRadius: "0 0 12px 12px", + fontSize: theme.typography.body2.fontSize, + color: theme.palette.text.secondary, +})); + +const StyledListItemButton = styled(ListItemButton)(() => ({})); + +export interface ShareSetting { + is_private?: boolean; + use_custom_password?: boolean; + password?: string; + share_view?: boolean; + show_readme?: boolean; + downloads?: boolean; + expires?: boolean; + + downloads_val: valueOption; + expires_val: valueOption; +} + +export interface ShareSettingProps { + setting: ShareSetting; + file?: FileResponse; + onSettingChange: (value: ShareSetting) => void; + editing?: boolean; +} + +interface valueOption { + value: number; + label: string; + inputValue?: string; +} + +export const expireOptions: valueOption[] = [ + { value: 300, label: "modals.5minutes" }, + { value: 3600, label: "modals.1hour" }, + { value: 24 * 3600, label: "modals.1day" }, + { value: 7 * 24 * 3600, label: "modals.7days" }, + { value: 30 * 24 * 3600, label: "modals.30days" }, +]; + +export const downloadOptions: valueOption[] = [ + { value: 1, label: "1" }, + { value: 2, label: "2" }, + { value: 3, label: "3" }, + { value: 4, label: "4" }, + { value: 5, label: "5" }, + { value: 20, label: "20" }, + { value: 50, label: "50" }, + { value: 100, label: "100" }, +]; + +const isNumeric = (num: any) => + (typeof num === "number" || (typeof num === "string" && num.trim() !== "")) && !isNaN(num as number); + +const filter = createFilterOptions(); + +const ShareSettingContent = ({ setting, file, editing, onSettingChange }: ShareSettingProps) => { + const { t } = useTranslation(); + + const [expanded, setExpanded] = useState(undefined); + + const handleExpand = (panel: string) => (_event: any, isExpanded: boolean) => { + setExpanded(isExpanded ? panel : undefined); + }; + + const handleCheck = (prop: "is_private" | "share_view" | "show_readme" | "expires" | "downloads") => () => { + if (!setting[prop]) { + handleExpand(prop)(null, true); + } + + onSettingChange({ ...setting, [prop]: !setting[prop] }); + }; + + return ( + + + + + + + + + + + + + + + {t("application:modals.privateShareDes")} + {setting.is_private && ( + + {!editing && ( + { + onSettingChange({ ...setting, use_custom_password: !setting.use_custom_password }); + }} + /> + } + label={t("application:modals.useCustomPassword")} + /> + )} + + + { + const value = e.target.value.trim(); + if (!/^[a-zA-Z0-9]*$/.test(value) || value.length > 32) return; + onSettingChange({ ...setting, password: value }); + }} + required + /> + + + + )} + + + {file?.type == FileType.folder && ( + <> + + + + + + + + + + + + + {t("application:modals.shareViewDes")} + + + + + + + + + + + + + + + ]} /> + + + + )} + + + + + + + + + + + + + + {t("application:modals.expirePrefix")} + + { + const filtered = filter(options, params); + + const { inputValue } = params; + const value = parseInt(inputValue) * 60; + if (inputValue !== "" && isNumeric(inputValue) && parseInt(inputValue) > 0 && value != 300) { + filtered.push({ + inputValue, + value, + label: inputValue + " " + t("application:modals.minutes"), + }); + } + + return filtered; + }} + onChange={(_event, newValue) => { + let expiry = 0; + let label = ""; + if (typeof newValue === "string") { + expiry = parseInt(newValue); + label = newValue + " " + t("application:modals.minutes"); + } else { + expiry = newValue?.value ?? 0; + label = newValue?.label ?? ""; + } + + onSettingChange({ + ...setting, + expires_val: { value: expiry, label }, + }); + }} + freeSolo + getOptionLabel={(option: string | valueOption) => (typeof option === "string" ? option : t(option.label))} + disableClearable + options={expireOptions} + renderInput={(params) => } + /> + + {t("application:modals.expireSuffix")} + + + {file?.type == FileType.file && ( + + + + + + + + + + + + + + {t("application:modals.expirePrefix")} + + { + const filtered = filter(options, params); + + const { inputValue } = params; + const value = parseInt(inputValue); + if ( + inputValue !== "" && + isNumeric(inputValue) && + parseInt(inputValue) > 0 && + !filtered.find((v) => v.value == value) + ) { + filtered.push({ + inputValue, + value, + label: inputValue, + }); + } + + return filtered; + }} + onChange={(_event, newValue) => { + let downloads = 0; + let label = ""; + if (typeof newValue === "string") { + downloads = parseInt(newValue); + label = newValue; + } else { + downloads = newValue?.value ?? 0; + label = newValue?.label ?? ""; + } + + onSettingChange({ + ...setting, + downloads_val: { value: downloads, label }, + }); + }} + freeSolo + getOptionLabel={(option: string | valueOption) => + typeof option === "string" + ? option + : t("application:modals.downloadLimitOptions", { + num: option.label, + }) + } + disableClearable + options={downloadOptions} + renderInput={(params) => } + /> + + {t("application:modals.expireSuffix")} + + + )} + + ); +}; + +export default ShareSettingContent; diff --git a/src/component/FileManager/Dialogs/StaleVersionConfirm.tsx b/src/component/FileManager/Dialogs/StaleVersionConfirm.tsx new file mode 100755 index 0000000..3d2eb8a --- /dev/null +++ b/src/component/FileManager/Dialogs/StaleVersionConfirm.tsx @@ -0,0 +1,90 @@ +import { Trans, useTranslation } from "react-i18next"; +import { Button, DialogContent, Stack } from "@mui/material"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { useCallback } from "react"; +import DraggableDialog, { StyledDialogContentText } from "../../Dialogs/DraggableDialog.tsx"; +import { askSaveAs, staleVersionDialogPromisePool } from "../../../redux/thunks/dialog.ts"; +import { closeStaleVersionDialog } from "../../../redux/globalStateSlice.ts"; +import CrUri from "../../../util/uri.ts"; + +const StaleVersionConfirm = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const open = useAppSelector((state) => state.globalState.staleVersionDialogOpen); + const uri = useAppSelector((state) => state.globalState.staleVersionUri); + const promiseId = useAppSelector((state) => state.globalState.staleVersionPromiseId); + + const onClose = useCallback(() => { + dispatch(closeStaleVersionDialog()); + if (promiseId) { + staleVersionDialogPromisePool[promiseId]?.reject("cancel"); + } + }, [dispatch, promiseId]); + + const onAccept = useCallback( + (e?: React.FormEvent) => { + if (e) { + e.preventDefault(); + } + if (promiseId) { + staleVersionDialogPromisePool[promiseId]?.resolve({ overwrite: true }); + dispatch(closeStaleVersionDialog()); + } + }, + [promiseId, name], + ); + + const onSaveAs = useCallback(async () => { + if (!uri) { + return; + } + try { + const fileName = new CrUri(uri).elements().pop(); + if (fileName && promiseId) { + const saveAsDst = await dispatch(askSaveAs(fileName)); + const dst = new CrUri(saveAsDst.uri).join(saveAsDst.name); + staleVersionDialogPromisePool[promiseId]?.resolve({ + overwrite: false, + saveAs: dst.toString(), + }); + dispatch(closeStaleVersionDialog()); + } + } catch (e) { + return; + } + }, [dispatch, promiseId, uri]); + + return ( + + {t("modals.saveAs")} + + } + dialogProps={{ + open: open ?? false, + onClose: onClose, + fullWidth: true, + maxWidth: "sm", + }} + > + + + + {t("modals.conflictDes1")} +
      + ,
    • ]} /> +
    +
    +
    +
    +
    + ); +}; +export default StaleVersionConfirm; diff --git a/src/component/FileManager/Dialogs/Tags.tsx b/src/component/FileManager/Dialogs/Tags.tsx new file mode 100755 index 0000000..bbb0214 --- /dev/null +++ b/src/component/FileManager/Dialogs/Tags.tsx @@ -0,0 +1,211 @@ +import { Autocomplete, DialogContent, Stack, useTheme } from "@mui/material"; +import { enqueueSnackbar } from "notistack"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { FileResponse, Metadata } from "../../../api/explorer.ts"; +import { defaultColors } from "../../../constants"; +import { closeTagsDialog } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { patchTags } from "../../../redux/thunks/file.ts"; +import SessionManager, { UserSettings } from "../../../session"; +import { addRecentUsedColor } from "../../../session/utils.ts"; +import { FilledTextField } from "../../Common/StyledComponents.tsx"; +import DialogAccordion from "../../Dialogs/DialogAccordion.tsx"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; +import FileTag from "../Explorer/FileTag.tsx"; +import CircleColorSelector, { customizeMagicColor } from "../FileInfo/ColorCircle/CircleColorSelector.tsx"; +import { FileManagerIndex } from "../FileManager.tsx"; + +export interface Tag { + key: string; + color?: string; +} + +export const getUniqueTagsFromFiles = (targets: FileResponse[]) => { + const tags: { + [key: string]: Tag; + } = {}; + targets.forEach((target) => { + if (target.metadata) { + Object.keys(target.metadata).forEach((key: string) => { + if (key.startsWith(Metadata.tag_prefix)) { + // trim prefix for key + const tagKey = key.slice(Metadata.tag_prefix.length); + tags[tagKey] = { + key: key.slice(Metadata.tag_prefix.length), + color: target.metadata?.[key], + }; + } + }); + } + }); + return Object.values(tags); +}; + +const Tags = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + + const [hex, setHex] = useState(undefined); + const [tags, setTags] = useState([]); + const [name, setName] = useState(""); + const [loading, setLoading] = useState(false); + + const open = useAppSelector((state) => state.globalState.tagsDialogOpen); + const targets = useAppSelector((state) => state.globalState.tagsDialogFile); + + const onClose = useCallback(() => { + if (!loading) { + dispatch(closeTagsDialog()); + } + }, [dispatch, loading]); + + const onAccept = useCallback( + async (e?: React.FormEvent) => { + if (e) { + e.preventDefault(); + } + + if (!targets) return; + + setLoading(true); + try { + await dispatch(patchTags(FileManagerIndex.main, targets, tags)); + } catch (e) { + } finally { + setLoading(false); + dispatch(closeTagsDialog()); + } + }, + [name, dispatch, targets, tags, setLoading], + ); + + const presetColors = useMemo(() => { + const colors = new Set(defaultColors); + + const recentColors = SessionManager.get(UserSettings.UsedCustomizedTagColors) as string[] | undefined; + + if (recentColors) { + recentColors.forEach((color) => { + colors.add(color); + }); + } + + return [...colors]; + }, [hex]); + + useEffect(() => { + if (targets && open) { + setTags(getUniqueTagsFromFiles(targets)); + } + }, [targets, open]); + + const onColorChange = useCallback( + (color: string | undefined) => { + color = color == theme.palette.action.selected ? undefined : color; + addRecentUsedColor(color, UserSettings.UsedCustomizedTagColors); + setHex(color); + }, + [theme, setHex], + ); + + const onTagAdded = useCallback( + (_e: any, newValue: (string | Tag)[]) => { + const duplicateMap: { [key: string]: boolean } = {}; + newValue = newValue.filter((tag) => { + const tagKey = typeof tag === "string" ? tag : tag.key; + if (!tagKey) { + return false; + } + if (duplicateMap[tagKey]) { + enqueueSnackbar(t("application:modals.duplicateTag", { tag: tagKey }), { variant: "warning" }); + return false; + } + duplicateMap[tagKey] = true; + return true; + }); + setTags(newValue.map((tag) => (typeof tag === "string" ? { key: tag, color: hex } : tag) as Tag)); + }, + [hex, setTags], + ); + + // const onNameChange = useCallback( + // (e: ChangeEvent) => { + // setName(e.target.value); + // }, + // [dispatch, setName], + // ); + + return ( + + + + o?.key} + value={tags} + freeSolo + autoSelect={true} + onChange={onTagAdded} + renderTags={(value: readonly Tag[], getTagProps) => + value.map((option: Tag, index: number) => ( + + )) + } + renderInput={(params) => ( + + )} + /> + + + + + + + ); +}; +export default Tags; diff --git a/src/component/FileManager/Dialogs/VersionControl.tsx b/src/component/FileManager/Dialogs/VersionControl.tsx new file mode 100755 index 0000000..7ecb586 --- /dev/null +++ b/src/component/FileManager/Dialogs/VersionControl.tsx @@ -0,0 +1,285 @@ +import { + Alert, + Box, + DialogContent, + IconButton, + ListItemText, + Menu, + Skeleton, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { confirmOperation } from "../../../redux/thunks/dialog.ts"; +import { downloadSingleFile } from "../../../redux/thunks/download.ts"; +import { setFileVersion } from "../../../redux/thunks/file.ts"; +import { openViewers } from "../../../redux/thunks/viewer.ts"; +import { sizeToString } from "../../../util"; +import AutoHeight from "../../Common/AutoHeight.tsx"; +import { closeVersionControlDialog } from "../../../redux/globalStateSlice.ts"; +import { Entity, EntityType, FileResponse } from "../../../api/explorer.ts"; +import { deleteVersion, getFileInfo } from "../../../api/api.ts"; +import { NoWrapTableCell, StyledTableContainerPaper } from "../../Common/StyledComponents.tsx"; +import TimeBadge from "../../Common/TimeBadge.tsx"; +import { AnonymousUser } from "../../Common/User/UserAvatar.tsx"; +import UserBadge from "../../Common/User/UserBadge.tsx"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; +import MoreVertical from "../../Icons/MoreVertical.tsx"; +import { SquareMenuItem } from "../ContextMenu/ContextMenu.tsx"; +import { FileManagerIndex } from "../FileManager.tsx"; + +const VersionControl = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const [anchorEl, setAnchorEl] = useState(null); + const [actionTarget, setActionTarget] = useState(null); + const [fileExtended, setFileExtended] = useState(undefined); + const [loading, setLoading] = useState(false); + + const open = useAppSelector((state) => state.globalState.versionControlDialogOpen); + const target = useAppSelector((state) => state.globalState.versionControlDialogFile); + const highlight = useAppSelector((state) => state.globalState.versionControlHighlight); + + const onClose = useCallback(() => { + if (!loading) { + dispatch(closeVersionControlDialog()); + } + }, [dispatch, loading]); + + useEffect(() => { + if (target && open) { + setFileExtended(undefined); + dispatch( + getFileInfo({ + uri: target.path, + extended: true, + }), + ).then((res) => setFileExtended(res)); + } + }, [target, open]); + + const versionEntities = useMemo(() => { + return fileExtended?.extended_info?.entities?.filter((e) => e.type == EntityType.version); + }, [fileExtended?.extended_info?.entities]); + + const hilightButNotFound = useMemo(() => { + return highlight && fileExtended?.extended_info && !versionEntities?.some((e) => e.id == highlight); + }, [highlight, fileExtended?.extended_info?.entities]); + + const handleActionClose = () => { + setAnchorEl(null); + }; + + const handleOpenAction = (event: React.MouseEvent, element: Entity) => { + setAnchorEl(event.currentTarget); + setActionTarget(element); + }; + + const downloadEntity = useCallback(() => { + if (!target || !actionTarget) { + return; + } + dispatch(downloadSingleFile(target, actionTarget.id)); + setAnchorEl(null); + }, [target, actionTarget, dispatch]); + + const openEntity = useCallback(() => { + if (!target || !actionTarget) { + return; + } + dispatch(openViewers(FileManagerIndex.main, target, actionTarget.size, actionTarget.id)); + setAnchorEl(null); + }, [target, actionTarget, dispatch]); + + const setAsCurrent = useCallback(() => { + if (!target || !actionTarget) { + return; + } + + setLoading(true); + dispatch(setFileVersion(FileManagerIndex.main, target, actionTarget.id)) + .then(() => { + setFileExtended((prev) => + prev + ? { + ...prev, + primary_entity: actionTarget.id, + } + : undefined, + ); + }) + .finally(() => { + setLoading(false); + }); + + setAnchorEl(null); + }, [target, actionTarget, setLoading, dispatch]); + + const deleteTargetVersion = useCallback(() => { + if (!target || !actionTarget) { + return; + } + + dispatch(confirmOperation(t("fileManager.deleteVersionWarning"))).then(() => { + setLoading(true); + dispatch( + deleteVersion({ + uri: target.path, + version: actionTarget.id, + }), + ) + .then(() => { + setFileExtended((prev) => + prev + ? { + ...prev, + extended_info: prev.extended_info + ? { + ...prev.extended_info, + entities: prev.extended_info.entities?.filter((e) => e.id !== actionTarget.id), + } + : undefined, + } + : undefined, + ); + }) + .finally(() => { + setLoading(false); + }); + }); + + setAnchorEl(null); + }, [t, target, actionTarget, setLoading, dispatch]); + + return ( + <> + + + {t("application:fileManager.open")} + + + {t("application:fileManager.download")} + + {target?.owned && actionTarget?.id !== fileExtended?.primary_entity && ( + + {t("application:fileManager.setAsCurrent")} + + )} + {target?.owned && actionTarget?.id !== fileExtended?.primary_entity && ( + + {t("application:fileManager.delete")} + + )} + + + + + {hilightButNotFound && ( + + {t("application:fileManager.versionNotFound")} + + )} + + + + + {t("fileManager.actions")} + {t("fileManager.createdAt")} + {t("fileManager.size")} + {t("fileManager.createdBy")} + {t("application:fileManager.storagePolicy")} + + + + {!fileExtended && ( + + + + + + + + + )} + {versionEntities && + versionEntities.map((e) => ( + + highlight == e.id ? `inset 0 0 0 2px ${theme.palette.primary.light}` : "none", + "&:last-child td, &:last-child th": { border: 0 }, + }} + hover + > + + handleOpenAction(event, e)} size={"small"}> + + + + + + + {sizeToString(e.size)} + + + + {e.storage_policy?.name} + + ))} + +
    + {!versionEntities && fileExtended && ( + + + {t("application:setting.listEmpty")} + + + )} +
    +
    +
    +
    + + ); +}; +export default VersionControl; diff --git a/src/component/FileManager/Dnd/DisableDropDelay.tsx b/src/component/FileManager/Dnd/DisableDropDelay.tsx new file mode 100755 index 0000000..4f44857 --- /dev/null +++ b/src/component/FileManager/Dnd/DisableDropDelay.tsx @@ -0,0 +1,20 @@ +import { useDrop } from "react-dnd"; +import { useEffect } from "react"; + +const DisableDropDelay = () => { + const [_, bodyDropRef] = useDrop(() => ({ + accept: "file", + drop: () => { + // do something + }, + })); + + useEffect(() => { + bodyDropRef(document.body); + return () => { + bodyDropRef(null); + }; + }, []); +}; + +export default DisableDropDelay; diff --git a/src/component/FileManager/Dnd/DndWrappedFile.tsx b/src/component/FileManager/Dnd/DndWrappedFile.tsx new file mode 100755 index 0000000..a976fca --- /dev/null +++ b/src/component/FileManager/Dnd/DndWrappedFile.tsx @@ -0,0 +1,151 @@ +import { memo, useCallback, useContext, useEffect } from "react"; +import { useDrag, useDrop } from "react-dnd"; +import { getEmptyImage } from "react-dnd-html5-backend"; +import { FileResponse, FileType } from "../../../api/explorer.ts"; +import { setDragging } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { processDnd } from "../../../redux/thunks/file.ts"; +import { getFileLinkedUri, mergeRefs } from "../../../util"; + +import { useTheme } from "@mui/material/styles"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import CrUri, { Filesystem } from "../../../util/uri.ts"; +import { FileBlockProps } from "../Explorer/Explorer.tsx"; +import { FileManagerIndex } from "../FileManager.tsx"; +import { FmIndexContext } from "../FmIndexContext.tsx"; + +export interface DragItem { + target: FileResponse; + includeSelected?: boolean; +} + +export interface DropResult { + dropEffect: string; + uri?: string; +} + +export const DropEffect = { + copy: "copy", + move: "move", +}; + +export interface UseFileDragProps { + file?: FileResponse; + includeSelected?: boolean; + dropUri?: string; +} + +export const NoOpDropUri = "noop"; +export const useFileDrag = ({ file, includeSelected, dropUri }: UseFileDragProps) => { + const dispatch = useAppDispatch(); + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down("md")); + const fmIndex = useContext(FmIndexContext); + // const { addEventListenerForWindow, removeEventListenerForWindow } = + // useDragScrolling(["#" + MainExplorerContainerID]); + + // @ts-ignore + const [{ isDragging }, drag, preview] = useDrag({ + type: "file", + item: { + target: file, + includeSelected, + }, + end: (item, monitor) => { + // Ignore NoOpDropUri + const target = monitor.getDropResult(); + if (!item || !target || !target.uri || target.uri == NoOpDropUri) { + return; + } + + dispatch(processDnd(0, item as DragItem, target)); + }, + canDrag: () => { + if (!file || fmIndex == FileManagerIndex.selector || isTablet) { + return false; + } + + const crUri = new CrUri(file.path); + return file.owned && crUri.fs() != Filesystem.share; + }, + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + }), + }); + + const [{ canDrop, isOver }, drop] = useDrop({ + accept: "file", + drop: () => (file ? { uri: getFileLinkedUri(file) } : { uri: dropUri }), + collect: (monitor) => ({ + isOver: monitor.isOver(), + canDrop: monitor.canDrop(), + }), + canDrop: (item, _monitor) => { + const dropExist = !!dropUri || (!!file && file.type == FileType.folder); + if (!dropExist || fmIndex == FileManagerIndex.selector) { + return false; + } + + if (!item) { + return false; + } + + return true; + }, + }); + const isActive = canDrop && isOver; + + useEffect(() => { + preview(getEmptyImage(), { captureDraggingState: true }); + // eslint-disable-next-line + }, []); + + useEffect(() => { + if (isDragging) { + // addEventListenerForWindow(); + } + dispatch( + setDragging({ + dragging: isDragging, + draggingWithSelected: !!includeSelected, + }), + ); + }, [isDragging]); + + return [drag, drop, isActive, isDragging] as const; +}; + +export interface DndWrappedFileProps extends FileBlockProps { + component: React.MemoExoticComponent<(props: FileBlockProps) => JSX.Element>; +} + +const DndWrappedFile = memo((props: DndWrappedFileProps) => { + const fmIndex = useContext(FmIndexContext); + const globalDragging = useAppSelector((state) => state.globalState.dndState); + const isSelected = useAppSelector((state) => state.fileManager[fmIndex].selected[props.file.path]); + + const [drag, drop, isOver, isDragging] = useFileDrag({ + file: props.file.placeholder ? undefined : props.file, + includeSelected: true, + }); + + const mergedRef = useCallback( + (val: any) => { + mergeRefs(drop, drag)(val); + }, + [drop, drag], + ); + + const Component = props.component; + + return ( + + ); +}); + +export default DndWrappedFile; diff --git a/src/component/FileManager/Dnd/DragLayer.tsx b/src/component/FileManager/Dnd/DragLayer.tsx new file mode 100755 index 0000000..292263e --- /dev/null +++ b/src/component/FileManager/Dnd/DragLayer.tsx @@ -0,0 +1,112 @@ +import { useDragLayer, XYCoord } from "react-dnd"; +import { FileResponse } from "../../../api/explorer.ts"; +import { Badge, Box, Paper, PaperProps } from "@mui/material"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import { useEffect, useMemo, useState } from "react"; +import { DragItem } from "./DndWrappedFile.tsx"; +import DisableDropDelay from "./DisableDropDelay.tsx"; +import { FileNameText, Header } from "../Explorer/GridView/GridFile.tsx"; +import FileSmallIcon from "../Explorer/FileSmallIcon.tsx"; + +interface DragPreviewProps extends PaperProps { + files: FileResponse[]; + pointerOffset: XYCoord | null; +} + +const DragPreview = ({ pointerOffset, files, ...rest }: DragPreviewProps) => { + const [size, setSize] = useState([0, 0]); + useEffect(() => { + setSize([220, 48]); + }, []); + if (!files || files.length == 0) { + return undefined; + } + return ( + + theme.transitions.create(["width", "height"]), + }} + {...rest} + > +
    + + {files[0]?.name} +
    +
    + {[...Array(Math.min(2, files.length - 1)).keys()].map((i) => ( + theme.transitions.create(["width", "height"]), + }} + elevation={3} + /> + ))} +
    + ); +}; + +const DragLayer = () => { + DisableDropDelay(); + + const { itemType, isDragging, item, pointerOffset } = useDragLayer((monitor) => ({ + item: monitor.getItem(), + itemType: monitor.getItemType(), + pointerOffset: monitor.getClientOffset(), + isDragging: monitor.isDragging(), + })); + + const selected = useAppSelector((state) => state.fileManager[0].selected); + const draggingFiles = useMemo(() => { + if (item && (item as DragItem) && item.target) { + const selectedList = item.includeSelected + ? Object.keys(selected) + .map((key) => selected[key]) + .filter((x) => x.path != item.target.path) + : []; + return [item.target, ...selectedList]; + } + + return []; + }, [selected, item]); + + if (!isDragging) { + return null; + } + + return ( + + + + ); +}; + +export default DragLayer; diff --git a/src/component/FileManager/Dnd/useDndScrolling.ts b/src/component/FileManager/Dnd/useDndScrolling.ts new file mode 100755 index 0000000..42d987d --- /dev/null +++ b/src/component/FileManager/Dnd/useDndScrolling.ts @@ -0,0 +1,72 @@ +import { useRef } from "react"; +import { throttle } from "lodash"; + +const threshold = 0.1; + +const useDragScrolling = (containers: string[]) => { + const isScrolling = useRef(false); + const targets = containers.map((id) => document.querySelector(id) as HTMLElement); + const rects = useRef([]); + + const goDown = (target: HTMLElement) => { + return () => { + target.scrollTop += 5; + + const { offsetHeight, scrollTop, scrollHeight } = target; + const isScrollEnd = offsetHeight + scrollTop >= scrollHeight; + + if (isScrolling.current && !isScrollEnd) { + window.requestAnimationFrame(goDown(target)); + } + }; + }; + + const goUp = (target: HTMLElement) => { + return () => { + target.scrollTop -= 5; + if (isScrolling.current && target.scrollTop > 0) { + window.requestAnimationFrame(goUp(target)); + } + }; + }; + + const onDragOver = (event: MouseEvent) => { + // detect if mouse is in any rect + rects.current.forEach((rect, index) => { + if (event.clientX < rect.left || event.clientX > rect.right) { + isScrolling.current = false; + return; + } + + const height = rect.bottom - rect.top; + if (event.clientY > rect.top && event.clientY < rect.top + threshold * height) { + isScrolling.current = true; + window.requestAnimationFrame(goUp(targets[index])); + } else if (event.clientY < rect.bottom && event.clientY > rect.bottom - threshold * height) { + isScrolling.current = true; + window.requestAnimationFrame(goDown(targets[index])); + } else { + isScrolling.current = false; + } + }); + }; + + const throttleOnDragOver = throttle(onDragOver, 300); + + const addEventListenerForWindow = () => { + rects.current = targets.map((t) => t.getBoundingClientRect()); + window.addEventListener("dragover", throttleOnDragOver, false); + }; + + const removeEventListenerForWindow = () => { + window.removeEventListener("dragover", throttleOnDragOver, false); + isScrolling.current = false; + }; + + return { + addEventListenerForWindow, + removeEventListenerForWindow, + }; +}; + +export default useDragScrolling; diff --git a/src/component/FileManager/Explorer/EmojiIcon.tsx b/src/component/FileManager/Explorer/EmojiIcon.tsx new file mode 100755 index 0000000..fa3de32 --- /dev/null +++ b/src/component/FileManager/Explorer/EmojiIcon.tsx @@ -0,0 +1,24 @@ +import { SvgIconProps, Typography } from "@mui/material"; + +export interface EmojiIconProps extends SvgIconProps { + emoji: string; +} + +const EmojiIcon = ({ sx, fontSize, emoji, ...rest }: EmojiIconProps) => { + return ( + theme.palette.text.primary, + minWidth: "24px", + pl: "4px", + ...sx, + }} + fontSize={fontSize} + {...rest} + > + {emoji} + + ); +}; + +export default EmojiIcon; diff --git a/src/component/FileManager/Explorer/EmptyFileList.tsx b/src/component/FileManager/Explorer/EmptyFileList.tsx new file mode 100755 index 0000000..3ffba71 --- /dev/null +++ b/src/component/FileManager/Explorer/EmptyFileList.tsx @@ -0,0 +1,229 @@ +import { + Alert, + AlertTitle, + Box, + ListItemIcon, + ListItemText, + MenuList, + Paper, + Stack, + Tooltip, + Typography, +} from "@mui/material"; +import { grey } from "@mui/material/colors"; +import React, { memo, useContext, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { NavigatorCapability } from "../../../api/explorer.ts"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import { isMacbook } from "../../../redux/thunks/file.ts"; +import Boolset from "../../../util/boolset.ts"; +import { Filesystem } from "../../../util/uri.ts"; +import Nothing from "../../Common/Nothing.tsx"; +import { KeyIndicator } from "../../Frame/NavBar/SearchBar.tsx"; +import ArrowSync from "../../Icons/ArrowSync.tsx"; +import Border from "../../Icons/Border.tsx"; +import BorderAll from "../../Icons/BorderAll.tsx"; +import BorderInside from "../../Icons/BorderInside.tsx"; +import FolderLink from "../../Icons/FolderLink.tsx"; +import MoreHorizontal from "../../Icons/MoreHorizontal.tsx"; +import PinOutlined from "../../Icons/PinOutlined.tsx"; +import { DenseDivider, SquareMenuItem } from "../ContextMenu/ContextMenu.tsx"; +import { FmIndexContext } from "../FmIndexContext.tsx"; +import { ActionButton, ActionButtonGroup } from "../TopBar/TopActions.tsx"; + +interface EmptyFileListProps { + [key: string]: any; +} + +export const SearchLimitReached = () => { + const { t } = useTranslation("application"); + return ( + + {t("fileManager.recursiveLimitReached")} + {t("fileManager.recursiveLimitReachedDes")} + + ); +}; + +export const SharedWithMeEmpty = () => { + const { t } = useTranslation("application"); + + return ( + + (t.palette.mode == "dark" ? grey[900] : grey[100]), + borderRadius: (t) => `${t.shape.borderRadius}px`, + p: 1, + position: "relative", + "&::after": { + content: '""', + position: "absolute", + bottom: 0, + left: 0, + width: "100%", + height: "50px", + background: (t) => + `linear-gradient(to bottom, transparent, ${t.palette.mode == "dark" ? grey[900] : grey[100]})`, + pointerEvents: "none", + }, + }} + > + + + `1px solid ${t.palette.divider}`, + backgroundColor: (t) => t.palette.background.paper, + height: "42px", + borderRadius: (t) => `${t.shape.borderRadius}px`, + }} + /> + t.palette.background.paper, + height: "42px", + }} + > + + + + + + `1px solid ${t.palette.primary.main}`, + }} + > + + + + + + + + + + + + + {t("application:fileManager.pin")} + + + + + + + + {t("application:fileManager.saveShortcut")} + + + + + + + + + {t("application:fileManager.pin")} + + + + + + + {t("application:fileManager.selectAll")} + + {isMacbook ? "⌘" : "Ctrl"}+A + + + + + + + {t("application:fileManager.selectNone")} + + + + + + {t("application:fileManager.invertSelection")} + + + + + + + + + {t("application:fileManager.shareWithMeEmpty")} + + + {t("application:fileManager.shareWithMeEmptyDes")} + + + + ); +}; + +const EmptyFileList = memo( + React.forwardRef(({ ...rest }: EmptyFileListProps, ref) => { + const { t } = useTranslation("application"); + const fmIndex = useContext(FmIndexContext); + const currentFs = useAppSelector((state) => state.fileManager[fmIndex]?.current_fs); + const search_params = useAppSelector((state) => state.fileManager[fmIndex]?.search_params); + const recursion_limit_reached = useAppSelector((state) => state.fileManager[fmIndex].list?.recursion_limit_reached); + const capability = useAppSelector((state) => state.fileManager[fmIndex].list?.props.capability); + + const canCreate = useMemo(() => { + const bs = new Boolset(capability); + return bs.enabled(NavigatorCapability.create_file); + }, [capability]); + + return ( + + {currentFs == Filesystem.shared_with_me && ( + <> + + {recursion_limit_reached && } + + )} + {currentFs != Filesystem.shared_with_me && ( + <> + + {recursion_limit_reached && } + + )} + + ); + }), +); + +export default EmptyFileList; diff --git a/src/component/FileManager/Explorer/Explorer.tsx b/src/component/FileManager/Explorer/Explorer.tsx new file mode 100755 index 0000000..dc11cd3 --- /dev/null +++ b/src/component/FileManager/Explorer/Explorer.tsx @@ -0,0 +1,160 @@ +import { Box, useMediaQuery, useTheme } from "@mui/material"; +import React, { RefCallback, useCallback, useContext, useEffect, useMemo } from "react"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { useAreaSelection } from "../../../hooks/areaSelection.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { ConfigLoadState } from "../../../redux/siteConfigSlice.ts"; +import { openEmptyContextMenu } from "../../../redux/thunks/filemanager.ts"; +import { loadSiteConfig } from "../../../redux/thunks/site.ts"; +import CircularProgress from "../../Common/CircularProgress.tsx"; +import "../../Common/FadeTransition.css"; +import { RadiusFrame } from "../../Frame/RadiusFrame.tsx"; +import ExplorerError from "./ExplorerError.tsx"; +import GridView, { FmFile } from "./GridView/GridView.tsx"; + +import { Layouts } from "../../../redux/fileManagerSlice.ts"; +import { SearchParam } from "../../../util/uri.ts"; +import { FileManagerIndex } from "../FileManager.tsx"; +import { FmIndexContext } from "../FmIndexContext.tsx"; +import EmptyFileList, { SearchLimitReached } from "./EmptyFileList.tsx"; +import GalleryView from "./GalleryView/GalleryView.tsx"; +import { ListViewColumn } from "./ListView/Column.tsx"; +import ListView from "./ListView/ListView.tsx"; +import SingleFileView from "./SingleFileView.tsx"; + +export const ExplorerPage = { + Error: 1, + Loading: 2, + GridView: 0, + SingleFileView: 3, + Empty: 4, + ListView: 5, + GalleryView: 6, +}; + +export interface FileBlockProps { + showThumb?: boolean; + file: FmFile; + isDragging?: boolean; + isDropOver?: boolean; + dragRef?: RefCallback; + index?: number; + search?: SearchParam; + columns?: ListViewColumn[]; + boxHeight?: number; +} + +const Explorer = () => { + const dispatch = useAppDispatch(); + const theme = useTheme(); + const isTouch = useMediaQuery("(pointer: coarse)"); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const fmIndex = useContext(FmIndexContext); + const loading = useAppSelector((state) => state.fileManager[fmIndex].loading); + const error = useAppSelector((state) => state.fileManager[fmIndex].error); + const showError = useAppSelector((state) => state.fileManager[fmIndex].showError); + const singleFileView = useAppSelector((state) => state.fileManager[fmIndex].list?.single_file_view); + const explorerConfigLoading = useAppSelector((state) => state.siteConfig.explorer.loaded); + const files = useAppSelector((state) => state.fileManager[fmIndex].list?.files); + const recursion_limit_reached = useAppSelector((state) => state.fileManager[fmIndex].list?.recursion_limit_reached); + const layout = useAppSelector((state) => state.fileManager[fmIndex].layout); + + const selectContainerRef = React.useRef(null); + + useEffect(() => { + dispatch(loadSiteConfig("explorer")); + }, []); + + const index = useMemo(() => { + if (showError) { + return ExplorerPage.Error; + } else if (loading || explorerConfigLoading == ConfigLoadState.NotLoaded) { + return ExplorerPage.Loading; + } else { + if (files?.length === 0) { + return ExplorerPage.Empty; + } + + if (singleFileView && fmIndex == FileManagerIndex.main) { + return ExplorerPage.SingleFileView; + } + + switch (layout) { + case Layouts.grid: + return ExplorerPage.GridView; + case Layouts.list: + return ExplorerPage.ListView; + case Layouts.gallery: + return ExplorerPage.GalleryView; + default: + return ExplorerPage.GridView; + } + } + }, [loading, showError, explorerConfigLoading, singleFileView, fmIndex, files?.length, layout]); + + const enableAreaSelection = index == ExplorerPage.GridView; + + const [handleMouseDown, handleMouseUp, handleMouseMove] = useAreaSelection( + selectContainerRef, + fmIndex, + enableAreaSelection, + ); + + const onContextMenu = useCallback( + (e: React.MouseEvent) => { + if (index == ExplorerPage.Error || index == ExplorerPage.Loading) return; + dispatch(openEmptyContextMenu(fmIndex, e)); + }, + [dispatch, index], + ); + + return ( + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={index} + > + + {index == ExplorerPage.Error && } + {index == ExplorerPage.Loading && ( + + + + )} + {index == ExplorerPage.GridView && } + {index == ExplorerPage.SingleFileView && } + {index == ExplorerPage.Empty && } + {index == ExplorerPage.ListView && } + {index == ExplorerPage.GalleryView && } + {recursion_limit_reached && (index == ExplorerPage.GridView || index == ExplorerPage.GalleryView) && ( + + + + )} + + + + + ); +}; + +export default Explorer; diff --git a/src/component/FileManager/Explorer/ExplorerError.tsx b/src/component/FileManager/Explorer/ExplorerError.tsx new file mode 100755 index 0000000..39c0f23 --- /dev/null +++ b/src/component/FileManager/Explorer/ExplorerError.tsx @@ -0,0 +1,134 @@ +import { Alert, AlertTitle, Box, Button, Typography } from "@mui/material"; +import React, { memo, useCallback, useContext, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { AppError, Code, Response } from "../../../api/request.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { navigateToPath, retrySharePassword } from "../../../redux/thunks/filemanager.ts"; +import { Filesystem } from "../../../util/uri.ts"; +import { FilledTextField, SecondaryButton } from "../../Common/StyledComponents.tsx"; +import ArrowLeft from "../../Icons/ArrowLeft.tsx"; +import LinkDismiss from "../../Icons/LinkDismiss.tsx"; +import LockClosed from "../../Icons/LockClosed.tsx"; +import { FmIndexContext } from "../FmIndexContext.tsx"; + +interface ExplorerErrorProps { + error?: Response; + [key: string]: any; +} + +const RetryPassword = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const fmIndex = useContext(FmIndexContext); + const [password, setPassword] = useState(""); + + return ( + e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()}> + + + setPassword(e.target.value)} + label={t("application:share.enterPassword")} + /> + + + + ); +}; + +const ExplorerError = memo( + React.forwardRef(({ error, ...rest }: ExplorerErrorProps, ref) => { + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + const fmIndex = useContext(FmIndexContext); + const fs = useAppSelector((state) => state.fileManager[fmIndex].current_fs); + const previousPath = useAppSelector((state) => state.fileManager[fmIndex].previous_path); + const { t } = useTranslation("application"); + const appErr = useMemo(() => { + if (error) { + return new AppError(error); + } + + return undefined; + }, [error]); + const navigateBack = useCallback(() => { + previousPath && dispatch(navigateToPath(fmIndex, previousPath)); + }, [dispatch, fmIndex, previousPath]); + + const signIn = useCallback(() => { + navigate("/session?redirect=" + encodeURIComponent(window.location.pathname + window.location.search)); + }, [navigate]); + + const innerError = () => { + switch (error?.code) { + case Code.AnonymouseAccessDenied: + return ( + + + {t("application:fileManager.anonymousAccessDenied")} + + {t("application:login.signIn")} + + + ); + case Code.IncorrectPassword: + return ; + // @ts-ignore + case Code.NodeFound: + if (fs == Filesystem.share) { + return ( + + + {t("application:share.shareNotExist")} + + ); + } + default: + return ( + + {t("application:fileManager.listError")} + {appErr && appErr.message} + {error?.correlation_id && ( + + {t("common:requestID", { id: error.correlation_id })} + + )} + + ); + } + }; + return ( + + {innerError()} + + ); + }), +); + +export default ExplorerError; diff --git a/src/component/FileManager/Explorer/FileIcon.tsx b/src/component/FileManager/Explorer/FileIcon.tsx new file mode 100755 index 0000000..62b664d --- /dev/null +++ b/src/component/FileManager/Explorer/FileIcon.tsx @@ -0,0 +1,116 @@ +import { Avatar, Badge, BadgeProps, Box, Skeleton, styled, SvgIconProps, Tooltip } from "@mui/material"; +import { forwardRef, memo, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { FileResponse, FileType, Metadata } from "../../../api/explorer.ts"; +import UserAvatar from "../../Common/User/UserAvatar.tsx"; +import ShareAndroid from "../../Icons/ShareAndroid.tsx"; +import EmojiIcon from "./EmojiIcon.tsx"; +import FileTypeIcon from "./FileTypeIcon.tsx"; + +export interface FileIconProps { + file?: FileResponse; + variant?: "default" | "small" | "large"; + loading?: boolean; + notLoaded?: boolean; + [key: string]: any; + iconProps?: SvgIconProps; +} + +interface StyledBadgeProps extends BadgeProps { + iconVariant?: "default" | "small" | "large" | "largeMobile" | "shareSingle"; +} + +const StyledBadge = styled(Badge)(({ iconVariant }) => ({ + "& .MuiBadge-badge": { + right: 3, + top: variantTop[iconVariant ?? "default"], + padding: "0", + height: "initial", + minWidth: "initial", + }, + verticalAlign: "initial", +})); + +const variantTop = { + default: 18, + small: 15, + large: 70, + largeMobile: 52, + shareSingle: 26, +}; + +const variantAvatarSize = { + default: 16, + small: 13, + large: 32, + largeMobile: 24, + shareSingle: 20, +}; + +const FileIcon = memo( + forwardRef(({ file, loading, variant = "default", iconProps, notLoaded, sx, ...rest }: FileIconProps, ref) => { + const { t } = useTranslation(); + const iconColor = useMemo(() => { + if (file && file.metadata && file.metadata[Metadata.icon_color]) { + return file.metadata[Metadata.icon_color]; + } + }, [file]); + const typedIcon = useMemo(() => { + if (file?.metadata?.[Metadata.emoji]) { + const { sx, ...restIcon } = iconProps ?? {}; + return ; + } + return ( + + ); + }, [file, iconColor, iconProps, notLoaded]); + const badgeContent = useMemo(() => { + const avatarSize = variantAvatarSize[variant]; + if (file?.metadata?.[Metadata.share_redirect]) { + return ( + + ); + } else if (file?.shared) { + return ( + + theme.palette.background.default, + }} + > + + + + ); + } + }, [file, variant]); + return ( + + {!loading && + (badgeContent ? ( + + {typedIcon} + + ) : ( + typedIcon + ))} + {loading && } + + ); + }), +); + +export default FileIcon; diff --git a/src/component/FileManager/Explorer/FileSmallIcon.tsx b/src/component/FileManager/Explorer/FileSmallIcon.tsx new file mode 100755 index 0000000..0fbe652 --- /dev/null +++ b/src/component/FileManager/Explorer/FileSmallIcon.tsx @@ -0,0 +1,95 @@ +import { Box, Fade } from "@mui/material"; +import { memo, useCallback, useContext } from "react"; +import { TransitionGroup } from "react-transition-group"; +import { FileResponse } from "../../../api/explorer.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { fileIconClicked } from "../../../redux/thunks/file.ts"; +import CheckmarkCircle from "../../Icons/CheckmarkCircle.tsx"; +import CheckUnchecked from "../../Icons/CheckUnchecked.tsx"; +import FileIcon from "./FileIcon.tsx"; + +import { FmIndexContext } from "../FmIndexContext.tsx"; + +export interface FileSmallIconProps { + selected: boolean; + file: FileResponse; + loading?: boolean; + ignoreHovered?: boolean; + variant?: "list" | "grid"; +} + +const FileSmallIcon = memo(({ selected, variant, loading, file, ignoreHovered }: FileSmallIconProps) => { + const dispatch = useAppDispatch(); + const fmIndex = useContext(FmIndexContext); + const hovered = useAppSelector((state) => state.fileManager[fmIndex].multiSelectHovered[file.path]); + const onIconClick = useCallback( + (e: React.MouseEvent) => { + if (!loading) { + return dispatch(fileIconClicked(fmIndex, file, e)); + } + }, + [file, loading, dispatch], + ); + const isInList = variant === "list"; + return ( + + {!selected && (!hovered || ignoreHovered) && ( + + + + )} + {!selected && hovered && !ignoreHovered && ( + + + + + + )} + {selected && ( + + + + + + )} + + + ); +}); + +export default FileSmallIcon; diff --git a/src/component/FileManager/Explorer/FileTag.tsx b/src/component/FileManager/Explorer/FileTag.tsx new file mode 100755 index 0000000..633c483 --- /dev/null +++ b/src/component/FileManager/Explorer/FileTag.tsx @@ -0,0 +1,73 @@ +import { Chip, ChipProps, darken, styled, Tooltip, useTheme } from "@mui/material"; +import { useCallback, useContext } from "react"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { Metadata } from "../../../api/explorer.ts"; +import { searchMetadata } from "../../../redux/thunks/filemanager.ts"; +import { FmIndexContext } from "../FmIndexContext.tsx"; + +export const TagChip = styled(Chip)<{ defaultStyle?: boolean }>(({ defaultStyle }) => { + const base = { + "& .MuiChip-deleteIcon": {}, + }; + if (!defaultStyle) return { ...base, height: 18, minWidth: 32 }; + return base; +}); + +export interface FileTagProps extends ChipProps { + tagColor?: string; + defaultStyle?: boolean; + spacing?: number; + openInNewTab?: boolean; + disableClick?: boolean; +} + +const FileTag = ({ disableClick, tagColor, sx, label, defaultStyle, spacing, openInNewTab, ...rest }: FileTagProps) => { + const theme = useTheme(); + const fmIndex = useContext(FmIndexContext); + const dispatch = useAppDispatch(); + const root = useAppSelector((state) => state.fileManager[fmIndex].path_root); + const stopPropagation = useCallback( + (e: any) => { + if (!disableClick) e.stopPropagation(); + }, + [disableClick], + ); + const onClick = useCallback( + (e: any) => { + if (disableClick) { + return; + } + e.stopPropagation(); + dispatch(searchMetadata(fmIndex, Metadata.tag_prefix + label, tagColor, openInNewTab)); + }, + [root, dispatch, fmIndex, disableClick], + ); + + const hackColor = + !!tagColor && theme.palette.getContrastText(tagColor) != theme.palette.text.primary ? "error" : undefined; + return ( + + theme.palette.getContrastText(tagColor), + "&:hover": { + backgroundColor: darken(tagColor, 0.1), + }, + }, + spacing !== undefined && { mr: spacing }, + ]} + onClick={onClick} + onMouseDown={stopPropagation} + size="small" + label={label} + color={hackColor} + {...rest} + /> + + ); +}; + +export default FileTag; diff --git a/src/component/FileManager/Explorer/FileTagSummary.tsx b/src/component/FileManager/Explorer/FileTagSummary.tsx new file mode 100755 index 0000000..fcd111f --- /dev/null +++ b/src/component/FileManager/Explorer/FileTagSummary.tsx @@ -0,0 +1,91 @@ +import * as React from "react"; +import { memo, useCallback, useMemo } from "react"; +import { Box, Popover, Stack, useMediaQuery, useTheme } from "@mui/material"; +import { bindHover, bindPopover } from "material-ui-popup-state"; +import { bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import HoverPopover from "material-ui-popup-state/HoverPopover"; +import FileTag, { TagChip } from "./FileTag.tsx"; + +export interface FileTagSummaryProps { + tags: { key: string; value: string }[]; + max?: number; + [key: string]: any; +} + +const FileTagSummary = memo(({ tags, sx, max = 1, ...restProps }: FileTagSummaryProps) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + const popupState = usePopupState({ + variant: "popover", + popupId: "demoMenu", + }); + + const { open, ...rest } = bindPopover(popupState); + const stopPropagation = useCallback((e: any) => e.stopPropagation(), []); + const [shown, hidden] = useMemo(() => { + if (tags.length <= max) { + return [tags, []]; + } + return [tags.slice(0, max), tags.slice(max)]; + }, [tags, max]); + + const { onClick, ...triggerProps } = bindTrigger(popupState); + const onMobileClick = (e: React.MouseEvent) => { + e.stopPropagation(); + onClick(e); + }; + + const PopoverComponent = isMobile ? Popover : HoverPopover; + + return ( + + {shown.map((tag) => ( + + ))} + {hidden.length > 0 && ( + + )} + + {open && ( + + + {hidden.map((tag, i) => ( + + ))} + + + )} + + ); +}); + +export default FileTagSummary; diff --git a/src/component/FileManager/Explorer/FileTypeIcon.tsx b/src/component/FileManager/Explorer/FileTypeIcon.tsx new file mode 100755 index 0000000..9d0c5fa --- /dev/null +++ b/src/component/FileManager/Explorer/FileTypeIcon.tsx @@ -0,0 +1,206 @@ +import { Icon as IconifyIcon } from "@iconify/react/dist/iconify.js"; +import { Android } from "@mui/icons-material"; +import { Box, SvgIconProps, useTheme } from "@mui/material"; +import SvgIcon from "@mui/material/SvgIcon/SvgIcon"; +import { useMemo } from "react"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import { fileExtension } from "../../../util"; +import Book from "../../Icons/Book.tsx"; +import Document from "../../Icons/Document.tsx"; +import DocumentFlowchart from "../../Icons/DocumentFlowchart.tsx"; +import DocumentPDF from "../../Icons/DocumentPDF.tsx"; +import FileExclBox from "../../Icons/FileExclBox.tsx"; +import FilePowerPointBox from "../../Icons/FilePowerPointBox.tsx"; +import FileWordBox from "../../Icons/FileWordBox.tsx"; +import Folder from "../../Icons/Folder.tsx"; +import FolderOutlined from "../../Icons/FolderOutlined.tsx"; +import FolderZip from "../../Icons/FolderZip.tsx"; +import Image from "../../Icons/Image.tsx"; +import LanguageC from "../../Icons/LanguageC.tsx"; +import LanguageCPP from "../../Icons/LanguageCPP.tsx"; +import LanguageGo from "../../Icons/LanguageGo.tsx"; +import LanguageJS from "../../Icons/LanguageJS.tsx"; +import LanguagePython from "../../Icons/LanguagePython.tsx"; +import LanguageRust from "../../Icons/LanguageRust.tsx"; +import MagnetOn from "../../Icons/MagnetOn.tsx"; +import Markdown from "../../Icons/Markdown.tsx"; +import MusicNote1 from "../../Icons/MusicNote1.tsx"; +import Notepad from "../../Icons/Notepad.tsx"; +import Raw from "../../Icons/Raw.tsx"; +import Video from "../../Icons/Video.tsx"; +import Whiteboard from "../../Icons/Whiteboard.tsx"; +import WindowApps from "../../Icons/WindowApps.tsx"; + +export interface FileTypeIconProps extends SvgIconProps { + name: string; + fileType: number; + notLoaded?: boolean; + customizedColor?: string; + [key: string]: any; +} + +export interface FileTypeIconSetting { + exts: string[]; + icon?: string; + iconify?: string; + img?: string; + color?: string; + color_dark?: string; +} + +export interface ExpandedIconSettings { + [key: string]: FileTypeIconSetting; +} + +export const builtInIcons: { + [key: string]: typeof SvgIcon | ((props: SvgIconProps) => JSX.Element); +} = { + audio: MusicNote1, + video: Video, + image: Image, + pdf: DocumentPDF, + word: FileWordBox, + ppt: FilePowerPointBox, + excel: FileExclBox, + text: Notepad, + torrent: MagnetOn, + zip: FolderZip, + exe: WindowApps, + android: Android, + go: LanguageGo, + c: LanguageC, + cpp: LanguageCPP, + js: LanguageJS, + python: LanguagePython, + book: Book, + rust: LanguageRust, + raw: Raw, + flowchart: DocumentFlowchart, + whiteboard: Whiteboard, + markdown: Markdown, +}; + +interface TypeIcon { + icon?: typeof SvgIcon | ((props: SvgIconProps) => JSX.Element); + color?: string; + color_dark?: string; + img?: string; + hideUnknown?: boolean; + reverseDarkMode?: boolean; +} + +interface IconComponentProps { + icon?: typeof SvgIcon | ((props: SvgIconProps) => JSX.Element); + color?: string; + color_dark?: string; + isDefault?: boolean; + img?: string; + iconify?: string; +} + +const FileTypeIcon = ({ + name, + fileType, + notLoaded, + sx, + hideUnknown, + customizedColor, + reverseDarkMode, + ...rest +}: FileTypeIconProps) => { + const theme = useTheme(); + const iconOptions = useAppSelector((state) => state.siteConfig.explorer.typed?.icons) as ExpandedIconSettings; + const IconComponent: IconComponentProps = useMemo(() => { + if (fileType === 1) { + return notLoaded ? { icon: FolderOutlined } : { icon: Folder }; + } + + if (name) { + const fileSuffix = fileExtension(name); + if (fileSuffix && iconOptions) { + const options = iconOptions[fileSuffix]; + if (options) { + const { icon, color, color_dark, img, iconify } = options; + if (icon) { + return { + icon: builtInIcons[icon], + color, + color_dark, + }; + } else if (img) { + return { + img, + }; + } else if (iconify) { + return { + iconify, + color, + color_dark, + }; + } + } + } + } + + return { icon: Document, isDefault: true }; + }, [fileType, name, notLoaded]); + + const iconColor = useMemo(() => { + if (customizedColor) { + return customizedColor; + } + if (theme.palette.mode == (reverseDarkMode ? "light" : "dark")) { + return IconComponent.color_dark ?? IconComponent.color ?? theme.palette.action.active; + } else { + return IconComponent.color ?? theme.palette.action.active; + } + }, [IconComponent, theme, customizedColor]); + + if (IconComponent.icon) { + if (IconComponent.isDefault && hideUnknown) { + return <>; + } + return ( + + ); + } else if (IconComponent.iconify) { + return ( + //@ts-ignore + + ); + } else { + return ( + //@ts-ignore + + ); + } +}; + +export default FileTypeIcon; diff --git a/src/component/FileManager/Explorer/GalleryView/GalleryImage.tsx b/src/component/FileManager/Explorer/GalleryView/GalleryImage.tsx new file mode 100755 index 0000000..7a87a4f --- /dev/null +++ b/src/component/FileManager/Explorer/GalleryView/GalleryImage.tsx @@ -0,0 +1,214 @@ +import { CheckCircle } from "@mui/icons-material"; +import { Box, Fade, IconButton, ImageListItem, ImageListItemBar, lighten, styled } from "@mui/material"; +import React, { memo, useCallback, useEffect, useState } from "react"; +import { TransitionGroup } from "react-transition-group"; +import { FileType, Metadata } from "../../../../api/explorer.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import { fileIconClicked, loadFileThumb } from "../../../../redux/thunks/file.ts"; +import { navigateReconcile } from "../../../../redux/thunks/filemanager.ts"; +import CheckUnchecked from "../../../Icons/CheckUnchecked.tsx"; +import { FileBlockProps } from "../Explorer.tsx"; +import FileIcon from "../FileIcon.tsx"; +import { + LargeIconContainer, + ThumbBox, + ThumbBoxContainer, + ThumbLoadingPlaceholder, + useFileBlockState, +} from "../GridView/GridFile.tsx"; + +const StyledImageListItem = styled(ImageListItem)<{ + transparent?: boolean; + disabled?: boolean; + isDropOver?: boolean; +}>(({ transparent, isDropOver, disabled, theme }) => { + return { + opacity: transparent || disabled ? 0.5 : 1, + pointerEvents: disabled ? "none" : "auto", + cursor: "pointer", + boxShadow: isDropOver ? `0 0 0 2px ${theme.palette.primary.light}` : "none", + transition: theme.transitions.create(["height", "width", "opacity", "box-shadow"]), + }; +}); + +const GalleryImage = memo((props: FileBlockProps) => { + const { file, columns, search, isDragging, isDropOver } = props; + const dispatch = useAppDispatch(); + + const { + fmIndex, + isSelected, + isLoadingIndicator, + noThumb, + uploading, + ref, + inView, + showLock, + fileTag, + onClick, + onDoubleClicked, + hoverStateOff, + hoverStateOn, + onContextMenu, + setRefFunc, + disabled, + fileDisabled, + } = useFileBlockState(props); + + const [hovered, setHovered] = useState(false); + + // undefined: not loaded, null: no thumb + const [thumbSrc, setThumbSrc] = useState(noThumb ? null : undefined); + const [imageLoading, setImageLoading] = useState(true); + + const tryLoadThumbSrc = useCallback(async () => { + const thumbSrc = await dispatch(loadFileThumb(0, file)); + setThumbSrc(thumbSrc); + }, [dispatch, file, setThumbSrc, setImageLoading]); + + const onImgLoadError = useCallback(() => { + setImageLoading(false); + setThumbSrc(null); + }, [setImageLoading, setThumbSrc]); + + useEffect(() => { + if (!inView) { + return; + } + + if (isLoadingIndicator) { + if (file.first) { + dispatch(navigateReconcile(fmIndex, { next_page: true })); + } + return; + } + + if (file.type == FileType.folder) { + return; + } + + if ((file.metadata && file.metadata[Metadata.thumbDisabled] !== undefined) || showLock) { + // No thumb available + setThumbSrc(null); + return; + } + + tryLoadThumbSrc(); + }, [inView]); + + const onIconClick = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + return dispatch(fileIconClicked(fmIndex, file, e)); + }, + [file, dispatch], + ); + + return ( + setHovered(true)} + onMouseLeave={() => setHovered(false)} + > + + {thumbSrc && ( + + + (isSelected ? lighten(theme.palette.primary.light, 0.85) : "initial"), + transition: (theme) => + theme.transitions.create(["padding"], { + duration: theme.transitions.duration.shortest, + }), + }} + > + setImageLoading(false)} + onError={onImgLoadError} + /> + + + + )} + {(thumbSrc === undefined || (thumbSrc && imageLoading)) && ( + + + + )} + {thumbSrc === null && ( + + + + + + )} + + + + + {!isSelected && ( + + + + + + )} + {isSelected && ( + + + + + + )} + + + } + actionPosition="left" + /> + + + ); +}); + +export default GalleryImage; diff --git a/src/component/FileManager/Explorer/GalleryView/GalleryView.tsx b/src/component/FileManager/Explorer/GalleryView/GalleryView.tsx new file mode 100755 index 0000000..da4dc3c --- /dev/null +++ b/src/component/FileManager/Explorer/GalleryView/GalleryView.tsx @@ -0,0 +1,115 @@ +import { Box, ImageList } from "@mui/material"; +import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; +import { mergeRefs } from "../../../../util"; +import DndWrappedFile from "../../Dnd/DndWrappedFile.tsx"; +import { FmIndexContext } from "../../FmIndexContext.tsx"; +import { FmFile, loadingPlaceHolderNumb } from "../GridView/GridView.tsx"; +import GalleryImage from "./GalleryImage.tsx"; + +const GalleryView = React.forwardRef( + ( + { + ...rest + }: { + [key: string]: any; + }, + ref, + ) => { + const { t } = useTranslation("application"); + const dispatch = useAppDispatch(); + const containerRef = useRef(); + const fmIndex = useContext(FmIndexContext); + const [boxHeight, setBoxHeight] = useState(0); + const [col, setCol] = useState(0); + + const files = useAppSelector((state) => state.fileManager[fmIndex].list?.files); + const pagination = useAppSelector((state) => state.fileManager[fmIndex].list?.pagination); + const search_params = useAppSelector((state) => state.fileManager[fmIndex]?.search_params); + const galleryWidth = useAppSelector((state) => state.fileManager[fmIndex].galleryWidth); + + const mergedRef = useCallback( + (val: any) => { + mergeRefs(containerRef, ref)(val); + }, + [containerRef, ref], + ); + + const list = useMemo(() => { + const list: FmFile[] = []; + if (!files) { + return list; + } + + files.forEach((file) => { + list.push(file); + }); + + // Add loading placeholder if there is next page + if (pagination && pagination.next_token) { + for (let i = 0; i < loadingPlaceHolderNumb; i++) { + const id = `loadingPlaceholder-${pagination.next_token}-${i}`; + list.push({ + ...files[0], + path: files[0].path + "/" + id, + id: `loadingPlaceholder-${pagination.next_token}-${i}`, + first: i == 0, + placeholder: true, + }); + } + } + return list; + }, [files, pagination, search_params]); + + const resizeGallery = useCallback( + (containerWidth: number, boxSize: number) => { + const boxCount = Math.floor(containerWidth / boxSize); + const newCols = Math.max(1, boxCount); + const boxHeight = containerWidth / newCols; + setBoxHeight(boxHeight); + setCol(newCols); + }, + [setBoxHeight, setCol], + ); + + useEffect(() => { + if (!containerRef.current) return; + const resizeObserver = new ResizeObserver(() => { + const containerWidth = containerRef.current?.clientWidth ?? 100; + resizeGallery(containerWidth, galleryWidth); + }); + resizeObserver.observe(containerRef.current); + return () => resizeObserver.disconnect(); // clean up + }, [galleryWidth]); + + return ( + + + {boxHeight > 0 && + list.map((file, index) => ( + + ))} + + + ); + }, +); + +export default GalleryView; diff --git a/src/component/FileManager/Explorer/GridView/GridFile.tsx b/src/component/FileManager/Explorer/GridView/GridFile.tsx new file mode 100755 index 0000000..8679b90 --- /dev/null +++ b/src/component/FileManager/Explorer/GridView/GridFile.tsx @@ -0,0 +1,463 @@ +import { + alpha, + Box, + ButtonBase, + Fade, + Skeleton, + styled, + Tooltip, + Typography, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { bindPopover } from "material-ui-popup-state"; +import { usePopupState } from "material-ui-popup-state/hooks"; +import HoverPopover from "material-ui-popup-state/HoverPopover"; +import React, { memo, useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { useInView } from "react-intersection-observer"; +import { TransitionGroup } from "react-transition-group"; +import { FileType, Metadata } from "../../../../api/explorer.ts"; +import { bindDelayedHover } from "../../../../hooks/delayedHover.tsx"; +import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; +import { fileClicked, fileDoubleClicked, loadFileThumb, openFileContextMenu } from "../../../../redux/thunks/file.ts"; +import { fileHovered, navigateReconcile } from "../../../../redux/thunks/filemanager.ts"; +import FileIcon from "../FileIcon.tsx"; +import FileSmallIcon from "../FileSmallIcon.tsx"; +import FileTagSummary from "../FileTagSummary.tsx"; +// @ts-ignore +import Highlighter from "react-highlight-words"; + +import { ContextMenuTypes } from "../../../../redux/fileManagerSlice.ts"; +import { FileManagerIndex } from "../../FileManager.tsx"; +import { FmIndexContext } from "../../FmIndexContext.tsx"; +import { getFileTags } from "../../Sidebar/Tags.tsx"; +import { FileBlockProps } from "../Explorer.tsx"; +import UploadingTag from "../UploadingTag.tsx"; + +const StyledButtonBase = styled(ButtonBase)<{ + selected: boolean; + square?: boolean; + transparent?: boolean; + isDropOver?: boolean; +}>(({ theme, transparent, isDropOver, square, selected }) => { + let bgColor = theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900]; + let bgColorHover = theme.palette.mode === "light" ? theme.palette.grey[300] : theme.palette.grey[700]; + + if (selected) { + bgColor = alpha(theme.palette.primary.main, 0.18); + bgColorHover = bgColor; + } + return { + opacity: transparent ? 0.5 : 1, + borderRadius: theme.shape.borderRadius, + backgroundColor: bgColor, + width: "100%", + display: "flex", + alignItems: "stretch", + transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms", + transitionProperty: "background-color,opacity,box-shadow", + boxShadow: isDropOver ? `0 0 0 2px ${theme.palette.primary.light}` : "none", + "&:hover": { + backgroundColor: bgColorHover, + }, + "&::before": square && { + content: "''", + display: "inline-block", + flex: "0 0 0px", + height: 0, + paddingBottom: "100%", + }, + }; +}); + +const Content = styled(Box)(() => ({ + display: "flex", + flexDirection: "column", + flexGrow: 1, + overflow: "hidden", +})); + +export const Header = styled(Box)(() => ({ + height: 48, + display: "flex", + justifyContent: "left", + alignItems: "initial", + width: "100%", +})); + +const ThumbContainer = styled(Box)(({ theme }) => ({ + flexGrow: "1", + borderRadius: "8px", + height: "100%", + overflow: "hidden", + margin: `0 ${theme.spacing(1)} ${theme.spacing(1)} ${theme.spacing(1)}`, + position: "relative", +})); + +export const FileNameText = styled(Typography)(() => ({ + flexGrow: 1, + textAlign: "left", + textOverflow: "ellipsis", + whiteSpace: "nowrap", + overflow: "hidden", + padding: "14px 12px 14px 0", +})); + +export const ThumbBoxContainer = styled(Box)(() => ({ + position: "absolute", + top: 0, + left: 0, + width: "100%", + height: "100%", +})); + +export const ThumbBox = styled("img")<{ loaded: boolean }>(({ theme, loaded }) => ({ + objectFit: "cover", + width: "100%", + height: "100%", + transition: theme.transitions.create(["opacity", "border-radius"], { + easing: theme.transitions.easing.easeInOut, + duration: theme.transitions.duration.standard, + }), + opacity: loaded ? 1 : 0, + userSelect: "none", + WebkitUserDrag: "none", + MozUserDrag: "none", + msUserDrag: "none", +})); + +export const ThumbLoadingPlaceholder = styled(Skeleton)(() => ({ + borderRadius: "8px", + position: "absolute", + height: "100%", + width: "100%", +})); + +export const LargeIconContainer = styled(Box)(({ theme }) => ({ + display: "flex", + justifyContent: "center", + alignItems: "center", + height: "100%", + backgroundColor: theme.palette.background.default, +})); + +export const ThumbPopoverImg = styled("img")<{ width?: number; height?: number }>(({ width, height }) => ({ + display: "block", + maxWidth: width ?? "initial", + maxHeight: height ?? "initial", + objectFit: "contain", + width: "auto", + height: "auto", + userSelect: "none", + WebkitUserDrag: "none", + MozUserDrag: "none", + msUserDrag: "none", +})); + +export const useFileBlockState = (props: FileBlockProps) => { + const { file, search, dragRef } = props; + const dispatch = useAppDispatch(); + const isTouch = useMediaQuery("(pointer: coarse)"); + const fmIndex = useContext(FmIndexContext); + const isSelected = useAppSelector((state) => state.fileManager[fmIndex].selected[file.path]); + const thumbWidth = useAppSelector((state) => state.siteConfig.explorer.config.thumbnail_width); + const thumbHeight = useAppSelector((state) => state.siteConfig.explorer.config.thumbnail_height); + const isLoadingIndicator = file.placeholder; + const noThumb = + (file.type == FileType.folder || (file.metadata && file.metadata[Metadata.thumbDisabled] != undefined)) && + !isLoadingIndicator; + const uploading = file.metadata && file.metadata[Metadata.upload_session_id] != undefined; + const { ref, inView } = useInView({ + triggerOnce: true, + rootMargin: "200px 0px", + skip: noThumb, + }); + const fileTag = useMemo(() => getFileTags(file), [file]); + + const onClick = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + if (!isLoadingIndicator) { + dispatch(fileClicked(fmIndex, file, e)); + } + }, + [file, dispatch, fmIndex, isLoadingIndicator], + ); + + const onDoubleClicked = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + if (!isLoadingIndicator) { + dispatch(fileDoubleClicked(fmIndex, file, e)); + } + }, + [file, dispatch, fmIndex, isLoadingIndicator], + ); + + const setHoverState = useCallback( + (hovered: boolean) => { + dispatch(fileHovered(fmIndex, file, hovered)); + }, + [dispatch, fmIndex, file], + ); + + const hoverStateOff = useCallback(() => { + if (!isTouch) { + setHoverState(false); + } + }, [setHoverState, isTouch]); + const hoverStateOn = useCallback(() => { + if (!isTouch) { + setHoverState(true); + } + }, [setHoverState, isTouch]); + + const onContextMenu = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + dispatch( + openFileContextMenu(fmIndex, file, false, e, !search ? ContextMenuTypes.file : ContextMenuTypes.searchResult), + ); + }, + [dispatch, file, fmIndex, search], + ); + + const setRefFunc = useCallback( + (e: HTMLElement | null) => { + if (isLoadingIndicator) { + ref(e); + } + + if (dragRef) { + dragRef(e); + } + }, + [dragRef, isLoadingIndicator, ref], + ); + + const fileDisabled = fmIndex == FileManagerIndex.selector && file.type == FileType.file; + const disabled = isLoadingIndicator || fileDisabled; + + return { + onClick, + fmIndex, + isSelected, + isLoadingIndicator, + noThumb, + uploading, + ref, + inView, + fileTag, + onDoubleClicked, + hoverStateOff, + hoverStateOn, + onContextMenu, + setRefFunc, + disabled, + fileDisabled, + thumbWidth, + thumbHeight, + }; +}; + +const GridFile = memo((props: FileBlockProps) => { + const { file, isDragging, isDropOver, search, showThumb, index, dragRef } = props; + const dispatch = useAppDispatch(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + const isTouch = useMediaQuery("(pointer: coarse)"); + const { + fmIndex, + isSelected, + isLoadingIndicator, + noThumb, + uploading, + ref, + inView, + fileTag, + onClick, + onDoubleClicked, + hoverStateOff, + hoverStateOn, + onContextMenu, + setRefFunc, + disabled, + fileDisabled, + thumbWidth, + thumbHeight, + } = useFileBlockState(props); + + const popupState = usePopupState({ + variant: "popover", + popupId: "thumbPreview" + file.id, + }); + + // undefined: not loaded, null: no thumb + const [thumbSrc, setThumbSrc] = useState(noThumb ? null : undefined); + const [imageLoading, setImageLoading] = useState(true); + + const tryLoadThumbSrc = useCallback(async () => { + const thumbSrc = await dispatch(loadFileThumb(0, file)); + setThumbSrc(thumbSrc); + }, [dispatch, file, setThumbSrc, setImageLoading]); + + const onImgLoadError = useCallback(() => { + setImageLoading(false); + setThumbSrc(null); + }, [setImageLoading, setThumbSrc]); + + useEffect(() => { + if (!inView) { + return; + } + + if (isLoadingIndicator) { + if (file.first) { + dispatch(navigateReconcile(fmIndex, { next_page: true })); + } + return; + } + + if (!showThumb || file.type == FileType.folder) { + return; + } + + if (file.metadata && file.metadata[Metadata.thumbDisabled] !== undefined) { + // No thumb available + setThumbSrc(null); + return; + } + + tryLoadThumbSrc(); + }, [inView]); + + const hoverProps = bindDelayedHover(popupState, 800); + const { open: thumbPopoverOpen, ...rest } = bindPopover(popupState); + + const stopPop = useCallback((e: React.MouseEvent) => { + e.stopPropagation(); + }, []); + + return ( + <> + + +
    + + {!isLoadingIndicator && ( + + + {search?.name ? ( + + ) : ( + file.name + )} + + + )} + {!uploading && fileTag && fileTag.length > 0 && ( + + )} + {uploading && } + {isLoadingIndicator && ( + + )} +
    + {showThumb && ( + + + {thumbSrc && ( + + + setImageLoading(false)} + onError={onImgLoadError} + {...(isTouch ? {} : hoverProps)} + /> + + + )} + {(thumbSrc === undefined || (thumbSrc && imageLoading)) && ( + + + + )} + {thumbSrc === null && ( + + + + + + )} + + + )} +
    + {thumbSrc && showThumb && ( + t.zIndex.drawer, + }} + anchorOrigin={{ + vertical: "center", + horizontal: "center", + }} + transformOrigin={{ + vertical: "center", + horizontal: "center", + }} + {...rest} + > + + + )} +
    + + ); +}); + +export default GridFile; diff --git a/src/component/FileManager/Explorer/GridView/GridView.tsx b/src/component/FileManager/Explorer/GridView/GridView.tsx new file mode 100755 index 0000000..e1fed98 --- /dev/null +++ b/src/component/FileManager/Explorer/GridView/GridView.tsx @@ -0,0 +1,150 @@ +import { Box, Grid, Stack, styled, Typography } from "@mui/material"; +import React, { useContext, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { FileResponse, FileType } from "../../../../api/explorer.ts"; +import { useAppSelector } from "../../../../redux/hooks.ts"; +import DndWrappedFile from "../../Dnd/DndWrappedFile.tsx"; + +import { FmIndexContext } from "../../FmIndexContext.tsx"; +import GridFile from "./GridFile.tsx"; + +export interface GridViewProps { + [key: string]: any; +} + +export interface FmFile extends FileResponse { + id: string; + first?: boolean; + placeholder?: boolean; +} + +interface listComponents { + Folders?: JSX.Element[]; + Files: JSX.Element[]; +} + +const AutoFillGrid = styled(Grid)(({ theme }) => ({ + [theme.breakpoints.down("md")]: { + gridTemplateColumns: "repeat(auto-fill,minmax(160px,1fr))!important", + }, + [theme.breakpoints.up("md")]: { + gridTemplateColumns: "repeat(auto-fill,minmax(220px,1fr))!important", + }, + gridGap: theme.spacing(2), + display: "grid!important", + padding: theme.spacing(1), +})); + +const GridItem = styled(Grid)(() => ({ + flex: "1 1 220px!important", +})); + +export const loadingPlaceHolderNumb = 3; + +const GridView = React.forwardRef(({ ...rest }: GridViewProps, ref) => { + const { t } = useTranslation("application"); + const fmIndex = useContext(FmIndexContext); + const files = useAppSelector((state) => state.fileManager[fmIndex].list?.files); + const mixedType = useAppSelector((state) => state.fileManager[fmIndex].list?.mixed_type); + const pagination = useAppSelector((state) => state.fileManager[fmIndex].list?.pagination); + const showThumb = useAppSelector((state) => state.fileManager[fmIndex].showThumb); + const search_params = useAppSelector((state) => state.fileManager[fmIndex]?.search_params); + const list = useMemo(() => { + const list: listComponents = { + Files: [], + }; + if (files) { + files.forEach((file, index) => { + if (file.type === FileType.folder && !mixedType) { + if (!list.Folders) { + list.Folders = []; + } + list.Folders.push( + + + , + ); + } else { + list.Files.push( + + + , + ); + } + }); + + // Add loading placeholder if there is next page + if (pagination && pagination.next_token) { + for (let i = 0; i < loadingPlaceHolderNumb; i++) { + const id = `loadingPlaceholder-${pagination.next_token}-${i}`; + const loadingPlaceholder = ( + + 0 ? showThumb : mixedType} + file={{ + ...files[0], + path: files[0].path + "/" + id, + id: `loadingPlaceholder-${pagination.next_token}-${i}`, + first: i == 0, + placeholder: true, + }} + /> + + ); + const _ = + list.Files.length > 0 ? list.Files.push(loadingPlaceholder) : list.Folders?.push(loadingPlaceholder); + } + } + } + return list; + }, [files, mixedType, pagination, showThumb]); + return ( + + + {list.Folders && list.Folders.length > 0 && ( + + + {t("fileManager.folders")} + + + {list.Folders.map((f) => f)} + + + )} + {list.Files.length > 0 && ( + + {!mixedType && ( + + {t("fileManager.files")} + + )} + + {list.Files.map((f) => f)} + + + )} + + + ); +}); + +export default GridView; diff --git a/src/component/FileManager/Explorer/ListView/AddColumn.tsx b/src/component/FileManager/Explorer/ListView/AddColumn.tsx new file mode 100755 index 0000000..defbf77 --- /dev/null +++ b/src/component/FileManager/Explorer/ListView/AddColumn.tsx @@ -0,0 +1,133 @@ +import { useTranslation } from "react-i18next"; + +import { Icon } from "@iconify/react/dist/iconify.js"; +import { ListItemIcon, Menu } from "@mui/material"; +import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import { useAppSelector } from "../../../../redux/hooks.ts"; +import { SecondaryButton } from "../../../Common/StyledComponents.tsx"; +import Add from "../../../Icons/Add.tsx"; +import { CascadingSubmenu } from "../../ContextMenu/CascadingMenu.tsx"; +import { DenseDivider, SquareMenuItem } from "../../ContextMenu/ContextMenu.tsx"; +import { ColumType, ColumTypeProps, getColumnTypeDefaults, ListViewColumnSetting } from "./Column.tsx"; + +export interface AddColumnProps { + onColumnAdded: (column: ListViewColumnSetting) => void; +} + +const options: ColumType[] = [ + ColumType.name, + ColumType.size, + ColumType.date_modified, + ColumType.date_created, + ColumType.parent, +]; + +const recycleOptions: ColumType[] = [ColumType.recycle_restore_parent, ColumType.recycle_expire]; + +// null => divider +const mediaInfoOptions: (ColumType | null)[] = [ + ColumType.aperture, + ColumType.exposure, + ColumType.iso, + ColumType.focal_length, + ColumType.exposure_bias, + ColumType.flash, + null, + ColumType.camera_make, + ColumType.camera_model, + ColumType.lens_make, + ColumType.lens_model, + null, + ColumType.software, + ColumType.taken_at, + ColumType.image_size, + null, + ColumType.title, + ColumType.artist, + ColumType.album, + ColumType.duration, + null, + ColumType.street, + ColumType.locality, + ColumType.place, + ColumType.district, + ColumType.region, + ColumType.country, +]; + +const AddColumn = (props: AddColumnProps) => { + const { t } = useTranslation(); + const customPropsOptions = useAppSelector((state) => state.siteConfig.explorer?.config?.custom_props); + const conditionPopupState = usePopupState({ + variant: "popover", + popupId: "columns", + }); + const { onClose, ...menuProps } = bindMenu(conditionPopupState); + const onConditionAdd = (type: ColumType, p?: ColumTypeProps) => { + props.onColumnAdded({ type, props: p }); + onClose(); + }; + return ( + <> + } sx={{ px: "15px" }}> + {t("fileManager.addColumn")} + + + {options.map((option, index) => ( + onConditionAdd(option)}> + {t(getColumnTypeDefaults({ type: option }).title)} + + ))} + + {recycleOptions.map((option, index) => ( + onConditionAdd(option)}> + {t(getColumnTypeDefaults({ type: option }).title)} + + ))} + + + {mediaInfoOptions.map((option, index) => + option ? ( + onConditionAdd(option)}> + {t(getColumnTypeDefaults({ type: option }).title)} + + ) : ( + + ), + )} + + {customPropsOptions && customPropsOptions.length > 0 && ( + + {customPropsOptions.map((option, index) => ( + onConditionAdd(ColumType.custom_props, { custom_props_id: option.id })} + > + {option.icon && ( + + + + )} + {t(option.name)} + + ))} + + )} + + + ); +}; + +export default AddColumn; diff --git a/src/component/FileManager/Explorer/ListView/Cell.tsx b/src/component/FileManager/Explorer/ListView/Cell.tsx new file mode 100755 index 0000000..cc60350 --- /dev/null +++ b/src/component/FileManager/Explorer/ListView/Cell.tsx @@ -0,0 +1,381 @@ +import { Box, Fade, PopoverProps, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { memo, useCallback, useEffect, useMemo, useState } from "react"; +import { sizeToString } from "../../../../util"; +import CrUri, { SearchParam } from "../../../../util/uri.ts"; +import FileSmallIcon from "../FileSmallIcon.tsx"; +import { FmFile } from "../GridView/GridView.tsx"; +import { ColumType, ListViewColumn } from "./Column.tsx"; +// @ts-ignore +import dayjs, { Dayjs } from "dayjs"; +import { bindPopover } from "material-ui-popup-state"; +import { usePopupState } from "material-ui-popup-state/hooks"; +import HoverPopover from "material-ui-popup-state/HoverPopover"; +import Highlighter from "react-highlight-words"; +import { useTranslation } from "react-i18next"; +import { TransitionGroup } from "react-transition-group"; +import { FileType, Metadata } from "../../../../api/explorer.ts"; +import { bindDelayedHover } from "../../../../hooks/delayedHover.tsx"; +import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; +import { loadFileThumb } from "../../../../redux/thunks/file.ts"; +import AutoHeight from "../../../Common/AutoHeight.tsx"; +import { NoWrapBox } from "../../../Common/StyledComponents.tsx"; +import TimeBadge from "../../../Common/TimeBadge.tsx"; +import Info from "../../../Icons/Info.tsx"; +import FileBadge from "../../FileBadge.tsx"; +import { CustomPropsItem, customPropsMetadataPrefix } from "../../Sidebar/CustomProps/CustomProps.tsx"; +import { getPropsContent } from "../../Sidebar/CustomProps/CustomPropsItem.tsx"; +import { + getAlbum, + getAperture, + getArtist, + getCameraMake, + getCameraModel, + getCountry, + getDistrict, + getDuration, + getExposure, + getExposureBias, + getFlash, + getFocalLength, + getImageSize, + getIso, + getLensMake, + getLensModel, + getLocality, + getMediaTitle, + getPlace, + getRegion, + getSoftware, + getStreet, + takenAt, +} from "../../Sidebar/MediaInfo.tsx"; +import { MediaMetaElements } from "../../Sidebar/MediaMetaCard.tsx"; +import FileTagSummary from "../FileTagSummary.tsx"; +import { ThumbLoadingPlaceholder, ThumbPopoverImg } from "../GridView/GridFile.tsx"; +import UploadingTag from "../UploadingTag.tsx"; + +export interface CellProps { + file: FmFile; + column: ListViewColumn; + isSelected?: boolean; + search?: SearchParam; + fileTag?: { + key: string; + value: string; + }[]; + uploading?: boolean; + noThumb?: boolean; + thumbWidth?: number; + thumbHeight?: number; +} + +export interface ThumbPopoverProps { + file: FmFile; + popupState: PopoverProps; + thumbWidth?: number; + thumbHeight?: number; +} + +export const ThumbPopover = memo((props: ThumbPopoverProps) => { + const { t } = useTranslation(); + const { + file, + popupState: { open, ...rest }, + thumbWidth, + thumbHeight, + } = props; + + const dispatch = useAppDispatch(); + // undefined: not loaded, null: no thumb + const [thumbSrc, setThumbSrc] = useState(undefined); + const [imageLoading, setImageLoading] = useState(true); + + const tryLoadThumbSrc = useCallback(async () => { + const thumbSrc = await dispatch(loadFileThumb(0, file)); + setThumbSrc(thumbSrc); + }, [dispatch, file, setThumbSrc, setImageLoading]); + + const onImgLoadError = useCallback(() => { + setImageLoading(false); + setThumbSrc(null); + }, [setImageLoading, setThumbSrc]); + + useEffect(() => { + if (open && !thumbSrc) { + tryLoadThumbSrc(); + } + }, [open]); + + const showPlaceholder = thumbSrc === undefined || (thumbSrc && imageLoading); + + return ( + + + + {showPlaceholder && ( + + + + )} + {thumbSrc && ( + + setImageLoading(false)} + onError={onImgLoadError} + src={thumbSrc} + draggable={false} + /> + + )} + {thumbSrc === null && ( + + + + {t("fileManager.failedLoadPreview")} + + + )} + + + + ); +}); + +const FileNameCell = memo((props: CellProps) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + const isTouch = useMediaQuery("(pointer: coarse)"); + const { file, uploading, noThumb, fileTag, search, isSelected, thumbWidth, thumbHeight } = props; + + const popupState = usePopupState({ + variant: "popover", + popupId: "thumbPreview" + file.id, + }); + + const hoverState = bindDelayedHover(popupState, 800); + + return ( + <> + + + + + + + + {search?.name ? ( + + ) : ( + file.name + )} + + + {!uploading && fileTag && fileTag.length > 0 && } + {uploading && } + + {!noThumb && ( + + )} + + ); +}); + +interface FolderSizeCellProps { + file: FmFile; +} + +const FolderSizeCell = memo(({ file }: FolderSizeCellProps) => { + const { t } = useTranslation(); + if (file.type == FileType.folder || file.metadata?.[Metadata.share_redirect]) { + return ; + } + return {sizeToString(file.size)}; +}); + +interface FolderDateCellProps { + file: FmFile; + dateType: "created" | "modified" | "expired"; +} + +const FolderDateCell = memo(({ file, dateType }: FolderDateCellProps) => { + const { t } = useTranslation(); + let datetime: string | Dayjs = ""; + switch (dateType) { + case "created": + datetime = file.created_at; + break; + case "modified": + datetime = file.updated_at; + break; + case "expired": + datetime = file.metadata?.[Metadata.expected_collect_time] + ? dayjs.unix(parseInt(file.metadata?.[Metadata.expected_collect_time])) + : ""; + } + + if (!datetime) { + return ; + } + return ; +}); + +const FolderCell = memo(({ path }: { path: string }) => { + return ( + + ); +}); + +const MediaElementsCell = memo(({ element }: { element?: MediaMetaElements | string }) => { + if (!element) { + return ; + } + if (typeof element === "string") { + return {element}; + } + return ; +}); + +const Cell = memo((props: CellProps) => { + const { t } = useTranslation(); + const customProps = useAppSelector((state) => state.siteConfig.explorer?.config?.custom_props); + const customProp = useMemo(() => { + if (!props.column.props?.custom_props_id || props.column.type !== ColumType.custom_props) { + return undefined; + } + const customProp = customProps?.find((p) => p.id === props.column.props?.custom_props_id); + if (!customProp) { + return undefined; + } + const value = props.file.metadata?.[`${customPropsMetadataPrefix}${customProp.id}`]; + if (value === undefined) { + return undefined; + } + return { + id: customProp.id, + props: customProp, + value: value ?? "", + } as CustomPropsItem; + }, [customProps, props.column.props?.custom_props_id, props.column.type, props.file.metadata]); + + const { file, column, uploading, fileTag, search, isSelected } = props; + switch (column.type) { + case ColumType.name: + return ; + case ColumType.size: + return ; + case ColumType.date_modified: + return ; + case ColumType.date_created: + return ; + case ColumType.parent: { + let crUrl = new CrUri(file.path); + return ; + } + case ColumType.recycle_restore_parent: { + if (!file.metadata?.[Metadata.restore_uri]) { + return ; + } + + let crUrl = new CrUri(file.metadata[Metadata.restore_uri]); + return ; + } + case ColumType.recycle_expire: + return ; + case ColumType.aperture: + return ; + case ColumType.exposure: + return ; + case ColumType.iso: + return ; + case ColumType.camera_make: + return ; + case ColumType.camera_model: + return ; + case ColumType.lens_make: + return ; + case ColumType.lens_model: + return ; + case ColumType.focal_length: + return ; + case ColumType.exposure_bias: + return ; + case ColumType.flash: + return ; + case ColumType.software: + return ; + case ColumType.taken_at: + return ; + case ColumType.image_size: + return ( + {getImageSize(file)?.map((size) => )} + ); + case ColumType.title: + return ; + case ColumType.artist: + return ; + case ColumType.album: + return ; + case ColumType.duration: + return ; + case ColumType.street: + return ; + case ColumType.locality: + return ; + case ColumType.place: + return ; + case ColumType.district: + return ; + case ColumType.region: + return ; + case ColumType.country: + return ; + case ColumType.custom_props: + if (customProp) { + return getPropsContent(customProp, () => {}, false, true); + } + return ; + } +}); + +export default Cell; diff --git a/src/component/FileManager/Explorer/ListView/Column.tsx b/src/component/FileManager/Explorer/ListView/Column.tsx new file mode 100755 index 0000000..a453261 --- /dev/null +++ b/src/component/FileManager/Explorer/ListView/Column.tsx @@ -0,0 +1,332 @@ +import { Box, Fade, IconButton, styled } from "@mui/material"; +import { useCallback, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { CustomProps } from "../../../../api/explorer.ts"; +import { NoWrapTypography } from "../../../Common/StyledComponents.tsx"; +import ArrowSortDownFilled from "../../../Icons/ArrowSortDownFilled.tsx"; +import Divider from "../../../Icons/Divider.tsx"; +import { ResizeProps } from "./ListHeader.tsx"; + +export interface ListViewColumn { + type: ColumType; + width?: number; + defaults: ColumTypeDefaults; + props?: ColumTypeProps; +} + +export interface ListViewColumnSetting { + type: ColumType; + width?: number; + props?: ColumTypeProps; +} + +export interface ColumTypeProps { + metadata_key?: string; + custom_props_id?: string; +} + +export enum ColumType { + name = 0, + date_modified = 1, + size = 2, + metadata = 3, + date_created = 4, + permission = 5, + parent = 6, + recycle_restore_parent = 7, + recycle_expire = 8, + + // Media info + aperture = 9, + exposure = 10, + iso = 11, + camera_make = 12, + camera_model = 13, + lens_make = 14, + lens_model = 15, + focal_length = 16, + exposure_bias = 17, + flash = 18, + software = 19, + taken_at = 20, + image_size = 21, + title = 22, + artist = 23, + album = 24, + duration = 25, + street = 27, + locality = 28, + place = 29, + district = 30, + region = 31, + country = 32, + + // Custom props + custom_props = 26, +} + +export interface ColumTypeDefaults { + title: string; + width: number; + widthMobile?: number; + minWidth?: number; + order_by?: string; +} + +export interface ColumnProps { + index: number; + column: ListViewColumn; + showDivider?: boolean; + startResizing: (props: ResizeProps) => void; + sortable?: boolean; + sortDirection?: string; + setSortBy?: (order_by: string, order_direction: string) => void; +} + +export const ColumnTypeDefaults: { [key: number]: ColumTypeDefaults } = { + [ColumType.name]: { + title: "application:fileManager.name", + widthMobile: 300, + width: 600, + order_by: "name", + }, + [ColumType.size]: { + title: "application:fileManager.size", + width: 100, + order_by: "size", + }, + [ColumType.date_modified]: { + title: "application:fileManager.lastModified", + width: 200, + order_by: "updated_at", + }, + [ColumType.date_created]: { + title: "application:fileManager.createDate", + width: 200, + order_by: "created_at", + }, + [ColumType.parent]: { + title: "application:fileManager.parentFolder", + width: 200, + }, + [ColumType.recycle_restore_parent]: { + title: "application:fileManager.originalLocation", + width: 200, + }, + [ColumType.recycle_expire]: { + title: "application:fileManager.expires", + width: 200, + }, + [ColumType.aperture]: { + title: "application:fileManager.aperture", + width: 100, + }, + [ColumType.exposure]: { + title: "application:fileManager.exposure", + width: 100, + }, + [ColumType.iso]: { + title: "application:fileManager.iso", + width: 100, + }, + [ColumType.camera_make]: { + title: "application:fileManager.cameraMake", + width: 100, + }, + [ColumType.camera_model]: { + title: "application:fileManager.cameraModel", + width: 100, + }, + [ColumType.lens_make]: { + title: "application:fileManager.lensMake", + width: 100, + }, + [ColumType.lens_model]: { + title: "application:fileManager.lensModel", + width: 100, + }, + [ColumType.focal_length]: { + title: "application:fileManager.focalLength", + width: 100, + }, + [ColumType.exposure_bias]: { + title: "application:fileManager.exposureBias", + width: 100, + }, + [ColumType.flash]: { + title: "application:fileManager.flash", + width: 100, + }, + [ColumType.software]: { + title: "application:fileManager.software", + width: 100, + }, + [ColumType.taken_at]: { + title: "application:fileManager.takenAt", + width: 200, + }, + [ColumType.image_size]: { + title: "application:fileManager.resolution", + width: 100, + }, + [ColumType.title]: { + title: "application:fileManager.title", + width: 200, + }, + [ColumType.artist]: { + title: "application:fileManager.artist", + width: 100, + }, + [ColumType.album]: { + title: "application:fileManager.album", + width: 200, + }, + [ColumType.duration]: { + title: "application:fileManager.duration", + width: 100, + }, + [ColumType.street]: { + title: "application:fileManager.street", + width: 100, + }, + [ColumType.locality]: { + title: "application:fileManager.locality", + width: 100, + }, + [ColumType.place]: { + title: "application:fileManager.place", + width: 100, + }, + [ColumType.district]: { + title: "application:fileManager.district", + width: 100, + }, + [ColumType.region]: { + title: "application:fileManager.region", + width: 100, + }, + [ColumType.country]: { + title: "application:fileManager.country", + width: 100, + }, +}; + +export const getColumnTypeDefaults = ( + c: ListViewColumnSetting, + isMobile?: boolean, + customProps?: CustomProps[], +): ColumTypeDefaults => { + if (ColumnTypeDefaults[c.type]) { + return { + ...ColumnTypeDefaults[c.type], + width: + isMobile && ColumnTypeDefaults[c.type].widthMobile + ? ColumnTypeDefaults[c.type].widthMobile + : ColumnTypeDefaults[c.type].width, + }; + } + + if (c.type === ColumType.custom_props) { + const customProp = customProps?.find((p) => p.id === c.props?.custom_props_id); + return { + title: customProp?.name ?? "application:fileManager.customProps", + width: 100, + }; + } + + return { + title: "application:fileManager.metadataColumn", + width: 100, + }; +}; + +const ColumnContainer = styled(Box)<{ + w: number; +}>(({ w }) => ({ + height: "39px", + width: `${w}px`, + display: "flex", + alignItems: "center", + padding: "0 10px", +})); + +const DividerContainer = styled(Box)(({ theme }) => ({ + color: theme.palette.divider, + maxWidth: "10px", + display: "flex", + alignItems: "center", + cursor: "col-resize", + "&:hover": { + color: theme.palette.primary.main, + }, + transition: theme.transitions.create(["color"], { + duration: theme.transitions.duration.shortest, + }), + position: "relative", + right: "-8px", +})); + +const SortArrow = styled(ArrowSortDownFilled)<{ + direction?: string; +}>(({ theme, direction }) => ({ + width: "18px", + height: "18px", + color: !direction ? theme.palette.action.disabled : theme.palette.action.active, + transform: `rotate(${direction === "asc" ? 180 : 0}deg)`, + transition: theme.transitions.create(["color", "transform"], { + duration: theme.transitions.duration.shortest, + }), +})); + +const Column = ({ column, showDivider, index, startResizing, sortDirection, setSortBy, sortable }: ColumnProps) => { + const [showSortButton, setShowSortButton] = useState(false); + const { t } = useTranslation(); + const onSortOptionChange = useCallback(() => { + if (!sortable || !column.defaults.order_by) return; + const newDirection = sortDirection === "asc" ? "desc" : "asc"; + setSortBy && setSortBy(column.defaults.order_by, newDirection); + }, [setSortBy, sortDirection, sortable, column]); + + return ( + + setShowSortButton(!!sortable)} + onMouseLeave={() => setShowSortButton(false)} + onClick={sortable ? onSortOptionChange : undefined} + > + + {t(column.defaults.title, { + metadata: column.props?.metadata_key, + })} + + {sortable && ( + + + + + + )} + + + + startResizing({ + index, + startX: e.clientX, + }) + } + > + + + + + ); +}; + +export default Column; diff --git a/src/component/FileManager/Explorer/ListView/ColumnSetting.tsx b/src/component/FileManager/Explorer/ListView/ColumnSetting.tsx new file mode 100755 index 0000000..55557e6 --- /dev/null +++ b/src/component/FileManager/Explorer/ListView/ColumnSetting.tsx @@ -0,0 +1,228 @@ +import { + Box, + DialogContent, + IconButton, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, +} from "@mui/material"; +import { useSnackbar } from "notistack"; +import type { Dispatch, SetStateAction } from "react"; +import React, { useCallback, useEffect, useState } from "react"; +import { DndProvider, useDrag, useDrop } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; +import { useTranslation } from "react-i18next"; +import { setListViewColumnSettingDialog } from "../../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; +import { applyListColumns } from "../../../../redux/thunks/filemanager.ts"; +import AutoHeight from "../../../Common/AutoHeight.tsx"; +import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx"; +import { StyledTableContainerPaper } from "../../../Common/StyledComponents.tsx"; +import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import ArrowDown from "../../../Icons/ArrowDown.tsx"; +import Dismiss from "../../../Icons/Dismiss.tsx"; +import { FileManagerIndex } from "../../FileManager.tsx"; +import AddColumn from "./AddColumn.tsx"; +import { getColumnTypeDefaults, ListViewColumnSetting } from "./Column.tsx"; + +const DND_TYPE = "column-row"; + +interface DraggableColumnRowProps { + column: ListViewColumnSetting; + index: number; + moveRow: (from: number, to: number) => void; + columns: ListViewColumnSetting[]; + setColumns: Dispatch>; + t: (key: string) => string; + onDelete: (idx: number) => void; + isFirst: boolean; + isLast: boolean; +} + +const DraggableColumnRow: React.FC = ({ + column, + index, + moveRow, + columns, + t, + onDelete, + isFirst, + isLast, +}) => { + const ref = React.useRef(null); + const customProps = useAppSelector((state) => state.siteConfig.explorer?.config?.custom_props); + const [, drop] = useDrop({ + accept: DND_TYPE, + hover(item: any, monitor) { + if (!ref.current) return; + + const dragIndex = item.index; + const hoverIndex = index; + if (dragIndex === hoverIndex) return; + + const hoverBoundingRect = ref.current.getBoundingClientRect(); + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; + const clientOffset = monitor.getClientOffset(); + if (!clientOffset) return; + + const hoverClientY = clientOffset.y - hoverBoundingRect.top; + + if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) return; + if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) return; + + moveRow(dragIndex, hoverIndex); + item.index = hoverIndex; + }, + }); + const [{ isDragging }, drag] = useDrag({ + type: DND_TYPE, + item: { index }, + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + }), + }); + drag(drop(ref)); + return ( + + + {t(getColumnTypeDefaults(column, false, customProps).title)} + + + + moveRow(index, index - 1)} disabled={isFirst}> + + + moveRow(index, index + 1)} disabled={isLast}> + + + onDelete(index)} disabled={columns.length <= 1}> + + + + + + ); +}; + +const ColumnSetting = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + const [columns, setColumns] = useState([]); + + const open = useAppSelector((state) => state.globalState.listViewColumnSettingDialogOpen); + const listViewColumns = useAppSelector((state) => state.fileManager[FileManagerIndex.main].listViewColumns); + + useEffect(() => { + if (open) { + setColumns(listViewColumns ?? []); + } + }, [open]); + + const onClose = useCallback(() => { + dispatch(setListViewColumnSettingDialog(false)); + }, [dispatch]); + + const onSubmitted = useCallback(() => { + if (columns.length > 0) { + dispatch(applyListColumns(FileManagerIndex.main, columns)); + } + dispatch(setListViewColumnSettingDialog(false)); + }, [dispatch, columns]); + + const onColumnAdded = useCallback( + (column: ListViewColumnSetting) => { + const existed = columns.find((c) => c.type === column.type); + if ( + !existed || + existed.props?.metadata_key != column.props?.metadata_key || + existed.props?.custom_props_id != column.props?.custom_props_id + ) { + setColumns((prev) => [...prev, column]); + } else { + enqueueSnackbar(t("application:fileManager.columnExisted"), { + variant: "warning", + action: DefaultCloseAction, + }); + } + }, + [columns], + ); + + return ( + } + dialogProps={{ + open: open ?? false, + onClose: onClose, + fullWidth: true, + maxWidth: "sm", + disableRestoreFocus: true, + }} + > + + + + + + + + {t("fileManager.column")} + {t("fileManager.actions")} + + + + {columns.map((column, index) => ( + { + if (from === to || to < 0 || to >= columns.length) return; + setColumns((prev) => { + const arr = [...prev]; + const [moved] = arr.splice(from, 1); + arr.splice(to, 0, moved); + return arr; + }); + }} + columns={columns} + setColumns={setColumns} + t={t} + onDelete={(idx) => setColumns((prev) => prev.filter((_, i) => i !== idx))} + isFirst={index === 0} + isLast={index === columns.length - 1} + /> + ))} + +
    +
    +
    +
    +
    +
    + ); +}; +export default ColumnSetting; diff --git a/src/component/FileManager/Explorer/ListView/ListBody.tsx b/src/component/FileManager/Explorer/ListView/ListBody.tsx new file mode 100755 index 0000000..2d76ec7 --- /dev/null +++ b/src/component/FileManager/Explorer/ListView/ListBody.tsx @@ -0,0 +1,69 @@ +import { ListViewColumn } from "./Column.tsx"; +import React, { useContext, useMemo } from "react"; +import { FmIndexContext } from "../../FmIndexContext.tsx"; +import { useAppSelector } from "../../../../redux/hooks.ts"; +import { Virtuoso } from "react-virtuoso"; +import DndWrappedFile from "../../Dnd/DndWrappedFile.tsx"; +import Row from "./Row.tsx"; +import { FmFile, loadingPlaceHolderNumb } from "../GridView/GridView.tsx"; + +export interface ListBodyProps { + columns: ListViewColumn[]; +} + +const ListBody = ({ columns }: ListBodyProps) => { + const fmIndex = useContext(FmIndexContext); + const files = useAppSelector((state) => state.fileManager[fmIndex].list?.files); + const mixedType = useAppSelector((state) => state.fileManager[fmIndex].list?.mixed_type); + const pagination = useAppSelector((state) => state.fileManager[fmIndex].list?.pagination); + const search_params = useAppSelector((state) => state.fileManager[fmIndex]?.search_params); + + const list = useMemo(() => { + const list: FmFile[] = []; + if (!files) { + return list; + } + + files.forEach((file) => { + list.push(file); + }); + + // Add loading placeholder if there is next page + if (pagination && pagination.next_token) { + for (let i = 0; i < loadingPlaceHolderNumb; i++) { + const id = `loadingPlaceholder-${pagination.next_token}-${i}`; + list.push({ + ...files[0], + path: files[0].path + "/" + id, + id: `loadingPlaceholder-${pagination.next_token}-${i}`, + first: i == 0, + placeholder: true, + }); + } + } + return list; + }, [files, mixedType, pagination, search_params]); + + return ( + ( + + )} + /> + ); +}; + +export default ListBody; diff --git a/src/component/FileManager/Explorer/ListView/ListHeader.tsx b/src/component/FileManager/Explorer/ListView/ListHeader.tsx new file mode 100755 index 0000000..719ecf8 --- /dev/null +++ b/src/component/FileManager/Explorer/ListView/ListHeader.tsx @@ -0,0 +1,132 @@ +import Column, { ListViewColumn } from "./Column.tsx"; +import { Box, Fade, IconButton, Tooltip } from "@mui/material"; +import { useCallback, useContext, useMemo, useRef, useState } from "react"; +import { FmIndexContext } from "../../FmIndexContext.tsx"; +import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; +import { changeSortOption } from "../../../../redux/thunks/filemanager.ts"; +import SessionManager, { UserSettings } from "../../../../session"; +import { Add } from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { setListViewColumnSettingDialog } from "../../../../redux/globalStateSlice.ts"; + +export interface ListHeaderProps { + columns: ListViewColumn[]; + setColumns: React.Dispatch>; + commitColumnSetting: () => void; +} + +export interface ResizeProps { + index: number; + startX: number; +} + +const ListHeader = ({ setColumns, commitColumnSetting, columns }: ListHeaderProps) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + const [showDivider, setShowDivider] = useState(false); + const resizeProps = useRef(); + const startResizing = (props: ResizeProps) => { + resizeProps.current = props; + document.body.style.cursor = "col-resize"; + window.addEventListener("mousemove", onMouseMove); + window.addEventListener("mouseup", onMouseUp); + }; + + const onMouseMove = useCallback( + (e: MouseEvent) => { + if (!resizeProps.current) { + return; + } + const column = columns[resizeProps.current.index]; + const currentWidth = column.width ?? column.defaults.width; + const minWidth = column.defaults.minWidth ?? 100; + const newWidth = Math.max(minWidth, currentWidth + (e.clientX - resizeProps.current.startX)); + setColumns((prev) => + prev.map((c, index) => (index === resizeProps.current?.index ? { ...c, width: newWidth } : c)), + ); + }, + [columns, setColumns], + ); + + const onMouseUp = useCallback(() => { + document.body.style.removeProperty("cursor"); + window.removeEventListener("mousemove", onMouseMove); + window.removeEventListener("mouseup", onMouseUp); + commitColumnSetting(); + }, [onMouseMove, commitColumnSetting]); + + const fmIndex = useContext(FmIndexContext); + const orderMethodOptions = useAppSelector((state) => state.fileManager[fmIndex].list?.props.order_by_options); + const orderDirectionOption = useAppSelector( + (state) => state.fileManager[fmIndex].list?.props.order_direction_options, + ); + const sortBy = useAppSelector((state) => state.fileManager[fmIndex].sortBy); + const sortDirection = useAppSelector((state) => state.fileManager[fmIndex].sortDirection); + + const allAvailableSortOptions = useMemo((): { + [key: string]: boolean; + } => { + if (!orderMethodOptions || !orderDirectionOption) return {}; + const res: { [key: string]: boolean } = {}; + orderMethodOptions.forEach((method) => { + // make sure orderDirectionOption contains both asc and desc + if (orderDirectionOption.includes("asc") && orderDirectionOption.includes("desc")) { + res[method] = true; + } + }); + return res; + }, [orderMethodOptions, sortDirection]); + + const setSortBy = useCallback( + (order_by: string, order_direction: string) => { + dispatch(changeSortOption(fmIndex, order_by, order_direction)); + }, + [dispatch, fmIndex], + ); + + return ( + setShowDivider(true)} + onMouseLeave={() => setShowDivider(false)} + sx={{ + display: "flex", + borderBottom: (theme) => `1px solid ${theme.palette.divider}`, + }} + > + {columns.map((column, index) => ( + + ))} + + + + dispatch(setListViewColumnSettingDialog(true))} sx={{ ml: 1 }} size={"small"}> + + + + + + + ); +}; + +export default ListHeader; diff --git a/src/component/FileManager/Explorer/ListView/ListView.tsx b/src/component/FileManager/Explorer/ListView/ListView.tsx new file mode 100755 index 0000000..0b83473 --- /dev/null +++ b/src/component/FileManager/Explorer/ListView/ListView.tsx @@ -0,0 +1,98 @@ +import { Box, useMediaQuery, useTheme } from "@mui/material"; +import React, { useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; +import { applyListColumns } from "../../../../redux/thunks/filemanager.ts"; +import { FmIndexContext } from "../../FmIndexContext.tsx"; +import { SearchLimitReached } from "../EmptyFileList.tsx"; +import { getColumnTypeDefaults, ListViewColumn, ListViewColumnSetting } from "./Column.tsx"; +import ListBody from "./ListBody.tsx"; +import ListHeader from "./ListHeader.tsx"; + +const ListView = React.forwardRef( + ( + { + ...rest + }: { + [key: string]: any; + }, + ref, + ) => { + const { t } = useTranslation("application"); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const dispatch = useAppDispatch(); + const fmIndex = useContext(FmIndexContext); + const recursion_limit_reached = useAppSelector((state) => state.fileManager[fmIndex].list?.recursion_limit_reached); + const columnSetting = useAppSelector((state) => state.fileManager[fmIndex].listViewColumns); + const customProps = useAppSelector((state) => state.siteConfig.explorer?.config?.custom_props); + + const [columns, setColumns] = useState( + columnSetting.map( + (c): ListViewColumn => ({ + type: c.type, + width: c.width, + props: c.props, + defaults: getColumnTypeDefaults(c, isMobile, customProps), + }), + ), + ); + + useEffect(() => { + setColumns( + columnSetting.map( + (c): ListViewColumn => ({ + type: c.type, + width: c.width, + props: c.props, + defaults: getColumnTypeDefaults(c, isMobile, customProps), + }), + ), + ); + }, [columnSetting, customProps]); + + const totalWidth = useMemo(() => { + return columns.reduce((acc, column) => acc + (column.width ?? column.defaults.width), 0); + }, [columns]); + + const commitColumnSetting = useCallback(() => { + let settings: ListViewColumnSetting[] = []; + setColumns((prev) => { + settings = [ + ...prev.map((c) => ({ + type: c.type, + width: c.width, + props: c.props, + })), + ]; + return prev; + }); + if (settings.length > 0) { + dispatch(applyListColumns(fmIndex, settings)); + } + }, [dispatch, setColumns]); + + return ( + + + + {recursion_limit_reached && ( + + + + )} + + ); + }, +); + +export default ListView; diff --git a/src/component/FileManager/Explorer/ListView/Row.tsx b/src/component/FileManager/Explorer/ListView/Row.tsx new file mode 100755 index 0000000..4a9321d --- /dev/null +++ b/src/component/FileManager/Explorer/ListView/Row.tsx @@ -0,0 +1,129 @@ +import { alpha, Box, Skeleton, styled } from "@mui/material"; +import { memo, useEffect } from "react"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import { navigateReconcile } from "../../../../redux/thunks/filemanager.ts"; +import { NoWrapTypography } from "../../../Common/StyledComponents.tsx"; +import { FileBlockProps } from "../Explorer.tsx"; +import { useFileBlockState } from "../GridView/GridFile.tsx"; +import Cell from "./Cell.tsx"; + +const RowContainer = styled(Box)<{ + selected: boolean; + transparent?: boolean; + isDropOver?: boolean; + disabled?: boolean; +}>(({ theme, disabled, transparent, isDropOver, selected }) => { + let bgColor = "initial"; + let bgColorHover = theme.palette.action.hover; + + if (selected) { + bgColor = alpha(theme.palette.primary.main, 0.18); + bgColorHover = bgColor; + } + return { + minHeight: "36px", + borderBottom: `1px solid ${theme.palette.divider}`, + display: "flex", + backgroundColor: bgColor, + "&:hover": { + backgroundColor: bgColorHover, + }, + pointerEvents: disabled ? "none" : "auto", + opacity: transparent || disabled ? 0.5 : 1, + transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms", + transitionProperty: "background-color,opacity,box-shadow", + boxShadow: isDropOver ? `inset 0 0 0 2px ${theme.palette.primary.light}` : "none", + }; +}); + +const Column = styled(Box)<{ w: number }>(({ theme, w }) => ({ + display: "flex", + alignItems: "center", + width: `${w}px`, + padding: "0 10px", +})); + +const Row = memo((props: FileBlockProps) => { + const { file, columns, search, isDragging, isDropOver } = props; + const dispatch = useAppDispatch(); + + const { + fmIndex, + isSelected, + isLoadingIndicator, + noThumb, + uploading, + ref, + inView, + showLock, + fileTag, + onClick, + onDoubleClicked, + hoverStateOff, + hoverStateOn, + onContextMenu, + setRefFunc, + disabled, + fileDisabled, + thumbWidth, + thumbHeight, + } = useFileBlockState(props); + + useEffect(() => { + if (!inView) { + return; + } + + if (isLoadingIndicator) { + if (file.first) { + dispatch(navigateReconcile(fmIndex, { next_page: true })); + } + return; + } + }, [inView]); + + return ( + + {columns?.map((column, index) => ( + + + {!file.placeholder && ( + + )} + + {file.placeholder && } + + + ))} + + ); +}); + +export default Row; diff --git a/src/component/FileManager/Explorer/SingleFileView.tsx b/src/component/FileManager/Explorer/SingleFileView.tsx new file mode 100755 index 0000000..8301800 --- /dev/null +++ b/src/component/FileManager/Explorer/SingleFileView.tsx @@ -0,0 +1,281 @@ +import { + Alert, + Box, + Button, + ButtonGroup, + Container, + Divider, + Link, + Stack, + styled, + Typography, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { bindPopover, usePopupState } from "material-ui-popup-state/hooks"; +import React, { forwardRef, useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Link as RouterLink } from "react-router-dom"; +import { FileResponse, Share } from "../../../api/explorer.ts"; +import { bindDelayedHover } from "../../../hooks/delayedHover.tsx"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { downloadSingleFile } from "../../../redux/thunks/download.ts"; +import { createShareShortcut, openFileContextMenu } from "../../../redux/thunks/file.ts"; +import { queueLoadShareInfo } from "../../../redux/thunks/share.ts"; +import { openViewers } from "../../../redux/thunks/viewer.ts"; +import SessionManager from "../../../session/index.ts"; +import { sizeToString } from "../../../util/index.ts"; +import CrUri from "../../../util/uri.ts"; +import { SecondaryButton } from "../../Common/StyledComponents.tsx"; +import UserAvatar from "../../Common/User/UserAvatar.tsx"; +import CaretDown from "../../Icons/CaretDown.tsx"; +import Download from "../../Icons/Download.tsx"; +import Eye from "../../Icons/Eye.tsx"; +import FolderLink from "../../Icons/FolderLink.tsx"; +import Open from "../../Icons/Open.tsx"; +import Timer from "../../Icons/Timer.tsx"; +import useActionDisplayOpt from "../ContextMenu/useActionDisplayOpt.ts"; +import { FmIndexContext } from "../FmIndexContext.tsx"; +import { PropTypography, ShareExpires, ShareStatistics } from "../TopBar/ShareInfoPopover.tsx"; +import FileIcon from "./FileIcon.tsx"; +import FileTagSummary from "./FileTagSummary.tsx"; +import { useFileBlockState } from "./GridView/GridFile.tsx"; +import { ThumbPopover } from "./ListView/Cell.tsx"; + +const ShareContainer = styled(Box)(({ theme }) => ({ + border: `1px solid ${theme.palette.divider}`, + borderRadius: theme.shape.borderRadius, + padding: theme.spacing(2), + width: "100%", + backgroundColor: theme.palette.background.default, + boxShadow: `0 0 10px 0 rgba(0, 0, 0, 0.1)`, +})); + +const FileList = ({ file }: { file: FileResponse }) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const isTouch = useMediaQuery("(pointer: coarse)"); + + const { uploading, noThumb, fileTag, isSelected, thumbWidth, thumbHeight } = useFileBlockState({ + file, + }); + + const user = useMemo(() => { + return SessionManager.currentLoginOrNull(); + }, []); + + const popupState = usePopupState({ + variant: "popover", + popupId: "thumbPreview" + file.id, + }); + + const hoverState = bindDelayedHover(popupState, 800); + const stopPropagation = useCallback((e: React.MouseEvent) => e.stopPropagation(), []); + + return ( + <> + + + + + + + {file?.name}{" "} + {fileTag && fileTag.length > 0 && ( + + )} + + + {sizeToString(file?.size ?? 0)} + + + + {!noThumb && ( + + )} + + ); +}; +const SingleFileView = forwardRef((_props, ref: React.Ref) => { + const { t } = useTranslation(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const dispatch = useAppDispatch(); + const fmIndex = useContext(FmIndexContext); + const file = useAppSelector((state) => state.fileManager[fmIndex].list?.files[0]); + const [loading, setLoading] = useState(false); + const [shareInfo, setShareInfo] = useState(null); + + const displayOpt = useActionDisplayOpt(file ? [file] : []); + + useEffect(() => { + if (file) { + dispatch(queueLoadShareInfo(new CrUri(file.path))) + .then((info) => { + setShareInfo(info); + }) + .catch((_e) => { + setShareInfo(null); + }) + .finally(() => { + setLoading(false); + }); + } else { + setShareInfo(null); + } + }, [file]); + + const openMore = useCallback( + (e: React.MouseEvent) => { + if (file) { + dispatch(openFileContextMenu(fmIndex, file, true, e)); + } + }, + [dispatch, file], + ); + const download = useCallback(async () => { + if (!file) { + return; + } + + setLoading(true); + try { + await dispatch(downloadSingleFile(file)); + } finally { + setLoading(false); + } + }, [file, dispatch]); + + const user = useMemo(() => { + return SessionManager.currentLoginOrNull(); + }, []); + + return ( + + {shareInfo && ( + + + + + + + {shareInfo.owner.nickname} + , + ]} + values={{ nick: shareInfo.owner.nickname, num: 1 }} + /> + + + + + + + + + + {file && } + + + {(shareInfo.remain_downloads || shareInfo.expires) && ( + }> + + + )} + + + + {!!user && file && ( + dispatch(createShareShortcut(fmIndex))} + disabled={loading} + startIcon={} + > + {t("application:fileManager.save")} + + )} + {displayOpt.showOpen && file && ( + dispatch(openViewers(0, file))} + disabled={loading} + startIcon={} + > + {t("application:fileManager.open")} + + )} + + + + + + + + + )} + + ); +}); + +export default SingleFileView; diff --git a/src/component/FileManager/Explorer/UploadingTag.tsx b/src/component/FileManager/Explorer/UploadingTag.tsx new file mode 100755 index 0000000..5cd2289 --- /dev/null +++ b/src/component/FileManager/Explorer/UploadingTag.tsx @@ -0,0 +1,41 @@ +import { Stack } from "@mui/material"; +import { memo, useCallback, useContext } from "react"; +import { useTranslation } from "react-i18next"; +import { Metadata } from "../../../api/explorer.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { searchMetadata } from "../../../redux/thunks/filemanager.ts"; +import { FmIndexContext } from "../FmIndexContext.tsx"; +import { TagChip } from "./FileTag.tsx"; + +export interface UploadingTagProps { + disabled?: boolean; + [key: string]: any; +} + +const FileTagSummary = memo(({ sx, disabled, ...restProps }: UploadingTagProps) => { + const fmIndex = useContext(FmIndexContext); + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const stopPropagation = useCallback((e: any) => { + e.stopPropagation(); + }, []); + const onClick = useCallback( + (e: any) => { + e.stopPropagation(); + dispatch(searchMetadata(fmIndex, Metadata.upload_session_id)); + }, + [dispatch, fmIndex], + ); + return ( + + + + ); +}); + +export default FileTagSummary; diff --git a/src/component/FileManager/FileBadge.tsx b/src/component/FileManager/FileBadge.tsx new file mode 100755 index 0000000..0341c28 --- /dev/null +++ b/src/component/FileManager/FileBadge.tsx @@ -0,0 +1,154 @@ +import { FileResponse, FileType } from "../../api/explorer.ts"; +import FileIcon from "./Explorer/FileIcon.tsx"; +import React, { useMemo } from "react"; +import { Box, ButtonProps, Skeleton, Tooltip } from "@mui/material"; +import { BadgeText, DefaultButton } from "../Common/StyledComponents.tsx"; +import CrUri from "../../util/uri.ts"; +import { useTranslation } from "react-i18next"; +import { usePopupState } from "material-ui-popup-state/hooks"; +import { bindHover, bindPopover } from "material-ui-popup-state"; +import HoverPopover from "material-ui-popup-state/HoverPopover"; +import Breadcrumb from "./TopBar/Breadcrumb.tsx"; +import { useBreadcrumbButtons } from "./TopBar/BreadcrumbButton.tsx"; + +export interface FileBadgeFile { + path: string; + type: number; +} + +export interface FileBadgeProps extends ButtonProps { + file?: FileResponse; + simplifiedFile?: FileBadgeFile; + unknown?: boolean; + clickable?: boolean; +} + +const FileBadge = ({ file, clickable, simplifiedFile, unknown, ...rest }: FileBadgeProps) => { + const { t } = useTranslation(); + const popupState = usePopupState({ + variant: "popover", + popupId: "fileBadge", + }); + const hoverProps = bindHover(popupState); + const popoverProps = bindPopover(popupState); + + const name = useMemo(() => { + if (unknown) { + return t("application:modals.unknownParent"); + } + + if (file?.name) { + return file?.name; + } + + try { + const uri = new CrUri(simplifiedFile?.path ?? ""); + return uri.elements().pop() ?? ""; + } catch (e) { + return ""; + } + }, [file, unknown, simplifiedFile]); + + const f = useMemo(() => { + if (file) { + return file; + } + + return { + name, + type: simplifiedFile?.type ?? FileType.folder, + id: "", + created_at: "", + updated_at: "", + size: 0, + path: simplifiedFile?.path ?? "", + } as FileResponse; + }, [file, unknown, simplifiedFile]); + + const [loading, displayName, startIcon, onClick] = useBreadcrumbButtons({ + name, + is_latest: false, + path: f.path, + }); + + const StartIcon = useMemo(() => { + if (loading) { + return ; + } + if (startIcon?.Icons?.[0]) { + const Icon = startIcon?.Icons?.[0]; + return ; + } + if (startIcon?.Element) { + return startIcon.Element({ sx: { width: 20, height: 20 } }); + } + }, [startIcon, loading]); + + const tooltip = useMemo(() => { + if (unknown) { + return t("application:modals.unknownParentDes"); + } + + return ""; + }, [file, unknown, simplifiedFile]); + + const parent = useMemo(() => { + const uri = simplifiedFile?.path ?? file?.path; + if (!uri) { + return ""; + } + + const crUri = new CrUri(uri); + return crUri.parent().toString(); + }, [file, unknown, simplifiedFile]); + + return ( + <> + + + + {StartIcon ? ( + StartIcon + ) : ( + + )} + + {name == "" ? displayName : name} + + + + {!unknown && ( + + + + + + )} + + ); +}; + +export default FileBadge; diff --git a/src/component/FileManager/FileInfo/ColorCircle/CircleColorSelector.tsx b/src/component/FileManager/FileInfo/ColorCircle/CircleColorSelector.tsx new file mode 100755 index 0000000..8dc319d --- /dev/null +++ b/src/component/FileManager/FileInfo/ColorCircle/CircleColorSelector.tsx @@ -0,0 +1,173 @@ +import { Box, Button, Divider, Popover, styled, Tooltip, useTheme } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { CSSProperties, useCallback, useState } from "react"; +import { bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import { bindPopover } from "material-ui-popup-state"; +import Sketch from "@uiw/react-color-sketch"; + +export interface CircleColorSelectorProps { + colors: string[]; + selectedColor: string; + onChange: (color: string) => void; + showColorValueInCustomization?: boolean; +} + +export const customizeMagicColor = "-"; + +export const SelectorBox = styled(Box)({ + display: "flex", + flexWrap: "wrap", + gap: 4, +}); + +interface ColorCircleProps { + color: string; + selected: boolean; + isCustomization?: boolean; + onClick: (e: React.MouseEvent) => void; + size?: number; + noMb?: boolean; +} + +const ColorCircleBox = styled("div")(({ + color, + selected, + size = 20, + + noMb, +}: { + color: string; + selected: boolean; + size?: number; + noMb?: boolean; +}) => { + return { + cursor: "pointer", + display: "flex", + alignItems: "center", + justifyContent: "center", + width: `${size}px`, + height: `${size}px`, + padding: "3px", + borderRadius: "50%", + marginRight: 0, + marginTop: 0, + marginBottom: noMb ? 0 : "4px", + boxSizing: "border-box", + transform: "scale(1)", + boxShadow: `${color} 0px 0px ${selected ? 5 : 0}px`, + transition: "transform 100ms ease 0s, box-shadow 100ms ease 0s", + background: color, + ":hover": { + transform: "scale(1.2)", + }, + }; +}); + +const ColorCircleBoxChild = styled("div")(({ selected }: { selected: boolean }) => { + const theme = useTheme(); + return { + "--circle-point-background-color": theme.palette.background.default, + height: selected ? "100%" : 0, + width: selected ? "100%" : 0, + borderRadius: "50%", + backgroundColor: "var(--circle-point-background-color)", + boxSizing: "border-box", + transition: "height 100ms ease 0s, width 100ms ease 0s", + transform: "scale(0.5)", + }; +}); + +export const ColorCircle = ({ color, selected, isCustomization, onClick, size, noMb }: ColorCircleProps) => { + const { t } = useTranslation(); + const displayColor = isCustomization + ? "conic-gradient(red, yellow, lime, aqua, blue, magenta, red)" + : color == "" + ? "linear-gradient(45deg, rgba(217,217,217,1) 46%, rgba(217,217,217,1) 47%, rgba(128,128,128,1) 47%)" + : color; + return ( + + + + + + ); +}; + +const CircleColorSelector = (props: CircleColorSelectorProps) => { + const theme = useTheme(); + const { t } = useTranslation(); + const [customizeColor, setCustomizeColor] = useState(props.selectedColor); + const popupState = usePopupState({ + variant: "popover", + popupId: "color-picker", + }); + + const onClick = useCallback( + (color: string) => () => { + if (color === customizeMagicColor) { + return; + } + props.onChange(color); + }, + [props.onChange], + ); + + const { onClose, ...restPopover } = bindPopover(popupState); + const onApply = () => { + onClose(); + onClick(customizeColor)(); + }; + return ( + + {props.colors.map((color) => ( + + ))} + + { + setCustomizeColor(color.hex); + }} + /> + + + + + + + ); +}; + +export default CircleColorSelector; diff --git a/src/component/FileManager/FileInfo/FolderColorQuickAction.tsx b/src/component/FileManager/FileInfo/FolderColorQuickAction.tsx new file mode 100755 index 0000000..80b7d0d --- /dev/null +++ b/src/component/FileManager/FileInfo/FolderColorQuickAction.tsx @@ -0,0 +1,55 @@ +import { Box, BoxProps, Stack, styled, Typography, useTheme } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { useMemo, useState } from "react"; +import { FileResponse, Metadata } from "../../../api/explorer.ts"; +import CircleColorSelector, { customizeMagicColor } from "./ColorCircle/CircleColorSelector.tsx"; +import SessionManager, { UserSettings } from "../../../session"; +import { defaultColors } from "../../../constants"; + +const StyledBox = styled(Box)(({ theme }) => ({ + margin: `0 ${theme.spacing(0.5)}`, + padding: `${theme.spacing(0.5)} ${theme.spacing(1)}`, +})); + +export interface FolderColorQuickActionProps extends BoxProps { + file: FileResponse; + onColorChange: (color?: string) => void; +} + +const FolderColorQuickAction = ({ file, onColorChange, ...rest }: FolderColorQuickActionProps) => { + const { t } = useTranslation(); + const theme = useTheme(); + const [hex, setHex] = useState( + (file.metadata && file.metadata[Metadata.icon_color]) ?? theme.palette.action.active, + ); + const presetColors = useMemo(() => { + const colors = new Set(defaultColors); + + const recentColors = SessionManager.get(UserSettings.UsedCustomizedIconColors) as string[] | undefined; + + if (recentColors) { + recentColors.forEach((color) => { + colors.add(color); + }); + } + + return [...colors]; + }, []); + return ( + + + {t("application:fileManager.folderColor")} + { + onColorChange(color == theme.palette.action.active ? undefined : color); + setHex(color); + }} + /> + + + ); +}; + +export default FolderColorQuickAction; diff --git a/src/component/FileManager/FileManager.tsx b/src/component/FileManager/FileManager.tsx new file mode 100755 index 0000000..f0d91f9 --- /dev/null +++ b/src/component/FileManager/FileManager.tsx @@ -0,0 +1,108 @@ +import { Box, Stack, useMediaQuery, useTheme } from "@mui/material"; +import { useEffect } from "react"; +import { useHotkeys } from "react-hotkeys-hook"; +import useNavigation from "../../hooks/useNavigation.tsx"; +import { clearSelected } from "../../redux/fileManagerSlice.ts"; +import { resetDialogs } from "../../redux/globalStateSlice.ts"; +import { useAppDispatch } from "../../redux/hooks.ts"; +import { resetFm, selectAll, shortCutDelete } from "../../redux/thunks/filemanager.ts"; +import ImageViewer from "../Viewers/ImageViewer/ImageViewer.tsx"; +import Explorer from "./Explorer/Explorer.tsx"; +import { FmIndexContext } from "./FmIndexContext.tsx"; +import PaginationFooter from "./Pagination/PaginationFooter.tsx"; +import { ReadMe } from "./ReadMe/ReadMe.tsx"; +import Sidebar from "./Sidebar/Sidebar.tsx"; +import SidebarDialog from "./Sidebar/SidebarDialog.tsx"; +import NavHeader from "./TopBar/NavHeader.tsx"; + +export const FileManagerIndex = { + main: 0, + selector: 1, +}; + +export interface FileManagerProps { + index?: number; + initialPath?: string; + skipRender?: boolean; +} + +export const FileManager = ({ index = 0, initialPath, skipRender }: FileManagerProps) => { + const dispatch = useAppDispatch(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const isTablet = useMediaQuery(theme.breakpoints.down("md")); + + useNavigation(index, initialPath); + + useEffect(() => { + if (index == FileManagerIndex.main) { + dispatch(resetDialogs()); + return () => { + dispatch(resetFm(index)); + }; + } + }, []); + + const selectAllRef = useHotkeys( + ["Control+a", "Meta+a"], + () => { + dispatch(selectAll(index)); + }, + { enabled: index == FileManagerIndex.main, preventDefault: true }, + ); + + const delRef = useHotkeys( + ["meta+backspace", "delete"], + () => { + dispatch(shortCutDelete(index)); + }, + { enabled: index == FileManagerIndex.main, preventDefault: true }, + ); + + const escRef = useHotkeys( + "esc", + () => { + dispatch(clearSelected({ index, value: {} })); + }, + { enabled: index == FileManagerIndex.main, preventDefault: true }, + ); + + if (skipRender) { + return null; + } + + return ( + + { + e.currentTarget.focus(); + }} + ref={(ref) => { + selectAllRef(ref); + delRef(ref); + escRef(ref); + }} + direction={"column"} + sx={{ + flexGrow: 1, + mb: index == FileManagerIndex.main && !isMobile ? 1 : 0, + overflow: "auto", + "&:focus": { + outline: "none", + }, + }} + tabIndex={0} + spacing={1} + > + + + + {index == FileManagerIndex.main && (isTablet ? : )} + {index == FileManagerIndex.main && } + + + + {index == FileManagerIndex.main && } + + ); +}; diff --git a/src/component/FileManager/FmIndexContext.tsx b/src/component/FileManager/FmIndexContext.tsx new file mode 100755 index 0000000..77bd986 --- /dev/null +++ b/src/component/FileManager/FmIndexContext.tsx @@ -0,0 +1,3 @@ +import { createContext } from "react"; + +export const FmIndexContext = createContext(0); diff --git a/src/component/FileManager/FolderPicker.tsx b/src/component/FileManager/FolderPicker.tsx new file mode 100755 index 0000000..c15d516 --- /dev/null +++ b/src/component/FileManager/FolderPicker.tsx @@ -0,0 +1,70 @@ +import { Box, styled, useMediaQuery, useTheme } from "@mui/material"; +import Grid from "@mui/material/Grid2"; +import { useAppSelector } from "../../redux/hooks.ts"; +import { getFileLinkedUri } from "../../util"; // Grid version 2 +import ContextMenu from "./ContextMenu/ContextMenu.tsx"; +import { FileManager, FileManagerIndex } from "./FileManager.tsx"; +import TreeNavigation from "./TreeView/TreeNavigation.tsx"; + +const StyledGridItem = styled(Grid)(() => ({ + display: "flex", + height: "100%", +})); + +export const useFolderSelector = () => { + const currentPath = useAppSelector((state) => state.fileManager[FileManagerIndex.selector].pure_path); + const selected = useAppSelector((state) => state.fileManager[FileManagerIndex.selector].selected); + + if (selected && Object.keys(selected).length > 0) { + const selectedFile = selected[Object.keys(selected)[0]]; + return [selectedFile, getFileLinkedUri(selectedFile)] as const; + } + + return [undefined, currentPath] as const; +}; + +export interface FolderPickerProps { + disableSharedWithMe?: boolean; + disableTrash?: boolean; + initialPath?: string; +} + +const FolderPicker = ({ disableSharedWithMe, disableTrash, initialPath }: FolderPickerProps) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const path = useAppSelector((state) => state.fileManager[FileManagerIndex.main].path); + + return ( + + + + + + + + + + + + ); +}; +export default FolderPicker; diff --git a/src/component/FileManager/NewButton.tsx b/src/component/FileManager/NewButton.tsx new file mode 100755 index 0000000..f7101d7 --- /dev/null +++ b/src/component/FileManager/NewButton.tsx @@ -0,0 +1,34 @@ +import Add from "../Icons/Add.tsx"; +import { Button, IconButton, useMediaQuery, useTheme } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch } from "../../redux/hooks.ts"; +import { openNewContextMenu } from "../../redux/thunks/filemanager.ts"; +import { FileManagerIndex } from "./FileManager.tsx"; + +const NewButton = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + + if (isMobile) { + return ( + dispatch(openNewContextMenu(FileManagerIndex.main, e))}> + + + ); + } + + return ( + + ); +}; + +export default NewButton; diff --git a/src/component/FileManager/Pagination/PaginationFooter.tsx b/src/component/FileManager/Pagination/PaginationFooter.tsx new file mode 100755 index 0000000..62baec6 --- /dev/null +++ b/src/component/FileManager/Pagination/PaginationFooter.tsx @@ -0,0 +1,76 @@ +import { Box, Pagination, Slide, styled, useMediaQuery, useTheme } from "@mui/material"; +import { forwardRef, useContext } from "react"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { changePage } from "../../../redux/thunks/filemanager.ts"; +import { RadiusFrame } from "../../Frame/RadiusFrame.tsx"; +import { MinPageSize } from "../TopBar/ViewOptionPopover.tsx"; +import PaginationItem from "./PaginationItem.tsx"; + +import { PaginationResults } from "../../../api/explorer.ts"; +import { FmIndexContext } from "../FmIndexContext.tsx"; + +const PaginationFrame = styled(RadiusFrame)(({ theme }) => ({ + padding: theme.spacing(0.5), +})); + +export interface PaginationState { + currentPage: number; + totalPages: number; + usePagination: boolean; + moreItems: boolean; + useEndlessLoading: boolean; + nextToken?: string; +} + +export const usePaginationState = (fmIndex: number) => { + const pagination = useAppSelector((state) => state.fileManager[fmIndex].list?.pagination); + return getPaginationState(pagination); +}; + +export const getPaginationState = (pagination?: PaginationResults) => { + const totalItems = pagination?.total_items; + const page = pagination?.page; + const pageSize = pagination?.page_size; + + const currentPage = (page ?? 0) + 1; + const totalPages = Math.ceil((totalItems ?? 1) / (pageSize && pageSize > 0 ? pageSize : MinPageSize)); + const usePagination = totalPages > 1; + return { + currentPage, + totalPages, + usePagination, + useEndlessLoading: !usePagination, + moreItems: pagination?.next_token || (usePagination && currentPage < totalPages), + nextToken: pagination?.next_token, + } as PaginationState; +}; + +const PaginationFooter = forwardRef((_props, ref) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const dispatch = useAppDispatch(); + const fmIndex = useContext(FmIndexContext); + const paginationState = usePaginationState(fmIndex); + const onPageChange = (_event: unknown, page: number) => { + dispatch(changePage(fmIndex, page - 1)); + }; + + return ( + + + + } + shape="rounded" + color="primary" + count={paginationState.totalPages} + page={paginationState.currentPage} + onChange={onPageChange} + /> + + + + ); +}); + +export default PaginationFooter; diff --git a/src/component/FileManager/Pagination/PaginationItem.tsx b/src/component/FileManager/Pagination/PaginationItem.tsx new file mode 100755 index 0000000..22dc0c2 --- /dev/null +++ b/src/component/FileManager/Pagination/PaginationItem.tsx @@ -0,0 +1,46 @@ +import { PaginationItem, PaginationItemProps, styled } from "@mui/material"; +import { NoOpDropUri, useFileDrag } from "../Dnd/DndWrappedFile.tsx"; +import { useCallback, useEffect, useRef } from "react"; +import { mergeRefs } from "../../../util"; + +let timeOut: ReturnType | undefined = undefined; + +const StyledPaginationItem = styled(PaginationItem)<{ isDropOver?: boolean }>(({ theme, isDropOver }) => ({ + transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms !important", + transitionProperty: "background-color,opacity,box-shadow", + boxShadow: isDropOver ? `inset 0 0 0 2px ${theme.palette.primary.light}` : "none", +})); + +const CustomPaginationItem = (props: PaginationItemProps) => { + const [drag, drop, isOver, isDragging] = useFileDrag({ + dropUri: props.type !== "start-ellipsis" && props.type !== "end-ellipsis" ? NoOpDropUri : undefined, + }); + const buttonRef = useRef(); + + useEffect(() => { + if ( + isOver && + props.onClick && + props.type !== "start-ellipsis" && + props.type !== "end-ellipsis" && + buttonRef.current && + !props.selected + ) { + if (timeOut) { + clearTimeout(timeOut); + } + timeOut = setTimeout(() => buttonRef.current?.click(), 500); + } + }, [isOver]); + + const mergedRef = useCallback( + (val: any) => { + mergeRefs(drop, buttonRef)(val); + }, + [drop, buttonRef], + ); + + return ; +}; + +export default CustomPaginationItem; diff --git a/src/component/FileManager/ReadMe/ReadMe.tsx b/src/component/FileManager/ReadMe/ReadMe.tsx new file mode 100755 index 0000000..303d1a3 --- /dev/null +++ b/src/component/FileManager/ReadMe/ReadMe.tsx @@ -0,0 +1,36 @@ +import { useMediaQuery, useTheme } from "@mui/material"; +import { useContext, useEffect } from "react"; +import { closeShareReadme } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { detectReadMe } from "../../../redux/thunks/share.ts"; +import { FmIndexContext } from "../FmIndexContext.tsx"; +import ReadMeDialog from "./ReadMeDialog.tsx"; +import ReadMeSideBar from "./ReadMeSideBar.tsx"; + +export const ReadMe = () => { + const fmIndex = useContext(FmIndexContext); + const dispatch = useAppDispatch(); + const detect = useAppSelector((state) => state.globalState.shareReadmeDetect); + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down("md")); + + useEffect(() => { + if (detect) { + dispatch(detectReadMe(fmIndex, isTablet)); + } + }, [detect, dispatch]); + + useEffect(() => { + if (detect === 0) { + setTimeout(() => { + dispatch(closeShareReadme()); + }, 500); + } + }, [detect]); + + if (isTablet) { + return ; + } + + return ; +}; diff --git a/src/component/FileManager/ReadMe/ReadMeContent.tsx b/src/component/FileManager/ReadMe/ReadMeContent.tsx new file mode 100755 index 0000000..b64e0e8 --- /dev/null +++ b/src/component/FileManager/ReadMe/ReadMeContent.tsx @@ -0,0 +1,82 @@ +import { Box, Skeleton, useTheme } from "@mui/material"; +import { lazy, Suspense, useCallback, useEffect, useState } from "react"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { getEntityContent } from "../../../redux/thunks/file.ts"; +import { markdownImagePreviewHandler } from "../../../redux/thunks/viewer.ts"; +import Header from "../Sidebar/Header.tsx"; + +const MarkdownEditor = lazy(() => import("../../Viewers/MarkdownEditor/Editor.tsx")); + +const Loading = () => { + return ( + + + + + + + + ); +}; + +const ReadMeContent = () => { + const theme = useTheme(); + const dispatch = useAppDispatch(); + const readMeTarget = useAppSelector((state) => state.globalState.shareReadmeTarget); + const [loading, setLoading] = useState(true); + const [value, setValue] = useState(""); + + useEffect(() => { + if (readMeTarget) { + setLoading(true); + dispatch(getEntityContent(readMeTarget)) + .then((res) => { + const content = new TextDecoder().decode(res); + setValue(content); + setLoading(false); + }) + .catch(() => { + setLoading(false); + }); + } + }, [readMeTarget]); + + const imagePreviewHandler = useCallback( + async (imageSource: string) => { + return dispatch(markdownImagePreviewHandler(imageSource, readMeTarget?.path ?? "")); + }, + [dispatch, readMeTarget], + ); + + return ( + +
    + + {loading && } + {!loading && ( + }> + {}} + initialValue={value} + imagePreviewHandler={imagePreviewHandler} + /> + + )} + + + ); +}; + +export default ReadMeContent; diff --git a/src/component/FileManager/ReadMe/ReadMeDialog.tsx b/src/component/FileManager/ReadMe/ReadMeDialog.tsx new file mode 100755 index 0000000..cbc0a9f --- /dev/null +++ b/src/component/FileManager/ReadMe/ReadMeDialog.tsx @@ -0,0 +1,35 @@ +import { Dialog, Slide } from "@mui/material"; +import { TransitionProps } from "@mui/material/transitions"; +import { forwardRef } from "react"; +import { closeShareReadme } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import ReadMeContent from "./ReadMeContent.tsx"; + +const Transition = forwardRef(function Transition( + props: TransitionProps & { + children: React.ReactElement; + }, + ref: React.Ref, +) { + return ; +}); + +const ReadMeDialog = () => { + const dispatch = useAppDispatch(); + const readMeOpen = useAppSelector((state) => state.globalState.shareReadmeOpen); + + return ( + { + dispatch(closeShareReadme()); + }} + > + + + ); +}; + +export default ReadMeDialog; diff --git a/src/component/FileManager/ReadMe/ReadMeSideBar.tsx b/src/component/FileManager/ReadMe/ReadMeSideBar.tsx new file mode 100755 index 0000000..046a86f --- /dev/null +++ b/src/component/FileManager/ReadMe/ReadMeSideBar.tsx @@ -0,0 +1,28 @@ +import { Box, Collapse } from "@mui/material"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import { RadiusFrame } from "../../Frame/RadiusFrame.tsx"; +import ReadMeContent from "./ReadMeContent.tsx"; + +const ReadMeSideBar = () => { + const readMeOpen = useAppSelector((state) => state.globalState.shareReadmeOpen); + return ( + + + theme.shape.borderRadius / 8, + }} + withBorder={true} + > + + + + + ); +}; + +export default ReadMeSideBar; diff --git a/src/component/FileManager/Search/AdvanceSearch/AddCondition.tsx b/src/component/FileManager/Search/AdvanceSearch/AddCondition.tsx new file mode 100755 index 0000000..8b8e7f8 --- /dev/null +++ b/src/component/FileManager/Search/AdvanceSearch/AddCondition.tsx @@ -0,0 +1,294 @@ +import { useTranslation } from "react-i18next"; + +import { Icon } from "@iconify/react/dist/iconify.js"; +import { ListItemIcon, ListItemText, Menu } from "@mui/material"; +import dayjs from "dayjs"; +import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import { FileType, Metadata } from "../../../../api/explorer.ts"; +import { useAppSelector } from "../../../../redux/hooks.ts"; +import { SecondaryButton } from "../../../Common/StyledComponents.tsx"; +import Add from "../../../Icons/Add.tsx"; +import CalendarClock from "../../../Icons/CalendarClock.tsx"; +import FolderOutlined from "../../../Icons/FolderOutlined.tsx"; +import HardDriveOutlined from "../../../Icons/HardDriveOutlined.tsx"; +import Info from "../../../Icons/Info.tsx"; +import Numbers from "../../../Icons/Numbers.tsx"; +import Tag from "../../../Icons/Tag.tsx"; +import TextBulletListSquareEdit from "../../../Icons/TextBulletListSquareEdit.tsx"; +import TextCaseTitle from "../../../Icons/TextCaseTitle.tsx"; +import { CascadingSubmenu } from "../../ContextMenu/CascadingMenu.tsx"; +import { DenseDivider, SquareMenuItem } from "../../ContextMenu/ContextMenu.tsx"; +import { customPropsMetadataPrefix } from "../../Sidebar/CustomProps/CustomProps.tsx"; +import { Condition, ConditionType } from "./ConditionBox.tsx"; + +export interface AddConditionProps { + onConditionAdd: (condition: Condition) => void; +} + +interface ConditionOption { + name: string; + icon?: JSX.Element; + condition: Condition; +} + +const options: ConditionOption[] = [ + { + name: "application:modals.fileName", + icon: , + condition: { type: ConditionType.name, case_folding: true }, + }, + { + name: "application:navbar.fileType", + icon: , + condition: { type: ConditionType.type, file_type: FileType.file }, + }, + { + name: "application:fileManager.tags", + icon: , + condition: { type: ConditionType.tag }, + }, + { + name: "application:fileManager.metadata", + icon: , + condition: { type: ConditionType.metadata }, + }, + { + name: "application:navbar.fileSize", + icon: , + condition: { type: ConditionType.size, size_lte: 0, size_gte: 0 }, + }, + { + name: "application:fileManager.createDate", + icon: , + condition: { + type: ConditionType.created, + created_gte: dayjs().subtract(7, "d").unix(), + created_lte: dayjs().unix(), + }, + }, + { + name: "application:fileManager.updatedDate", + icon: , + condition: { + type: ConditionType.modified, + updated_gte: dayjs().subtract(7, "d").unix(), + updated_lte: dayjs().unix(), + }, + }, +]; + +const mediaMetaOptions: (ConditionOption | null)[] = [ + { + name: "application:fileManager.title", + condition: { + type: ConditionType.metadata, + metadata_key_readonly: true, + metadata_key: Metadata.music_title, + id: Metadata.music_title, + }, + }, + { + name: "application:fileManager.artist", + condition: { + type: ConditionType.metadata, + metadata_key_readonly: true, + metadata_key: Metadata.music_artist, + id: Metadata.music_artist, + }, + }, + { + name: "application:fileManager.album", + condition: { + type: ConditionType.metadata, + metadata_key_readonly: true, + metadata_key: Metadata.music_album, + id: Metadata.music_album, + }, + }, + null, // divider + { + name: "application:fileManager.cameraMake", + condition: { + type: ConditionType.metadata, + metadata_key_readonly: true, + metadata_key: Metadata.camera_make, + id: Metadata.camera_make, + }, + }, + { + name: "application:fileManager.cameraModel", + condition: { + type: ConditionType.metadata, + metadata_key_readonly: true, + metadata_key: Metadata.camera_model, + id: Metadata.camera_model, + }, + }, + { + name: "application:fileManager.lensMake", + condition: { + type: ConditionType.metadata, + metadata_key_readonly: true, + metadata_key: Metadata.lens_make, + id: Metadata.lens_make, + }, + }, + { + name: "application:fileManager.lensModel", + condition: { + type: ConditionType.metadata, + metadata_key_readonly: true, + metadata_key: Metadata.lens_model, + id: Metadata.lens_model, + }, + }, + null, // divider + { + name: "application:fileManager.street", + condition: { + type: ConditionType.metadata, + metadata_key_readonly: true, + metadata_key: Metadata.street, + id: Metadata.street, + }, + }, + { + name: "application:fileManager.locality", + condition: { + type: ConditionType.metadata, + metadata_key_readonly: true, + metadata_key: Metadata.locality, + id: Metadata.locality, + }, + }, + { + name: "application:fileManager.place", + condition: { + type: ConditionType.metadata, + metadata_key_readonly: true, + metadata_key: Metadata.place, + id: Metadata.place, + }, + }, + { + name: "application:fileManager.district", + condition: { + type: ConditionType.metadata, + metadata_key_readonly: true, + metadata_key: Metadata.district, + id: Metadata.district, + }, + }, + { + name: "application:fileManager.region", + condition: { + type: ConditionType.metadata, + metadata_key_readonly: true, + metadata_key: Metadata.region, + id: Metadata.region, + }, + }, + { + name: "application:fileManager.country", + condition: { + type: ConditionType.metadata, + metadata_key_readonly: true, + metadata_key: Metadata.country, + id: Metadata.country, + }, + }, +]; + +const AddCondition = (props: AddConditionProps) => { + const { t } = useTranslation(); + const customPropsOptions = useAppSelector((state) => state.siteConfig.explorer?.config?.custom_props); + const conditionPopupState = usePopupState({ + variant: "popover", + popupId: "conditions", + }); + const { onClose, ...menuProps } = bindMenu(conditionPopupState); + const onConditionAdd = (condition: Condition) => { + props.onConditionAdd({ + ...condition, + id: condition.type == ConditionType.metadata && !condition.id ? Math.random().toString() : condition.id, + }); + onClose(); + }; + return ( + <> + } sx={{ px: "15px" }}> + {t("navbar.addCondition")} + + + {options.map((option, index) => ( + onConditionAdd(option.condition)}> + {option.icon} + {t(option.name)} + + ))} + } + popupId={"mediaInfo"} + title={t("application:fileManager.mediaInfo")} + > + {mediaMetaOptions.map((option, index) => + option ? ( + onConditionAdd(option.condition)}> + + {t(option.name)} + + + ) : ( + + ), + )} + + {customPropsOptions && customPropsOptions.length > 0 && ( + } + popupId={"customProps"} + title={t("application:fileManager.customProps")} + > + {customPropsOptions.map((option, index) => ( + + onConditionAdd({ + type: ConditionType.metadata, + id: customPropsMetadataPrefix + option.id, + metadata_key: customPropsMetadataPrefix + option.id, + }) + } + > + {option.icon && ( + + + + )} + {t(option.name)} + + ))} + + )} + + + ); +}; + +export default AddCondition; diff --git a/src/component/FileManager/Search/AdvanceSearch/AdvanceSearch.tsx b/src/component/FileManager/Search/AdvanceSearch/AdvanceSearch.tsx new file mode 100755 index 0000000..d173140 --- /dev/null +++ b/src/component/FileManager/Search/AdvanceSearch/AdvanceSearch.tsx @@ -0,0 +1,203 @@ +import { Collapse, DialogContent } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { TransitionGroup } from "react-transition-group"; +import { Metadata } from "../../../../api/explorer.ts"; +import { defaultPath } from "../../../../hooks/useNavigation.tsx"; +import { closeAdvanceSearch } from "../../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; +import { advancedSearch } from "../../../../redux/thunks/filemanager.ts"; +import { SearchParam } from "../../../../util/uri.ts"; +import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx"; +import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import { FileManagerIndex } from "../../FileManager.tsx"; +import AddCondition from "./AddCondition.tsx"; +import ConditionBox, { Condition, ConditionType } from "./ConditionBox.tsx"; + +const searchParamToConditions = (search_params: SearchParam, base: string): Condition[] => { + const applied: Condition[] = [ + { + type: ConditionType.base, + base_uri: base, + }, + ]; + if (search_params.name) { + applied.push({ + type: ConditionType.name, + names: search_params.name, + name_op_or: search_params.name_op_or, + case_folding: search_params.case_folding, + }); + } + + if (search_params.type != undefined) { + applied.push({ + type: ConditionType.type, + file_type: search_params.type, + }); + } + + if (search_params.size_gte != undefined || search_params.size_lte != undefined) { + applied.push({ + type: ConditionType.size, + size_gte: search_params.size_gte, + size_lte: search_params.size_lte, + }); + } + + if (search_params.created_at_gte != undefined || search_params.created_at_lte != undefined) { + applied.push({ + type: ConditionType.created, + created_gte: search_params.created_at_gte, + created_lte: search_params.created_at_lte, + }); + } + + if (search_params.updated_at_gte != undefined || search_params.updated_at_lte != undefined) { + applied.push({ + type: ConditionType.modified, + updated_gte: search_params.updated_at_gte, + updated_lte: search_params.updated_at_lte, + }); + } + + const tags: string[] = []; + if (search_params.metadata) { + Object.entries(search_params.metadata).forEach(([key, value]) => { + if (key.startsWith(Metadata.tag_prefix)) { + tags.push(key.slice(Metadata.tag_prefix.length)); + } else { + applied.push({ + type: ConditionType.metadata, + metadata_key: key, + metadata_value: value, + id: key, + }); + } + }); + } + + if (search_params.metadata_strong_match) { + Object.entries(search_params.metadata_strong_match).forEach(([key, value]) => { + applied.push({ + type: ConditionType.metadata, + metadata_key: key, + metadata_value: value, + id: key, + metadata_strong_match: true, + }); + }); + } + + if (tags.length > 0) { + applied.push({ + type: ConditionType.tag, + tags: tags, + }); + } + + console.log(search_params); + + return applied; +}; + +const AdvanceSearch = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + + const [conditions, setConditions] = useState([]); + const open = useAppSelector((state) => state.globalState.advanceSearchOpen); + const base = useAppSelector((state) => state.globalState.advanceSearchBasePath); + const initialNames = useAppSelector((state) => state.globalState.advanceSearchInitialNameCondition); + const search_params = useAppSelector((state) => state.fileManager[FileManagerIndex.main].search_params); + const current_base = useAppSelector((state) => state.fileManager[FileManagerIndex.main].pure_path); + + const onClose = useCallback(() => { + dispatch(closeAdvanceSearch()); + }, [dispatch]); + + useEffect(() => { + if (open) { + if (initialNames && base) { + setConditions([ + { + type: ConditionType.base, + base_uri: base, + }, + { + type: ConditionType.name, + names: initialNames, + case_folding: true, + }, + ]); + return; + } + + if (search_params) { + const existedConditions = searchParamToConditions(search_params, current_base ?? defaultPath); + if (existedConditions.length > 0) { + setConditions(existedConditions); + } + } + } + }, [open]); + + const onConditionRemove = (condition: Condition) => { + setConditions(conditions.filter((c) => c !== condition)); + }; + + const onConditionAdd = (condition: Condition) => { + if (conditions.find((c) => c.type === condition.type && c.id === condition.id)) { + enqueueSnackbar(t("application:navbar.conditionDuplicate"), { + variant: "warning", + action: DefaultCloseAction, + }); + return; + } + + setConditions([...conditions, condition]); + }; + + const submitSearch = useCallback(() => { + dispatch(advancedSearch(FileManagerIndex.main, conditions)); + }, [dispatch, conditions]); + + return ( + } + dialogProps={{ + open: open ?? false, + onClose: onClose, + fullWidth: true, + maxWidth: "xs", + }} + > + + + {conditions.map((condition, index) => ( + + 2 && condition.type != ConditionType.base ? onConditionRemove : undefined} + condition={condition} + onChange={(condition) => { + const new_conditions = [...conditions]; + new_conditions[index] = condition; + setConditions(new_conditions); + }} + /> + + ))} + + + + ); +}; + +export default AdvanceSearch; diff --git a/src/component/FileManager/Search/AdvanceSearch/ConditionBox.tsx b/src/component/FileManager/Search/AdvanceSearch/ConditionBox.tsx new file mode 100755 index 0000000..f051d86 --- /dev/null +++ b/src/component/FileManager/Search/AdvanceSearch/ConditionBox.tsx @@ -0,0 +1,199 @@ +import { Icon } from "@iconify/react/dist/iconify.js"; +import { Box, Grow, IconButton, Typography } from "@mui/material"; +import { forwardRef, useCallback, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useAppSelector } from "../../../../redux/hooks.ts"; +import CalendarClock from "../../../Icons/CalendarClock.tsx"; +import Dismiss from "../../../Icons/Dismiss.tsx"; +import FolderOutlined from "../../../Icons/FolderOutlined.tsx"; +import HardDriveOutlined from "../../../Icons/HardDriveOutlined.tsx"; +import Numbers from "../../../Icons/Numbers.tsx"; +import Search from "../../../Icons/Search.tsx"; +import Tag from "../../../Icons/Tag.tsx"; +import TextCaseTitle from "../../../Icons/TextCaseTitle.tsx"; +import { customPropsMetadataPrefix } from "../../Sidebar/CustomProps/CustomProps.tsx"; +import { CustomPropsConditon } from "./CustomPropsConditon.tsx"; +import { DateTimeCondition } from "./DateTimeCondition.tsx"; +import { FileNameCondition, StyledBox } from "./FileNameCondition.tsx"; +import { FileTypeCondition } from "./FileTypeCondition.tsx"; +import { MetadataCondition } from "./MetadataCondition.tsx"; +import { SearchBaseCondition } from "./SearchBaseCondition.tsx"; +import { SizeCondition } from "./SizeCondition.tsx"; +import { TagCondition } from "./TagCondition.tsx"; + +export interface Condition { + type: ConditionType; + case_folding?: boolean; + names?: string[]; + name_op_or?: boolean; + file_type?: number; + size_gte?: number; + size_lte?: number; + time?: number; + metadata_key?: string; + metadata_value?: string; + metadata_strong_match?: boolean; + base_uri?: string; + tags?: string[]; + id?: string; + metadata_key_readonly?: boolean; + created_gte?: number; + created_lte?: number; + updated_gte?: number; + updated_lte?: number; +} + +export enum ConditionType { + name, + size, + created, + modified, + type, + metadata, + base, + tag, +} + +export interface ConditionProps { + condition: Condition; + onChange: (condition: Condition) => void; + onRemove?: (condition: Condition) => void; + index: number; +} + +const ConditionBox = forwardRef((props: ConditionProps, ref) => { + const { condition, index, onRemove, onChange } = props; + const customPropsOptions = useAppSelector((state) => state.siteConfig.explorer?.config?.custom_props); + const { t } = useTranslation(); + const [hovered, setHovered] = useState(false); + + const onNameConditionAdded = useCallback( + (_e: any, newValue: string[]) => { + onChange({ + ...condition, + names: newValue, + }); + }, + [onChange], + ); + + const customPropsOption = useMemo(() => { + if ( + condition.type !== ConditionType.metadata || + !condition.metadata_key || + !condition.metadata_key.startsWith(customPropsMetadataPrefix) + ) { + return undefined; + } + return customPropsOptions?.find( + (option) => option.id === condition?.metadata_key?.slice(customPropsMetadataPrefix.length), + ); + }, [customPropsOptions, condition.type, condition.metadata_key]); + + const title = useMemo(() => { + switch (condition.type) { + case ConditionType.base: + return t("application:navbar.searchBase"); + case ConditionType.name: + return t("application:modals.fileName"); + case ConditionType.type: + return t("application:navbar.fileType"); + case ConditionType.tag: + return t("application:fileManager.tags"); + case ConditionType.metadata: + if (customPropsOption) { + return t(customPropsOption.name); + } + return t("application:fileManager.metadata"); + case ConditionType.size: + return t("application:navbar.fileSize"); + case ConditionType.modified: + return t("application:fileManager.updatedDate"); + case ConditionType.created: + return t("application:fileManager.createDate"); + default: + return "Unknown"; + } + }, [t, condition, customPropsOption]); + + const ConditionIcon = useMemo(() => { + switch (condition.type) { + case ConditionType.base: + return Search; + case ConditionType.type: + return FolderOutlined; + case ConditionType.tag: + return Tag; + case ConditionType.metadata: + if (customPropsOption?.icon) { + return customPropsOption?.icon; + } + return Numbers; + case ConditionType.size: + return HardDriveOutlined; + case ConditionType.modified: + case ConditionType.created: + return CalendarClock; + + default: + return TextCaseTitle; + } + }, [condition.type, customPropsOption]); + + return ( + 0 ? 1 : 0, + }} + onMouseEnter={() => setHovered(true)} + onMouseLeave={() => setHovered(false)} + > + + {typeof ConditionIcon !== "string" ? ( + + ) : ( + + )} + {title} + + onRemove(condition) : undefined}> + + + + + + {condition.type == ConditionType.name && ( + + )} + {condition.type == ConditionType.type && } + {condition.type == ConditionType.base && } + {condition.type == ConditionType.tag && } + {condition.type == ConditionType.metadata && !customPropsOption && ( + + )} + {condition.type == ConditionType.metadata && customPropsOption && ( + + )} + {condition.type == ConditionType.size && } + {condition.type == ConditionType.created && ( + + )} + {condition.type == ConditionType.modified && ( + + )} + + + ); +}); + +export default ConditionBox; diff --git a/src/component/FileManager/Search/AdvanceSearch/CustomPropsConditon.tsx b/src/component/FileManager/Search/AdvanceSearch/CustomPropsConditon.tsx new file mode 100755 index 0000000..d95d0cf --- /dev/null +++ b/src/component/FileManager/Search/AdvanceSearch/CustomPropsConditon.tsx @@ -0,0 +1,36 @@ +import { Box } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { CustomProps } from "../../../../api/explorer.ts"; +import { getPropsContent } from "../../Sidebar/CustomProps/CustomPropsItem.tsx"; +import { Condition } from "./ConditionBox.tsx"; + +export const CustomPropsConditon = ({ + condition, + onChange, + option, +}: { + onChange: (condition: Condition) => void; + condition: Condition; + option: CustomProps; +}) => { + const { t } = useTranslation(); + return ( + + {getPropsContent( + { + props: option, + id: option.id, + value: condition.metadata_value ?? "", + }, + (value) => onChange({ ...condition, metadata_value: value }), + false, + false, + true, + )} + + ); +}; diff --git a/src/component/FileManager/Search/AdvanceSearch/DateTimeCondition.tsx b/src/component/FileManager/Search/AdvanceSearch/DateTimeCondition.tsx new file mode 100755 index 0000000..6aad624 --- /dev/null +++ b/src/component/FileManager/Search/AdvanceSearch/DateTimeCondition.tsx @@ -0,0 +1,54 @@ +import { Box } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { Condition } from "./ConditionBox.tsx"; +import { DateTimePicker, LocalizationProvider } from "@mui/x-date-pickers"; +import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; +import dayjs from "dayjs"; + +export const DateTimeCondition = ({ + condition, + onChange, + field, +}: { + onChange: (condition: Condition) => void; + condition: Condition; + field: string; +}) => { + const { t } = useTranslation(); + return ( + + + + onChange({ + ...condition, + [field + "_gte"]: newValue ? newValue.unix() : 0, + }) + } + /> + + onChange({ + ...condition, + [field + "_lte"]: newValue ? newValue.unix() : 0, + }) + } + /> + + + ); +}; diff --git a/src/component/FileManager/Search/AdvanceSearch/FileNameCondition.tsx b/src/component/FileManager/Search/AdvanceSearch/FileNameCondition.tsx new file mode 100755 index 0000000..6e37dc4 --- /dev/null +++ b/src/component/FileManager/Search/AdvanceSearch/FileNameCondition.tsx @@ -0,0 +1,106 @@ +import { Autocomplete, Box, Chip, FormControlLabel, styled } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { FilledTextField, StyledCheckbox } from "../../../Common/StyledComponents.tsx"; +import { Condition } from "./ConditionBox.tsx"; + +export const StyledBox = styled(Box)(({ theme }) => ({ + padding: `${theme.spacing(1)} ${theme.spacing(2)}`, + paddingBottom: theme.spacing(2), + borderRadius: theme.shape.borderRadius, + border: `1px solid ${theme.palette.divider}`, +})); +export const FileNameCondition = ({ + condition, + onChange, + onNameConditionAdded, +}: { + onChange: (condition: Condition) => void; + condition: Condition; + onNameConditionAdded: (_e: any, newValue: string[]) => void; +}) => { + const { t } = useTranslation(); + return ( + <> + + value.map((option: string, index: number) => { + const { key, ...tagProps } = getTagProps({ index }); + return ; + }) + } + renderInput={(params) => ( + + )} + /> + + { + onChange({ + ...condition, + case_folding: e.target.checked, + }); + }} + disableRipple + checked={condition.case_folding} + size="small" + /> + } + label={t("application:navbar.caseFolding")} + /> + { + onChange({ + ...condition, + name_op_or: !e.target.checked, + }); + }} + disableRipple + checked={!condition.name_op_or} + size="small" + /> + } + label={t("application:navbar.notNameOpOr")} + /> + + + ); +}; diff --git a/src/component/FileManager/Search/AdvanceSearch/FileTypeCondition.tsx b/src/component/FileManager/Search/AdvanceSearch/FileTypeCondition.tsx new file mode 100755 index 0000000..7bb5704 --- /dev/null +++ b/src/component/FileManager/Search/AdvanceSearch/FileTypeCondition.tsx @@ -0,0 +1,57 @@ +import { FormControl, ListItemIcon, ListItemText } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { SquareMenuItem } from "../../ContextMenu/ContextMenu.tsx"; +import { FileType } from "../../../../api/explorer.ts"; +import Document from "../../../Icons/Document.tsx"; +import Folder from "../../../Icons/Folder.tsx"; +import { Condition } from "./ConditionBox.tsx"; +import { DenseSelect } from "../../../Common/StyledComponents.tsx"; + +export const FileTypeCondition = ({ + condition, + onChange, +}: { + onChange: (condition: Condition) => void; + condition: Condition; +}) => { + const { t } = useTranslation(); + return ( + + + onChange({ + ...condition, + file_type: e.target.value as number, + }) + } + > + + + + + + {t("application:fileManager.file")} + + + + + + + + {t("application:fileManager.folder")} + + + + + ); +}; diff --git a/src/component/FileManager/Search/AdvanceSearch/MetadataCondition.tsx b/src/component/FileManager/Search/AdvanceSearch/MetadataCondition.tsx new file mode 100755 index 0000000..73b4ea9 --- /dev/null +++ b/src/component/FileManager/Search/AdvanceSearch/MetadataCondition.tsx @@ -0,0 +1,40 @@ +import { Box } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { Condition } from "./ConditionBox.tsx"; +import { FilledTextField } from "../../../Common/StyledComponents.tsx"; + +export const MetadataCondition = ({ + condition, + onChange, +}: { + onChange: (condition: Condition) => void; + condition: Condition; +}) => { + const { t } = useTranslation(); + return ( + + onChange({ ...condition, metadata_key: e.target.value })} + disabled={condition.metadata_key_readonly} + type="text" + fullWidth + /> + onChange({ ...condition, metadata_value: e.target.value })} + type="text" + fullWidth + /> + + ); +}; diff --git a/src/component/FileManager/Search/AdvanceSearch/SearchBaseCondition.tsx b/src/component/FileManager/Search/AdvanceSearch/SearchBaseCondition.tsx new file mode 100755 index 0000000..2a7a1e2 --- /dev/null +++ b/src/component/FileManager/Search/AdvanceSearch/SearchBaseCondition.tsx @@ -0,0 +1,27 @@ +import { PathSelectorForm } from "../../../Common/Form/PathSelectorForm.tsx"; +import { defaultPath } from "../../../../hooks/useNavigation.tsx"; +import { Condition } from "./ConditionBox.tsx"; + +export const SearchBaseCondition = ({ + condition, + onChange, +}: { + onChange: (condition: Condition) => void; + condition: Condition; +}) => { + return ( + onChange({ ...condition, base_uri: path })} + path={condition.base_uri ?? defaultPath} + variant={"searchIn"} + textFieldProps={{ + sx: { + "& .MuiOutlinedInput-input": { + paddingTop: "15.5px", + paddingBottom: "15.5px", + }, + }, + }} + /> + ); +}; diff --git a/src/component/FileManager/Search/AdvanceSearch/SizeCondition.tsx b/src/component/FileManager/Search/AdvanceSearch/SizeCondition.tsx new file mode 100755 index 0000000..ecdaa42 --- /dev/null +++ b/src/component/FileManager/Search/AdvanceSearch/SizeCondition.tsx @@ -0,0 +1,39 @@ +import { Box } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { Condition } from "./ConditionBox.tsx"; +import SizeInput from "../../../Common/SizeInput.tsx"; + +export const SizeCondition = ({ + condition, + onChange, +}: { + onChange: (condition: Condition) => void; + condition: Condition; +}) => { + const { t } = useTranslation(); + return ( + + onChange({ ...condition, size_gte: e })} + inputProps={{ + fullWidth: true, + }} + /> + onChange({ ...condition, size_lte: e })} + inputProps={{ + fullWidth: true, + }} + /> + + ); +}; diff --git a/src/component/FileManager/Search/AdvanceSearch/TagCondition.tsx b/src/component/FileManager/Search/AdvanceSearch/TagCondition.tsx new file mode 100755 index 0000000..d39f0eb --- /dev/null +++ b/src/component/FileManager/Search/AdvanceSearch/TagCondition.tsx @@ -0,0 +1,48 @@ +import { Autocomplete, Chip } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { FilledTextField } from "../../../Common/StyledComponents.tsx"; +import { Condition } from "./ConditionBox.tsx"; + +export const TagCondition = ({ + condition, + onChange, +}: { + onChange: (condition: Condition) => void; + condition: Condition; +}) => { + const { t } = useTranslation(); + return ( + <> + onChange({ ...condition, tags: value })} + renderTags={(value: readonly string[], getTagProps) => + value.map((option: string, index: number) => { + const { key, ...tagProps } = getTagProps({ index }); + return ; + }) + } + renderInput={(params) => ( + + )} + /> + + ); +}; diff --git a/src/component/FileManager/Search/FullSearchOptions.tsx b/src/component/FileManager/Search/FullSearchOptions.tsx new file mode 100755 index 0000000..da95a42 --- /dev/null +++ b/src/component/FileManager/Search/FullSearchOptions.tsx @@ -0,0 +1,76 @@ +import { Box, List, ListItem, ListItemAvatar, ListItemButton, ListItemText } from "@mui/material"; +import { Trans, useTranslation } from "react-i18next"; +import React, { useCallback } from "react"; +// @ts-ignore +import Highlighter from "react-highlight-words"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { SearchOutlined } from "@mui/icons-material"; +import { FileType } from "../../../api/explorer.ts"; +import FileBadge from "../FileBadge.tsx"; +import { quickSearch } from "../../../redux/thunks/filemanager.ts"; +import { FileManagerIndex } from "../FileManager.tsx"; + +export interface FullSearchOptionProps { + options: string[]; + keyword: string; +} + +const FullSearchOption = ({ options, keyword }: FullSearchOptionProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const onClick = useCallback( + (base: string) => () => dispatch(quickSearch(FileManagerIndex.main, base, keyword)), + [keyword, dispatch], + ); + + return ( + + {options.map((option) => ( + + + + theme.palette.action.active, + width: 24, + height: 24, + mt: "7px", + ml: "5px", + }} + /> + + ]} + /> + } + slotProps={{ + primary: { + variant: "body2", + }, + }} + /> + + + + ))} + + ); +}; + +export default FullSearchOption; diff --git a/src/component/FileManager/Search/FuzzySearchResult.tsx b/src/component/FileManager/Search/FuzzySearchResult.tsx new file mode 100755 index 0000000..86b6d59 --- /dev/null +++ b/src/component/FileManager/Search/FuzzySearchResult.tsx @@ -0,0 +1,99 @@ +import { FileResponse, FileType, Metadata } from "../../../api/explorer.ts"; +import { List, ListItem, ListItemAvatar, ListItemButton, ListItemText } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { sizeToString } from "../../../util"; +import FileIcon from "../Explorer/FileIcon.tsx"; +import React, { useCallback } from "react"; +import FileBadge from "../FileBadge.tsx"; +import CrUri from "../../../util/uri.ts"; +// @ts-ignore +import Highlighter from "react-highlight-words"; + +import { openFileContextMenu } from "../../../redux/thunks/file.ts"; +import { FileManagerIndex } from "../FileManager.tsx"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { ContextMenuTypes } from "../../../redux/fileManagerSlice.ts"; + +export interface FuzzySearchResultProps { + files: FileResponse[]; + keyword: string; +} + +const FuzzySearchResult = ({ files, keyword }: FuzzySearchResultProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const getFileTypeText = useCallback( + (file: FileResponse) => { + if (file.metadata?.[Metadata.share_redirect]) { + return t("fileManager.symbolicFile"); + } + + if (file.type == FileType.folder) { + return t("application:fileManager.folder"); + } + return sizeToString(file.size); + }, + [t], + ); + + return ( + + {files.map((file) => ( + + + dispatch(openFileContextMenu(FileManagerIndex.main, file, true, e, ContextMenuTypes.searchResult)) + } + > + + + + + } + secondary={getFileTypeText(file)} + slotProps={{ + primary: { + variant: "body2", + }, + + secondary: { + variant: "body2", + }, + }} + /> + + + + ))} + + ); +}; + +export default FuzzySearchResult; diff --git a/src/component/FileManager/Search/SearchIndicator.tsx b/src/component/FileManager/Search/SearchIndicator.tsx new file mode 100755 index 0000000..7838352 --- /dev/null +++ b/src/component/FileManager/Search/SearchIndicator.tsx @@ -0,0 +1,89 @@ +import { alpha, Button, ButtonGroup, Grow, styled, useMediaQuery, useTheme } from "@mui/material"; +import { useContext, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { clearSearch, openAdvancedSearch } from "../../../redux/thunks/filemanager.ts"; +import Dismiss from "../../Icons/Dismiss.tsx"; +import Search from "../../Icons/Search.tsx"; +import { FileManagerIndex } from "../FileManager.tsx"; +import { FmIndexContext } from "../FmIndexContext.tsx"; + +export const StyledButtonGroup = styled(ButtonGroup)(({ theme }) => ({ + "& .MuiButtonGroup-firstButton, .MuiButtonGroup-lastButton": { + "&:hover": { + border: "none", + }, + }, +})); +export const StyledButton = styled(Button)(({ theme }) => ({ + border: "none", + backgroundColor: alpha(theme.palette.primary.main, 0.1), + "&:hover": { + backgroundColor: alpha(theme.palette.primary.main, 0.2), + }, + fontSize: theme.typography.caption.fontSize, + minWidth: 0, + "& .MuiButton-startIcon": {}, +})); + +export const SearchIndicator = () => { + const { t } = useTranslation(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const dispatch = useAppDispatch(); + const fmIndex = useContext(FmIndexContext); + + const search_params = useAppSelector((state) => state.fileManager[fmIndex].search_params); + + const searchConditionsCount = useMemo(() => { + if (!search_params) { + return 0; + } + + let count = 0; + if (search_params.name) { + count++; + } + if (search_params.metadata) { + count += Object.keys(search_params.metadata).length; + } + if (search_params.type != undefined) { + count++; + } + if (search_params.size_gte || search_params.size_lte) { + count++; + } + if (search_params.created_at_gte || search_params.created_at_lte) { + count++; + } + if (search_params.updated_at_gte || search_params.updated_at_lte) { + count++; + } + if (search_params.metadata_strong_match) { + count += Object.keys(search_params.metadata_strong_match).length; + } + return count; + }, [search_params]); + + return ( + 0}> + + } + onClick={() => dispatch(openAdvancedSearch(fmIndex))} + > + {isMobile + ? searchConditionsCount + : t("fileManager.searchConditions", { + num: searchConditionsCount, + })} + + dispatch(clearSearch(fmIndex))}> + + + + + ); +}; diff --git a/src/component/FileManager/Search/SearchPopup.tsx b/src/component/FileManager/Search/SearchPopup.tsx new file mode 100755 index 0000000..580c8e9 --- /dev/null +++ b/src/component/FileManager/Search/SearchPopup.tsx @@ -0,0 +1,249 @@ +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { setSearchPopup } from "../../../redux/globalStateSlice.ts"; +import { + Box, + debounce, + Dialog, + Divider, + Grow, + IconButton, + styled, + Tooltip, + Typography, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { forwardRef, useCallback, useEffect, useMemo, useState } from "react"; +import { OutlineIconTextField } from "../../Common/Form/OutlineIconTextField.tsx"; +import { SearchOutlined } from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { FileManagerIndex } from "../FileManager.tsx"; +import { FileResponse } from "../../../api/explorer.ts"; +import Fuse from "fuse.js"; +import AutoHeight from "../../Common/AutoHeight.tsx"; +import FuzzySearchResult from "./FuzzySearchResult.tsx"; +import CrUri, { Filesystem } from "../../../util/uri.ts"; +import SessionManager from "../../../session"; +import { defaultPath } from "../../../hooks/useNavigation.tsx"; +import FullSearchOption from "./FullSearchOptions.tsx"; +import { TransitionProps } from "@mui/material/transitions"; +import { openAdvancedSearch, quickSearch } from "../../../redux/thunks/filemanager.ts"; +import Options from "../../Icons/Options.tsx"; + +const StyledDialog = styled(Dialog)<{ + expanded?: boolean; +}>(({ theme, expanded }) => ({ + "& .MuiDialog-container": { + alignItems: "flex-start", + height: expanded ? "100%" : "initial", + }, + zIndex: theme.zIndex.modal - 1, +})); + +const StyledOutlinedIconTextFiled = styled(OutlineIconTextField)(() => ({ + "& .MuiOutlinedInput-notchedOutline": { + border: "none", + }, +})); + +export const GrowDialogTransition = forwardRef(function Transition( + props: TransitionProps & { + children: React.ReactElement; + }, + ref: React.Ref, +) { + return ; +}); + +const SearchPopup = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + + const [keywords, setKeywords] = useState(""); + const [searchedKeyword, setSearchedKeyword] = useState(""); + const [treeSearchResults, setTreeSearchResults] = useState([]); + + const onClose = () => { + dispatch(setSearchPopup(false)); + setKeywords(""); + setSearchedKeyword(""); + }; + + const open = useAppSelector((state) => state.globalState.searchPopupOpen); + const tree = useAppSelector((state) => state.fileManager[FileManagerIndex.main]?.tree); + const path = useAppSelector((state) => state.fileManager[FileManagerIndex.main]?.path); + const single_file_view = useAppSelector((state) => state.fileManager[FileManagerIndex.main]?.list?.single_file_view); + + const searchTree = useMemo( + () => + debounce((request: { input: string }, callback: (results?: FileResponse[]) => void) => { + const options = { + includeScore: true, + // Search in `author` and in `tags` array + keys: ["file.name"], + }; + const fuse = new Fuse(Object.values(tree), options); + const result = fuse.search( + request.input + .split(" ") + .filter((k) => k != "") + .join(" "), + { limit: 50 }, + ); + const res: FileResponse[] = []; + result + .filter((r) => r.item.file != undefined) + .forEach((r) => { + if (r.item.file) { + res.push(r.item.file); + } + }); + callback(res); + }, 400), + [tree], + ); + + useEffect(() => { + let active = true; + + if (keywords === "" || keywords.length < 2) { + setTreeSearchResults([]); + setSearchedKeyword(""); + return undefined; + } + + searchTree({ input: keywords }, (results?: FileResponse[]) => { + if (active) { + setTreeSearchResults(results ?? []); + setSearchedKeyword(keywords); + } + }); + return () => { + active = false; + }; + }, [keywords, setSearchedKeyword, searchTree]); + + const fullSearchOptions = useMemo(() => { + if (!open || !keywords) { + return []; + } + + const res: string[] = []; + const current = new CrUri(path ?? defaultPath); + // current folder - not currently in root + if (!current.is_root()) { + res.push(current.toString()); + } + // current root - not in single file view + if (!single_file_view) { + res.push(current.base()); + } + // my files - user login and not my fs + if (SessionManager.currentLoginOrNull() && !(current.fs() == Filesystem.my)) { + res.push(defaultPath); + } + return res; + }, [open, path, single_file_view, keywords]); + + const onEnter = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + e.stopPropagation(); + e.preventDefault(); + if (fullSearchOptions.length > 0) { + dispatch(quickSearch(FileManagerIndex.main, fullSearchOptions[0], keywords)); + } + } + }, + [fullSearchOptions, keywords], + ); + + return ( + + + } + variant="outlined" + autoFocus + onKeyDown={onEnter} + value={keywords} + onChange={(e) => setKeywords(e.target.value)} + placeholder={t("navbar.searchFiles")} + fullWidth + /> + + dispatch(openAdvancedSearch(FileManagerIndex.main, keywords))} + > + + + + + + {keywords && } + + + {fullSearchOptions.length > 0 && ( + <> + + {t("navbar.searchFilesTitle")} + + + {treeSearchResults.length > 0 && } + + )} + {treeSearchResults.length > 0 && ( + <> + + {t("navbar.recentlyViewed")} + + + + )} + + + + ); +}; + +export default SearchPopup; diff --git a/src/component/FileManager/Sidebar/BasicInfo.tsx b/src/component/FileManager/Sidebar/BasicInfo.tsx new file mode 100755 index 0000000..c29d1fc --- /dev/null +++ b/src/component/FileManager/Sidebar/BasicInfo.tsx @@ -0,0 +1,267 @@ +import { Link, Skeleton, Typography } from "@mui/material"; +import dayjs from "dayjs"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getFileInfo, sendPatchViewSync } from "../../../api/api.ts"; +import { ExplorerView, FileResponse, FileType, FolderSummary, Metadata } from "../../../api/explorer.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import SessionManager from "../../../session/index.ts"; +import { sizeToString } from "../../../util"; +import CrUri from "../../../util/uri.ts"; +import TimeBadge from "../../Common/TimeBadge.tsx"; +import FileBadge from "../FileBadge.tsx"; +import InfoRow from "./InfoRow.tsx"; + +export interface BasicInfoProps { + target: FileResponse; +} + +const BasicInfo = ({ target }: BasicInfoProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + // null: not valid, undefined: not loaded, FolderSummary: loaded + const [folderSummary, setFolderSummary] = useState(null); + useEffect(() => { + setFolderSummary(null); + }, [target]); + + const [viewSetting, setViewSetting] = useState(undefined); + + useEffect(() => { + setViewSetting(target?.extended_info?.view); + }, [target]); + + const isSymbolicLink = useMemo(() => { + return !!(target.metadata && target.metadata[Metadata.share_redirect]); + }, [target.metadata]); + const fileType = useMemo(() => { + let srcType = ""; + switch (target.type) { + case FileType.file: + srcType = t("fileManager.file"); + break; + case FileType.folder: + srcType = t("fileManager.folder"); + break; + default: + srcType = t("fileManager.file"); + } + + if (isSymbolicLink) { + return t("fileManager.symbolicLink", { srcType }); + } + + return srcType; + }, [target, isSymbolicLink, t]); + + const displaySize = useCallback( + (size: number): string => sizeToString(size) + t("fileManager.bytes", { bytes: size.toLocaleString() }), + [t], + ); + + const storagePolicy = useMemo(() => { + if (target.extended_info) { + if (!target.extended_info.storage_policy) { + return t("fileManager.unset"); + } + + return target.extended_info.storage_policy.name; + } + return ; + }, [target.extended_info, t]); + + const targetCrUri = useMemo(() => { + return new CrUri(target.path); + }, [target]); + + const viewSyncEnabled = useMemo(() => { + return !SessionManager.currentLoginOrNull()?.user?.disable_view_sync; + }, [target]); + + const restoreParent = useMemo(() => { + if (!target.metadata || !target.metadata[Metadata.restore_uri]) { + return null; + } + return new CrUri(target.metadata[Metadata.restore_uri]); + }, [target]); + + const getFolderSummary = useCallback(() => { + setFolderSummary(undefined); + dispatch(getFileInfo({ uri: target.path, folder_summary: true })) + .then((res) => { + setFolderSummary(res.folder_summary ?? null); + }) + .catch(() => { + setFolderSummary(null); + }); + }, [target, setFolderSummary, dispatch]); + + const folderSize = useMemo(() => { + if (!folderSummary) { + return ""; + } + + const sizeText = displaySize(folderSummary.size); + if (!folderSummary.completed) { + return t("fileManager.moreThan", { text: sizeText }); + } + return sizeText; + }, [folderSummary, t]); + + const folderChildren = useMemo(() => { + if (!folderSummary) { + return ""; + } + + let files = folderSummary.files.toLocaleString(); + let folders = folderSummary.folders.toLocaleString(); + + if (!folderSummary.completed) { + files += "+"; + folders += "+"; + } + + return t("application:fileManager.folderChildren", { + files, + folders, + }); + }, [folderSummary, t]); + + const handleDeleteViewSetting = useCallback(() => { + dispatch(sendPatchViewSync({ uri: target.path })) + .then(() => { + setViewSetting(undefined); + }) + .catch((error) => { + console.error("Failed to delete view setting:", error); + }); + }, [target.path, dispatch]); + + return ( + <> + + {t("application:fileManager.basicInfo")} + + + + } + /> + {restoreParent && ( + + } + /> + )} + {target.metadata && target.metadata[Metadata.expected_collect_time] && ( + + } + /> + )} + {target.type == FileType.folder && !isSymbolicLink && ( + <> + {!folderSummary && ( + + ) : ( + + {t("fileManager.calculate")} + + ) + } + /> + )} + {folderSummary && ( + <> + + + } + /> + + )} + + )} + {target.type == FileType.file && ( + <> + + + ) + } + /> + + )} + } + /> + } + /> + {target.type == FileType.folder && viewSyncEnabled && target.owned && !restoreParent && !isSymbolicLink && ( + + {t("application:fileManager.saved")}{" "} + { + e.preventDefault(); + handleDeleteViewSetting(); + }} + underline={"hover"} + > + {t("application:fileManager.deleteViewSetting")} + + + ) : ( + t("application:fileManager.notSet") + ) + } + /> + )} + + ); +}; + +export default BasicInfo; diff --git a/src/component/FileManager/Sidebar/CustomProps/AddButton.tsx b/src/component/FileManager/Sidebar/CustomProps/AddButton.tsx new file mode 100755 index 0000000..42e8a6b --- /dev/null +++ b/src/component/FileManager/Sidebar/CustomProps/AddButton.tsx @@ -0,0 +1,109 @@ +import { Icon } from "@iconify/react"; +import { Box, ListItemIcon, ListItemText, Menu, styled, Typography } from "@mui/material"; +import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { CustomProps } from "../../../../api/explorer.ts"; +import Add from "../../../Icons/Add.tsx"; +import { SquareMenuItem } from "../../ContextMenu/ContextMenu.tsx"; +import { CustomPropsItem } from "./CustomProps.tsx"; + +const BorderedCard = styled(Box)(({ theme }) => ({ + border: `1px solid ${theme.palette.divider}`, + borderRadius: theme.shape.borderRadius, + padding: theme.spacing(2), + backgroundColor: theme.palette.background.paper, +})); + +const BorderedCardClickable = styled(BorderedCard)<{ disabled?: boolean }>(({ theme, disabled }) => ({ + cursor: "pointer", + "&:hover": { + backgroundColor: theme.palette.action.hover, + }, + transition: "background-color 0.3s ease", + height: "100%", + borderStyle: "dashed", + display: "flex", + alignItems: "center", + gap: 8, + justifyContent: "center", + color: theme.palette.text.secondary, + opacity: disabled ? 0.5 : 1, + pointerEvents: disabled ? "none" : "auto", +})); + +export interface AddButtonProps { + options: CustomProps[]; + existingPropIds: string[]; + onPropAdd: (prop: CustomPropsItem) => void; + loading?: boolean; + disabled?: boolean; +} + +const AddButton = ({ options, existingPropIds, onPropAdd, disabled }: AddButtonProps) => { + const { t } = useTranslation(); + const propPopupState = usePopupState({ + variant: "popover", + popupId: "customProps", + }); + const { onClose, ...menuProps } = bindMenu(propPopupState); + + const unSelectedOptions = useMemo(() => { + return options?.filter((option) => !existingPropIds.includes(option.id)) ?? []; + }, [options, existingPropIds]); + + const handlePropAdd = (prop: CustomProps) => { + onPropAdd({ + props: prop, + id: prop.id, + value: prop.default ?? "", + }); + onClose(); + }; + + if (unSelectedOptions.length === 0) { + return undefined; + } + + return ( + <> + + + + {t("fileManager.add")} + + + + {unSelectedOptions.map((option) => ( + handlePropAdd(option)}> + {option.icon && ( + + + + )} + + {t(option.name)} + + + ))} + + + ); +}; + +export default AddButton; diff --git a/src/component/FileManager/Sidebar/CustomProps/BooleanPropsContent.tsx b/src/component/FileManager/Sidebar/CustomProps/BooleanPropsContent.tsx new file mode 100755 index 0000000..9258a73 --- /dev/null +++ b/src/component/FileManager/Sidebar/CustomProps/BooleanPropsContent.tsx @@ -0,0 +1,31 @@ +import { Box, FormControlLabel } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { isTrueVal } from "../../../../session/utils.ts"; +import { StyledCheckbox } from "../../../Common/StyledComponents.tsx"; +import { PropsContentProps } from "./CustomPropsItem.tsx"; + +const BooleanPropsItem = ({ prop, onChange, loading, readOnly, fullSize }: PropsContentProps) => { + const { t } = useTranslation(); + + const handleChange = (_: any, checked: boolean) => { + onChange(checked.toString()); + }; + + return ( + + } + label={fullSize ? t(prop.props.name) : undefined} + disabled={readOnly || loading} + /> + + ); +}; + +export default BooleanPropsItem; diff --git a/src/component/FileManager/Sidebar/CustomProps/CustomProps.tsx b/src/component/FileManager/Sidebar/CustomProps/CustomProps.tsx new file mode 100755 index 0000000..5883e0f --- /dev/null +++ b/src/component/FileManager/Sidebar/CustomProps/CustomProps.tsx @@ -0,0 +1,126 @@ +import { Collapse, Stack, Typography } from "@mui/material"; +import { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { TransitionGroup } from "react-transition-group"; +import { sendMetadataPatch } from "../../../../api/api.ts"; +import { CustomProps as CustomPropsType, FileResponse } from "../../../../api/explorer.ts"; +import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; +import { DisplayOption } from "../../ContextMenu/useActionDisplayOpt.ts"; +import AddButton from "./AddButton.tsx"; +import CustomPropsCard from "./CustomPropsItem.tsx"; + +export interface CustomPropsProps { + file: FileResponse; + setTarget: (target: FileResponse | undefined | null) => void; + targetDisplayOptions?: DisplayOption; +} + +export interface CustomPropsItem { + props: CustomPropsType; + id: string; + value: string; +} + +export const customPropsMetadataPrefix = "props:"; + +const CustomProps = ({ file, setTarget, targetDisplayOptions }: CustomPropsProps) => { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const dispatch = useAppDispatch(); + const custom_props = useAppSelector((state) => state.siteConfig.explorer?.config?.custom_props); + + const existingProps = useMemo(() => { + if (!file.metadata) { + return []; + } + return Object.keys(file.metadata) + .filter((key) => key.startsWith(customPropsMetadataPrefix)) + .map((key) => { + const propId = key.slice(customPropsMetadataPrefix.length); + return { + id: propId, + props: custom_props?.find((prop) => prop.id === propId), + value: file.metadata?.[key] ?? "", + } as CustomPropsItem; + }); + }, [file.metadata]); + + const existingPropIds = useMemo(() => { + return existingProps?.map((prop) => prop.id) ?? []; + }, [existingProps]); + + const handlePropPatch = (remove?: boolean) => (props: CustomPropsItem[]) => { + setLoading(true); + dispatch( + sendMetadataPatch({ + uris: [file.path], + patches: props.map((prop) => ({ + key: customPropsMetadataPrefix + prop.id, + value: prop.value, + remove, + })), + }), + ) + .then(() => { + if (remove) { + const newMetadata = { ...file.metadata }; + props.forEach((prop) => { + delete newMetadata[customPropsMetadataPrefix + prop.id]; + }); + setTarget({ ...file, metadata: newMetadata }); + } else { + setTarget({ + ...file, + metadata: { + ...file.metadata, + ...Object.assign({}, ...props.map((prop) => ({ [customPropsMetadataPrefix + prop.id]: prop.value }))), + }, + }); + } + }) + .finally(() => { + setLoading(false); + }); + }; + + if (existingProps.length === 0 && (!custom_props || custom_props.length === 0)) { + return undefined; + } + + return ( + + + {t("fileManager.customProps")} + + { + handlePropPatch(false)([prop]); + }} + /> + + {existingProps.map((prop, index) => ( + + { + handlePropPatch(false)([prop]); + }} + onPropDelete={(prop) => { + handlePropPatch(true)([prop]); + }} + readOnly={!targetDisplayOptions?.showCustomProps} + /> + + ))} + + + ); +}; + +export default CustomProps; diff --git a/src/component/FileManager/Sidebar/CustomProps/CustomPropsItem.tsx b/src/component/FileManager/Sidebar/CustomProps/CustomPropsItem.tsx new file mode 100755 index 0000000..210250e --- /dev/null +++ b/src/component/FileManager/Sidebar/CustomProps/CustomPropsItem.tsx @@ -0,0 +1,216 @@ +import { Icon } from "@iconify/react/dist/iconify.js"; +import { + alpha, + Box, + Grow, + IconButton, + ListItemIcon, + ListItemText, + Menu, + styled, + Typography, + useTheme, +} from "@mui/material"; +import { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { CustomProps, CustomPropsType } from "../../../../api/explorer.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import { searchMetadata } from "../../../../redux/thunks/filemanager.ts"; +import { copyToClipboard } from "../../../../util"; +import Clipboard from "../../../Icons/Clipboard.tsx"; +import DeleteOutlined from "../../../Icons/DeleteOutlined.tsx"; +import MoreVertical from "../../../Icons/MoreVertical.tsx"; +import Search from "../../../Icons/Search.tsx"; +import { SquareMenuItem } from "../../ContextMenu/ContextMenu.tsx"; +import { FileManagerIndex } from "../../FileManager.tsx"; +import { StyledButtonBase } from "../MediaMetaCard.tsx"; +import BooleanPropsItem from "./BooleanPropsContent.tsx"; +import { CustomPropsItem } from "./CustomProps.tsx"; +import LinkPropsContent from "./LinkPropsContent.tsx"; +import MultiSelectPropsContent from "./MultiSelectPropsContent.tsx"; +import NumberPropsContent from "./NumberPropsContent.tsx"; +import RatingPropsItem from "./RatingPropsItem.tsx"; +import SelectPropsContent from "./SelectPropsContent.tsx"; +import TextPropsContent from "./TextPropsContent.tsx"; +import UserPropsContent from "./UserPropsContent.tsx"; + +export interface CustomPropsCardProps { + prop: CustomPropsItem; + loading?: boolean; + onPropPatch: (prop: CustomPropsItem) => void; + onPropDelete?: (prop: CustomPropsItem) => void; + readOnly?: boolean; +} + +export interface PropsContentProps { + prop: CustomPropsItem; + onChange: (value: string) => void; + loading?: boolean; + readOnly?: boolean; + backgroundColor?: string; + fullSize?: boolean; +} + +const PropsCard = styled(StyledButtonBase)(({ theme }) => ({ + flexDirection: "column", + alignItems: "flex-start", + gap: 9, +})); + +export const getPropsContent = ( + prop: CustomPropsItem, + onChange: (value: string) => void, + loading?: boolean, + readOnly?: boolean, + fullSize?: boolean, +) => { + switch (prop.props.type) { + case CustomPropsType.text: + return ( + + ); + case CustomPropsType.rating: + return ( + + ); + case CustomPropsType.number: + return ( + + ); + case CustomPropsType.boolean: + return ( + + ); + case CustomPropsType.select: + return ( + + ); + case CustomPropsType.multi_select: + return ( + + ); + case CustomPropsType.link: + return ( + + ); + default: + return null; + } +}; + +export const isCustomPropStrongMatch = (prop: CustomProps) => { + return prop.type === CustomPropsType.rating || prop.type === CustomPropsType.number; +}; + +const CustomPropsCard = ({ prop, loading, onPropPatch, onPropDelete, readOnly }: CustomPropsCardProps) => { + const { t } = useTranslation(); + const theme = useTheme(); + const dispatch = useAppDispatch(); + const [mouseOver, setMouseOver] = useState(false); + const [anchorEl, setAnchorEl] = useState(null); + + const handleMenuClose = () => { + setAnchorEl(null); + }; + + const handleCopy = () => { + const value = prop.value || ""; + copyToClipboard(value); + handleMenuClose(); + }; + + const handleSearch = () => { + if (prop.value) { + dispatch( + searchMetadata( + FileManagerIndex.main, + `props:${prop.props.id}`, + prop.value, + false, + isCustomPropStrongMatch(prop.props), + ), + ); + } + handleMenuClose(); + }; + + const handleDelete = () => { + if (onPropDelete) { + onPropDelete(prop); + } + handleMenuClose(); + }; + + const Content = useMemo(() => { + return getPropsContent(prop, (value) => onPropPatch({ ...prop, value }), loading, readOnly, true); + }, [prop, loading, onPropPatch, readOnly]); + + return ( + setMouseOver(true)} onMouseLeave={() => setMouseOver(false)}> + + + + setAnchorEl(e.currentTarget)}> + + + + + {prop.props.icon && } + + {prop.props.type === CustomPropsType.boolean ? Content : t(prop.props.name)} + + + + {prop.props.type !== CustomPropsType.boolean && ( + + {Content} + + )} + + + {prop.value && ( + <> + + + + + {t("application:fileManager.copyToClipboard")} + + + + + + {t("application:fileManager.searchProperty")} + + + )} + + + + + + {t("application:fileManager.delete")} + + + + ); +}; + +export default CustomPropsCard; diff --git a/src/component/FileManager/Sidebar/CustomProps/LinkPropsContent.tsx b/src/component/FileManager/Sidebar/CustomProps/LinkPropsContent.tsx new file mode 100755 index 0000000..98ff3a0 --- /dev/null +++ b/src/component/FileManager/Sidebar/CustomProps/LinkPropsContent.tsx @@ -0,0 +1,115 @@ +import { Box, IconButton, Link, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { NoLabelFilledTextField } from "../../../Common/StyledComponents.tsx"; +import Edit from "../../../Icons/Edit.tsx"; +import { PropsContentProps } from "./CustomPropsItem.tsx"; + +const LinkPropsContent = ({ prop, onChange, loading, readOnly }: PropsContentProps) => { + const { t } = useTranslation(); + const [value, setValue] = useState(prop.value); + const [isEditing, setIsEditing] = useState(false); + const [isHovered, setIsHovered] = useState(false); + + useEffect(() => { + setValue(prop.value); + }, [prop.value]); + + const handleEditClick = () => { + setIsEditing(true); + }; + + const handleBlur = () => { + setIsEditing(false); + if (value !== prop.value) { + onChange(value); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + handleBlur(); + } + if (e.key === "Escape") { + setValue(prop.value); + setIsEditing(false); + } + }; + + if (readOnly) { + if (!value) { + return null; + } + return ( + + {value} + + ); + } + + if (isEditing) { + return ( + e.stopPropagation()} + onChange={(e) => setValue(e.target.value)} + value={value ?? ""} + onBlur={handleBlur} + onKeyDown={handleKeyDown} + /> + ); + } + + if (!value) { + return ( + + {t("application:fileManager.clickToEdit")} + + ); + } + + return ( + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + + {value} + + {isHovered && ( + { + e.stopPropagation(); + handleEditClick(); + }} + > + + + )} + + ); +}; + +export default LinkPropsContent; diff --git a/src/component/FileManager/Sidebar/CustomProps/MultiSelectPropsContent.tsx b/src/component/FileManager/Sidebar/CustomProps/MultiSelectPropsContent.tsx new file mode 100755 index 0000000..8eec15d --- /dev/null +++ b/src/component/FileManager/Sidebar/CustomProps/MultiSelectPropsContent.tsx @@ -0,0 +1,123 @@ +import { Box, Chip, MenuItem, Select, styled, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { PropsContentProps } from "./CustomPropsItem.tsx"; + +export const NoLabelFilledSelect = styled(Select)(({ theme }) => ({ + "& .MuiSelect-select": { + paddingTop: theme.spacing(1), + paddingBottom: theme.spacing(1), + fontSize: theme.typography.body2.fontSize, + "&.Mui-disabled": { + borderBottomStyle: "none", + "&::before": { + borderBottomStyle: "none !important", + }, + }, + }, + "&.MuiInputBase-root.MuiFilledInput-root.MuiSelect-root": { + "&.Mui-disabled": { + borderBottomStyle: "none", + "&::before": { + borderBottomStyle: "none !important", + }, + }, + }, +})); + +const MultiSelectPropsContent = ({ prop, onChange, loading, readOnly }: PropsContentProps) => { + const { t } = useTranslation(); + const [values, setValues] = useState(() => { + if (prop.value) { + try { + return JSON.parse(prop.value); + } catch { + return []; + } + } + return []; + }); + + useEffect(() => { + if (prop.value) { + try { + setValues(JSON.parse(prop.value)); + } catch { + setValues([]); + } + } else { + setValues([]); + } + }, [prop.value]); + + const handleChange = (selectedValues: string[]) => { + setValues(selectedValues); + const newValue = JSON.stringify(selectedValues); + if (newValue !== prop.value) { + onChange(newValue); + } + }; + + const handleDelete = (valueToDelete: string) => { + const newValues = values.filter((value) => value !== valueToDelete); + handleChange(newValues); + }; + + if (readOnly) { + return ( + + {values.length > 0 + ? values.map((value, index) => ) + : ""} + + ); + } + + const options = prop.props.options || []; + + return ( + + handleChange(e.target.value as string[])} + onClick={(e) => e.stopPropagation()} + multiple + displayEmpty + renderValue={(selected) => { + const selectedArray = Array.isArray(selected) ? selected : []; + if (selectedArray.length === 0) { + return ( + + {t("application:fileManager.clickToEditSelect")} + + ); + } + return ( + + {selectedArray.map((value, index) => ( + handleDelete(value)} + onMouseDown={(e) => e.stopPropagation()} + /> + ))} + + ); + }} + > + {options.map((option) => ( + + {option} + + ))} + + + ); +}; + +export default MultiSelectPropsContent; diff --git a/src/component/FileManager/Sidebar/CustomProps/NumberPropsContent.tsx b/src/component/FileManager/Sidebar/CustomProps/NumberPropsContent.tsx new file mode 100755 index 0000000..801edb8 --- /dev/null +++ b/src/component/FileManager/Sidebar/CustomProps/NumberPropsContent.tsx @@ -0,0 +1,49 @@ +import { Typography } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { NoLabelFilledTextField } from "../../../Common/StyledComponents.tsx"; +import { PropsContentProps } from "./CustomPropsItem.tsx"; + +const NumberPropsContent = ({ prop, onChange, loading, readOnly }: PropsContentProps) => { + const { t } = useTranslation(); + const [value, setValue] = useState(prop.value); + + useEffect(() => { + setValue(prop.value); + }, [prop.value]); + + const onBlur = () => { + if (value !== prop.value) { + onChange(value); + } + }; + + if (readOnly) { + return {value}; + } + + return ( + e.stopPropagation()} + onChange={(e) => setValue(e.target.value)} + value={value ?? ""} + onBlur={onBlur} + required + /> + ); +}; + +export default NumberPropsContent; diff --git a/src/component/FileManager/Sidebar/CustomProps/RatingPropsItem.tsx b/src/component/FileManager/Sidebar/CustomProps/RatingPropsItem.tsx new file mode 100755 index 0000000..74ccae6 --- /dev/null +++ b/src/component/FileManager/Sidebar/CustomProps/RatingPropsItem.tsx @@ -0,0 +1,23 @@ +import { Rating } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { PropsContentProps } from "./CustomPropsItem.tsx"; + +const RatingPropsItem = ({ prop, onChange, loading, readOnly }: PropsContentProps) => { + const { t } = useTranslation(); + + const handleChange = (_: any, value: number | null) => { + onChange(value?.toString() ?? ""); + }; + + return ( + + ); +}; + +export default RatingPropsItem; diff --git a/src/component/FileManager/Sidebar/CustomProps/SelectPropsContent.tsx b/src/component/FileManager/Sidebar/CustomProps/SelectPropsContent.tsx new file mode 100755 index 0000000..8bf083a --- /dev/null +++ b/src/component/FileManager/Sidebar/CustomProps/SelectPropsContent.tsx @@ -0,0 +1,78 @@ +import { MenuItem, Select, styled, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { PropsContentProps } from "./CustomPropsItem.tsx"; + +const NoLabelFilledSelect = styled(Select)(({ theme }) => ({ + "& .MuiSelect-select": { + paddingTop: theme.spacing(1), + paddingBottom: theme.spacing(1), + fontSize: theme.typography.body2.fontSize, + "&.Mui-disabled": { + borderBottomStyle: "none", + "&::before": { + borderBottomStyle: "none !important", + }, + }, + }, + "&.MuiInputBase-root.MuiFilledInput-root.MuiSelect-root": { + "&.Mui-disabled": { + borderBottomStyle: "none", + "&::before": { + borderBottomStyle: "none !important", + }, + }, + }, +})); + +const SelectPropsContent = ({ prop, onChange, loading, readOnly }: PropsContentProps) => { + const { t } = useTranslation(); + const [value, setValue] = useState(prop.value || ""); + + useEffect(() => { + setValue(prop.value || ""); + }, [prop.value]); + + const handleChange = (selectedValue: string) => { + setValue(selectedValue); + if (selectedValue !== prop.value) { + onChange(selectedValue); + } + }; + + if (readOnly) { + return {value}; + } + + const options = prop.props.options || []; + + return ( + handleChange(e.target.value as string)} + onClick={(e) => e.stopPropagation()} + displayEmpty + renderValue={(selected) => { + if (!selected) { + return ( + + {t("application:fileManager.clickToEditSelect")} + + ); + } + return selected as string; + }} + > + {options.map((option) => ( + + {option} + + ))} + + ); +}; + +export default SelectPropsContent; diff --git a/src/component/FileManager/Sidebar/CustomProps/TextPropsContent.tsx b/src/component/FileManager/Sidebar/CustomProps/TextPropsContent.tsx new file mode 100755 index 0000000..15d1277 --- /dev/null +++ b/src/component/FileManager/Sidebar/CustomProps/TextPropsContent.tsx @@ -0,0 +1,48 @@ +import { Typography } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { NoLabelFilledTextField } from "../../../Common/StyledComponents.tsx"; +import { PropsContentProps } from "./CustomPropsItem.tsx"; + +const TextPropsContent = ({ prop, onChange, loading, readOnly }: PropsContentProps) => { + const { t } = useTranslation(); + const [value, setValue] = useState(prop.value); + + useEffect(() => { + setValue(prop.value); + }, [prop.value]); + + const onBlur = () => { + if (value !== prop.value) { + onChange(value); + } + }; + + if (readOnly) { + return {value}; + } + + return ( + e.stopPropagation()} + onChange={(e) => setValue(e.target.value)} + value={value ?? ""} + onBlur={onBlur} + required + multiline + /> + ); +}; + +export default TextPropsContent; diff --git a/src/component/FileManager/Sidebar/Data.tsx b/src/component/FileManager/Sidebar/Data.tsx new file mode 100755 index 0000000..57c2961 --- /dev/null +++ b/src/component/FileManager/Sidebar/Data.tsx @@ -0,0 +1,112 @@ +import { Link, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material"; +import { useCallback, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { EntityType, FileResponse } from "../../../api/explorer.ts"; +import { setVersionControlDialog } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { downloadSingleFile } from "../../../redux/thunks/download.ts"; +import { sizeToString } from "../../../util"; +import { NoWrapTableCell, StyledTableContainerPaper } from "../../Common/StyledComponents.tsx"; +import TimeBadge from "../../Common/TimeBadge.tsx"; + +export interface DataProps { + target: FileResponse; +} + +export const EntityTypeText: Record = { + [EntityType.thumbnail]: "application:fileManager.thumbnails", + [EntityType.live_photo]: "application:fileManager.livePhoto", + [EntityType.version]: "application:fileManager.version", +}; + +const Data = ({ target }: DataProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const downloadEntity = useCallback( + (entityId: string) => { + dispatch(downloadSingleFile(target, entityId)); + }, + [target, dispatch], + ); + + const versionSizes = useMemo(() => { + let size = 0; + let notFound = true; + target.extended_info?.entities?.forEach((entity) => { + if (entity.type === EntityType.version) { + size += entity.size; + notFound = false; + } + }); + + return notFound ? undefined : size; + }, [target.extended_info?.entities]); + + if (!target.extended_info?.entities) { + return null; + } + + return ( + <> + + {t("application:fileManager.data")} + + + + + + {t("fileManager.type")} + {t("fileManager.size")} + {t("fileManager.createdAt")} + {t("fileManager.storagePolicy")} + {t("fileManager.actions")} + + + + {versionSizes != undefined && ( + + + {t("fileManager.versionEntity")} + + {sizeToString(versionSizes)} + - + - + + dispatch(setVersionControlDialog({ open: true, file: target }))} + underline={"hover"} + > + {t("fileManager.manageVersions")} + + + + )} + {target.extended_info?.entities + ?.filter((e) => e.type != EntityType.version) + .map((e) => ( + + + {t(EntityTypeText[e.type as EntityType])} + + {sizeToString(e.size)} + + + + {e.storage_policy?.name} + + downloadEntity(e.id)}> + {t("fileManager.download")} + + + + ))} + +
    +
    + + ); +}; + +export default Data; diff --git a/src/component/FileManager/Sidebar/Details.tsx b/src/component/FileManager/Sidebar/Details.tsx new file mode 100755 index 0000000..1d1df2d --- /dev/null +++ b/src/component/FileManager/Sidebar/Details.tsx @@ -0,0 +1,68 @@ +import { Box, Stack, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; +import { FileResponse, FileType, Metadata } from "../../../api/explorer.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { loadFileThumb } from "../../../redux/thunks/file.ts"; +import { FileManagerIndex } from "../FileManager.tsx"; +import BasicInfo from "./BasicInfo.tsx"; +import CustomProps from "./CustomProps/CustomProps.tsx"; +import Data from "./Data.tsx"; +import MediaInfo from "./MediaInfo.tsx"; +import Tags from "./Tags.tsx"; +import { DisplayOption } from "../ContextMenu/useActionDisplayOpt.ts"; + +export interface DetailsProps { + inPhotoViewer?: boolean; + target: FileResponse; + setTarget: (target: FileResponse | undefined | null) => void; + targetDisplayOptions?: DisplayOption; +} + +const InfoBlock = ({ title, children }: { title: string; children: React.ReactNode }) => { + return ( + + {title} + + {children} + + + ); +}; + +const Details = ({ target, inPhotoViewer, setTarget, targetDisplayOptions }: DetailsProps) => { + const dispatch = useAppDispatch(); + const [thumbSrc, setThumbSrc] = useState(null); + useEffect(() => { + if (target.type == FileType.file && (!target.metadata || target.metadata[Metadata.thumbDisabled] === undefined)) { + dispatch(loadFileThumb(FileManagerIndex.main, target)).then((src) => { + setThumbSrc(src); + }); + } + + setThumbSrc(null); + }, [target]); + + return ( + + {thumbSrc && !inPhotoViewer && ( + { + setThumbSrc(null); + }} + src={thumbSrc} + sx={{ + borderRadius: "8px", + }} + component={"img"} + /> + )} + + + + + + + ); +}; + +export default Details; diff --git a/src/component/FileManager/Sidebar/Header.tsx b/src/component/FileManager/Sidebar/Header.tsx new file mode 100755 index 0000000..29945f9 --- /dev/null +++ b/src/component/FileManager/Sidebar/Header.tsx @@ -0,0 +1,43 @@ +import { Box, IconButton, Skeleton, Typography } from "@mui/material"; +import { FileResponse } from "../../../api/explorer.ts"; +import { closeShareReadme, closeSidebar } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import Dismiss from "../../Icons/Dismiss.tsx"; +import FileIcon from "../Explorer/FileIcon.tsx"; + +export interface HeaderProps { + target: FileResponse | undefined | null; + variant?: "readme"; +} +const Header = ({ target, variant }: HeaderProps) => { + const dispatch = useAppDispatch(); + return ( + + {target !== null && } + {target !== null && ( + + + {target && target.name} + {!target && } + + + )} + { + dispatch(variant == "readme" ? closeShareReadme() : closeSidebar()); + }} + sx={{ + ml: 1, + placeSelf: "flex-start", + position: "relative", + top: "-4px", + }} + size={"small"} + > + + + + ); +}; + +export default Header; diff --git a/src/component/FileManager/Sidebar/InfoRow.tsx b/src/component/FileManager/Sidebar/InfoRow.tsx new file mode 100755 index 0000000..33b835f --- /dev/null +++ b/src/component/FileManager/Sidebar/InfoRow.tsx @@ -0,0 +1,22 @@ +import { Box, Typography } from "@mui/material"; +import React from "react"; + +export interface InfoRowProps { + title: string; + content: React.ReactNode | string; +} + +const InfoRow = ({ title, content }: InfoRowProps) => { + return ( + + + {title} + + + {content} + + + ); +}; + +export default InfoRow; diff --git a/src/component/FileManager/Sidebar/Map/LeafletMapBox.tsx b/src/component/FileManager/Sidebar/Map/LeafletMapBox.tsx new file mode 100755 index 0000000..a84953b --- /dev/null +++ b/src/component/FileManager/Sidebar/Map/LeafletMapBox.tsx @@ -0,0 +1,79 @@ +import { Box, useTheme } from "@mui/material"; +import L from "leaflet"; +import "leaflet/dist/leaflet.css"; +import { useMemo } from "react"; +import { MapContainer, Marker, TileLayer, useMap } from "react-leaflet"; +import { MapLoaderProps } from "./MapLoader.tsx"; + +// @ts-ignore +delete L.Icon.Default.prototype._getIconUrl; + +L.Icon.Default.mergeOptions({ + iconUrl: "/static/img/marker-icon.png", // .src removed + iconRetinaUrl: "/static/img/marker-icon-2x.png", // .src removed + shadowUrl: "/static/img/marker-shadow.png", // .src removed +}); + +const includeUkraineFlag = false; +const FlagRemoval = () => { + const map = useMap(); + if (!includeUkraineFlag) { + map.attributionControl.setPrefix( + 'Leaflet', + ); + } + return null; +}; + +const LeafletMapBox = ({ center, height, mapProvider, googleTileType, sx, ...rest }: MapLoaderProps) => { + const theme = useTheme(); + const googleTileUrl = useMemo(() => { + switch (googleTileType) { + case "terrain": + return "http://{s}.google.com/vt/lyrs=p&x={x}&y={y}&z={z}"; + case "satellite": + return "http://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}"; + default: + return "http://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}"; + } + }, [googleTileType]); + return ( + + + + {/* OPEN STREEN MAPS TILES */} + {mapProvider == "openstreetmap" && ( + + )} + {/* GOOGLE MAPS TILES */} + {mapProvider == "google" && ( + + )} + + + + ); +}; + +export default LeafletMapBox; diff --git a/src/component/FileManager/Sidebar/Map/MapLoader.tsx b/src/component/FileManager/Sidebar/Map/MapLoader.tsx new file mode 100755 index 0000000..1725ce7 --- /dev/null +++ b/src/component/FileManager/Sidebar/Map/MapLoader.tsx @@ -0,0 +1,32 @@ +import { BoxProps, Skeleton } from "@mui/material"; +import { lazy, Suspense } from "react"; +import { useAppSelector } from "../../../../redux/hooks.ts"; + +const LeafletMapBox = lazy(() => import("./LeafletMapBox.tsx")); +const Mapbox = lazy(() => import("./Mapbox.tsx")); + +export interface MapLoaderProps extends BoxProps { + height: number; + center: [number, number]; + mapProvider?: string; + googleTileType?: string; + token?: string; +} + +const MapLoader = (props: MapLoaderProps) => { + const mapProvider = useAppSelector((state) => state.siteConfig.explorer.config.map_provider); + const mapboxAk = useAppSelector((state) => state.siteConfig.explorer.config.mapbox_ak); + const googleTileType = useAppSelector((state) => state.siteConfig.explorer.config.google_map_tile_type); + return ( + }> + {mapProvider !== "mapbox" && ( + + )} + {mapProvider === "mapbox" && ( + + )} + + ); +}; + +export default MapLoader; diff --git a/src/component/FileManager/Sidebar/Map/Mapbox.tsx b/src/component/FileManager/Sidebar/Map/Mapbox.tsx new file mode 100755 index 0000000..5d39186 --- /dev/null +++ b/src/component/FileManager/Sidebar/Map/Mapbox.tsx @@ -0,0 +1,85 @@ +import { Box, useTheme } from "@mui/material"; +import mapboxgl from "mapbox-gl"; +import "mapbox-gl/dist/mapbox-gl.css"; +import { useEffect, useRef } from "react"; +import { MapLoaderProps } from "./MapLoader.tsx"; + +const MapboxComponent = ({ center, height, mapProvider, token, sx, ...rest }: MapLoaderProps) => { + const theme = useTheme(); + const mapRef = useRef(null); + const mapContainerRef = useRef(null); + const markerRef = useRef(null); + + // Dynamic map style based on theme mode + const mapStyle = "mapbox://styles/mapbox/standard"; + + useEffect(() => { + if (!mapContainerRef.current) return; + + // Set Mapbox access token + mapboxgl.accessToken = token; + + // Initialize map + mapRef.current = new mapboxgl.Map({ + container: mapContainerRef.current, + style: mapStyle, + config: { + basemap: { + lightPreset: theme.palette.mode === "dark" ? "night" : "day", + }, + }, + center: [center[1], center[0]], // Mapbox uses [lng, lat] format + zoom: 13, + }); + + // Add marker + markerRef.current = new mapboxgl.Marker().setLngLat([center[1], center[0]]).addTo(mapRef.current); + + return () => { + if (markerRef.current) { + markerRef.current.remove(); + } + if (mapRef.current) { + mapRef.current.remove(); + } + }; + }, [center]); + + useEffect(() => { + if (mapRef.current) { + mapRef.current.setConfigProperty("basemap", "lightPreset", theme.palette.mode === "dark" ? "night" : "day"); + } + }, [theme.palette.mode]); + + // Update marker position when center changes + useEffect(() => { + if (markerRef.current) { + markerRef.current.setLngLat([center[1], center[0]]); + } + if (mapRef.current) { + mapRef.current.setCenter([center[1], center[0]]); + } + }, [center]); + + return ( + +
    + + ); +}; + +export default MapboxComponent; diff --git a/src/component/FileManager/Sidebar/MediaInfo.tsx b/src/component/FileManager/Sidebar/MediaInfo.tsx new file mode 100755 index 0000000..a8b25fa --- /dev/null +++ b/src/component/FileManager/Sidebar/MediaInfo.tsx @@ -0,0 +1,718 @@ +import { Typography } from "@mui/material"; +import dayjs from "dayjs"; +import duration from "dayjs/plugin/duration"; +import { TFunction } from "i18next"; +import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { FileResponse, Metadata } from "../../../api/explorer.ts"; +import { formatLocalTime } from "../../../util/datetime.ts"; +import CameraFilled from "../../Icons/CameraFilled.tsx"; +import CameraRounded from "../../Icons/CameraRounded.tsx"; +import ClockFilled from "../../Icons/ClockFilled.tsx"; +import Copyright from "../../Icons/Copyright.tsx"; +import DarkTheme from "../../Icons/DarkTheme.tsx"; +import Image from "../../Icons/Image.tsx"; +import InfoFilled from "../../Icons/InfoFilled.tsx"; +import LocationFilled from "../../Icons/LocationFilled.tsx"; +import MusicNote1 from "../../Icons/MusicNote1.tsx"; +import Notepad from "../../Icons/Notepad.tsx"; +import Person from "../../Icons/Person.tsx"; +import WindowApps from "../../Icons/WindowApps.tsx"; +import MapLoader from "./Map/MapLoader.tsx"; +import MediaMetaCard, { MediaMetaContent, MediaMetaElements } from "./MediaMetaCard.tsx"; + +dayjs.extend(duration); + +export interface MediaInfoProps { + target: FileResponse; +} + +const formatBitrate = (bits: string): string => { + if (!bits) return ""; + + const bitrate = parseFloat(bits); + if (isNaN(bitrate)) return bits; + + if (bitrate < 1000) { + return `${bitrate.toFixed(0)} bps`; + } else if (bitrate < 1000000) { + return `${(bitrate / 1000).toFixed(1)} kbps`; + } else { + return `${(bitrate / 1000000).toFixed(2)} mbps`; + } +}; + +export const getAperture = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.f_number]) { + const fInt = parseFloat(target.metadata[Metadata.f_number]); + if (fInt) { + return { + display: `ƒ/${fInt.toFixed(1)}`, + searchKey: Metadata.f_number, + searchValue: target.metadata[Metadata.f_number], + }; + } + } +}; + +export const getExposure = (target: FileResponse, t: TFunction): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.exposure_time]) { + return { + display: t("application:fileManager.exposureValue", { + num: target.metadata[Metadata.exposure_time], + }), + searchKey: Metadata.exposure_time, + searchValue: target.metadata[Metadata.exposure_time], + }; + } +}; + +export const getIso = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.iso_speed_ratings]) { + return { + display: target.metadata[Metadata.iso_speed_ratings], + searchKey: Metadata.iso_speed_ratings, + searchValue: target.metadata[Metadata.iso_speed_ratings], + }; + } +}; + +export const getCameraMake = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.camera_make]) { + return { + display: target.metadata[Metadata.camera_make], + searchKey: Metadata.camera_make, + searchValue: target.metadata[Metadata.camera_make], + }; + } +}; + +export const getCameraModel = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.camera_model]) { + return { + display: target.metadata[Metadata.camera_model], + searchKey: Metadata.camera_model, + searchValue: target.metadata[Metadata.camera_model], + }; + } +}; + +export const getLensMake = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.lens_make]) { + return { + display: target.metadata[Metadata.lens_make], + searchKey: Metadata.lens_make, + searchValue: target.metadata[Metadata.lens_make], + }; + } +}; + +export const getLensModel = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.lens_model]) { + return { + display: target.metadata[Metadata.lens_model], + searchKey: Metadata.lens_model, + searchValue: target.metadata[Metadata.lens_model], + }; + } +}; + +export const getFocalLength = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.focal_length]) { + return { + display: `${target.metadata[Metadata.focal_length]}mm`, + searchKey: Metadata.focal_length, + searchValue: target.metadata?.[Metadata.focal_length], + }; + } +}; + +export const getExposureBias = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.exposure_bias_value]) { + const evFloat = parseFloat(target.metadata[Metadata.exposure_bias_value]); + return { + display: `${evFloat.toFixed(1)} ev`, + searchKey: Metadata.exposure_bias_value, + searchValue: target.metadata[Metadata.exposure_bias_value], + }; + } +}; + +export const getFlash = (target: FileResponse, t: TFunction): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.flash]) { + return { + display: target.metadata[Metadata.flash] == "1" ? t("fileManager.on") : t("fileManager.off"), + searchKey: Metadata.flash, + searchValue: target.metadata[Metadata.flash], + }; + } +}; + +export const getSoftware = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.software]) { + return { + display: target.metadata[Metadata.software], + searchKey: Metadata.software, + searchValue: target.metadata[Metadata.software], + }; + } +}; + +export const takenAt = (target: FileResponse): string | undefined => { + if (target.metadata?.[Metadata.taken_at]) { + return formatLocalTime(target.metadata[Metadata.taken_at], true); + } +}; + +export const getImageSize = (target: FileResponse): (MediaMetaElements | string)[] | undefined => { + if (!target.metadata?.[Metadata.pixel_x_dimension] || !target.metadata?.[Metadata.pixel_y_dimension]) { + return undefined; + } + + const holder: (MediaMetaElements | string)[] = []; + const x = parseInt(target.metadata[Metadata.pixel_x_dimension]); + const y = parseInt(target.metadata[Metadata.pixel_y_dimension]); + const mp = (x * y) / 1000000; + if (mp > 0.1) { + holder.push(`${mp.toFixed(1)} MP · `); + } + holder.push({ + display: `${x}`, + searchKey: Metadata.pixel_x_dimension, + searchValue: target.metadata[Metadata.pixel_x_dimension], + }); + holder.push(" x "); + holder.push({ + display: `${y}`, + searchKey: Metadata.pixel_y_dimension, + searchValue: target.metadata[Metadata.pixel_y_dimension], + }); + + return holder; +}; + +export const getMediaTitle = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.music_title]) { + return { + display: target.metadata[Metadata.music_title], + searchKey: Metadata.music_title, + searchValue: target.metadata[Metadata.music_title], + }; + } else if (target.metadata?.[Metadata.stream_title]) { + return { + display: target.metadata[Metadata.stream_title], + searchKey: Metadata.stream_title, + searchValue: target.metadata[Metadata.stream_title], + }; + } +}; + +export const getArtist = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.music_artist]) { + return { + display: target.metadata[Metadata.music_artist], + searchKey: Metadata.music_artist, + searchValue: target.metadata[Metadata.music_artist], + }; + } +}; + +export const getAlbum = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.music_album]) { + return { + display: target.metadata[Metadata.music_album], + searchKey: Metadata.music_album, + searchValue: target.metadata[Metadata.music_album], + }; + } +}; + +export const getDuration = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.stream_duration]) { + return { + display: dayjs.duration(parseFloat(target.metadata[Metadata.stream_duration]), "seconds").format("HH:mm:ss"), + searchKey: Metadata.stream_duration, + searchValue: target.metadata[Metadata.stream_duration], + }; + } +}; + +export const getStreet = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.street]) { + return { + display: target.metadata[Metadata.street], + searchKey: Metadata.street, + searchValue: target.metadata[Metadata.street], + }; + } +}; + +export const getLocality = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.locality]) { + return { + display: target.metadata[Metadata.locality], + searchKey: Metadata.locality, + searchValue: target.metadata[Metadata.locality], + }; + } +}; + +export const getPlace = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.place]) { + return { + display: target.metadata[Metadata.place], + searchKey: Metadata.place, + searchValue: target.metadata[Metadata.place], + }; + } +}; + +export const getDistrict = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.district]) { + return { + display: target.metadata[Metadata.district], + searchKey: Metadata.district, + searchValue: target.metadata[Metadata.district], + }; + } +}; + +export const getRegion = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.region]) { + return { + display: target.metadata[Metadata.region], + searchKey: Metadata.region, + searchValue: target.metadata[Metadata.region], + }; + } +}; + +export const getCountry = (target: FileResponse): MediaMetaElements | undefined => { + if (target.metadata?.[Metadata.country]) { + return { + display: target.metadata[Metadata.country], + searchKey: Metadata.country, + searchValue: target.metadata[Metadata.country], + }; + } +}; + +const MediaInfo = ({ target }: MediaInfoProps) => { + if (!target.metadata) { + return undefined; + } + + const { t } = useTranslation(); + + const exifContents = useMemo(() => { + let res: MediaMetaContent[] = []; + const aperture = getAperture(target); + if (aperture) { + res.push({ + title: [t("fileManager.aperture")], + content: [aperture], + }); + } + + const exposure = getExposure(target, t); + if (exposure) { + res.push({ + title: [t("fileManager.exposure")], + content: [exposure], + }); + } + + const iso = getIso(target); + if (iso) { + res.push({ + title: [t("fileManager.iso")], + content: [iso], + }); + } + + return res; + }, [target, t]); + + const cameraModelContent = useMemo(() => { + let title: (MediaMetaElements | string)[] = []; + let content: (MediaMetaElements | string)[] = []; + + const cameraMake = getCameraMake(target); + if (cameraMake) { + title.push(cameraMake); + } + + const cameraModel = getCameraModel(target); + if (cameraModel) { + if (title.length > 0) { + title.push(" "); + } + title.push(cameraModel); + } + + const lensMake = getLensMake(target); + if (lensMake) { + content.push(lensMake); + } + + const lensModel = getLensModel(target); + if (lensModel) { + if (content.length > 0) { + content.push(" "); + } + content.push(lensModel); + } + + const focalLength = getFocalLength(target); + if (focalLength) { + // Push ( to the start of the content + const contentEmpty = content.length == 0; + if (!contentEmpty) { + content.unshift(" ("); + } + content.unshift(focalLength); + if (!contentEmpty) { + content.push(")"); + } + } + if (title.length == 0) { + title = content; + } + if (title.length > 0 || content.length > 0) { + return { title, content }; + } + return undefined; + }, [target, t]); + + const lightInfoContent = useMemo(() => { + let res: MediaMetaContent[] = []; + + const exposureBias = getExposureBias(target); + if (exposureBias) { + res.push({ + title: [t("fileManager.exposureBias")], + content: [exposureBias], + }); + } + + const flash = getFlash(target, t); + if (flash) { + res.push({ + title: [t("fileManager.flash")], + content: [flash], + }); + } + return res; + }, [target, t]); + + const copyRightContent = useMemo(() => { + const holder: (MediaMetaElements | string)[] = []; + if (target.metadata?.[Metadata.copy_right]) { + holder.push({ + display: target.metadata[Metadata.copy_right], + searchKey: Metadata.copy_right, + searchValue: target.metadata[Metadata.copy_right], + }); + } + + if (target.metadata?.[Metadata.artist]) { + if (holder.length > 0) { + holder.push(" "); + } + holder.push({ + display: target.metadata[Metadata.artist], + searchKey: Metadata.artist, + searchValue: target.metadata[Metadata.artist], + }); + } + + if (holder.length == 0) { + return undefined; + } + + return { + title: [t("fileManager.copyright")], + content: holder, + } as MediaMetaContent; + }, [target, t]); + + const softwareContent = useMemo(() => { + const software = getSoftware(target); + if (!software) { + return undefined; + } + + return { + title: [t("fileManager.software")], + content: [software], + } as MediaMetaContent; + }, [target, t]); + + const mapBoxGps = useMemo(() => { + if (!target.metadata?.[Metadata.gps_lat] || !target.metadata?.[Metadata.gps_lng]) { + return undefined; + } + + return [parseFloat(target.metadata[Metadata.gps_lat]), parseFloat(target.metadata[Metadata.gps_lng])] as [ + number, + number, + ]; + }, [target, t]); + + const takenTimeContent = useMemo(() => { + const takenTime = takenAt(target); + if (!takenTime) { + return undefined; + } + + return { + title: [t("application:fileManager.takenAt")], + content: [takenTime], + } as MediaMetaContent; + }, [target, t]); + + const imageSizeContent = useMemo(() => { + const imageSize = getImageSize(target); + if (!imageSize) { + return undefined; + } + + return { + title: [t("fileManager.resolution")], + content: imageSize, + } as MediaMetaContent; + }, [target, t]); + + const mediaTitleContent = useMemo(() => { + const res = { + title: [t("fileManager.title")], + content: [], + } as MediaMetaContent; + + const mediaTitle = getMediaTitle(target); + if (mediaTitle) { + res.content.push(mediaTitle); + return res; + } else { + return undefined; + } + }, [target, t]); + + const musicArtistContent = useMemo(() => { + const artist = getArtist(target); + if (!artist) { + return undefined; + } + return { + title: [t("fileManager.artist")], + content: [artist], + } as MediaMetaContent; + }, [target, t]); + + const albumContent = useMemo(() => { + const album = getAlbum(target); + if (!album) { + return undefined; + } + + return { + title: [t("fileManager.album")], + content: [album], + } as MediaMetaContent; + }, [target, t]); + + const durationContent = useMemo(() => { + const duration = getDuration(target); + if (!duration) { + return undefined; + } + + return { + title: [t("fileManager.duration")], + content: [duration], + } as MediaMetaContent; + }, [target, t]); + + const streamFormatContent = useMemo(() => { + let res: MediaMetaContent[] = []; + + if (target.metadata?.[Metadata.stream_format_long]) { + res.push({ + title: [t("fileManager.format")], + content: [ + { + display: target.metadata[Metadata.stream_format_long], + searchKey: Metadata.stream_format_long, + searchValue: target.metadata[Metadata.stream_format_long], + }, + ], + }); + + if (target.metadata?.[Metadata.stream_bit_rate]) { + res[0].content.push(" · "); + res[0].content.push({ + display: formatBitrate(target.metadata[Metadata.stream_bit_rate]), + searchKey: Metadata.stream_bit_rate, + searchValue: target.metadata[Metadata.stream_bit_rate], + }); + } + } + + if (res.length == 0) { + return undefined; + } + + return res; + }, [target, t]); + + const singleStreamContents = useMemo(() => { + let res: MediaMetaContent[] = []; + const allMetas = Object.keys(target.metadata ?? {}); + const streamGrouped: { + [key: string]: { type: string; [key: string]: string }; + } = {}; + allMetas.forEach((meta) => { + if (!meta.startsWith("stream:stream_")) { + return; + } + + const [prefix, group, type, ...other] = meta.split("_"); + if (!streamGrouped[group]) { + streamGrouped[group] = { + type: type, + }; + } + + if (type != "video" && type != "audio") { + return; + } + + streamGrouped[group][other.join("_")] = target.metadata?.[meta] ?? ""; + }); + + for (const [group, item] of Object.entries(streamGrouped)) { + let content: string[] = []; + if (item[Metadata.stream_indexed_codec]) { + content.push(item[Metadata.stream_indexed_codec]); + } + if (item[Metadata.stream_indexed_bitrate]) { + content.push(formatBitrate(item[Metadata.stream_indexed_bitrate])); + } + if (item[Metadata.stream_indexed_width] && item[Metadata.stream_indexed_height]) { + content.push(`${item[Metadata.stream_indexed_width]}x${item[Metadata.stream_indexed_height]}`); + } + if (content.length == 0) { + continue; + } + res.push({ + title: [`Stream #${group} (${item.type})`], + content: [content.join(" · ")], + }); + } + + if (res.length > 0) { + return res; + } + + return undefined; + }, [target, t]); + + const gpsAddressContent = useMemo(() => { + let content: (MediaMetaElements | string)[] = []; + + // Build address components in hierarchical order (most specific to least specific) + const addressComponents: (MediaMetaElements | undefined)[] = []; + + const street = getStreet(target); + if (street) { + addressComponents.push(street); + } + const locality = getLocality(target); + if (locality) { + addressComponents.push(locality); + } + const place = getPlace(target); + if (place) { + addressComponents.push(place); + } + const district = getDistrict(target); + if (district) { + addressComponents.push(district); + } + const region = getRegion(target); + if (region) { + addressComponents.push(region); + } + const country = getCountry(target); + if (country) { + addressComponents.push(country); + } + + // Filter out undefined components and join with commas + const validComponents = addressComponents.filter(Boolean) as MediaMetaElements[]; + + if (validComponents.length > 0) { + // Add the first component + content.push(validComponents[0]); + + // Add remaining components with comma separators + for (let i = 1; i < validComponents.length; i++) { + content.push(", "); + content.push(validComponents[i]); + } + + return { title: [t("fileManager.address")], content }; + } + + return undefined; + }, [target, t]); + + const showExifBasic = exifContents.length > 0; + const showLightInfo = lightInfoContent.length > 0; + const showCopyRight = !!copyRightContent; + const showCameraModel = !!cameraModelContent; + const showMediaInfo = + showExifBasic || + showCameraModel || + showLightInfo || + showCopyRight || + softwareContent || + takenTimeContent || + imageSizeContent || + mediaTitleContent || + musicArtistContent || + durationContent || + streamFormatContent || + singleStreamContents || + mapBoxGps || + gpsAddressContent; + + if (!showMediaInfo) { + return undefined; + } + + return ( + <> + + {t("fileManager.mediaInfo")} + + {showExifBasic && } + {showLightInfo && } + {showCameraModel && } + {takenTimeContent && } + {imageSizeContent && } + {showCopyRight && } + {softwareContent && } + {mapBoxGps && } + {gpsAddressContent && } + {mediaTitleContent && } + {musicArtistContent && } + {albumContent && } + {durationContent && } + {streamFormatContent && } + {singleStreamContents && singleStreamContents.map((content) => )} + + ); +}; + +export default MediaInfo; diff --git a/src/component/FileManager/Sidebar/MediaMetaCard.tsx b/src/component/FileManager/Sidebar/MediaMetaCard.tsx new file mode 100755 index 0000000..452d40c --- /dev/null +++ b/src/component/FileManager/Sidebar/MediaMetaCard.tsx @@ -0,0 +1,144 @@ +import { + Box, + Link, + LinkProps, + ListItemIcon, + ListItemText, + Menu, + styled, + SvgIconProps, + Typography, +} from "@mui/material"; +import SvgIcon from "@mui/material/SvgIcon/SvgIcon"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { searchMetadata } from "../../../redux/thunks/filemanager.ts"; +import { copyToClipboard } from "../../../util"; +import Clipboard from "../../Icons/Clipboard.tsx"; +import Search from "../../Icons/Search.tsx"; +import { SquareMenuItem } from "../ContextMenu/ContextMenu.tsx"; +import { FileManagerIndex } from "../FileManager.tsx"; + +export interface MediaMetaElements { + display: string; + searchKey: string; + searchValue: string; +} + +export interface MediaMetaContent { + title: (MediaMetaElements | string)[]; + content: (MediaMetaElements | string)[]; +} + +export interface MediaMetaCardProps { + contents: MediaMetaContent[]; + icon?: typeof SvgIcon | ((props: SvgIconProps) => JSX.Element); +} + +export const StyledButtonBase = styled(Box)(({ theme }) => { + let bgColor = theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900]; + let bgColorHover = theme.palette.mode === "light" ? theme.palette.grey[300] : theme.palette.grey[700]; + return { + borderRadius: theme.shape.borderRadius, + backgroundColor: bgColor, + display: "flex", + width: "100%", + wordBreak: "break-all", + alignItems: "center", + justifyContent: "flex-start", + padding: "8px 16px", + transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms", + transitionProperty: "background-color,opacity,box-shadow", + gap: 15, + + textAlign: "left", + height: "100%", + userSelect: "text", + overflow: "hidden", + }; +}); + +export interface MediaMetaElementsProps extends LinkProps { + element: MediaMetaElements; +} + +export const MediaMetaElements = ({ element, ...rest }: MediaMetaElementsProps) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + + const handleSearch = () => { + dispatch(searchMetadata(FileManagerIndex.main, element.searchKey, element.searchValue)); + handleClose(); + }; + + const handleCopy = () => { + copyToClipboard(element.display); + handleClose(); + }; + + const [anchorEl, setAnchorEl] = useState(null); + const handleClose = () => { + setAnchorEl(null); + }; + return ( + <> + + + + + + + {t("application:fileManager.searchSomething", { + text: element?.display ?? "", + })} + + + + + + + {t("application:fileManager.copyToClipboard")} + + + setAnchorEl(e.currentTarget)} underline="hover" {...rest}> + {element.display} + + + ); +}; + +const MediaMetaCard = ({ contents, icon }: MediaMetaCardProps) => { + const Icon = icon; + return ( + <> + + {Icon && } + + {contents.map(({ title, content }) => ( + + + {title.map((element) => + typeof element === "string" ? element : , + )} + + + {content.map((element) => + typeof element === "string" ? element : , + )} + + + ))} + + + + ); +}; +export default MediaMetaCard; diff --git a/src/component/FileManager/Sidebar/Sidebar.tsx b/src/component/FileManager/Sidebar/Sidebar.tsx new file mode 100755 index 0000000..f3c19f2 --- /dev/null +++ b/src/component/FileManager/Sidebar/Sidebar.tsx @@ -0,0 +1,82 @@ +import { Box, Collapse } from "@mui/material"; +import { useCallback, useEffect, useState } from "react"; +import { getFileInfo } from "../../../api/api.ts"; +import { FileResponse } from "../../../api/explorer.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { RadiusFrame } from "../../Frame/RadiusFrame.tsx"; +import SidebarContent from "./SidebarContent.tsx"; + +export interface SideBarProps { + inPhotoViewer?: boolean; +} + +const Sidebar = ({ inPhotoViewer }: SideBarProps) => { + const dispatch = useAppDispatch(); + const sidebarOpen = useAppSelector((state) => state.globalState.sidebarOpen); + const sidebarTarget = useAppSelector((state) => state.globalState.sidebarTarget); + // null: not valid, undefined: not loaded, FileResponse: loaded + const [target, setTarget] = useState(undefined); + + const loadExtendedInfo = useCallback( + (path: string) => { + dispatch( + getFileInfo({ + uri: path, + extended: true, + }), + ).then((res) => { + setTarget((r) => ({ ...res, capability: r?.capability })); + }); + }, + [target, dispatch, setTarget], + ); + + useEffect(() => { + if (sidebarTarget && sidebarOpen) { + if (typeof sidebarTarget === "string") { + } else { + setTarget(sidebarTarget); + loadExtendedInfo(sidebarTarget.path); + } + } else { + setTarget(null); + } + }, [sidebarTarget, setTarget]); + + return ( + + + (inPhotoViewer ? 0 : theme.shape.borderRadius / 8), + }} + withBorder={!inPhotoViewer} + > + + + + + ); +}; + +export default Sidebar; diff --git a/src/component/FileManager/Sidebar/SidebarContent.tsx b/src/component/FileManager/Sidebar/SidebarContent.tsx new file mode 100755 index 0000000..ce0729b --- /dev/null +++ b/src/component/FileManager/Sidebar/SidebarContent.tsx @@ -0,0 +1,68 @@ +import { Box } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { FileResponse } from "../../../api/explorer.ts"; +import useActionDisplayOpt from "../ContextMenu/useActionDisplayOpt.ts"; +import Details from "./Details.tsx"; +import Header from "./Header.tsx"; + +export interface SidebarContentProps { + target: FileResponse | undefined | null; + inPhotoViewer?: boolean; + setTarget: (target: FileResponse | undefined | null) => void; +} + +interface TabPanelProps { + children?: React.ReactNode; + index: number; + value: number; +} + +function TabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props; + + return ( + + ); +} + +const SidebarContent = ({ target, inPhotoViewer, setTarget }: SidebarContentProps) => { + const { t } = useTranslation(); + const targetDisplayOptions = useActionDisplayOpt(target ? [target] : []); + return ( + +
    + {target != null && ( + <> + + + +
    + + + + )} + + ); +}; + +export default SidebarContent; diff --git a/src/component/FileManager/Sidebar/SidebarDialog.tsx b/src/component/FileManager/Sidebar/SidebarDialog.tsx new file mode 100755 index 0000000..b43fc32 --- /dev/null +++ b/src/component/FileManager/Sidebar/SidebarDialog.tsx @@ -0,0 +1,67 @@ +import { Dialog, Slide } from "@mui/material"; +import { TransitionProps } from "@mui/material/transitions"; +import { forwardRef, useCallback, useEffect, useState } from "react"; +import { getFileInfo } from "../../../api/api.ts"; +import { FileResponse } from "../../../api/explorer.ts"; +import { closeSidebar } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { SideBarProps } from "./Sidebar.tsx"; +import SidebarContent from "./SidebarContent.tsx"; + +const Transition = forwardRef(function Transition( + props: TransitionProps & { + children: React.ReactElement; + }, + ref: React.Ref, +) { + return ; +}); + +const SidebarDialog = ({ inPhotoViewer }: SideBarProps) => { + const dispatch = useAppDispatch(); + const sidebarOpen = useAppSelector((state) => state.globalState.sidebarOpen); + const sidebarTarget = useAppSelector((state) => state.globalState.sidebarTarget); + // null: not valid, undefined: not loaded, FileResponse: loaded + const [target, setTarget] = useState(undefined); + + const loadExtendedInfo = useCallback( + (path: string) => { + dispatch( + getFileInfo({ + uri: path, + extended: true, + }), + ).then((res) => { + setTarget((r) => ({ ...res, capability: r?.capability })); + }); + }, + [target, dispatch, setTarget], + ); + + useEffect(() => { + if (sidebarTarget && sidebarOpen) { + if (typeof sidebarTarget === "string") { + } else { + setTarget(sidebarTarget); + loadExtendedInfo(sidebarTarget.path); + } + } else { + setTarget(null); + } + }, [sidebarTarget, setTarget]); + + return ( + { + dispatch(closeSidebar()); + }} + > + + + ); +}; + +export default SidebarDialog; diff --git a/src/component/FileManager/Sidebar/Tags.tsx b/src/component/FileManager/Sidebar/Tags.tsx new file mode 100755 index 0000000..8abf58d --- /dev/null +++ b/src/component/FileManager/Sidebar/Tags.tsx @@ -0,0 +1,57 @@ +import { FileResponse, Metadata } from "../../../api/explorer.ts"; +import { useTranslation } from "react-i18next"; +import { Box, Typography } from "@mui/material"; +import React, { useMemo } from "react"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import FileTag from "../Explorer/FileTag.tsx"; + +export interface TagsProps { + target: FileResponse; +} + +export const getFileTags = (file: FileResponse) => { + if (file.metadata) { + const tags: { key: string; value: string }[] = []; + Object.keys(file.metadata).forEach((key: string) => { + if (key.startsWith(Metadata.tag_prefix)) { + // trim prefix for key + tags.push({ + key: key.slice(Metadata.tag_prefix.length), + value: file.metadata?.[key] ?? "", + }); + } + }); + return tags; + } +}; + +const Tags = ({ target }: TagsProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const fileTag = useMemo(() => getFileTags(target), [target]); + + if (!fileTag || fileTag.length === 0) { + return null; + } + + return ( + <> + + {t("application:fileManager.tags")} + + + {fileTag.map((tag, i) => ( + + ))} + + + ); +}; + +export default Tags; diff --git a/src/component/FileManager/TopBar/Breadcrumb.tsx b/src/component/FileManager/TopBar/Breadcrumb.tsx new file mode 100755 index 0000000..50e9663 --- /dev/null +++ b/src/component/FileManager/TopBar/Breadcrumb.tsx @@ -0,0 +1,247 @@ +import { Box, ClickAwayListener, Menu, styled, TextField, useMediaQuery, useTheme } from "@mui/material"; +import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; +import { useIsOverflow } from "../../../hooks/useOverflow.tsx"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { navigateToPath } from "../../../redux/thunks/filemanager.ts"; +import { mergeRefs } from "../../../util"; +import CrUri from "../../../util/uri.ts"; +import ChevronRight from "../../Icons/ChevronRight.tsx"; +import MoreHorizontal from "../../Icons/MoreHorizontal.tsx"; +import { NoOpDropUri, useFileDrag } from "../Dnd/DndWrappedFile.tsx"; +import { FmIndexContext } from "../FmIndexContext.tsx"; +import BreadcrumbButton, { BreadcrumbButtonBase, BreadcrumbButtonProps } from "./BreadcrumbButton.tsx"; +import BreadcrumbHiddenItem from "./BreadcrumbHiddenItem.tsx"; + +const PathTextField = styled(TextField)(() => ({ + "& .MuiOutlinedInput-notchedOutline": { + border: "none", + }, + "& .MuiOutlinedInput-input": { + padding: "6px 4px", + fontSize: "0.8125rem", + lineHeight: "1.5", + fontFamily: "monospace", + }, + height: "32px", + overflow: "hidden", + verticalAlign: "middle", +})); + +const RightIcon = styled(ChevronRight)(({ theme }) => ({ + fontSize: 15, + mx: 0.5, + verticalAlign: "middle", + color: theme.palette.text.disabled, +})); + +interface pathElements extends BreadcrumbButtonProps {} + +const useBreadcrumb = (targetPath?: string) => { + if (targetPath) { + const uri = new CrUri(targetPath); + const elements = uri.elements(); + return [targetPath, elements, uri.base(true)] as const; + } + + const index = useContext(FmIndexContext); + + const base = useAppSelector((s) => s.fileManager[index].path_root_with_category); + const path = useAppSelector((s) => s.fileManager[index].path); + const elements = useAppSelector((s) => s.fileManager[index].path_elements); + + return [path, elements, base] as const; +}; + +export interface BreadcrumbProps { + targetPath?: string; + displayOnly?: boolean; +} + +const Breadcrumb = (props: BreadcrumbProps) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const dispatch = useAppDispatch(); + const fmIndex = useContext(FmIndexContext); + const previousElements = useRef(0); + const [editing, setEditing] = useState(false); + const [editedPath, setEditedPath] = useState(""); + const [maxHiding, setMaxHiding] = useState(0); + const [anchorEl, setAnchorEl] = useState(null); + const hiddenOpen = Boolean(anchorEl); + const hiddenExpandButtonRef = useRef(); + const chainRef = useRef(null); + + const openHiddenMenu = (e: React.MouseEvent) => { + e.stopPropagation(); + setAnchorEl(e.currentTarget); + }; + + const [path, elements, base] = useBreadcrumb(props.targetPath); + + const onEdit = useCallback(() => { + if (props.displayOnly) { + return; + } + if (path) { + const uri = new CrUri(path); + setEditedPath(uri.path()); + } + setEditing(true); + }, [path, props.displayOnly]); + + const buttons = useMemo(() => { + if (!base || !elements) { + return []; + } + + const res: pathElements[] = []; + + // First, inject root button + res.push({ + path: base, + is_root: true, + displayOnly: props.displayOnly, + is_latest: elements.length === 0, + }); + + const currentBase = new CrUri(base); + res.push( + ...elements.map((e, i) => { + return { + path: currentBase.join(e).toString(), + name: e, + displayOnly: props.displayOnly, + is_latest: i === elements.length - 1, + }; + }), + ); + + return res; + }, [elements, base, props.displayOnly]); + + const hidedButtons = useMemo(() => { + return buttons.slice(0, maxHiding); + }, [buttons, maxHiding]); + + const displayedButtons = useMemo(() => { + return buttons.slice(maxHiding); + }, [buttons, maxHiding]); + + const isOverflow = useIsOverflow(chainRef, (_isOverflow) => {}); + + useEffect(() => { + if (isOverflow && !isMobile) { + setMaxHiding(buttons.length - 1); + } + }, [isOverflow, isMobile]); + + // Cancel collapse when elements are less than previous + useEffect(() => { + const current = elements?.length ?? 0; + if (previousElements.current > current) { + setTimeout(() => { + setMaxHiding(0); + }, theme.transitions.duration.standard); + } + previousElements.current = current; + }, [elements]); + + const submitNewPath = useCallback(() => { + setEditing(false); + if (!path || !editedPath) { + return; + } + const uri = new CrUri(path); + if (uri.path() == editedPath || props.displayOnly) { + // No change + return; + } + + // Apply new path and navigate + dispatch(navigateToPath(fmIndex, uri.setPath(editedPath).toString())); + }, [path, editedPath, props.displayOnly, fmIndex, dispatch]); + + const [drag, drop, isOver, isDragging] = useFileDrag({ + dropUri: NoOpDropUri, + }); + + useEffect(() => { + if (isOver && hiddenExpandButtonRef.current) { + hiddenExpandButtonRef.current?.click(); + } + }, [isOver]); + + return ( + <> + + {editing && ( + submitNewPath()}> + { + e.target.select(); + }} + fullWidth + onClick={(e) => e.stopPropagation()} + InputProps={{ + readOnly: props.displayOnly, + }} + onKeyPress={(e) => { + if (e.key === "Enter") { + submitNewPath(); + } + }} + value={editedPath} + onChange={(e) => setEditedPath(e.target.value)} + /> + + )} + {!editing && ( + <> + {hidedButtons.length > 0 && ( + + + + )} + {...displayedButtons.map((b, i) => ( + <> + {i == 0 && hidedButtons.length > 0 && } + + {i != displayedButtons.length - 1 && } + + ))} + + )} + + setAnchorEl(null)} + slotProps={{ + list: { + dense: true, + }, + }} + > + {hidedButtons && + hidedButtons.map((b) => setAnchorEl(null)} {...b} />)} + + + ); +}; + +export default Breadcrumb; diff --git a/src/component/FileManager/TopBar/BreadcrumbButton.tsx b/src/component/FileManager/TopBar/BreadcrumbButton.tsx new file mode 100755 index 0000000..fe82649 --- /dev/null +++ b/src/component/FileManager/TopBar/BreadcrumbButton.tsx @@ -0,0 +1,293 @@ +import { Button, Skeleton, styled, SvgIconProps, Tooltip } from "@mui/material"; +import { bindHover, bindPopover } from "material-ui-popup-state"; +import { usePopupState } from "material-ui-popup-state/hooks"; +import { useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Share } from "../../../api/explorer.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { navigateToPath, openContextUrlFromUri } from "../../../redux/thunks/filemanager.ts"; +import { queueLoadShareInfo } from "../../../redux/thunks/share.ts"; +import PageTitle from "../../../router/PageTitle.tsx"; +import SessionManager from "../../../session"; +import CrUri, { Filesystem, UriQuery, UriSearchCategory } from "../../../util/uri.ts"; +import { NoWrapBox } from "../../Common/StyledComponents.tsx"; +import UserAvatar from "../../Common/User/UserAvatar.tsx"; +import CaretDown from "../../Icons/CaretDown.tsx"; +import Delete from "../../Icons/Delete.tsx"; +import DeleteOutlined from "../../Icons/DeleteOutlined.tsx"; +import DocumentText from "../../Icons/DocumentText.tsx"; +import DocumentTextOutlined from "../../Icons/DocumentTextOutlined.tsx"; +import Home from "../../Icons/Home.tsx"; +import HomeOutlined from "../../Icons/HomeOutlined.tsx"; +import Image from "../../Icons/Image.tsx"; +import ImageOutlined from "../../Icons/ImageOutlined.tsx"; +import LinkDismiss from "../../Icons/LinkDismiss.tsx"; +import MusicNote1 from "../../Icons/MusicNote1.tsx"; +import MusicNote1Outlined from "../../Icons/MusicNote1Outlined.tsx"; +import PeopleTeam from "../../Icons/PeopleTeam.tsx"; +import PeopleTeamOutlined from "../../Icons/PeopleTeamOutlined.tsx"; +import Video from "../../Icons/Video.tsx"; +import VideoOutlined from "../../Icons/VideoOutlined.tsx"; +import { useFileDrag } from "../Dnd/DndWrappedFile.tsx"; +import { FileManagerIndex } from "../FileManager.tsx"; +import { FmIndexContext } from "../FmIndexContext.tsx"; +import ShareInfoPopover from "./ShareInfoPopover.tsx"; + +export const BreadcrumbButtonBase = styled(Button)<{ isDropOver?: boolean }>(({ theme, isDropOver }) => ({ + color: theme.palette.text.secondary, + transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms !important", + transitionProperty: "background-color,opacity,box-shadow", + boxShadow: isDropOver ? `inset 0 0 0 2px ${theme.palette.primary.light}` : "none", + minHeight: theme.spacing(4), +})); + +export interface BreadcrumbButtonProps { + path: string; + name?: string; + is_latest?: boolean; + displayOnly?: boolean; + is_root?: boolean; + count_share_views?: boolean; + [key: string]: any; +} + +export interface StartIcon { + Element?: (props: { [key: string]: any }) => JSX.Element; + Icons?: ((props: SvgIconProps) => JSX.Element)[]; +} + +export const useBreadcrumbButtons = ({ + name, + is_latest, + path, + displayOnly, + is_root, + count_share_views, +}: BreadcrumbButtonProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const fmIndex = useContext(FmIndexContext); + const [loading, setLoading] = useState(false); + const [shareInfo, setShareInfo] = useState(undefined); + + const uri = useMemo(() => { + return new CrUri(path); + }, [path]); + + useEffect(() => { + if (uri.is_root() && uri.fs() == Filesystem.share) { + setLoading(true); + dispatch(queueLoadShareInfo(uri, count_share_views)) + .then((info) => { + setShareInfo(info); + }) + .catch((_e) => { + setShareInfo(null); + }) + .finally(() => { + setLoading(false); + }); + } else { + setShareInfo(undefined); + } + }, [uri]); + + const onClick = useCallback( + (e?: React.MouseEvent) => { + e && e.stopPropagation(); + if (is_latest && !displayOnly && !is_root && e) { + dispatch(openContextUrlFromUri(fmIndex, path, e)); + return; + } + dispatch(navigateToPath(fmIndex, path, undefined, displayOnly)); + }, + [dispatch, fmIndex, path, is_latest, displayOnly, is_root], + ); + + const displayName = useMemo(() => { + if (uri.is_root()) { + switch (uri.fs()) { + case Filesystem.my: + if (uri.is_search()) { + const searchCategory = uri.query(UriQuery.category); + if (searchCategory.length == 1) { + switch (searchCategory[0]) { + case UriSearchCategory.image: + return t("navbar.photos"); + case UriSearchCategory.video: + return t("navbar.videos"); + case UriSearchCategory.audio: + return t("navbar.music"); + case UriSearchCategory.document: + return t("navbar.documents"); + } + } + } + if (uri.id()) { + const current = SessionManager.currentLoginOrNull(); + if (!current || current.user.id != uri.id()) { + return t("navbar.hisFiles"); + } + } + return t("navbar.myFiles"); + case Filesystem.trash: + return t("navbar.trash"); + case Filesystem.shared_by_me: + return t("navbar.myShare"); + case Filesystem.shared_with_me: + return t("navbar.sharedWithMe"); + case Filesystem.share: + if (shareInfo) { + return shareInfo.name + ? shareInfo.name + : t("application:share.somebodyShare", { + name: shareInfo.owner.nickname, + }); + } else if (shareInfo === null) { + return t("application:share.expiredLink"); + } + + return ""; + default: + "Root"; + } + } + + return name; + }, [name, uri, t, shareInfo]); + + const startIcon = useMemo(() => { + if (uri.is_root()) { + switch (uri.fs()) { + case Filesystem.my: + if (uri.is_search()) { + const searchCategory = uri.query(UriQuery.category); + if (searchCategory.length == 1) { + switch (searchCategory[0]) { + case UriSearchCategory.image: + return { Icons: [Image, ImageOutlined] }; + case UriSearchCategory.video: + return { Icons: [Video, VideoOutlined] }; + case UriSearchCategory.audio: + return { Icons: [MusicNote1, MusicNote1Outlined] }; + case UriSearchCategory.document: + return { Icons: [DocumentText, DocumentTextOutlined] }; + } + } + } + const uid = uri.id(); + if (uid) { + const current = SessionManager.currentLoginOrNull(); + if (!current || current.user.id != uri.id()) { + return { + Element: (props: { [key: string]: any }) => ( + + ), + }; + } + } + return { Icons: [Home, HomeOutlined] }; + case Filesystem.trash: + return { Icons: [Delete, DeleteOutlined] }; + case Filesystem.shared_with_me: + return { Icons: [PeopleTeam, PeopleTeamOutlined] }; + case Filesystem.share: + if (shareInfo) { + return { + Element: (props: { [key: string]: any }) => ( + + ), + }; + } else if (shareInfo === null) { + return { Icons: [LinkDismiss, LinkDismiss] }; + } + return undefined; + default: + return undefined; + } + } + }, [uri, shareInfo]); + + return [loading, displayName, startIcon, onClick, shareInfo] as const; +}; + +const BreadcrumbButton = ({ name, is_root, is_latest, path, displayOnly, ...rest }: BreadcrumbButtonProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const fmIndex = useContext(FmIndexContext); + + const [loading, displayName, startIcon, onClick, shareInfo] = useBreadcrumbButtons({ + name, + is_latest, + path, + displayOnly, + is_root, + count_share_views: true, + }); + const maxWidth = is_latest ? "300px" : is_root ? "initial" : "100px"; + const StartIcon = useMemo(() => { + if (loading) { + return ; + } + if (startIcon?.Icons?.[0]) { + const Icon = startIcon?.Icons?.[0]; + return ; + } + if (startIcon?.Element) { + return startIcon.Element({ sx: { ml: 0.5, width: 20, height: 20 } }); + } + return null; + }, [startIcon, loading]); + + const [drag, drop, isOver, isDragging] = useFileDrag({ + dropUri: path, + }); + + const popupState = usePopupState({ + variant: "popover", + popupId: "shareInfo", + }); + + return ( + <> + {is_latest && !displayOnly && fmIndex == FileManagerIndex.main && } + + theme.transitions.create(["max-width", "color", "background-color"], { + easing: theme.transitions.easing.easeInOut, + duration: theme.transitions.duration.standard, + }), + color: (theme) => (is_latest && !displayOnly ? theme.palette.text.primary : theme.palette.text.secondary), + maxWidth: maxWidth, + }} + startIcon={StartIcon} + endIcon={is_latest && !is_root && !displayOnly ? : undefined} + ref={drop} + {...(shareInfo ? bindHover(popupState) : {})} + {...rest} + > + {loading && } + {!loading && ( + + {displayName} + + )} + + {shareInfo && displayName && ( + + )} + + ); +}; + +export default BreadcrumbButton; diff --git a/src/component/FileManager/TopBar/BreadcrumbHiddenItem.tsx b/src/component/FileManager/TopBar/BreadcrumbHiddenItem.tsx new file mode 100755 index 0000000..60f11c4 --- /dev/null +++ b/src/component/FileManager/TopBar/BreadcrumbHiddenItem.tsx @@ -0,0 +1,74 @@ +import { BreadcrumbButtonProps, useBreadcrumbButtons } from "./BreadcrumbButton.tsx"; +import { ListItemIcon, ListItemText, MenuItem, Skeleton, styled } from "@mui/material"; +import { useContext, useMemo } from "react"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import FileIcon from "../Explorer/FileIcon.tsx"; +import { useFileDrag } from "../Dnd/DndWrappedFile.tsx"; + +import { FmIndexContext } from "../FmIndexContext.tsx"; + +export interface BreadcrumbHiddenItem extends BreadcrumbButtonProps { + onClose: () => void; +} + +export const StyledMenuItem = styled(MenuItem)<{ isDropOver?: boolean }>(({ theme, isDropOver }) => ({ + transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms !important", + transitionProperty: "background-color,opacity,box-shadow", + boxShadow: isDropOver ? `inset 0 0 0 2px ${theme.palette.primary.light}` : "none", +})); + +const BreadcrumbHiddenItem = ({ name, is_root, is_latest, path, onClose, ...rest }: BreadcrumbHiddenItem) => { + const [loading, displayName, startIcon, onClick] = useBreadcrumbButtons({ + name, + is_latest, + path, + count_share_views: true, + }); + const onItemClick = async () => { + onClose(); + onClick && onClick(); + }; + + const fmIndex = useContext(FmIndexContext); + const file = useAppSelector((s) => s.fileManager[fmIndex].tree[path]?.file); + + const StartIcon = useMemo(() => { + if (loading) { + return ; + } + if (startIcon?.Icons?.[0]) { + const Icon = startIcon?.Icons?.[0]; + return ; + } + if (startIcon?.Element) { + return startIcon.Element({ sx: { width: 20, height: 20 } }); + } + }, [startIcon, loading]); + + const [drag, drop, isOver, isDragging] = useFileDrag({ + dropUri: path, + }); + + return ( + + + {StartIcon ? ( + StartIcon + ) : ( + + )} + + {loading ? : displayName} + + ); +}; + +export default BreadcrumbHiddenItem; diff --git a/src/component/FileManager/TopBar/MoreActionMenu.tsx b/src/component/FileManager/TopBar/MoreActionMenu.tsx new file mode 100755 index 0000000..11d0586 --- /dev/null +++ b/src/component/FileManager/TopBar/MoreActionMenu.tsx @@ -0,0 +1,133 @@ +import { ListItemIcon, ListItemText, MenuProps, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { usePopupState } from "material-ui-popup-state/hooks"; +import { useCallback, useContext } from "react"; +import { useTranslation } from "react-i18next"; +import { clearSelected } from "../../../redux/fileManagerSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { createShareShortcut, isMacbook } from "../../../redux/thunks/file.ts"; +import { inverseSelection, pinCurrentView, refreshFileList, selectAll } from "../../../redux/thunks/filemanager.ts"; +import SessionManager from "../../../session"; +import { Filesystem } from "../../../util/uri.ts"; +import { KeyIndicator } from "../../Frame/NavBar/SearchBar.tsx"; +import ArrowSync from "../../Icons/ArrowSync.tsx"; +import Border from "../../Icons/Border.tsx"; +import BorderAll from "../../Icons/BorderAll.tsx"; +import BorderInside from "../../Icons/BorderInside.tsx"; +import FolderLink from "../../Icons/FolderLink.tsx"; +import PinOutlined from "../../Icons/PinOutlined.tsx"; +import { DenseDivider, SquareMenu, SquareMenuItem } from "../ContextMenu/ContextMenu.tsx"; +import { FmIndexContext } from "../FmIndexContext.tsx"; + +const MoreActionMenu = ({ onClose, ...rest }: MenuProps) => { + const { t } = useTranslation(); + const fmIndex = useContext(FmIndexContext); + const fs = useAppSelector((state) => state.fileManager[fmIndex].current_fs); + const dispatch = useAppDispatch(); + const isLogin = !!SessionManager.currentLoginOrNull(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + + const mountPopupState = usePopupState({ + variant: "popover", + popupId: "mount", + }); + + const onPinClicked = useCallback(() => { + dispatch(pinCurrentView(fmIndex)); + onClose && onClose({}, "escapeKeyDown"); + }, [dispatch, onClose, fmIndex]); + + const onCreateShortcutClicked = useCallback(() => { + dispatch(createShareShortcut(fmIndex)); + onClose && onClose({}, "escapeKeyDown"); + }, [dispatch, onClose, fmIndex]); + + const onSelectAllClicked = useCallback(() => { + onClose && onClose({}, "escapeKeyDown"); + dispatch(selectAll(fmIndex)); + }, [dispatch, onClose, fmIndex]); + + const onSelectNoneClicked = useCallback(() => { + onClose && onClose({}, "escapeKeyDown"); + dispatch(clearSelected({ index: fmIndex, value: undefined })); + }, [dispatch, onClose, fmIndex]); + + const onInverseSelectionClicked = useCallback(() => { + onClose && onClose({}, "escapeKeyDown"); + dispatch(inverseSelection(fmIndex)); + }, [dispatch, onClose, fmIndex]); + + const onRefreshClicked = useCallback(() => { + onClose && onClose({}, "escapeKeyDown"); + dispatch(refreshFileList(fmIndex)); + }, [dispatch, onClose, fmIndex]); + + return ( + + {isMobile && ( + <> + + + + + {t("application:fileManager.refresh")} + + + )} + {isLogin && ( + + + + + {t("application:fileManager.pin")} + + )} + {isLogin && fs == Filesystem.share && ( + + + + + {t("application:fileManager.saveShortcut")} + + )} + {isLogin && } + + + + + {t("application:fileManager.selectAll")} + + {isMacbook ? "⌘" : "Ctrl"}+A + + + + + + + {t("application:fileManager.selectNone")} + + + + + + {t("application:fileManager.invertSelection")} + + + ); +}; + +export default MoreActionMenu; diff --git a/src/component/FileManager/TopBar/NavHeader.tsx b/src/component/FileManager/TopBar/NavHeader.tsx new file mode 100755 index 0000000..a86bf58 --- /dev/null +++ b/src/component/FileManager/TopBar/NavHeader.tsx @@ -0,0 +1,43 @@ +import { Stack, useMediaQuery, useTheme } from "@mui/material"; +import Breadcrumb from "./Breadcrumb.tsx"; +import TopActions from "./TopActions.tsx"; +import { RadiusFrame } from "../../Frame/RadiusFrame.tsx"; +import TopActionsSecondary from "./TopActionsSecondary.tsx"; +import { SearchIndicator } from "../Search/SearchIndicator.tsx"; + +const NavHeader = () => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + return ( + + + + + + {!isMobile && ( + + + + )} + + + + + ); +}; + +export default NavHeader; diff --git a/src/component/FileManager/TopBar/ShareInfoPopover.tsx b/src/component/FileManager/TopBar/ShareInfoPopover.tsx new file mode 100755 index 0000000..4a32cc7 --- /dev/null +++ b/src/component/FileManager/TopBar/ShareInfoPopover.tsx @@ -0,0 +1,127 @@ +import { Box, Divider, PopoverProps, Stack, styled, Typography } from "@mui/material"; +import HoverPopover from "material-ui-popup-state/HoverPopover"; +import { useCallback } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Share } from "../../../api/explorer.ts"; +import TimeBadge from "../../Common/TimeBadge.tsx"; +import UserBadge from "../../Common/User/UserBadge.tsx"; +import Eye from "../../Icons/Eye.tsx"; +import Timer from "../../Icons/Timer.tsx"; + +interface ShareInfoPopoverProps extends PopoverProps { + displayName: string; + shareInfo: Share; +} + +export const PropTypography = styled(Typography)({ + display: "flex", + alignItems: "center", + gap: 8, +}); + +export const separator = " • "; + +export interface ShareExpiresProps { + expires?: string; + remain_downloads?: number; +} +export const ShareExpires = ({ expires, remain_downloads }: ShareExpiresProps) => { + const { t } = useTranslation(); + return ( + <> + {expires && ( + + ]} + /> + + )} + {expires && remain_downloads != undefined && separator} + {remain_downloads != undefined && + t("application:share.expireAfterDownloads", { + downloads: remain_downloads, + })} + + ); +}; + +export const ShareStatistics = ({ shareInfo }: { shareInfo: Share }) => { + const { t } = useTranslation(); + return ( + <> + {t("application:share.statisticsViews", { + views: shareInfo.visited, + })} + {shareInfo.downloaded && + separator + + t("application:share.statisticsDownloads", { + downloads: shareInfo.downloaded, + })} + + ); +}; + +const ShareInfoPopover = ({ displayName, shareInfo, ...rest }: ShareInfoPopoverProps) => { + const { t } = useTranslation(); + const stopPropagation = useCallback((e: any) => e.stopPropagation(), []); + return ( + + + + {displayName} + + + {(shareInfo.remain_downloads || shareInfo.expires) && ( + + + + + )} + {shareInfo.visited > 0 && ( + + + + + )} + + + + + {shareInfo.created_at && ( + + )} + + + + ); +}; + +export default ShareInfoPopover; diff --git a/src/component/FileManager/TopBar/SortMethodMenu.tsx b/src/component/FileManager/TopBar/SortMethodMenu.tsx new file mode 100755 index 0000000..9fa3951 --- /dev/null +++ b/src/component/FileManager/TopBar/SortMethodMenu.tsx @@ -0,0 +1,122 @@ +import { ListItemIcon, ListItemText, Menu, MenuItem, MenuProps } from "@mui/material"; +import { useContext, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { changeSortOption } from "../../../redux/thunks/filemanager.ts"; +import Checkmark from "../../Icons/Checkmark.tsx"; + +import { FmIndexContext } from "../FmIndexContext.tsx"; + +interface sortOption { + label: string; + order_by: string; + order_direction: string; + selected?: boolean; +} + +const supportOption: { + [key: string]: sortOption; +} = { + name_asc: { + label: "application:fileManager.sortMethods.A-Z", + order_by: "name", + order_direction: "asc", + }, + name_desc: { + label: "application:fileManager.sortMethods.Z-A", + order_by: "name", + order_direction: "desc", + }, + size_asc: { + label: "application:fileManager.sortMethods.smallest", + order_by: "size", + order_direction: "asc", + }, + size_desc: { + label: "application:fileManager.sortMethods.largest", + order_by: "size", + order_direction: "desc", + }, + updated_at_asc: { + label: "application:fileManager.sortMethods.oldestModified", + order_by: "updated_at", + order_direction: "asc", + }, + updated_at_desc: { + label: "application:fileManager.sortMethods.newestModified", + order_by: "updated_at", + order_direction: "desc", + }, + created_at_asc: { + label: "application:fileManager.sortMethods.oldestUploaded", + order_by: "created_at", + order_direction: "asc", + }, + created_at_desc: { + label: "application:fileManager.sortMethods.newestUploaded", + order_by: "created_at", + order_direction: "desc", + }, + _asc: { + label: "application:fileManager.sortMethods.oldestUploaded", + order_by: "created_at", + order_direction: "asc", + }, + _desc: { + label: "application:fileManager.sortMethods.oldestUploaded", + order_by: "created_at", + order_direction: "asc", + }, +}; + +const SortMethodMenu = ({ onClose, ...rest }: MenuProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const fmIndex = useContext(FmIndexContext); + const orderMethodOptions = useAppSelector((state) => state.fileManager[fmIndex].list?.props.order_by_options); + const orderDirectionOption = useAppSelector( + (state) => state.fileManager[fmIndex].list?.props.order_direction_options, + ); + const sortBy = useAppSelector((state) => state.fileManager[fmIndex].sortBy); + const sortDirection = useAppSelector((state) => state.fileManager[fmIndex].sortDirection); + + const options = useMemo(() => { + if (!orderMethodOptions || !orderDirectionOption) return []; + const res: sortOption[] = []; + const selectedVal = !sortBy || !sortDirection ? "created_at_asc" : `${sortBy}_${sortDirection}`; + orderMethodOptions.forEach((method) => { + orderDirectionOption.forEach((direction) => { + const key = `${method}_${direction}`; + if (supportOption[key]) { + res.push({ ...supportOption[key], selected: key == selectedVal }); + } + }); + }); + return res; + }, [orderMethodOptions, orderDirectionOption, sortBy, sortDirection]); + + const selectOption = (option: sortOption) => { + dispatch(changeSortOption(fmIndex, option.order_by, option.order_direction)); + onClose && onClose({}, "escapeKeyDown"); + }; + + return ( + + {options.map((option) => ( + selectOption(option)}> + {!option.selected && {t(option.label)}} + {option.selected && ( + <> + + + + {t(option.label)} + + )} + + ))} + + ); +}; + +export default SortMethodMenu; diff --git a/src/component/FileManager/TopBar/TopActions.tsx b/src/component/FileManager/TopBar/TopActions.tsx new file mode 100755 index 0000000..3f2ddaa --- /dev/null +++ b/src/component/FileManager/TopBar/TopActions.tsx @@ -0,0 +1,81 @@ +import { Button, ButtonGroup, styled, useMediaQuery, useTheme } from "@mui/material"; +import { bindPopover } from "material-ui-popup-state"; +import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import { useContext } from "react"; +import { useTranslation } from "react-i18next"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import ArrowSort from "../../Icons/ArrowSort.tsx"; +import TableSettingsOutlined from "../../Icons/TableSettings.tsx"; +import SortMethodMenu from "./SortMethodMenu.tsx"; +import ViewOptionPopover from "./ViewOptionPopover.tsx"; + +import MoreHorizontal from "../../Icons/MoreHorizontal.tsx"; +import { FmIndexContext } from "../FmIndexContext.tsx"; +import MoreActionMenu from "./MoreActionMenu.tsx"; + +export const ActionButton = styled(Button)(({ theme }) => ({ + border: `1px solid ${theme.palette.divider}`, + color: theme.palette.text.primary, +})); + +export const ActionButtonGroup = styled(ButtonGroup)(({ theme }) => ({ + "& .MuiButtonGroup-firstButton, .MuiButtonGroup-middleButton, .MuiButtonGroup-lastButton": { + "&:hover": { + "border-color": theme.palette.primary.main, + }, + }, + height: "100%", +})); + +const TopActions = () => { + const { t } = useTranslation(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const fmIndex = useContext(FmIndexContext); + const sortOptions = useAppSelector((state) => state.fileManager[fmIndex].list?.props.order_by_options); + const isSingleFileView = useAppSelector((state) => state.fileManager[fmIndex].list?.single_file_view); + const viewPopupState = usePopupState({ + variant: "popover", + popupId: "viewOption", + }); + const sortPopupState = usePopupState({ + variant: "popover", + popupId: "sortOption", + }); + const morePopupState = usePopupState({ + variant: "popover", + popupId: "moreActions", + }); + return ( + <> + + } + > + {isMobile ? : t("application:fileManager.view")} + + {(!(!sortOptions || isSingleFileView) || !isMobile) && ( + } + {...bindTrigger(sortPopupState)} + > + {isMobile ? : t("application:fileManager.sortMethod")} + + )} + {isMobile && ( + + + + )} + + {isMobile && } + + + + ); +}; + +export default TopActions; diff --git a/src/component/FileManager/TopBar/TopActionsSecondary.tsx b/src/component/FileManager/TopBar/TopActionsSecondary.tsx new file mode 100755 index 0000000..acb67c5 --- /dev/null +++ b/src/component/FileManager/TopBar/TopActionsSecondary.tsx @@ -0,0 +1,65 @@ +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { ActionButton, ActionButtonGroup } from "./TopActions.tsx"; +import ArrowSync from "../../Icons/ArrowSync.tsx"; +import { styled, Tooltip } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { useContext, useState } from "react"; +import MoreHorizontal from "../../Icons/MoreHorizontal.tsx"; +import { refreshFileList } from "../../../redux/thunks/filemanager.ts"; +import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import MoreActionMenu from "./MoreActionMenu.tsx"; +import { FileManagerIndex } from "../FileManager.tsx"; +import { FmIndexContext } from "../FmIndexContext.tsx"; + +const SpinArrowSync = styled(ArrowSync)(() => ({ + "@keyframes spin": { + from: { + transform: "rotate(0deg)", + }, + to: { + transform: "rotate(360deg)", + }, + }, +})); + +const TopActionsSecondary = () => { + const { t } = useTranslation(); + const fmIndex = useContext(FmIndexContext); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(false); + const morePopupState = usePopupState({ + variant: "popover", + popupId: "moreActions", + }); + const refresh = async () => { + setLoading(true); + await dispatch(refreshFileList(fmIndex)); + setLoading(false); + }; + return ( + <> + + + refresh()}> + + + + {fmIndex == FileManagerIndex.main && ( + + + + )} + + + + ); +}; + +export default TopActionsSecondary; diff --git a/src/component/FileManager/TopBar/ViewOptionPopover.tsx b/src/component/FileManager/TopBar/ViewOptionPopover.tsx new file mode 100755 index 0000000..6140226 --- /dev/null +++ b/src/component/FileManager/TopBar/ViewOptionPopover.tsx @@ -0,0 +1,244 @@ +import { + Box, + Collapse, + Popover, + PopoverProps, + Slider, + SvgIconProps, + ToggleButton, + ToggleButtonGroup, + Typography, +} from "@mui/material"; +import React, { useContext, useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { Layouts } from "../../../redux/fileManagerSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { + applyGalleryWidth, + changePageSize, + setLayoutSetting, + setThumbToggle, +} from "../../../redux/thunks/filemanager.ts"; +import NavIconTransition from "../../Frame/NavBar/NavIconTransition.tsx"; +import AppsList from "../../Icons/AppsList.tsx"; +import AppsListOutlined from "../../Icons/AppsListOutlined.tsx"; +import Grid from "../../Icons/Grid.tsx"; +import GridOutlined from "../../Icons/GridOutlined.tsx"; +import ImageCopy from "../../Icons/ImageCopy.tsx"; +import ImageCopyOutlined from "../../Icons/ImageCopyOutlined.tsx"; +import ImageOffOutlined from "../../Icons/ImageOffOutlined.tsx"; +import ImageOutlined from "../../Icons/ImageOutlined.tsx"; + +import { setListViewColumnSettingDialog } from "../../../redux/globalStateSlice.ts"; +import Setting from "../../Icons/Setting.tsx"; +import { FmIndexContext } from "../FmIndexContext.tsx"; + +const layoutOptions: { + label: string; + value: string; + icon: ((props: SvgIconProps) => JSX.Element)[]; +}[] = [ + { + label: "application:fileManager.gridView", + value: "grid", + icon: [Grid, GridOutlined], + }, + { + label: "application:fileManager.listView", + value: "list", + icon: [AppsList, AppsListOutlined], + }, + { + label: "application:fileManager.galleryView", + value: "gallery", + icon: [ImageCopy, ImageCopyOutlined], + }, +]; + +const thumbOptions: { + label: string; + value: boolean; + icon: (props: SvgIconProps) => JSX.Element; +}[] = [ + { + label: "application:fileManager.on", + value: true, + icon: ImageOutlined, + }, + { + label: "application:fileManager.off", + value: false, + icon: ImageOffOutlined, + }, +]; + +export const MinPageSize = 50; + +const ViewOptionPopover = ({ ...rest }: PopoverProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const fmIndex = useContext(FmIndexContext); + const layout = useAppSelector((state) => state.fileManager[fmIndex].layout); + const showThumb = useAppSelector((state) => state.fileManager[fmIndex].showThumb); + const pageSize = useAppSelector((state) => state.fileManager[fmIndex].pageSize); + const pageSizeMax = useAppSelector((state) => state.fileManager[fmIndex].list?.props.max_page_size); + const galleryWidth = useAppSelector((state) => state.fileManager[fmIndex].galleryWidth); + const [desiredPageSize, setDesiredPageSize] = React.useState(pageSize); + const pageSizeMaxSafe = pageSizeMax ?? desiredPageSize; + const step = pageSizeMaxSafe - MinPageSize <= 100 ? 1 : 10; + const [desiredImageWidth, setDesiredImageWidth] = React.useState(galleryWidth); + + useEffect(() => { + setDesiredPageSize(pageSize); + }, [pageSize]); + + const handleLayoutChange = (_event: React.MouseEvent, newMode: string) => { + if (newMode) { + dispatch(setLayoutSetting(fmIndex, newMode)); + } + }; + + const handleThumbChange = (_event: React.MouseEvent, newMode: boolean) => { + dispatch(setThumbToggle(fmIndex, newMode)); + }; + + const handlePageSlideChange = (_event: Event, newValue: number | number[]) => { + setDesiredPageSize(newValue as number); + }; + + const commitPageSize = (_event: React.SyntheticEvent | Event, newValue: number | number[]) => { + const pageSize = Math.max(MinPageSize, newValue as number); + dispatch(changePageSize(fmIndex, pageSize)); + }; + + const handleImageSizeChange = (_event: Event, newValue: number | number[]) => { + setDesiredImageWidth(newValue as number); + }; + + const commitImageSize = (_event: React.SyntheticEvent | Event, newValue: number | number[]) => { + dispatch(applyGalleryWidth(fmIndex, newValue as number)); + }; + + return ( + + + + + {t("application:fileManager.layout")} + + + {layoutOptions.map((option) => ( + + + {t(option.label)} + + ))} + + + + + {t("application:fileManager.thumbnails")} + + + {thumbOptions.map((option) => ( + + + {t(option.label)} + + ))} + + + + + {t("application:fileManager.listColumnSetting")} + + + dispatch(setListViewColumnSettingDialog(true))}> + + {t("application:fileManager.listColumnSetting")} + + + + + + {t("application:fileManager.imageSize")} + + + + + + 50 + + + 500 + + + + + + {t("application:fileManager.paginationSize")} + + + + + + {MinPageSize} + + + {pageSizeMaxSafe} + + + + + + ); +}; + +export default ViewOptionPopover; diff --git a/src/component/FileManager/TreeView/Pinned.tsx b/src/component/FileManager/TreeView/Pinned.tsx new file mode 100755 index 0000000..6399e32 --- /dev/null +++ b/src/component/FileManager/TreeView/Pinned.tsx @@ -0,0 +1,61 @@ +import React, { memo, useContext, useMemo } from "react"; +import SessionManager from "../../../session"; +import TreeFiles from "./TreeFiles.tsx"; +import CrUri, { Filesystem } from "../../../util/uri.ts"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import { FmIndexContext } from "../FmIndexContext.tsx"; + +interface PinnedItem { + uri: string; + name?: string; + crUri: CrUri; + useElements?: boolean; +} + +export const usePinned = () => { + const fmIndex = useContext(FmIndexContext); + const generation = useAppSelector((state) => state.globalState.pinedGeneration); + const path_root = useAppSelector((state) => state.fileManager[fmIndex].path_root); + const pined = useMemo(() => { + try { + return SessionManager.currentLogin().user.pined?.map((p): PinnedItem => { + return { + uri: p.uri, + name: p.name, + crUri: new CrUri(p.uri), + useElements: p.uri == path_root, + }; + }); + } catch (e) {} + }, [generation, path_root]); + + return pined; +}; + +const Pinned = memo(() => { + const fmIndex = useContext(FmIndexContext); + const elements = useAppSelector((state) => state.fileManager[fmIndex].path_elements); + const pined = usePinned(); + + return ( + <> + {pined?.map((p, index) => ( + + ))} + + ); +}); +export default Pinned; diff --git a/src/component/FileManager/TreeView/TreeFile.tsx b/src/component/FileManager/TreeView/TreeFile.tsx new file mode 100755 index 0000000..0507150 --- /dev/null +++ b/src/component/FileManager/TreeView/TreeFile.tsx @@ -0,0 +1,301 @@ +import React, { useCallback, useContext, useMemo, useState } from "react"; + +import { Fade, IconButton, Skeleton, styled, Tooltip } from "@mui/material"; +import { SideNavItemBase } from "../../Frame/NavBar/SideNavItem.tsx"; +import clsx from "clsx"; +import FileIcon from "../Explorer/FileIcon.tsx"; +import { FileResponse } from "../../../api/explorer.ts"; +import { TreeItem, treeItemClasses, TreeItemContentProps, TreeItemProps, useTreeItem } from "@mui/x-tree-view"; +import NavIconTransition from "../../Frame/NavBar/NavIconTransition.tsx"; +import { mergeRefs } from "../../../util"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { loadChild, navigateToPath } from "../../../redux/thunks/filemanager.ts"; +import FacebookCircularProgress from "../../Common/CircularProgress.tsx"; +import CaretDown from "../../Icons/CaretDown.tsx"; +import { StartIcon } from "../TopBar/BreadcrumbButton.tsx"; +import { openFileContextMenu } from "../../../redux/thunks/file.ts"; +import Dismiss from "../../Icons/Dismiss.tsx"; +import { useTranslation } from "react-i18next"; +import { unPinFromSidebar } from "../../../redux/thunks/settings.ts"; +import { pinedPrefix } from "./TreeFiles.tsx"; +import { useFileDrag } from "../Dnd/DndWrappedFile.tsx"; +import { FmIndexContext } from "../FmIndexContext.tsx"; +import { NoWrapTypography } from "../../Common/StyledComponents.tsx"; + +const CustomContentRoot = styled(SideNavItemBase)<{ + isDragging?: boolean; + isDropOver?: boolean; +}>(({ theme, isDragging, isDropOver }) => ({ + "& .MuiTreeItem-iconContainer": { + marginLeft: theme.spacing(1), + }, + opacity: isDragging ? 0.5 : 1, + transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms", + transitionProperty: "opacity,box-shadow,background-color", + boxShadow: isDropOver ? `inset 0 0 0 2px ${theme.palette.primary.light}` : "none", + height: "32px", +})); + +const StyledTreeItemRoot = styled(TreeItem)(() => ({ + [`& .${treeItemClasses.group}`]: { + marginLeft: 0, + [`& .${treeItemClasses.content}`]: { + marginLeft: 0, + }, + }, +})) as unknown as typeof TreeItem; + +export const CaretDownIcon = styled(CaretDown)<{ expanded: boolean }>(({ theme, expanded }) => ({ + fontSize: "12px!important", + transform: `rotate(${expanded ? 0 : -90}deg)`, + transition: theme.transitions.create("transform", { + duration: theme.transitions.duration.shortest, + easing: theme.transitions.easing.easeInOut, + }), +})); + +export interface CustomContentProps extends TreeItemContentProps { + parent?: string; + level?: number; + notLoaded?: boolean; + file?: FileResponse; + fileIcon?: StartIcon; + loading?: boolean; + pinned?: boolean; + canDrop?: boolean; +} + +export interface TreeFileProps extends TreeItemProps { + parent?: string; + level?: number; + notLoaded?: boolean; + file?: FileResponse; + fileIcon?: StartIcon; + loading?: boolean; + pinned?: boolean; + canDrop?: boolean; +} + +const SmallIconButton = styled(IconButton)(() => ({ + fontSize: "0.8rem", +})); + +interface UnpinButton { + show: boolean; + uri: string; +} +const UnpinButton = (props: UnpinButton) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const [loading, setLoading] = useState(false); + const onClick = useCallback( + async (e: React.MouseEvent) => { + e.stopPropagation(); + setLoading(true); + try { + await dispatch(unPinFromSidebar(props.uri)); + } catch (e) { + setLoading(false); + } + }, + [setLoading], + ); + + return ( + + + e.stopPropagation()} onClick={onClick} size="small"> + + + + + ); +}; + +const CustomContent = React.memo( + React.forwardRef(function CustomContent(props: CustomContentProps, ref) { + const dispatch = useAppDispatch(); + const fmIndex = useContext(FmIndexContext); + const [loading, setLoading] = useState(false); + const [showDelete, setShowDelete] = useState(false); + const { classes, className, label, nodeId, icon: iconProp, expansionIcon, displayIcon, file, fileIcon } = props; + + const { disabled, expanded, selected, focused, handleExpansion, handleSelection, preventSelection } = + useTreeItem(nodeId); + + const uri = useMemo(() => { + // Trim 'pinedPrefix' if exist in prefix + if (nodeId.startsWith(pinedPrefix)) { + return nodeId.substring(pinedPrefix.length); + } + + return nodeId; + }, [nodeId]); + + const icon = iconProp || expansionIcon || displayIcon; + + const handleMouseDown = (event: React.MouseEvent) => { + preventSelection(event); + }; + + const handleExpansionClick = useCallback( + async (event: React.MouseEvent) => { + event.stopPropagation(); + let timeOutID: NodeJS.Timeout | undefined; + handleExpansion(event); + if (!expanded) { + try { + await dispatch(loadChild(fmIndex, uri, () => (timeOutID = setTimeout(() => setLoading(true), 300)))); + } finally { + if (timeOutID) { + clearTimeout(timeOutID); + } + setLoading(false); + } + } + }, + [handleExpansion, setLoading, dispatch, uri], + ); + + const handleSelectionClick = useCallback( + (event: React.MouseEvent) => { + event.stopPropagation(); + handleSelection(event); + dispatch(navigateToPath(fmIndex, uri, file)); + }, + [dispatch, handleSelection, fmIndex, uri], + ); + + const FileItemIcon = useMemo(() => { + if (props.loading) { + return ; + } + if (fileIcon && fileIcon.Icons) { + return ( + + ); + } + if (fileIcon && fileIcon.Element) { + return ; + } + return ( + + ); + }, [file, fileIcon, selected, props.notLoaded, props.loading]); + + const onContextMenu = useCallback( + (e: React.MouseEvent) => { + if (!file || file.name === "") { + return; + } + dispatch(openFileContextMenu(fmIndex, file, true, e)); + }, + [file, dispatch, fmIndex], + ); + + const fileName = useMemo( + () => ( + + + {!props.loading && label} + {props.loading && } + + + ), + [label, handleSelectionClick, props.loading], + ); + + const onMouseEnter = useCallback(() => { + if (props.pinned) setShowDelete(true); + }, [setShowDelete, props.pinned]); + + const onMouseLeave = useCallback(() => { + if (props.pinned) setShowDelete(false); + }, [setShowDelete, props.pinned]); + + const [drag, drop, isOver, isDragging] = useFileDrag({ + file, + dropUri: props.canDrop ? nodeId : undefined, + }); + + const mergedRef = useCallback( + (val: any) => { + mergeRefs(ref as React.Ref, drop, drag)(val); + }, + [ref, drop, drag], + ); + + return ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions + `${theme.spacing((props.level ?? 0) * 2)}!important`, + }} + onClick={handleSelectionClick} + ref={mergedRef} + > + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */} +
    + {icon && !loading && } + {icon && loading && } +
    + {FileItemIcon} + {fileName} + +
    + ); + }), +); + +const TreeFile = React.memo( + React.forwardRef(function CustomTreeItem(props: TreeFileProps, ref: React.Ref) { + const contentProps = useMemo(() => { + const { level, file, notLoaded, fileIcon, loading, pinned, canDrop } = props; + return { level, file, notLoaded, fileIcon, loading, pinned, canDrop }; + }, [props.level, props.file, props.notLoaded, props.fileIcon, props.loading, props.canDrop, props.pinned]); + return ( + + ); + }), +); + +export default TreeFile; diff --git a/src/component/FileManager/TreeView/TreeFiles.tsx b/src/component/FileManager/TreeView/TreeFiles.tsx new file mode 100755 index 0000000..b72ec52 --- /dev/null +++ b/src/component/FileManager/TreeView/TreeFiles.tsx @@ -0,0 +1,143 @@ +import { Box } from "@mui/material"; +import path from "path-browserify"; +import React, { useContext, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import CrUri, { Filesystem } from "../../../util/uri.ts"; +import SideNavItem from "../../Frame/NavBar/SideNavItem.tsx"; +import { FmIndexContext } from "../FmIndexContext.tsx"; +import { useBreadcrumbButtons } from "../TopBar/BreadcrumbButton.tsx"; +import TreeFile from "./TreeFile.tsx"; + +export interface TreeFilesProps { + path: string; + elements?: string[]; + notLoaded?: boolean; + level: number; + flatten?: boolean; + labelOverwrite?: string; + pinned?: boolean; + canDrop?: boolean; + [key: string]: any; +} + +export const pinedPrefix = "Pined"; + +const TreeFiles = React.memo( + React.forwardRef( + ( + { path: p, level, elements, labelOverwrite, notLoaded, pinned, flatten, canDrop, ...rest }: TreeFilesProps, + ref: React.Ref, + ) => { + const { t } = useTranslation(); + const fmIndex = useContext(FmIndexContext); + const parentsCache = useAppSelector((state) => state.fileManager[fmIndex].tree[p]); + const [limit, setLimit] = React.useState(50); + const uri = useMemo(() => new CrUri(p), [p]); + const nodeId = useMemo(() => { + if (pinned && flatten) { + return pinedPrefix + p; + } + + return p; + }, [pinned, p, flatten]); + const [loading, displayName, startIcon, onClick] = useBreadcrumbButtons({ + name: parentsCache && parentsCache.file ? parentsCache.file.name : path.basename(uri.path()), + is_latest: false, + path: p, + }); + + const childTreeFiles = useMemo(() => { + var res: TreeFilesProps[] = []; + + let newParent: string | null = null; + let elementPushed = false; + // Add current if loaded children exist + if (elements && elements.length >= 1) { + newParent = new CrUri(p).join(elements[0]).toString(); + } + + // load from store cache + let currentIndex = 0; + if (parentsCache && parentsCache.children) { + parentsCache.children.forEach((child) => { + let childElements: string[] | undefined = undefined; + if (newParent && newParent == child) { + childElements = elements?.slice(1); + elementPushed = true; + currentIndex = res.length; + } + res.push({ + level: level + 1, + path: child, + elements: childElements, + }); + }); + } + + if (elements && elements.length >= 1 && !elementPushed) { + const childElements = elements.slice(1); + currentIndex = res.length; + res.push({ + level: level + 1, + path: newParent ?? "", + elements: childElements, + notLoaded: childElements.length > 0, + }); + } + + if (currentIndex >= limit) { + [res[currentIndex], res[0]] = [res[0], res[currentIndex]]; + } + + return res; + }, [p, elements, parentsCache, limit]); + + const shadowChild = useMemo(() => { + if (flatten || (parentsCache?.children && parentsCache.children.length == 0)) { + return null; + } + return ; + }, [parentsCache, flatten]); + + return ( + <> + + {!flatten && childTreeFiles.length > 0 + ? childTreeFiles + .slice(0, limit) + .map((f) => ( + + )) + : shadowChild} + + {limit < childTreeFiles.length ? ( + setLimit((l) => l + 50)} /> + ) : null} + + ); + }, + ), +); + +export default TreeFiles; diff --git a/src/component/FileManager/TreeView/TreeNavigation.tsx b/src/component/FileManager/TreeView/TreeNavigation.tsx new file mode 100755 index 0000000..b57fbe2 --- /dev/null +++ b/src/component/FileManager/TreeView/TreeNavigation.tsx @@ -0,0 +1,147 @@ +import { Box, Collapse, Fade } from "@mui/material"; + +import { ChevronRight, ExpandMore } from "@mui/icons-material"; +import { TreeView } from "@mui/x-tree-view"; +import React, { useEffect } from "react"; +import { TransitionGroup } from "react-transition-group"; +import { defaultPath, defaultSharedWithMePath, defaultTrashPath } from "../../../hooks/useNavigation.tsx"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import SessionManager, { UserSettings } from "../../../session"; +import CrUri, { Filesystem } from "../../../util/uri.ts"; +import { FileManagerIndex } from "../FileManager.tsx"; +import { FmIndexContext } from "../FmIndexContext.tsx"; +import Pinned, { usePinned } from "./Pinned.tsx"; +import TreeFiles from "./TreeFiles.tsx"; + +export interface TreeNavigationProps { + scrollRef?: React.MutableRefObject; + index?: number; + hideWithDrawer?: boolean; + disableSharedWithMe?: boolean; + disableTrash?: boolean; +} + +const TreeNavigation = React.memo( + ({ index = 0, scrollRef, hideWithDrawer, disableSharedWithMe, disableTrash }: TreeNavigationProps) => { + const base = useAppSelector((s) => s.fileManager[index].path_root); + const path = useAppSelector((s) => s.fileManager[index].pure_path_with_category); + const currentFs = useAppSelector((s) => s.fileManager[index].current_fs); + const elements = useAppSelector((s) => s.fileManager[index].path_elements); + const drawerOpen = useAppSelector((s) => s.globalState.drawerOpen); + const [expanded, setExpanded] = React.useState([]); + + useEffect(() => { + const res: string[] = []; + if (path) { + const p = new CrUri(path); + if (p.is_search()) { + return; + } + } + if (base && elements) { + const b = new CrUri(base); + res.push(base); + elements.forEach((element) => { + b.join(element); + res.push(b.toString()); + }); + } + if (SessionManager.getWithFallback(UserSettings.TreeViewAutoExpand)) { + setExpanded((e) => [...new Set([...e, ...res])]); + } + }, [path, base, elements]); + + const pinned = usePinned(); + const alreadyPinned = base && pinned && pinned.find((p) => p.uri == base); + const showShareTree = base && currentFs && currentFs == Filesystem.share && !alreadyPinned; + + useEffect(() => { + if (showShareTree && scrollRef && scrollRef.current) { + scrollRef.current?.scrollTo({ top: 0, behavior: "smooth" }); + } + }, [showShareTree]); + + const isLogin = !!SessionManager.currentLoginOrNull(); + + return ( + + + + } + defaultExpandIcon={} + expanded={expanded} + onNodeToggle={(_event, nodeIds: string[]) => { + setExpanded(nodeIds); + }} + > + + {showShareTree && ( + + + + )} + {isLogin && ( + <> + + {index == FileManagerIndex.main && ( + <> + + + + + + )} + {!disableSharedWithMe && ( + + )} + {!disableTrash && ( + + )} + + + )} + + + + + + ); + }, +); + +export default TreeNavigation; diff --git a/src/component/Frame/FrameManagerBundle.tsx b/src/component/Frame/FrameManagerBundle.tsx new file mode 100755 index 0000000..27f97ef --- /dev/null +++ b/src/component/Frame/FrameManagerBundle.tsx @@ -0,0 +1,4 @@ +import { FileManager } from "../FileManager/FileManager.tsx"; +import NavBarFrame, { AutoNavbarFrame } from "./NavBarFrame.tsx"; + +export { AutoNavbarFrame, FileManager, NavBarFrame }; diff --git a/src/component/Frame/HeadlessFrame.tsx b/src/component/Frame/HeadlessFrame.tsx new file mode 100755 index 0000000..e7a09db --- /dev/null +++ b/src/component/Frame/HeadlessFrame.tsx @@ -0,0 +1,106 @@ +import { Box, Container, Grid, Paper } from "@mui/material"; +import { Outlet, useNavigation } from "react-router-dom"; +import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts"; +import AutoHeight from "../Common/AutoHeight.tsx"; +import CircularProgress from "../Common/CircularProgress.tsx"; +import Logo from "../Common/Logo.tsx"; +import LanguageSwitcher from "../Common/LanguageSwitcher.tsx"; +import PoweredBy from "./PoweredBy.tsx"; + +const Loading = () => { + return ( + + + + ); +}; + +const HeadlessFrame = () => { + const loading = useAppSelector((state) => state.globalState.loading.headlessFrame); + const { headless_footer, headless_bottom, sidebar_bottom } = useAppSelector( + (state) => state.siteConfig.basic?.config?.custom_html ?? {}, + ); + const dispatch = useAppDispatch(); + let navigation = useNavigation(); + + return ( + + theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900], + flexGrow: 1, + height: "100vh", + overflow: "auto", + }} + > + + + + `${theme.spacing(2)} ${theme.spacing(3)} ${theme.spacing(3)}`, + }} + > + + + {/* è¯­è¨€åˆ‡æ¢æŒ‰é’® */} + + + +
    + + + {headless_bottom && ( + +
    + + )} + + {(loading || navigation.state !== "idle") && } +
    + + +
    + + {headless_footer && ( + +
    + + )} + + + + ); +}; + +export default HeadlessFrame; diff --git a/src/component/Frame/NavBar/AppDrawer.tsx b/src/component/Frame/NavBar/AppDrawer.tsx new file mode 100755 index 0000000..2089923 --- /dev/null +++ b/src/component/Frame/NavBar/AppDrawer.tsx @@ -0,0 +1,93 @@ +import { Box, Drawer, Popover, PopoverProps, Stack, useMediaQuery, useTheme } from "@mui/material"; +import { useContext, useRef } from "react"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import SessionManager from "../../../session"; +import TreeNavigation from "../../FileManager/TreeView/TreeNavigation.tsx"; +import { PageVariant, PageVariantContext } from "../NavBarFrame.tsx"; +import DrawerHeader from "./DrawerHeader.tsx"; +import PageNavigation, { AdminPageNavigation } from "./PageNavigation.tsx"; +import StorageSummary from "./StorageSummary.tsx"; + +const DrawerContent = () => { + const { sidebar_bottom } = useAppSelector((state) => state.siteConfig.basic?.config?.custom_html ?? {}); + const scrollRef = useRef(); + const user = SessionManager.currentLoginOrNull(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const pageVariant = useContext(PageVariantContext); + const isDashboard = pageVariant === PageVariant.dashboard; + return ( + <> + + + {!isDashboard && ( + <> + + + {user && } + + )} + {isDashboard && } + {sidebar_bottom && ( + +
    + + )} + + + ); +}; + +export const DrawerPopover = (props: PopoverProps) => { + const dispatch = useAppDispatch(); + const open = useAppSelector((state) => state.globalState.drawerOpen); + const drawerWidth = useAppSelector((state) => state.globalState.drawerWidth); + return ( + + + + + + ); +}; + +const AppDrawer = () => { + const theme = useTheme(); + const open = useAppSelector((state) => state.globalState.drawerOpen); + const drawerWidth = useAppSelector((state) => state.globalState.drawerWidth); + const appBarBg = theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900]; + + return ( + + + + ); +}; + +export default AppDrawer; diff --git a/src/component/Frame/NavBar/AppMain.tsx b/src/component/Frame/NavBar/AppMain.tsx new file mode 100755 index 0000000..9854307 --- /dev/null +++ b/src/component/Frame/NavBar/AppMain.tsx @@ -0,0 +1,97 @@ +import { Box, styled, useMediaQuery, useTheme } from "@mui/material"; +import { useContext, useEffect, useMemo, useState } from "react"; +import { Navigate, Outlet, useNavigation } from "react-router-dom"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { GroupPermission } from "../../../api/user.ts"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import SessionManager from "../../../session"; +import { GroupBS } from "../../../session/utils.ts"; +import FacebookCircularProgress from "../../Common/CircularProgress.tsx"; +import { PageVariant, PageVariantContext } from "../NavBarFrame.tsx"; +import { DrawerHeaderContainer } from "./DrawerHeader.tsx"; + +const StyledLoadingContainer = styled(Box)(() => ({ + display: "flex", + alignItems: "center", + justifyContent: "center", + width: "100%", + height: "100%", +})); + +export const PageLoading = () => { + return ( + + + + ); +}; + +const AppMain = () => { + const open = useAppSelector((state) => state.globalState.drawerOpen); + const drawerWidth = useAppSelector((state) => state.globalState.drawerWidth); + const [innerHeight, setInnerHeight] = useState(window.innerHeight); + let navigation = useNavigation(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const pageVariant = useContext(PageVariantContext); + const isDashboard = pageVariant == PageVariant.dashboard; + const user = SessionManager.currentLoginOrNull(); + const isAdmin = useMemo(() => { + return GroupBS(user?.user).enabled(GroupPermission.is_admin); + }, [user?.user?.group?.permission]); + + useEffect(() => { + const handleResize = () => { + setInnerHeight(window.innerHeight); + }; + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + + return ( + ({ + flexGrow: 1, + transition: theme.transitions.create("margin", { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + marginRight: isMobile ? 0 : 2, + marginLeft: isMobile ? 0 : `-${drawerWidth - 16}px`, + ...(open && { + transition: theme.transitions.create("margin", { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + marginLeft: 0, + }), + height: isMobile ? "100%" : window.innerHeight, + minHeight: window.innerHeight, + display: "flex", + flexDirection: "column", + width: "100%", + overflow: "hidden", + })} + component={"main"} + > + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={navigation.state !== "idle" ? "loading" : "idle"} + > + {navigation.state !== "idle" ? ( + + ) : isDashboard && !isAdmin ? ( + + ) : ( + + )} + + + + ); +}; + +export default AppMain; diff --git a/src/component/Frame/NavBar/DarkThemeSwitcher.tsx b/src/component/Frame/NavBar/DarkThemeSwitcher.tsx new file mode 100755 index 0000000..9bde80e --- /dev/null +++ b/src/component/Frame/NavBar/DarkThemeSwitcher.tsx @@ -0,0 +1,102 @@ +import { IconButton, Popover, ToggleButton, ToggleButtonGroup, Tooltip } from "@mui/material"; +import DarkTheme from "../../Icons/DarkTheme.tsx"; +import { useTranslation } from "react-i18next"; +import { useMemo, useState } from "react"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import Sunny from "../../Icons/Sunny.tsx"; +import Moon from "../../Icons/Moon.tsx"; +import SunWithTime from "../../Icons/SunWithTime.tsx"; +import { setDarkMode } from "../../../redux/globalStateSlice.ts"; +import SessionManager, { UserSettings } from "../../../session"; + +interface SwitchPopoverProps { + open?: boolean; + anchorEl?: HTMLElement | null; + onClose?: () => void; +} + +export const SwitchPopover = ({ open, anchorEl, onClose }: SwitchPopoverProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const darkMode = useAppSelector((state) => state.globalState.darkMode); + const currentMode = useMemo(() => { + if (darkMode === undefined) { + return "system"; + } + return darkMode ? "dark" : "light"; + }, [darkMode]); + const handleChange = (_event: React.MouseEvent, newMode: string) => { + let newSetting: boolean | undefined; + if (newMode === "light") { + newSetting = false; + } else if (newMode === "dark") { + newSetting = true; + } + dispatch(setDarkMode(newSetting)); + SessionManager.set(UserSettings.PreferredDarkMode, newSetting); + onClose && onClose(); + }; + + const inner = ( + + + + {t("navbar.toLightMode")} + + + + {t("setting.syncWithSystem")} + + + + {t("navbar.toDarkMode")} + + + ); + + return onClose ? ( + + {inner} + + ) : ( + inner + ); +}; + +const DarkThemeSwitcher = () => { + const { t } = useTranslation(); + const [anchorEl, setAnchorEl] = useState(null); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + return ( + <> + + + + + + setAnchorEl(null)} /> + + ); +}; + +export default DarkThemeSwitcher; diff --git a/src/component/Frame/NavBar/DrawerHeader.tsx b/src/component/Frame/NavBar/DrawerHeader.tsx new file mode 100755 index 0000000..ef7c3ed --- /dev/null +++ b/src/component/Frame/NavBar/DrawerHeader.tsx @@ -0,0 +1,54 @@ +import { ChevronLeft } from "@mui/icons-material"; +import { Box, Fade, IconButton, styled, useMediaQuery, useTheme } from "@mui/material"; +import { useState } from "react"; +import { setDrawerOpen } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import Logo from "../../Common/Logo.tsx"; + +export const DrawerHeaderContainer = styled("div")(({ theme }) => ({ + display: "flex", + alignItems: "center", + padding: theme.spacing(0, 1), + // necessary for content to be below app bar + ...theme.mixins.toolbar, + justifyContent: "flex-end", +})); + +const DrawerHeader = ({ disabled }: { disabled?: boolean }) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const dispatch = useAppDispatch(); + + const [showCollapse, setShowCollapse] = useState(false); + + return ( + setShowCollapse(disabled ? false : true)} + onMouseLeave={() => setShowCollapse(false)} + > + + + + {!isMobile && ( + + + dispatch(setDrawerOpen(false))}> + + + + + )} + + ); +}; + +export default DrawerHeader; diff --git a/src/component/Frame/NavBar/FileSelectedActions.tsx b/src/component/Frame/NavBar/FileSelectedActions.tsx new file mode 100755 index 0000000..7be705b --- /dev/null +++ b/src/component/Frame/NavBar/FileSelectedActions.tsx @@ -0,0 +1,165 @@ +import { Badge, Box, IconButton, Stack, styled, Tooltip, useMediaQuery, useTheme } from "@mui/material"; +import React, { forwardRef } from "react"; +import { useTranslation } from "react-i18next"; +import { FileResponse } from "../../../api/explorer.ts"; +import { clearSelected, ContextMenuTypes } from "../../../redux/fileManagerSlice.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { downloadFiles } from "../../../redux/thunks/download.ts"; +import { + deleteFile, + dialogBasedMoveCopy, + openFileContextMenu, + openShareDialog, + renameFile, +} from "../../../redux/thunks/file.ts"; +import { openViewers } from "../../../redux/thunks/viewer.ts"; +import useActionDisplayOpt from "../../FileManager/ContextMenu/useActionDisplayOpt.ts"; +import { FileManagerIndex } from "../../FileManager/FileManager.tsx"; +import { ActionButton, ActionButtonGroup } from "../../FileManager/TopBar/TopActions.tsx"; +import CopyOutlined from "../../Icons/CopyOutlined.tsx"; +import DeleteOutlined from "../../Icons/DeleteOutlined.tsx"; +import Dismiss from "../../Icons/Dismiss.tsx"; +import Download from "../../Icons/Download.tsx"; +import FolderArrowRightOutlined from "../../Icons/FolderArrowRightOutlined.tsx"; +import MoreHorizontal from "../../Icons/MoreHorizontal.tsx"; +import Open from "../../Icons/Open.tsx"; +import RenameOutlined from "../../Icons/RenameOutlined.tsx"; +import ShareOutlined from "../../Icons/ShareOutlined.tsx"; + +export interface FileSelectedActionsProps { + targets: FileResponse[]; +} + +const StyledActionButton = styled(ActionButton)(({ theme }) => ({ + // disabled + "&.MuiButtonBase-root.Mui-disabled": { + color: theme.palette.text.primary, + fontWeight: theme.typography.fontWeightRegular, + fontSize: theme.typography.body2.fontSize, + }, +})); + +const StyledActionButtonGroup = styled(ActionButtonGroup)(({ theme }) => ({ + backgroundColor: theme.palette.background.default, +})); + +const FileSelectedActions = forwardRef(({ targets }: FileSelectedActionsProps, ref: React.Ref) => { + const dispatch = useAppDispatch(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const isTablet = useMediaQuery(theme.breakpoints.down("md")); + const { t } = useTranslation(); + const displayOpt = useActionDisplayOpt(targets, ContextMenuTypes.file); + + if (isMobile) { + return ( + + + dispatch( + clearSelected({ + index: FileManagerIndex.main, + value: undefined, + }), + ) + } + > + + + + + dispatch(openFileContextMenu(FileManagerIndex.main, targets[0], false, e))}> + + + + + + ); + } + + return ( + + + + + dispatch( + clearSelected({ + index: FileManagerIndex.main, + value: undefined, + }), + ) + } + > + + + theme.palette.text.primary }}> + {t("application:navbar.objectsSelected", { + num: targets.length, + })} + + + {!isTablet && ( + + {displayOpt.showOpen && ( + + dispatch(openViewers(0, targets[0]))}> + + + + )} + {displayOpt.showDownload && ( + + dispatch(downloadFiles(0, targets))}> + + + + )} + {displayOpt.showCopy && ( + + dispatch(dialogBasedMoveCopy(0, targets, true))}> + + + + )} + {displayOpt.showMove && ( + + dispatch(dialogBasedMoveCopy(0, targets, false))}> + + + + )} + {displayOpt.showRename && ( + + dispatch(renameFile(0, targets[0]))}> + + + + )} + {displayOpt.showShare && ( + + dispatch(openShareDialog(0, targets[0]))}> + + + + )} + {displayOpt.showDelete && ( + + dispatch(deleteFile(0, targets))}> + + + + )} + + )} + + dispatch(openFileContextMenu(FileManagerIndex.main, targets[0], false, e))}> + + + + + + ); +}); + +export default FileSelectedActions; diff --git a/src/component/Frame/NavBar/NavBarMainActions.tsx b/src/component/Frame/NavBar/NavBarMainActions.tsx new file mode 100755 index 0000000..14a1d63 --- /dev/null +++ b/src/component/Frame/NavBar/NavBarMainActions.tsx @@ -0,0 +1,32 @@ +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import { useMemo } from "react"; +import { Box } from "@mui/material"; +import SearchBar from "./SearchBar.tsx"; +import FileSelectedActions from "./FileSelectedActions.tsx"; +import { FileManagerIndex } from "../../FileManager/FileManager.tsx"; + +const NavBarMainActions = () => { + const selected = useAppSelector((state) => state.fileManager[FileManagerIndex.main].selected); + const targets = useMemo(() => { + return Object.keys(selected).map((key) => selected[key]); + }, [selected]); + return ( + <> + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${targets.length > 0}`} + > + + {targets.length == 0 && } + {targets.length > 0 && } + + + + + ); +}; + +export default NavBarMainActions; diff --git a/src/component/Frame/NavBar/NavIconTransition.tsx b/src/component/Frame/NavBar/NavIconTransition.tsx new file mode 100755 index 0000000..8404f72 --- /dev/null +++ b/src/component/Frame/NavBar/NavIconTransition.tsx @@ -0,0 +1,38 @@ +import { Box, Fade, SvgIconProps } from "@mui/material"; +import { TransitionGroup } from "react-transition-group"; +import "../../Common/FadeTransition.css"; +import SvgIcon from "@mui/material/SvgIcon/SvgIcon"; + +export interface NavIconTransitionProps { + fileIcon: ((props: SvgIconProps) => JSX.Element)[] | (typeof SvgIcon)[]; + active?: boolean; + [key: string]: any; + iconProps?: SvgIconProps; +} + +const NavIconTransition = ({ fileIcon, active, iconProps, ...rest }: NavIconTransitionProps) => { + const [Active, InActive] = fileIcon; + return ( + + + {active && ( + + + + + + )} + {!active && ( + + + + + + )} + + + + ); +}; + +export default NavIconTransition; diff --git a/src/component/Frame/NavBar/PageNavigation.tsx b/src/component/Frame/NavBar/PageNavigation.tsx new file mode 100755 index 0000000..ac7e502 --- /dev/null +++ b/src/component/Frame/NavBar/PageNavigation.tsx @@ -0,0 +1,305 @@ +import { Icon as Iconify } from "@iconify/react"; +import { Box, SvgIconProps, useTheme } from "@mui/material"; +import SvgIcon from "@mui/material/SvgIcon/SvgIcon"; +import { memo, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useLocation, useNavigate } from "react-router-dom"; +import { GroupPermission } from "../../../api/user.ts"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import SessionManager from "../../../session"; +import { GroupBS } from "../../../session/utils.ts"; +import ProDialog from "../../Admin/Common/ProDialog.tsx"; +import BoxMultiple from "../../Icons/BoxMultiple.tsx"; +import BoxMultipleFilled from "../../Icons/BoxMultipleFilled.tsx"; +import CloudDownload from "../../Icons/CloudDownload.tsx"; +import CloudDownloadOutlined from "../../Icons/CloudDownloadOutlined.tsx"; +import CubeSync from "../../Icons/CubeSync.tsx"; +import CubeSyncFilled from "../../Icons/CubeSyncFilled.tsx"; +import CubeTree from "../../Icons/CubeTree.tsx"; +import CubeTreeFilled from "../../Icons/CubeTreeFilled.tsx"; +import DataHistogram from "../../Icons/DataHistogram.tsx"; +import DataHistogramFilled from "../../Icons/DataHistogramFilled.tsx"; +import Folder from "../../Icons/Folder.tsx"; +import FolderOutlined from "../../Icons/FolderOutlined.tsx"; +import HomeOutlined from "../../Icons/HomeOutlined.tsx"; +import Payment from "../../Icons/Payment.tsx"; +import PaymentFilled from "../../Icons/PaymentFilled.tsx"; +import People from "../../Icons/People.tsx"; +import PeopleFilled from "../../Icons/PeopleFilled.tsx"; +import Person from "../../Icons/Person.tsx"; +import PersonOutlined from "../../Icons/PersonOutlined.tsx"; +import PhoneLaptop from "../../Icons/PhoneLaptop.tsx"; +import PhoneLaptopOutlined from "../../Icons/PhoneLaptopOutlined.tsx"; +import SendLogging from "../../Icons/SendLogging.tsx"; +import SendLoggingFilled from "../../Icons/SendLoggingFilled.tsx"; +import Server from "../../Icons/Server.tsx"; +import ServerFilled from "../../Icons/ServerFilled.tsx"; +import Setting from "../../Icons/Setting.tsx"; +import SettingsOutlined from "../../Icons/SettingsOutlined.tsx"; +import ShareAndroid from "../../Icons/ShareAndroid.tsx"; +import ShareOutlined from "../../Icons/ShareOutlined.tsx"; +import Storage from "../../Icons/Storage.tsx"; +import StorageOutlined from "../../Icons/StorageOutlined.tsx"; +import Warning from "../../Icons/Warning.tsx"; +import WarningOutlined from "../../Icons/WarningOutlined.tsx"; +import WrenchSettings from "../../Icons/WrenchSettings.tsx"; +import { ProChip } from "../../Pages/Setting/SettingForm.tsx"; +import NavIconTransition from "./NavIconTransition.tsx"; +import SideNavItem from "./SideNavItem.tsx"; + +export interface NavigationItem { + label: string; + icon?: ((props: SvgIconProps) => JSX.Element)[] | (typeof SvgIcon)[]; + iconifyName?: string; + path: string; + pro?: boolean; +} + +let NavigationItems: NavigationItem[]; +NavigationItems = [ + { + label: "navbar.myShare", + icon: [ShareAndroid, ShareOutlined], + path: "/shares", + }, +]; + +const ConnectNavigationItem: NavigationItem = { + label: "navbar.connect", + icon: [PhoneLaptop, PhoneLaptopOutlined], + path: "/connect", +}; + +const TaskNavigationItem: NavigationItem = { + label: "navbar.taskQueue", + icon: [CubeSyncFilled, CubeSync], + path: "/tasks", +}; + +const RemoteDownloadNavigationItem: NavigationItem = { + label: "navbar.remoteDownload", + icon: [CloudDownload, CloudDownloadOutlined], + path: "/downloads", +}; + +export const SideNavItemComponent = ({ item }: { item: NavigationItem }) => { + const { t } = useTranslation("application"); + const navigate = useNavigate(); + const location = useLocation(); + const theme = useTheme(); + const [proOpen, setProOpen] = useState(false); + const active = useMemo(() => { + return location.pathname == item.path || location.pathname.startsWith(item.path + "/"); + }, [location.pathname, item.path]); + return ( + <> + {item.pro && setProOpen(false)} />} + + item.pro ? setProOpen(true) : item.iconifyName ? window.open(item.path, "_blank") : navigate(item.path) + } + label={ + item.pro ? ( + + {t(item.label)} + t.typography.caption.fontSize, + }} + label="Pro" + color="primary" + size="small" + /> + + ) : ( + t(item.label) + ) + } + active={active} + icon={ + !item.icon ? ( + + + + ) : ( + + ) + } + /> + + ); +}; + +let AdminNavigationItems: NavigationItem[]; +AdminNavigationItems = [ + { + label: "dashboard:nav.summary", + icon: [DataHistogramFilled, DataHistogram], + path: "/admin/home", + }, + { + label: "dashboard:nav.settings", + icon: [Setting, SettingsOutlined], + path: "/admin/settings", + }, + { + label: "dashboard:nav.fileSystem", + icon: [CubeTreeFilled, CubeTree], + path: "/admin/filesystem", + }, + { + label: "dashboard:nav.storagePolicy", + icon: [Storage, StorageOutlined], + path: "/admin/policy", + }, + { + label: "dashboard:nav.nodes", + icon: [ServerFilled, Server], + path: "/admin/node", + }, + { + label: "dashboard:nav.groups", + icon: [PeopleFilled, People], + path: "/admin/group", + }, + { + label: "dashboard:nav.users", + icon: [Person, PersonOutlined], + path: "/admin/user", + }, + { + label: "dashboard:nav.files", + icon: [Folder, FolderOutlined], + path: "/admin/file", + }, + { + label: "dashboard:nav.entities", + icon: [BoxMultipleFilled, BoxMultiple], + path: "/admin/blob", + }, + { + label: "dashboard:nav.shares", + icon: [ShareAndroid, ShareOutlined], + path: "/admin/share", + }, + { + label: "dashboard:nav.tasks", + icon: [CubeSyncFilled, CubeSync], + path: "/admin/task", + }, + { + label: "dashboard:vas.orders", + icon: [PaymentFilled, Payment], + path: "/admin/payment", + pro: true, + }, + { + label: "dashboard:nav.events", + icon: [SendLoggingFilled, SendLogging], + path: "/admin/event", + pro: true, + }, + { + label: "dashboard:nav.abuseReport", + icon: [Warning, WarningOutlined], + path: "/admin/abuse", + pro: true, + }, +]; + +export const AdminPageNavigation = memo(() => { + return ( + <> + + + {AdminNavigationItems.slice(1).map((item) => ( + + ))} + + + + ); +}); + +const PageNavigation = () => { + const shopNavEnabled = useAppSelector((state) => state.siteConfig.basic.config.shop_nav_enabled); + const appPromotionEnabled = useAppSelector((state) => state.siteConfig.basic.config.app_promotion); + const user = SessionManager.currentLoginOrNull(); + const isAdmin = useMemo(() => { + return GroupBS(user?.user).enabled(GroupPermission.is_admin); + }, [user?.user?.group?.permission]); + const remoteDownloadEnabled = useMemo(() => { + return GroupBS(user?.user).enabled(GroupPermission.remote_download); + }, [user?.user?.group?.permission]); + const connectEnabled = useMemo(() => { + return GroupBS(user?.user).enabled(GroupPermission.webdav) || appPromotionEnabled; + }, [user?.user?.group?.permission, appPromotionEnabled]); + const isLogin = !!user; + const customNavItems = useAppSelector((state) => state.siteConfig.basic.config.custom_nav_items); + + return ( + <> + {isLogin && ( + + <> + {NavigationItems.map((item) => ( + + ))} + {connectEnabled && } + + {remoteDownloadEnabled && } + + + )} + {customNavItems && customNavItems.length > 0 && ( + + {customNavItems.map((item) => ( + + ))} + + )} + {isLogin && isAdmin && ( + + )} + + ); +}; + +export default PageNavigation; diff --git a/src/component/Frame/NavBar/SearchBar.tsx b/src/component/Frame/NavBar/SearchBar.tsx new file mode 100755 index 0000000..9fa0e56 --- /dev/null +++ b/src/component/Frame/NavBar/SearchBar.tsx @@ -0,0 +1,79 @@ +import { alpha, Button, IconButton, styled, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { useHotkeys } from "react-hotkeys-hook"; +import { Trans, useTranslation } from "react-i18next"; +import { setSearchPopup } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import Search from "../../Icons/Search.tsx"; + +export const KeyIndicator = styled("code")(({ theme }) => ({ + backgroundColor: theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900], + border: `1px solid ${theme.palette.divider}`, + boxShadow: + theme.palette.mode === "light" + ? "0 1px 1px rgba(0, 0, 0, 0.2), 0 2px 0 0 rgba(255, 255, 255, 0.7) inset" + : "0 1px 1px rgba(0, 0, 0, 0.2), 0 2px 0 0 #3d3e42 inset", + padding: theme.spacing(0, 0.5), + borderRadius: 4, +})); + +const SearchButton = styled(Button)(({ theme }) => ({ + backgroundColor: theme.palette.background.default, + color: theme.palette.text.disabled, + border: `1px solid ${theme.palette.divider}`, + pl: 2, + pr: 8, + " :hover": { + border: `1px solid ${theme.palette.primary.main}`, + backgroundColor: alpha(theme.palette.primary.main, 0.04), + }, +})); + +const SearchBar = () => { + const dispatch = useAppDispatch(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const { t } = useTranslation(); + useHotkeys( + "/", + () => { + dispatch(setSearchPopup(true)); + }, + { preventDefault: true }, + ); + + if (isMobile) { + return ( + dispatch(setSearchPopup(true))}> + + + ); + } + + return ( + ({ + backgroundColor: theme.palette.background.default, + color: theme.palette.text.disabled, + border: `1px solid ${theme.palette.divider}`, + pl: 2, + pr: 8, + height: "100%", + })} + onClick={() => dispatch(setSearchPopup(true))} + variant={"outlined"} + startIcon={} + > + + + , + ]} + /> + + ); +}; + +export default SearchBar; diff --git a/src/component/Frame/NavBar/SideNavItem.tsx b/src/component/Frame/NavBar/SideNavItem.tsx new file mode 100755 index 0000000..1c7ee2b --- /dev/null +++ b/src/component/Frame/NavBar/SideNavItem.tsx @@ -0,0 +1,77 @@ +import { Box, ButtonBase, darken, lighten, styled } from "@mui/material"; +import * as React from "react"; +import { NoWrapTypography } from "../../Common/StyledComponents.tsx"; + +const StyledButtonBase = styled(ButtonBase)<{ + active?: boolean; +}>(({ theme, active }) => ({ + borderRadius: "90px", + display: "flex", + justifyContent: "left", + alignItems: "initial", + width: "100%", + backgroundColor: active + ? `${ + theme.palette.mode == "light" + ? lighten(theme.palette.primary.main, 0.7) + : darken(theme.palette.primary.main, 0.7) + }!important` + : "initial", + transition: + "background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms", +})); + +export interface SideNavItemBaseProps { + active?: boolean; + [key: string]: any; +} +export const SideNavItemBase = React.forwardRef( + ({ active, ...rest }: SideNavItemBaseProps, ref: React.Ref) => { + return ; + }, +); + +const StyledSideNavItem = styled(SideNavItemBase)<{ level?: number }>(({ theme, level }) => ({ + "&:hover": { + backgroundColor: theme.palette.action.hover, + }, + padding: "4px", + paddingLeft: `${28 + (level ?? 0) * 16}px`, + height: "32px", + display: "flex", + alignItems: "center", +})); + +export interface SideNavItemProps extends SideNavItemBaseProps { + icon?: React.ReactNode; + label?: string | React.ReactNode; + level?: number; + [key: string]: any; +} + +const SideNavItem = React.forwardRef( + ({ icon, label, level, sx, ...rest }: SideNavItemProps, ref: React.Ref) => { + return ( + + + {icon} + + {label} + + ); + }, +); + +export default SideNavItem; diff --git a/src/component/Frame/NavBar/SplitHandle.tsx b/src/component/Frame/NavBar/SplitHandle.tsx new file mode 100755 index 0000000..8be17bc --- /dev/null +++ b/src/component/Frame/NavBar/SplitHandle.tsx @@ -0,0 +1,78 @@ +import { Box, Fade } from "@mui/material"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { useEffect, useRef, useState } from "react"; +import { setDrawerWidth } from "../../../redux/globalStateSlice.ts"; +import SessionManager, { UserSettings } from "../../../session"; + +export interface SplitHandleProps {} + +const minDrawerWidth = 236; + +const SplitHandle = (_props: SplitHandleProps) => { + const dispatch = useAppDispatch(); + const [moving, setMoving] = useState(false); + const [cursor, setCursor] = useState(0); + const finalWidth = useRef(0); + + const drawerWidth = useAppSelector((state) => state.globalState.drawerWidth); + const drawerOpen = useAppSelector((state) => state.globalState.drawerOpen); + + useEffect(() => { + setCursor(drawerWidth - 4); + finalWidth.current = drawerWidth - 4; + }, []); + + const handler = () => { + setMoving(true); + document.body.style.userSelect = "none"; + function onMouseMove(e: MouseEvent) { + e.preventDefault(); + const newWidth = e.clientX - document.body.offsetLeft; + const cappedWidth = Math.max(Math.min(newWidth, window.innerWidth / 2), minDrawerWidth); + setCursor(cappedWidth); + finalWidth.current = cappedWidth; + } + function onMouseUp() { + document.body.removeEventListener("mousemove", onMouseMove); + setMoving(false); + dispatch(setDrawerWidth(finalWidth.current + 4)); + SessionManager.set(UserSettings.DrawerWidth, finalWidth.current + 4); + document.body.style.userSelect = "initial"; + } + + document.body.addEventListener("mousemove", onMouseMove); + document.body.addEventListener("mouseup", onMouseUp, { once: true }); + }; + + return ( + <> + {drawerOpen && ( + theme.zIndex.drawer + 2, + }} + /> + )} + + theme.zIndex.drawer + 1, + }} + /> + + + ); +}; + +export default SplitHandle; diff --git a/src/component/Frame/NavBar/StorageSummary.tsx b/src/component/Frame/NavBar/StorageSummary.tsx new file mode 100755 index 0000000..e9c601d --- /dev/null +++ b/src/component/Frame/NavBar/StorageSummary.tsx @@ -0,0 +1,67 @@ +import { LinearProgress, linearProgressClasses, Skeleton, styled, Typography } from "@mui/material"; +import { memo, useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { updateUserCapacity } from "../../../redux/thunks/filemanager.ts"; +import { sizeToString } from "../../../util"; +import { RadiusFrame } from "../RadiusFrame.tsx"; + +const StyledBox = styled(RadiusFrame)(({ theme }) => ({ + padding: theme.spacing(1, 2, 1, 2), + margin: theme.spacing(0, 2, 0, 2), +})); + +const StorageHeaderContainer = styled("div")(() => ({ + display: "flex", + justifyContent: "space-between", + alignItems: "center", +})); + +const BorderLinearProgress = styled(LinearProgress)<{ warning: boolean }>(({ theme, warning }) => ({ + height: 8, + borderRadius: 5, + [`&.${linearProgressClasses.colorPrimary}`]: { + backgroundColor: theme.palette.grey[theme.palette.mode === "light" ? 200 : 800], + }, + [`& .${linearProgressClasses.bar}`]: { + borderRadius: 5, + backgroundColor: warning ? theme.palette.warning.main : theme.palette.primary.main, + }, + marginTop: theme.spacing(1), +})); + +const StorageSummary = memo(() => { + const { t } = useTranslation("application"); + const dispatch = useAppDispatch(); + const capacity = useAppSelector((state) => state.fileManager[0].capacity); + useEffect(() => { + if (!capacity) { + dispatch(updateUserCapacity(0)); + return; + } + }, [capacity]); + return ( + + + {t("application:navbar.storage")} + + {capacity && ( + capacity.total} + variant="determinate" + value={Math.min(100, (capacity.used / capacity.total) * 100)} + /> + )} + {!capacity && } + + {capacity ? ( + `${sizeToString(capacity.used)} / ${sizeToString(capacity.total)}` + ) : ( + + )} + + + ); +}); + +export default StorageSummary; diff --git a/src/component/Frame/NavBar/TopAppBar.tsx b/src/component/Frame/NavBar/TopAppBar.tsx new file mode 100755 index 0000000..6674d05 --- /dev/null +++ b/src/component/Frame/NavBar/TopAppBar.tsx @@ -0,0 +1,146 @@ +import { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar"; +import { AppBar, Box, Collapse, IconButton, Stack, Toolbar, Tooltip, useMediaQuery, useTheme } from "@mui/material"; +import { Menu } from "@mui/icons-material"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { setDrawerOpen, setMobileDrawerOpen } from "../../../redux/globalStateSlice.ts"; +import NewButton from "../../FileManager/NewButton.tsx"; +import UserAction from "./UserAction.tsx"; +import Setting from "../../Icons/Setting.tsx"; +import DarkThemeSwitcher from "./DarkThemeSwitcher.tsx"; +import NavBarMainActions from "./NavBarMainActions.tsx"; +import MusicPlayer from "../../Viewers/MusicPlayer/MusicPlayer.tsx"; +import { TaskListIconButton } from "../../Uploader/TaskListIconButton.tsx"; +import { useNavigate } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import SessionManager from "../../../session"; +import { useContext, useState } from "react"; +import { DrawerPopover } from "./AppDrawer.tsx"; +import { PageVariant, PageVariantContext } from "../NavBarFrame.tsx"; + +interface AppBarProps extends MuiAppBarProps { + open?: boolean; +} + +const TopAppBar = () => { + const dispatch = useAppDispatch(); + const theme = useTheme(); + const pageVariant = useContext(PageVariantContext); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const isMainPage = pageVariant == PageVariant.default; + const { t } = useTranslation(); + const navigate = useNavigate(); + const open = useAppSelector((state) => state.globalState.drawerOpen); + const mobileDrawerOpen = useAppSelector((state) => state.globalState.mobileDrawerOpen); + const drawerWidth = useAppSelector((state) => state.globalState.drawerWidth); + const musicPlayer = useAppSelector((state) => state.globalState.musicPlayer); + const [mobileMenuAnchor, setMobileMenuAnchor] = useState(null); + + const appBarBg = theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900]; + const isLogin = !!SessionManager.currentLoginOrNull(); + + const onMobileMenuClicked = (e: React.MouseEvent) => { + setMobileMenuAnchor(e.currentTarget); + dispatch(setMobileDrawerOpen(true)); + }; + + const onCloseMobileMenu = () => { + dispatch(setMobileDrawerOpen(false)); + }; + + // @ts-ignore + return ( + ({ + transition: theme.transitions.create(["margin", "width"], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + backgroundColor: appBarBg, + color: theme.palette.getContrastText(appBarBg), + ...(open && + !isMobile && { + width: `calc(100% - ${drawerWidth}px)`, + marginLeft: `${drawerWidth}px`, + transition: theme.transitions.create(["margin", "width"], { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + }), + })} + position="fixed" + > + + + dispatch(setDrawerOpen(true))} + edge="start" + sx={{ + mr: isMobile ? 1 : 2, + ml: isMobile ? -1 : -1.5, + }} + > + + + + {isMobile && ( + <> + + + )} + {!isMobile && isMainPage && ( + + + + + )} + + + {!isMobile && } + {musicPlayer && } + {!isMobile ? ( + <> + + {isLogin && ( + + navigate("/settings")}> + + + + )} + + + ) : ( + <> + {isMainPage && } + {isMainPage && } + + + )} + + + + ); +}; + +export default TopAppBar; diff --git a/src/component/Frame/NavBar/UserAction.tsx b/src/component/Frame/NavBar/UserAction.tsx new file mode 100755 index 0000000..ab79422 --- /dev/null +++ b/src/component/Frame/NavBar/UserAction.tsx @@ -0,0 +1,173 @@ +import { + Box, + Divider, + IconButton, + ListItemIcon, + ListItemText, + MenuList, + Popover, + PopoverProps, + styled, + Typography, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { bindPopover } from "material-ui-popup-state"; +import { bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { GroupPermission } from "../../../api/user.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { signout } from "../../../redux/thunks/session.ts"; +import SessionManager, { Session } from "../../../session"; +import { GroupBS } from "../../../session/utils.ts"; +import UserAvatar from "../../Common/User/UserAvatar.tsx"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx"; +import HomeOutlined from "../../Icons/HomeOutlined.tsx"; +import Person from "../../Icons/Person.tsx"; +import SettingsOutlined from "../../Icons/SettingsOutlined.tsx"; +import SignOut from "../../Icons/SignOut.tsx"; +import WrenchSettings from "../../Icons/WrenchSettings.tsx"; + +const StyledTypography = styled(Typography)(() => ({ + lineHeight: 1, +})); + +const UserPopover = ({ open, onClose, ...rest }: PopoverProps) => { + const user = SessionManager.currentUser(); + const { t } = useTranslation(); + const navigate = useNavigate(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + + if (!user) { + return null; + } + + const isAdmin = useMemo(() => { + return GroupBS(user).enabled(GroupPermission.is_admin); + }, [user.group?.permission]); + + const signWithHint = (email: string) => { + navigate("/session?phase=email&email=" + encodeURIComponent(email)); + }; + + const signOut = useCallback(() => { + dispatch(signout()); + onClose && onClose({}, "backdropClick"); + }, []); + + const openMyProfile = useCallback(() => { + navigate(`/profile/${user?.id}`); + onClose && onClose({}, "backdropClick"); + }, [user?.id]); + + const openSetting = useCallback(() => { + navigate(`/settings`); + onClose && onClose({}, "backdropClick"); + }, [user?.id]); + + const openDashboard = useCallback(() => { + navigate(`/admin/home`); + onClose && onClose({}, "backdropClick"); + }, [user?.id]); + + return ( + + + + + {user.nickname} + + + {user.group?.name} + + + + {user.email} + + + + + {isAdmin && ( + + + + + {t("navbar.dashboard")} + + )} + {isMobile && ( + + + + + {t("navbar.setting")} + + )} + + + + + {t("navbar.myProfile")} + + + + + + {t("login.logout")} + + + + ); +}; + +const UserAction = () => { + const navigate = useNavigate(); + const [current, setCurrent] = useState(); + const popupState = usePopupState({ variant: "popover", popupId: "user" }); + useEffect(() => { + try { + const session = SessionManager.currentLogin(); + if (session) { + setCurrent(session); + } + } catch (e) {} + }, []); + return ( + <> + + {!current && navigate("/session")} />} + {current && } + + + + ); +}; + +export default UserAction; diff --git a/src/component/Frame/NavBarFrame.tsx b/src/component/Frame/NavBarFrame.tsx new file mode 100755 index 0000000..4ba1d2e --- /dev/null +++ b/src/component/Frame/NavBarFrame.tsx @@ -0,0 +1,71 @@ +import { Box, useMediaQuery, useTheme } from "@mui/material"; +import { createContext, useEffect } from "react"; +import { DndProvider } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; +import { useLocation } from "react-router-dom"; +import { setMobileDrawerOpen } from "../../redux/globalStateSlice.ts"; +import { useAppDispatch } from "../../redux/hooks.ts"; +import ContextMenu from "../FileManager/ContextMenu/ContextMenu.tsx"; +import Dialogs from "../FileManager/Dialogs/Dialogs.tsx"; +import DragLayer from "../FileManager/Dnd/DragLayer.tsx"; +import { FileManagerIndex } from "../FileManager/FileManager.tsx"; +import SearchPopup from "../FileManager/Search/SearchPopup.tsx"; +import Uploader from "../Uploader/Uploader.tsx"; +import AppDrawer from "./NavBar/AppDrawer.tsx"; +import Main from "./NavBar/AppMain.tsx"; +import SplitHandle from "./NavBar/SplitHandle.tsx"; +import TopAppBar from "./NavBar/TopAppBar.tsx"; + +export enum PageVariant { + default, + dashboard, +} + +export interface NavBarFrameProps { + variant?: PageVariant; +} + +export const PageVariantContext = createContext(PageVariant.default); + +export const AutoNavbarFrame = () => { + const path = useLocation().pathname; + return ; +}; + +const NavBarFrame = ({ variant }: NavBarFrameProps) => { + const theme = useTheme(); + const dispatch = useAppDispatch(); + const isTouch = useMediaQuery("(pointer: coarse)"); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const location = useLocation(); + + useEffect(() => { + if (isMobile) { + dispatch(setMobileDrawerOpen(false)); + } + }, [location]); + return ( + + (theme.palette.mode === "light" ? theme.palette.grey[100] : theme.palette.grey[900]), + display: "flex", + }} + > + + {!isMobile && variant != PageVariant.dashboard && !isTouch && } + {!isMobile && !isTouch && } + + {!isMobile && } + {variant != PageVariant.dashboard && } + + + {variant != PageVariant.dashboard && } +
    + + + + ); +}; + +export default NavBarFrame; diff --git a/src/component/Frame/PoweredBy.tsx b/src/component/Frame/PoweredBy.tsx new file mode 100755 index 0000000..9df4c50 --- /dev/null +++ b/src/component/Frame/PoweredBy.tsx @@ -0,0 +1,56 @@ +import { Box, BoxProps, Typography, useTheme } from "@mui/material"; +import LogoIcon from "./assets/logo.svg"; +import LogoIconDark from "./assets/logo_light.svg"; + +export interface PoweredByProps extends BoxProps {} + +const PoweredBy = ({ ...rest }: PoweredByProps) => { + const theme = useTheme(); + return ( + + + theme.palette.action.disabled, + }} + fontWeight={500} + > + Powered by + + + + + ); +}; + +export default PoweredBy; diff --git a/src/component/Frame/RadiusFrame.tsx b/src/component/Frame/RadiusFrame.tsx new file mode 100755 index 0000000..638cd12 --- /dev/null +++ b/src/component/Frame/RadiusFrame.tsx @@ -0,0 +1,10 @@ +import { Box, styled } from "@mui/material"; + +export const RadiusFrame = styled(Box)<{ + withBorder?: boolean; + square?: boolean; +}>(({ theme, withBorder, square }) => ({ + borderRadius: square ? 0 : theme.shape.borderRadius, + backgroundColor: theme.palette.background.paper, + border: withBorder ? `1px solid ${theme.palette.divider}` : "initial", +})); diff --git a/src/component/Frame/assets/logo.svg b/src/component/Frame/assets/logo.svg new file mode 100755 index 0000000..de7c93d --- /dev/null +++ b/src/component/Frame/assets/logo.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/component/Frame/assets/logo_light.svg b/src/component/Frame/assets/logo_light.svg new file mode 100755 index 0000000..7bdb432 --- /dev/null +++ b/src/component/Frame/assets/logo_light.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/component/Icons/Add.tsx b/src/component/Icons/Add.tsx new file mode 100755 index 0000000..e00a627 --- /dev/null +++ b/src/component/Icons/Add.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Add(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/AppFolder.tsx b/src/component/Icons/AppFolder.tsx new file mode 100755 index 0000000..64407ad --- /dev/null +++ b/src/component/Icons/AppFolder.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function AppFolder(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/AppGeneric.tsx b/src/component/Icons/AppGeneric.tsx new file mode 100755 index 0000000..46b8ad2 --- /dev/null +++ b/src/component/Icons/AppGeneric.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function AppGeneric(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/AppsList.tsx b/src/component/Icons/AppsList.tsx new file mode 100755 index 0000000..491dab9 --- /dev/null +++ b/src/component/Icons/AppsList.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function AppsList(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/AppsListOutlined.tsx b/src/component/Icons/AppsListOutlined.tsx new file mode 100755 index 0000000..f1de6f8 --- /dev/null +++ b/src/component/Icons/AppsListOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function AppsListOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Archive.tsx b/src/component/Icons/Archive.tsx new file mode 100755 index 0000000..441ed4e --- /dev/null +++ b/src/component/Icons/Archive.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Archive(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ArchiveArrow.tsx b/src/component/Icons/ArchiveArrow.tsx new file mode 100755 index 0000000..5f8ab40 --- /dev/null +++ b/src/component/Icons/ArchiveArrow.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ArchiveArrow(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ArrowClockwise.tsx b/src/component/Icons/ArrowClockwise.tsx new file mode 100755 index 0000000..149dba2 --- /dev/null +++ b/src/component/Icons/ArrowClockwise.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ArrowClockwise(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ArrowClockwiseFilled.tsx b/src/component/Icons/ArrowClockwiseFilled.tsx new file mode 100755 index 0000000..7c2f43b --- /dev/null +++ b/src/component/Icons/ArrowClockwiseFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ArrowClockwiseFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ArrowDown.tsx b/src/component/Icons/ArrowDown.tsx new file mode 100755 index 0000000..f813f28 --- /dev/null +++ b/src/component/Icons/ArrowDown.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ArrowDown(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ArrowHookDownLeft.tsx b/src/component/Icons/ArrowHookDownLeft.tsx new file mode 100755 index 0000000..3d64366 --- /dev/null +++ b/src/component/Icons/ArrowHookDownLeft.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ArrowHookDownLeft(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ArrowHookUpRight.tsx b/src/component/Icons/ArrowHookUpRight.tsx new file mode 100755 index 0000000..605d0ee --- /dev/null +++ b/src/component/Icons/ArrowHookUpRight.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ArrowHookUpRight(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ArrowImport.tsx b/src/component/Icons/ArrowImport.tsx new file mode 100755 index 0000000..3dfd0b4 --- /dev/null +++ b/src/component/Icons/ArrowImport.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ArrowImport(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ArrowLeft.tsx b/src/component/Icons/ArrowLeft.tsx new file mode 100755 index 0000000..04226cc --- /dev/null +++ b/src/component/Icons/ArrowLeft.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const ArrowLeft = createSvgIcon( + , + "ArrowLeft", +); + +export default ArrowLeft; diff --git a/src/component/Icons/ArrowRepeatAll.tsx b/src/component/Icons/ArrowRepeatAll.tsx new file mode 100755 index 0000000..1bdae1d --- /dev/null +++ b/src/component/Icons/ArrowRepeatAll.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ArrowRepeatAll(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ArrowRepeatOne.tsx b/src/component/Icons/ArrowRepeatOne.tsx new file mode 100755 index 0000000..6976de9 --- /dev/null +++ b/src/component/Icons/ArrowRepeatOne.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ArrowRepeatOne(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ArrowShuffle.tsx b/src/component/Icons/ArrowShuffle.tsx new file mode 100755 index 0000000..8be0b5f --- /dev/null +++ b/src/component/Icons/ArrowShuffle.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ArrowShuffle(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ArrowSort.tsx b/src/component/Icons/ArrowSort.tsx new file mode 100755 index 0000000..391f12b --- /dev/null +++ b/src/component/Icons/ArrowSort.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ArrowSort(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ArrowSortDownFilled.tsx b/src/component/Icons/ArrowSortDownFilled.tsx new file mode 100755 index 0000000..d6b784a --- /dev/null +++ b/src/component/Icons/ArrowSortDownFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ArrowSortDownFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ArrowSync.tsx b/src/component/Icons/ArrowSync.tsx new file mode 100755 index 0000000..00862a0 --- /dev/null +++ b/src/component/Icons/ArrowSync.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ArrowSync(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ArrowSyncCircleFilled.tsx b/src/component/Icons/ArrowSyncCircleFilled.tsx new file mode 100755 index 0000000..febf8ad --- /dev/null +++ b/src/component/Icons/ArrowSyncCircleFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ArrowSyncCircleFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/BinFullOutlined.tsx b/src/component/Icons/BinFullOutlined.tsx new file mode 100755 index 0000000..3fd3e12 --- /dev/null +++ b/src/component/Icons/BinFullOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function BinFullOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Book.tsx b/src/component/Icons/Book.tsx new file mode 100755 index 0000000..6974878 --- /dev/null +++ b/src/component/Icons/Book.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Book(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/BookInformation.tsx b/src/component/Icons/BookInformation.tsx new file mode 100755 index 0000000..0c7ce44 --- /dev/null +++ b/src/component/Icons/BookInformation.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function BookInformation(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Border.tsx b/src/component/Icons/Border.tsx new file mode 100755 index 0000000..a1a44e8 --- /dev/null +++ b/src/component/Icons/Border.tsx @@ -0,0 +1,30 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Border(props: SvgIconProps) { + return ( + + + + + + + + + + + + + ); +} diff --git a/src/component/Icons/BorderAll.tsx b/src/component/Icons/BorderAll.tsx new file mode 100755 index 0000000..db07d30 --- /dev/null +++ b/src/component/Icons/BorderAll.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function BorderAll(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/BorderInside.tsx b/src/component/Icons/BorderInside.tsx new file mode 100755 index 0000000..1d3d566 --- /dev/null +++ b/src/component/Icons/BorderInside.tsx @@ -0,0 +1,12 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function BorderInside(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Bot.tsx b/src/component/Icons/Bot.tsx new file mode 100755 index 0000000..f9e6046 --- /dev/null +++ b/src/component/Icons/Bot.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Bot(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/BoxMultiple.tsx b/src/component/Icons/BoxMultiple.tsx new file mode 100755 index 0000000..18ac6e4 --- /dev/null +++ b/src/component/Icons/BoxMultiple.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function BoxMultiple(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/BoxMultipleFilled.tsx b/src/component/Icons/BoxMultipleFilled.tsx new file mode 100755 index 0000000..45b9c1b --- /dev/null +++ b/src/component/Icons/BoxMultipleFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function BoxMultipleFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/BranchCompare.tsx b/src/component/Icons/BranchCompare.tsx new file mode 100755 index 0000000..9c4bc65 --- /dev/null +++ b/src/component/Icons/BranchCompare.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function BranchCompare(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/BranchForkLink.tsx b/src/component/Icons/BranchForkLink.tsx new file mode 100755 index 0000000..1a744ea --- /dev/null +++ b/src/component/Icons/BranchForkLink.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function BranchForkLink(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Broom.tsx b/src/component/Icons/Broom.tsx new file mode 100755 index 0000000..2b11cdb --- /dev/null +++ b/src/component/Icons/Broom.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const Broom = createSvgIcon( + , + "Broom", +); + +export default Broom; diff --git a/src/component/Icons/BuildingShop.tsx b/src/component/Icons/BuildingShop.tsx new file mode 100755 index 0000000..3a73845 --- /dev/null +++ b/src/component/Icons/BuildingShop.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function BuildingShop(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/BuildingShopFilled.tsx b/src/component/Icons/BuildingShopFilled.tsx new file mode 100755 index 0000000..c0533de --- /dev/null +++ b/src/component/Icons/BuildingShopFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function BuildingShopFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CalendarClock.tsx b/src/component/Icons/CalendarClock.tsx new file mode 100755 index 0000000..58c79f0 --- /dev/null +++ b/src/component/Icons/CalendarClock.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CalendarClock(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CameraFilled.tsx b/src/component/Icons/CameraFilled.tsx new file mode 100755 index 0000000..231c5a7 --- /dev/null +++ b/src/component/Icons/CameraFilled.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const CameraFilled = createSvgIcon( + , + "CameraFilled", +); + +export default CameraFilled; diff --git a/src/component/Icons/CameraRounded.tsx b/src/component/Icons/CameraRounded.tsx new file mode 100755 index 0000000..b8b03a7 --- /dev/null +++ b/src/component/Icons/CameraRounded.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const CameraRounded = createSvgIcon( + , + "CameraRounded", +); + +export default CameraRounded; diff --git a/src/component/Icons/CaretDown.tsx b/src/component/Icons/CaretDown.tsx new file mode 100755 index 0000000..5cc0601 --- /dev/null +++ b/src/component/Icons/CaretDown.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CaretDown(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CaretRight.tsx b/src/component/Icons/CaretRight.tsx new file mode 100755 index 0000000..7620a7e --- /dev/null +++ b/src/component/Icons/CaretRight.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CaretRight(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Cart.tsx b/src/component/Icons/Cart.tsx new file mode 100755 index 0000000..ca53002 --- /dev/null +++ b/src/component/Icons/Cart.tsx @@ -0,0 +1,12 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Cart(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CheckCircleFilled.tsx b/src/component/Icons/CheckCircleFilled.tsx new file mode 100755 index 0000000..920e67a --- /dev/null +++ b/src/component/Icons/CheckCircleFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CheckCircleFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CheckUnchecked.tsx b/src/component/Icons/CheckUnchecked.tsx new file mode 100755 index 0000000..4cd438c --- /dev/null +++ b/src/component/Icons/CheckUnchecked.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CheckUnchecked(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CheckboxChecked.tsx b/src/component/Icons/CheckboxChecked.tsx new file mode 100755 index 0000000..362e0c0 --- /dev/null +++ b/src/component/Icons/CheckboxChecked.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CheckboxChecked(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Checkmark.tsx b/src/component/Icons/Checkmark.tsx new file mode 100755 index 0000000..72a2484 --- /dev/null +++ b/src/component/Icons/Checkmark.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Checkmark(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CheckmarkCircle.tsx b/src/component/Icons/CheckmarkCircle.tsx new file mode 100755 index 0000000..f5c426c --- /dev/null +++ b/src/component/Icons/CheckmarkCircle.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CheckmarkCircle(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CheckmarkCircleFilled.tsx b/src/component/Icons/CheckmarkCircleFilled.tsx new file mode 100755 index 0000000..0a7c8f8 --- /dev/null +++ b/src/component/Icons/CheckmarkCircleFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CheckmarkCircleFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ChevronRight.tsx b/src/component/Icons/ChevronRight.tsx new file mode 100755 index 0000000..702b6d8 --- /dev/null +++ b/src/component/Icons/ChevronRight.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ChevronRight(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CircleHintFilled.tsx b/src/component/Icons/CircleHintFilled.tsx new file mode 100755 index 0000000..8ef6afd --- /dev/null +++ b/src/component/Icons/CircleHintFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CircleHintFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Clipboard.tsx b/src/component/Icons/Clipboard.tsx new file mode 100755 index 0000000..51965e9 --- /dev/null +++ b/src/component/Icons/Clipboard.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const Clipboard = createSvgIcon( + , + "Clipboard", +); + +export default Clipboard; diff --git a/src/component/Icons/ClockArrowDownload.tsx b/src/component/Icons/ClockArrowDownload.tsx new file mode 100755 index 0000000..c08763c --- /dev/null +++ b/src/component/Icons/ClockArrowDownload.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const ClockArrowDownload = createSvgIcon( + , + "ClockArrowDownload", +); + +export default ClockArrowDownload; diff --git a/src/component/Icons/ClockFilled.tsx b/src/component/Icons/ClockFilled.tsx new file mode 100755 index 0000000..4c35200 --- /dev/null +++ b/src/component/Icons/ClockFilled.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const ClockFilled = createSvgIcon( + , + "ClockFilled", +); + +export default ClockFilled; diff --git a/src/component/Icons/CloudArrowIUp.tsx b/src/component/Icons/CloudArrowIUp.tsx new file mode 100755 index 0000000..95866d8 --- /dev/null +++ b/src/component/Icons/CloudArrowIUp.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CloudArrowIUp(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CloudDownload.tsx b/src/component/Icons/CloudDownload.tsx new file mode 100755 index 0000000..dd5f254 --- /dev/null +++ b/src/component/Icons/CloudDownload.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CloudDownload(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CloudDownloadOutlined.tsx b/src/component/Icons/CloudDownloadOutlined.tsx new file mode 100755 index 0000000..fe9907d --- /dev/null +++ b/src/component/Icons/CloudDownloadOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CloudDownloadOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CloudFilled.tsx b/src/component/Icons/CloudFilled.tsx new file mode 100755 index 0000000..57ceebe --- /dev/null +++ b/src/component/Icons/CloudFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CloudFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CoinStack.tsx b/src/component/Icons/CoinStack.tsx new file mode 100755 index 0000000..f763e7f --- /dev/null +++ b/src/component/Icons/CoinStack.tsx @@ -0,0 +1,12 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CoinStack(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Color.tsx b/src/component/Icons/Color.tsx new file mode 100755 index 0000000..ac708cd --- /dev/null +++ b/src/component/Icons/Color.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Color(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CommentMultiple.tsx b/src/component/Icons/CommentMultiple.tsx new file mode 100755 index 0000000..90afcb7 --- /dev/null +++ b/src/component/Icons/CommentMultiple.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CommentMultiple(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CopperCoin.tsx b/src/component/Icons/CopperCoin.tsx new file mode 100755 index 0000000..35f81d6 --- /dev/null +++ b/src/component/Icons/CopperCoin.tsx @@ -0,0 +1,12 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CopperCoin(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CopyOutlined.tsx b/src/component/Icons/CopyOutlined.tsx new file mode 100755 index 0000000..d92d013 --- /dev/null +++ b/src/component/Icons/CopyOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CopyOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Copyright.tsx b/src/component/Icons/Copyright.tsx new file mode 100755 index 0000000..c50fea5 --- /dev/null +++ b/src/component/Icons/Copyright.tsx @@ -0,0 +1,11 @@ +import { createSvgIcon } from "@mui/material"; + +const Copyright = createSvgIcon( + + + + , + "Copyright", +); + +export default Copyright; diff --git a/src/component/Icons/CubeSync.tsx b/src/component/Icons/CubeSync.tsx new file mode 100755 index 0000000..92d8506 --- /dev/null +++ b/src/component/Icons/CubeSync.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CubeSync(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CubeSyncFilled.tsx b/src/component/Icons/CubeSyncFilled.tsx new file mode 100755 index 0000000..cdb3fb6 --- /dev/null +++ b/src/component/Icons/CubeSyncFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CubeSyncFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CubeTree.tsx b/src/component/Icons/CubeTree.tsx new file mode 100755 index 0000000..6adbfd4 --- /dev/null +++ b/src/component/Icons/CubeTree.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CubeTree(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/CubeTreeFilled.tsx b/src/component/Icons/CubeTreeFilled.tsx new file mode 100755 index 0000000..1938882 --- /dev/null +++ b/src/component/Icons/CubeTreeFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function CubeTreeFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Currency.tsx b/src/component/Icons/Currency.tsx new file mode 100755 index 0000000..f8f95d9 --- /dev/null +++ b/src/component/Icons/Currency.tsx @@ -0,0 +1,12 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Currency(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/DarkTheme.tsx b/src/component/Icons/DarkTheme.tsx new file mode 100755 index 0000000..e23e3a1 --- /dev/null +++ b/src/component/Icons/DarkTheme.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function DarkTheme(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/DataBarVerticalStar.tsx b/src/component/Icons/DataBarVerticalStar.tsx new file mode 100755 index 0000000..e019c9d --- /dev/null +++ b/src/component/Icons/DataBarVerticalStar.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function DataBarVerticalStar(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/DataHistogram.tsx b/src/component/Icons/DataHistogram.tsx new file mode 100755 index 0000000..5e29e75 --- /dev/null +++ b/src/component/Icons/DataHistogram.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function DataHistogram(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/DataHistogramFilled.tsx b/src/component/Icons/DataHistogramFilled.tsx new file mode 100755 index 0000000..8d7c57e --- /dev/null +++ b/src/component/Icons/DataHistogramFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function DataHistogramFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Delete.tsx b/src/component/Icons/Delete.tsx new file mode 100755 index 0000000..a4e3e29 --- /dev/null +++ b/src/component/Icons/Delete.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Delete(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/DeleteOutlined.tsx b/src/component/Icons/DeleteOutlined.tsx new file mode 100755 index 0000000..01a7cfb --- /dev/null +++ b/src/component/Icons/DeleteOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function DeleteOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/DesktopFlow.tsx b/src/component/Icons/DesktopFlow.tsx new file mode 100755 index 0000000..93895ed --- /dev/null +++ b/src/component/Icons/DesktopFlow.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function DesktopFlow(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Discord.tsx b/src/component/Icons/Discord.tsx new file mode 100755 index 0000000..d7877da --- /dev/null +++ b/src/component/Icons/Discord.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Discord(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Dismiss.tsx b/src/component/Icons/Dismiss.tsx new file mode 100755 index 0000000..2b15f6c --- /dev/null +++ b/src/component/Icons/Dismiss.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Dismiss(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/DismissCircleFilled.tsx b/src/component/Icons/DismissCircleFilled.tsx new file mode 100755 index 0000000..b27b730 --- /dev/null +++ b/src/component/Icons/DismissCircleFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function DismissCircleFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Divider.tsx b/src/component/Icons/Divider.tsx new file mode 100755 index 0000000..ead2330 --- /dev/null +++ b/src/component/Icons/Divider.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Divider(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Document.tsx b/src/component/Icons/Document.tsx new file mode 100755 index 0000000..e8d10f9 --- /dev/null +++ b/src/component/Icons/Document.tsx @@ -0,0 +1,10 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Document(props: SvgIconProps) { + return ( + + + + + ); +} diff --git a/src/component/Icons/DocumentArrowDownFilled.tsx b/src/component/Icons/DocumentArrowDownFilled.tsx new file mode 100755 index 0000000..d8978d9 --- /dev/null +++ b/src/component/Icons/DocumentArrowDownFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function DocumentArrowDownFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/DocumentCopyFilled.tsx b/src/component/Icons/DocumentCopyFilled.tsx new file mode 100755 index 0000000..2a96b8d --- /dev/null +++ b/src/component/Icons/DocumentCopyFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function DocumentCopyFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/DocumentDataLink.tsx b/src/component/Icons/DocumentDataLink.tsx new file mode 100755 index 0000000..f1cf4ee --- /dev/null +++ b/src/component/Icons/DocumentDataLink.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function DocumentDataLink(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/DocumentFlowchart.tsx b/src/component/Icons/DocumentFlowchart.tsx new file mode 100755 index 0000000..e636750 --- /dev/null +++ b/src/component/Icons/DocumentFlowchart.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const DocumentFlowchart = createSvgIcon( + , + "DocumentFlowchart", +); + +export default DocumentFlowchart; diff --git a/src/component/Icons/DocumentPDF.tsx b/src/component/Icons/DocumentPDF.tsx new file mode 100755 index 0000000..6e01d84 --- /dev/null +++ b/src/component/Icons/DocumentPDF.tsx @@ -0,0 +1,11 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function DocumentPDF(props: SvgIconProps) { + return ( + + + + + + ); +} diff --git a/src/component/Icons/DocumentText.tsx b/src/component/Icons/DocumentText.tsx new file mode 100755 index 0000000..0a445ce --- /dev/null +++ b/src/component/Icons/DocumentText.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function DocumentText(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/DocumentTextOutlined.tsx b/src/component/Icons/DocumentTextOutlined.tsx new file mode 100755 index 0000000..f12825b --- /dev/null +++ b/src/component/Icons/DocumentTextOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function DocumentTextOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Download.tsx b/src/component/Icons/Download.tsx new file mode 100755 index 0000000..22582b0 --- /dev/null +++ b/src/component/Icons/Download.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const Download = createSvgIcon( + , + "Download", +); + +export default Download; diff --git a/src/component/Icons/Earth.tsx b/src/component/Icons/Earth.tsx new file mode 100755 index 0000000..34f7b34 --- /dev/null +++ b/src/component/Icons/Earth.tsx @@ -0,0 +1,7 @@ +import { createSvgIcon } from "@mui/material"; + +const Earth = createSvgIcon( + , + "Earth", +); +export default Earth; diff --git a/src/component/Icons/Edit.tsx b/src/component/Icons/Edit.tsx new file mode 100755 index 0000000..fad3f8e --- /dev/null +++ b/src/component/Icons/Edit.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Edit(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/EditSetting.tsx b/src/component/Icons/EditSetting.tsx new file mode 100755 index 0000000..d15c63b --- /dev/null +++ b/src/component/Icons/EditSetting.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function EditSetting(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/EmailClock.tsx b/src/component/Icons/EmailClock.tsx new file mode 100755 index 0000000..0f2d80e --- /dev/null +++ b/src/component/Icons/EmailClock.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function EmailClock(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/EmojiEdit.tsx b/src/component/Icons/EmojiEdit.tsx new file mode 100755 index 0000000..6c25021 --- /dev/null +++ b/src/component/Icons/EmojiEdit.tsx @@ -0,0 +1,7 @@ +import { createSvgIcon } from "@mui/material"; + +const EmojiEdit = createSvgIcon( + , + "EmojiEdit", +); +export default EmojiEdit; diff --git a/src/component/Icons/Enter.tsx b/src/component/Icons/Enter.tsx new file mode 100755 index 0000000..f3179a3 --- /dev/null +++ b/src/component/Icons/Enter.tsx @@ -0,0 +1,12 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Enter(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Eye.tsx b/src/component/Icons/Eye.tsx new file mode 100755 index 0000000..173759d --- /dev/null +++ b/src/component/Icons/Eye.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const Eye = createSvgIcon( + , + "Eye", +); + +export default Eye; diff --git a/src/component/Icons/EyeOff.tsx b/src/component/Icons/EyeOff.tsx new file mode 100755 index 0000000..3269008 --- /dev/null +++ b/src/component/Icons/EyeOff.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function EyeOff(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/FastForward.tsx b/src/component/Icons/FastForward.tsx new file mode 100755 index 0000000..3d25b6f --- /dev/null +++ b/src/component/Icons/FastForward.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function FastForward(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/FileAdd.tsx b/src/component/Icons/FileAdd.tsx new file mode 100755 index 0000000..e221ffd --- /dev/null +++ b/src/component/Icons/FileAdd.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function FileAdd(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/FileExclBox.tsx b/src/component/Icons/FileExclBox.tsx new file mode 100755 index 0000000..4f48ef8 --- /dev/null +++ b/src/component/Icons/FileExclBox.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function FileExclBox(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/FilePowerPointBox.tsx b/src/component/Icons/FilePowerPointBox.tsx new file mode 100755 index 0000000..2e4657d --- /dev/null +++ b/src/component/Icons/FilePowerPointBox.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function FilePowerPointBox(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/FileWordBox.tsx b/src/component/Icons/FileWordBox.tsx new file mode 100755 index 0000000..77ee733 --- /dev/null +++ b/src/component/Icons/FileWordBox.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function FileWordBox(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/FilmstripImage.tsx b/src/component/Icons/FilmstripImage.tsx new file mode 100755 index 0000000..f9721d8 --- /dev/null +++ b/src/component/Icons/FilmstripImage.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function FilmstripImage(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Filter.tsx b/src/component/Icons/Filter.tsx new file mode 100755 index 0000000..8c4a498 --- /dev/null +++ b/src/component/Icons/Filter.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Filter(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Fingerprint.tsx b/src/component/Icons/Fingerprint.tsx new file mode 100755 index 0000000..20623d9 --- /dev/null +++ b/src/component/Icons/Fingerprint.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Fingerprint(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Folder.tsx b/src/component/Icons/Folder.tsx new file mode 100755 index 0000000..e436884 --- /dev/null +++ b/src/component/Icons/Folder.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Folder(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/FolderAdd.tsx b/src/component/Icons/FolderAdd.tsx new file mode 100755 index 0000000..29f7e1c --- /dev/null +++ b/src/component/Icons/FolderAdd.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function FolderAdd(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/FolderArrowRightOutlined.tsx b/src/component/Icons/FolderArrowRightOutlined.tsx new file mode 100755 index 0000000..b8080b1 --- /dev/null +++ b/src/component/Icons/FolderArrowRightOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function FolderArrowRightOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/FolderArrowUp.tsx b/src/component/Icons/FolderArrowUp.tsx new file mode 100755 index 0000000..f41f3d3 --- /dev/null +++ b/src/component/Icons/FolderArrowUp.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function FolderArrowUp(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/FolderLink.tsx b/src/component/Icons/FolderLink.tsx new file mode 100755 index 0000000..dae8397 --- /dev/null +++ b/src/component/Icons/FolderLink.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const FolderLink = createSvgIcon( + , + "FolderLink", +); + +export default FolderLink; diff --git a/src/component/Icons/FolderOutlined.tsx b/src/component/Icons/FolderOutlined.tsx new file mode 100755 index 0000000..4f26023 --- /dev/null +++ b/src/component/Icons/FolderOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function FolderOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/FolderZip.tsx b/src/component/Icons/FolderZip.tsx new file mode 100755 index 0000000..b5dc24f --- /dev/null +++ b/src/component/Icons/FolderZip.tsx @@ -0,0 +1,10 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function FolderZip(props: SvgIconProps) { + return ( + + + + + ); +} diff --git a/src/component/Icons/FullScreenMaximize.tsx b/src/component/Icons/FullScreenMaximize.tsx new file mode 100755 index 0000000..781f9b5 --- /dev/null +++ b/src/component/Icons/FullScreenMaximize.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const FullScreenMaximize = createSvgIcon( + , + "FullScreenMaximize", +); + +export default FullScreenMaximize; diff --git a/src/component/Icons/FullScreenMinimize.tsx b/src/component/Icons/FullScreenMinimize.tsx new file mode 100755 index 0000000..ef094eb --- /dev/null +++ b/src/component/Icons/FullScreenMinimize.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const FullScreenMinimize = createSvgIcon( + , + "FullScreenMinimize", +); + +export default FullScreenMinimize; diff --git a/src/component/Icons/Gift.tsx b/src/component/Icons/Gift.tsx new file mode 100755 index 0000000..12a0ef6 --- /dev/null +++ b/src/component/Icons/Gift.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Gift(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Globe.tsx b/src/component/Icons/Globe.tsx new file mode 100755 index 0000000..fbbe732 --- /dev/null +++ b/src/component/Icons/Globe.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Globe(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/GlobeFilled.tsx b/src/component/Icons/GlobeFilled.tsx new file mode 100755 index 0000000..998c558 --- /dev/null +++ b/src/component/Icons/GlobeFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function GlobeFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Grid.tsx b/src/component/Icons/Grid.tsx new file mode 100755 index 0000000..3601771 --- /dev/null +++ b/src/component/Icons/Grid.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Grid(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/GridOutlined.tsx b/src/component/Icons/GridOutlined.tsx new file mode 100755 index 0000000..f8378b6 --- /dev/null +++ b/src/component/Icons/GridOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function GridOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/HardDrive.tsx b/src/component/Icons/HardDrive.tsx new file mode 100755 index 0000000..a923de5 --- /dev/null +++ b/src/component/Icons/HardDrive.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function HardDrive(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/HardDriveOutlined.tsx b/src/component/Icons/HardDriveOutlined.tsx new file mode 100755 index 0000000..6ad6daf --- /dev/null +++ b/src/component/Icons/HardDriveOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function HardDriveOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/HistoryOutlined.tsx b/src/component/Icons/HistoryOutlined.tsx new file mode 100755 index 0000000..6acf1cc --- /dev/null +++ b/src/component/Icons/HistoryOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function HistoryOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Home.tsx b/src/component/Icons/Home.tsx new file mode 100755 index 0000000..627b29d --- /dev/null +++ b/src/component/Icons/Home.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Home(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/HomeOutlined.tsx b/src/component/Icons/HomeOutlined.tsx new file mode 100755 index 0000000..3a65e4e --- /dev/null +++ b/src/component/Icons/HomeOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function HomeOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Icons.tsx b/src/component/Icons/Icons.tsx new file mode 100755 index 0000000..288a98c --- /dev/null +++ b/src/component/Icons/Icons.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Icons(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Image.tsx b/src/component/Icons/Image.tsx new file mode 100755 index 0000000..aa01ef6 --- /dev/null +++ b/src/component/Icons/Image.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Image(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ImageAarowCounterclockwise.tsx b/src/component/Icons/ImageAarowCounterclockwise.tsx new file mode 100755 index 0000000..850f620 --- /dev/null +++ b/src/component/Icons/ImageAarowCounterclockwise.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ImageArrowCounterclockwise(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ImageCopy.tsx b/src/component/Icons/ImageCopy.tsx new file mode 100755 index 0000000..942284e --- /dev/null +++ b/src/component/Icons/ImageCopy.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ImageCopy(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ImageCopyOutlined.tsx b/src/component/Icons/ImageCopyOutlined.tsx new file mode 100755 index 0000000..1be6d97 --- /dev/null +++ b/src/component/Icons/ImageCopyOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ImageCopyOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ImageEdit.tsx b/src/component/Icons/ImageEdit.tsx new file mode 100755 index 0000000..f45b614 --- /dev/null +++ b/src/component/Icons/ImageEdit.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const ImageEdit = createSvgIcon( + , + "ImageEdit", +); + +export default ImageEdit; diff --git a/src/component/Icons/ImageOffOutlined.tsx b/src/component/Icons/ImageOffOutlined.tsx new file mode 100755 index 0000000..e966708 --- /dev/null +++ b/src/component/Icons/ImageOffOutlined.tsx @@ -0,0 +1,10 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ImageOffOutlined(props: SvgIconProps) { + return ( + + + + + ); +} diff --git a/src/component/Icons/ImageOutlined.tsx b/src/component/Icons/ImageOutlined.tsx new file mode 100755 index 0000000..411cfa0 --- /dev/null +++ b/src/component/Icons/ImageOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ImageOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/InPrivate.tsx b/src/component/Icons/InPrivate.tsx new file mode 100755 index 0000000..3590243 --- /dev/null +++ b/src/component/Icons/InPrivate.tsx @@ -0,0 +1,7 @@ +import { createSvgIcon } from "@mui/material"; + +const InPrivate = createSvgIcon( + , + "InPrivate", +); +export default InPrivate; diff --git a/src/component/Icons/Info.tsx b/src/component/Icons/Info.tsx new file mode 100755 index 0000000..de98349 --- /dev/null +++ b/src/component/Icons/Info.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const Info = createSvgIcon( + , + "Info", +); + +export default Info; diff --git a/src/component/Icons/InfoFilled.tsx b/src/component/Icons/InfoFilled.tsx new file mode 100755 index 0000000..d476d23 --- /dev/null +++ b/src/component/Icons/InfoFilled.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const InfoFilled = createSvgIcon( + , + "InfoFilled", +); + +export default InfoFilled; diff --git a/src/component/Icons/IosArrow.tsx b/src/component/Icons/IosArrow.tsx new file mode 100755 index 0000000..a885065 --- /dev/null +++ b/src/component/Icons/IosArrow.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function IosArrow(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/IosArrowLeft.tsx b/src/component/Icons/IosArrowLeft.tsx new file mode 100755 index 0000000..fd95f2a --- /dev/null +++ b/src/component/Icons/IosArrowLeft.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function IosArrowLeft(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/LanguageC.tsx b/src/component/Icons/LanguageC.tsx new file mode 100755 index 0000000..b1929ac --- /dev/null +++ b/src/component/Icons/LanguageC.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function LanguageC(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/LanguageCPP.tsx b/src/component/Icons/LanguageCPP.tsx new file mode 100755 index 0000000..b0b3593 --- /dev/null +++ b/src/component/Icons/LanguageCPP.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function LanguageCPP(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/LanguageGo.tsx b/src/component/Icons/LanguageGo.tsx new file mode 100755 index 0000000..b107872 --- /dev/null +++ b/src/component/Icons/LanguageGo.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function LanguageGo(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/LanguageJS.tsx b/src/component/Icons/LanguageJS.tsx new file mode 100755 index 0000000..3b18ae1 --- /dev/null +++ b/src/component/Icons/LanguageJS.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function LanguageJS(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/LanguagePHP.tsx b/src/component/Icons/LanguagePHP.tsx new file mode 100755 index 0000000..5cc1e9e --- /dev/null +++ b/src/component/Icons/LanguagePHP.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function LanguagePHP(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/LanguagePython.tsx b/src/component/Icons/LanguagePython.tsx new file mode 100755 index 0000000..702d24e --- /dev/null +++ b/src/component/Icons/LanguagePython.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function LanguagePython(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/LanguageRust.tsx b/src/component/Icons/LanguageRust.tsx new file mode 100755 index 0000000..98a4d59 --- /dev/null +++ b/src/component/Icons/LanguageRust.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function LanguageRust(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Link.tsx b/src/component/Icons/Link.tsx new file mode 100755 index 0000000..844adcb --- /dev/null +++ b/src/component/Icons/Link.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Link(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/LinkDismiss.tsx b/src/component/Icons/LinkDismiss.tsx new file mode 100755 index 0000000..8cf8fac --- /dev/null +++ b/src/component/Icons/LinkDismiss.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const LinkDismiss = createSvgIcon( + , + "LinkDismiss", +); + +export default LinkDismiss; diff --git a/src/component/Icons/LinkEdit.tsx b/src/component/Icons/LinkEdit.tsx new file mode 100755 index 0000000..a46d77b --- /dev/null +++ b/src/component/Icons/LinkEdit.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function LinkEdit(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/LinkOutlined.tsx b/src/component/Icons/LinkOutlined.tsx new file mode 100755 index 0000000..6db714b --- /dev/null +++ b/src/component/Icons/LinkOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function LinkOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/LinkSetting.tsx b/src/component/Icons/LinkSetting.tsx new file mode 100755 index 0000000..16034c2 --- /dev/null +++ b/src/component/Icons/LinkSetting.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const LinkSetting = createSvgIcon( + , + "LinkSetting", +); + +export default LinkSetting; diff --git a/src/component/Icons/LocationFilled.tsx b/src/component/Icons/LocationFilled.tsx new file mode 100755 index 0000000..c19bb54 --- /dev/null +++ b/src/component/Icons/LocationFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function LocationFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/LockClosed.tsx b/src/component/Icons/LockClosed.tsx new file mode 100755 index 0000000..ab70211 --- /dev/null +++ b/src/component/Icons/LockClosed.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function LockClosed(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/LockClosedKey.tsx b/src/component/Icons/LockClosedKey.tsx new file mode 100755 index 0000000..61f4e2d --- /dev/null +++ b/src/component/Icons/LockClosedKey.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const LockClosedKey = createSvgIcon( + , + "LockClosedKey", +); + +export default LockClosedKey; diff --git a/src/component/Icons/LockClosedOutlined.tsx b/src/component/Icons/LockClosedOutlined.tsx new file mode 100755 index 0000000..c6661c7 --- /dev/null +++ b/src/component/Icons/LockClosedOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function LockClosedOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/MagnetOn.tsx b/src/component/Icons/MagnetOn.tsx new file mode 100755 index 0000000..98ebaa1 --- /dev/null +++ b/src/component/Icons/MagnetOn.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function MagnetOn(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/MailOutlined.tsx b/src/component/Icons/MailOutlined.tsx new file mode 100755 index 0000000..aa30afa --- /dev/null +++ b/src/component/Icons/MailOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function MailOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Markdown.tsx b/src/component/Icons/Markdown.tsx new file mode 100755 index 0000000..792bfc5 --- /dev/null +++ b/src/component/Icons/Markdown.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Markdown(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Money.tsx b/src/component/Icons/Money.tsx new file mode 100755 index 0000000..80d68f5 --- /dev/null +++ b/src/component/Icons/Money.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Money(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Moon.tsx b/src/component/Icons/Moon.tsx new file mode 100755 index 0000000..530624b --- /dev/null +++ b/src/component/Icons/Moon.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Moon(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/MoreHorizontal.tsx b/src/component/Icons/MoreHorizontal.tsx new file mode 100755 index 0000000..dffab7c --- /dev/null +++ b/src/component/Icons/MoreHorizontal.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function MoreHorizontal(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/MoreVertical.tsx b/src/component/Icons/MoreVertical.tsx new file mode 100755 index 0000000..d2a82e6 --- /dev/null +++ b/src/component/Icons/MoreVertical.tsx @@ -0,0 +1,7 @@ +import { createSvgIcon } from "@mui/material"; + +const MoreVertical = createSvgIcon( + , + "MoreVertical", +); +export default MoreVertical; diff --git a/src/component/Icons/MusicNote1.tsx b/src/component/Icons/MusicNote1.tsx new file mode 100755 index 0000000..244e768 --- /dev/null +++ b/src/component/Icons/MusicNote1.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function MusicNote1(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/MusicNote1Outlined.tsx b/src/component/Icons/MusicNote1Outlined.tsx new file mode 100755 index 0000000..1679759 --- /dev/null +++ b/src/component/Icons/MusicNote1Outlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function MusicNote1Outlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/MusicNote2.tsx b/src/component/Icons/MusicNote2.tsx new file mode 100755 index 0000000..09a2a3a --- /dev/null +++ b/src/component/Icons/MusicNote2.tsx @@ -0,0 +1,10 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function MusicNote2(props: SvgIconProps) { + return ( + + + , + + ); +} diff --git a/src/component/Icons/MusicNote2Play.tsx b/src/component/Icons/MusicNote2Play.tsx new file mode 100755 index 0000000..3078046 --- /dev/null +++ b/src/component/Icons/MusicNote2Play.tsx @@ -0,0 +1,10 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function MusicNote2Play(props: SvgIconProps) { + return ( + + + , + + ); +} diff --git a/src/component/Icons/Notepad.tsx b/src/component/Icons/Notepad.tsx new file mode 100755 index 0000000..5427c74 --- /dev/null +++ b/src/component/Icons/Notepad.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Notepad(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Numbers.tsx b/src/component/Icons/Numbers.tsx new file mode 100755 index 0000000..07a7d4d --- /dev/null +++ b/src/component/Icons/Numbers.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Numbers(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Open.tsx b/src/component/Icons/Open.tsx new file mode 100755 index 0000000..677ff27 --- /dev/null +++ b/src/component/Icons/Open.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const Open = createSvgIcon( + , + "Open", +); + +export default Open; diff --git a/src/component/Icons/OpenFilled.tsx b/src/component/Icons/OpenFilled.tsx new file mode 100755 index 0000000..f457c1d --- /dev/null +++ b/src/component/Icons/OpenFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function OpenFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Options.tsx b/src/component/Icons/Options.tsx new file mode 100755 index 0000000..d4e88c2 --- /dev/null +++ b/src/component/Icons/Options.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Options(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/PackageOpen.tsx b/src/component/Icons/PackageOpen.tsx new file mode 100755 index 0000000..7abeb30 --- /dev/null +++ b/src/component/Icons/PackageOpen.tsx @@ -0,0 +1,17 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function PackageOpen(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Password.tsx b/src/component/Icons/Password.tsx new file mode 100755 index 0000000..47990e3 --- /dev/null +++ b/src/component/Icons/Password.tsx @@ -0,0 +1,10 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Password(props: SvgIconProps) { + return ( + + + + + ); +} diff --git a/src/component/Icons/Pause.tsx b/src/component/Icons/Pause.tsx new file mode 100755 index 0000000..072b82b --- /dev/null +++ b/src/component/Icons/Pause.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Pause(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Payment.tsx b/src/component/Icons/Payment.tsx new file mode 100755 index 0000000..2edf93d --- /dev/null +++ b/src/component/Icons/Payment.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Payment(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/PaymentFilled.tsx b/src/component/Icons/PaymentFilled.tsx new file mode 100755 index 0000000..cc5f349 --- /dev/null +++ b/src/component/Icons/PaymentFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function PaymentFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/People.tsx b/src/component/Icons/People.tsx new file mode 100755 index 0000000..fe79502 --- /dev/null +++ b/src/component/Icons/People.tsx @@ -0,0 +1,7 @@ +import { createSvgIcon } from "@mui/material"; + +const People = createSvgIcon( + , + "People", +); +export default People; diff --git a/src/component/Icons/PeopleFilled.tsx b/src/component/Icons/PeopleFilled.tsx new file mode 100755 index 0000000..1a59367 --- /dev/null +++ b/src/component/Icons/PeopleFilled.tsx @@ -0,0 +1,7 @@ +import { createSvgIcon } from "@mui/material"; + +const PeopleFilled = createSvgIcon( + , + "PeopleFilled", +); +export default PeopleFilled; diff --git a/src/component/Icons/PeopleStar.tsx b/src/component/Icons/PeopleStar.tsx new file mode 100755 index 0000000..5f3a21a --- /dev/null +++ b/src/component/Icons/PeopleStar.tsx @@ -0,0 +1,7 @@ +import { createSvgIcon } from "@mui/material"; + +const PeopleStar = createSvgIcon( + , + "PeopleStar", +); +export default PeopleStar; diff --git a/src/component/Icons/PeopleTeam.tsx b/src/component/Icons/PeopleTeam.tsx new file mode 100755 index 0000000..5abb484 --- /dev/null +++ b/src/component/Icons/PeopleTeam.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function PeopleTeam(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/PeopleTeamOutlined.tsx b/src/component/Icons/PeopleTeamOutlined.tsx new file mode 100755 index 0000000..1a222a5 --- /dev/null +++ b/src/component/Icons/PeopleTeamOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function PeopleTeamOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Person.tsx b/src/component/Icons/Person.tsx new file mode 100755 index 0000000..4d28044 --- /dev/null +++ b/src/component/Icons/Person.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Person(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/PersonLock.tsx b/src/component/Icons/PersonLock.tsx new file mode 100755 index 0000000..2a0c498 --- /dev/null +++ b/src/component/Icons/PersonLock.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const PersonLock = createSvgIcon( + , + "PersonLock", +); + +export default PersonLock; diff --git a/src/component/Icons/PersonOutlined.tsx b/src/component/Icons/PersonOutlined.tsx new file mode 100755 index 0000000..fa4ddce --- /dev/null +++ b/src/component/Icons/PersonOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function PersonOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/PersonPasskey.tsx b/src/component/Icons/PersonPasskey.tsx new file mode 100755 index 0000000..b8f66f8 --- /dev/null +++ b/src/component/Icons/PersonPasskey.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function PersonPasskey(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/PersonStar.tsx b/src/component/Icons/PersonStar.tsx new file mode 100755 index 0000000..35d8018 --- /dev/null +++ b/src/component/Icons/PersonStar.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function PersonStar(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/PhoneLaptop.tsx b/src/component/Icons/PhoneLaptop.tsx new file mode 100755 index 0000000..fd06dfe --- /dev/null +++ b/src/component/Icons/PhoneLaptop.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function PhoneLaptop(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/PhoneLaptopOutlined.tsx b/src/component/Icons/PhoneLaptopOutlined.tsx new file mode 100755 index 0000000..3b4eb19 --- /dev/null +++ b/src/component/Icons/PhoneLaptopOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function PhoneLaptopOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/PinOutlined.tsx b/src/component/Icons/PinOutlined.tsx new file mode 100755 index 0000000..f04c9ba --- /dev/null +++ b/src/component/Icons/PinOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function PinOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Play.tsx b/src/component/Icons/Play.tsx new file mode 100755 index 0000000..8bd85c6 --- /dev/null +++ b/src/component/Icons/Play.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Play(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Pulse.tsx b/src/component/Icons/Pulse.tsx new file mode 100755 index 0000000..3f53608 --- /dev/null +++ b/src/component/Icons/Pulse.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const Pulse = createSvgIcon( + , + "Pulse", +); + +export default Pulse; diff --git a/src/component/Icons/QQ.tsx b/src/component/Icons/QQ.tsx new file mode 100755 index 0000000..a922ec9 --- /dev/null +++ b/src/component/Icons/QQ.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function QQ(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/QuestionCircle.tsx b/src/component/Icons/QuestionCircle.tsx new file mode 100755 index 0000000..89fce2e --- /dev/null +++ b/src/component/Icons/QuestionCircle.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function QuestionCircle(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Raw.tsx b/src/component/Icons/Raw.tsx new file mode 100755 index 0000000..5985db9 --- /dev/null +++ b/src/component/Icons/Raw.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Raw(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/RectangleLandscapeSync.tsx b/src/component/Icons/RectangleLandscapeSync.tsx new file mode 100755 index 0000000..c21b4b6 --- /dev/null +++ b/src/component/Icons/RectangleLandscapeSync.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function RectangleLandscapeSync(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/RectangleLandscapeSyncOff.tsx b/src/component/Icons/RectangleLandscapeSyncOff.tsx new file mode 100755 index 0000000..c2e0d69 --- /dev/null +++ b/src/component/Icons/RectangleLandscapeSyncOff.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function RectangleLandscapeSync(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/RenameFilled.tsx b/src/component/Icons/RenameFilled.tsx new file mode 100755 index 0000000..b0bfc87 --- /dev/null +++ b/src/component/Icons/RenameFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function RenameFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/RenameOutlined.tsx b/src/component/Icons/RenameOutlined.tsx new file mode 100755 index 0000000..9c92b7d --- /dev/null +++ b/src/component/Icons/RenameOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function RenameOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Save.tsx b/src/component/Icons/Save.tsx new file mode 100755 index 0000000..8ab199e --- /dev/null +++ b/src/component/Icons/Save.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Save(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Savings.tsx b/src/component/Icons/Savings.tsx new file mode 100755 index 0000000..0eaf4a7 --- /dev/null +++ b/src/component/Icons/Savings.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Savings(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Search.tsx b/src/component/Icons/Search.tsx new file mode 100755 index 0000000..7afc499 --- /dev/null +++ b/src/component/Icons/Search.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Search(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/SendLogging.tsx b/src/component/Icons/SendLogging.tsx new file mode 100755 index 0000000..e704ce7 --- /dev/null +++ b/src/component/Icons/SendLogging.tsx @@ -0,0 +1,12 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function SendLogging(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/SendLoggingFilled.tsx b/src/component/Icons/SendLoggingFilled.tsx new file mode 100755 index 0000000..7603d78 --- /dev/null +++ b/src/component/Icons/SendLoggingFilled.tsx @@ -0,0 +1,12 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function SendLoggingFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Server.tsx b/src/component/Icons/Server.tsx new file mode 100755 index 0000000..ea30e4f --- /dev/null +++ b/src/component/Icons/Server.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Server(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ServerFilled.tsx b/src/component/Icons/ServerFilled.tsx new file mode 100755 index 0000000..9a6abbc --- /dev/null +++ b/src/component/Icons/ServerFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ServerFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Setting.tsx b/src/component/Icons/Setting.tsx new file mode 100755 index 0000000..3251960 --- /dev/null +++ b/src/component/Icons/Setting.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Setting(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/SettingsOutlined.tsx b/src/component/Icons/SettingsOutlined.tsx new file mode 100755 index 0000000..b0def8c --- /dev/null +++ b/src/component/Icons/SettingsOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function SettingsOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Share.tsx b/src/component/Icons/Share.tsx new file mode 100755 index 0000000..aad4271 --- /dev/null +++ b/src/component/Icons/Share.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const Share = createSvgIcon( + , + "Share", +); + +export default Share; diff --git a/src/component/Icons/ShareAndroid.tsx b/src/component/Icons/ShareAndroid.tsx new file mode 100755 index 0000000..350c3cf --- /dev/null +++ b/src/component/Icons/ShareAndroid.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ShareAndroid(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ShareFilled.tsx b/src/component/Icons/ShareFilled.tsx new file mode 100755 index 0000000..99f81bd --- /dev/null +++ b/src/component/Icons/ShareFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ShareFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ShareOutlined.tsx b/src/component/Icons/ShareOutlined.tsx new file mode 100755 index 0000000..e12b64f --- /dev/null +++ b/src/component/Icons/ShareOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ShareOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Shield.tsx b/src/component/Icons/Shield.tsx new file mode 100755 index 0000000..e6755a6 --- /dev/null +++ b/src/component/Icons/Shield.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Shield(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ShieldAdd.tsx b/src/component/Icons/ShieldAdd.tsx new file mode 100755 index 0000000..f8cd5cc --- /dev/null +++ b/src/component/Icons/ShieldAdd.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ShieldAdd(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/ShieldFilled.tsx b/src/component/Icons/ShieldFilled.tsx new file mode 100755 index 0000000..b08e93c --- /dev/null +++ b/src/component/Icons/ShieldFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function ShieldFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/SignOut.tsx b/src/component/Icons/SignOut.tsx new file mode 100755 index 0000000..dfc8e78 --- /dev/null +++ b/src/component/Icons/SignOut.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function SignOut(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/SlideText.tsx b/src/component/Icons/SlideText.tsx new file mode 100755 index 0000000..3d55e74 --- /dev/null +++ b/src/component/Icons/SlideText.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Add(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Sparkle.tsx b/src/component/Icons/Sparkle.tsx new file mode 100755 index 0000000..2d34334 --- /dev/null +++ b/src/component/Icons/Sparkle.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Sparkle(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/SparkleFilled.tsx b/src/component/Icons/SparkleFilled.tsx new file mode 100755 index 0000000..5ff162b --- /dev/null +++ b/src/component/Icons/SparkleFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function SparkleFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/StarFilled.tsx b/src/component/Icons/StarFilled.tsx new file mode 100755 index 0000000..b7e3abe --- /dev/null +++ b/src/component/Icons/StarFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function StarFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Storage.tsx b/src/component/Icons/Storage.tsx new file mode 100755 index 0000000..55209c2 --- /dev/null +++ b/src/component/Icons/Storage.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Storage(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/StorageOutlined.tsx b/src/component/Icons/StorageOutlined.tsx new file mode 100755 index 0000000..ea8cb32 --- /dev/null +++ b/src/component/Icons/StorageOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function StorageOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Subtitles.tsx b/src/component/Icons/Subtitles.tsx new file mode 100755 index 0000000..eebc4c8 --- /dev/null +++ b/src/component/Icons/Subtitles.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Subtitles(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/SunWithTime.tsx b/src/component/Icons/SunWithTime.tsx new file mode 100755 index 0000000..a9cc69d --- /dev/null +++ b/src/component/Icons/SunWithTime.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function SunWithTime(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Sunny.tsx b/src/component/Icons/Sunny.tsx new file mode 100755 index 0000000..574b321 --- /dev/null +++ b/src/component/Icons/Sunny.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Sunny(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/TableSettings.tsx b/src/component/Icons/TableSettings.tsx new file mode 100755 index 0000000..9c2078c --- /dev/null +++ b/src/component/Icons/TableSettings.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function TableSettingsOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Tag.tsx b/src/component/Icons/Tag.tsx new file mode 100755 index 0000000..0f47a4d --- /dev/null +++ b/src/component/Icons/Tag.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const Tag = createSvgIcon( + , + "Tag", +); + +export default Tag; diff --git a/src/component/Icons/Tags.tsx b/src/component/Icons/Tags.tsx new file mode 100755 index 0000000..2eb78a4 --- /dev/null +++ b/src/component/Icons/Tags.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const Tags = createSvgIcon( + , + "Tags", +); + +export default Tags; diff --git a/src/component/Icons/TaskList.tsx b/src/component/Icons/TaskList.tsx new file mode 100755 index 0000000..2905865 --- /dev/null +++ b/src/component/Icons/TaskList.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function TaskList(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/TaskListOutlined.tsx b/src/component/Icons/TaskListOutlined.tsx new file mode 100755 index 0000000..cc206ca --- /dev/null +++ b/src/component/Icons/TaskListOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function TaskListOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/TaskListRegular.tsx b/src/component/Icons/TaskListRegular.tsx new file mode 100755 index 0000000..4bcc6b9 --- /dev/null +++ b/src/component/Icons/TaskListRegular.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function TaskListRegular(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Telegram.tsx b/src/component/Icons/Telegram.tsx new file mode 100755 index 0000000..0f80d47 --- /dev/null +++ b/src/component/Icons/Telegram.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Telegram(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/TextBulletListSquareEdiFilled.tsx b/src/component/Icons/TextBulletListSquareEdiFilled.tsx new file mode 100755 index 0000000..c2c0963 --- /dev/null +++ b/src/component/Icons/TextBulletListSquareEdiFilled.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function TextBulletListSquareEditFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/TextBulletListSquareEdit.tsx b/src/component/Icons/TextBulletListSquareEdit.tsx new file mode 100755 index 0000000..273a646 --- /dev/null +++ b/src/component/Icons/TextBulletListSquareEdit.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function TextBulletListSquareEdit(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/TextCaseTitle.tsx b/src/component/Icons/TextCaseTitle.tsx new file mode 100755 index 0000000..1bdad8d --- /dev/null +++ b/src/component/Icons/TextCaseTitle.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function TextCaseTitle(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/TextEditStyle.tsx b/src/component/Icons/TextEditStyle.tsx new file mode 100755 index 0000000..6d1be26 --- /dev/null +++ b/src/component/Icons/TextEditStyle.tsx @@ -0,0 +1,7 @@ +import { createSvgIcon } from "@mui/material"; + +const TextEditStyle = createSvgIcon( + , + "TextEditStyle", +); +export default TextEditStyle; diff --git a/src/component/Icons/TextGrammarLighting.tsx b/src/component/Icons/TextGrammarLighting.tsx new file mode 100755 index 0000000..a044c90 --- /dev/null +++ b/src/component/Icons/TextGrammarLighting.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function TextGrammarLighting(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/TextIndentIncrease.tsx b/src/component/Icons/TextIndentIncrease.tsx new file mode 100755 index 0000000..332283b --- /dev/null +++ b/src/component/Icons/TextIndentIncrease.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function TextIndentIncrease(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/TicketDiagonal.tsx b/src/component/Icons/TicketDiagonal.tsx new file mode 100755 index 0000000..7fad1a1 --- /dev/null +++ b/src/component/Icons/TicketDiagonal.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function TicketDiagonal(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Timer.tsx b/src/component/Icons/Timer.tsx new file mode 100755 index 0000000..73e8274 --- /dev/null +++ b/src/component/Icons/Timer.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const Timer = createSvgIcon( + , + "Timer", +); + +export default Timer; diff --git a/src/component/Icons/Translate.tsx b/src/component/Icons/Translate.tsx new file mode 100755 index 0000000..432b0a1 --- /dev/null +++ b/src/component/Icons/Translate.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Translate(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Upload.tsx b/src/component/Icons/Upload.tsx new file mode 100755 index 0000000..3b42b70 --- /dev/null +++ b/src/component/Icons/Upload.tsx @@ -0,0 +1,12 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Upload(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/UploadFilled.tsx b/src/component/Icons/UploadFilled.tsx new file mode 100755 index 0000000..3e07029 --- /dev/null +++ b/src/component/Icons/UploadFilled.tsx @@ -0,0 +1,12 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function UploadFilled(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Video.tsx b/src/component/Icons/Video.tsx new file mode 100755 index 0000000..205147f --- /dev/null +++ b/src/component/Icons/Video.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Video(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/VideoOutlined.tsx b/src/component/Icons/VideoOutlined.tsx new file mode 100755 index 0000000..df11823 --- /dev/null +++ b/src/component/Icons/VideoOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function VideoOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/WalletCreditCard.tsx b/src/component/Icons/WalletCreditCard.tsx new file mode 100755 index 0000000..7abde4a --- /dev/null +++ b/src/component/Icons/WalletCreditCard.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const WalletCreditCard = createSvgIcon( + , + "WalletCreditCard", +); + +export default WalletCreditCard; diff --git a/src/component/Icons/Warning.tsx b/src/component/Icons/Warning.tsx new file mode 100755 index 0000000..299aab5 --- /dev/null +++ b/src/component/Icons/Warning.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function Warning(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/WarningOutlined.tsx b/src/component/Icons/WarningOutlined.tsx new file mode 100755 index 0000000..7e4a8d5 --- /dev/null +++ b/src/component/Icons/WarningOutlined.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function WarningOutlined(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/Whiteboard.tsx b/src/component/Icons/Whiteboard.tsx new file mode 100755 index 0000000..063e94d --- /dev/null +++ b/src/component/Icons/Whiteboard.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from "@mui/material"; + +const WhiteBoard = createSvgIcon( + , + "WhiteBoard", +); + +export default WhiteBoard; diff --git a/src/component/Icons/WindowApps.tsx b/src/component/Icons/WindowApps.tsx new file mode 100755 index 0000000..cf7d9d4 --- /dev/null +++ b/src/component/Icons/WindowApps.tsx @@ -0,0 +1,9 @@ +import { SvgIcon, SvgIconProps } from "@mui/material"; + +export default function WindowApps(props: SvgIconProps) { + return ( + + + + ); +} diff --git a/src/component/Icons/WrenchSettings.tsx b/src/component/Icons/WrenchSettings.tsx new file mode 100755 index 0000000..6f13959 --- /dev/null +++ b/src/component/Icons/WrenchSettings.tsx @@ -0,0 +1,7 @@ +import { createSvgIcon } from "@mui/material"; + +const WrenchSettings = createSvgIcon( + , + "WrenchSettings", +); +export default WrenchSettings; diff --git a/src/component/Pages/Devices/AppPromotion.tsx b/src/component/Pages/Devices/AppPromotion.tsx new file mode 100755 index 0000000..f08cd65 --- /dev/null +++ b/src/component/Pages/Devices/AppPromotion.tsx @@ -0,0 +1,166 @@ +import { alpha, Box, Grid, Typography, useTheme } from "@mui/material"; +import { QRCodeSVG } from "qrcode.react"; +import { useMemo, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import SessionManager from "../../../session"; +import { SecondaryButton } from "../../Common/StyledComponents.tsx"; + +const AppPromotion = () => { + const title = useAppSelector((state) => state.siteConfig.basic.config.title); + const { t } = useTranslation(); + const [showQr, setShowQr] = useState(false); + const theme = useTheme(); + const refreshToken = useMemo(() => { + return ( + window.location.origin + + "/login?refresh_token=" + + (SessionManager.currentLoginOrNull()?.token?.refresh_token ?? "") + ); + }, [showQr]); + + return ( + + + + + + + `linear-gradient(180deg, transparent 82%, ${alpha(theme.palette.secondary.main, 0.3)} 0%)`, + }} + />, + ]} + /> + + + +
      +
    1. + + {t("setting.downloadOurApp")} + + + + + + +
    2. +
    3. + + {t("setting.fillInEndpoint")} + + + setShowQr(false)} + style={{ + transition: "all .3s ease-in", + filter: showQr ? "initial" : "blur(8px)", + backgroundColor: "#fff", + }} + value={refreshToken} + /> + + {!showQr && ( + setShowQr(true)} + sx={{ + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + }} + color={"inherit"} + variant={"outlined"} + > + {t("setting.shoeQr")} + + )} + +
    4. +
    5. + + {t("setting.loginApp")} + +
    6. +
    +
    +
    +
    + + + + + + + + + + + +
    + ); +}; + +export default AppPromotion; diff --git a/src/component/Pages/Devices/ConnectionInfoDialog.tsx b/src/component/Pages/Devices/ConnectionInfoDialog.tsx new file mode 100755 index 0000000..b051cb5 --- /dev/null +++ b/src/component/Pages/Devices/ConnectionInfoDialog.tsx @@ -0,0 +1,72 @@ +import { useTranslation } from "react-i18next"; +import { DialogContent, DialogProps, FilledInput, IconButton, InputAdornment, InputLabel, Stack } from "@mui/material"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; +import { DavAccount } from "../../../api/setting.ts"; +import FormControl from "@mui/material/FormControl"; +import CopyOutlined from "../../Icons/CopyOutlined.tsx"; +import { copyToClipboard } from "../../../util"; +import SessionManager from "../../../session"; + +export interface ConnectionInfoDialogProps extends DialogProps { + account?: DavAccount; +} + +const InfoTextField = ({ label, value }: { label: string; value: string }) => { + return ( + + {label} + + copyToClipboard(value)}> + + + + } + inputProps={{ + readOnly: true, + }} + /> + + ); +}; + +const ConnectionInfoDialog = ({ onClose, account, ...rest }: ConnectionInfoDialogProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + return ( + onClose && onClose({}, "escapeKeyDown")} + dialogProps={{ + onClose: onClose, + fullWidth: true, + maxWidth: "xs", + disableRestoreFocus: true, + ...rest, + }} + > + + + + + + + + + ); +}; +export default ConnectionInfoDialog; diff --git a/src/component/Pages/Devices/CreateDAVAccountDialog.tsx b/src/component/Pages/Devices/CreateDAVAccountDialog.tsx new file mode 100755 index 0000000..8341bad --- /dev/null +++ b/src/component/Pages/Devices/CreateDAVAccountDialog.tsx @@ -0,0 +1,177 @@ +import { Checkbox, DialogContent, DialogProps, FormGroup, Stack, Typography, useTheme } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useEffect, useMemo, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { sendCreateDavAccounts, sendUpdateDavAccounts } from "../../../api/api.ts"; +import { DavAccount, DavAccountOption } from "../../../api/setting.ts"; +import { GroupPermission } from "../../../api/user.ts"; +import { defaultPath } from "../../../hooks/useNavigation.tsx"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import SessionManager from "../../../session"; +import Boolset from "../../../util/boolset.ts"; +import { Filesystem } from "../../../util/uri.ts"; +import { Code } from "../../Common/Code.tsx"; +import { OutlineIconTextField } from "../../Common/Form/OutlineIconTextField.tsx"; +import { PathSelectorForm } from "../../Common/Form/PathSelectorForm.tsx"; +import { SmallFormControlLabel } from "../../Common/StyledComponents.tsx"; +import DialogAccordion from "../../Dialogs/DialogAccordion.tsx"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; +import Tag from "../../Icons/Tag.tsx"; + +export interface CreateDAVAccountDialogProps extends DialogProps { + onAccountAdded?: (account: DavAccount) => void; + onAccountUpdated?: (account: DavAccount) => void; + editTarget?: DavAccount; +} + +const CreateDAVAccountDialog = ({ + onClose, + onAccountAdded, + onAccountUpdated, + editTarget, + open, + ...rest +}: CreateDAVAccountDialogProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + + const [loading, setLoading] = useState(false); + const [name, setName] = useState(""); + const [path, setPath] = useState(defaultPath); + const [readonly, setReadonly] = useState(false); + const [blockSysFilesUpload, setBlockSysFilesUpload] = useState(false); + const [proxy, setProxy] = useState(false); + + const theme = useTheme(); + + const groupProxyEnabled = useMemo(() => { + const perm = new Boolset(SessionManager.currentLoginOrNull()?.user.group?.permission); + return perm.enabled(GroupPermission.webdav_proxy); + }, []); + + useEffect(() => { + if (open && editTarget) { + setName(editTarget.name); + setPath(editTarget.uri); + const options = new Boolset(editTarget.options); + setReadonly(options.enabled(DavAccountOption.readonly)); + setBlockSysFilesUpload(options.enabled(DavAccountOption.disable_sys_files)); + setProxy(options.enabled(DavAccountOption.proxy)); + } + }, [open]); + + const onAccept = () => { + if (!name || !path) { + return; + } + + setLoading(true); + const req = { + name, + uri: path, + proxy, + readonly, + disable_sys_files: blockSysFilesUpload, + }; + dispatch(editTarget ? sendUpdateDavAccounts(editTarget.id, req) : sendCreateDavAccounts(req)) + .then((account) => { + onClose && onClose({}, "escapeKeyDown"); + !editTarget && onAccountAdded && onAccountAdded(account); + editTarget && onAccountUpdated && onAccountUpdated(account); + }) + .finally(() => { + setLoading(false); + }); + }; + + return ( + + + + } + variant="outlined" + value={name} + multiline + onChange={(e) => setName(e.target.value)} + label={t("setting.annotation")} + fullWidth + /> + + + + setReadonly(e.target.checked)} checked={readonly} />} + label={t("application:setting.readonlyOn")} + /> + + {t("application:setting.readonlyTooltip")} + + setBlockSysFilesUpload(e.target.checked)} + checked={blockSysFilesUpload} + /> + } + label={t("application:setting.blockSysFilesUpload")} + /> + + ]} + /> + + {groupProxyEnabled && ( + <> + setProxy(e.target.checked)} checked={proxy} />} + label={t("application:setting.proxy")} + /> + + {t("application:setting.proxyTooltip")} + + + )} + + + + + + ); +}; +export default CreateDAVAccountDialog; diff --git a/src/component/Pages/Devices/DavAccountList.tsx b/src/component/Pages/Devices/DavAccountList.tsx new file mode 100755 index 0000000..1b0184d --- /dev/null +++ b/src/component/Pages/Devices/DavAccountList.tsx @@ -0,0 +1,148 @@ +import * as React from "react"; +import { useCallback, useState } from "react"; +import { Box, Table, TableBody, TableContainer, TableHead, TableRow, Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { getDavAccounts } from "../../../api/api.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { DavAccount } from "../../../api/setting.ts"; +import DavAccountRow from "./DavAccountRow.tsx"; +import { NoWrapTableCell } from "../../Common/StyledComponents.tsx"; +import { CellHeaderWithPadding } from "../../FileManager/Dialogs/LockConflictDetails.tsx"; +import CreateDAVAccountDialog from "./CreateDAVAccountDialog.tsx"; +import ConnectionInfoDialog from "./ConnectionInfoDialog.tsx"; + +const defaultPageSize = 50; + +export interface DavAccountListProps { + creatAccountDialog: boolean; + setCreateAccountDialog: (value: boolean) => void; +} + +const DavAccountList = ({ creatAccountDialog, setCreateAccountDialog }: DavAccountListProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const [nextPageToken, setNextPageToken] = useState(""); + const [accounts, setAccounts] = useState([]); + const [loading, setLoading] = useState(false); + const [accountInfoTarget, setAccountInfoTarget] = useState(); + const [editTarget, setEditTarget] = useState(); + const [editOpen, setEditOpen] = useState(false); + + const loadNextPage = useCallback( + (originAccounts: DavAccount[], token?: string) => () => { + setLoading(true); + dispatch( + getDavAccounts({ + page_size: defaultPageSize, + next_page_token: token, + }), + ) + .then((res) => { + setAccounts([...originAccounts, ...res.accounts]); + if (res.pagination?.next_token) { + setNextPageToken(res.pagination.next_token); + } else { + setNextPageToken(undefined); + } + }) + .catch(() => { + setNextPageToken(undefined); + }) + .finally(() => { + setLoading(false); + }); + }, + [dispatch], + ); + + const refresh = () => { + loadNextPage([], "")(); + }; + + const onAccountDeleted = useCallback( + (id: string) => { + setAccounts((accounts) => accounts.filter((account) => account.id !== id)); + }, + [setAccounts], + ); + + const onAccountAdded = (account: DavAccount) => { + setAccounts([account, ...accounts]); + setAccountInfoTarget(account); + }; + + const onEditOpen = (account: DavAccount) => { + setEditOpen(true); + setEditTarget(account); + }; + + return ( + + setCreateAccountDialog(false)} + onAccountAdded={onAccountAdded} + /> + setEditOpen(false)} + onAccountUpdated={(account) => setAccounts(accounts.map((a) => (a.id == account.id ? account : a)))} + /> + setAccountInfoTarget(undefined)} + /> + + + + + {t("setting.annotation")} + + {t("setting.rootFolder")} + + {t("fileManager.permissions")} + {t("setting.proxy")} + {t("fileManager.createdAt")} + {t("fileManager.actions")} + + + + {accounts.map((account) => ( + setAccountInfoTarget(account)} + key={account.id} + account={account} + onAccountDeleted={onAccountDeleted} + onEditClicked={() => onEditOpen(account)} + /> + ))} + {nextPageToken != undefined && ( + <> + {[...Array(4)].map((_, i) => ( + + ))} + + )} + +
    + {nextPageToken == undefined && accounts.length == 0 && ( + + + {t("application:setting.listEmpty")} + + + )} +
    +
    + ); +}; + +export default DavAccountList; diff --git a/src/component/Pages/Devices/DavAccountRow.tsx b/src/component/Pages/Devices/DavAccountRow.tsx new file mode 100755 index 0000000..b40ae8e --- /dev/null +++ b/src/component/Pages/Devices/DavAccountRow.tsx @@ -0,0 +1,164 @@ +import * as React from "react"; +import { useEffect, useMemo } from "react"; +import { IconButton, ListItemText, Menu, Skeleton, TableRow } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { NoWrapCell, SquareChip } from "../../Common/StyledComponents.tsx"; +import { DavAccount, DavAccountOption } from "../../../api/setting.ts"; +import FileBadge from "../../FileManager/FileBadge.tsx"; +import { FileType } from "../../../api/explorer.ts"; +import TimeBadge from "../../Common/TimeBadge.tsx"; +import Boolset from "../../../util/boolset.ts"; +import { defaultPath } from "../../../hooks/useNavigation.tsx"; +import { useInView } from "react-intersection-observer"; +import Eye from "../../Icons/Eye.tsx"; +import { Edit } from "@mui/icons-material"; +import CloudFilled from "../../Icons/CloudFilled.tsx"; +import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import MoreVertical from "../../Icons/MoreVertical.tsx"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx"; +import { sendDeleteDavAccount } from "../../../api/api.ts"; + +export interface DavAccountRowProps { + account?: DavAccount; + onLoad?: () => void; + loading?: boolean; + onAccountDeleted: (id: string) => void; + onClick?: () => void; + onEditClicked?: (account: DavAccount) => void; +} + +const DavAccountRow = ({ account, onAccountDeleted, onLoad, loading, onClick, onEditClicked }: DavAccountRowProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const { ref, inView } = useInView({ + rootMargin: "200px 0px", + triggerOnce: true, + skip: !loading || !onLoad, + }); + + const actionMenuState = usePopupState({ + variant: "popover", + popupId: "action" + account?.id, + }); + + useEffect(() => { + if (!inView) { + return; + } + + if (onLoad) { + onLoad(); + } + }, [inView]); + + const [readOnly, proxy] = useMemo(() => { + if (!account?.options) return [false, false] as const; + const bs = new Boolset(account.options); + return [bs.enabled(DavAccountOption.readonly), bs.enabled(DavAccountOption.proxy)] as const; + }, [account?.options]); + + const { onClose, ...rest } = bindMenu(actionMenuState); + + const onEdit = () => { + if (account) { + onEditClicked?.(account); + } + onClose && onClose(); + }; + + const onDelete = () => { + if (!account?.id) { + return; + } + + onClose(); + dispatch(sendDeleteDavAccount(account.id)); + onAccountDeleted(account.id); + }; + + return ( + + + {loading ? : account?.name} + + + {loading ? ( + + ) : ( + + )} + + + {loading ? ( + + ) : readOnly ? ( + } size={"small"} label={t("setting.readonlyOn")} /> + ) : ( + } size={"small"} label={t("setting.readonlyOff")} /> + )} + + + {loading ? ( + + ) : proxy ? ( + } + sx={{ + backgroundColor: (theme) => theme.palette.primary.light, + }} + color={"success"} + size={"small"} + label={t("application:setting.proxied")} + /> + ) : ( + t("setting.none") + )} + + + {loading || !account ? ( + + ) : ( + + )} + + e.stopPropagation()}> + {account && ( + + + + )} + + e.stopPropagation()} + onClose={onClose} + slotProps={{ + paper: { + sx: { + minWidth: 150, + }, + }, + }} + {...rest} + > + + {t(`fileManager.edit`)} + + + {t(`fileManager.delete`)} + + + + ); +}; + +export default DavAccountRow; diff --git a/src/component/Pages/Devices/Devices.tsx b/src/component/Pages/Devices/Devices.tsx new file mode 100755 index 0000000..a7cfef0 --- /dev/null +++ b/src/component/Pages/Devices/Devices.tsx @@ -0,0 +1,92 @@ +import PageHeader, { PageTabQuery } from "../PageHeader.tsx"; +import { Button, Container, Grow } from "@mui/material"; +import * as React from "react"; +import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useSearchParams } from "react-router-dom"; +import ResponsiveTabs from "../../Common/ResponsiveTabs.tsx"; +import DavAccountList from "./DavAccountList.tsx"; +import Add from "../../Icons/Add.tsx"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { loadSiteConfig } from "../../../redux/thunks/site.ts"; +import Nothing from "../../Common/Nothing.tsx"; +import SessionManager from "../../../session"; +import Boolset from "../../../util/boolset.ts"; +import { GroupPermission } from "../../../api/user.ts"; +import AppPromotion from "./AppPromotion.tsx"; +import PageContainer from "../PageContainer.tsx"; + +export enum DevicePageTab { + Dav = "dav", + App = "app", +} + +const Devices = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const [searchParams, setSearchParams] = useSearchParams(); + const [creatAccountDialog, setCreateAccountDialog] = useState(false); + const appPromotion = useAppSelector((state) => state.siteConfig.app.config?.app_promotion); + + const webDavEnabled = useMemo(() => { + const user = SessionManager.currentLoginOrNull(); + let enabled = false; + if (user && user.user.group?.permission) { + const bs = new Boolset(user.user.group.permission); + enabled = bs.enabled(GroupPermission.webdav); + } + + return enabled; + }, []); + + const tabs = useMemo(() => { + const res = []; + if (webDavEnabled) { + res.push({ + label: t("application:setting.webdavAccounts"), + value: DevicePageTab.Dav, + }); + } + if (appPromotion) { + res.push({ + label: t("application:setting.iOSApp"), + value: DevicePageTab.App, + }); + } + return res; + }, [webDavEnabled, appPromotion]); + + const [tab, setTab] = useState( + searchParams.get(PageTabQuery) ?? (webDavEnabled ? DevicePageTab.Dav : DevicePageTab.App), + ); + + useEffect(() => { + dispatch(loadSiteConfig("app")); + }, []); + + return ( + + + + + + } + title={t("application:navbar.connect")} + /> + setTab(newValue)} tabs={tabs} /> + {tab == DevicePageTab.Dav && webDavEnabled && ( + + )} + {tab == DevicePageTab.App && appPromotion && } + + {!webDavEnabled && !appPromotion && } + + + ); +}; + +export default Devices; diff --git a/src/component/Pages/HomeRedirect.tsx b/src/component/Pages/HomeRedirect.tsx new file mode 100755 index 0000000..5de2ec5 --- /dev/null +++ b/src/component/Pages/HomeRedirect.tsx @@ -0,0 +1,16 @@ +import SessionManager from "../../session"; +import { useNavigate } from "react-router-dom"; +import { useEffect } from "react"; + +export const HomeRedirect = () => { + const navigate = useNavigate(); + useEffect(() => { + if (SessionManager.currentLoginOrNull()) { + navigate("/home"); + } else { + navigate("/session"); + } + }, []); + + return
    ; +}; diff --git a/src/component/Pages/Login/Activate.tsx b/src/component/Pages/Login/Activate.tsx new file mode 100755 index 0000000..cbf6194 --- /dev/null +++ b/src/component/Pages/Login/Activate.tsx @@ -0,0 +1,87 @@ +import { useTranslation } from "react-i18next"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { useNavigate } from "react-router-dom"; +import { useSnackbar } from "notistack"; +import { useQuery } from "../../../util"; +import React, { useEffect, useState } from "react"; +import { Box, Button, Typography } from "@mui/material"; +import PageTitle from "../../../router/PageTitle.tsx"; +import CheckmarkCircle from "../../Icons/CheckmarkCircle.tsx"; +import { setHeadlessFrameLoading } from "../../../redux/globalStateSlice.ts"; +import { sendEmailActivate } from "../../../api/api.ts"; + +const Activate = () => { + const { t } = useTranslation(); + const { reg_captcha } = useAppSelector((state) => state.siteConfig.login.config); + const navigate = useNavigate(); + const { enqueueSnackbar } = useSnackbar(); + const dispatch = useAppDispatch(); + const query = useQuery(); + + const [success, setSuccess] = useState(true); + const [email, setEmail] = useState(""); + + useEffect(() => { + const sign = query.get("sign"); + const id = query.get("id"); + if (!sign || !id) { + setSuccess(false); + navigate("/session"); + return; + } + + dispatch(setHeadlessFrameLoading(true)); + dispatch(sendEmailActivate(id, decodeURIComponent(sign))) + .then((u) => { + setEmail(u?.email ?? ""); + setSuccess(true); + }) + .catch(() => { + navigate("/session"); + }) + .finally(() => { + dispatch(setHeadlessFrameLoading(false)); + }); + }, []); + + return ( + + + + {success && ( + <> + + + theme.palette.success.main, + mt: 1, + }} + > + {t("application:login.accountActivated")} + + + + + )} + + + ); +}; + +export default Activate; diff --git a/src/component/Pages/Login/Phases/Phase2FA.tsx b/src/component/Pages/Login/Phases/Phase2FA.tsx new file mode 100755 index 0000000..6d02e65 --- /dev/null +++ b/src/component/Pages/Login/Phases/Phase2FA.tsx @@ -0,0 +1,43 @@ +import { useTranslation } from "react-i18next"; +import { useAppSelector } from "../../../../redux/hooks.ts"; +import { Control } from "../Signin/SignIn.tsx"; +import { FormControl, styled, Typography } from "@mui/material"; +import { MuiOtpInput } from "mui-one-time-password-input"; + +interface Phase2FAProps { + control?: Control; + otp: string; + onOtpChange: (otp: string) => void; + loading: boolean; +} + +const MuiOtpInputStyled = styled(MuiOtpInput)` + display: flex; + gap: 8px; + max-width: 650px; + margin-inline: auto; +`; + +const Phase2FA = ({ control, otp, onOtpChange, loading }: Phase2FAProps) => { + const { t } = useTranslation(); + const regEnabled = useAppSelector((state) => state.siteConfig.login.config.register_enabled); + return ( + <> + {t("login.input2FACode")} + + + + + {control?.submit} + {control?.back} + + ); +}; + +export default Phase2FA; diff --git a/src/component/Pages/Login/Phases/PhaseCollectEmail.tsx b/src/component/Pages/Login/Phases/PhaseCollectEmail.tsx new file mode 100755 index 0000000..f51a12a --- /dev/null +++ b/src/component/Pages/Login/Phases/PhaseCollectEmail.tsx @@ -0,0 +1,106 @@ +import { Box, Divider, FormControl, Link, Stack } from "@mui/material"; +import { useEffect } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Link as RouterLink } from "react-router-dom"; +import { useAppSelector } from "../../../../redux/hooks.ts"; +import { useQuery } from "../../../../util"; +import { OutlineIconTextField } from "../../../Common/Form/OutlineIconTextField.tsx"; +import MailOutlined from "../../../Icons/MailOutlined.tsx"; +import PasskeyLoginButton from "../Signin/PasskeyLoginButton.tsx"; +import { Control } from "../Signin/SignIn.tsx"; + +export const LegalLinks = () => { + const { t } = useTranslation(); + const tos = useAppSelector((state) => state.siteConfig.login.config.tos_url); + const privacyPolicy = useAppSelector((state) => state.siteConfig.login.config.privacy_policy_url); + return ( + <> + {(tos || privacyPolicy) && ( + + {tos && ( + + {t("login.termOfUse")} + + )} + {tos && privacyPolicy && " | "} + {privacyPolicy && ( + + {t("login.privacyPolicy")} + + )} + + )} + + ); +}; + +interface PhaseCollectEmailProps { + email: string; + setEmail: (email: string) => void; + control?: Control; +} + +const PhaseCollectEmail = ({ email, setEmail, control }: PhaseCollectEmailProps) => { + const { t } = useTranslation(); + const query = useQuery(); + const { register_enabled, authn } = useAppSelector((state) => state.siteConfig.login.config); + const tos = useAppSelector((state) => state.siteConfig.login.config.tos_url); + const privacyPolicy = useAppSelector((state) => state.siteConfig.login.config.privacy_policy_url); + + const showFooter = tos || privacyPolicy || authn; + + useEffect(() => { + if (!!query.get("email")) { + setEmail(query.get("email") ?? ""); + } + }, []); + + return ( + <> + + setEmail(e.target.value)} + icon={} + autoComplete={"username webauthn"} + value={email} + autoFocus + /> + + {control?.submit} + {control?.back} + {register_enabled && ( + + ]} + /> + + )} + {showFooter && ( + <> + + {authn && } + + + )} + + ); +}; + +export default PhaseCollectEmail; diff --git a/src/component/Pages/Login/Phases/PhaseCollectPassword.tsx b/src/component/Pages/Login/Phases/PhaseCollectPassword.tsx new file mode 100755 index 0000000..70e62a2 --- /dev/null +++ b/src/component/Pages/Login/Phases/PhaseCollectPassword.tsx @@ -0,0 +1,89 @@ +import { Divider, FormControl, Link, Stack, Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { PrepareLoginResponse } from "../../../../api/user.ts"; +import { useAppSelector } from "../../../../redux/hooks.ts"; +import { Captcha, CaptchaParams } from "../../../Common/Captcha/Captcha.tsx"; +import { OutlineIconTextField } from "../../../Common/Form/OutlineIconTextField.tsx"; +import Password from "../../../Icons/Password.tsx"; +import PasskeyLoginButton from "../Signin/PasskeyLoginButton.tsx"; +import { Control } from "../Signin/SignIn.tsx"; + +interface PhaseCollectPasswordProps { + pwd: string; + email: string; + setPwd: (pwd: string) => void; + control?: Control; + loginOptions?: PrepareLoginResponse; + captchaGen: number; + setCaptchaState: (state: CaptchaParams) => void; + onForget?: () => void; +} + +const PhaseCollectPassword = ({ + pwd, + email, + setPwd, + control, + loginOptions, + captchaGen, + setCaptchaState, + onForget, +}: PhaseCollectPasswordProps) => { + const { t } = useTranslation(); + const { login_captcha, authn } = useAppSelector((state) => state.siteConfig.login.config); + + const moreOptions = loginOptions?.webauthn_enabled && authn; + return ( + <> + {loginOptions?.password_enabled && ( + <> + {t("login.enterPasswordHint", { email: email })} + + setPwd(e.target.value)} + icon={} + value={pwd} + autoComplete={"true"} + /> + + {t("login.forgetPassword")} + + + {login_captcha && ( + + + + )} + {control?.submit} + {moreOptions && ( + + + {t("login.or")} + + + )} + + )} + {!loginOptions?.password_enabled && ( + + {t("login.paswordlessHint", { email: email })} + + )} + {loginOptions?.webauthn_enabled && authn && } + {control?.back} + + ); +}; + +export default PhaseCollectPassword; diff --git a/src/component/Pages/Login/Phases/PhaseForgetPassword.tsx b/src/component/Pages/Login/Phases/PhaseForgetPassword.tsx new file mode 100755 index 0000000..96194a7 --- /dev/null +++ b/src/component/Pages/Login/Phases/PhaseForgetPassword.tsx @@ -0,0 +1,31 @@ +import { useTranslation } from "react-i18next"; +import { Control } from "../Signin/SignIn.tsx"; +import { useAppSelector } from "../../../../redux/hooks.ts"; +import { FormControl } from "@mui/material"; +import { Captcha, CaptchaParams } from "../../../Common/Captcha/Captcha.tsx"; + +interface PhaseForgetPasswordProps { + email: string; + control?: Control; + captchaGen: number; + setCaptchaState: (state: CaptchaParams) => void; +} + +const PhaseForgetPassword = ({ captchaGen, setCaptchaState, control }: PhaseForgetPasswordProps) => { + const { t } = useTranslation(); + const { forget_captcha } = useAppSelector((state) => state.siteConfig.login.config); + + return ( + <> + {forget_captcha && ( + + + + )} + {control?.submit} + {control?.back} + + ); +}; + +export default PhaseForgetPassword; diff --git a/src/component/Pages/Login/Phases/PhaseSignupNeeded.tsx b/src/component/Pages/Login/Phases/PhaseSignupNeeded.tsx new file mode 100755 index 0000000..fbca7ed --- /dev/null +++ b/src/component/Pages/Login/Phases/PhaseSignupNeeded.tsx @@ -0,0 +1,26 @@ +import { useTranslation } from "react-i18next"; +import { Alert, Stack, Typography } from "@mui/material"; +import { useAppSelector } from "../../../../redux/hooks.ts"; +import { Control } from "../Signin/SignIn.tsx"; + +interface SignupNeededProps { + email: string; + control?: Control; +} + +const SignupNeeded = ({ email, control }: SignupNeededProps) => { + const { t } = useTranslation(); + const regEnabled = useAppSelector((state) => state.siteConfig.login.config.register_enabled); + return ( + <> + {regEnabled && {t("login.signupHint", { email: email })}} + {!regEnabled && {t("login.accountNotFoundHint", { email: email })}} + + {regEnabled && control?.submit} + + {control?.back} + + ); +}; + +export default SignupNeeded; diff --git a/src/component/Pages/Login/Reset.tsx b/src/component/Pages/Login/Reset.tsx new file mode 100755 index 0000000..a53c9ae --- /dev/null +++ b/src/component/Pages/Login/Reset.tsx @@ -0,0 +1,133 @@ +import { LoadingButton } from "@mui/lab"; +import { Box, FormControl, Link, Typography } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useRef, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Link as RouterLink, useNavigate } from "react-router-dom"; +import { sendReset } from "../../../api/api.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import PageTitle from "../../../router/PageTitle.tsx"; +import { useQuery } from "../../../util"; +import { OutlineIconTextField } from "../../Common/Form/OutlineIconTextField.tsx"; +import { DefaultCloseAction } from "../../Common/Snackbar/snackbar.tsx"; +import Password from "../../Icons/Password.tsx"; + +const Reset = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const { enqueueSnackbar } = useSnackbar(); + const dispatch = useAppDispatch(); + const query = useQuery(); + + const formRef = useRef(null); + const [password, setPassword] = useState(""); + const [passwordRepeat, setPasswordRepeat] = useState(""); + const [loading, setLoading] = useState(false); + + const submit = () => { + if (!formRef.current) { + return; + } + + if (!formRef.current.checkValidity()) { + formRef.current.reportValidity(); + return; + } + + if (passwordRepeat != password) { + enqueueSnackbar({ + message: t("login.passwordNotMatch"), + variant: "warning", + action: DefaultCloseAction, + }); + return; + } + + setLoading(true); + dispatch( + sendReset(query.get("id") ?? "0", { + password, + secret: query.get("secret") ?? "", + }), + ) + .then((u) => { + navigate("/session?phase=email&email=" + encodeURIComponent(u?.email ?? "")); + enqueueSnackbar({ + message: t("login.passwordReset"), + variant: "success", + action: DefaultCloseAction, + }); + }) + .finally(() => { + setLoading(false); + }); + }; + + return ( + + + + {t("login.repeatPassword")} + + + + setPassword(e.target.value)} + icon={} + value={password} + autoComplete={"false"} + /> + + + setPasswordRepeat(e.target.value)} + icon={} + value={passwordRepeat} + autoComplete={"false"} + /> + + + {t("login.resetPassword")} + + + ]} + /> + + + + + + ); +}; + +export default Reset; diff --git a/src/component/Pages/Login/SessionIntro.tsx b/src/component/Pages/Login/SessionIntro.tsx new file mode 100755 index 0000000..2753c8b --- /dev/null +++ b/src/component/Pages/Login/SessionIntro.tsx @@ -0,0 +1,24 @@ +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { useEffect } from "react"; +import { ConfigLoadState } from "../../../redux/siteConfigSlice.ts"; +import { setHeadlessFrameLoading } from "../../../redux/globalStateSlice.ts"; +import { loadSiteConfig } from "../../../redux/thunks/site.ts"; +import { Outlet } from "react-router-dom"; + +const SessionIntro = () => { + const dispatch = useAppDispatch(); + const loginConfigLoading = useAppSelector((state) => state.siteConfig.login.loaded); + useEffect(() => { + dispatch(loadSiteConfig("login")); + }, []); + useEffect(() => { + if (loginConfigLoading == ConfigLoadState.NotLoaded) { + dispatch(setHeadlessFrameLoading(true)); + } else { + dispatch(setHeadlessFrameLoading(false)); + } + }, [loginConfigLoading]); + return <>{loginConfigLoading != ConfigLoadState.NotLoaded && }; +}; + +export default SessionIntro; diff --git a/src/component/Pages/Login/SideTransition.css b/src/component/Pages/Login/SideTransition.css new file mode 100755 index 0000000..34e705d --- /dev/null +++ b/src/component/Pages/Login/SideTransition.css @@ -0,0 +1,23 @@ +.side-enter { + opacity: 0; + transform: translateX(-100%); +} +.side-enter-active { + opacity: 1; + transform: translateX(0%); +} +.side-exit { + opacity: 1; + transform: translateX(0%); +} +.side-exit-active { + opacity: 0; + transform: translateX(100%); +} +.side-enter-active, +.side-exit-active { + transition: + opacity 300ms, + transform 300ms; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} diff --git a/src/component/Pages/Login/Signin/PasskeyLoginButton.tsx b/src/component/Pages/Login/Signin/PasskeyLoginButton.tsx new file mode 100755 index 0000000..39bde48 --- /dev/null +++ b/src/component/Pages/Login/Signin/PasskeyLoginButton.tsx @@ -0,0 +1,117 @@ +import { LoadingButton } from "@mui/lab"; +import { ButtonProps } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { sendFinishPasskeyLogin, sendPreparePasskeyLogin } from "../../../../api/api.ts"; +import { setHeadlessFrameLoading } from "../../../../redux/globalStateSlice.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import { refreshUserSession } from "../../../../redux/thunks/session.ts"; +import { bufferEncode, urlBase64BufferDecode, useQuery } from "../../../../util"; +import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx"; +import Fingerprint from "../../../Icons/Fingerprint.tsx"; + +export interface PasskeyLoginButtonProps extends ButtonProps { + autoComplete?: boolean; +} + +export default function PasskeyLoginButton({ autoComplete, ...rest }: PasskeyLoginButtonProps) { + const { t } = useTranslation(); + const { enqueueSnackbar } = useSnackbar(); + const dispatch = useAppDispatch(); + const query = useQuery(); + + const [loading, setLoading] = useState(false); + const abortRef = useRef(new AbortController()); + + useEffect(() => { + abortRef.current = new AbortController(); + if ( + autoComplete && + navigator.credentials && + window.PublicKeyCredential && + PublicKeyCredential.isConditionalMediationAvailable + ) { + PublicKeyCredential.isConditionalMediationAvailable().then((v) => { + if (!abortRef.current.signal.aborted) startLogin(true)(); + }); + } + return () => { + abortRef.current?.abort(); + }; + }, []); + + const startLogin = (conditional: boolean) => async () => { + if (!navigator.credentials || !window.PublicKeyCredential) { + enqueueSnackbar({ + message: t("setting.browserNotSupported"), + variant: "warning", + action: DefaultCloseAction, + }); + + return; + } + + if (!conditional) { + abortRef.current.abort(); + } + + setLoading(!conditional); + try { + const opts = await dispatch(sendPreparePasskeyLogin()); + const credential = await navigator.credentials.get({ + publicKey: { + // url decode base64 + challenge: urlBase64BufferDecode(opts.options.publicKey.challenge), + timeout: opts.options.publicKey.timeout, + rpId: opts.options.publicKey.rpId, + }, + ...(conditional ? { mediation: "conditional", signal: abortRef.current.signal } : {}), + }); + if (credential) { + dispatch(setHeadlessFrameLoading(true)); + const c = credential as PublicKeyCredential; + const response = await dispatch( + sendFinishPasskeyLogin({ + session_id: opts.session_id, + response: JSON.stringify({ + id: credential.id, + type: credential.type, + rawId: bufferEncode(c.rawId), + response: { + // @ts-ignore + attestationObject: bufferEncode(c.response.attestationObject), + clientDataJSON: bufferEncode(c.response.clientDataJSON), + // @ts-ignore + signature: bufferEncode(c.response.signature), + // @ts-ignore + userHandle: bufferEncode(c.response.userHandle), + // @ts-ignore + authenticatorData: bufferEncode(c.response.authenticatorData), + }, + }), + }), + ); + dispatch(refreshUserSession(response, query.get("redirect"))); + } + } catch (e) { + console.log(e); + } finally { + !conditional && setLoading(false); + dispatch(setHeadlessFrameLoading(false)); + } + }; + + return ( + } + fullWidth + {...rest} + > + {t("login.useFIDO2")} + + ); +} diff --git a/src/component/Pages/Login/Signin/SignIn.tsx b/src/component/Pages/Login/Signin/SignIn.tsx new file mode 100755 index 0000000..83334fd --- /dev/null +++ b/src/component/Pages/Login/Signin/SignIn.tsx @@ -0,0 +1,300 @@ +import { ArrowBackIos } from "@mui/icons-material"; +import { LoadingButton } from "@mui/lab"; +import { Box, Button, Typography } from "@mui/material"; +import { enqueueSnackbar } from "notistack"; +import { FormEvent, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { send2FALogin, sendLogin, sendPrepareLogin, sendResetEmail } from "../../../../api/api.ts"; +import { AppError, Code } from "../../../../api/request.ts"; +import { PrepareLoginResponse } from "../../../../api/user.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import { refreshUserSession } from "../../../../redux/thunks/session.ts"; +import PageTitle from "../../../../router/PageTitle.tsx"; +import { useQuery } from "../../../../util"; +import { CaptchaParams } from "../../../Common/Captcha/Captcha.tsx"; +import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx"; +import Phase2FA from "../Phases/Phase2FA.tsx"; +import PhaseCollectEmail from "../Phases/PhaseCollectEmail.tsx"; +import PhaseCollectPassword from "../Phases/PhaseCollectPassword.tsx"; +import PhaseForgetPassword from "../Phases/PhaseForgetPassword.tsx"; +import PhaseSignupNeeded from "../Phases/PhaseSignupNeeded.tsx"; +import "../SideTransition.css"; + +enum EmailLoginPhase { + CollectEmail, + CollectPassword, + SignupNeeded, + Collect2FA, + ForgetPassword, +} + +export interface Control { + submit: JSX.Element; + back: JSX.Element; +} + +interface phaseSetting { + title: string; + nextButtonText: string; + showBackButton: boolean; + previous?: EmailLoginPhase; + control?: Control; +} + +const EmailLogin = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + const query = useQuery(); + + const [phase, setPhase] = useState(EmailLoginPhase.CollectEmail); + const [email, setEmail] = useState(""); + const [pwd, setPwd] = useState(""); + const [otp, setOTP] = useState(""); + const [captchaGen, setCaptchaGen] = useState(0); + const [loading, setLoading] = useState(false); + const captchaState = useRef(); + const twoFaSession = useRef(""); + const [direction, setDirection] = useState<"left" | "right" | "up" | "down">("right"); + const [loginOptions, setLoginOptions] = useState(); + + const prepareLogin = useCallback(async () => { + try { + setLoading(true); + const opts = await dispatch(sendPrepareLogin(email)); + setLoginOptions(opts); + setPhase(EmailLoginPhase.CollectPassword); + } catch (e) { + if (e instanceof AppError && e.code === Code.NodeFound) { + // User not registered yet, guided to register + setPhase(EmailLoginPhase.SignupNeeded); + } + } finally { + setLoading(false); + } + }, [dispatch, email, setPhase]); + + const passwordLogin = useCallback( + async (email: string, password: string, captcha?: CaptchaParams) => { + try { + setLoading(true); + const loginRes = await dispatch(sendLogin({ email, password, ...captcha })); + dispatch(refreshUserSession(loginRes, query.get("redirect"))); + } catch (e) { + if (e instanceof AppError && e.code === Code.Continue) { + twoFaSession.current = e.response.data; + // User not registered yet, guided to register + setPhase(EmailLoginPhase.Collect2FA); + } else { + setCaptchaGen((g) => g + 1); + } + } finally { + setLoading(false); + } + }, + [dispatch, setPhase, setCaptchaGen, setLoading], + ); + + const finish2FA = useCallback(async (otp: string, ticket: string) => { + try { + setLoading(true); + const loginRes = await dispatch(send2FALogin({ otp, session_id: ticket })); + dispatch(refreshUserSession(loginRes, query.get("redirect"))); + } finally { + setOTP(""); + setLoading(false); + } + }, []); + + const submitSendResetEmail = useCallback( + async (email: string, captcha?: CaptchaParams) => { + try { + setLoading(true); + await dispatch(sendResetEmail({ email, ...captcha })); + setPhase(EmailLoginPhase.CollectEmail); + enqueueSnackbar({ + message: t("login.resetEmailSent"), + variant: "success", + action: DefaultCloseAction, + }); + } catch (e) { + setCaptchaGen((g) => g + 1); + } finally { + setLoading(false); + } + }, + [dispatch, setPhase, setCaptchaGen, setLoading], + ); + + const submit = (e?: FormEvent) => { + e?.preventDefault(); + switch (phase) { + case EmailLoginPhase.CollectEmail: + prepareLogin(); + break; + case EmailLoginPhase.SignupNeeded: + navigate(`/session/signup?email=${email}`); + break; + case EmailLoginPhase.CollectPassword: + passwordLogin(email, pwd, captchaState.current); + break; + case EmailLoginPhase.Collect2FA: + finish2FA(otp, twoFaSession.current); + break; + case EmailLoginPhase.ForgetPassword: + submitSendResetEmail(email, captchaState.current); + break; + } + }; + + useEffect(() => { + if (otp.length === 6) { + submit(); + } + }, [otp]); + + const phaseConfig = useMemo((): phaseSetting => { + var phaseSetting: phaseSetting = { + title: t("login.siginToYourAccount"), + nextButtonText: t("login.continue"), + showBackButton: false, + }; + switch (phase) { + case EmailLoginPhase.CollectEmail: + phaseSetting = { + title: t("login.siginToYourAccount"), + nextButtonText: t("login.continue"), + showBackButton: false, + }; + break; + case EmailLoginPhase.SignupNeeded: + phaseSetting = { + title: t("login.siginToYourAccount"), + nextButtonText: t("login.signUpAccount"), + showBackButton: true, + previous: EmailLoginPhase.CollectEmail, + }; + break; + case EmailLoginPhase.CollectPassword: + phaseSetting = { + title: t("login.enterPassword"), + nextButtonText: t("login.signIn"), + showBackButton: true, + previous: EmailLoginPhase.CollectEmail, + }; + break; + case EmailLoginPhase.Collect2FA: + phaseSetting = { + title: t("login.2FA"), + nextButtonText: t("login.signIn"), + showBackButton: true, + previous: EmailLoginPhase.CollectPassword, + }; + break; + case EmailLoginPhase.ForgetPassword: + phaseSetting = { + title: t("login.resetPassword"), + nextButtonText: t("login.sendMeAnEmail"), + showBackButton: true, + previous: EmailLoginPhase.CollectPassword, + }; + break; + default: + break; + } + phaseSetting.control = { + submit: ( + + {phaseSetting.nextButtonText} + + ), + back: ( + <> + {phaseSetting.showBackButton && ( + + )} + + ), + }; + return phaseSetting; + }, [phase, t, loading]); + + return ( + + {phaseConfig.title} + + + node.addEventListener("transitionend", done, false)} + classNames="side" + key={phase} + > + + {phase === EmailLoginPhase.CollectPassword && ( + setPhase(EmailLoginPhase.ForgetPassword)} + setPwd={setPwd} + control={phaseConfig.control} + loginOptions={loginOptions} + captchaGen={captchaGen} + setCaptchaState={(s) => (captchaState.current = s)} + /> + )} + {phase === EmailLoginPhase.SignupNeeded && ( + + )} + {phase === EmailLoginPhase.Collect2FA && ( + setOTP(t)} + control={phaseConfig.control} + /> + )} + {phase === EmailLoginPhase.CollectEmail && ( + + )} + {phase === EmailLoginPhase.ForgetPassword && ( + (captchaState.current = s)} + control={phaseConfig.control} + /> + )} + + + + + + ); +}; + +const SignIn = () => { + const { t } = useTranslation(); + + return ( + + + + + ); +}; + +export default SignIn; diff --git a/src/component/Pages/Login/Signup.tsx b/src/component/Pages/Login/Signup.tsx new file mode 100755 index 0000000..a110b99 --- /dev/null +++ b/src/component/Pages/Login/Signup.tsx @@ -0,0 +1,253 @@ +import { LoadingButton } from "@mui/lab"; +import { Box, Divider, FormControl, Link, Typography } from "@mui/material"; +import i18next from "i18next"; +import { useSnackbar } from "notistack"; +import { useEffect, useRef, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { Link as RouterLink, useNavigate } from "react-router-dom"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { sendSinUp } from "../../../api/api.ts"; +import { AppError, Code } from "../../../api/request.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import PageTitle from "../../../router/PageTitle.tsx"; +import { useQuery } from "../../../util"; +import { Captcha, CaptchaParams } from "../../Common/Captcha/Captcha.tsx"; +import { OutlineIconTextField } from "../../Common/Form/OutlineIconTextField.tsx"; +import { DefaultCloseAction } from "../../Common/Snackbar/snackbar.tsx"; +import EmailClock from "../../Icons/EmailClock.tsx"; +import MailOutlined from "../../Icons/MailOutlined.tsx"; +import Password from "../../Icons/Password.tsx"; +import { LegalLinks } from "./Phases/PhaseCollectEmail.tsx"; + +export enum SignUpPhase { + Main, + EmailActivation, +} + +interface signUpPhaseProps { + title: string; +} + +const signUpPhaseSettings: Record = { + [SignUpPhase.Main]: { + title: "login.createNewAccount", + }, + [SignUpPhase.EmailActivation]: { + title: "login.lastStep", + }, +}; + +const SignUp = () => { + const { t } = useTranslation(); + const { reg_captcha } = useAppSelector((state) => state.siteConfig.login.config); + const tos = useAppSelector((state) => state.siteConfig.login.config.tos_url); + const privacyPolicy = useAppSelector((state) => state.siteConfig.login.config.privacy_policy_url); + const navigate = useNavigate(); + const { enqueueSnackbar } = useSnackbar(); + const dispatch = useAppDispatch(); + const query = useQuery(); + + const [phase, setPhase] = useState(SignUpPhase.Main); + const formRef = useRef(null); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [passwordRepeat, setPasswordRepeat] = useState(""); + const [captchaGen, setCaptchaGen] = useState(0); + const captchaState = useRef(); + const [loading, setLoading] = useState(false); + + const showFooter = tos || privacyPolicy; + + useEffect(() => { + if (!!query.get("email")) { + setEmail(query.get("email") ?? ""); + } + }, []); + + const submit = () => { + if (!formRef.current) { + return; + } + + if (!formRef.current.checkValidity()) { + formRef.current.reportValidity(); + return; + } + + if (passwordRepeat != password) { + enqueueSnackbar({ + message: t("login.passwordNotMatch"), + variant: "warning", + action: DefaultCloseAction, + }); + return; + } + + setLoading(true); + dispatch( + sendSinUp({ + email, + password, + language: i18next.language, + ...captchaState.current, + }), + ) + .then(() => { + navigate("/session?phase=email&email=" + encodeURIComponent(email)); + enqueueSnackbar({ + message: t("login.signUpSuccess"), + variant: "success", + action: DefaultCloseAction, + }); + }) + .catch((e) => { + if (e instanceof AppError && e.code === Code.Continue) { + setPhase(SignUpPhase.EmailActivation); + } else { + setCaptchaGen((g) => g + 1); + } + }) + .finally(() => { + setLoading(false); + }); + }; + + return ( + + + + {t(signUpPhaseSettings[phase].title)} + + + node.addEventListener("transitionend", done, false)} + classNames="side" + key={phase} + > + + {phase === SignUpPhase.EmailActivation && ( + + t.palette.action.disabled, + }} + /> + + {t("application:login.activateDescription")} + + + )} + {phase === SignUpPhase.Main && ( + + + setEmail(e.target.value)} + icon={} + value={email} + autoFocus={true} + /> + + + setPassword(e.target.value)} + icon={} + value={password} + autoComplete={"false"} + /> + + + setPasswordRepeat(e.target.value)} + icon={} + value={passwordRepeat} + autoComplete={"false"} + /> + + {reg_captcha && ( + + (captchaState.current = s)} + /> + + )} + + {t("login.signUp")} + + + ]} + /> + + {showFooter && ( + <> + + + + )} + + )} + + + + + + + ); +}; + +export default SignUp; diff --git a/src/component/Pages/NoMatch.tsx b/src/component/Pages/NoMatch.tsx new file mode 100755 index 0000000..0467694 --- /dev/null +++ b/src/component/Pages/NoMatch.tsx @@ -0,0 +1,46 @@ +import { LoadingButton } from "@mui/lab"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { Box, Typography } from "@mui/material"; +import React from "react"; +import DismissCircleFilled from "../Icons/DismissCircleFilled.tsx"; + +const NoMatch = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + + return ( + + + theme.palette.action.active, + mt: 1, + }} + > + {t("common:pageNotFound")} + + navigate("/")} + fullWidth + variant="contained" + color="primary" + > + {t("application:navbar.backToHomepage")} + + + ); +}; + +export default NoMatch; diff --git a/src/component/Pages/PageContainer.tsx b/src/component/Pages/PageContainer.tsx new file mode 100755 index 0000000..c3bb492 --- /dev/null +++ b/src/component/Pages/PageContainer.tsx @@ -0,0 +1,22 @@ +import { BoxProps, useMediaQuery, useTheme } from "@mui/material"; +import { RadiusFrame } from "../Frame/RadiusFrame.tsx"; + +const PageContainer = (props: BoxProps) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + return ( + + ); +}; + +export default PageContainer; diff --git a/src/component/Pages/PageHeader.tsx b/src/component/Pages/PageHeader.tsx new file mode 100755 index 0000000..2c7a3a3 --- /dev/null +++ b/src/component/Pages/PageHeader.tsx @@ -0,0 +1,39 @@ +import { Box, IconButton, Tooltip, Typography } from "@mui/material"; +import ArrowClockwiseFilled from "../Icons/ArrowClockwiseFilled.tsx"; +import { useTranslation } from "react-i18next"; +import PageTitle from "../../router/PageTitle.tsx"; + +export const PageTabQuery = "tab"; + +export interface PageHeaderProps { + title: string; + loading?: boolean; + onRefresh?: () => void; + skipChangingDocumentTitle?: boolean; + secondaryAction?: React.ReactNode; +} + +const PageHeader = ({ title, secondaryAction, onRefresh, loading, skipChangingDocumentTitle }: PageHeaderProps) => { + const { t } = useTranslation(); + return ( + + + + {title} + + {!skipChangingDocumentTitle && } + {onRefresh && ( + + + + + + )} + + {secondaryAction && secondaryAction} + + + ); +}; + +export default PageHeader; diff --git a/src/component/Pages/Pages.tsx b/src/component/Pages/Pages.tsx new file mode 100755 index 0000000..4de121a --- /dev/null +++ b/src/component/Pages/Pages.tsx @@ -0,0 +1,8 @@ +import TaskList from "./Tasks/TaskList.tsx"; +import ShareList from "./Shares/ShareList.tsx"; +import DownloadList from "./Tasks/DownloadList.tsx"; +import Devices from "./Devices/Devices.tsx"; +import Setting from "./Setting/Setting.tsx"; +import Profile from "./Profile/Profile.tsx"; + +export { Setting, TaskList, ShareList, DownloadList, Devices, Profile }; diff --git a/src/component/Pages/Profile/Profile.tsx b/src/component/Pages/Profile/Profile.tsx new file mode 100755 index 0000000..bcf75bd --- /dev/null +++ b/src/component/Pages/Profile/Profile.tsx @@ -0,0 +1,186 @@ +import * as React from "react"; +import { useCallback, useEffect, useState } from "react"; +import { Box, Container, FormControl, Grid, ListItemText, SelectChangeEvent } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import PageHeader from "../PageHeader.tsx"; +import { getUserShares } from "../../../api/api.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import Nothing from "../../Common/Nothing.tsx"; +import { setSelected } from "../../../redux/fileManagerSlice.ts"; +import { Share } from "../../../api/explorer.ts"; +import { DenseSelect } from "../../Common/StyledComponents.tsx"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx"; +import ShareCard from "../Shares/ShareCard.tsx"; +import { useParams } from "react-router-dom"; +import { ShareLinksInProfileLevel, User } from "../../../api/user.ts"; +import { loadUserInfo } from "../../../redux/thunks/session.ts"; +import { UserProfile } from "../../Common/User/UserPopover.tsx"; +import PageContainer from "../PageContainer.tsx"; + +const defaultPageSize = 50; + +const Profile = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const [nextPageToken, setNextPageToken] = useState(""); + const [shares, setShares] = useState([]); + const [loading, setLoading] = useState(false); + const [orderDirection, setOrderDirection] = useState("desc"); + const [user, setUser] = useState(); + + const { id } = useParams<{ id: string }>(); + + useEffect(() => { + if (!id) { + return; + } + dispatch(loadUserInfo(id)).then((u) => { + if (u) { + setUser(u); + } + }); + }, [id]); + + const loadNextPage = useCallback( + (originShares: Share[], token?: string, direction?: string) => () => { + setLoading(true); + dispatch( + getUserShares( + { + page_size: defaultPageSize, + order_direction: direction ?? orderDirection, + next_page_token: token, + }, + id ?? "", + ), + ) + .then((res) => { + setShares([...originShares, ...res.shares]); + if (res.pagination?.next_token) { + setNextPageToken(res.pagination.next_token); + } else { + setNextPageToken(undefined); + } + }) + .catch(() => { + setNextPageToken(undefined); + }) + .finally(() => { + setLoading(false); + }); + }, + [dispatch, orderDirection, setSelected], + ); + + const refresh = (direction?: string) => { + loadNextPage([], "", direction)(); + }; + + const onShareDeleted = useCallback( + (id: string) => { + setShares((shares) => shares.filter((share) => share.id !== id)); + }, + [setShares], + ); + + const onSelectChange = useCallback( + (e: SelectChangeEvent) => { + setOrderDirection(e.target.value as string); + refresh(e.target.value as string); + }, + [refresh, setOrderDirection], + ); + + return ( + + + + + theme.palette.mode == "light" ? "rgba(0, 0, 0, 0.06)" : "rgba(255, 255, 255, 0.09)", + borderRadius: (theme) => `${theme.shape.borderRadius}px`, + mb: 4, + p: 2, + }} + > + {user && } + + + + + + + {t("application:share.createdAtDesc")} + + + + + {t("application:share.createdAtAsc")} + + + + + ) + } + skipChangingDocumentTitle + onRefresh={ + user && user.share_links_in_profile !== ShareLinksInProfileLevel.hide_share ? () => refresh() : undefined + } + loading={loading} + title={t("application:share.somebodyShare", { + name: user?.nickname ?? "-", + })} + /> + + + {user && + user.share_links_in_profile !== ShareLinksInProfileLevel.hide_share && + shares.map((share) => )} + {nextPageToken != undefined && + user && + user?.share_links_in_profile !== ShareLinksInProfileLevel.hide_share && ( + <> + {[...Array(4)].map((_, i) => ( + + ))} + + )} + {user && user.share_links_in_profile === ShareLinksInProfileLevel.hide_share && ( + <> + + + + + )} + + + {nextPageToken == undefined && shares.length == 0 && ( + + + + )} + + + ); +}; + +export default Profile; diff --git a/src/component/Pages/Setting/AvatarCropperDialog.tsx b/src/component/Pages/Setting/AvatarCropperDialog.tsx new file mode 100755 index 0000000..980727e --- /dev/null +++ b/src/component/Pages/Setting/AvatarCropperDialog.tsx @@ -0,0 +1,263 @@ +import { DialogContent } from "@mui/material"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; +import { useTranslation } from "react-i18next"; +import { DependencyList, useEffect, useRef, useState } from "react"; +import { centerCrop, Crop, makeAspectCrop, PixelCrop, ReactCrop } from "react-image-crop"; +import "react-image-crop/dist/ReactCrop.css"; +import { useSnackbar } from "notistack"; +import { DefaultCloseAction } from "../../Common/Snackbar/snackbar.tsx"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { sendUploadAvatar } from "../../../api/api.ts"; + +export function useDebounceEffect(fn: () => void, waitTime: number, deps?: DependencyList) { + useEffect(() => { + const t = setTimeout(() => { + // @ts-ignore + fn.apply(undefined, deps); + }, waitTime); + + return () => { + clearTimeout(t); + }; + }, deps); +} + +const TO_RADIANS = Math.PI / 180; + +export async function canvasPreview( + image: HTMLImageElement, + canvas: HTMLCanvasElement, + crop: PixelCrop, + scale = 1, + rotate = 0, +) { + const ctx = canvas.getContext("2d"); + + if (!ctx) { + throw new Error("No 2d context"); + } + + const scaleX = image.naturalWidth / image.width; + const scaleY = image.naturalHeight / image.height; + // devicePixelRatio slightly increases sharpness on retina devices + // at the expense of slightly slower render times and needing to + // size the image back down if you want to download/upload and be + // true to the images natural size. + const pixelRatio = window.devicePixelRatio; + // const pixelRatio = 1 + + canvas.width = Math.floor(crop.width * scaleX * pixelRatio); + canvas.height = Math.floor(crop.height * scaleY * pixelRatio); + + ctx.scale(pixelRatio, pixelRatio); + ctx.imageSmoothingQuality = "high"; + + const cropX = crop.x * scaleX; + const cropY = crop.y * scaleY; + + const rotateRads = rotate * TO_RADIANS; + const centerX = image.naturalWidth / 2; + const centerY = image.naturalHeight / 2; + + ctx.save(); + + // 5) Move the crop origin to the canvas origin (0,0) + ctx.translate(-cropX, -cropY); + // 4) Move the origin to the center of the original position + ctx.translate(centerX, centerY); + // 3) Rotate around the origin + ctx.rotate(rotateRads); + // 2) Scale the image + ctx.scale(scale, scale); + // 1) Move the center of the image to the origin (0,0) + ctx.translate(-centerX, -centerY); + ctx.drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight, 0, 0, image.naturalWidth, image.naturalHeight); + + ctx.restore(); +} + +export interface AvatarCropperDialogProps { + open?: boolean; + onClose: () => void; + file?: File; + onAvatarUpdated: () => void; +} + +function centerAspectCrop(mediaWidth: number, mediaHeight: number, aspect: number) { + return centerCrop( + makeAspectCrop( + { + unit: "px", + width: mediaWidth * 0.9, + }, + aspect, + mediaWidth, + mediaHeight, + ), + mediaWidth, + mediaHeight, + ); +} + +const AvatarCropperDialog = ({ open, onClose, onAvatarUpdated, file }: AvatarCropperDialogProps) => { + const { t } = useTranslation(); + const { enqueueSnackbar } = useSnackbar(); + const dispatch = useAppDispatch(); + const [src, setSrc] = useState(null); + const [crop, setCrop] = useState(undefined); + const [completedCrop, setCompletedCrop] = useState(); + const imageRef = useRef(null); + const previewCanvasRef = useRef(null); + const blobUrlRef = useRef(""); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (open) { + setCrop(undefined); + setSrc(null); + if (file) { + const reader = new FileReader(); + reader.addEventListener("load", () => setSrc(reader.result as string)); + reader.readAsDataURL(file); + } + } + }, [open]); + + const onImageLoad = (e: React.SyntheticEvent) => { + const { width, height } = e.currentTarget; + setCrop(centerAspectCrop(width, height, 1)); + }; + + const renderImage = async (): Promise => { + const image = imageRef.current; + const previewCanvas = previewCanvasRef.current; + if (!image || !previewCanvas || !completedCrop) { + throw new Error("Crop canvas does not exist"); + } + + // This will size relative to the uploaded image + // size. If you want to size according to what they + // are looking at on screen, remove scaleX + scaleY + const scaleX = image.naturalWidth / image.width; + const scaleY = image.naturalHeight / image.height; + + const offscreen = new OffscreenCanvas(completedCrop.width * scaleX, completedCrop.height * scaleY); + const ctx = offscreen.getContext("2d"); + if (!ctx) { + throw new Error("No 2d context"); + } + + ctx.drawImage( + previewCanvas, + 0, + 0, + previewCanvas.width, + previewCanvas.height, + 0, + 0, + offscreen.width, + offscreen.height, + ); + // You might want { type: "image/jpeg", quality: <0 to 1> } to + // reduce image size + const imgType = "image/png"; + const blob = await offscreen.convertToBlob({ + type: imgType, + }); + + return blob; + }; + + const onSubmit = () => { + setLoading(true); + renderImage() + .then((blob) => { + dispatch(sendUploadAvatar(blob, "image/png")) + .then(() => { + enqueueSnackbar({ + message: t("application:setting.avatarUpdated", {}), + variant: "success", + action: DefaultCloseAction, + }); + onAvatarUpdated(); + onClose(); + }) + .finally(() => { + setLoading(false); + }); + }) + .catch((e) => { + enqueueSnackbar({ + message: e.message, + variant: "error", + action: DefaultCloseAction, + }); + setLoading(false); + }); + }; + + useDebounceEffect( + async () => { + if (completedCrop?.width && completedCrop?.height && imageRef.current && previewCanvasRef.current) { + console.log(completedCrop); + // We use canvasPreview as it's much faster than imgPreview. + canvasPreview(imageRef.current, previewCanvasRef.current, completedCrop, 1, 0); + } + }, + 100, + [completedCrop], + ); + + return ( + + + {src && ( + setCrop(percentCrop)} + minWidth={100} + aspect={1} + onComplete={(p) => setCompletedCrop(p)} + minHeight={100} + > + Avatar + + )} + {!!completedCrop && ( + + )} + + + ); +}; + +export default AvatarCropperDialog; diff --git a/src/component/Pages/Setting/AvatarSetting.tsx b/src/component/Pages/Setting/AvatarSetting.tsx new file mode 100755 index 0000000..f037046 --- /dev/null +++ b/src/component/Pages/Setting/AvatarSetting.tsx @@ -0,0 +1,115 @@ +import { User } from "../../../api/user.ts"; +import UserAvatar from "../../Common/User/UserAvatar.tsx"; +import SettingForm from "./SettingForm.tsx"; +import { useTranslation } from "react-i18next"; +import Edit from "../../Icons/Edit.tsx"; +import { SecondaryButton } from "../../Common/StyledComponents.tsx"; +import { ListItemIcon, ListItemText, Menu } from "@mui/material"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx"; +import React, { useState } from "react"; +import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import Upload from "../../Icons/Upload.tsx"; +import Fingerprint from "../../Icons/Fingerprint.tsx"; +import AvatarCropperDialog from "./AvatarCropperDialog.tsx"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { sendUploadAvatar } from "../../../api/api.ts"; +import { useSnackbar } from "notistack"; +import { DefaultCloseAction } from "../../Common/Snackbar/snackbar.tsx"; + +export interface AvatarSettingProps { + user: User; +} + +const AvatarSetting = ({ user }: AvatarSettingProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + const popupState = usePopupState({ + popupId: "avatarEdit", + variant: "popover", + }); + const fileInput = React.createRef(); + const { onClose } = bindMenu(popupState); + const [imageCropperOpen, setImageCropperOpen] = useState(false); + const [avtarFile, setAvtarFile] = useState(undefined); + const [avatarGeneration, setAvatarGeneration] = useState(1); + + const onUploadClicked = () => { + onClose(); + fileInput.current?.click(); + }; + + const onImageChange = () => { + const file = fileInput.current?.files?.[0]; + if (!file) { + return; + } + + fileInput.current.value = ""; + setAvtarFile(file); + setImageCropperOpen(true); + }; + + const onAvatarUpdated = () => { + setAvatarGeneration((a) => a + 1); + }; + + const setGravatar = () => { + dispatch(sendUploadAvatar()).then(() => { + enqueueSnackbar({ + message: t("application:setting.avatarUpdated", {}), + variant: "success", + action: DefaultCloseAction, + }); + onAvatarUpdated(); + }); + onClose(); + }; + + return ( + + + } {...bindTrigger(popupState)}> + {t("fileManager.edit")} + + setImageCropperOpen(false)} + open={imageCropperOpen} + /> + + + + + + + {t(`setting.uploadImage`)} + + + + + + {t(`setting.useGravatar`)} + + + + ); +}; + +export default AvatarSetting; diff --git a/src/component/Pages/Setting/PreferenceSetting.tsx b/src/component/Pages/Setting/PreferenceSetting.tsx new file mode 100755 index 0000000..4954508 --- /dev/null +++ b/src/component/Pages/Setting/PreferenceSetting.tsx @@ -0,0 +1,348 @@ +import { LoadingButton } from "@mui/lab"; +import { + Box, + Checkbox, + Chip, + Collapse, + FormControl, + FormHelperText, + ListItemText, + Stack, + styled, + ToggleButton, + ToggleButtonGroup, + Typography, + useMediaQuery, + useTheme, +} from "@mui/material"; +import i18next from "i18next"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { sendUpdateUserSetting } from "../../../api/api.ts"; +import { UserSettings as UserSettingsType } from "../../../api/user.ts"; +import { languages } from "../../../i18n.ts"; +import { setPreferredTheme } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { clearLocalCustomView } from "../../../redux/thunks/filemanager.ts"; +import { selectLanguage } from "../../../redux/thunks/settings.ts"; +import SessionManager, { UserSettings } from "../../../session"; +import { refreshTimeZone, timeZone } from "../../../util/datetime.ts"; +import { + DenseAutocomplete, + DenseFilledTextField, + DenseSelect, + SmallFormControlLabel, +} from "../../Common/StyledComponents.tsx"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx"; +import { ColorCircle, SelectorBox } from "../../FileManager/FileInfo/ColorCircle/CircleColorSelector.tsx"; +import { SwitchPopover } from "../../Frame/NavBar/DarkThemeSwitcher.tsx"; +import RectangleLandscapeSync from "../../Icons/RectangleLandscapeSync.tsx"; +import RectangleLandscapeSyncOff from "../../Icons/RectangleLandscapeSyncOff.tsx"; +import SettingForm from "./SettingForm.tsx"; + +export interface PreferenceSettingProps { + setting: UserSettingsType; + setSetting: (setting: UserSettingsType) => void; +} + +declare namespace Intl { + type Key = "calendar" | "collation" | "currency" | "numberingSystem" | "timeZone" | "unit"; + + function supportedValuesOf(input: Key): string[]; +} + +interface ThemeOption { + [key: string]: any; +} + +const OutlinedSettingBox = styled(Box)(({ theme }) => ({ + borderRadius: theme.shape.borderRadius, + padding: theme.spacing(1), + border: `1px solid ${theme.palette.mode === "light" ? "rgba(0, 0, 0, 0.23)" : "rgba(255, 255, 255, 0.23)"}`, +})); + +const PreferenceSetting = ({ setting, setSetting }: PreferenceSettingProps) => { + const { t } = useTranslation(); + const theme = useTheme(); + const dispatch = useAppDispatch(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + const { themes } = useAppSelector((s) => s.siteConfig.basic.config); + + const [timeZoneValue, setTimeZoneValue] = useState(timeZone); + const versionMaxRef = useRef(null); + const [loading, setLoading] = useState(false); + const preferredTheme = useAppSelector((state) => state.globalState.preferredTheme); + const defaultTheme = useAppSelector((state) => state.siteConfig.basic.config.default_theme); + const [versionRetentionEnabled, setVersionRetentionEnabled] = useState(setting.version_retention_enabled); + const [versionRetentionMax, setVersionRetentionMax] = useState(setting.version_retention_max); + const [versionRetentionExts, setVersionRetentionExts] = useState(setting.version_retention_ext); + const [showSaveButton, setShowSaveButton] = useState(false); + const [autoExpandTreeView, setAutoExpandTreeView] = useState( + SessionManager.getWithFallback(UserSettings.TreeViewAutoExpand), + ); + + const onRetentionCheckChange = (e: React.ChangeEvent) => { + setVersionRetentionEnabled(e.target.checked); + setShowSaveButton(true); + }; + + const onVersionRetentionMaxChange = (e: React.ChangeEvent) => { + setVersionRetentionMax(parseInt(e.target.value) ?? 0); + setShowSaveButton(true); + }; + + const newExtAdded = (_e: any, newValue: string[]) => { + // Remove start dots in each value if presented + setVersionRetentionExts(newValue.map((v) => v.replace(/^\./, ""))); + setShowSaveButton(true); + }; + + useEffect(() => { + setVersionRetentionEnabled(setting.version_retention_enabled); + setVersionRetentionMax(setting.version_retention_max); + setVersionRetentionExts(setting.version_retention_ext); + }, [setting]); + + const selectTimeZone = (value: string) => { + setTimeZoneValue(value); + SessionManager.set(UserSettings.TimeZone, value); + refreshTimeZone(); + }; + + const themeOptions: ThemeOption = useMemo(() => { + if (themes) { + return JSON.parse(themes); + } + + return {}; + }, [themes]); + + const applyTheme = (color: string) => { + dispatch(setPreferredTheme(color)); + dispatch( + sendUpdateUserSetting({ + preferred_theme: color, + }), + ); + }; + + const saveVersionRetention = () => { + setLoading(true); + dispatch( + sendUpdateUserSetting({ + version_retention_enabled: versionRetentionEnabled, + version_retention_max: versionRetentionMax, + version_retention_ext: versionRetentionExts, + }), + ) + .then(() => { + setShowSaveButton(false); + setSetting({ + ...setting, + version_retention_enabled: versionRetentionEnabled, + version_retention_max: versionRetentionMax, + version_retention_ext: versionRetentionExts, + }); + }) + .finally(() => { + setLoading(false); + }); + }; + + const onDisableViewSyncChange = (e: React.MouseEvent, enabled: boolean) => { + setSetting({ ...setting, disable_view_sync: !enabled }); + setLoading(true); + dispatch( + sendUpdateUserSetting({ + disable_view_sync: !enabled, + }), + ) + .then(() => { + const user = SessionManager.currentLoginOrNull(); + if (user?.user) { + SessionManager.updateUserIfExist({ + ...user?.user, + disable_view_sync: !enabled, + }); + } + clearLocalCustomView(); + setLoading(false); + }) + .finally(() => { + setLoading(false); + }); + }; + + const onAutoExpandTreeViewChange = (e: React.ChangeEvent) => { + setAutoExpandTreeView(e.target.checked); + SessionManager.set(UserSettings.TreeViewAutoExpand, e.target.checked); + }; + + return ( + + + + dispatch(selectLanguage(e.target.value as string))}> + {languages.map((l) => ( + + + {l.displayName} + + + ))} + + {t("setting.languageDes")} + + + + + selectTimeZone(e.target.value as string)}> + {Intl.supportedValuesOf("timeZone").map((v) => ( + + + {v} + + + ))} + + {t("setting.timezoneDes")} + + + + + + + + {Object.keys(themeOptions).map((color, index) => ( + applyTheme(color)} + selected={(preferredTheme && preferredTheme == color) || (!preferredTheme && defaultTheme == color)} + /> + ))} + + + + + + } + label={t("application:setting.enableVersionRetention")} + /> + + {t("application:setting.enableVersionRetentionDes")} + + + + + + + value.map((option: string, index: number) => { + const { key, ...tagProps } = getTagProps({ index }); + return ; + }) + } + renderInput={(params) => ( + + )} + /> + + + + + + + + {t("fileManager.save")} + + + + + + + + + + {t("setting.syncViewOn")} + + + + {t("setting.syncViewOff")} + + + {t("setting.syncViewDes")} + + + } + label={t("application:setting.autoExpandTreeView")} + /> + {t("setting.autoExpandTreeViewDes")} + + + ); +}; + +export default PreferenceSetting; diff --git a/src/component/Pages/Setting/ProfileSetting.tsx b/src/component/Pages/Setting/ProfileSetting.tsx new file mode 100755 index 0000000..62d646f --- /dev/null +++ b/src/component/Pages/Setting/ProfileSetting.tsx @@ -0,0 +1,165 @@ +import { LoadingButton } from "@mui/lab"; +import { Collapse, Grid2, Stack, Typography, useMediaQuery, useTheme, styled } from "@mui/material"; +import { bindPopover, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import { useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { sendUpdateUserSetting } from "../../../api/api.ts"; +import { UserSettings, ShareLinksInProfileLevel } from "../../../api/user.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import SessionManager from "../../../session"; +import { DefaultButton, DenseFilledTextField } from "../../Common/StyledComponents.tsx"; +import TimeBadge from "../../Common/TimeBadge.tsx"; +import CaretDown from "../../Icons/CaretDown.tsx"; +import AvatarSetting from "./AvatarSetting.tsx"; +import ProfileSettingPopover, { useProfileSettingSummary } from "./ProfileSettingPopover.tsx"; +import SettingForm from "./SettingForm.tsx"; + +export interface ProfileSettingProps { + setting: UserSettings; + setSetting: (setting: UserSettings) => void; +} + +const ProfileDropButton = styled(DefaultButton)(({ theme }) => ({ + color: theme.palette.text.secondary, + minWidth: 0, + minHeight: 0, + fontSize: theme.typography.body2.fontSize, + fontWeight: theme.typography.body2.fontWeight, + borderRadius: "4px", + padding: "0px 4px", + position: "relative", + left: "-4px", +})); + +const ProfileSetting = ({ setting, setSetting }: ProfileSettingProps) => { + const { t } = useTranslation(); + const theme = useTheme(); + const dispatch = useAppDispatch(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + const nickRef = useRef(null); + + const user = SessionManager.currentLoginOrNull(); + const [nick, setNick] = useState(user?.user.nickname); + const [nickLoading, setNickLoading] = useState(false); + const [profileSettingLoading, setProfileSettingLoading] = useState(false); + + const profileSettingPopup = usePopupState({ + variant: "popover", + popupId: "profileSetting", + }); + + const profileSettingSummary = useProfileSettingSummary(setting.share_links_in_profile); + + const onClick = () => { + // Validate input length + if (nickRef.current && nickRef.current.checkValidity()) { + setNickLoading(true); + dispatch(sendUpdateUserSetting({ nick })) + .then(() => { + if (user?.user) { + SessionManager.updateUserIfExist({ + ...user?.user, + nickname: nick ?? "", + }); + } + }) + .finally(() => { + setNickLoading(false); + }); + } else { + // Input is invalid, show validation errors + nickRef.current?.reportValidity(); + } + }; + + const onProfileSettingChange = (value: ShareLinksInProfileLevel) => { + setProfileSettingLoading(true); + dispatch(sendUpdateUserSetting({ share_links_in_profile: value })) + .then(() => { + setSetting({ + ...setting, + share_links_in_profile: value, + }); + profileSettingPopup.close(); + }) + .finally(() => { + setProfileSettingLoading(false); + }); + }; + + return ( + + + + {user && } + + + + + + + + setNick(e.target.value)} + value={nick} + required + inputProps={{ maxLength: 255 }} + helperText={t("setting.nickNameDes")} + inputRef={nickRef} + /> + + + {t("fileManager.save")} + + + + + + + {user?.user.id} + + + + + + + + + + + + + {user?.user.group?.name} + + + + + } + disabled={profileSettingLoading} + > + {profileSettingSummary} + + + + + + + + + ); +}; + +export default ProfileSetting; diff --git a/src/component/Pages/Setting/ProfileSettingPopover.tsx b/src/component/Pages/Setting/ProfileSettingPopover.tsx new file mode 100755 index 0000000..7f31542 --- /dev/null +++ b/src/component/Pages/Setting/ProfileSettingPopover.tsx @@ -0,0 +1,127 @@ +import { + Divider, + List, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, + Popover, + PopoverProps, + SvgIconProps, +} from "@mui/material"; +import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { ShareLinksInProfileLevel } from "../../../api/user.ts"; +import Eye from "../../Icons/Eye.tsx"; +import EyeOff from "../../Icons/EyeOff.tsx"; +import Globe from "../../Icons/Globe.tsx"; + +export interface ProfileSettingPopoverProps extends PopoverProps { + currentValue: ShareLinksInProfileLevel; + onValueChange?: (value: ShareLinksInProfileLevel) => void; + readOnly?: boolean; +} + +const profileSettingOptions: { + value: ShareLinksInProfileLevel; + label: string; + description: string; + icon?: ((props: SvgIconProps) => JSX.Element) | typeof Eye; +}[] = [ + { + value: ShareLinksInProfileLevel.public_share_only, + label: "application:setting.publicShareOnly", + description: "application:setting.publicShareOnlyDes", + icon: Eye, + }, + { + value: ShareLinksInProfileLevel.all_share, + label: "application:setting.allShare", + description: "application:setting.allShareDes", + icon: Globe, + }, + { + value: ShareLinksInProfileLevel.hide_share, + label: "application:setting.hideShare", + description: "application:setting.hideShareDes", + icon: EyeOff, + }, +]; + +export const useProfileSettingSummary = (value: ShareLinksInProfileLevel) => { + const { t } = useTranslation(); + + const summary = useMemo(() => { + const option = profileSettingOptions.find((opt) => opt.value === (value ?? "")); + return option ? t(option.label) : t("application:setting.publicShareOnly"); + }, [value, t]); + + return summary; +}; + +const ProfileSettingPopover = ({ currentValue, onValueChange, readOnly, ...rest }: ProfileSettingPopoverProps) => { + const { t } = useTranslation(); + + const handleChange = (value: ShareLinksInProfileLevel) => () => { + if (onValueChange && !readOnly) { + onValueChange(value); + } + }; + + return ( + + + {profileSettingOptions.map((option, index) => ( + <> + + + {option.icon && ( + + (currentValue === option.value ? theme.palette.primary.main : "inherit"), + }} + /> + + )} + + + + {index < profileSettingOptions.length - 1 && } + + ))} + + + + ); +}; + +export default ProfileSettingPopover; diff --git a/src/component/Pages/Setting/Security/Disable2FADialog.tsx b/src/component/Pages/Setting/Security/Disable2FADialog.tsx new file mode 100755 index 0000000..cb5d8d4 --- /dev/null +++ b/src/component/Pages/Setting/Security/Disable2FADialog.tsx @@ -0,0 +1,123 @@ +import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import { useTranslation } from "react-i18next"; +import { useSnackbar } from "notistack"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import React, { useEffect, useState } from "react"; +import { Box, DialogContent, FormControl, Stack, styled, Typography } from "@mui/material"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import AutoHeight from "../../../Common/AutoHeight.tsx"; +import FacebookCircularProgress from "../../../Common/CircularProgress.tsx"; +import { sendUpdateUserSetting } from "../../../../api/api.ts"; +import { MuiOtpInput } from "mui-one-time-password-input"; + +export interface Disable2FADialogProps { + open?: boolean; + onClose: () => void; + on2FADisabled: () => void; +} + +const MuiOtpInputStyled = styled(MuiOtpInput)` + display: flex; + gap: 8px; + max-width: 650px; + margin-inline: auto; +`; + +const Disable2FADialog = ({ open, onClose, on2FADisabled }: Disable2FADialogProps) => { + const { t } = useTranslation(); + const { enqueueSnackbar } = useSnackbar(); + const dispatch = useAppDispatch(); + + const [loading, setLoading] = useState(false); + const [code, setCode] = useState(""); + + useEffect(() => { + if (open) { + setLoading(false); + setCode(""); + } + }, [open]); + + useEffect(() => { + if (code.length === 6) { + setLoading(true); + dispatch( + sendUpdateUserSetting({ + two_fa_enabled: false, + two_fa_code: code, + }), + ) + .then(() => { + enqueueSnackbar({ + message: t("setting.settingSaved"), + variant: "success", + }); + on2FADisabled(); + onClose(); + }) + .catch(() => { + setLoading(false); + setCode(""); + }); + } + }, [code]); + + return ( + + + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${loading}`} + > + + {loading && ( + + + + )} + {!loading && ( + + {t("setting.inputCurrent2FACode")} + + + + + )} + + + + + + + ); +}; + +export default Disable2FADialog; diff --git a/src/component/Pages/Setting/Security/Enable2FADialog.tsx b/src/component/Pages/Setting/Security/Enable2FADialog.tsx new file mode 100755 index 0000000..f42192a --- /dev/null +++ b/src/component/Pages/Setting/Security/Enable2FADialog.tsx @@ -0,0 +1,156 @@ +import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; +import { useTranslation } from "react-i18next"; +import { useSnackbar } from "notistack"; +import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; +import React, { useEffect, useState } from "react"; +import { Box, DialogContent, FormControl, Stack, styled, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import AutoHeight from "../../../Common/AutoHeight.tsx"; +import FacebookCircularProgress from "../../../Common/CircularProgress.tsx"; +import { get2FAInitSecret, sendUpdateUserSetting } from "../../../../api/api.ts"; +import { QRCodeSVG } from "qrcode.react"; +import SessionManager from "../../../../session"; +import { MuiOtpInput } from "mui-one-time-password-input"; + +export interface Enable2FADialogProps { + open?: boolean; + onClose: () => void; + on2FAEnabled: () => void; +} + +const MuiOtpInputStyled = styled(MuiOtpInput)` + display: flex; + gap: 8px; + max-width: 650px; + margin-inline: auto; +`; + +const Enable2FADialog = ({ open, onClose, on2FAEnabled }: Enable2FADialogProps) => { + const { t } = useTranslation(); + const { enqueueSnackbar } = useSnackbar(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + const siteTitle = useAppSelector((state) => state.siteConfig.basic.config.title); + const user = SessionManager.currentLoginOrNull(); + + const [loading, setLoading] = useState(false); + const [code, setCode] = useState(""); + const [secret, setSecret] = useState(""); + + useEffect(() => { + if (open) { + setLoading(true); + setCode(""); + dispatch(get2FAInitSecret()) + .then((res) => { + setSecret(res); + }) + .finally(() => { + setLoading(false); + }); + } + }, [open]); + + useEffect(() => { + if (code.length === 6) { + setLoading(true); + dispatch( + sendUpdateUserSetting({ + two_fa_enabled: true, + two_fa_code: code, + }), + ) + .then(() => { + enqueueSnackbar({ + message: t("setting.settingSaved"), + variant: "success", + }); + on2FAEnabled(); + onClose(); + }) + .catch(() => { + setLoading(false); + setCode(""); + }); + } + }, [code]); + + return ( + + + + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${loading}`} + > + + {loading && ( + + + + )} + {!loading && ( + + + + + + + {t("setting.2faDescription")} + + + {t("setting.inputCurrent2FACode")} + + + + + + + )} + + + + + + + ); +}; + +export default Enable2FADialog; diff --git a/src/component/Pages/Setting/Security/PasskeyList.tsx b/src/component/Pages/Setting/Security/PasskeyList.tsx new file mode 100755 index 0000000..470e944 --- /dev/null +++ b/src/component/Pages/Setting/Security/PasskeyList.tsx @@ -0,0 +1,196 @@ +import { Box, IconButton, List, Typography, useMediaQuery, useTheme } from "@mui/material"; +import dayjs from "dayjs"; +import { useSnackbar } from "notistack"; +import { useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { + sendDeletePasskey, + sendFinishPasskeyRegistration, + sendPreparePasskeyRegistration, +} from "../../../../api/api.ts"; +import { Passkey, UserSettings } from "../../../../api/user.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import { confirmOperation } from "../../../../redux/thunks/dialog.ts"; +import { bufferEncode, urlBase64BufferDecode } from "../../../../util"; +import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx"; +import { NoWrapTypography, SecondaryLoadingButton } from "../../../Common/StyledComponents.tsx"; +import TimeBadge from "../../../Common/TimeBadge.tsx"; +import Dismiss from "../../../Icons/Dismiss.tsx"; +import Fingerprint from "../../../Icons/Fingerprint.tsx"; +import ShieldAdd from "../../../Icons/ShieldAdd.tsx"; +import SettingListItem from "../SettingListItem.tsx"; + +export interface PasskeyProps { + setting: UserSettings; + onPasskeyAdded: (passkey: Passkey) => void; + onPasskeyDeleted: (passkeyID: string) => void; +} + +const PasskeyList = (props: PasskeyProps) => { + const { t } = useTranslation(); + const { enqueueSnackbar } = useSnackbar(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + + const [loading, setLoading] = useState(false); + const [deleteLoading, setDeleteLoading] = useState(false); + const addPasskey = async () => { + if (!navigator.credentials || !window.PublicKeyCredential) { + enqueueSnackbar({ + message: t("setting.browserNotSupported"), + variant: "warning", + action: DefaultCloseAction, + }); + + return; + } + + setLoading(true); + try { + const opts = await dispatch(sendPreparePasskeyRegistration()); + const credential = await navigator.credentials.create({ + publicKey: { + rp: opts.publicKey.rp, + user: { + id: urlBase64BufferDecode(opts.publicKey.user.id), + name: opts.publicKey.user.name, + displayName: opts.publicKey.user.displayName, + }, + + // url decode base64 + challenge: urlBase64BufferDecode(opts.publicKey.challenge), + pubKeyCredParams: opts.publicKey.pubKeyCredParams, + timeout: opts.publicKey.timeout, + excludeCredentials: opts.publicKey.excludeCredentials?.map((c) => ({ + id: urlBase64BufferDecode(c.id), + type: c.type, + })), + authenticatorSelection: opts.publicKey.authenticatorSelection, + }, + }); + if (credential) { + const c = credential as PublicKeyCredential; + const newPasskey = await dispatch( + sendFinishPasskeyRegistration({ + response: JSON.stringify({ + id: credential.id, + type: credential.type, + rawId: bufferEncode(c.rawId), + response: { + // @ts-ignore + attestationObject: bufferEncode(c.response.attestationObject), + clientDataJSON: bufferEncode(c.response.clientDataJSON), + }, + }), + name: t("setting.passkeyName"), + ua: navigator.userAgent, + }), + ); + + props.onPasskeyAdded(newPasskey); + } + } catch (e) { + console.error(e); + } finally { + setLoading(false); + } + }; + + const deletePasskey = (passkeyID: string) => { + dispatch(confirmOperation(t("setting.removedAuthenticatorConfirm"))).then(() => { + setDeleteLoading(true); + dispatch(sendDeletePasskey(passkeyID)) + .then(() => { + props.onPasskeyDeleted(passkeyID); + }) + .finally(() => { + setDeleteLoading(false); + }); + }); + }; + return ( + + {!props.setting.passkeys?.length && ( + `${theme.shape.borderRadius}px`, + border: (theme) => + `1px solid ${theme.palette.mode === "light" ? "rgba(0, 0, 0, 0.23)" : "rgba(255, 255, 255, 0.23)"}`, + textAlign: "center", + p: 1, + py: 2, + }} + > + theme.palette.text.secondary, + }} + /> + + {t("setting.noAuthenticator")} + + + )} + + {props.setting.passkeys?.map((passkey) => ( + deletePasskey(passkey.id)} disabled={deleteLoading}> + + + } + settingTitle={{passkey.name}} + settingDescription={ + + ]} + /> + + passkey.used_at && dayjs().diff(dayjs(passkey.used_at), "day") < 7 + ? theme.palette.success.main + : theme.palette.text.secondary, + }} + > + {passkey.used_at ? ( + ]} + /> + ) : ( + t("setting.neverUsed") + )} + + + } + /> + ))} + + } + > + {t("setting.addNewAuthenticator")} + + + ); +}; + +export default PasskeyList; diff --git a/src/component/Pages/Setting/Security/SecuritySetting.tsx b/src/component/Pages/Setting/Security/SecuritySetting.tsx new file mode 100755 index 0000000..8a54572 --- /dev/null +++ b/src/component/Pages/Setting/Security/SecuritySetting.tsx @@ -0,0 +1,212 @@ +import { LoadingButton } from "@mui/lab"; +import { Box, Collapse, Stack, useMediaQuery, useTheme } from "@mui/material"; +import { useSnackbar } from "notistack"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { sendUpdateUserSetting } from "../../../../api/api.ts"; +import { Passkey, UserSettings } from "../../../../api/user.ts"; +import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; +import SessionManager from "../../../../session"; +import { DefaultCloseAction } from "../../../Common/Snackbar/snackbar.tsx"; +import { DenseFilledTextField, SecondaryButton, SquareChip } from "../../../Common/StyledComponents.tsx"; +import Edit from "../../../Icons/Edit.tsx"; +import Open from "../../../Icons/Open.tsx"; +import { ProfileSettingProps } from "../ProfileSetting.tsx"; +import SettingForm from "../SettingForm.tsx"; +import Disable2FADialog from "./Disable2FADialog.tsx"; +import Enable2FADialog from "./Enable2FADialog.tsx"; +import PasskeyList from "./PasskeyList.tsx"; + +export interface SecuritySettingProps { + setting: UserSettings; + setSetting: (setting: UserSettings) => void; +} + +const SecuritySetting = ({ setting, setSetting }: ProfileSettingProps) => { + const { t } = useTranslation(); + const theme = useTheme(); + const dispatch = useAppDispatch(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + const { enqueueSnackbar } = useSnackbar(); + const navigate = useNavigate(); + + const authEnabled = useAppSelector((s) => s.siteConfig.login.config.authn); + + const resetPwdFormRef = React.createRef(); + const [showResetPassword, setShowResetPassword] = useState(false); + const [resetLoading, setResetLoading] = useState(false); + const [currentPassword, setCurrentPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [repeatPassword, setRepeatPassword] = useState(""); + const [enable2FAOpen, setEnable2FAOpen] = useState(false); + const [disable2FAOpen, setDisable2FAOpen] = useState(false); + + const submitResetPassword = () => { + if (!resetPwdFormRef.current) { + return; + } + if (!resetPwdFormRef.current.checkValidity()) { + resetPwdFormRef.current.reportValidity(); + return; + } + + if (newPassword !== repeatPassword) { + enqueueSnackbar({ + variant: "warning", + message: t("login.passwordNotMatch"), + action: DefaultCloseAction, + }); + return; + } + + setResetLoading(true); + dispatch( + sendUpdateUserSetting({ + current_password: currentPassword, + new_password: newPassword, + }), + ) + .then(() => { + SessionManager.signOutCurrent(); + navigate("/session"); + setShowResetPassword(false); + enqueueSnackbar({ + variant: "success", + message: t("login.passwordReset"), + action: DefaultCloseAction, + }); + }) + .finally(() => { + setResetLoading(false); + }); + }; + + const on2FAChange = (enabled: boolean) => () => { + setSetting({ + ...setting, + two_fa_enabled: enabled, + }); + }; + + const onPasskeyAdded = (passkey: Passkey) => { + setSetting({ + ...setting, + passkeys: setting.passkeys ? [...setting.passkeys, passkey] : [passkey], + }); + }; + + const onPasskeyDeleted = (passkeyID: string) => { + setSetting({ + ...setting, + passkeys: setting.passkeys?.filter((p) => p.id !== passkeyID), + }); + }; + + return ( + + + + setShowResetPassword(true)} + variant={"contained"} + startIcon={} + > + {t("login.resetPassword")} + + + +
    + + setCurrentPassword(e.target.value)} + inputProps={{ + type: "password", + minLength: 4, + maxLength: 64, + }} + /> + setNewPassword(e.target.value)} + inputProps={{ + type: "password", + minLength: 6, + maxLength: 128, + }} + /> + setRepeatPassword(e.target.value)} + inputProps={{ + type: "password", + minLength: 6, + maxLength: 128, + }} + /> + + + {t("fileManager.save")} + + + +
    +
    +
    + + {t("setting.2fa")} + {setting.two_fa_enabled && ( + theme.typography.caption.fontSize, + }} + color={"success"} + size={"small"} + variant={"outlined"} + label={t("setting.enabled")} + /> + )} +
    + } + lgWidth={5} + > + (setting.two_fa_enabled ? setDisable2FAOpen(true) : setEnable2FAOpen(true))} + variant={"contained"} + startIcon={} + > + {t(`setting.${setting.two_fa_enabled ? "disable" : "enable"}2FA`)} + + + {authEnabled && ( + + + + )} + setEnable2FAOpen(false)} on2FAEnabled={on2FAChange(true)} /> + setDisable2FAOpen(false)} + on2FADisabled={on2FAChange(false)} + /> + + ); +}; + +export default SecuritySetting; diff --git a/src/component/Pages/Setting/Setting.tsx b/src/component/Pages/Setting/Setting.tsx new file mode 100755 index 0000000..d387073 --- /dev/null +++ b/src/component/Pages/Setting/Setting.tsx @@ -0,0 +1,120 @@ +import { PersonOutline } from "@mui/icons-material"; +import { Box, Container } from "@mui/material"; +import { useQueryState } from "nuqs"; +import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { CSSTransition, SwitchTransition } from "react-transition-group"; +import { getUserSettings } from "../../../api/api.ts"; +import { UserSettings } from "../../../api/user.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { loadSiteConfig } from "../../../redux/thunks/site.ts"; +import FacebookCircularProgress from "../../Common/CircularProgress.tsx"; +import ResponsiveTabs, { Tab } from "../../Common/ResponsiveTabs.tsx"; +import EditSetting from "../../Icons/EditSetting.tsx"; +import LockClosedOutlined from "../../Icons/LockClosedOutlined.tsx"; +import PageContainer from "../PageContainer.tsx"; +import PageHeader, { PageTabQuery } from "../PageHeader.tsx"; +import PreferenceSetting from "./PreferenceSetting.tsx"; +import ProfileSetting from "./ProfileSetting.tsx"; +import SecuritySetting from "./Security/SecuritySetting.tsx"; + +export enum SettingPageTab { + Profile = "profile", + Preference = "preference", + Security = "security", +} + +const Setting = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const [tab, setTab] = useQueryState(PageTabQuery); + const [loading, setLoading] = useState(true); + const [setting, setSetting] = useState(undefined); + + useEffect(() => { + setLoading(true); + dispatch(loadSiteConfig("login")); + dispatch(getUserSettings()) + .then((res) => { + setSetting(res); + }) + .finally(() => { + setLoading(false); + }); + }, []); + + const tabs: Tab[] = useMemo(() => { + const res = []; + res.push( + ...[ + { + label: t("application:setting.profile"), + value: SettingPageTab.Profile, + icon: , + }, + { + label: t("application:setting.preference"), + value: SettingPageTab.Preference, + icon: , + }, + { + label: t("application:setting.security"), + value: SettingPageTab.Security, + icon: , + }, + ], + ); + return res; + }, [t]); + + return ( + + + + setTab(newValue)} + tabs={tabs} + /> + + node.addEventListener("transitionend", done, false)} + classNames="fade" + key={`${loading}`} + > + + {loading && ( + + + + )} + {!loading && setting && ( + + {(!tab || tab == SettingPageTab.Profile) && ( + + )} + {tab == SettingPageTab.Preference && } + {tab == SettingPageTab.Security && } + + )} + + + + + + ); +}; + +export default Setting; diff --git a/src/component/Pages/Setting/SettingForm.tsx b/src/component/Pages/Setting/SettingForm.tsx new file mode 100755 index 0000000..29106fe --- /dev/null +++ b/src/component/Pages/Setting/SettingForm.tsx @@ -0,0 +1,94 @@ +import { Chip, Grid2, Typography, styled } from "@mui/material"; +import { useCallback, useEffect, useState } from "react"; +import ProDialog from "../../Admin/Common/ProDialog"; + +export const ProChip = styled(Chip)(({ theme }) => ({ + marginLeft: 8, + height: "20px", + fontSize: "12px", + background: `linear-gradient(45deg, ${theme.palette.primary.main} 30%, ${theme.palette.primary.light} 90%)`, + color: theme.palette.primary.contrastText, +})); + +export interface SettingFormProps { + title?: React.ReactNode; + children: React.ReactNode; + lgWidth?: number; + secondary?: React.ReactNode; + spacing?: number; + anchorId?: string; + noContainer?: boolean; + pro?: boolean; +} + +const SettingForm = ({ + title, + children, + lgWidth = 8, + secondary, + spacing, + noContainer, + anchorId, + pro, +}: SettingFormProps) => { + const [proOpen, setProOpen] = useState(false); + useEffect(() => { + if (anchorId && window.location.hash === `#${anchorId}`) { + const anchor = document.getElementById(`anchor-${anchorId}`); + if (anchor) { + anchor.scrollIntoView({ behavior: "smooth" }); + // clear hash, not query + window.history.replaceState({}, "", window.location.pathname + window.location.search); + } + } + }, [anchorId]); + + const handleProClick = useCallback( + (e: React.MouseEvent) => { + if (pro) { + e.stopPropagation(); + setProOpen(true); + } + }, + [pro], + ); + + const inner = ( + <> + + {title && ( + + {title} + {pro && } + + )} +
    {children}
    +
    + {secondary && secondary} + {pro && setProOpen(false)} />} + + ); + if (noContainer) { + return inner; + } + return ( + + {inner} + + ); +}; + +export default SettingForm; diff --git a/src/component/Pages/Setting/SettingListItem.tsx b/src/component/Pages/Setting/SettingListItem.tsx new file mode 100755 index 0000000..7326b79 --- /dev/null +++ b/src/component/Pages/Setting/SettingListItem.tsx @@ -0,0 +1,62 @@ +import { + Avatar, + ListItem, + ListItemAvatar, + ListItemProps, + ListItemSecondaryAction, + styled, + SvgIconProps, +} from "@mui/material"; +import { StyledListItemText } from "../../Common/StyledComponents.tsx"; +import * as React from "react"; + +const StyledListItem = styled(ListItem)(({ theme }) => ({ + borderRadius: theme.shape.borderRadius, + border: `1px solid ${theme.palette.mode === "light" ? "rgba(0, 0, 0, 0.23)" : "rgba(255, 255, 255, 0.23)"}`, + marginTop: theme.spacing(1), + paddingBottom: theme.spacing(0.5), + paddingTop: theme.spacing(0.5), +})); + +export interface SettingListItemProps extends ListItemProps { + icon: (props: SvgIconProps) => JSX.Element; + iconColor?: string; + settingTitle?: React.ReactNode; + settingDescription?: React.ReactNode; + settingAction?: React.ReactNode; +} + +const SettingListItem = ({ + icon, + iconColor, + settingTitle, + settingDescription, + settingAction, + ...rest +}: SettingListItemProps) => { + const Icon = icon; + return ( + + + + + + + + {settingAction && {settingAction}} + + ); +}; + +export default SettingListItem; diff --git a/src/component/Pages/Setting/StorageSetting.tsx b/src/component/Pages/Setting/StorageSetting.tsx new file mode 100755 index 0000000..505a4fd --- /dev/null +++ b/src/component/Pages/Setting/StorageSetting.tsx @@ -0,0 +1,128 @@ +import { Box, Stack, styled, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { Capacity, UserSettings } from "../../../api/user.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { updateUserCapacity } from "../../../redux/thunks/filemanager.ts"; +import { loadSiteConfig } from "../../../redux/thunks/site.ts"; +import { sizeToString } from "../../../util"; +import SettingForm from "./SettingForm.tsx"; + +export const StorageBar = styled(Box)(({ theme }) => ({ + height: "10px", + borderRadius: `${theme.shape.borderRadius}px`, + width: "100%", + backgroundColor: theme.palette.grey[theme.palette.mode === "light" ? 200 : 800], + overflow: "hidden", +})); + +export const StoragePart = styled(Box)(() => ({ + transition: "width .6s ease", + height: "100%", + fontSize: "12px", + lineHeight: "20px", + float: "left", +})); + +export const StorageBlock = styled(Box)(() => ({ + display: "inline-block", + width: "10px", + height: "10px", + borderRadius: "50%", + marginRight: "5px", +})); + +export interface StorageSettingProps { + setting: UserSettings; +} + +export const CapacityBar = ({ capacity, forceRow }: { capacity?: Capacity; forceRow?: boolean }) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")) || forceRow; + const { t } = useTranslation(); + const [storageBreakdown, setStorageBreakdown] = useState({ + used: 0, + base: 0, + }); + useEffect(() => { + let summary = { + used: 0, + base: 0, + }; + + if (!capacity) { + return; + } + + summary.used = capacity.used / capacity.total; + summary.base = 1; + setStorageBreakdown(summary); + }, [capacity]); + + return ( + <> + + theme.palette.warning.light, + width: `${storageBreakdown.used * 100}%`, + }} + /> + + + + theme.palette.warning.light, + }} + /> + {t("vas.used", { + size: sizeToString(capacity?.used ?? 0), + })} + + + theme.palette.grey[theme.palette.mode === "light" ? 200 : 800], + }} + /> + {t("vas.total", { + size: sizeToString(capacity?.total ?? 0), + })} + + + + ); +}; + +const StorageSetting = ({ setting }: StorageSettingProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + const navigate = useNavigate(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + const capacity = useAppSelector((state) => state.fileManager[0].capacity); + + const [storageBreakdown, setStorageBreakdown] = useState({ + used: 0, + base: 0, + }); + + useEffect(() => { + dispatch(updateUserCapacity(0)); + dispatch(loadSiteConfig("vas")); + }, []); + + return ( + + + + + + + + ); +}; + +export default StorageSetting; diff --git a/src/component/Pages/Shares/ShareCard.tsx b/src/component/Pages/Shares/ShareCard.tsx new file mode 100755 index 0000000..194d0ae --- /dev/null +++ b/src/component/Pages/Shares/ShareCard.tsx @@ -0,0 +1,265 @@ +import { + Box, + Chip, + Grid, + ListItemIcon, + ListItemText, + Menu, + MenuProps, + Skeleton, + Tooltip, + Typography, + useTheme, +} from "@mui/material"; +import { bindMenu, bindTrigger, usePopupState } from "material-ui-popup-state/hooks"; +import { useSnackbar } from "notistack"; +import { useCallback, useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { useInView } from "react-intersection-observer"; +import { useNavigate } from "react-router-dom"; +import { sendDeleteShare } from "../../../api/api.ts"; +import { FileType, Share } from "../../../api/explorer.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { confirmOperation } from "../../../redux/thunks/dialog.ts"; +import { openShareEditByID } from "../../../redux/thunks/share.ts"; +import SessionManager from "../../../session"; +import { copyToClipboard } from "../../../util/index.ts"; +import { DefaultCloseAction } from "../../Common/Snackbar/snackbar.tsx"; +import { NoWrapBox, NoWrapTypography } from "../../Common/StyledComponents.tsx"; +import TimeBadge from "../../Common/TimeBadge.tsx"; +import { DenseDivider, SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx"; +import FileTypeIcon from "../../FileManager/Explorer/FileTypeIcon.tsx"; +import Clipboard from "../../Icons/Clipboard.tsx"; +import DeleteOutlined from "../../Icons/DeleteOutlined.tsx"; +import Eye from "../../Icons/Eye.tsx"; +import LinkEdit from "../../Icons/LinkEdit.tsx"; +import LockClosedOutlined from "../../Icons/LockClosedOutlined.tsx"; +import Open from "../../Icons/Open.tsx"; +import { SummaryButton } from "../Tasks/TaskCard.tsx"; + +export interface ShareCardProps { + share?: Share; + onLoad?: () => void; + loading?: boolean; + onShareDeleted: (id: string) => void; +} + +interface ActionMenuProps extends MenuProps { + share: Share; + onShareDeleted: (id: string) => void; +} + +const ActionMenu = ({ share, onShareDeleted, onClose, ...rest }: ActionMenuProps) => { + const theme = useTheme(); + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + + const deleteShare = useCallback(() => { + dispatch(confirmOperation(t("fileManager.deleteShareWarning"))).then(() => { + dispatch(sendDeleteShare(share.id)).then(() => { + onClose && onClose({}, "backdropClick"); + enqueueSnackbar({ + message: t("application:share.shareCanceled"), + variant: "success", + action: DefaultCloseAction, + }); + onShareDeleted(share.id); + }); + }); + }, [t, share.id, onClose, dispatch, enqueueSnackbar]); + + const openEdit = useCallback(() => { + dispatch(openShareEditByID(share.id, share.password, share.source_type == FileType.file)); + onClose && onClose({}, "backdropClick"); + }, [dispatch, share, onClose]); + + const openLink = useCallback(() => { + window.open(share.url, "_blank"); + onClose && onClose({}, "backdropClick"); + }, [share, onClose]); + + const copyLink = useCallback(() => { + copyToClipboard(share.url); + onClose && onClose({}, "backdropClick"); + }, [share, onClose, t]); + + return ( + + + + + + {t(`fileManager.open`)} + + + + + + {t(`share.copyLinkToClipboard`)} + + {!share.expired && ( + + + + + {t(`fileManager.${share?.expired ? "editAndReactivate" : "edit"}`)} + + )} + + + + + + {t(`fileManager.delete`)} + + + ); +}; + +const ShareCard = ({ share, onShareDeleted, onLoad, loading }: ShareCardProps) => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const { ref, inView } = useInView({ + rootMargin: "200px 0px", + triggerOnce: true, + skip: !loading || !onLoad, + }); + + const popupState = usePopupState({ + popupId: "shareAction", + variant: "popover", + }); + + useEffect(() => { + if (!inView) { + return; + } + + if (onLoad) { + onLoad(); + } + }, [inView]); + + const user = SessionManager.currentLoginOrNull(); + + return ( + <> + {share && } + + { + e.preventDefault(); + if (share?.owner?.id === user?.user.id) { + popupState.open(e); + } + }} + sx={{ p: 0, minHeight: 0, width: "100%", textAlign: "left" }} + {...(share?.owner?.id != user?.user.id + ? { + onClick: () => { + window.open(share?.url ?? "#", "_blank"); + }, + } + : bindTrigger(popupState))} + > + + + {share ? ( + + ) : ( + + )} + + + + + {share && share?.password_protected && } + + + {loading ? : share?.name} + + + + {share?.expired && ( + + )} + + + + + {!share?.created_at ? ( + + ) : ( + + + + + {share?.visited ?? 0} + + + )} + + + + + + + + + ); +}; + +export default ShareCard; diff --git a/src/component/Pages/Shares/ShareList.tsx b/src/component/Pages/Shares/ShareList.tsx new file mode 100755 index 0000000..a9063db --- /dev/null +++ b/src/component/Pages/Shares/ShareList.tsx @@ -0,0 +1,134 @@ +import * as React from "react"; +import { useCallback, useState } from "react"; +import { Box, Container, FormControl, Grid, ListItemText, SelectChangeEvent } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import PageHeader from "../PageHeader.tsx"; +import { getShares } from "../../../api/api.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import Nothing from "../../Common/Nothing.tsx"; +import { setSelected } from "../../../redux/fileManagerSlice.ts"; +import ShareCard from "./ShareCard.tsx"; +import { Share } from "../../../api/explorer.ts"; +import { DenseSelect } from "../../Common/StyledComponents.tsx"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx"; +import PageContainer from "../PageContainer.tsx"; + +const defaultPageSize = 50; + +const ShareList = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const [nextPageToken, setNextPageToken] = useState(""); + const [shares, setShares] = useState([]); + const [loading, setLoading] = useState(false); + const [orderDirection, setOrderDirection] = useState("desc"); + + const loadNextPage = useCallback( + (originShares: Share[], token?: string, direction?: string) => () => { + setLoading(true); + dispatch( + getShares({ + page_size: defaultPageSize, + order_direction: direction ?? orderDirection, + next_page_token: token, + }), + ) + .then((res) => { + setShares([...originShares, ...res.shares]); + if (res.pagination?.next_token) { + setNextPageToken(res.pagination.next_token); + } else { + setNextPageToken(undefined); + } + }) + .catch(() => { + setNextPageToken(undefined); + }) + .finally(() => { + setLoading(false); + }); + }, + [dispatch, orderDirection, setSelected], + ); + + const refresh = (direction?: string) => { + loadNextPage([], "", direction)(); + }; + + const onShareDeleted = useCallback( + (id: string) => { + setShares((shares) => shares.filter((share) => share.id !== id)); + }, + [setShares], + ); + + const onSelectChange = useCallback( + (e: SelectChangeEvent) => { + setOrderDirection(e.target.value as string); + refresh(e.target.value as string); + }, + [refresh, setOrderDirection], + ); + + return ( + + + + + + + {t("application:share.createdAtDesc")} + + + + + {t("application:share.createdAtAsc")} + + + + + } + onRefresh={() => refresh()} + loading={loading} + title={t("application:navbar.myShare")} + /> + + + {shares.map((share) => ( + + ))} + {nextPageToken != undefined && ( + <> + {[...Array(4)].map((_, i) => ( + + ))} + + )} + + + {nextPageToken == undefined && shares.length == 0 && ( + + + + )} + + + ); +}; + +export default ShareList; diff --git a/src/component/Pages/Tasks/DownloadFileList.tsx b/src/component/Pages/Tasks/DownloadFileList.tsx new file mode 100755 index 0000000..c3bab09 --- /dev/null +++ b/src/component/Pages/Tasks/DownloadFileList.tsx @@ -0,0 +1,257 @@ +import { + Box, + Button, + Grow, + Stack, + Table, + TableCell, + TableContainer, + TableRow, + TextField, + Typography, + useTheme, +} from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { TableVirtuoso } from "react-virtuoso"; +import { sendCancelDownloadTask, sendSetDownloadTarget } from "../../../api/api.ts"; +import { FileType } from "../../../api/explorer.ts"; +import { DownloadTaskFile, TaskSummary } from "../../../api/workflow.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { confirmOperation } from "../../../redux/thunks/dialog.ts"; +import { fileBase, sizeToString } from "../../../util"; +import { StyledCheckbox, StyledTableContainerPaper } from "../../Common/StyledComponents.tsx"; +import FileIcon from "../../FileManager/Explorer/FileIcon.tsx"; +import Dismiss from "../../Icons/Dismiss.tsx"; +import { getProgressColor } from "./TaskCard.tsx"; + +export interface DownloadFileListProps { + taskId: string; + summary?: TaskSummary; + downloading?: boolean; + readonly?: boolean; +} + +const DownloadFileList = ({ taskId, summary, downloading, readonly }: DownloadFileListProps) => { + const [selectedMask, setSelectedMask] = useState<{ + [key: number]: boolean; + }>({}); + const [changeApplied, setChangeApplied] = useState(true); + const [loading, setLoading] = useState(false); + const [height, setHeight] = useState(33); + const [filterText, setFilterText] = useState(""); + const files = summary?.props?.download?.files; + const { t } = useTranslation(); + const theme = useTheme(); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + + const progressColor = useMemo(() => { + return getProgressColor(theme); + }, [theme]); + + const filteredFiles = useMemo(() => { + if (!files) { + return []; + } + if (!filterText) { + return files; + } + return files.filter((file) => file.name.toLowerCase().includes(filterText.toLowerCase())); + }, [files, filterText]); + + const setFileSelected = (value: DownloadTaskFile, selected: boolean) => { + setSelectedMask((prev) => { + return { + ...prev, + [value.index]: selected, + }; + }); + setChangeApplied(false); + }; + + const submitChanges = () => { + setLoading(true); + dispatch( + sendSetDownloadTarget(taskId, { + files: Object.keys(selectedMask).map((key) => ({ + index: parseInt(key), + download: selectedMask[parseInt(key)], + })), + }), + ) + .then(() => { + setChangeApplied(true); + enqueueSnackbar({ + message: t("download.operationSubmitted"), + variant: "success", + }); + }) + .finally(() => { + setLoading(false); + }); + }; + + const selectAll = () => { + const newMask = { ...selectedMask }; + filteredFiles.forEach((file) => { + newMask[file.index] = true; + }); + setSelectedMask(newMask); + setChangeApplied(false); + }; + + const reverseSelect = () => { + const newMask = { ...selectedMask }; + filteredFiles.forEach((file) => { + const currentSelection = newMask[file.index] ?? file.selected; + newMask[file.index] = !currentSelection; + }); + setSelectedMask(newMask); + setChangeApplied(false); + }; + + const cancelTask = () => { + dispatch(confirmOperation(t("download.cancelTaskConfirm"))).then(() => { + setLoading(true); + dispatch(sendCancelDownloadTask(taskId)) + .then(() => { + enqueueSnackbar({ + message: t("download.taskCanceled"), + variant: "success", + }); + }) + .finally(() => { + setLoading(false); + }); + }); + }; + + return ( + + {files && ( + <> + {!readonly && files.length > 20 && ( + + setFilterText(e.target.value)} + sx={{ flexGrow: 1 }} + /> + + + + )} + + { + setHeight(h + 0.5); + }} + components={{ + // eslint-disable-next-line react/display-name + Table: (props) => , + // eslint-disable-next-line react/display-name + TableRow: (props) => { + const index = props["data-index"]; + const percentage = (files[index]?.progress ?? 0) * 100; + const progressBgColor = theme.palette.background.default; + return ( + + ); + }, + }} + data={filteredFiles} + itemContent={(_index, value) => ( + <> + + { + setFileSelected(value, e.target.checked); + }} + disabled={!downloading || readonly} + disableRipple + checked={selectedMask[value.index] ?? value.selected} + size="small" + /> + + + + + {fileBase(value.name)} + + + + + {sizeToString(value.size)} + + + + + {((value.progress ?? 0) * 100).toFixed(2)} % + + + + )} + /> + {filteredFiles.length === 0 && ( + + {t("download.noFilesFound")} + + )} + + + )} + {downloading && !readonly && summary?.phase == "monitor" && ( + + + + + + + + + )} + + ); +}; + +export default DownloadFileList; diff --git a/src/component/Pages/Tasks/DownloadList.tsx b/src/component/Pages/Tasks/DownloadList.tsx new file mode 100755 index 0000000..a5ba2b8 --- /dev/null +++ b/src/component/Pages/Tasks/DownloadList.tsx @@ -0,0 +1,164 @@ +import * as React from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { ListTaskCategory, TaskResponse } from "../../../api/workflow.ts"; +import { Box, Container, FormControlLabel, FormGroup, Switch, Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import PageHeader from "../PageHeader.tsx"; +import { getTasks } from "../../../api/api.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import Nothing from "../../Common/Nothing.tsx"; +import TaskCard from "./TaskCard.tsx"; +import PageContainer from "../PageContainer.tsx"; + +const defaultPageSize = 25; +const autoRefreshInterval = 20 * 1000; + +const DownloadList = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + + const [nextPageToken, setNextPageToken] = useState(""); + const [tasks, setTasks] = useState([]); + const [downloadingTasks, setDownloadingTasks] = useState(undefined); + const [loading, setLoading] = useState(false); + const [autoRefresh, setAutoRefresh] = useState(true); + const interval = React.useRef(); + const downloadingListHash = useRef(""); + + useEffect(() => { + if (autoRefresh && !interval.current) { + interval.current = setInterval(() => { + refresh(); + }, autoRefreshInterval); + } else { + clearInterval(interval.current); + interval.current = undefined; + } + }, [autoRefresh]); + + useEffect(() => { + return () => { + clearInterval(interval.current); + interval.current = undefined; + }; + }, []); + + const loadNextPage = useCallback( + (originTasks: TaskResponse[], token?: string) => () => { + setLoading(true); + dispatch( + getTasks({ + page_size: defaultPageSize, + category: ListTaskCategory.downloaded, + next_page_token: token, + }), + ) + .then((res) => { + if (token) { + setAutoRefresh(false); + } + setTasks([...originTasks, ...res.tasks]); + if (res.pagination?.next_token) { + setNextPageToken(res.pagination.next_token); + } else { + setNextPageToken(undefined); + } + }) + .catch(() => { + setNextPageToken(undefined); + }) + .finally(() => { + setLoading(false); + }); + }, + [dispatch, setAutoRefresh, setTasks], + ); + + const loadDownloading = useCallback(() => { + setLoading(true); + dispatch( + getTasks({ + page_size: defaultPageSize, + category: ListTaskCategory.downloading, + }), + ) + .then((res) => { + setDownloadingTasks(res.tasks); + // New hash = id of first downloading task + id of last downloading task + length of downloading tasks + const newHash = `${res.tasks[0]?.id ?? ""}-${res.tasks[res.tasks.length - 1]?.id ?? ""}-${res.tasks.length}`; + + if (downloadingListHash.current != "" && downloadingListHash.current != newHash) { + loadNextPage([], "")(); + } + downloadingListHash.current = newHash; + }) + .catch(() => {}) + .finally(() => { + setLoading(false); + }); + }, [dispatch, setAutoRefresh, setDownloadingTasks]); + + const refresh = () => { + loadDownloading(); + }; + + const toggleAutoRefresh = (event: React.ChangeEvent) => { + if (event.target.checked && tasks.length > defaultPageSize) { + loadNextPage([], "")(); + } + setAutoRefresh(event.target.checked); + }; + + return ( + + + + } + label={ + + {t("setting.autoRefresh")} + + } + /> + + } + onRefresh={refresh} + loading={loading} + title={t("application:navbar.remoteDownload")} + /> + + {t("download.active")} + + {downloadingTasks != undefined && downloadingTasks.length == 0 && ( + + + + )} + {downloadingTasks == undefined && } + + {downloadingTasks && downloadingTasks.map((task) => )} + + {t("download.finished")} + + {tasks.map((task) => ( + + ))} + {nextPageToken != undefined && ( + + )} + + {nextPageToken == undefined && tasks.length == 0 && ( + + + + )} + + + ); +}; + +export default DownloadList; diff --git a/src/component/Pages/Tasks/PieceProgress.tsx b/src/component/Pages/Tasks/PieceProgress.tsx new file mode 100755 index 0000000..72a476e --- /dev/null +++ b/src/component/Pages/Tasks/PieceProgress.tsx @@ -0,0 +1,63 @@ +import { memo, useEffect, useRef } from "react"; +import { Box, useTheme } from "@mui/material"; + +export interface PieceProgressProps { + pieces: string; + total: number; +} + +const PieceProgress: React.FC = memo(({ pieces, total }) => { + const theme = useTheme(); + const canvasRef = useRef(null); + useEffect(() => { + if (!pieces || !canvasRef.current || !total) { + return; + } + + const canvas = canvasRef.current; + const context = canvas.getContext("2d"); + if (!context) { + return; + } + + context.clearRect(0, 0, canvas.width, canvas.height); + context.strokeStyle = theme.palette.primary.light; + + const bits = Uint8Array.from(atob(pieces), (c) => c.charCodeAt(0)); + for (let i = 0; i < canvas.width; i++) { + let bitIndex = Math.floor(((i + 1) / canvas.width) * total); + // Read bool from unit8 array + let bit = bits[Math.floor(bitIndex / 8)] & (1 << bitIndex % 8); + if (bit) { + context.beginPath(); + context.moveTo(i, 0); + context.lineTo(i, canvas.height); + context.stroke(); + } + } + }, [pieces, theme, total]); + return ( + theme.palette.action.hover, + }} + > + + + ); +}); + +export default PieceProgress; diff --git a/src/component/Pages/Tasks/StepProgressBar.tsx b/src/component/Pages/Tasks/StepProgressBar.tsx new file mode 100755 index 0000000..e34a1da --- /dev/null +++ b/src/component/Pages/Tasks/StepProgressBar.tsx @@ -0,0 +1,35 @@ +import { Box, Skeleton, Typography } from "@mui/material"; +import BorderLinearProgress from "../../Common/BorderLinearProgress.tsx"; + +export interface StepProgressBarProps { + title?: string; + secondary?: string; + caption?: string; + progress?: number; + loading?: boolean; + indeterminate?: boolean; +} + +const StepProgressBar = ({ title, secondary, progress, loading, indeterminate }: StepProgressBarProps) => { + return ( + + + + {loading ? : title} + + {secondary && ( + + {secondary} + + )} + + {loading ? ( + + ) : ( + + )} + + ); +}; + +export default StepProgressBar; diff --git a/src/component/Pages/Tasks/StepProgressPopover.tsx b/src/component/Pages/Tasks/StepProgressPopover.tsx new file mode 100755 index 0000000..5359a17 --- /dev/null +++ b/src/component/Pages/Tasks/StepProgressPopover.tsx @@ -0,0 +1,217 @@ +import { Box, Divider, PopoverProps, Typography } from "@mui/material"; +import HoverPopover from "material-ui-popup-state/HoverPopover"; +import { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getTasksPhaseProgress } from "../../../api/api.ts"; +import { TaskProgress, TaskProgresses } from "../../../api/workflow.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { sizeToString } from "../../../util"; +import StepProgressBar from "./StepProgressBar.tsx"; + +export interface StepProgressPopoverProps extends PopoverProps { + taskId: string; +} + +export const ProgressKeys = { + relocate: "relocate", + upload: "upload", + upload_single_: "upload_single_", + archive_count: "archive_count", + archive_size: "archive_size", + upload_count: "upload_count", + extract_count: "extract_count", + extract_size: "extract_size", + download: "download", + imported: "imported", + indexed: "indexed", +}; + +const ProgressBar = ({ pkey, p }: { pkey: string; p: TaskProgress }) => { + const { t } = useTranslation(); + if (pkey == ProgressKeys.relocate) { + return ( + + ); + } + + if (pkey == ProgressKeys.imported) { + return ( + + ); + } + + if (pkey == ProgressKeys.indexed) { + return ( + + ); + } + + if (pkey.startsWith(ProgressKeys.upload_single_)) { + return ( + + ); + } + + if (pkey == ProgressKeys.archive_count) { + let secondary = `${p.current}`; + if (p.total != 0) { + secondary += ` / ${p.total}`; + } + return ( + + ); + } + + if (pkey == ProgressKeys.archive_size) { + let secondary = sizeToString(p.current); + if (p.total != 0) { + secondary += ` / ${sizeToString(p.total)}`; + } + return ( + + ); + } + + if (pkey == ProgressKeys.upload) { + return ( + + ); + } + + if (pkey == ProgressKeys.extract_size) { + let secondary = sizeToString(p.current); + if (p.total != 0) { + secondary += ` / ${sizeToString(p.total)}`; + } + return ( + + ); + } + + if (pkey == ProgressKeys.extract_count) { + let secondary = `${p.current}`; + if (p.total != 0) { + secondary += ` / ${p.total}`; + } + return ( + + ); + } + + if (pkey == ProgressKeys.upload_count) { + let secondary = `${p.current}`; + if (p.total != 0) { + secondary += ` / ${p.total}`; + } + return ( + + ); + } + + if (pkey == ProgressKeys.download) { + return ( + + ); + } +}; + +const StepProgressPopover = ({ taskId, open, ...rest }: StepProgressPopoverProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const stopPropagation = useCallback((e: any) => e.stopPropagation(), []); + const [progress, setProgress] = useState(undefined); + + useEffect(() => { + if (open) { + dispatch(getTasksPhaseProgress(taskId)).then((res) => setProgress(res)); + } + }, [open, taskId]); + + return ( + + + {!progress && } + {progress && + Object.keys(progress).map((key, index) => ( + <> + + {index < Object.keys(progress).length - 1 && } + + ))} + {progress && Object.keys(progress).length == 0 && ( + + {t("setting.progressNotAvailable")} + + )} + + + ); +}; + +export default StepProgressPopover; diff --git a/src/component/Pages/Tasks/TaskCard.tsx b/src/component/Pages/Tasks/TaskCard.tsx new file mode 100755 index 0000000..cfd468f --- /dev/null +++ b/src/component/Pages/Tasks/TaskCard.tsx @@ -0,0 +1,222 @@ +import { + Box, + darken, + lighten, + Skeleton, + styled, + SvgIconProps, + Theme, + Typography, + useMediaQuery, + useTheme, +} from "@mui/material"; +import MuiAccordion, { AccordionProps } from "@mui/material/Accordion"; +import MuiAccordionDetails from "@mui/material/AccordionDetails"; +import MuiAccordionSummary, { AccordionSummaryProps } from "@mui/material/AccordionSummary"; +import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useInView } from "react-intersection-observer"; +import { FileType } from "../../../api/explorer.ts"; +import { TaskResponse, TaskType } from "../../../api/workflow.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { DefaultButton } from "../../Common/StyledComponents.tsx"; +import FileIcon from "../../FileManager/Explorer/FileIcon.tsx"; +import Archive from "../../Icons/Archive.tsx"; +import ArchiveArrow from "../../Icons/ArchiveArrow.tsx"; +import ArrowImport from "../../Icons/ArrowImport.tsx"; +import StorageOutlined from "../../Icons/StorageOutlined.tsx"; +import TaskDetail from "./TaskDetail.tsx"; +import TaskSummaryStatus from "./TaskSummaryStatus.tsx"; +import TaskSummaryTitle from "./TaskSummaryTitle.tsx"; + +const Accordion = styled((props: AccordionProps) => )(({ + theme, + expanded, +}) => { + const bgColor = expanded + ? theme.palette.mode == "light" + ? "rgba(0, 0, 0, 0.06)" + : "rgba(255, 255, 255, 0.09)" + : "initial"; + return { + borderRadius: theme.shape.borderRadius, + backgroundColor: bgColor, + "&::before": { + display: "none", + }, + boxShadow: expanded ? `0 0 0 1px ${theme.palette.divider}` : "none", + transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms", + marginBottom: theme.spacing(1), + }; +}); + +const AccordionSummary = styled((props: AccordionSummaryProps) => )(() => ({ + flexDirection: "row-reverse", + minHeight: 0, + padding: 0, + "& .MuiAccordionSummary-content": { + margin: 0, + }, +})); + +const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({ + padding: theme.spacing(2), + borderRadius: `0 0 ${theme.shape.borderRadius}px ${theme.shape.borderRadius}px`, + backgroundColor: theme.palette.background.default, +})); + +export const getProgressColor = (theme: Theme) => + theme.palette.mode === "dark" ? darken(theme.palette.primary.main, 0.4) : lighten(theme.palette.primary.main, 0.85); + +export const SummaryButton = styled(DefaultButton)<{ + expanded: boolean; + percentage?: number; +}>(({ theme, expanded, percentage }) => { + percentage = percentage ?? 0; + const bgColor = theme.palette.mode == "light" ? "rgba(0, 0, 0, 0.06)" : "rgba(255, 255, 255, 0.09)"; + const progressColor = getProgressColor(theme); + const progressBgColor = !expanded ? bgColor : "rgba(0,0,0,0)"; + return { + minHeight: 48, + justifyContent: "flex-start", + transition: "all 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms", + borderRadius: expanded + ? `${theme.shape.borderRadius}px ${theme.shape.borderRadius}px 0 0` + : `${theme.shape.borderRadius}px`, + backgroundColor: bgColor, + background: `linear-gradient(to right, ${progressColor} 0%,${progressColor} ${percentage}%,${progressBgColor} ${percentage}%,${progressBgColor} 100%)`, + "&:hover": { + backgroundColor: theme.palette.mode == "light" ? "rgba(0, 0, 0, 0.09)" : "rgba(255, 255, 255, 0.13)", + }, + }; +}); + +export interface TaskCardProps { + loading?: boolean; + showProgress?: boolean; + task?: TaskResponse; + onLoad?: () => void; +} + +const taskIconsMap: { + [key: string]: (props: SvgIconProps) => JSX.Element; +} = { + [TaskType.create_archive]: Archive, + [TaskType.extract_archive]: ArchiveArrow, + [TaskType.relocate]: StorageOutlined, + [TaskType.import]: ArrowImport, +}; + +const TaskCard = ({ loading, showProgress, onLoad, task }: TaskCardProps) => { + const { t } = useTranslation(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + const dispatch = useAppDispatch(); + const { ref, inView } = useInView({ + rootMargin: "200px 0px", + triggerOnce: true, + skip: !loading, + }); + + const [expanded, setExpanded] = useState(false); + + useEffect(() => { + if (!inView) { + return; + } + + if (onLoad) { + onLoad(); + } + }, [inView]); + + const handleChange = (_event: React.SyntheticEvent, newExpanded: boolean) => { + if (loading) { + return; + } + setExpanded(newExpanded); + }; + + const TaskIcon = useMemo(() => { + return taskIconsMap[task?.type ?? ""] ?? Archive; + }, [task?.type]); + + return ( + + + + ) : task?.type === TaskType.remote_download ? ( + 1 ? FileType.folder : FileType.file, + name: task?.summary?.props.download?.name ?? "", + }} + /> + ) : ( + + ) + } + > + + + {loading || !task ? ( + + ) : ( + + + + )} + + + + {loading || !task ? ( + + ) : ( + + )} + + + + + {task && } + + ); +}; + +export default TaskCard; diff --git a/src/component/Pages/Tasks/TaskDetail.tsx b/src/component/Pages/Tasks/TaskDetail.tsx new file mode 100755 index 0000000..d3fd7c2 --- /dev/null +++ b/src/component/Pages/Tasks/TaskDetail.tsx @@ -0,0 +1,97 @@ +import { + Alert, + Divider, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { TaskResponse, TaskStatus } from "../../../api/workflow.ts"; +import { StyledTableContainerPaper } from "../../Common/StyledComponents.tsx"; +import DownloadFileList from "./DownloadFileList.tsx"; +import TaskProgress from "./TaskProgress.tsx"; +import TaskProps from "./TaskProps.tsx"; + +export interface TaskDetailProps { + task: TaskResponse; + downloading?: boolean; +} + +const TaskDetail = ({ task, downloading }: TaskDetailProps) => { + const { t } = useTranslation(); + return ( + + + {task.summary?.props?.download && ( + <> + + {t("setting.fileList")} + + + + + )} + + {t("setting.taskProgress")} + + {!!task.summary?.props?.failed && ( + + {t("setting.partialSuccessWarning", { + num: task.summary?.props?.failed, + })} + + )} + {task.status == TaskStatus.error && {task.error}} + + + + + + {t("setting.taskDetails")} + + + {task.error_history && } + + {task.error_history && ( + + + {t("setting.retryErrorHistory")} + + +
    + + + # + {t("common:error")} + + + + {task.error_history.map((error, index) => ( + + + {index + 1} + + {error} + + ))} + +
    +
    + + )} + + ); +}; + +export default TaskDetail; diff --git a/src/component/Pages/Tasks/TaskList.tsx b/src/component/Pages/Tasks/TaskList.tsx new file mode 100755 index 0000000..9cb77bd --- /dev/null +++ b/src/component/Pages/Tasks/TaskList.tsx @@ -0,0 +1,124 @@ +import * as React from "react"; +import { useCallback, useEffect, useState } from "react"; +import { ListTaskCategory, TaskResponse } from "../../../api/workflow.ts"; +import { Box, Container, FormControlLabel, FormGroup, Switch, Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import PageHeader from "../PageHeader.tsx"; +import { getTasks } from "../../../api/api.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import Nothing from "../../Common/Nothing.tsx"; +import TaskCard from "./TaskCard.tsx"; +import PageContainer from "../PageContainer.tsx"; + +const defaultPageSize = 25; +const autoRefreshInterval = 20 * 1000; + +const TaskList = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const policyOption = useAppSelector((state) => state.globalState.policyOptionCache); + const [nextPageToken, setNextPageToken] = useState(""); + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(false); + const [autoRefresh, setAutoRefresh] = useState(true); + const interval = React.useRef(); + + useEffect(() => { + if (autoRefresh && !interval.current) { + interval.current = setInterval(() => { + refresh(); + }, autoRefreshInterval); + } else { + clearInterval(interval.current); + interval.current = undefined; + } + }, [autoRefresh]); + + useEffect(() => { + return () => { + clearInterval(interval.current); + interval.current = undefined; + }; + }, []); + + const loadNextPage = useCallback( + (originTasks: TaskResponse[], token?: string) => () => { + setLoading(true); + dispatch( + getTasks({ + page_size: defaultPageSize, + category: ListTaskCategory.general, + next_page_token: token, + }), + ) + .then((res) => { + if (token) { + setAutoRefresh(false); + } + setTasks([...originTasks, ...res.tasks]); + if (res.pagination?.next_token) { + setNextPageToken(res.pagination.next_token); + } else { + setNextPageToken(undefined); + } + }) + .catch(() => { + setNextPageToken(undefined); + }) + .finally(() => { + setLoading(false); + }); + }, + [dispatch, setAutoRefresh, setTasks], + ); + + const refresh = () => { + loadNextPage([], "")(); + }; + + const toggleAutoRefresh = (event: React.ChangeEvent) => { + if (event.target.checked && tasks.length > defaultPageSize) { + loadNextPage([], "")(); + } + setAutoRefresh(event.target.checked); + }; + + return ( + + + + } + label={ + + {t("setting.autoRefresh")} + + } + /> + + } + onRefresh={refresh} + loading={loading} + title={t("application:navbar.taskQueue")} + /> + {tasks.map((task) => ( + + ))} + {nextPageToken != undefined && ( + + )} + + {nextPageToken == undefined && tasks.length == 0 && ( + + + + )} + + + ); +}; + +export default TaskList; diff --git a/src/component/Pages/Tasks/TaskProgress.tsx b/src/component/Pages/Tasks/TaskProgress.tsx new file mode 100755 index 0000000..ab591bc --- /dev/null +++ b/src/component/Pages/Tasks/TaskProgress.tsx @@ -0,0 +1,250 @@ +import { Box, Stepper, useMediaQuery, useTheme } from "@mui/material"; +import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { NodeTypes, TaskStatus, TaskSummary, TaskType } from "../../../api/workflow.ts"; +import PieceProgress from "./PieceProgress.tsx"; +import TaskProgressStep from "./TaskProgressStep.tsx"; + +export interface TaskProgressProps { + taskId: string; + taskStatus: string; + taskType: string; + summary?: TaskSummary; + node?: { + type: string; + }; +} + +interface StepModel { + title: string; + state: string; + description?: string; + supportProgress?: boolean; +} + +const queueingStep: StepModel = { + title: "setting.queueToStart", + state: "", +}; + +const completedStep: StepModel = { + title: "setting.finished", + state: "", +}; + +const stepOptions: { + [key: string]: StepModel[][]; +} = { + [TaskType.remote_download]: [ + // Master + [ + queueingStep, + { + title: "fileManager.download", + state: "monitor", + description: "setting.downloadDes", + }, + { + title: "setting.transferring", + state: "transfer", + description: "setting.downloadTransferDes", + supportProgress: true, + }, + { + title: "setting.awaitSeeding", + state: "seeding", + description: "setting.awaitSeedingDes", + }, + completedStep, + ], + // Slave + [ + queueingStep, + { + title: "fileManager.download", + state: "monitor", + description: "setting.downloadDes", + }, + { + title: "setting.transferring", + state: "transfer", + description: "setting.downloadTransferDes", + supportProgress: true, + }, + { + title: "setting.awaitSeeding", + state: "seeding", + description: "setting.awaitSeedingDes", + }, + completedStep, + ], + ], + [TaskType.import]: [ + // Master + [ + queueingStep, + { + title: "setting.importingFiles", + state: "", + description: "setting.importingFilesDes", + supportProgress: true, + }, + ], + ], + [TaskType.relocate]: [ + // Master + [ + queueingStep, + { + title: "setting.indexingFiles", + state: "", + description: "setting.indexingFilesDes", + }, + { + title: "setting.transferring", + state: "transfer", + description: "setting.transferringRelocateDes", + supportProgress: true, + }, + { + title: "setting.committingChanges", + state: "finish", + description: "setting.relocateFinishing", + }, + ], + ], + [TaskType.extract_archive]: [ + // Master + [ + queueingStep, + { + title: "setting.downloadingZip", + description: "setting.downloadingZipDes", + state: "download_zip", + supportProgress: true, + }, + { + title: "setting.extractingFiles", + state: "", + description: "setting.extractingFilesDes", + supportProgress: true, + }, + ], + // Slave + [ + queueingStep, + { + title: "setting.sendTask", + description: "setting.sendTaskDes", + state: "", + }, + { + title: "setting.extractingFiles", + state: "await_slave_complete", + description: "setting.extractingFilesDes", + supportProgress: true, + }, + ], + ], + [TaskType.create_archive]: [ + // Master + [ + queueingStep, + { + title: "setting.prepare", + state: "", + description: "setting.preparingWorkspaceDes", + }, + { + title: "setting.compressFiles", + state: "compress_files", + description: "setting.compressFilesDes", + supportProgress: true, + }, + { + title: "setting.transferring", + state: "upload_archive", + description: "setting.uploadArchiveFileDes", + supportProgress: true, + }, + ], + // Slave + [ + queueingStep, + { + title: "setting.indexingFiles", + state: "", + description: "setting.indexForArchiveDes", + }, + { + title: "setting.compressFiles", + state: "await_slave_compressing", + description: "setting.compressFilesDes", + supportProgress: true, + }, + { + title: "setting.transferring", + state: "await_slave_uploading", + description: "setting.uploadArchiveFileDes", + supportProgress: true, + }, + { + title: "setting.committingChanges", + state: "complete_upload", + description: "setting.createArchiveFinishing", + }, + ], + ], +}; + +const TaskProgress = ({ taskId, taskStatus, taskType, summary, node }: TaskProgressProps) => { + const { t } = useTranslation(); + const [activeStep, setActiveStep] = useState(0); + const steps = useMemo((): StepModel[] => { + return stepOptions[taskType]?.[node?.type == NodeTypes.slave ? 1 : 0] ?? []; + }, [taskId, node?.type]); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + + useEffect(() => { + if (taskStatus == TaskStatus.queued) { + setActiveStep(0); + return; + } + if (taskStatus == TaskStatus.completed) { + setActiveStep(steps.length); + return; + } + let active = 1; + for (let i = 1; i < steps.length; i++) { + if (steps[i].state == summary?.phase) { + active = i; + } + } + + setActiveStep(active); + }, [steps, taskStatus, summary?.phase]); + + return ( + + + {steps.map((step, index) => ( + + ))} + + {taskType == TaskType.remote_download && summary?.props.download?.pieces && summary?.phase == "monitor" && ( + + )} + + ); +}; + +export default TaskProgress; diff --git a/src/component/Pages/Tasks/TaskProgressStep.tsx b/src/component/Pages/Tasks/TaskProgressStep.tsx new file mode 100755 index 0000000..e5f43ac --- /dev/null +++ b/src/component/Pages/Tasks/TaskProgressStep.tsx @@ -0,0 +1,91 @@ +import { Step, StepIcon, StepIconProps, StepLabel, Typography } from "@mui/material"; +import { StepProps } from "@mui/material/Step/Step"; +import { bindHover, bindPopover } from "material-ui-popup-state"; +import { usePopupState } from "material-ui-popup-state/hooks"; +import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { TaskStatus } from "../../../api/workflow.ts"; +import FacebookCircularProgress from "../../Common/CircularProgress.tsx"; +import CheckCircleFilled from "../../Icons/CheckCircleFilled.tsx"; +import DismissCircleFilled from "../../Icons/DismissCircleFilled.tsx"; +import StepProgressPopover from "./StepProgressPopover.tsx"; + +interface ProgressStepIconProps extends StepIconProps {} + +const ProgressStepIcon = (status: string) => (props: ProgressStepIconProps) => { + const { active, completed, icon, ...rest } = props; + + let newIcon = icon; + if (active) { + newIcon = ; + if (status == TaskStatus.error) { + newIcon = theme.palette.error.main }} />; + } + } else if (completed) { + newIcon = theme.palette.primary.main }} />; + } + + if (active && status == TaskStatus.error) { + newIcon = theme.palette.error.main }} />; + } + + if (active && status == TaskStatus.canceled) { + newIcon = ( + theme.palette.action.active, + }} + /> + ); + } + + return ; +}; + +export interface TaskProgressStepProps extends StepProps { + taskId: string; + taskStatus: string; + title: string; + description?: string; + showProgress?: boolean; + progressing?: boolean; +} + +const TaskProgressStep = ({ + taskId, + taskStatus, + title, + description, + showProgress, + progressing, + ...rest +}: TaskProgressStepProps) => { + const popupState = usePopupState({ + variant: "popover", + popupId: `progress_${taskId}_${title}`, + }); + const { open, ...restPopup } = bindPopover(popupState); + + const StepIconComponent = useMemo(() => { + return ProgressStepIcon(taskStatus); + }, [taskStatus]); + const { t } = useTranslation(); + return ( + <> + + {t(description)} : undefined} + slots={{ + stepIcon: StepIconComponent, + }} + > + {t(title)} + + + {showProgress && progressing && } + + ); +}; + +export default TaskProgressStep; diff --git a/src/component/Pages/Tasks/TaskProps.tsx b/src/component/Pages/Tasks/TaskProps.tsx new file mode 100755 index 0000000..214f267 --- /dev/null +++ b/src/component/Pages/Tasks/TaskProps.tsx @@ -0,0 +1,185 @@ +import { Grid, Stack, Typography } from "@mui/material"; +import dayjs from "dayjs"; +import duration from "dayjs/plugin/duration"; +import { TFunction } from "i18next"; +import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { FileType } from "../../../api/explorer.ts"; +import { TaskResponse, TaskStatus, TaskType } from "../../../api/workflow.ts"; +import { sizeToString } from "../../../util"; +import { formatDuration } from "../../../util/datetime.ts"; +import TimeBadge from "../../Common/TimeBadge.tsx"; +import FileBadge from "../../FileManager/FileBadge.tsx"; + +dayjs.extend(duration); + +export interface TaskPropsProps { + task: TaskResponse; +} + +interface TaskPropsBlockProps { + label: string; + value: React.ReactNode; +} + +const TaskPropsBlock = ({ label, value }: TaskPropsBlockProps) => { + return ( + + + + {label} + + + + + {value} + + + + ); +}; + +export const getTaskStatusText = (status: TaskStatus, t: TFunction) => { + switch (status) { + case TaskStatus.queued: + return t("application:setting.queueing"); + case TaskStatus.processing: + return t("application:setting.processing"); + case TaskStatus.suspending: + return t("application:setting.processing") + t("application:setting.suspended"); + case TaskStatus.canceled: + return t("application:setting.canceled"); + case TaskStatus.error: + return t("application:setting.failed"); + case TaskStatus.completed: + return t("application:setting.finished"); + default: + return t("application:uplaoder.unknownStatus"); + } +}; + +const TaskProps = ({ task }: TaskPropsProps) => { + const { t } = useTranslation(); + const status = useMemo(() => getTaskStatusText(task.status as TaskStatus, t), [task.status, t]); + + return ( + + } + /> + } + /> + + + {task.summary?.props.src && ( + + } + /> + )} + {task.summary?.props.src_str && ( + + {task.summary?.props.src_str} + + } + /> + )} + {task.summary?.props.src_multiple && ( + + {task.summary?.props.src_multiple.map((src, index) => ( + + ))} + + } + /> + )} + {task.summary?.props.dst && ( + + } + /> + )} + + {task.resume_time && (task.status == TaskStatus.suspending || task.status == TaskStatus.processing) && ( + } + /> + )} + + {!!task.summary?.props.download?.num_pieces && ( + + )} + {!!task.summary?.props.download?.uploaded && ( + + )} + {!!task.summary?.props.download?.upload_speed && ( + + )} + {!!task.summary?.props.download?.hash && ( + + )} + + ); +}; + +export default TaskProps; diff --git a/src/component/Pages/Tasks/TaskSummaryStatus.tsx b/src/component/Pages/Tasks/TaskSummaryStatus.tsx new file mode 100755 index 0000000..a0edb2b --- /dev/null +++ b/src/component/Pages/Tasks/TaskSummaryStatus.tsx @@ -0,0 +1,155 @@ +import { Box, styled, Tooltip, Typography, useTheme } from "@mui/material"; +import { forwardRef } from "react"; +import { useTranslation } from "react-i18next"; +import { TaskStatus, TaskSummary, TaskType } from "../../../api/workflow.ts"; +import { useAppDispatch } from "../../../redux/hooks.ts"; +import { sizeToString } from "../../../util"; +import ArrowSyncCircleFilled from "../../Icons/ArrowSyncCircleFilled.tsx"; +import CheckCircleFilled from "../../Icons/CheckCircleFilled.tsx"; +import CircleHintFilled from "../../Icons/CircleHintFilled.tsx"; +import DismissCircleFilled from "../../Icons/DismissCircleFilled.tsx"; + +const ArrowSyncCircleFilledSpin = styled(ArrowSyncCircleFilled)({ + animation: "spin 4s linear infinite", + "@keyframes spin": { + from: { + transform: "rotate(0deg)", + }, + to: { + transform: "rotate(360deg)", + }, + }, +}); + +export interface TaskSummaryStatusProps { + type: string; + status: string; + summary?: TaskSummary; + error?: string; + simplified?: boolean; +} + +interface TaskStatusContentProps { + color: string; + icon: React.ReactNode; + title: string; + [key: string]: any; +} +const TaskStatusContent = forwardRef(({ icon, title, color, ...props }: TaskStatusContentProps, ref) => { + return ( + + + {icon} + {title} + + + ); +}); + +const TaskSummaryStatus = ({ type, status, summary, error, simplified }: TaskSummaryStatusProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + + switch (status) { + case TaskStatus.completed: + return ( + } + color={theme.palette.success.main} + /> + ); + case TaskStatus.error: + return ( + + } + color={theme.palette.error.main} + /> + + ); + case TaskStatus.processing: + case TaskStatus.suspending: + if (type == TaskType.remote_download) { + if (summary?.phase == "monitor" && summary?.props.download) { + const downloadStatus = summary.props.download; + return ( + + {!simplified && + `${sizeToString(downloadStatus.download_speed)} /s - ${sizeToString( + downloadStatus.downloaded, + )} / ${sizeToString(downloadStatus.total)}`} + } + color={theme.palette.primary.main} + /> + + ); + } else if (summary?.phase == "seeding") { + return ( + } + color={theme.palette.primary.main} + /> + ); + } else if (summary?.phase == "transfer") { + return ( + } + color={theme.palette.primary.main} + /> + ); + } else { + return ( + } + color={theme.palette.primary.main} + /> + ); + } + } + return ( + } + color={theme.palette.primary.main} + /> + ); + case TaskStatus.queued: + return ( + } + color={theme.palette.action.active} + /> + ); + case TaskStatus.canceled: + return ( + } + color={theme.palette.action.active} + /> + ); + } +}; + +export default TaskSummaryStatus; diff --git a/src/component/Pages/Tasks/TaskSummaryTitle.tsx b/src/component/Pages/Tasks/TaskSummaryTitle.tsx new file mode 100755 index 0000000..8933fe0 --- /dev/null +++ b/src/component/Pages/Tasks/TaskSummaryTitle.tsx @@ -0,0 +1,139 @@ +import { Box, Chip, styled, Typography } from "@mui/material"; +import { useMemo } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { FileType } from "../../../api/explorer.ts"; +import { TaskSummary, TaskType } from "../../../api/workflow.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { newMyUri } from "../../../util/uri.ts"; +import FileBadge from "../../FileManager/FileBadge.tsx"; + +export interface TaskSummaryTitleProps { + type: string; + summary?: TaskSummary; + isInDashboard?: boolean; +} + +const StyledFileBadge = styled(FileBadge)(() => ({ + paddingLeft: 8, + paddingRight: 8, + marginLeft: 4, + marginRight: 4, + maxWidth: "200px", +})); + +const StyledChip = styled(Chip)(() => ({ + marginLeft: 8, + height: "20px", +})); + +const TaskSummaryTitle = ({ type, summary, isInDashboard = false }: TaskSummaryTitleProps) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const policyOption = useAppSelector((state) => state.globalState.policyOptionCache); + + const selectedCount = useMemo(() => { + let selected = 0; + for (const file of summary?.props.download?.files ?? []) { + if (file.selected) { + selected++; + } + } + + return selected; + }, [summary?.props.download?.files]); + + switch (type) { + case TaskType.remote_download: + return ( + + + {isInDashboard && t("dashboard:task.remoteDownload")} + {summary?.props.download?.name ?? t("download.unknownTaskName")} + {selectedCount > 1 && } + + + ); + case TaskType.create_archive: + return ( + + {summary?.props.src_multiple?.slice(0, 3).map((src) => ( + + ))} + , + , + ]} + values={{ + more: (summary?.props.src_multiple?.length ?? 0) > 3 ? "..." : "", + }} + /> + ); + case TaskType.import: + return ( + p.id == summary?.props.dst_policy_id)?.name ?? "Unknown" + : "", + }} + components={[ + , + ]} + /> + ); + default: + return ( + , + , + ]} + values={{ + more: (summary?.props.src_multiple?.length ?? 0) > 3 ? "..." : "", + }} + /> + ); + } +}; + +export default TaskSummaryTitle; diff --git a/src/component/Uploader/DropFile.tsx b/src/component/Uploader/DropFile.tsx new file mode 100755 index 0000000..246b67d --- /dev/null +++ b/src/component/Uploader/DropFile.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import Backdrop from "@mui/material/Backdrop"; +import { Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import CloudArrowIUp from "../Icons/CloudArrowIUp.tsx"; + +export function DropFileBackground({ open }: { open: boolean }) { + const { t } = useTranslation(); + return ( + theme.zIndex.drawer + 1, + color: "#fff", + flexDirection: "column", + }} + open={open} + > +
    + +
    +
    + {t("uploader.dropFileHere")} +
    +
    + ); +} diff --git a/src/component/Uploader/PasteUploadDialog.tsx b/src/component/Uploader/PasteUploadDialog.tsx new file mode 100755 index 0000000..01677cc --- /dev/null +++ b/src/component/Uploader/PasteUploadDialog.tsx @@ -0,0 +1,84 @@ +import React, { useCallback, useEffect } from "react"; +import { Box, DialogContent, styled, Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts"; +import DraggableDialog from "../Dialogs/DraggableDialog.tsx"; +import { setUploadFromClipboardDialog } from "../../redux/globalStateSlice.ts"; +import Clipboard from "../Icons/Clipboard.tsx"; + +export interface PasteUploadDialogProps { + onFilePasted: (files: File[]) => void; +} + +export interface PasteTargetProps extends PasteUploadDialogProps { + onClose: () => void; +} + +const PasteTargetContainer = styled(Box)(({ theme }) => ({ + borderRadius: theme.shape.borderRadius, + backgroundColor: theme.palette.mode == "light" ? "rgba(0, 0, 0, 0.06)" : "rgba(255, 255, 255, 0.09)", + display: "flex", + justifyContent: "center", + alignItems: "center", + flexDirection: "column", + padding: theme.spacing(2), + color: theme.palette.text.secondary, +})); + +const PasteTarget = ({ onFilePasted, onClose }: PasteTargetProps) => { + const { t } = useTranslation(); + const pasteHandler = (e: ClipboardEvent) => { + e.preventDefault(); + if (!e.clipboardData?.files.length) { + return; + } + + onFilePasted(Array.from(e.clipboardData.files)); + onClose(); + }; + + useEffect(() => { + // listen to onpaste event for window + window.addEventListener("paste", pasteHandler); + return () => { + window.removeEventListener("paste", pasteHandler); + }; + }, []); + + const disableDefault = useCallback((e: React.MouseEvent | React.KeyboardEvent | React.ClipboardEvent) => { + e.preventDefault(); + return false; + }, []); + return ( + + + {t("uploader.pasteFilesHere")} + + ); +}; + +export default function PasteUploadDialog({ onFilePasted }: PasteUploadDialogProps) { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const open = useAppSelector((state) => state.globalState.uploadFromClipboardDialogOpen); + + const onClose = useCallback(() => { + dispatch(setUploadFromClipboardDialog(false)); + }, []); + + return ( + + + + + + ); +} diff --git a/src/component/Uploader/Popup/ConcurrentOptionDialog.tsx b/src/component/Uploader/Popup/ConcurrentOptionDialog.tsx new file mode 100755 index 0000000..7fad39d --- /dev/null +++ b/src/component/Uploader/Popup/ConcurrentOptionDialog.tsx @@ -0,0 +1,52 @@ +import React, { useCallback, useState } from "react"; +import { DialogContent, FilledInput, InputLabel } from "@mui/material"; +import FormControl from "@mui/material/FormControl"; +import { useTranslation } from "react-i18next"; +import SessionManager, { UserSettings } from "../../../session"; +import DraggableDialog from "../../Dialogs/DraggableDialog.tsx"; + +export interface ConcurrentOptionDialogProps { + open: boolean; + onClose: () => void; + onSave: (count: string) => void; +} + +export default function ConcurrentOptionDialog({ open, onClose, onSave }: ConcurrentOptionDialogProps) { + const { t } = useTranslation(); + const [count, setCount] = useState(SessionManager.getWithFallback(UserSettings.ConcurrentLimit)); + + const onAccept = useCallback(() => { + onSave(count); + }, [count]); + + return ( + + + + {t("uploader.concurrentTaskNumber")} + setCount(e.target.value)} + /> + + + + ); +} diff --git a/src/component/Uploader/Popup/MoreActions.tsx b/src/component/Uploader/Popup/MoreActions.tsx new file mode 100755 index 0000000..2371e88 --- /dev/null +++ b/src/component/Uploader/Popup/MoreActions.tsx @@ -0,0 +1,155 @@ +import { Icon, ListItemIcon, Menu, MenuItem, Tooltip } from "@mui/material"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { DenseDivider } from "../../FileManager/ContextMenu/ContextMenu.tsx"; +import Checkmark from "../../Icons/Checkmark.tsx"; +import TextGrammarLighting from "../../Icons/TextGrammarLighting.tsx"; +import DeleteOutlined from "../../Icons/DeleteOutlined.tsx"; +import ArrowClockwise from "../../Icons/ArrowClockwise.tsx"; +import ConcurrentOptionDialog from "./ConcurrentOptionDialog.tsx"; +import SessionManager, { UserSettings } from "../../../session"; +import UploadManager from "../core"; + +export interface MoreActionsProps { + anchorEl: null | HTMLElement; + onClose: () => void; + uploadManager: UploadManager; + useAvgSpeed: boolean; + setUseAvgSpeed: (val: boolean) => void; + filter: string; + setFilter: (val: string) => void; + sorter: string; + setSorter: (val: string) => void; + cleanFinished: () => void; + retryFailed: () => void; +} + +export default function MoreActions({ + anchorEl, + onClose, + uploadManager, + useAvgSpeed, + setUseAvgSpeed, + filter, + setFilter, + sorter, + setSorter, + cleanFinished, + retryFailed, +}: MoreActionsProps) { + const { t } = useTranslation("application", { keyPrefix: "uploader" }); + const [concurrentDialog, setConcurrentDialog] = useState(false); + const [overwrite, setOverwrite] = useState(SessionManager.getWithFallback(UserSettings.UploadOverwrite)); + const actionClicked = (next: () => void) => () => { + onClose(); + next(); + }; + + const toggleOverwrite = (val: boolean) => { + SessionManager.set(UserSettings.UploadOverwrite, val); + uploadManager.overwrite = val; + setOverwrite(val); + }; + + const open = Boolean(anchorEl); + const id = open ? "uploader-action-popover" : undefined; + + const listItems = useMemo( + () => [ + { + tooltip: t("overwriteTooltip"), + onClick: () => toggleOverwrite(!overwrite), + icon: overwrite ? : , + text: t("overwrite"), + divider: true, + }, + { + tooltip: t("hideCompletedTooltip"), + onClick: () => setFilter(filter === "default" ? "ongoing" : "default"), + icon: filter !== "default" ? : , + text: t("hideCompleted"), + divider: true, + }, + { + tooltip: t("addTimeAscTooltip"), + onClick: () => setSorter("default"), + icon: sorter === "default" ? : , + text: t("addTimeAsc"), + divider: false, + }, + { + tooltip: t("addTimeDescTooltip"), + onClick: () => setSorter("reverse"), + icon: sorter === "reverse" ? : , + text: t("addTimeDesc"), + divider: true, + }, + { + tooltip: t("showInstantSpeedTooltip"), + onClick: () => setUseAvgSpeed(false), + icon: useAvgSpeed ? : , + text: t("showInstantSpeed"), + divider: false, + }, + { + tooltip: t("showAvgSpeedTooltip"), + onClick: () => setUseAvgSpeed(true), + icon: !useAvgSpeed ? : , + text: t("showAvgSpeed"), + divider: true, + }, + { + tooltip: t("cleanCompletedTooltip"), + onClick: () => cleanFinished(), + icon: , + text: t("cleanCompleted"), + divider: false, + }, + { + tooltip: t("retryFailedTasksTooltip"), + onClick: () => retryFailed(), + icon: , + text: t("retryFailedTasks"), + divider: true, + }, + { + tooltip: t("setConcurrentTooltip"), + onClick: () => setConcurrentDialog(true), + icon: , + text: t("setConcurrent"), + divider: false, + }, + ], + [useAvgSpeed, setUseAvgSpeed, sorter, setSorter, filter, setFilter, cleanFinished, overwrite], + ); + + const onConcurrentLimitSave = (val: string) => { + const valNum = parseInt(val); + if (valNum > 0) { + SessionManager.set(UserSettings.ConcurrentLimit, valNum); + uploadManager.changeConcurrentLimit(valNum); + } + setConcurrentDialog(false); + }; + + return ( + <> + + {listItems.map((item) => [ + + + {item.icon} + {item.text} + + , + item.divider && , + ])} + + setConcurrentDialog(false)} + onSave={onConcurrentLimitSave} + /> + + ); +} diff --git a/src/component/Uploader/Popup/TaskDetail.tsx b/src/component/Uploader/Popup/TaskDetail.tsx new file mode 100755 index 0000000..b5b6e32 --- /dev/null +++ b/src/component/Uploader/Popup/TaskDetail.tsx @@ -0,0 +1,101 @@ +import React from "react"; +import Grid from "@mui/material/Grid"; +import TimeAgo from "timeago-react"; +import Base, { Status } from "../core/uploader/base"; +import { Trans, useTranslation } from "react-i18next"; +import { sizeToString } from "../../../util"; +import FileBadge from "../../FileManager/FileBadge.tsx"; +import { FileType } from "../../../api/explorer.ts"; + +export interface TaskDetailProps { + uploader: Base; + error: string; +} + +export default function TaskDetail({ uploader, error }: TaskDetailProps) { + const { t } = useTranslation(); + const items = [ + { + name: t("uploader.fileName"), + value: uploader.task.name, + }, + { + name: t("uploader.fileSize"), + value: `${sizeToString(uploader.task.size)} ${ + uploader.task.session && uploader.task.session.chunk_size > 0 + ? t("uploader.chunkDescription", { + total: uploader.task.chunkProgress.length, + size: sizeToString(uploader.task.session.chunk_size), + }) + : t("uploader.noChunks") + }`, + }, + { + name: t("uploader.storagePolicy"), + value: uploader.task.policy.name, + }, + { + name: t("uploader.destination"), + value: ( + + ), + }, + uploader.task.session + ? { + name: t("uploader.uploadSession"), + value: ( + <> + , + ]} + /> + + ), + } + : null, + uploader.status === Status.error + ? { + name: t("uploader.errorDetails"), + value: error, + } + : null, + ]; + return ( + theme.typography.body2.fontSize }}> + {items.map((i) => ( + <> + {i && ( + + + {i.name} + + theme.palette.text.secondary, + wordBreak: "break-all", + }} + > + {i.value} + + + )} + + ))} + + ); +} diff --git a/src/component/Uploader/Popup/TaskList.tsx b/src/component/Uploader/Popup/TaskList.tsx new file mode 100755 index 0000000..8f07002 --- /dev/null +++ b/src/component/Uploader/Popup/TaskList.tsx @@ -0,0 +1,322 @@ +import React, { useMemo, useState } from "react"; +import { + Accordion, + AccordionDetails, + alpha, + AppBar, + Box, + Dialog, + DialogContent, + Fade, + IconButton, + Slide, + SlideProps, + styled, + Toolbar, + Tooltip, + Typography, +} from "@mui/material"; +import { lighten, useTheme } from "@mui/material/styles"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import UploadTask from "./UploadTask.tsx"; +import MoreActions from "./MoreActions.js"; +import { Virtuoso } from "react-virtuoso"; +import Base, { Status } from "../core/uploader/base"; +import { useTranslation } from "react-i18next"; +import { useAppSelector } from "../../../redux/hooks.ts"; +import { defaultPath } from "../../../hooks/useNavigation.tsx"; +import SessionManager, { UserSettings } from "../../../session"; +import Nothing from "../../Common/Nothing.tsx"; +import Dismiss from "../../Icons/Dismiss.tsx"; +import MoreHorizontal from "../../Icons/MoreHorizontal.tsx"; +import Add from "../../Icons/Add.tsx"; +import { ExpandMoreRounded } from "@mui/icons-material"; +import { UploadProgressTotal } from "../../../redux/globalStateSlice.ts"; + +const Transition = React.forwardRef(function Transition(props: SlideProps, ref) { + return ; +}); + +const StyledDialog = styled(Dialog)(({ fullScreen, theme }) => ({ + "& .MuiDialog-container": { + alignItems: "flex-end", + justifyContent: "flex-end", + }, + "& .MuiDialog-paper": { + border: `1px solid ${theme.palette.divider}`, + }, + ...(fullScreen + ? { + margin: 0, + position: "fixed", + inset: "-1!important", + } + : { top: "auto!important", left: "auto!important" }), +})); + +const StyledDialogContent = styled(DialogContent)(({ theme }) => ({ + [theme.breakpoints.up("md")]: { + width: 500, + minHeight: 300, + maxHeight: "calc(100vh - 140px)", + }, + padding: 0, + paddingTop: "0!important", +})); + +const CaretDownIcon = styled(ExpandMoreRounded)<{ expanded: boolean }>(({ theme, expanded }) => ({ + transform: `rotate(${expanded ? 0 : 180}deg)`, + transition: theme.transitions.create("transform", { + duration: theme.transitions.duration.shortest, + easing: theme.transitions.easing.easeInOut, + }), +})); + +const sorters: { + [key: string]: (a: Base, b: Base) => number; +} = { + default: (a, b) => a.id - b.id, + reverse: (a, b) => b.id - a.id, +}; + +const filters: { + [key: string]: (u: Base) => boolean; +} = { + default: (_u) => true, + ongoing: (u) => u.status < Status.finished, +}; + +export interface TaskListProps { + open: boolean; + onClose: () => void; + selectFile: (path: string) => void; + taskList: Base[]; + onCancel: (filter: (u: any) => boolean) => void; + uploadManager: any; + progress?: UploadProgressTotal; + setUploaders: (f: (u: Base[]) => Base[]) => void; +} + +export default function TaskList({ + open, + onClose, + selectFile, + taskList, + onCancel, + uploadManager, + progress, + setUploaders, +}: TaskListProps) { + const { t } = useTranslation("application", { keyPrefix: "uploader" }); + const theme = useTheme(); + const fullScreen = useMediaQuery(theme.breakpoints.down("md")); + const path = useAppSelector((state) => state.fileManager[0].pure_path); + const [expanded, setExpanded] = useState(true); + const [useAvgSpeed, setUseAvgSpeed] = useState(SessionManager.getWithFallback(UserSettings.UseAvgSpeed)); + const [anchorEl, setAnchorEl] = useState(null); + const [filter, setFilter] = useState(SessionManager.getWithFallback(UserSettings.TaskFilter)); + const [sorter, setSorter] = useState(SessionManager.getWithFallback(UserSettings.TaskSorter)); + const [refreshList, setRefreshList] = useState(false); + + const handleActionClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleActionClose = () => { + setAnchorEl(null); + }; + + const close = (_e: any, reason: string) => { + if (reason !== "backdropClick") { + onClose(); + } else { + setExpanded(false); + } + }; + const handlePanelChange = (_event: any, isExpanded: boolean) => { + setExpanded(isExpanded); + }; + + useMemo(() => { + if (open) { + setExpanded(true); + } + }, [taskList]); + + const stopPop = (func: (e: React.MouseEvent) => void) => (e: React.MouseEvent) => { + e.stopPropagation(); + func(e); + }; + + const progressBar = useMemo( + () => + progress && progress.totalSize > 0 ? ( + 0 && !expanded}> +
    + +
    +
    + ) : null, + [progress, expanded, theme], + ); + + const list = useMemo(() => { + const currentList = taskList.filter(filters[filter]).sort(sorters[sorter]); + if (currentList.length === 0) { + return ; + } + + return ( + ( + setRefreshList((r) => !r)} + /> + )} + /> + ); + }, [taskList, useAvgSpeed, fullScreen, filter, sorter, refreshList]); + + const retryFailed = () => { + taskList.forEach((task) => { + if (task.status === Status.error) { + task.retry(); + } + }); + }; + + return ( + <> + { + SessionManager.set(UserSettings.UseAvgSpeed, v); + setUseAvgSpeed(v); + }} + filter={filter} + sorter={sorter} + setFilter={(v) => { + SessionManager.set(UserSettings.TaskFilter, v); + setFilter(v); + }} + setSorter={(v) => { + SessionManager.set(UserSettings.TaskSorter, v); + setSorter(v); + }} + retryFailed={retryFailed} + cleanFinished={() => setUploaders((u) => u.filter(filters["ongoing"]))} + /> + + + setExpanded(!expanded)} + sx={{ + boxShadow: "none", + position: "relative", + cursor: "pointer", + }} + > + {progressBar} + + + close(null, ""))} aria-label="Close"> + + + + + {t("uploadTasks")} + + + + + + + + {path && ( + + selectFile(path ?? defaultPath))}> + + + + )} + {!fullScreen && ( + + setExpanded(!expanded))}> + + + + )} + + + + + {list} + + + + + ); +} diff --git a/src/component/Uploader/Popup/UploadTask.tsx b/src/component/Uploader/Popup/UploadTask.tsx new file mode 100755 index 0000000..a310c04 --- /dev/null +++ b/src/component/Uploader/Popup/UploadTask.tsx @@ -0,0 +1,423 @@ +import { alpha, Box, Divider, Grow, IconButton, ListItemButton, ListItemText, styled, Tooltip } from "@mui/material"; +import MuiAccordion from "@mui/material/Accordion"; +import MuiAccordionDetails from "@mui/material/AccordionDetails"; +import MuiAccordionSummary from "@mui/material/AccordionSummary"; +import Chip from "@mui/material/Chip"; +import { lighten, useTheme } from "@mui/material/styles"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import React, { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { FileType } from "../../../api/explorer.ts"; +import { navigateToPath } from "../../../redux/thunks/filemanager.ts"; +import { sizeToString } from "../../../util"; +import { NoWrapBox } from "../../Common/StyledComponents.tsx"; +import FileTypeIcon from "../../FileManager/Explorer/FileTypeIcon.tsx"; +import ArrowClockwiseFilled from "../../Icons/ArrowClockwiseFilled.tsx"; +import Delete from "../../Icons/Delete.tsx"; +import DocumentArrowDownFilled from "../../Icons/DocumentArrowDownFilled.tsx"; +import Play from "../../Icons/Play.tsx"; +import RenameFilled from "../../Icons/RenameFilled.tsx"; +import { useUpload } from "../UseUpload.js"; +import { SelectType } from "../core"; +import { CreateUploadSessionError, UploaderError } from "../core/errors"; +import Base, { Status } from "../core/uploader/base"; +import TaskDetail from "./TaskDetail.js"; + +const Accordion = styled(MuiAccordion)(() => ({ + maxWidth: "100%", + boxShadow: "none", + "&:not(:last-child)": { + borderBottom: 0, + }, + "&:before": { + display: "none", + }, + "& .Mui-expanded": { + margin: "0!important", + }, +})); + +const AccordionSummary = styled(MuiAccordionSummary)(() => ({ + minHeight: "56px", + padding: 0, + display: "block", + "& .MuiAccordionSummary-content": { + margin: 0, + display: "block", + "& .Mui-expanded": { + margin: "0!important", + }, + }, +})); + +const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({ + "& .MuiAccordionActions-root": { + paddingLeft: 16, + paddingRight: 16, + paddingTop: 8, + paddingBottom: 8, + display: "block", + backgroundColor: theme.palette.background.default, + }, +})); + +const getSpeedText = (speed: number, speedAvg: number, useSpeedAvg: boolean) => { + let displayedSpeed = speedAvg; + if (!useSpeedAvg) { + displayedSpeed = speed; + } + + return `${sizeToString(displayedSpeed ? displayedSpeed : 0)} /s`; +}; + +const getErrMsg = (error?: Error) => { + if (error) { + let errMsg = error.message; + if (error instanceof UploaderError) { + errMsg = error.Message(); + } + + return errMsg; + } +}; + +export interface UploadTaskProps { + uploader: Base; + useAvgSpeed: boolean; + onCancel: (filter: (u: Base) => boolean) => void; + onClose: (u: any, reason: string) => void; + selectFile: (path: string, type: SelectType, uploader: Base) => void; + onRefresh: () => void; +} + +export default function UploadTask({ + uploader, + useAvgSpeed, + onCancel, + onClose, + selectFile, + onRefresh, +}: UploadTaskProps) { + const { t } = useTranslation("application", { keyPrefix: "uploader" }); + const theme = useTheme(); + const [taskHover, setTaskHover] = useState(false); + const [expanded, setExpanded] = useState(false); + const { status, error, progress, speed, speedAvg, retry } = useUpload(uploader); + const fullScreen = useMediaQuery(theme.breakpoints.down("md")); + const [loading, setLoading] = useState(false); + const navigateToDst = (path: string) => { + onClose(null, "backdropClick"); + navigateToPath(0, path); + }; + + const toggleDetail = (_event: any, newExpanded: boolean) => { + setExpanded(!!newExpanded); + }; + + useEffect(() => { + if (status >= Status.finished) { + onRefresh(); + } + }, [status]); + + const statusText = useMemo(() => { + // const parent = filename(uploader.task.dst); + const parent = "TODO: parent"; + switch (status) { + case Status.added: + case Status.initialized: + case Status.queued: + return
    {t("pendingInQueue")}
    ; + case Status.preparing: + return
    {t("preparing")}
    ; + case Status.error: + return ( + + {getErrMsg(error)} +
    +
    + ); + case Status.finishing: + return
    {t("processing")}
    ; + case Status.resumable: + return ( + progress && ( +
    + {t("progressDescription", { + uploaded: sizeToString(progress.total.loaded), + total: sizeToString(progress.total.size), + percentage: progress.total.percent.toFixed(2), + })} +
    + ) + ); + case Status.processing: + if (progress) { + return ( +
    + {t("progressDescriptionFull", { + speed: getSpeedText(speed, speedAvg, useAvgSpeed), + uploaded: sizeToString(progress.total.loaded), + total: sizeToString(progress.total.size), + percentage: progress.total.percent.toFixed(2), + })} +
    + ); + } + return
    {t("progressDescriptionPlaceHolder")}
    ; + case Status.finished: + return ( + + {t("uploaded")} +
    +
    + ); + default: + return
    {t("unknownStatus")}
    ; + } + }, [status, error, progress, speed, speedAvg, useAvgSpeed]); + + const resumeLabel = useMemo( + () => + uploader.task.resumed && !fullScreen ? ( + + ) : null, + [status, fullScreen], + ); + + const continueLabel = useMemo( + () => + status === Status.resumable && !fullScreen ? ( + theme.typography.caption.fontSize, + }} + size="small" + color={"secondary"} + label={t("resumable")} + /> + ) : null, + [status, fullScreen], + ); + + const progressBar = useMemo( + () => + (status === Status.processing || status === Status.finishing || status === Status.resumable) && progress ? ( + + ) : null, + [status, progress, theme], + ); + + const taskDetail = useMemo(() => { + return ; + }, [uploader, expanded]); + + const cancel = () => { + setLoading(true); + uploader.cancel().then(() => { + setLoading(false); + onCancel((u) => u.id != uploader.id); + }); + }; + + const stopRipple = (e: React.MouseEvent | React.TouchEvent) => { + e.stopPropagation(); + }; + + const secondaryAction = useMemo(() => { + if (!taskHover && !fullScreen) { + return <>; + } + + const isConflict = error instanceof CreateUploadSessionError && error.IsConflictError(); + + const actions: { + show: boolean; + title: string; + click: () => void; + icon: JSX.Element; + loading: boolean; + }[] = [ + { + show: status === Status.error && !isConflict, + title: t("retry"), + click: () => retry(), + icon: , + loading: false, + }, + { + show: status === Status.error && isConflict, + title: t("rename"), + click: () => retry({ new_prefix: t("fileCopyName") }), + icon: , + loading: false, + }, + { + show: status === Status.error && isConflict, + title: t("overwrite"), + click: () => retry({ overwrite: true }), + icon: , + loading: false, + }, + { + show: true, + title: status === Status.finished ? t("deleteTask") : t("cancelAndDelete"), + click: cancel, + icon: , + loading: loading, + }, + { + show: status === Status.resumable, + title: t("selectAndResume"), + click: () => selectFile(uploader.task.dst, SelectType.File, uploader), + icon: , + loading: false, + }, + ]; + + return ( + <> + {actions.map((a) => ( + <> + {a.show && ( + + + { + e.stopPropagation(); + a.click(); + }} + size="small" + > + {a.icon} + + + + )} + + ))} + + ); + }, [status, loading, taskHover, fullScreen, uploader, t]); + + const fileIcon = useMemo(() => { + if (!fullScreen) { + return ; + } + }, [uploader, fullScreen]); + + return ( + <> + + + setTaskHover(false)} + onMouseOver={() => setTaskHover(true)} + > + {progressBar} + + {fileIcon} + + + {uploader.task.name} + +
    {resumeLabel}
    +
    {continueLabel}
    +
    + } + secondary={ + + {statusText} + + } + slotProps={{ + primary: { + variant: "body2", + }, + + secondary: { + variant: "caption", + }, + }} + /> + {secondaryAction} + +
    + + {taskDetail} + + + + ); +} diff --git a/src/component/Uploader/TaskListIconButton.tsx b/src/component/Uploader/TaskListIconButton.tsx new file mode 100755 index 0000000..4261dca --- /dev/null +++ b/src/component/Uploader/TaskListIconButton.tsx @@ -0,0 +1,36 @@ +import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts"; +import { Badge, Box, IconButton, Tooltip } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import UploadFilled from "../Icons/UploadFilled.tsx"; +import { useCallback } from "react"; +import { openUploadTaskList } from "../../redux/globalStateSlice.ts"; + +export const TaskListIconButton = () => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + const queued = useAppSelector((state) => state.globalState.uploadTaskCount); + + const openList = useCallback(() => { + dispatch(openUploadTaskList()); + }, [dispatch]); + + if (!queued) { + return null; + } + + return ( + + + + theme.typography.body2.fontSize }} + badgeContent={queued} + color={"secondary"} + > + + + + + + ); +}; diff --git a/src/component/Uploader/Uploader.tsx b/src/component/Uploader/Uploader.tsx new file mode 100755 index 0000000..f722b70 --- /dev/null +++ b/src/component/Uploader/Uploader.tsx @@ -0,0 +1,277 @@ +import dayjs from "dayjs"; +import { useSnackbar } from "notistack"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { ContextMenuTypes } from "../../redux/fileManagerSlice.ts"; +import { + closeUploadTaskList, + openUploadTaskList, + setUploadProgress, + setUploadRawFiles, +} from "../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts"; +import { refreshFileList, updateUserCapacity } from "../../redux/thunks/filemanager.ts"; +import SessionManager, { UserSettings } from "../../session"; +import useActionDisplayOpt from "../FileManager/ContextMenu/useActionDisplayOpt.ts"; +import { FileManagerIndex } from "../FileManager/FileManager.tsx"; +import UploadManager, { SelectType } from "./core"; +import { UploaderError } from "./core/errors"; +import Base, { Status } from "./core/uploader/base.ts"; +import { DropFileBackground } from "./DropFile.tsx"; +import PasteUploadDialog from "./PasteUploadDialog.tsx"; +import TaskList from "./Popup/TaskList.tsx"; + +let totalProgressCollector: NodeJS.Timeout | null = null; +let lastProgressStart = -1; +let dragCounter = 0; + +const defaultClipboardImageName = "image.png"; + +const Uploader = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + const [uploaders, setUploaders] = useState([]); + const [dropBgOpen, setDropBgOpen] = useState(false); + + const uploadEnabled = useRef(false); + + const totalProgress = useAppSelector((state) => state.globalState.uploadProgress); + const taskListOpen = useAppSelector((state) => state.globalState.uploadTaskListOpen); + const parent = useAppSelector((state) => state.fileManager[FileManagerIndex.main].list?.parent); + const path = useAppSelector((state) => state.fileManager[FileManagerIndex.main].pure_path); + const policy = useAppSelector((state) => state.fileManager[FileManagerIndex.main].list?.storage_policy); + const selectFileSignal = useAppSelector((state) => state.globalState.uploadFileSignal); + const selectFolderSignal = useAppSelector((state) => state.globalState.uploadFolderSignal); + const uploadRawPromiseId = useAppSelector((state) => state.globalState.uploadRawPromiseId); + const uploadRawFiles = useAppSelector((state) => state.globalState.uploadRawFiles); + + const displayOpt = useActionDisplayOpt([], ContextMenuTypes.empty, parent, FileManagerIndex.main); + + useEffect(() => { + if (!parent) { + uploadEnabled.current = false; + return; + } + uploadEnabled.current = displayOpt.showUpload ?? false; + }, [parent, displayOpt.showUpload]); + + const taskAdded = useCallback( + (original?: Base) => (tasks: Base[]) => { + if (original !== undefined) { + if (tasks.length !== 1 || tasks[0].key() !== original.key()) { + enqueueSnackbar(t("uploader.fileNotMatchError"), { + variant: "warning", + }); + return; + } + } + + tasks.forEach((t) => t.start()); + + dispatch(openUploadTaskList()); + setUploaders((uploaders) => { + if (original !== undefined) { + uploaders = uploaders.filter((u) => u.key() !== original.key()); + } + + return [...uploaders, ...tasks]; + }); + }, + [enqueueSnackbar, dispatch, setUploaders], + ); + + const uploadManager = useMemo(() => { + return new UploadManager({ + logLevel: "INFO", + concurrentLimit: parseInt(SessionManager.getWithFallback(UserSettings.ConcurrentLimit)), + overwrite: SessionManager.getWithFallback(UserSettings.UploadOverwrite), + dropZone: document.querySelector("body"), + onToast: (type, msg) => { + enqueueSnackbar(msg, { variant: type }); + }, + onDropOver: (_e) => { + if (!uploadEnabled.current) { + return; + } + dragCounter++; + setDropBgOpen((value) => !value); + }, + onDropLeave: (_e) => { + if (!uploadEnabled.current) { + return; + } + dragCounter--; + setDropBgOpen((value) => !value); + }, + onProactiveFileAdded: taskAdded(), + onPoolEmpty: () => { + setTimeout(() => { + dispatch(refreshFileList(0)); + dispatch(updateUserCapacity(0)); + }, 1000); + }, + }); + }, [enqueueSnackbar, taskAdded, dispatch]); + + useEffect(() => { + uploadManager.setPolicy(policy, path); + }, [policy, path]); + + const handleUploaderError = useCallback( + (e: any) => { + if (e instanceof UploaderError) { + enqueueSnackbar(e.Message(), { variant: "warning" }); + } else { + enqueueSnackbar(t("uploader:unknownError", { msg: e.message }), { + variant: "error", + }); + } + }, + [enqueueSnackbar, t], + ); + + const selectFile = useCallback( + (path: string, type = SelectType.File, original?: Base) => { + dispatch(openUploadTaskList()); + + // eslint-disable-next-line no-unreachable + uploadManager + .select(path, type) + .then(taskAdded(original)) + .catch((e) => { + handleUploaderError(e); + }); + }, + [uploadManager, taskAdded, handleUploaderError, dispatch], + ); + + const getClipboardFileName = useCallback( + (f: File) => { + if (f.type.startsWith("image") && f.name == defaultClipboardImageName) { + return t("uploader.clipboardDefaultFileName", { + date: dayjs().valueOf(), + }); + } + return f.name; + }, + [t], + ); + + const addRawFiles = useCallback( + (files: File[]) => { + uploadManager.addRawFiles(files, getClipboardFileName).catch((e) => { + handleUploaderError(e); + }); + }, + [uploadManager, taskAdded, handleUploaderError, dispatch, getClipboardFileName], + ); + + useEffect(() => { + if (uploadRawFiles && uploadRawFiles.length > 0) { + uploadManager.addRawFiles(uploadRawFiles, getClipboardFileName, uploadRawPromiseId).catch((e) => { + handleUploaderError(e); + }); + dispatch(setUploadRawFiles({ files: [], promiseId: [] })); + } + }, [uploadRawFiles, uploadRawPromiseId, handleUploaderError, uploadManager]); + + useEffect(() => { + const unfinished = uploadManager.resumeTasks(); + setUploaders((uploaders) => [ + ...uploaders, + ...unfinished.filter((u) => uploaders.find((v) => v.key() === u.key()) === undefined), + ]); + if (!totalProgressCollector) { + totalProgressCollector = setInterval(() => { + let totalSize = 0; + let processedSize = 0; + let total = 0; + let processed = 0; + + setUploaders((uploaders) => { + uploaders.forEach((u) => { + if (u.id <= lastProgressStart) { + return; + } + + totalSize += u.task.size; + total += 1; + + if (u.status === Status.finished || u.status === Status.canceled || u.status === Status.error) { + processedSize += u.task.size; + processed += 1; + } + + if ( + u.status === Status.added || + u.status === Status.initialized || + u.status === Status.queued || + u.status === Status.preparing || + u.status === Status.processing || + u.status === Status.finishing + ) { + processedSize += u.progress ? u.progress.total.loaded : 0; + } + }); + + if (total > 0 && processed === total) { + lastProgressStart = uploaders[uploaders.length - 1].id; + } + return uploaders; + }); + + dispatch( + setUploadProgress({ + progress: { + totalSize, + processedSize, + }, + count: total, + }), + ); + }, 2000); + } + + return () => { + if (uploadManager) { + uploadManager.destroy(); + } + }; + }, []); + + useEffect(() => { + if (selectFileSignal && selectFileSignal > 0 && path) { + selectFile(path); + } + }, [selectFileSignal]); + + useEffect(() => { + if (selectFolderSignal && selectFolderSignal > 0 && path) { + selectFile(path, SelectType.Directory); + } + }, [selectFolderSignal]); + + const deleteTask = useCallback((filter: (b: Base) => boolean) => { + setUploaders((uploaders) => uploaders.filter(filter)); + }, []); + + return ( + <> + + + dispatch(closeUploadTaskList())} + setUploaders={setUploaders} + /> + + ); +}; + +export default Uploader; diff --git a/src/component/Uploader/UseUpload.tsx b/src/component/Uploader/UseUpload.tsx new file mode 100755 index 0000000..77d1e05 --- /dev/null +++ b/src/component/Uploader/UseUpload.tsx @@ -0,0 +1,52 @@ +import React, { useEffect, useRef, useState } from "react"; +import Base, { RetryOption } from "./core/uploader/base.ts"; +import { useSnackbar } from "notistack"; + +export function useUpload(uploader: Base) { + const startLoadedRef = useRef(0); + const [status, setStatus] = useState(uploader.status); + const [error, setError] = useState(uploader.error); + const [progress, setProgress] = useState(uploader.progress); + const { enqueueSnackbar } = useSnackbar(); + + useEffect(() => { + /* eslint-disable @typescript-eslint/no-empty-function */ + uploader.subscribe({ + onTransition: (newStatus) => { + setStatus(newStatus); + }, + onError: (err) => { + setError(err); + setStatus(uploader.status); + }, + onProgress: (data) => { + setProgress(data); + }, + onMsg: (msg, color) => { + enqueueSnackbar(msg, { variant: color }); + }, + }); + }, []); + + // 获å–上传速度 + const [speed, speedAvg] = React.useMemo(() => { + if (progress == undefined || progress.total == null || progress.total.loaded == null) return [0, 0]; + const duration = (Date.now() - (uploader.lastTime || 0)) / 1000; + const durationTotal = (Date.now() - (uploader.startTime || 0)) / 1000; + const res = + progress.total.loaded > startLoadedRef.current + ? Math.floor((progress.total.loaded - startLoadedRef.current) / duration) + : 0; + const resAvg = progress.total.loaded > 0 ? Math.floor(progress.total.loaded / durationTotal) : 0; + + startLoadedRef.current = progress.total.loaded; + uploader.lastTime = Date.now(); + return [res, resAvg]; + }, [progress?.total.loaded]); + + const retry = (opt?: RetryOption) => { + uploader.retry(opt); + }; + + return { status, error, progress, speed, speedAvg, retry }; +} diff --git a/src/component/Uploader/core/api/index.ts b/src/component/Uploader/core/api/index.ts new file mode 100755 index 0000000..9154d94 --- /dev/null +++ b/src/component/Uploader/core/api/index.ts @@ -0,0 +1,390 @@ +import { OneDriveChunkResponse, QiniuChunkResponse, QiniuFinishUploadRequest, QiniuPartsInfo, S3Part } from "../types"; +import { OBJtoXML, request } from "../utils"; +import { + CreateUploadSessionError, + DeleteUploadSessionError, + HTTPError, + LocalChunkUploadError, + ObsFinishUploadError, + OneDriveChunkError, + OneDriveFinishUploadError, + QiniuChunkError, + QiniuFinishUploadError, + S3LikeChunkError, + S3LikeFinishUploadError, + S3LikeUploadCallbackError, + SlaveChunkUploadError, + UpyunUploadError, +} from "../errors"; +import { ChunkInfo, ChunkProgress } from "../uploader/chunk"; +import { Progress } from "../uploader/base"; +import { CancelToken } from "axios"; +import { UploadCredential, UploadSessionRequest } from "../../../../api/explorer.ts"; +import { store } from "../../../../redux/store.ts"; +import { + sendCreateUploadSession, + sendDeleteUploadSession, + sendOneDriveCompleteUpload, + sendS3LikeCompleteUpload, + sendUploadChunk, +} from "../../../../api/api.ts"; +import { AppError } from "../../../../api/request.ts"; + +export async function createUploadSession(req: UploadSessionRequest, _cancel: CancelToken): Promise { + try { + return await store.dispatch(sendCreateUploadSession(req)); + } catch (e) { + if (e instanceof AppError) { + throw new CreateUploadSessionError(e.response); + } + + throw e; + } +} + +export async function deleteUploadSession(id: string, uri: string): Promise { + try { + return await store.dispatch(sendDeleteUploadSession({ id, uri })); + } catch (e) { + if (e instanceof AppError) { + throw new DeleteUploadSessionError(e.response); + } + + throw e; + } +} + +export async function localUploadChunk( + sessionID: string, + chunk: ChunkInfo, + onProgress: (p: Progress) => void, + cancel: CancelToken, +): Promise { + try { + return await store.dispatch( + sendUploadChunk(sessionID, chunk.chunk, chunk.index, cancel, (progressEvent) => { + onProgress({ + loaded: progressEvent.loaded, + total: progressEvent.total, + }); + }), + ); + } catch (e) { + if (e instanceof AppError) { + throw new LocalChunkUploadError(e.response, chunk.index); + } + + throw e; + } +} + +export async function slaveUploadChunk( + url: string, + credential: string, + chunk: ChunkInfo, + onProgress: (p: Progress) => void, + cancel: CancelToken, +): Promise { + const res = await request(`${url}?chunk=${chunk.index}`, { + method: "post", + headers: { + "content-type": "application/octet-stream", + Authorization: credential, + }, + data: chunk.chunk, + onUploadProgress: (progressEvent) => { + onProgress({ + loaded: progressEvent.loaded, + total: progressEvent.total, + }); + }, + cancelToken: cancel, + }); + + if (res.data.code != 0) { + throw new SlaveChunkUploadError(res.data, chunk.index); + } + + return res.data.data; +} + +export async function oneDriveUploadChunk( + url: string, + range: string, // if range is empty, this will be an request to query the session status + chunk: ChunkInfo, + onProgress: (p: Progress) => void, + cancel: CancelToken, +): Promise { + const res = await request(url, { + method: range === "" ? "get" : "put", + headers: { + "content-type": "application/octet-stream", + ...(range !== "" && { "content-range": range }), + }, + data: chunk.chunk, + onUploadProgress: (progressEvent) => { + onProgress({ + loaded: progressEvent.loaded, + total: progressEvent.total, + }); + }, + cancelToken: cancel, + }).catch((e) => { + if (e instanceof HTTPError && e.response) { + throw new OneDriveChunkError(e.response.data); + } + + throw e; + }); + + return res.data; +} + +export async function finishOneDriveUpload(sessionID: string, sessionKey: string): Promise { + try { + return await store.dispatch(sendOneDriveCompleteUpload(sessionID, sessionKey)); + } catch (e) { + if (e instanceof AppError) { + throw new OneDriveFinishUploadError(e.response); + } + + throw e; + } +} + +export async function s3LikeUploadChunk( + url: string, + chunk: ChunkInfo, + onProgress: (p: Progress) => void, + cancel: CancelToken, +): Promise { + const res = await request(url, { + method: "put", + headers: { + "content-type": "application/octet-stream", + }, + data: chunk.chunk, + onUploadProgress: (progressEvent) => { + onProgress({ + loaded: progressEvent.loaded, + total: progressEvent.total, + }); + }, + cancelToken: cancel, + responseType: "document", + transformResponse: undefined, + }).catch((e) => { + if (e instanceof HTTPError && e.response) { + throw new S3LikeChunkError(e.response.data); + } + + throw e; + }); + + return res.headers["etag"]; +} + +export async function obsFinishUpload(url: string, chunks: ChunkProgress[], cancel: CancelToken): Promise { + let body = encodePartsXML(chunks); + const res = await request(url, { + method: "post", + cancelToken: cancel, + transformResponse: undefined, + data: body, + headers: { + "content-type": "application/octet-stream", + }, + validateStatus: function (status) { + return status == 200; + }, + }).catch((e) => { + if (e instanceof HTTPError && e.response?.data?.message) { + throw new ObsFinishUploadError(e.response.data); + } + + if (e instanceof HTTPError && e.response?.data) { + // Decode xml + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(e.response.data, "text/xml"); + throw new S3LikeFinishUploadError(xmlDoc); + } + + throw e; + }); + + return res.data; +} + +export async function s3LikeFinishUpload( + url: string, + isOss: boolean, + chunks: ChunkProgress[], + cancel: CancelToken, + headers?: { [key: string]: string }, +): Promise { + let body = ""; + if (!isOss) { + body += encodePartsXML(chunks); + } + + const res = await request(url, { + method: "post", + cancelToken: cancel, + responseType: "document", + transformResponse: undefined, + data: body, + headers: { + "content-type": "application/octet-stream", + ...headers, + }, + validateStatus: function (status) { + return status == 200; + }, + }).catch((e) => { + if (e instanceof HTTPError && e.response) { + throw new S3LikeFinishUploadError(e.response.data); + } + + throw e; + }); + + return res.data; +} + +export async function qiniuDriveUploadChunk( + url: string, + upToken: string, + chunk: ChunkInfo, + onProgress: (p: Progress) => void, + cancel: CancelToken, +): Promise { + const res = await request(`${url}/${chunk.index + 1}`, { + method: "put", + headers: { + "content-type": "application/octet-stream", + authorization: "UpToken " + upToken, + }, + data: chunk.chunk, + onUploadProgress: (progressEvent) => { + onProgress({ + loaded: progressEvent.loaded, + total: progressEvent.total, + }); + }, + cancelToken: cancel, + }).catch((e) => { + if (e instanceof HTTPError && e.response) { + throw new QiniuChunkError(e.response.data); + } + + throw e; + }); + + return res.data; +} + +export async function qiniuFinishUpload( + url: string, + upToken: string, + chunks: ChunkProgress[], + cancel: CancelToken, + mimeType?: string, +): Promise { + const content: QiniuFinishUploadRequest = { + mimeType, + parts: chunks.map((chunk): QiniuPartsInfo => { + return { + etag: chunk.etag!, + partNumber: chunk.index + 1, + }; + }), + }; + + const res = await request(`${url}`, { + method: "post", + headers: { + "content-type": "application/json", + authorization: "UpToken " + upToken, + }, + data: content, + cancelToken: cancel, + }).catch((e) => { + if (e instanceof HTTPError && e.response) { + throw new QiniuFinishUploadError(e.response.data); + } + + throw e; + }); + + return res.data; +} + +export async function upyunFormUploadChunk( + url: string, + file: File, + policy: string, + credential: string, + onProgress: (p: Progress) => void, + cancel: CancelToken, + mimeType?: string, +): Promise { + const bodyFormData = new FormData(); + bodyFormData.append("policy", policy); + bodyFormData.append("authorization", credential); + if (mimeType) { + bodyFormData.append("content-type", mimeType); + } + // File must be the last element in the form + bodyFormData.append("file", file); + + const res = await request(`${url}`, { + method: "post", + headers: { + "content-type": "multipart/form-data", + }, + data: bodyFormData, + onUploadProgress: (progressEvent) => { + onProgress({ + loaded: progressEvent.loaded, + total: progressEvent.total, + }); + }, + cancelToken: cancel, + }).catch((e) => { + if (e instanceof HTTPError && e.response) { + throw new UpyunUploadError(e.response.data); + } + + throw e; + }); + + return res.data; +} + +export async function s3LikeUploadCallback(sessionID: string, sessionKey: string, policyType: string): Promise { + try { + return await store.dispatch(sendS3LikeCompleteUpload(policyType, sessionID, sessionKey)); + } catch (e) { + if (e instanceof AppError) { + throw new S3LikeUploadCallbackError(e.response); + } + + throw e; + } +} + +const encodePartsXML = (chunks: ChunkProgress[]): string => { + let body = ""; + body += ""; + chunks.forEach((chunk) => { + body += ""; + const part: S3Part = { + PartNumber: chunk.index + 1, + ETag: chunk.etag!, + }; + body += OBJtoXML(part); + body += ""; + }); + body += ""; + return body; +}; diff --git a/src/component/Uploader/core/errors/index.ts b/src/component/Uploader/core/errors/index.ts new file mode 100755 index 0000000..f18a18e --- /dev/null +++ b/src/component/Uploader/core/errors/index.ts @@ -0,0 +1,421 @@ +import { StoragePolicy } from "../../../../api/explorer.ts"; +import { AppError, Response } from "../../../../api/request.ts"; +import i18next from "../../../../i18n"; +import { sizeToString } from "../../../../util"; +import { OneDriveError, QiniuError, UpyunError } from "../types"; + +export enum UploaderErrorName { + InvalidFile = "InvalidFile", + NoPolicySelected = "NoPolicySelected", + UnknownPolicyType = "UnknownPolicyType", + FailedCreateUploadSession = "FailedCreateUploadSession", + FailedDeleteUploadSession = "FailedDeleteUploadSession", + HTTPRequestFailed = "HTTPRequestFailed", + LocalChunkUploadFailed = "LocalChunkUploadFailed", + SlaveChunkUploadFailed = "SlaveChunkUploadFailed", + WriteCtxFailed = "WriteCtxFailed", + RemoveCtxFailed = "RemoveCtxFailed", + ReadCtxFailed = "ReadCtxFailed", + InvalidCtxData = "InvalidCtxData", + CtxExpired = "CtxExpired", + ProcessingTaskDuplicated = "ProcessingTaskDuplicated", + OneDriveChunkUploadFailed = "OneDriveChunkUploadFailed", + OneDriveEmptyFile = "OneDriveEmptyFile", + FailedFinishOneDriveUpload = "FailedFinishOneDriveUpload", + S3LikeChunkUploadFailed = "S3LikeChunkUploadFailed", + S3LikeUploadCallbackFailed = "S3LikeUploadCallbackFailed", + COSUploadCallbackFailed = "COSUploadCallbackFailed", + COSPostUploadFailed = "COSPostUploadFailed", + UpyunPostUploadFailed = "UpyunPostUploadFailed", + QiniuChunkUploadFailed = "QiniuChunkUploadFailed", + FailedFinishOSSUpload = "FailedFinishOSSUpload", + FailedFinishQiniuUpload = "FailedFinishQiniuUpload", + FailedTransformResponse = "FailedTransformResponse", + LoadBalancePolicyNoAvailable = "LoadBalancePolicyNoAvailable", +} + +const RETRY_ERROR_LIST = [ + UploaderErrorName.FailedCreateUploadSession, + UploaderErrorName.HTTPRequestFailed, + UploaderErrorName.LocalChunkUploadFailed, + UploaderErrorName.SlaveChunkUploadFailed, + UploaderErrorName.ProcessingTaskDuplicated, + UploaderErrorName.FailedTransformResponse, +]; + +const RETRY_CODE_LIST = [-1]; +const CONFLICT_ERROR_CODE = 40004; + +export class UploaderError implements Error { + public stack: string | undefined; + constructor( + public name: UploaderErrorName, + public message: string, + ) { + this.stack = new Error().stack; + } + + public Message(): string { + return this.message; + } + + public Retryable(): boolean { + return RETRY_ERROR_LIST.includes(this.name); + } +} + +// æ–‡ä»¶æœªé€šè¿‡å­˜å‚¨ç­–ç•¥éªŒè¯ +export class FileValidateError extends UploaderError { + // 未通过验è¯çš„æ–‡ä»¶å±žæ€§ + public field: "size" | "suffix" | "suffix_denied" | "regexp"; + + // 对应的存储策略 + public policy: StoragePolicy; + + constructor(message: string, field: "size" | "suffix" | "suffix_denied" | "regexp", policy: StoragePolicy) { + super(UploaderErrorName.InvalidFile, message); + this.field = field; + this.policy = policy; + } + + public Message(): string { + if (this.field == "size") { + return i18next.t(`uploader.sizeExceedLimitError`, { + max: sizeToString(this.policy.max_size), + }); + } + + if (this.field == "suffix_denied") { + return ( + i18next.t("uploader.suffixNotAllowedError") + + i18next.t(`uploader.suffixDenied`, { + denied: this.policy.denied_suffix ? this.policy.denied_suffix.join(",") : "*", + }) + ); + } + + if (this.field == "regexp") { + return i18next.t("uploader.regexpNotAllowedError"); + } + + return ( + i18next.t(`uploader.suffixNotAllowedError`) + + i18next.t(`uploader.suffixAllowed`, { + supported: this.policy.allowed_suffix ? this.policy.allowed_suffix.join(",") : "*", + }) + ); + } +} + +// 未知存储策略 +export class UnknownPolicyError extends UploaderError { + // 对应的存储策略 + public policy: StoragePolicy; + + constructor(message: string, policy: StoragePolicy) { + super(UploaderErrorName.UnknownPolicyType, message); + this.policy = policy; + } +} + +// åŽç«¯ API 出错 +export class APIError extends UploaderError { + private appError: AppError; + constructor( + name: UploaderErrorName, + message: string, + protected response: Response, + ) { + super(name, message); + this.appError = new AppError(response); + } + + public Message(): string { + return `${this.message}: ${this.appError.message}`; + } + + public Retryable(): boolean { + return super.Retryable() && RETRY_CODE_LIST.includes(this.response.code); + } + + public IsConflictError(): boolean { + return this.response.code == CONFLICT_ERROR_CODE; + } +} + +// æ— æ³•åˆ›å»ºä¸Šä¼ ä¼šè¯ +export class CreateUploadSessionError extends APIError { + constructor(response: Response) { + super(UploaderErrorName.FailedCreateUploadSession, "", response); + } + + public Message(): string { + this.message = i18next.t(`uploader.createUploadSessionError`); + return super.Message(); + } +} + +// æ— æ³•åˆ é™¤ä¸Šä¼ ä¼šè¯ +export class DeleteUploadSessionError extends APIError { + constructor(response: Response) { + super(UploaderErrorName.FailedDeleteUploadSession, "", response); + } + + public Message(): string { + this.message = i18next.t(`uploader.deleteUploadSessionError`); + return super.Message(); + } +} + +// HTTP 请求出错 +export class HTTPError extends UploaderError { + public response?: any; + constructor( + public axiosErr: any, + protected url: string, + ) { + super(UploaderErrorName.HTTPRequestFailed, axiosErr.message); + this.response = axiosErr.response; + } + + public Message(): string { + return i18next.t(`uploader.requestError`, { + msg: this.axiosErr, + url: this.url, + }); + } +} + +// 本地分å—上传失败 +export class LocalChunkUploadError extends APIError { + constructor( + response: Response, + protected chunkIndex: number, + ) { + super(UploaderErrorName.LocalChunkUploadFailed, "", response); + } + + public Message(): string { + this.message = i18next.t(`uploader.chunkUploadError`, { + index: this.chunkIndex, + }); + return super.Message(); + } +} + +// 从机分å—上传失败 +export class SlaveChunkUploadError extends APIError { + constructor( + response: Response, + protected chunkIndex: number, + ) { + super(UploaderErrorName.SlaveChunkUploadFailed, "", response); + } + + public Message(): string { + this.message = i18next.t(`uploader.chunkUploadError`, { + index: this.chunkIndex, + }); + return super.Message(); + } +} + +// ä¸Šä¼ ä»»åŠ¡å†²çª +export class ProcessingTaskDuplicatedError extends UploaderError { + constructor() { + super(UploaderErrorName.ProcessingTaskDuplicated, "Processing task duplicated"); + } + + public Message(): string { + return i18next.t(`uploader.conflictError`); + } +} + +// OneDrive 分å—上传失败 +export class OneDriveChunkError extends UploaderError { + constructor(public response: OneDriveError) { + super(UploaderErrorName.OneDriveChunkUploadFailed, response.error.message); + } + + public Message(): string { + let msg = i18next.t(`uploader.chunkUploadErrorWithMsg`, { + msg: this.message, + }); + + if (this.response.error.retryAfterSeconds != undefined) { + msg += + " " + + i18next.t(`uploader.chunkUploadErrorWithRetryAfter`, { + retryAfter: this.response.error.retryAfterSeconds, + }); + } + + return msg; + } + + public Retryable(): boolean { + return super.Retryable() || this.response.error.retryAfterSeconds != undefined; + } +} + +// OneDrive 选择了空文件上传 +export class OneDriveEmptyFileSelected extends UploaderError { + constructor() { + super(UploaderErrorName.OneDriveEmptyFile, "empty file not supported"); + } + + public Message(): string { + return i18next.t("uploader.emptyFileError"); + } +} + +// OneDrive æ— æ³•å®Œæˆæ–‡ä»¶ä¸Šä¼  +export class OneDriveFinishUploadError extends APIError { + constructor(response: Response) { + super(UploaderErrorName.FailedFinishOneDriveUpload, "", response); + } + + public Message(): string { + this.message = i18next.t("uploader.finishUploadError"); + return super.Message(); + } +} + +// S3 类策略分å—上传失败 +export class S3LikeChunkError extends UploaderError { + constructor(public response: Document) { + super(UploaderErrorName.S3LikeChunkUploadFailed, response.getElementsByTagName("Message")[0].innerHTML); + } + + public Message(): string { + return i18next.t(`uploader.chunkUploadErrorWithMsg`, { + msg: this.message, + }); + } +} + +// OSS 完æˆä¼ å¤±è´¥ +export class S3LikeFinishUploadError extends UploaderError { + constructor(public response: Document) { + super(UploaderErrorName.S3LikeChunkUploadFailed, response.getElementsByTagName("Message")[0].innerHTML); + } + + public Message(): string { + return i18next.t(`uploader.ossFinishUploadError`, { + msg: this.message, + code: this.response.getElementsByTagName("Code")[0].innerHTML, + }); + } +} + +export interface ObsJsonError { + message: string; + code: string; +} + +export class ObsFinishUploadError extends UploaderError { + constructor(public response: ObsJsonError) { + super(UploaderErrorName.S3LikeChunkUploadFailed, response.message); + } + + public Message(): string { + return i18next.t(`uploader.ossFinishUploadError`, { + msg: this.message, + code: this.response.code, + }); + } +} + +// qiniu 分å—上传失败 +export class QiniuChunkError extends UploaderError { + constructor(public response: QiniuError) { + super(UploaderErrorName.QiniuChunkUploadFailed, response.error); + } + + public Message(): string { + return i18next.t(`uploader.chunkUploadErrorWithMsg`, { + msg: this.message, + }); + } +} + +// qiniu 完æˆä¼ å¤±è´¥ +export class QiniuFinishUploadError extends UploaderError { + constructor(public response: QiniuError) { + super(UploaderErrorName.FailedFinishQiniuUpload, response.error); + } + + public Message(): string { + return i18next.t(`uploader.finishUploadErrorWithMsg`, { + msg: this.message, + }); + } +} + +// COS 上传失败 +export class COSUploadError extends UploaderError { + constructor(public response: Document) { + super(UploaderErrorName.COSPostUploadFailed, response.getElementsByTagName("Message")[0].innerHTML); + } + + public Message(): string { + return i18next.t(`uploader.cosUploadFailed`, { + msg: this.message, + code: this.response.getElementsByTagName("Code")[0].innerHTML, + }); + } +} + +// COS 无法完æˆä¸Šä¼ å›žè°ƒ +export class COSUploadCallbackError extends APIError { + constructor(response: Response) { + super(UploaderErrorName.COSUploadCallbackFailed, "", response); + } + + public Message(): string { + this.message = i18next.t("uploader.finishUploadError"); + return super.Message(); + } +} + +// Upyun 上传失败 +export class UpyunUploadError extends UploaderError { + constructor(public response: UpyunError) { + super(UploaderErrorName.UpyunPostUploadFailed, response.message); + } + + public Message(): string { + return i18next.t("uploader.upyunUploadFailed", { + msg: this.message, + }); + } +} + +// S3 无法完æˆä¸Šä¼ å›žè°ƒ +export class S3LikeUploadCallbackError extends APIError { + constructor(response: Response) { + super(UploaderErrorName.S3LikeUploadCallbackFailed, "", response); + } + + public Message(): string { + this.message = i18next.t("uploader.finishUploadError"); + return super.Message(); + } +} + +// 无法解æžå“应 +export class TransformResponseError extends UploaderError { + constructor( + private response: string, + parseError: Error, + ) { + super(UploaderErrorName.FailedTransformResponse, parseError.message); + } + + public Message(): string { + return i18next.t("uploader.parseResponseError", { + msg: this.message, + content: this.response, + }); + } +} diff --git a/src/component/Uploader/core/index.ts b/src/component/Uploader/core/index.ts new file mode 100755 index 0000000..612cef9 --- /dev/null +++ b/src/component/Uploader/core/index.ts @@ -0,0 +1,287 @@ +import { PolicyType, StoragePolicy } from "../../../api/explorer.ts"; +import { defaultPath } from "../../../hooks/useNavigation.tsx"; +import { UnknownPolicyError, UploaderError, UploaderErrorName } from "./errors"; +import Logger, { LogLevel } from "./logger"; +import { Task, TaskType } from "./types"; +import Base, { MessageColor } from "./uploader/base"; +import COS from "./uploader/cos"; +import KS3 from "./uploader/ks3"; +import Local from "./uploader/local"; +import OBS from "./uploader/obs.ts"; +import OneDrive from "./uploader/onedrive"; +import OSS from "./uploader/oss"; +import ResumeHint from "./uploader/placeholder"; +import Qiniu from "./uploader/qiniu"; +import Remote from "./uploader/remote"; +import S3 from "./uploader/s3"; +import Upyun from "./uploader/upyun"; +import { + cleanupResumeCtx, + getAllFileEntries, + getDirectoryUploadDst, + getFileInput, + isFileDrop, + listResumeCtx, +} from "./utils"; +import { Pool } from "./utils/pool"; + +export interface Option { + logLevel: LogLevel; + concurrentLimit: number; + overwrite?: boolean; + dropZone: HTMLElement | null; + onDropOver?: (e: DragEvent) => void; + onDropLeave?: (e: DragEvent) => void; + onToast: (type: MessageColor, msg: string) => void; + onPoolEmpty?: () => void; + onProactiveFileAdded?: (uploaders: Base[]) => void; +} + +export enum SelectType { + File, + Directory, +} + +export default class UploadManager { + public logger: Logger; + public pool: Pool; + public overwrite?: boolean; + private static id = 0; + private policy?: StoragePolicy; + private fileInput: HTMLInputElement; + private directoryInput: HTMLInputElement; + private id = ++UploadManager.id; + // used for proactive upload (drop, paste) + private currentPath?: string; + private options: Option; + + constructor(private o: Option) { + this.logger = new Logger(o.logLevel, "MANAGER"); + this.logger.info(`Initialized with log level: ${o.logLevel}`); + + this.pool = new Pool(o.concurrentLimit, o.onPoolEmpty); + this.fileInput = getFileInput(this.id, false); + this.directoryInput = getFileInput(this.id, true); + this.overwrite = o.overwrite; + this.options = o; + + if (o.dropZone) { + this.logger.info(`Drag and drop container set to:`, o.dropZone); + o.dropZone.addEventListener("dragenter", this.dragEnter); + o.dropZone.addEventListener("dragleave", this.dragLeave); + o.dropZone.addEventListener("drop", this.onFileDroppedIn); + } + + window.addEventListener("beforeunload", this.beforeLeave); + } + + destroy() { + if (this.options.dropZone) { + this.options.dropZone.removeEventListener("dragenter", this.dragEnter); + this.options.dropZone.removeEventListener("dragleave", this.dragLeave); + this.options.dropZone.removeEventListener("drop", this.onFileDroppedIn); + } + } + + beforeLeave = (e: BeforeUnloadEvent) => { + if (this.pool.processing.length == 0) { + return; + } + var confirmationMessage = + "It looks like you have been editing something. " + "If you leave before saving, your changes will be lost."; + + (e || window.event).returnValue = confirmationMessage; //Gecko + IE + return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc. + }; + + changeConcurrentLimit = (newLimit: number) => { + this.pool.limit = newLimit; + }; + + dispatchUploader(task: Task): Base { + if (task.type == TaskType.resumeHint) { + return new ResumeHint(task, this); + } + + if (task.policy.relay) { + this.logger.info("Upload relay is enabled, cast to local uploader."); + return new Local(task, this); + } + + switch (task.policy.type) { + case PolicyType.local: + return new Local(task, this); + case PolicyType.remote: + return new Remote(task, this); + case PolicyType.onedrive: + return new OneDrive(task, this); + case PolicyType.oss: + return new OSS(task, this); + case PolicyType.qiniu: + return new Qiniu(task, this); + case PolicyType.cos: + return new COS(task, this); + case PolicyType.upyun: + return new Upyun(task, this); + case PolicyType.s3: + return new S3(task, this); + case PolicyType.ks3: + return new KS3(task, this); + case PolicyType.obs: + return new OBS(task, this); + default: + throw new UnknownPolicyError("Unknown policy type.", task.policy); + } + } + + // 设定当å‰å­˜å‚¨ç­–ç•¥ + public setPolicy(p?: StoragePolicy, path?: string) { + this.policy = p; + this.currentPath = path; + this.logger.info(`Switching path to:`, path); + if (p == undefined) { + this.logger.info(`Currently no policy selected`); + return; + } + + this.logger.info(`Switching policy to:`, p); + + if (p.allowed_suffix != undefined && p.allowed_suffix.length > 0) { + const acceptVal = p.allowed_suffix + .map((v) => { + return "." + v; + }) + .join(","); + this.logger.info(`Set allowed file suffix to ${acceptVal}`); + this.fileInput.setAttribute("accept", acceptVal); + } else { + this.logger.info(`Set allowed file suffix to *`); + this.fileInput.removeAttribute("accept"); + } + } + + // 选择文件 + public select = (dst: string, type = SelectType.File): Promise => { + return new Promise((resolve, reject) => { + if (this.policy == undefined) { + this.logger.warn(`Calling file selector while no policy is set`); + throw new UploaderError(UploaderErrorName.NoPolicySelected, "No policy selected."); + } + + this.fileInput.onchange = (ev: Event) => this.addFiles(ev, dst, resolve, reject); + this.directoryInput.onchange = (ev: Event) => this.addFiles(ev, dst, resolve, reject); + this.fileInput.value = ""; + this.directoryInput.value = ""; + type == SelectType.File ? this.fileInput.click() : this.directoryInput.click(); + }); + }; + + public resumeTasks = (): Base[] => { + const tasks = listResumeCtx(this.logger); + if (tasks.length > 0) { + this.logger.info(`Resumed ${tasks.length} unfinished task(s) from local storage:`, tasks); + } + return tasks + .filter((t) => t.chunkProgress.length > 0 && t.chunkProgress[0].loaded > 0) + .map((t) => this.dispatchUploader({ ...t, type: TaskType.resumeHint })); + }; + + public cleanupSessions = () => { + cleanupResumeCtx(this.logger); + }; + + public addRawFiles = async ( + files: File[], + getName?: (file: File) => string, + promiseIds?: string[], + ): Promise => { + if (!this.currentPath) { + return undefined; + } + const uploaders = await new Promise((resolve, reject) => + this.addFiles(files, this.currentPath ?? defaultPath, resolve, reject, getName), + ); + if (promiseIds) { + uploaders.forEach((u, i) => { + if (promiseIds[i]) { + u.promiseId = promiseIds[i]; + } + }); + } + this.o.onProactiveFileAdded && this.o.onProactiveFileAdded(uploaders); + }; + + private dragEnter = (e: DragEvent) => { + if (isFileDrop(e) && this.currentPath) { + e.preventDefault(); + if (this.options.onDropOver) { + this.options.onDropOver(e); + } + } + }; + + private dragLeave = (e: DragEvent) => { + if (isFileDrop(e) && this.currentPath) { + e.preventDefault(); + if (this.options.onDropLeave) { + this.options.onDropLeave(e); + } + } + }; + + private addFiles = ( + ev: Event | File[], + dst: string, + resolve: (value: Base[] | PromiseLike) => void, + reject: (reason?: any) => void, + getName?: (file: File) => string, + ) => { + let files: File[] = []; + if (ev instanceof Event) { + const target = ev.target as HTMLInputElement; + if (!ev || !target || !target.files) return; + if (target.files.length > 0) { + files = Array.from(target.files); + } + } else { + files = ev as File[]; + } + + if (files.length > 0) { + let uploaders: Base[] = []; + try { + uploaders = files.map((file): Base => { + return this.dispatchUploader({ + type: TaskType.file, + policy: this.policy as StoragePolicy, + dst: getDirectoryUploadDst(dst, file), + file: file, + size: file.size, + overwrite: this.overwrite, + name: getName ? getName(file) : file.name, + chunkProgress: [], + resumed: false, + }); + }); + resolve(uploaders); + } catch (e) { + reject(e); + } + } + }; + + private onFileDroppedIn = async (e: DragEvent) => { + if (!this.currentPath) { + return; + } + const containFile = e.dataTransfer && e.dataTransfer.types.includes("Files"); + if (containFile) { + this.o.onDropLeave && this.o.onDropLeave(e); + const items = await getAllFileEntries(e.dataTransfer!.items); + const uploaders = await new Promise((resolve, reject) => + this.addFiles(items, this.currentPath as string, resolve, reject), + ); + this.o.onProactiveFileAdded && this.o.onProactiveFileAdded(uploaders); + } + }; +} diff --git a/src/component/Uploader/core/logger.ts b/src/component/Uploader/core/logger.ts new file mode 100755 index 0000000..27b9e45 --- /dev/null +++ b/src/component/Uploader/core/logger.ts @@ -0,0 +1,37 @@ +export type LogLevel = "INFO" | "WARN" | "ERROR" | "OFF"; + +export default class Logger { + constructor( + public level: LogLevel = "OFF", + private prefix = "UPLOAD", + private id: number = 1, + ) {} + + private getPrintPrefix(level: LogLevel) { + return `Cloudreve-Uploader [${level}][${this.prefix}#${this.id}]:`; + } + + info(...args: unknown[]) { + const allowLevel: LogLevel[] = ["INFO"]; + if (allowLevel.includes(this.level)) { + // eslint-disable-next-line no-console + console.log(this.getPrintPrefix("INFO"), ...args); + } + } + + warn(...args: unknown[]) { + const allowLevel: LogLevel[] = ["INFO", "WARN"]; + if (allowLevel.includes(this.level)) { + // eslint-disable-next-line no-console + console.warn(this.getPrintPrefix("WARN"), ...args); + } + } + + error(...args: unknown[]) { + const allowLevel: LogLevel[] = ["INFO", "WARN", "ERROR"]; + if (allowLevel.includes(this.level)) { + // eslint-disable-next-line no-console + console.error(this.getPrintPrefix("ERROR"), ...args); + } + } +} diff --git a/src/component/Uploader/core/types.ts b/src/component/Uploader/core/types.ts new file mode 100755 index 0000000..9aa4a56 --- /dev/null +++ b/src/component/Uploader/core/types.ts @@ -0,0 +1,68 @@ +import { ChunkProgress } from "./uploader/chunk"; +import { StoragePolicy, UploadCredential } from "../../../api/explorer.ts"; + +export enum TaskType { + file, + resumeHint, +} + +export interface Task { + type: TaskType; + name: string; + size: number; + policy: StoragePolicy; + dst: string; + file: File; + child?: Task[]; + session?: UploadCredential; + chunkProgress: ChunkProgress[]; + resumed: boolean; + overwrite?: boolean; +} + +type Nullable = T | null; + +export interface OneDriveError { + error: { + code: string; + message: string; + innererror?: { + code: string; + }; + retryAfterSeconds?: number; + }; +} + +export interface OneDriveChunkResponse { + expirationDateTime: string; + nextExpectedRanges: string[]; +} + +export interface QiniuChunkResponse { + etag: string; + md5: string; +} + +export interface QiniuError { + error: string; +} + +export interface QiniuPartsInfo { + etag: string; + partNumber: number; +} + +export interface QiniuFinishUploadRequest { + parts: QiniuPartsInfo[]; + mimeType?: string; +} + +export interface UpyunError { + message: string; + code: number; +} + +export interface S3Part { + ETag: string; + PartNumber: number; +} diff --git a/src/component/Uploader/core/uploader/base.ts b/src/component/Uploader/core/uploader/base.ts new file mode 100755 index 0000000..b760c64 --- /dev/null +++ b/src/component/Uploader/core/uploader/base.ts @@ -0,0 +1,270 @@ +// 所有 Uploader 的基类 +import axios, { CanceledError, CancelTokenSource } from "axios"; +import { PolicyType } from "../../../../api/explorer.ts"; +import CrUri from "../../../../util/uri.ts"; +import { createUploadSession, deleteUploadSession } from "../api"; +import { UploaderError } from "../errors"; +import UploadManager from "../index"; +import Logger from "../logger"; +import { Task } from "../types"; +import * as utils from "../utils"; +import { CancelToken } from "../utils/request"; +import { validate } from "../utils/validator"; + +export enum Status { + added, + resumable, + initialized, + queued, + preparing, + processing, + finishing, + finished, + error, + canceled, +} + +export interface UploadHandlers { + onTransition: (newStatus: Status) => void; + onError: (err: Error) => void; + onProgress: (data: UploadProgress) => void; + onMsg: (msg: string, color: MessageColor) => void; +} +export type MessageColor = "error" | "default" | "success" | "warning" | "info" | "loading" | undefined; + +export interface UploadProgress { + total: ProgressCompose; + chunks?: ProgressCompose[]; +} + +export interface ProgressCompose { + size: number; + loaded: number; + percent: number; + fromCache?: boolean; +} + +export interface Progress { + total?: number; + loaded: number; +} + +export interface RetryOption { + overwrite?: boolean; + new_prefix?: string; +} + +const resumePolicy = [ + PolicyType.local, + PolicyType.remote, + PolicyType.qiniu, + PolicyType.oss, + PolicyType.onedrive, + PolicyType.s3, + PolicyType.ks3, +]; +const deleteUploadSessionDelay = 500; + +export const uploadPromisePool: { + [key: string]: { + resolve: (value: Task | PromiseLike) => void; + reject: (reason?: any) => void; + }; +} = {}; + +export default abstract class Base { + public child?: Base[]; + public status: Status = Status.added; + public error?: Error; + public progress?: UploadProgress; + + public id = ++Base.id; + private static id = 0; + + protected logger: Logger; + protected subscriber: UploadHandlers; + // ç”¨äºŽå–æ¶ˆè¯·æ±‚ + protected cancelToken: CancelTokenSource = CancelToken.source(); + + public lastTime = Date.now(); + public startTime = Date.now(); + public promiseId: string | undefined; + + constructor( + public task: Task, + protected manager: UploadManager, + ) { + this.logger = new Logger(this.manager.logger.level, "UPLOADER", this.id); + this.logger.info("Initialize new uploader for task: ", task); + this.subscriber = { + /* eslint-disable @typescript-eslint/no-empty-function */ + onTransition: (_newStatus: Status) => {}, + onError: (_err: Error) => {}, + onProgress: (_data: UploadProgress) => {}, + onMsg: (_msg, _color: MessageColor) => {}, + /* eslint-enable @typescript-eslint/no-empty-function */ + }; + } + + public subscribe = (handlers: UploadHandlers) => { + this.subscriber = handlers; + }; + + public start = async () => { + this.logger.info("Activate uploading task"); + this.transit(Status.initialized); + this.lastTime = this.startTime = Date.now(); + + try { + validate(this.task.file, this.task.policy); + } catch (e) { + this.logger.error("File validate failed with error:", e); + this.setError(e as Error); + return; + } + + this.logger.info("Enqueued in manager pool"); + this.transit(Status.queued); + this.manager.pool.enqueue(this).catch((e) => { + this.logger.info("Upload task failed with error:", e); + this.setError(e); + }); + }; + + public run = async () => { + this.logger.info("Start upload task, create upload session..."); + this.transit(Status.preparing); + const cachedInfo = utils.getResumeCtx(this.task, this.logger); + if (cachedInfo == null) { + const crUri = new CrUri(this.task.dst); + crUri.join(this.task.name); + this.task.session = await createUploadSession( + { + uri: crUri.toString(), + size: this.task.file.size, + policy_id: this.task.policy.id, + last_modified: this.task.file.lastModified, + mime_type: this.task.file.type, + entity_type: this.task.overwrite ? "version" : undefined, + }, + this.cancelToken.token, + ); + this.logger.info("Upload session created:", this.task.session); + } else { + this.task.session = cachedInfo.session; + this.task.resumed = true; + this.task.chunkProgress = cachedInfo.chunkProgress; + this.logger.info("Resume upload from cached ctx:", cachedInfo); + } + + this.transit(Status.processing); + await this.upload(); + await this.afterUpload(); + utils.removeResumeCtx(this.task, this.logger); + this.transit(Status.finished); + this.logger.info("Upload task completed"); + }; + + public abstract upload(): Promise; + protected async afterUpload(): Promise { + return; + } + + public cancel = async () => { + if (this.status === Status.finished) { + return; + } + + this.cancelToken.cancel(); + await this.cancelUploadSession(); + this.transit(Status.canceled); + }; + + public retry = (opt?: RetryOption) => { + this.reset(); + if (opt?.overwrite) { + this.task.overwrite = true; + } else if (opt?.new_prefix) { + this.task.name = opt.new_prefix + this.task.name; + } + this.start(); + }; + + protected reset = () => { + this.cancelToken = axios.CancelToken.source(); + this.progress = { + total: { + size: 0, + loaded: 0, + percent: 0, + }, + }; + }; + + protected setError(e: Error) { + if (e instanceof CanceledError) { + return; + } + + if (!(e instanceof UploaderError && e.Retryable()) || !resumePolicy.includes(this.task.policy.type)) { + this.logger.warn("Non-resume error occurs, clean resume ctx cache"); + this.cancelUploadSession(); + } + + this.status = Status.error; + this.error = e; + this.subscriber.onError(e); + } + + protected cancelUploadSession = (): Promise => { + return new Promise((resolve) => { + utils.removeResumeCtx(this.task, this.logger); + if (this.task.session) { + setTimeout(() => { + deleteUploadSession(this.task.session!?.session_id, this.task.session!?.uri) + .catch((e) => { + this.logger.warn("Failed to cancel upload session: ", e); + }) + .finally(() => { + resolve(); + }); + }, deleteUploadSessionDelay); + } else { + resolve(); + } + }); + }; + + protected transit(status: Status) { + this.status = status; + if (this.promiseId && status === Status.finished) { + const promise = uploadPromisePool[this.promiseId]; + delete uploadPromisePool[this.promiseId]; + this.promiseId = undefined; + if (promise) { + switch (status) { + case Status.finished: + promise.resolve(this.task); + break; + default: + promise.reject(this.error); + break; + } + } + } + this.subscriber.onTransition(status); + } + + public getProgressInfoItem(loaded: number, size: number, fromCache?: boolean): ProgressCompose { + return { + size, + loaded, + percent: (loaded / size) * 100, + ...(fromCache == null ? {} : { fromCache }), + }; + } + + public key(): string { + return utils.getResumeCtxKey(this.task); + } +} diff --git a/src/component/Uploader/core/uploader/chunk.ts b/src/component/Uploader/core/uploader/chunk.ts new file mode 100755 index 0000000..f7c61a3 --- /dev/null +++ b/src/component/Uploader/core/uploader/chunk.ts @@ -0,0 +1,201 @@ +import * as utils from "../utils"; +import Base from "./base"; + +export interface ChunkProgress { + loaded: number; + index: number; + etag?: string; +} + +export interface ChunkInfo { + chunk: Blob; + index: number; +} + +export default abstract class Chunk extends Base { + protected chunks: Blob[] = []; + private readonly DEFAULT_CONCURRENCY = 1; // Default concurrent uploads + private readonly MAX_RETRIES = 3; + private progressUpdateMutex = Promise.resolve(); // Ensure progress updates are serialized + + public upload = async () => { + this.logger.info("Preparing uploading file chunks."); + this.initBeforeUploadChunks(); + + this.logger.info("Starting concurrent uploading of file chunks:", this.chunks); + this.updateLocalCache(); + + await this.uploadChunksWithDynamicPool(); + }; + + private async uploadChunksWithDynamicPool() { + // Get chunks that need to be uploaded + const chunksToUpload = this.chunks + .map((chunk, index) => ({ chunk, index })) + .filter(({ chunk, index }) => this.task.chunkProgress[index].loaded < chunk.size || chunk.size === 0); + + if (chunksToUpload.length === 0) { + this.logger.info("All chunks already uploaded, skipping."); + return; + } + + this.logger.info(`Found ${chunksToUpload.length} chunks to upload out of ${this.chunks.length} total chunks.`); + + const concurrency = this.getConcurrency(); + let chunkIndex = 0; + let activeCount = 0; + let hasError = false; + let firstError: any = null; + + // Helper function to start a new upload with immediate callback + const startUpload = (chunkInfo: { chunk: Blob; index: number }): Promise => { + // Don't start new uploads if there's already an error + if (hasError) { + return Promise.resolve(); + } + + activeCount++; + this.logger.info(`Starting chunk [${chunkInfo.index}], active uploads: ${activeCount}`); + + return this.uploadChunkWithRetryAndCallback(chunkInfo, () => { + this.updateLocalCache(); + }) + .then(() => { + activeCount--; + this.logger.info(`Chunk [${chunkInfo.index}] completed successfully, active uploads: ${activeCount}`); + + // Start next chunk immediately if available and no error occurred + if (!hasError && chunkIndex < chunksToUpload.length) { + startUpload(chunksToUpload[chunkIndex++]); + } + }) + .catch((error) => { + activeCount--; + this.logger.error(`Chunk [${chunkInfo.index}] failed, stopping all uploads:`, error); + + // Mark error state to stop new uploads + hasError = true; + if (!firstError) { + firstError = error; + } + + // Cancel all remaining uploads + this.cancelToken.cancel(); + }); + }; + + // Start initial uploads up to concurrency limit + const initialPromises: Promise[] = []; + while (chunkIndex < chunksToUpload.length && initialPromises.length < concurrency) { + initialPromises.push(startUpload(chunksToUpload[chunkIndex++])); + } + + // Wait for all uploads to complete or fail + await Promise.allSettled(initialPromises); + + // Wait for any remaining uploads started by callbacks to finish + while (activeCount > 0) { + await new Promise((resolve) => setTimeout(resolve, 100)); // Small delay to check active count + } + + // Throw error immediately if any chunk failed + if (firstError) { + this.logger.error("Upload process stopped due to chunk failure"); + throw firstError; + } + + this.logger.info("All chunks uploaded successfully."); + } + + private async uploadChunkWithRetryAndCallback( + chunkInfo: ChunkInfo, + onComplete: () => void, + retryCount = 0, + ): Promise { + // Check for cancellation before attempting upload + if (this.cancelToken.token.reason) { + throw new Error("Upload cancelled by user"); + } + + try { + await this.uploadChunk(chunkInfo); + this.logger.info(`Chunk [${chunkInfo.index}] uploaded successfully.`); + onComplete(); // Call callback immediately after successful upload + } catch (error) { + // Don't retry if upload was cancelled + if (this.cancelToken.token.reason) { + throw error; + } + + if (retryCount < this.MAX_RETRIES) { + this.logger.warn( + `Chunk [${chunkInfo.index}] upload failed, retrying (${retryCount + 1}/${this.MAX_RETRIES}):`, + error, + ); + + // Wait with exponential backoff, but check for cancellation + const delay = Math.pow(2, retryCount) * 1000; + await new Promise((resolve, reject) => { + const timeout = setTimeout(resolve, delay); + + // Cancel the timeout if upload is cancelled + this.cancelToken.token.promise?.then(() => { + clearTimeout(timeout); + reject(new Error("Upload cancelled during retry delay")); + }); + }); + + await this.uploadChunkWithRetryAndCallback(chunkInfo, onComplete, retryCount + 1); + } else { + this.logger.error(`Chunk [${chunkInfo.index}] upload failed after ${this.MAX_RETRIES} retries:`, error); + throw error; + } + } + } + + private getConcurrency(): number { + return this.task.policy.chunk_concurrency || this.DEFAULT_CONCURRENCY; + } + + private initBeforeUploadChunks() { + this.chunks = utils.getChunks(this.task.file, this.task.session?.chunk_size); + const cachedInfo = utils.getResumeCtx(this.task, this.logger); + if (cachedInfo == null) { + this.task.chunkProgress = this.chunks.map( + (_, index): ChunkProgress => ({ + loaded: 0, + index, + }), + ); + } + + this.notifyResumeProgress(); + } + + protected abstract uploadChunk(chunkInfo: ChunkInfo): Promise; + + protected updateChunkProgress(loaded: number, index: number, etag?: string) { + // Serialize progress updates to avoid race conditions in concurrent uploads + this.progressUpdateMutex = this.progressUpdateMutex.then(async () => { + this.task.chunkProgress[index].loaded = loaded; + if (etag) { + this.task.chunkProgress[index].etag = etag; + } + this.notifyResumeProgress(); + }); + } + + private notifyResumeProgress() { + this.progress = { + total: this.getProgressInfoItem(utils.sumChunk(this.task.chunkProgress), this.task.file.size + 1), + chunks: this.chunks.map((chunk, index) => { + return this.getProgressInfoItem(this.task.chunkProgress[index].loaded, chunk.size, false); + }), + }; + this.subscriber.onProgress(this.progress); + } + + private updateLocalCache() { + utils.setResumeCtx(this.task, this.logger); + } +} diff --git a/src/component/Uploader/core/uploader/cos.ts b/src/component/Uploader/core/uploader/cos.ts new file mode 100755 index 0000000..7b960d5 --- /dev/null +++ b/src/component/Uploader/core/uploader/cos.ts @@ -0,0 +1,30 @@ +import { Status } from "./base"; +import { s3LikeFinishUpload, s3LikeUploadCallback, s3LikeUploadChunk } from "../api"; +import Chunk, { ChunkInfo } from "./chunk.ts"; +import { PolicyType } from "../../../../api/explorer.ts"; + +export default class COS extends Chunk { + protected async uploadChunk(chunkInfo: ChunkInfo) { + const etag = await s3LikeUploadChunk( + this.task.session?.upload_urls[chunkInfo.index]!, + chunkInfo, + (p) => { + this.updateChunkProgress(p.loaded, chunkInfo.index); + }, + this.cancelToken.token, + ); + + this.task.chunkProgress[chunkInfo.index].etag = etag; + } + + protected async afterUpload(): Promise { + this.logger.info(`Finishing multipart upload...`); + this.transit(Status.finishing); + await s3LikeFinishUpload(this.task.session!.completeURL, false, this.task.chunkProgress, this.cancelToken.token, { + "x-cos-forbid-overwrite": "true", + }); + + this.logger.info(`Sending S3-like upload callback...`); + return s3LikeUploadCallback(this.task.session!.session_id, this.task.session!.callback_secret, PolicyType.cos); + } +} diff --git a/src/component/Uploader/core/uploader/ks3.ts b/src/component/Uploader/core/uploader/ks3.ts new file mode 100755 index 0000000..5531837 --- /dev/null +++ b/src/component/Uploader/core/uploader/ks3.ts @@ -0,0 +1,28 @@ +import Chunk, { ChunkInfo } from "./chunk"; +import { s3LikeFinishUpload, s3LikeUploadCallback, s3LikeUploadChunk } from "../api"; +import { Status } from "./base"; +import { PolicyType } from "../../../../api/explorer.ts"; + +export default class KS3 extends Chunk { + protected async uploadChunk(chunkInfo: ChunkInfo) { + const etag = await s3LikeUploadChunk( + this.task.session?.upload_urls[chunkInfo.index]!, + chunkInfo, + (p) => { + this.updateChunkProgress(p.loaded, chunkInfo.index); + }, + this.cancelToken.token, + ); + + this.task.chunkProgress[chunkInfo.index].etag = etag; + } + + protected async afterUpload(): Promise { + this.logger.info(`Finishing multipart upload...`); + this.transit(Status.finishing); + await s3LikeFinishUpload(this.task.session!.completeURL, false, this.task.chunkProgress, this.cancelToken.token); + + this.logger.info(`Sending S3-like upload callback...`); + return s3LikeUploadCallback(this.task.session!.session_id, this.task.session!.callback_secret, PolicyType.ks3); + } +} diff --git a/src/component/Uploader/core/uploader/local.ts b/src/component/Uploader/core/uploader/local.ts new file mode 100755 index 0000000..5539776 --- /dev/null +++ b/src/component/Uploader/core/uploader/local.ts @@ -0,0 +1,15 @@ +import Chunk, { ChunkInfo } from "./chunk"; +import { localUploadChunk } from "../api"; + +export default class Local extends Chunk { + protected async uploadChunk(chunkInfo: ChunkInfo) { + return localUploadChunk( + this.task.session?.session_id!, + chunkInfo, + (p) => { + this.updateChunkProgress(p.loaded, chunkInfo.index); + }, + this.cancelToken.token, + ); + } +} diff --git a/src/component/Uploader/core/uploader/obs.ts b/src/component/Uploader/core/uploader/obs.ts new file mode 100755 index 0000000..840cedf --- /dev/null +++ b/src/component/Uploader/core/uploader/obs.ts @@ -0,0 +1,24 @@ +import Chunk, { ChunkInfo } from "./chunk"; +import { obsFinishUpload, s3LikeUploadChunk } from "../api"; +import { Status } from "./base"; + +export default class OBS extends Chunk { + protected async uploadChunk(chunkInfo: ChunkInfo) { + const etag = await s3LikeUploadChunk( + this.task.session?.upload_urls[chunkInfo.index]!, + chunkInfo, + (p) => { + this.updateChunkProgress(p.loaded, chunkInfo.index); + }, + this.cancelToken.token, + ); + + this.task.chunkProgress[chunkInfo.index].etag = etag; + } + + protected async afterUpload(): Promise { + this.logger.info(`Finishing multipart upload...`); + this.transit(Status.finishing); + return obsFinishUpload(this.task.session!.completeURL, this.task.chunkProgress, this.cancelToken.token); + } +} diff --git a/src/component/Uploader/core/uploader/onedrive.ts b/src/component/Uploader/core/uploader/onedrive.ts new file mode 100755 index 0000000..4b99312 --- /dev/null +++ b/src/component/Uploader/core/uploader/onedrive.ts @@ -0,0 +1,74 @@ +import Chunk, { ChunkInfo } from "./chunk"; +import { finishOneDriveUpload, oneDriveUploadChunk } from "../api"; +import { OneDriveChunkError, OneDriveEmptyFileSelected } from "../errors"; +import { Status } from "./base"; + +export default class OneDrive extends Chunk { + protected async uploadChunk(chunkInfo: ChunkInfo) { + if (chunkInfo.chunk.size === 0) { + throw new OneDriveEmptyFileSelected(); + } + + const rangeEnd = (this.progress?.total.loaded ?? 0) + chunkInfo.chunk.size - 1; + return this.sendRange(chunkInfo, this.progress?.total.loaded ?? 0, rangeEnd, 0).catch((e) => { + if ( + e instanceof OneDriveChunkError && + e.response.error.innererror && + e.response.error.innererror.code == "fragmentOverlap" + ) { + return this.alignChunkOffset(chunkInfo); + } + + throw e; + }); + } + + private async sendRange(chunkInfo: ChunkInfo, start: number, end: number, chunkOffset: number) { + const range = `bytes ${start}-${end}/${this.task.file.size}`; + return oneDriveUploadChunk( + `${this.task.session?.upload_urls[0]!}`, + range, + chunkInfo, + (p) => { + this.updateChunkProgress(chunkOffset + p.loaded, chunkInfo.index); + }, + this.cancelToken.token, + ); + } + + private async alignChunkOffset(chunkInfo: ChunkInfo) { + this.logger.info(`Chunk [${chunkInfo.index}] overlapped, checking next expected range...`); + const rangeStatus = await oneDriveUploadChunk( + `${this.task.session?.upload_urls[0]!}`, + "", + chunkInfo, + (_p) => { + return null; + }, + this.cancelToken.token, + ); + const expectedStart = parseInt(rangeStatus.nextExpectedRanges[0].split("-")[0]); + this.logger.info(`Next expected range start from OneDrive is ${expectedStart}.`); + + const loaded = this.progress?.total.loaded ?? 0; + + if (expectedStart >= loaded) { + this.logger.info(`This whole chunk is overlapped, skipping...`); + this.updateChunkProgress(chunkInfo.chunk.size, chunkInfo.index); + return; + } else { + this.updateChunkProgress(0, chunkInfo.index); + const rangeEnd = loaded + chunkInfo.chunk.size - 1; + const newChunkOffset = expectedStart - loaded; + chunkInfo.chunk = chunkInfo.chunk.slice(newChunkOffset); + this.updateChunkProgress(newChunkOffset, chunkInfo.index); + return this.sendRange(chunkInfo, expectedStart, rangeEnd, newChunkOffset); + } + } + + protected async afterUpload(): Promise { + this.logger.info(`Finishing upload...`); + this.transit(Status.finishing); + return finishOneDriveUpload(this.task.session!.session_id, this.task.session!.callback_secret); + } +} diff --git a/src/component/Uploader/core/uploader/oss.ts b/src/component/Uploader/core/uploader/oss.ts new file mode 100755 index 0000000..1e62572 --- /dev/null +++ b/src/component/Uploader/core/uploader/oss.ts @@ -0,0 +1,25 @@ +import { s3LikeFinishUpload, s3LikeUploadChunk } from "../api"; +import { Status } from "./base"; +import Chunk, { ChunkInfo } from "./chunk"; + +export default class OSS extends Chunk { + protected async uploadChunk(chunkInfo: ChunkInfo) { + return s3LikeUploadChunk( + this.task.session?.upload_urls[chunkInfo.index]!, + chunkInfo, + (p) => { + this.updateChunkProgress(p.loaded, chunkInfo.index); + }, + this.cancelToken.token, + ); + } + + protected async afterUpload(): Promise { + this.logger.info(`Finishing multipart upload...`); + this.transit(Status.finishing); + return s3LikeFinishUpload(this.task.session!.completeURL, true, this.task.chunkProgress, this.cancelToken.token, { + "x-oss-forbid-overwrite": "true", + "x-oss-complete-all": "yes", + }); + } +} diff --git a/src/component/Uploader/core/uploader/placeholder.ts b/src/component/Uploader/core/uploader/placeholder.ts new file mode 100755 index 0000000..58b6ae6 --- /dev/null +++ b/src/component/Uploader/core/uploader/placeholder.ts @@ -0,0 +1,21 @@ +import Chunk, { ChunkInfo } from "./chunk"; +import { qiniuDriveUploadChunk, qiniuFinishUpload } from "../api"; +import { Status } from "./base"; +import { Task } from "../types"; +import UploadManager from "../index"; +import * as utils from "../utils"; + +export default class ResumeHint extends Chunk { + constructor(task: Task, manager: UploadManager) { + super(task, manager); + this.status = Status.resumable; + this.progress = { + total: this.getProgressInfoItem(utils.sumChunk(this.task.chunkProgress), this.task.size + 1), + }; + this.subscriber.onProgress(this.progress); + } + + protected async uploadChunk(chunkInfo: ChunkInfo) { + return null; + } +} diff --git a/src/component/Uploader/core/uploader/qiniu.ts b/src/component/Uploader/core/uploader/qiniu.ts new file mode 100755 index 0000000..61622b7 --- /dev/null +++ b/src/component/Uploader/core/uploader/qiniu.ts @@ -0,0 +1,31 @@ +import Chunk, { ChunkInfo } from "./chunk"; +import { qiniuDriveUploadChunk, qiniuFinishUpload } from "../api"; +import { Status } from "./base"; + +export default class Qiniu extends Chunk { + protected async uploadChunk(chunkInfo: ChunkInfo) { + const chunkRes = await qiniuDriveUploadChunk( + this.task.session?.upload_urls[0]!, + this.task.session?.credential!, + chunkInfo, + (p) => { + this.updateChunkProgress(p.loaded, chunkInfo.index); + }, + this.cancelToken.token, + ); + + this.task.chunkProgress[chunkInfo.index].etag = chunkRes.etag; + } + + protected async afterUpload(): Promise { + this.logger.info(`Finishing multipart upload...`); + this.transit(Status.finishing); + return qiniuFinishUpload( + this.task.session?.upload_urls[0]!, + this.task.session?.credential!, + this.task.chunkProgress, + this.cancelToken.token, + this.task.session?.mime_type, + ); + } +} diff --git a/src/component/Uploader/core/uploader/remote.ts b/src/component/Uploader/core/uploader/remote.ts new file mode 100755 index 0000000..16e8486 --- /dev/null +++ b/src/component/Uploader/core/uploader/remote.ts @@ -0,0 +1,16 @@ +import Chunk, { ChunkInfo } from "./chunk"; +import { slaveUploadChunk } from "../api"; + +export default class Remote extends Chunk { + protected async uploadChunk(chunkInfo: ChunkInfo) { + return slaveUploadChunk( + `${this.task.session?.upload_urls[0]!}`, + this.task.session?.credential!, + chunkInfo, + (p) => { + this.updateChunkProgress(p.loaded, chunkInfo.index); + }, + this.cancelToken.token, + ); + } +} diff --git a/src/component/Uploader/core/uploader/s3.ts b/src/component/Uploader/core/uploader/s3.ts new file mode 100755 index 0000000..65a44fb --- /dev/null +++ b/src/component/Uploader/core/uploader/s3.ts @@ -0,0 +1,28 @@ +import Chunk, { ChunkInfo } from "./chunk"; +import { s3LikeFinishUpload, s3LikeUploadCallback, s3LikeUploadChunk } from "../api"; +import { Status } from "./base"; +import { PolicyType } from "../../../../api/explorer.ts"; + +export default class OSS extends Chunk { + protected async uploadChunk(chunkInfo: ChunkInfo) { + const etag = await s3LikeUploadChunk( + this.task.session?.upload_urls[chunkInfo.index]!, + chunkInfo, + (p) => { + this.updateChunkProgress(p.loaded, chunkInfo.index); + }, + this.cancelToken.token, + ); + + this.task.chunkProgress[chunkInfo.index].etag = etag; + } + + protected async afterUpload(): Promise { + this.logger.info(`Finishing multipart upload...`); + this.transit(Status.finishing); + await s3LikeFinishUpload(this.task.session!.completeURL, false, this.task.chunkProgress, this.cancelToken.token); + + this.logger.info(`Sending S3-like upload callback...`); + return s3LikeUploadCallback(this.task.session!.session_id, this.task.session!.callback_secret, PolicyType.s3); + } +} diff --git a/src/component/Uploader/core/uploader/upyun.ts b/src/component/Uploader/core/uploader/upyun.ts new file mode 100755 index 0000000..09192d0 --- /dev/null +++ b/src/component/Uploader/core/uploader/upyun.ts @@ -0,0 +1,21 @@ +import Base from "./base"; +import { upyunFormUploadChunk } from "../api"; + +export default class Upyun extends Base { + public upload = async () => { + this.logger.info("Starting uploading file stream:", this.task.file); + await upyunFormUploadChunk( + this.task.session?.upload_urls[0]!, + this.task.file, + this.task.session?.upload_policy!, + this.task.session?.credential!, + (p) => { + this.subscriber.onProgress({ + total: this.getProgressInfoItem(p.loaded, p.total ?? 1), + }); + }, + this.cancelToken.token, + this.task.session?.mime_type, + ); + }; +} diff --git a/src/component/Uploader/core/utils/helper.ts b/src/component/Uploader/core/utils/helper.ts new file mode 100755 index 0000000..e8d7573 --- /dev/null +++ b/src/component/Uploader/core/utils/helper.ts @@ -0,0 +1,279 @@ +import CrUri from "../../../../util/uri"; +import { UploaderError, UploaderErrorName } from "../errors"; +import Logger from "../logger"; +import { Task } from "../types"; +import { ChunkProgress } from "../uploader/chunk"; + +// æ–‡ä»¶åˆ†å— +export function getChunks(file: File, chunkByteSize: number | undefined): Blob[] { + // 如果 chunkByteSize 比文件大或为0ï¼Œåˆ™ç›´æŽ¥å–æ–‡ä»¶çš„å¤§å° + if (!chunkByteSize || chunkByteSize > file.size || chunkByteSize == 0) { + chunkByteSize = file.size; + } + + const chunks: Blob[] = []; + const count = Math.ceil(file.size / chunkByteSize); + for (let i = 0; i < count; i++) { + const chunk = file.slice(chunkByteSize * i, i === count - 1 ? file.size : chunkByteSize * (i + 1)); + chunks.push(chunk); + } + + if (chunks.length == 0) { + chunks.push(file.slice(0)); + } + return chunks; +} + +export function sumChunk(list: ChunkProgress[]) { + return list.reduce((data, loaded) => data + loaded.loaded, 0); +} + +const resumeKeyPrefix = "cd_upload_ctx_"; + +function isTask(toBeDetermined: Task | string): toBeDetermined is Task { + return !!(toBeDetermined as Task).name; +} + +export function getResumeCtxKey(task: Task | string): string { + if (isTask(task)) { + return `${resumeKeyPrefix}name_${task.name}_dst_${task.dst}_size_${task.size}_policy_${task.policy.id}`; + } + + return task; +} + +export function setResumeCtx(task: Task, logger: Logger) { + const ctxKey = getResumeCtxKey(task); + try { + localStorage.setItem(ctxKey, JSON.stringify(task)); + } catch (err) { + logger.warn(new UploaderError(UploaderErrorName.WriteCtxFailed, `setResumeCtx failed: ${ctxKey}`)); + } +} + +export function removeResumeCtx(task: Task | string, logger: Logger) { + const ctxKey = getResumeCtxKey(task); + try { + localStorage.removeItem(ctxKey); + } catch (err) { + logger.warn(new UploaderError(UploaderErrorName.RemoveCtxFailed, `removeResumeCtx failed. key: ${ctxKey}`)); + } +} + +export function cleanupResumeCtx(logger: Logger) { + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key && key.startsWith(resumeKeyPrefix)) { + try { + localStorage.removeItem(key); + } catch (err) { + logger.warn(new UploaderError(UploaderErrorName.RemoveCtxFailed, `removeResumeCtx failed. key: ${key}`)); + } + } + } +} + +export function getResumeCtx(task: Task | string, logger: Logger): Task | null { + const ctxKey = getResumeCtxKey(task); + let localInfoString: string | null = null; + try { + localInfoString = localStorage.getItem(ctxKey); + } catch { + logger.warn(new UploaderError(UploaderErrorName.ReadCtxFailed, `getResumeCtx failed. key: ${ctxKey}`)); + } + + if (localInfoString == null) { + return null; + } + + let localInfo: Task | null = null; + try { + localInfo = JSON.parse(localInfoString); + } catch { + // 本地信æ¯å·²è¢«ç ´å,直接删除 + removeResumeCtx(task, logger); + logger.warn(new UploaderError(UploaderErrorName.InvalidCtxData, `getResumeCtx failed to parse. key: ${ctxKey}`)); + } + + if (localInfo && localInfo.session && localInfo.session.expires < Math.floor(Date.now() / 1000)) { + removeResumeCtx(task, logger); + logger.warn( + new UploaderError( + UploaderErrorName.CtxExpired, + `upload session already expired at ${localInfo.session.expires}. key: ${ctxKey}`, + ), + ); + return null; + } + + return localInfo; +} + +export function listResumeCtx(logger: Logger): Task[] { + const res: Task[] = []; + for (let i = 0, len = localStorage.length; i < len; i++) { + const key = localStorage.key(i); + if (key && key.startsWith(resumeKeyPrefix)) { + const value = getResumeCtx(key, logger); + if (value) { + res.push(value); + } + } + } + + return res; +} + +export function OBJtoXML(obj: any): string { + let xml = ""; + for (const prop in obj) { + xml += "<" + prop + ">"; + if (Array.isArray(obj[prop])) { + for (const array of obj[prop]) { + // A real botch fix here + xml += ""; + xml += "<" + prop + ">"; + + xml += OBJtoXML(new Object(array)); + } + } else if (typeof obj[prop] == "object") { + xml += OBJtoXML(new Object(obj[prop])); + } else { + xml += obj[prop]; + } + xml += ""; + } + return xml.replace(/<\/?[0-9]{1,}>/g, ""); +} + +export function getFileInput(id: number, isFolder: boolean): HTMLInputElement { + const input = document.createElement("input"); + input.type = "file"; + input.id = `upload-file-input-${id}`; + if (isFolder) { + input.id = `upload-folder-input-${id}`; + input.setAttribute("webkitdirectory", "true"); + input.setAttribute("mozdirectory", "true"); + } else { + input.id = `upload-file-input-${id}`; + input.multiple = true; + } + input.hidden = true; + document.body.appendChild(input); + return input; +} + +export function pathJoin(parts: string[], sep = "/"): string { + parts = parts.map((part, index) => { + if (index) { + part = part.replace(new RegExp("^" + sep), ""); + } + if (index !== parts.length - 1) { + part = part.replace(new RegExp(sep + "$"), ""); + } + return part; + }); + return parts.join(sep); +} + +function basename(path: string): string { + const pathList = path.split("/"); + pathList.pop(); + return pathList.join("/") === "" ? "/" : pathList.join("/"); +} + +export function trimPrefix(src: string, prefix: string): string { + if (src.startsWith(prefix)) { + return src.slice(prefix.length); + } + return src; +} + +export function getDirectoryUploadDst(dst: string, file: any): string { + let relPath = file.webkitRelativePath; + if (!relPath || relPath == "") { + relPath = file.fsPath; + if (!relPath || relPath == "") { + return dst; + } + } + + const dstCrUrl = new CrUri(dst); + relPath = trimPrefix(relPath, "/"); + return dstCrUrl + .join(...relPath.split("/")) + .parent() + .toString(); +} + +// Wrap readEntries in a promise to make working with readEntries easier +async function readEntriesPromise(directoryReader: any): Promise { + try { + return await new Promise((resolve, reject) => { + directoryReader.readEntries(resolve, reject); + }); + } catch (err) { + console.log(err); + } +} + +async function readFilePromise(fileReader: any, path: string): Promise { + try { + return await new Promise((resolve, _reject) => { + fileReader.file((file: any) => { + file.fsPath = path; + resolve(file); + }); + }); + } catch (err) { + console.log(err); + } +} + +// Get all the entries (files or sub-directories) in a directory by calling readEntries until it returns empty array +async function readAllDirectoryEntries(directoryReader: any): Promise { + const entries: any[] = []; + let readEntries = await readEntriesPromise(directoryReader); + while (readEntries.length > 0) { + entries.push(...readEntries); + readEntries = await readEntriesPromise(directoryReader); + } + return entries; +} + +// Drop handler function to get all files +export async function getAllFileEntries(dataTransferItemList: DataTransferItemList): Promise { + const fileEntries: any[] = []; + // Use BFS to traverse entire directory/file structure + const queue: any[] = []; + // Unfortunately dataTransferItemList is not iterable i.e. no forEach + for (let i = 0; i < dataTransferItemList.length; i++) { + const fileEntry = dataTransferItemList[i].webkitGetAsEntry(); + if (!fileEntry) { + const file = dataTransferItemList[i].getAsFile(); + if (file) { + fileEntries.push(file); + } + } + + queue.push(dataTransferItemList[i].webkitGetAsEntry()); + } + while (queue.length > 0) { + const entry = queue.shift(); + if (!entry) { + continue; + } + if (entry.isFile) { + fileEntries.push(await readFilePromise(entry, entry.fullPath)); + } else if (entry.isDirectory) { + const reader = entry.createReader(); + const entries: any = await readAllDirectoryEntries(reader); + queue.push(...entries); + } + } + return fileEntries; +} + +export function isFileDrop(e: DragEvent): boolean { + return !!e.dataTransfer && e.dataTransfer.types.includes("Files"); +} diff --git a/src/component/Uploader/core/utils/index.ts b/src/component/Uploader/core/utils/index.ts new file mode 100755 index 0000000..7ca10e3 --- /dev/null +++ b/src/component/Uploader/core/utils/index.ts @@ -0,0 +1,4 @@ +export * from "./pool"; +export * from "./helper"; +export * from "./validator"; +export * from "./request"; diff --git a/src/component/Uploader/core/utils/pool.ts b/src/component/Uploader/core/utils/pool.ts new file mode 100755 index 0000000..e94fcc0 --- /dev/null +++ b/src/component/Uploader/core/utils/pool.ts @@ -0,0 +1,74 @@ +import { ProcessingTaskDuplicatedError } from "../errors"; +import Base from "../uploader/base"; + +export interface QueueContent { + uploader: Base; + resolve: () => void; + reject: (err?: any) => void; +} + +export class Pool { + queue: Array = []; + processing: Array = []; + onPoolEmpty?: () => void; + + constructor( + public limit: number, + onPoolEmpty?: () => void, + ) { + this.onPoolEmpty = onPoolEmpty; + } + + enqueue(uploader: Base) { + return new Promise((resolve, reject) => { + this.queue.push({ + uploader, + resolve, + reject, + }); + this.check(); + }); + } + + release(item: QueueContent) { + this.processing = this.processing.filter((v) => v !== item); + if (this.queue.length === 0 && this.processing.length === 0) { + this.onPoolEmpty?.(); + } + this.check(); + } + + run(item: QueueContent) { + this.queue = this.queue.filter((v) => v !== item); + if ( + this.processing.findIndex( + (v) => v.uploader.task.dst == item.uploader.task.dst && v.uploader.task.file.name == item.uploader.task.name, + ) > -1 + ) { + // 找到é‡å任务 + item.reject(new ProcessingTaskDuplicatedError()); + this.release(item); + return; + } + + this.processing.push(item); + item.uploader.run().then( + () => { + item.resolve(); + this.release(item); + }, + (err) => { + item.reject(err); + this.release(item); + }, + ); + } + + check() { + const processingNum = this.processing.length; + const availableNum = Math.max(0, this.limit - processingNum); + this.queue.slice(0, availableNum).forEach((item) => { + this.run(item); + }); + } +} diff --git a/src/component/Uploader/core/utils/request.ts b/src/component/Uploader/core/utils/request.ts new file mode 100755 index 0000000..075109a --- /dev/null +++ b/src/component/Uploader/core/utils/request.ts @@ -0,0 +1,41 @@ +import axios, { AxiosRequestConfig } from "axios"; +import { Response } from "../types"; +import { HTTPError, TransformResponseError } from "../errors"; + +export const { CancelToken } = axios; + +const baseConfig = { + transformResponse: [ + (response: any) => { + try { + return JSON.parse(response); + } catch (e) { + throw new TransformResponseError(response, e); + } + }, + ], +}; + +const cdBackendConfig = { + ...baseConfig, + baseURL: "/api/v4", + withCredentials: true, +}; + +export function request(url: string, config?: AxiosRequestConfig) { + return axios.request({ ...baseConfig, ...config, url }).catch((err) => { + if (axios.isCancel(err)) { + throw err; + } + + if (err instanceof TransformResponseError) { + throw err; + } + + throw new HTTPError(err, url); + }); +} + +export function requestAPI(url: string, config?: AxiosRequestConfig) { + return request>(url, { ...cdBackendConfig, ...config }); +} diff --git a/src/component/Uploader/core/utils/validator.ts b/src/component/Uploader/core/utils/validator.ts new file mode 100755 index 0000000..523c6b0 --- /dev/null +++ b/src/component/Uploader/core/utils/validator.ts @@ -0,0 +1,47 @@ +import { StoragePolicy } from "../../../../api/explorer.ts"; +import { FileValidateError } from "../errors"; + +interface Validator { + (file: File, policy: StoragePolicy): void; +} + +// validators +const checkers: Array = [ + function checkExt(file: File, policy: StoragePolicy) { + const ext = file?.name.split(".").pop(); + if (policy.allowed_suffix != undefined && policy.allowed_suffix.length > 0) { + if (ext === null || !ext || !policy.allowed_suffix.includes(ext)) + throw new FileValidateError("File suffix not allowed in policy.", "suffix", policy); + } + if (policy.denied_suffix != undefined && policy.denied_suffix.length > 0) { + if (ext && policy.denied_suffix.includes(ext)) + throw new FileValidateError("File suffix not allowed in policy.", "suffix_denied", policy); + } + }, + + function checkRegexp(file: File, policy: StoragePolicy) { + if (policy.allowed_name_regexp != undefined && policy.allowed_name_regexp.length > 0) { + if (!new RegExp(policy.allowed_name_regexp).test(file.name)) + throw new FileValidateError("File name must match the allowed regexp.", "regexp", policy); + } + if (policy.denied_name_regexp != undefined && policy.denied_name_regexp.length > 0) { + if (new RegExp(policy.denied_name_regexp).test(file.name)) + throw new FileValidateError("File name must not match the regexp.", "regexp", policy); + } + }, + + function checkSize(file: File, policy: StoragePolicy) { + if (policy.max_size > 0) { + if (file.size > policy.max_size) { + throw new FileValidateError("File size exceeds maximum limit.", "size", policy); + } + } + }, +]; + +/* å°†æ¯ä¸ª Validator 执行 + 失败返回 Error + */ +export function validate(file: File, policy: StoragePolicy) { + checkers.forEach((c) => c(file, policy)); +} diff --git a/src/component/Viewers/ArchivePreview/ArchivePreview.tsx b/src/component/Viewers/ArchivePreview/ArchivePreview.tsx new file mode 100755 index 0000000..47bd900 --- /dev/null +++ b/src/component/Viewers/ArchivePreview/ArchivePreview.tsx @@ -0,0 +1,466 @@ +import { Box, Breadcrumbs, Button, Link, Table, TableCell, TableContainer, Typography, useTheme } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { TableVirtuoso } from "react-virtuoso"; +import { getArchiveListFiles } from "../../../api/api.ts"; +import { ArchivedFile, FileType } from "../../../api/explorer.ts"; +import { closeArchiveViewer, setExtractArchiveDialog } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { fileBase, fileExtension, getFileLinkedUri, sizeToString } from "../../../util"; +import AutoHeight from "../../Common/AutoHeight.tsx"; +import EncodingSelector, { defaultEncodingValue } from "../../Common/Form/EncodingSelector.tsx"; +import { SecondaryButton, StyledCheckbox, StyledTableContainerPaper } from "../../Common/StyledComponents.tsx"; +import TimeBadge from "../../Common/TimeBadge.tsx"; +import FileIcon from "../../FileManager/Explorer/FileIcon.tsx"; +import ChevronRight from "../../Icons/ChevronRight.tsx"; +import Folder from "../../Icons/Folder.tsx"; +import Home from "../../Icons/Home.tsx"; +import ViewerDialog, { ViewerLoading } from "../ViewerDialog.tsx"; + +const ArchivePreview = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + const { enqueueSnackbar } = useSnackbar(); + const viewerState = useAppSelector((state) => state.globalState.archiveViewer); + + const [loading, setLoading] = useState(false); + const [files, setFiles] = useState([]); + const [currentPath, setCurrentPath] = useState(""); + const [selectedFiles, setSelectedFiles] = useState([]); + const [filterText, setFilterText] = useState(""); + const [height, setHeight] = useState(33); + const [encoding, setEncoding] = useState(defaultEncodingValue); + + const isZip = useMemo(() => { + return fileExtension(viewerState?.file?.name ?? "") === "zip"; + }, [viewerState?.file?.name]); + + const currentFiles = useMemo(() => { + if (!files) return []; + + if (!currentPath) { + return files.filter((file) => !file.name.includes("/")); + } + + // 如果在å­ç›®å½•,显示该目录下的文件和文件夹 + const pathPrefix = currentPath.endsWith("/") ? currentPath : currentPath + "/"; + const pathFiles = files.filter((file) => file.name.startsWith(pathPrefix) && file.name !== currentPath); + + // 去é‡å¹¶è½¬æ¢ä¸ºç›¸å¯¹è·¯å¾„ + const relativePaths = new Set(); + const result: ArchivedFile[] = []; + + pathFiles.forEach((file) => { + const relativePath = file.name.substring(pathPrefix.length); + const firstSlash = relativePath.indexOf("/"); + + if (firstSlash === -1) { + if (!relativePaths.has(relativePath)) { + relativePaths.add(relativePath); + result.push({ + ...file, + name: relativePath, + }); + } + } else { + const dirName = relativePath.substring(0, firstSlash); + if (!relativePaths.has(dirName)) { + relativePaths.add(dirName); + result.push({ + name: dirName, + size: 0, + updated_at: file.updated_at, + is_directory: true, + }); + } + } + }); + + return result; + }, [files, currentPath]); + + // 过滤文件 + const filteredFiles = useMemo(() => { + if (!filterText) return currentFiles; + return currentFiles.filter((file) => file.name.toLowerCase().includes(filterText.toLowerCase())); + }, [currentFiles, filterText]); + + // é¢åŒ…屑路径 + const breadcrumbPaths = useMemo(() => { + if (!currentPath) return []; + return currentPath.split("/").filter(Boolean); + }, [currentPath]); + + // 规范化路径,去除开头å¯èƒ½å­˜åœ¨çš„ `/` + const normalizeName = (name: string) => { + if (name && typeof name === "string" && name.startsWith("/")) { + return name.slice(1); + } + return name; + }; + + useEffect(() => { + if (!viewerState || !viewerState.open) { + setEncoding(defaultEncodingValue); + return; + } + + setLoading(true); + setFiles([]); + setCurrentPath(""); + setSelectedFiles([]); + setFilterText(""); + + dispatch( + getArchiveListFiles({ + uri: getFileLinkedUri(viewerState.file), + entity: viewerState.version, + text_encoding: encoding !== defaultEncodingValue ? encoding : undefined, + }), + ) + .then((res) => { + if (res.files) { + // è¡¥é½ç›®å½• + const allItems: ArchivedFile[] = []; + const allDirs = new Set(); + + // 目录项 + res.files + .filter((item) => item.is_directory) + .forEach((item) => { + const normalizedName = normalizeName(item.name); + allItems.push({ + ...item, + name: normalizedName, + }); + allDirs.add(normalizedName); + }); + + // 文件项,并补é½ç¼ºå¤±ç›®å½• + res.files + .filter((item) => !item.is_directory) + .forEach((item) => { + const normalizedName = normalizeName(item.name); + allItems.push({ + ...item, + name: normalizedName, + }); + + const dirElements = normalizedName.split("/"); + for (let i = 1; i < dirElements.length; i++) { + const dirName = dirElements.slice(0, i).join("/"); + if (!allDirs.has(dirName)) { + allDirs.add(dirName); + allItems.push({ + name: dirName, + size: 0, + updated_at: "1970-01-01T00:00:00Z", + is_directory: true, + }); + } + } + }); + + // æŽ’åºæ–‡ä»¶ + // å…ˆç›®å½•ï¼ŒåŽæ–‡ä»¶ï¼Œåˆ†åˆ«æŒ‰åç§°æŽ’åº + allItems.sort((a, b) => { + if (a.is_directory && !b.is_directory) return -1; + if (!a.is_directory && b.is_directory) return 1; + return a.name.localeCompare(b.name); + }); + + setFiles(allItems); + } + }) + .catch(() => { + onClose(); + }) + .finally(() => { + setLoading(false); + }); + }, [viewerState, encoding]); + + const onClose = useCallback(() => { + dispatch(closeArchiveViewer()); + }, [dispatch]); + + const navigateToDirectory = useCallback( + (dirName: string) => { + if (!currentPath) { + setCurrentPath(dirName); + } else { + setCurrentPath(currentPath + "/" + dirName); + } + setSelectedFiles([]); + }, + [currentPath], + ); + + const navigateToBreadcrumb = useCallback( + (index: number) => { + if (index === -1) { + setCurrentPath(""); + } else { + const newPath = breadcrumbPaths.slice(0, index + 1).join("/"); + setCurrentPath(newPath); + } + setSelectedFiles([]); + }, + [breadcrumbPaths], + ); + + const toggleFileSelection = useCallback( + (fileName: string) => { + const fullPath = currentPath ? currentPath + "/" + fileName : fileName; + setSelectedFiles((prev) => { + if (prev.includes(fullPath)) { + return prev.filter((f) => f !== fullPath); + } else { + return [...prev, fullPath]; + } + }); + }, + [currentPath], + ); + + const toggleSelectAll = useCallback(() => { + const allFiles = filteredFiles.map((file) => (currentPath ? currentPath + "/" + file.name : file.name)); + + const allSelected = allFiles.every((file) => selectedFiles.includes(file)); + + if (allSelected) { + setSelectedFiles((prev) => prev.filter((file) => !allFiles.includes(file))); + } else { + setSelectedFiles((prev) => [...new Set([...prev, ...allFiles])]); + } + }, [filteredFiles, selectedFiles, currentPath]); + + // 解压选中的文件 + const extractSelectedFiles = useCallback(() => { + if (selectedFiles.length === 0) { + return; + } + + dispatch(setExtractArchiveDialog({ open: true, file: viewerState?.file, mask: selectedFiles, encoding })); + }, [selectedFiles, t, enqueueSnackbar, encoding]); + + const extractArchive = useCallback(() => { + if (!viewerState?.file) { + return; + } + dispatch(setExtractArchiveDialog({ open: true, file: viewerState?.file, encoding })); + }, [viewerState?.file, encoding]); + + return ( + <> + + +
    + {loading && } + {!loading && ( + + + }> + navigateToBreadcrumb(-1)} + sx={{ + display: "flex", + alignItems: "center", + textDecoration: "none", + "&:hover": { textDecoration: "underline" }, + }} + > + + {t("fileManager.rootFolder")} + + {breadcrumbPaths.map((path, index) => { + const isLast = index === breadcrumbPaths.length - 1; + return isLast ? ( + + + {path} + + ) : ( + navigateToBreadcrumb(index)} + sx={{ + display: "flex", + alignItems: "center", + textDecoration: "none", + "&:hover": { textDecoration: "underline" }, + }} + > + + {path} + + ); + })} + + + + {filteredFiles.length > 0 ? ( + + { + setHeight(h + 0.5); + }} + components={{ + // eslint-disable-next-line react/display-name + Table: (props) => , + }} + data={filteredFiles} + itemContent={(_index, file) => { + const fullPath = currentPath ? currentPath + "/" + file.name : file.name; + const isSelected = selectedFiles.includes(fullPath); + + return ( + <> + + toggleFileSelection(file.name)} + size="small" + /> + + + + + {file.is_directory ? ( + navigateToDirectory(file.name)} + sx={{ + color: "primary.main", + fontWeight: 500, + textDecoration: "none", + background: "none", + border: "none", + cursor: "pointer", + padding: 0, + "&:hover": { textDecoration: "underline" }, + }} + > + {fileBase(file.name)} + + ) : ( + + {fileBase(file.name)} + + )} + + + + + {file.is_directory ? "-" : sizeToString(file.size)} + + + + + {file.updated_at ? : "-"} + + + + ); + }} + /> + + ) : ( + + {t("fileManager.nothingFound")} + + )} + + {!viewerState?.version && ( + + + + {selectedFiles.length > 0 && ( + + {t("fileManager.extractSelected")} + + )} + + {isZip && ( + + + + )} + + )} + + )} + + + + + ); +}; + +export default ArchivePreview; diff --git a/src/component/Viewers/CodeViewer/CodeViewer.tsx b/src/component/Viewers/CodeViewer/CodeViewer.tsx new file mode 100755 index 0000000..8977dd0 --- /dev/null +++ b/src/component/Viewers/CodeViewer/CodeViewer.tsx @@ -0,0 +1,321 @@ +import { LoadingButton } from "@mui/lab"; +import { Box, Button, ButtonGroup, IconButton, ListItemIcon, ListItemText, Menu, useTheme } from "@mui/material"; +import React, { lazy, Suspense, useCallback, useEffect, useRef, useState } from "react"; +import { useHotkeys } from "react-hotkeys-hook"; +import { useTranslation } from "react-i18next"; +import { closeCodeViewer } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { getEntityContent } from "../../../redux/thunks/file.ts"; +import { saveCode } from "../../../redux/thunks/viewer.ts"; +import { fileExtension } from "../../../util"; +import { CascadingSubmenu } from "../../FileManager/ContextMenu/CascadingMenu.tsx"; +import { DenseDivider, SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx"; +import useActionDisplayOpt, { canUpdate } from "../../FileManager/ContextMenu/useActionDisplayOpt.ts"; +import CaretDown from "../../Icons/CaretDown.tsx"; +import Checkmark from "../../Icons/Checkmark.tsx"; +import Setting from "../../Icons/Setting.tsx"; +import ViewerDialog, { ViewerLoading } from "../ViewerDialog.tsx"; + +const MonacoEditor = lazy(() => import("./MonacoEditor.tsx")); + +export const codePreviewSuffix: { + [key: string]: string; +} = { + md: "markdown", + json: "json", + php: "php", + py: "python", + bat: "bat", + cpp: "cpp", + c: "cpp", + h: "cpp", + cs: "csharp", + css: "css", + dockerfile: "dockerfile", + go: "go", + html: "html", + ini: "ini", + java: "java", + js: "javascript", + jsx: "javascript", + less: "less", + lua: "lua", + sh: "shell", + sql: "sql", + xml: "xml", + yaml: "yaml", +}; + +const allCharsets = [ + "utf-8", + "ibm866", + "iso-8859-2", + "iso-8859-3", + "iso-8859-4", + "iso-8859-5", + "iso-8859-6", + "iso-8859-7", + "iso-8859-8", + "iso-8859-8i", + "iso-8859-10", + "iso-8859-13", + "iso-8859-14", + "iso-8859-15", + "iso-8859-16", + "koi8-r", + "koi8-u", + "macintosh", + "windows-874", + "windows-1250", + "windows-1251", + "windows-1252", + "windows-1253", + "windows-1254", + "windows-1255", + "windows-1256", + "windows-1257", + "windows-1258", + "x-mac-cyrillic", + "gbk", + "gb18030", + "hz-gb-2312", + "big5", + "euc-jp", + "iso-2022-jp", + "shift-jis", + "euc-kr", + "iso-2022-kr", + "utf-16be", + "utf-16le", +]; + +const allLng = Array.from(new Set(Object.values(codePreviewSuffix))).sort(); + +const CodeViewer = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + const viewerState = useAppSelector((state) => state.globalState.codeViewer); + + const displayOpt = useActionDisplayOpt(viewerState?.file ? [viewerState?.file] : []); + const supportUpdate = canUpdate(displayOpt); + const [loading, setLoading] = useState(false); + const [value, setValue] = useState(""); + const [loaded, setLoaded] = useState(false); + const [saved, setSaved] = useState(true); + const [anchorEl, setAnchorEl] = useState(null); + const [optionAnchorEl, setOptionAnchorEl] = useState(null); + const [language, setLng] = useState(null); + const [wordWrap, setWordWrap] = useState<"off" | "on" | "wordWrapColumn" | "bounded">("off"); + const saveFunction = useRef<() => void>(() => {}); + + const loadContent = useCallback( + (charset?: string) => { + if (!viewerState || !viewerState.open) { + return; + } + + setLoaded(false); + setOptionAnchorEl(null); + dispatch(getEntityContent(viewerState.file, viewerState.version)) + .then((res) => { + setValue(new TextDecoder(charset).decode(res)); + setLoaded(true); + }) + .catch(() => { + onClose(); + }); + }, + [viewerState], + ); + + useEffect(() => { + if (!viewerState || !viewerState.open) { + return; + } + + setLng(null); + setSaved(true); + setLng(codePreviewSuffix[fileExtension(viewerState.file.name) ?? ""] ?? ""); + loadContent(); + }, [viewerState?.open]); + + const onClose = useCallback(() => { + dispatch(closeCodeViewer()); + }, [dispatch]); + + const openMore = useCallback( + (e: React.MouseEvent) => { + setAnchorEl(e.currentTarget); + }, + [dispatch], + ); + + const openOption = useCallback( + (e: React.MouseEvent) => { + setOptionAnchorEl(e.currentTarget); + }, + [dispatch], + ); + + const toggleWordWrap = useCallback(() => { + setOptionAnchorEl(null); + setWordWrap((prev) => (prev == "off" ? "on" : "off")); + }, []); + + const onSave = useCallback( + (saveAs?: boolean) => { + if (!viewerState?.file) { + return; + } + + setLoading(true); + dispatch(saveCode(value, viewerState.file, viewerState.version, saveAs)) + .then(() => { + setSaved(true); + }) + .finally(() => { + setLoading(false); + }); + }, + [value, viewerState], + ); + + const onChange = useCallback((v: string) => { + setValue(v); + setSaved(false); + }, []); + + useEffect(() => { + saveFunction.current = () => { + if (!saved && supportUpdate) { + onSave(false); + } + }; + }, [saved, supportUpdate, onSave]); + + useHotkeys( + ["Control+s", "Meta+s"], + () => { + saveFunction.current(); + }, + { preventDefault: true }, + ); + + return ( + + {supportUpdate && ( + + onSave(false)}> + {t("fileManager.save")} + + + + )} + + + + + } + fullScreenToggle + dialogProps={{ + open: !!(viewerState && viewerState.open), + onClose: onClose, + fullWidth: true, + maxWidth: "lg", + }} + > + setAnchorEl(null)} + slotProps={{ + paper: { + sx: { + minWidth: 150, + }, + }, + }} + > + onSave(true)} dense> + {t("modals.saveAs")} + + + setOptionAnchorEl(null)} + slotProps={{ + paper: { + sx: { + minWidth: 200, + }, + }, + }} + > + + {allCharsets.map((charset) => ( + loadContent(charset)}> + {charset} + + ))} + + + {allLng.map((l) => ( + setLng(l)}> + {l} + {l == language && ( + + + + )} + + ))} + + + + {t("fileManager.wordWrap")} + {wordWrap === "on" && ( + + + + )} + + + {!loaded && } + {loaded && ( + }> + + onChange(v as string)} + /> + + + )} + + ); +}; + +export default CodeViewer; diff --git a/src/component/Viewers/CodeViewer/MonacoEditor.tsx b/src/component/Viewers/CodeViewer/MonacoEditor.tsx new file mode 100755 index 0000000..b9496f6 --- /dev/null +++ b/src/component/Viewers/CodeViewer/MonacoEditor.tsx @@ -0,0 +1,396 @@ +import { Box } from "@mui/material"; +import * as monaco from "monaco-editor/esm/vs/editor/editor.api"; +import * as monacoEditor from "monaco-editor/esm/vs/editor/editor.api"; +import { useEffect, useMemo, useRef } from "react"; +import "./useWorker.ts"; + +/** + * @remarks + * This will be `IStandaloneEditorConstructionOptions` in newer versions of monaco-editor, or + * `IEditorConstructionOptions` in versions before that was introduced. + */ +export type EditorConstructionOptions = NonNullable[1]>; + +export type EditorWillMount = (monaco: typeof monacoEditor) => void | EditorConstructionOptions; + +export type EditorDidMount = (editor: monacoEditor.editor.IStandaloneCodeEditor, monaco: typeof monacoEditor) => void; + +export type EditorWillUnmount = ( + editor: monacoEditor.editor.IStandaloneCodeEditor, + monaco: typeof monacoEditor, +) => void | EditorConstructionOptions; + +export type ChangeHandler = (value: string, event: monacoEditor.editor.IModelContentChangedEvent) => void; + +export interface MonacoEditorBaseProps { + /** + * Width of editor. Defaults to 100%. + */ + width?: string | number; + + /** + * Height of editor. Defaults to 100%. + */ + height?: string | number; + + /** + * The initial value of the auto created model in the editor. + */ + defaultValue?: string; + + /** + * The initial language of the auto created model in the editor. Defaults to 'javascript'. + */ + language?: string; + + /** + * Theme to be used for rendering. + * The current out-of-the-box available themes are: 'vs' (default), 'vs-dark', 'hc-black'. + * You can create custom themes via `monaco.editor.defineTheme`. + */ + theme?: string | null; + + /** + * Optional string classname to append to the editor. + */ + className?: string | null; +} + +export interface MonacoEditorProps extends MonacoEditorBaseProps { + /** + * Value of the auto created model in the editor. + * If you specify `null` or `undefined` for this property, the component behaves in uncontrolled mode. + * Otherwise, it behaves in controlled mode. + */ + value?: string | null; + + /** + * Refer to Monaco interface {monaco.editor.IStandaloneEditorConstructionOptions}. + */ + options?: monacoEditor.editor.IStandaloneEditorConstructionOptions; + + /** + * Refer to Monaco interface {monaco.editor.IEditorOverrideServices}. + */ + overrideServices?: monacoEditor.editor.IEditorOverrideServices; + + /** + * An event emitted before the editor mounted (similar to componentWillMount of React). + */ + editorWillMount?: EditorWillMount; + + /** + * An event emitted when the editor has been mounted (similar to componentDidMount of React). + */ + editorDidMount?: EditorDidMount; + + /** + * An event emitted before the editor unmount (similar to componentWillUnmount of React). + */ + editorWillUnmount?: EditorWillUnmount; + + /** + * An event emitted when the content of the current model has changed. + */ + onChange?: ChangeHandler; + + /** + * An event emitted when the editor is blurred. + */ + onBlur?: (value: string) => void; + + /** + * Let the language be inferred from the uri + */ + uri?: (monaco: typeof monacoEditor) => monacoEditor.Uri; + + minHeight?: string | number; + + onSave?: React.MutableRefObject<() => void>; +} + +// ============ Diff Editor ============ + +export type DiffEditorWillMount = ( + monaco: typeof monacoEditor, +) => void | monacoEditor.editor.IStandaloneEditorConstructionOptions; + +export type DiffEditorDidMount = ( + editor: monacoEditor.editor.IStandaloneDiffEditor, + monaco: typeof monacoEditor, +) => void; + +export type DiffEditorWillUnmount = ( + editor: monacoEditor.editor.IStandaloneDiffEditor, + monaco: typeof monacoEditor, +) => void; + +export type DiffChangeHandler = ChangeHandler; + +export interface MonacoDiffEditorProps extends MonacoEditorBaseProps { + /** + * The original value to compare against. + */ + original?: string; + + /** + * Value of the auto created model in the editor. + * If you specify value property, the component behaves in controlled mode. Otherwise, it behaves in uncontrolled mode. + */ + value?: string; + + /** + * Refer to Monaco interface {monaco.editor.IDiffEditorConstructionOptions}. + */ + options?: monacoEditor.editor.IDiffEditorConstructionOptions; + + /** + * Refer to Monaco interface {monaco.editor.IEditorOverrideServices}. + */ + overrideServices?: monacoEditor.editor.IEditorOverrideServices; + + /** + * An event emitted before the editor mounted (similar to componentWillMount of React). + */ + editorWillMount?: DiffEditorWillMount; + + /** + * An event emitted when the editor has been mounted (similar to componentDidMount of React). + */ + editorDidMount?: DiffEditorDidMount; + + /** + * An event emitted before the editor unmount (similar to componentWillUnmount of React). + */ + editorWillUnmount?: DiffEditorWillUnmount; + + /** + * An event emitted when the content of the current model has changed. + */ + onChange?: DiffChangeHandler; + + /** + * Let the language be inferred from the uri + */ + originalUri?: (monaco: typeof monacoEditor) => monacoEditor.Uri; + + /** + * Let the language be inferred from the uri + */ + modifiedUri?: (monaco: typeof monacoEditor) => monacoEditor.Uri; + + onBlur?: (value: string) => void; +} + +function processSize(size: number | string) { + return !/^\d+$/.test(size as string) ? size : `${size}px`; +} + +function noop() {} + +function MonacoEditor({ + width, + height, + minHeight, + value, + defaultValue, + language, + theme, + options, + overrideServices, + editorWillMount, + editorDidMount, + editorWillUnmount, + onChange, + onBlur, + className, + uri, + onSave, +}: MonacoEditorProps) { + const containerElement = useRef(null); + + const editor = useRef(null); + + const _subscription = useRef(null); + const _subscriptionBlur = useRef(null); + + const __prevent_trigger_change_event = useRef(null); + + const fixedWidth = processSize(width); + + const fixedHeight = processSize(height); + + const style = useMemo( + () => ({ + width: fixedWidth, + height: fixedHeight, + }), + [fixedWidth, fixedHeight], + ); + + const handleEditorWillMount = () => { + const finalOptions = editorWillMount(monaco); + return finalOptions || {}; + }; + + const handleEditorDidMount = () => { + editorDidMount(editor.current, monaco); + + _subscription.current = editor.current.onDidChangeModelContent((event) => { + if (!__prevent_trigger_change_event.current) { + onChange?.(editor.current.getValue(), event); + } + }); + + _subscriptionBlur.current = editor.current.onDidBlurEditorText((event) => { + onBlur?.(editor.current.getValue()); + }); + + // Add key binding for Ctrl+S or Meta+S (Cmd+S on Mac) + editor.current.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => { + onSave?.current(); + }); + }; + + const handleEditorWillUnmount = () => { + editorWillUnmount(editor.current, monaco); + }; + + const initMonaco = () => { + const finalValue = value !== null ? value : defaultValue; + + if (containerElement.current) { + // Before initializing monaco editor + const finalOptions = { ...options, ...handleEditorWillMount() }; + const modelUri = uri?.(monaco); + let model = modelUri && monaco.editor.getModel(modelUri); + if (model) { + // Cannot create two models with the same URI, + // if model with the given URI is already created, just update it. + model.setValue(finalValue); + monaco.editor.setModelLanguage(model, language); + } else { + model = monaco.editor.createModel(finalValue, language, modelUri); + } + editor.current = monaco.editor.create( + containerElement.current, + { + model, + ...(className ? { extraEditorClassName: className } : {}), + ...finalOptions, + ...(theme ? { theme } : {}), + }, + overrideServices, + ); + // After initializing monaco editor + handleEditorDidMount(); + } + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(initMonaco, []); + + useEffect(() => { + if (editor.current) { + if (value === editor.current.getValue()) { + return; + } + + const model = editor.current.getModel(); + __prevent_trigger_change_event.current = true; + editor.current.pushUndoStop(); + // pushEditOperations says it expects a cursorComputer, but doesn't seem to need one. + model.pushEditOperations( + [], + [ + { + range: model.getFullModelRange(), + text: value, + }, + ], + undefined, + ); + editor.current.pushUndoStop(); + __prevent_trigger_change_event.current = false; + } + }, [value]); + + useEffect(() => { + if (editor.current) { + const model = editor.current.getModel(); + monaco.editor.setModelLanguage(model, language); + } + }, [language]); + + useEffect(() => { + if (editor.current) { + // Don't pass in the model on update because monaco crashes if we pass the model + // a second time. See https://github.com/microsoft/monaco-editor/issues/2027 + const { model: _model, ...optionsWithoutModel } = options; + editor.current.updateOptions({ + ...(className ? { extraEditorClassName: className } : {}), + ...optionsWithoutModel, + }); + } + }, [className, options]); + + useEffect(() => { + if (editor.current) { + editor.current.layout(); + } + }, [width, height]); + + useEffect(() => { + monaco.editor.setTheme(theme); + }, [theme]); + + useEffect( + () => () => { + if (editor.current) { + handleEditorWillUnmount(); + editor.current.dispose(); + } + if (_subscription.current) { + _subscription.current.dispose(); + } + if (_subscriptionBlur.current) { + _subscriptionBlur.current.dispose(); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ); + + return ( + + ); +} + +MonacoEditor.defaultProps = { + width: "100%", + height: "100%", + value: null, + defaultValue: "", + language: "javascript", + theme: null, + options: {}, + overrideServices: {}, + editorWillMount: noop, + editorDidMount: noop, + editorWillUnmount: noop, + onChange: noop, + className: null, +}; + +MonacoEditor.displayName = "MonacoEditor"; + +export default MonacoEditor; diff --git a/src/component/Viewers/CodeViewer/useWorker.ts b/src/component/Viewers/CodeViewer/useWorker.ts new file mode 100755 index 0000000..5865f6d --- /dev/null +++ b/src/component/Viewers/CodeViewer/useWorker.ts @@ -0,0 +1,27 @@ +import * as monaco from "monaco-editor"; +import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker"; +import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker"; +import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker"; +import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker"; +import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker"; + +// @ts-ignore +self.MonacoEnvironment = { + getWorker(_: any, label: string) { + if (label === "json") { + return new jsonWorker(); + } + if (label === "css" || label === "scss" || label === "less") { + return new cssWorker(); + } + if (label === "html" || label === "handlebars" || label === "razor") { + return new htmlWorker(); + } + if (label === "typescript" || label === "javascript") { + return new tsWorker(); + } + return new editorWorker(); + }, +}; + +monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true); diff --git a/src/component/Viewers/CustomViewer.tsx b/src/component/Viewers/CustomViewer.tsx new file mode 100755 index 0000000..95759ec --- /dev/null +++ b/src/component/Viewers/CustomViewer.tsx @@ -0,0 +1,59 @@ +import { useTranslation } from "react-i18next"; +import { useAppDispatch, useAppSelector } from "../../redux/hooks.ts"; +import ViewerDialog, { ViewerLoading } from "./ViewerDialog.tsx"; +import React, { useCallback, useEffect, useState } from "react"; +import { closeCustomViewer } from "../../redux/globalStateSlice.ts"; +import { Box, useTheme } from "@mui/material"; + +const CustomViewer = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + const viewerState = useAppSelector((state) => state.globalState.customViewer); + + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (!viewerState || !viewerState.open) { + return; + } + + setLoading(true); + }, [viewerState]); + + const onClose = useCallback(() => { + dispatch(closeCustomViewer()); + }, [dispatch]); + + return ( + <> + + {loading && } + {viewerState && ( + setLoading(false)} + src={viewerState.url} + sx={{ + width: "100%", + height: loading ? 0 : "100%", + border: "none", + minHeight: loading ? 0 : "calc(100vh - 200px)", + }} + component={"iframe"} + /> + )} + + + ); +}; + +export default CustomViewer; diff --git a/src/component/Viewers/DrawIO/DrawIOViewer.tsx b/src/component/Viewers/DrawIO/DrawIOViewer.tsx new file mode 100755 index 0000000..16d5f4d --- /dev/null +++ b/src/component/Viewers/DrawIO/DrawIOViewer.tsx @@ -0,0 +1,199 @@ +import { useTranslation } from "react-i18next"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import ViewerDialog, { ViewerLoading } from "../ViewerDialog.tsx"; +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { closeDrawIOViewer } from "../../../redux/globalStateSlice.ts"; +import { Box, ListItemText, Menu, useTheme } from "@mui/material"; +import useActionDisplayOpt, { canUpdate } from "../../FileManager/ContextMenu/useActionDisplayOpt.ts"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx"; +import { generateIframeSrc, handleRemoteInvoke } from "./drawio.ts"; +import { getEntityContent } from "../../../redux/thunks/file.ts"; +import { saveDrawIO } from "../../../redux/thunks/viewer.ts"; +import dayjs from "dayjs"; +import { formatLocalTime } from "../../../util/datetime.ts"; + +const DrawIOViewer = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + const viewerState = useAppSelector((state) => state.globalState.drawIOViewer); + const instanceID = useAppSelector((state) => state.siteConfig.basic.config.instance_id); + + const displayOpt = useActionDisplayOpt(viewerState?.file ? [viewerState?.file] : []); + const supportUpdate = useRef(false); + const [loading, setLoading] = useState(false); + const [src, setSrc] = useState(""); + const [loaded, setLoaded] = useState(false); + const pp = useRef(); + const [anchorEl, setAnchorEl] = useState(null); + + useEffect(() => { + if (!viewerState) { + return; + } + + if (!viewerState.open) { + setSrc(""); + return; + } + + setLoaded(false); + supportUpdate.current = canUpdate(displayOpt) && !viewerState?.version; + pp.current = undefined; + const src = generateIframeSrc( + viewerState.host, + viewerState.file, + !supportUpdate.current, + theme.palette.mode == "dark", + ); + setSrc(src); + window.addEventListener("message", handlePostMessage, false); + return () => { + window.removeEventListener("message", handlePostMessage, false); + }; + }, [viewerState?.open]); + + const handlePostMessage = async (e: MessageEvent) => { + console.log("Received PostMessage from " + e.origin, e.data); + let msg; + try { + msg = JSON.parse(e.data); + } catch (e) { + return; + } + + if (!viewerState?.file) { + return; + } + + switch (msg.event) { + case "exit": + onClose(); + break; + case "configure": + pp.current?.contentWindow?.postMessage( + // TODO: use userv config + JSON.stringify({ action: "configure", config: {} }), + "*", + ); + break; + case "remoteInvoke": + pp.current?.contentWindow && + handleRemoteInvoke(pp.current?.contentWindow, msg, dispatch, viewerState, supportUpdate.current, instanceID); + break; + case "save": + try { + dispatch(saveDrawIO(msg.xml, viewerState.file, false)); + pp.current?.contentWindow?.postMessage( + JSON.stringify({ + action: "status", + message: t("fileManager.saveSuccess", { + time: formatLocalTime(dayjs()), + }), + modified: false, + }), + "*", + ); + } catch (e) {} + break; + + case "init": + try { + const content = await dispatch(getEntityContent(viewerState.file, viewerState.version)); + const contentStr = new TextDecoder().decode(content); + pp.current?.contentWindow?.postMessage( + JSON.stringify({ + action: "load", + autosave: supportUpdate.current, + title: viewerState.file.name, + xml: contentStr, + desc: { + xml: contentStr, + id: viewerState.file.id, + size: content.byteLength, + etag: viewerState.file.primary_entity, + writeable: supportUpdate.current, + name: viewerState.file.name, + versionEnabled: true, + ver: 2, + instanceId: instanceID ?? "", + }, + disableAutoSave: !supportUpdate.current, + }), + "*", + ); + if (supportUpdate.current) { + pp.current?.contentWindow?.postMessage(JSON.stringify({ action: "remoteInvokeReady" }), "*"); + } + } catch (e) { + onClose(); + } + break; + } + }; + + const onClose = useCallback(() => { + dispatch(closeDrawIOViewer()); + }, [dispatch]); + + const openMore = useCallback( + (e: React.MouseEvent) => { + setAnchorEl(e.currentTarget); + }, + [dispatch], + ); + + const handleIframeOnload = useCallback((e: React.SyntheticEvent) => { + setLoaded(true); + pp.current = e.currentTarget; + }, []); + + return ( + + setAnchorEl(null)} + slotProps={{ + paper: { + sx: { + minWidth: 150, + }, + }, + }} + > + + {t("modals.saveAs")} + + + {(!loaded || !src) && } + {src && ( + + )} + + ); +}; + +export default DrawIOViewer; diff --git a/src/component/Viewers/DrawIO/drawio.ts b/src/component/Viewers/DrawIO/drawio.ts new file mode 100755 index 0000000..91a1457 --- /dev/null +++ b/src/component/Viewers/DrawIO/drawio.ts @@ -0,0 +1,117 @@ +import { FileResponse } from "../../../api/explorer.ts"; +import { fileExtension } from "../../../util"; +import i18next from "i18next"; +import SessionManager from "../../../session"; +import { saveDrawIO } from "../../../redux/thunks/viewer.ts"; +import { AppDispatch } from "../../../redux/store.ts"; +import { DrawIOViewerState } from "../../../redux/globalStateSlice.ts"; +import { getFileInfo } from "../../../api/api.ts"; + +const defaultHost = "https://embed.diagrams.net"; + +export const generateIframeSrc = ( + host: string | undefined, + file: FileResponse, + readOnly: boolean, + darkMode: boolean, +) => { + const ext = fileExtension(file.name); + const query = new URLSearchParams({ + embed: "1", + embedRT: "1", + configure: "1", + libraries: "1", + spin: "1", + proto: "json", + keepmodified: "1", + p: "nxtcld", + lang: i18next.t("fileManager.drawioLng"), + dark: darkMode ? "1" : "0", + }); + + if (ext == "dwb") { + query.set("ui", "sketch"); + } + + if (readOnly) { + query.set("chrome", "0"); + } + + return (host ?? defaultHost) + "?" + query.toString(); +}; + +export const handleRemoteInvoke = async ( + w: Window, + msg: any, + dispatch: AppDispatch, + viewerState: DrawIOViewerState, + writeable?: boolean, + instanceId?: string, +) => { + switch (msg.funtionName) { + case "getCurrentUser": + const currentUser = SessionManager.currentUser(); + sendResponse(w, msg, [ + currentUser + ? { + displayName: currentUser?.user.nickname, + uid: currentUser?.user.id, + } + : null, + ]); + break; + case "saveFile": + try { + const res = await dispatch(saveDrawIO(msg.functionArgs[2], viewerState.file, false)); + if (res) { + sendResponse(w, msg, [ + { + etag: res.primary_entity, + size: res.size, + }, + ]); + } + } catch (e) { + sendResponse(w, msg, null, `${e}`); + } + break; + case "getFileInfo": + try { + const res = await dispatch(getFileInfo({ uri: viewerState.file.path })); + if (res) { + sendResponse(w, msg, [ + { + id: res.id, + size: res.size, + writeable, + name: res.name, + etag: res.primary_entity, + versionEnabled: true, + ver: 2, + instanceId, + }, + ]); + } + } catch (e) { + sendResponse(w, msg, null, `${e}`); + } + break; + } +}; + +const sendResponse = (w: Window, msg: any, respose: any, error?: string) => { + var respMsg: { + action: string; + msgMarkers: any; + error?: { errResp?: string }; + resp?: any; + } = { action: "remoteInvokeResponse", msgMarkers: msg.msgMarkers }; + if (error) { + respMsg.error = { errResp: error }; + } else if (respose != null) { + respMsg.resp = respose; + } + + console.log("Send remote invoke response PostMessage", respMsg); + w.postMessage(JSON.stringify(respMsg), "*"); +}; diff --git a/src/component/Viewers/EpubViewer/Epub.tsx b/src/component/Viewers/EpubViewer/Epub.tsx new file mode 100755 index 0000000..c1f4a50 --- /dev/null +++ b/src/component/Viewers/EpubViewer/Epub.tsx @@ -0,0 +1,89 @@ +import { IReactReaderProps, IReactReaderStyle, ReactReader, ReactReaderStyle } from "react-reader"; + +import { type Rendition } from "epubjs"; +import { useEffect, useRef } from "react"; +import { useTheme } from "@mui/material"; + +function updateTheme(rendition: Rendition, theme: string) { + const themes = rendition.themes; + switch (theme) { + case "dark": { + themes.override("color", "#fff"); + themes.override("background", "#000"); + break; + } + case "light": { + themes.override("color", "#000"); + themes.override("background", "#fff"); + break; + } + } +} + +const lightReaderTheme: IReactReaderStyle = { + ...ReactReaderStyle, + readerArea: { + ...ReactReaderStyle.readerArea, + transition: undefined, + }, +}; + +const darkReaderTheme: IReactReaderStyle = { + ...ReactReaderStyle, + arrow: { + ...ReactReaderStyle.arrow, + color: "white", + }, + arrowHover: { + ...ReactReaderStyle.arrowHover, + color: "#ccc", + }, + readerArea: { + ...ReactReaderStyle.readerArea, + backgroundColor: "#000", + transition: undefined, + }, + titleArea: { + ...ReactReaderStyle.titleArea, + color: "#ccc", + }, + tocArea: { + ...ReactReaderStyle.tocArea, + background: "#111", + }, + tocButtonExpanded: { + ...ReactReaderStyle.tocButtonExpanded, + background: "#222", + }, + tocButtonBar: { + ...ReactReaderStyle.tocButtonBar, + background: "#fff", + }, + tocButton: { + ...ReactReaderStyle.tocButton, + color: "white", + }, +}; + +const Epub = (props: IReactReaderProps) => { + const rendition = useRef(undefined); + const theme = useTheme(); + useEffect(() => { + if (rendition.current) { + updateTheme(rendition.current, theme.palette.mode); + } + }, [theme.palette.mode]); + + return ( + { + updateTheme(_rendition, theme.palette.mode); + rendition.current = _rendition; + }} + /> + ); +}; + +export default Epub; diff --git a/src/component/Viewers/EpubViewer/EpubViewer.tsx b/src/component/Viewers/EpubViewer/EpubViewer.tsx new file mode 100755 index 0000000..0b098ca --- /dev/null +++ b/src/component/Viewers/EpubViewer/EpubViewer.tsx @@ -0,0 +1,102 @@ +import { Box, useTheme } from "@mui/material"; +import React, { Suspense, useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getFileEntityUrl } from "../../../api/api.ts"; +import { closeEpubViewer } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import SessionManager, { UserSettings } from "../../../session"; +import { getFileLinkedUri } from "../../../util"; +import ViewerDialog, { ViewerLoading } from "../ViewerDialog.tsx"; + +const Epub = React.lazy(() => import("./Epub.tsx")); + +const EpubViewer = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + const viewerState = useAppSelector((state) => state.globalState.epubViewer); + + const [loading, setLoading] = useState(false); + const [src, setSrc] = useState(""); + const [currentLocation, setLocation] = useState(null); + const locationChanged = useCallback( + (epubcifi: string) => { + setLocation(epubcifi); + if (viewerState?.file) { + SessionManager.set(`${UserSettings.BookLocationPrefix}_${viewerState.file.id}`, epubcifi); + } + }, + [viewerState?.file], + ); + + useEffect(() => { + if (!viewerState || !viewerState.open) { + setLocation(null); + return; + } + + setSrc(""); + dispatch( + getFileEntityUrl({ + uris: [getFileLinkedUri(viewerState.file)], + entity: viewerState.version, + }), + ) + .then((res) => { + setSrc(res.urls[0].url); + const location = SessionManager.get(`${UserSettings.BookLocationPrefix}_${viewerState.file.id}`); + if (location) { + setLocation(location); + } + }) + .catch(() => { + onClose(); + }); + }, [viewerState]); + + const onClose = useCallback(() => { + dispatch(closeEpubViewer()); + }, [dispatch]); + + return ( + <> + + {!src && } + {src && ( + }> + + } + location={currentLocation} + locationChanged={locationChanged} + epubInitOptions={{ + openAs: "epub", + }} + showToc={true} + url={src} + /> + + + )} + + + ); +}; + +export default EpubViewer; diff --git a/src/component/Viewers/Excalidraw/Excalidraw.tsx b/src/component/Viewers/Excalidraw/Excalidraw.tsx new file mode 100755 index 0000000..54f85b0 --- /dev/null +++ b/src/component/Viewers/Excalidraw/Excalidraw.tsx @@ -0,0 +1,78 @@ +import { Excalidraw as ExcalidrawComponent } from "@excalidraw/excalidraw"; +import { OrderedExcalidrawElement } from "@excalidraw/excalidraw/element/types"; +import "@excalidraw/excalidraw/index.css"; +import { AppState, BinaryFiles } from "@excalidraw/excalidraw/types"; +import { Box } from "@mui/material"; +import { useMemo } from "react"; +import "./excalidraw.css"; + +export interface ExcalidrawProps { + value: string; + initialValue: string; + darkMode?: boolean; + onChange: (value: string) => void; + readOnly?: boolean; + language?: string; + onSaveShortcut?: () => void; +} + +interface ExcalidrawState { + elements: OrderedExcalidrawElement[]; + appState: AppState; + files: BinaryFiles; + type: string; + version: number; + source: string; +} + +const serializeExcalidrawState = (elements: readonly OrderedExcalidrawElement[], appState: any, file: BinaryFiles) => { + if (!Array.isArray(appState.collaborators)) { + appState.collaborators = []; + } + return JSON.stringify({ + type: "excalidraw", + version: 2, + source: window.location.origin, + elements, + appState, + files: file, + }); +}; + +const Excalidraw = (props: ExcalidrawProps) => { + const initialValue = useMemo(() => { + try { + return JSON.parse(props.initialValue) as ExcalidrawState; + } catch (error) { + return null; + } + }, [props.initialValue]); + return ( + { + if ((e.ctrlKey || e.metaKey) && e.key === "s") { + e.preventDefault(); + props.onSaveShortcut?.(); + } + }} + > + { + props.onChange(serializeExcalidrawState(elements, state, file)); + }} + initialData={initialValue} + langCode={props.language} + theme={props.darkMode ? "dark" : "light"} + /> + + ); +}; + +export default Excalidraw; diff --git a/src/component/Viewers/Excalidraw/ExcalidrawViewer.tsx b/src/component/Viewers/Excalidraw/ExcalidrawViewer.tsx new file mode 100755 index 0000000..36a7a4c --- /dev/null +++ b/src/component/Viewers/Excalidraw/ExcalidrawViewer.tsx @@ -0,0 +1,169 @@ +import { LoadingButton } from "@mui/lab"; +import { Box, Button, ButtonGroup, ListItemText, Menu, useTheme } from "@mui/material"; +import React, { lazy, Suspense, useCallback, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import i18next from "../../../i18n.ts"; +import { closeExcalidrawViewer } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { getEntityContent } from "../../../redux/thunks/file.ts"; +import { saveExcalidraw } from "../../../redux/thunks/viewer.ts"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx"; +import useActionDisplayOpt, { canUpdate } from "../../FileManager/ContextMenu/useActionDisplayOpt.ts"; +import CaretDown from "../../Icons/CaretDown.tsx"; +import ViewerDialog, { ViewerLoading } from "../ViewerDialog.tsx"; + +const Excalidraw = lazy(() => import("./Excalidraw.tsx")); + +const ExcalidrawViewer = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + const viewerState = useAppSelector((state) => state.globalState.excalidrawViewer); + + const displayOpt = useActionDisplayOpt(viewerState?.file ? [viewerState?.file] : []); + const supportUpdate = canUpdate(displayOpt); + const [loading, setLoading] = useState(false); + const [value, setValue] = useState(""); + const [changedValue, setChangedValue] = useState(""); + const [loaded, setLoaded] = useState(false); + const [saved, setSaved] = useState(true); + const [anchorEl, setAnchorEl] = useState(null); + const saveFunction = useRef(() => {}); + + const loadContent = useCallback(() => { + if (!viewerState || !viewerState.open) { + return; + } + + setLoaded(false); + dispatch(getEntityContent(viewerState.file, viewerState.version)) + .then((res) => { + const content = new TextDecoder().decode(res); + setValue(content); + setChangedValue(content); + setLoaded(true); + }) + .catch(() => { + onClose(); + }); + }, [viewerState]); + + useEffect(() => { + if (!viewerState || !viewerState.open) { + return; + } + + setSaved(true); + loadContent(); + }, [viewerState?.open]); + + const onClose = useCallback(() => { + dispatch(closeExcalidrawViewer()); + }, [dispatch]); + + const openMore = useCallback( + (e: React.MouseEvent) => { + setAnchorEl(e.currentTarget); + }, + [dispatch], + ); + + const onSave = useCallback( + (saveAs?: boolean) => { + if (!viewerState?.file) { + return; + } + + setLoading(true); + dispatch(saveExcalidraw(changedValue, viewerState.file, viewerState.version, saveAs)) + .then(() => { + setSaved(true); + }) + .finally(() => { + setLoading(false); + }); + }, + [changedValue, viewerState], + ); + + const onChange = useCallback((v: string) => { + setChangedValue(v); + setSaved(false); + }, []); + + const onSaveShortcut = useCallback(() => { + if (!saved && supportUpdate) { + onSave(false); + } + }, [saved, supportUpdate, onSave]); + + useEffect(() => { + saveFunction.current = () => { + if (!saved && supportUpdate) { + onSave(false); + } + }; + }, [saved, supportUpdate, onSave]); + + return ( + + {supportUpdate && ( + + onSave(false)}> + {t("fileManager.save")} + + + + )} + + } + fullScreenToggle + dialogProps={{ + open: !!(viewerState && viewerState.open), + onClose: onClose, + fullWidth: true, + maxWidth: "lg", + }} + > + setAnchorEl(null)} + slotProps={{ + paper: { + sx: { + minWidth: 150, + }, + }, + }} + > + onSave(true)} dense> + {t("modals.saveAs")} + + + {!loaded && } + {loaded && ( + }> + onChange(v as string)} + onSaveShortcut={onSaveShortcut} + /> + + )} + + ); +}; + +export default ExcalidrawViewer; diff --git a/src/component/Viewers/Excalidraw/excalidraw.css b/src/component/Viewers/Excalidraw/excalidraw.css new file mode 100755 index 0000000..7a4d97d --- /dev/null +++ b/src/component/Viewers/Excalidraw/excalidraw.css @@ -0,0 +1,4 @@ +.excalidraw { + min-height: calc(100vh - 200px); + --zIndex-modal: 1400; +} diff --git a/src/component/Viewers/ImageViewer/ImageEditor.tsx b/src/component/Viewers/ImageViewer/ImageEditor.tsx new file mode 100755 index 0000000..d7bb689 --- /dev/null +++ b/src/component/Viewers/ImageViewer/ImageEditor.tsx @@ -0,0 +1,95 @@ +import { Backdrop, Box, useTheme } from "@mui/material"; +import i18next from "i18next"; +import { useSnackbar } from "notistack"; +import React, { useCallback, useEffect, useState } from "react"; +import FilerobotImageEditor from "react-filerobot-image-editor"; +import { useTranslation } from "react-i18next"; +import { getFileEntityUrl } from "../../../api/api.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { saveImage, switchToImageViewer } from "../../../redux/thunks/viewer.ts"; +import { fileExtension, getFileLinkedUri } from "../../../util"; +import "./editor.css"; + +export const editorSupportedExt = ["jpg", "jpeg", "png", "webp"]; + +const ImageEditor = () => { + const theme = useTheme(); + const { t, i18n } = useTranslation(); + const [nsLoaded, setNsLoaded] = useState(false); + + useEffect(() => { + i18next.loadNamespaces(["image_editor"]).then(() => { + setNsLoaded(true); + }); + }, []); + + const editorState = useAppSelector((state) => state.globalState.imageEditor); + const [imageSrc, setImageSrc] = React.useState(undefined); + useEffect(() => { + if (!editorState?.open) { + setImageSrc(undefined); + } else { + dispatch( + getFileEntityUrl({ + no_cache: true, + uris: [getFileLinkedUri(editorState.file)], + entity: editorState.version, + }), + ) + .then((res) => { + setImageSrc(res.urls[0].url); + }) + .catch(() => { + dispatch(switchToImageViewer()); + }); + } + }, [editorState]); + + const dispatch = useAppDispatch(); + const { enqueueSnackbar } = useSnackbar(); + + const save = useCallback( + async (name: string, data?: string) => { + if (!data || !editorState?.file) { + return; + } + + await dispatch(saveImage(name, data, editorState.file, editorState.version)); + dispatch(switchToImageViewer()); + }, + [dispatch, editorState], + ); + + return ( + <> + theme.zIndex.drawer + 1, + }} + open={true} + > + + {editorState && imageSrc && nsLoaded && ( + { + await save(editedImageObject.name, editedImageObject.imageBase64); + }} + defaultSavedImageName={editorState.file.name} + // @ts-ignore + defaultSavedImageType={fileExtension(editorState.file.name) ?? "png"} + onClose={() => dispatch(switchToImageViewer())} + disableSaveIfNoChanges={true} + previewPixelRatio={window.devicePixelRatio} + savingPixelRatio={4} + /> + )} + + + + ); +}; + +export default ImageEditor; diff --git a/src/component/Viewers/ImageViewer/ImageViewer.tsx b/src/component/Viewers/ImageViewer/ImageViewer.tsx new file mode 100755 index 0000000..b47fc42 --- /dev/null +++ b/src/component/Viewers/ImageViewer/ImageViewer.tsx @@ -0,0 +1,39 @@ +import { Backdrop, useMediaQuery, useTheme } from "@mui/material"; +import { grey } from "@mui/material/colors"; +import { lazy, Suspense } from "react"; +import { closeImageViewer } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import FacebookCircularProgress from "../../Common/CircularProgress.tsx"; + +const Lightbox = lazy(() => import("./Lightbox.tsx")); +const ImageEditor = lazy(() => import("./ImageEditor.tsx")); + +const Loading = ( + theme.zIndex.drawer + 1 }} open={true}> + + +); + +const ImageViewer = () => { + const dispatch = useAppDispatch(); + const theme = useTheme(); + const viewer = useAppSelector((state) => state.globalState.imageViewer); + const editorState = useAppSelector((state) => state.globalState.imageEditor); + const sideBarOpen = useAppSelector((state) => state.globalState.sidebarOpen); + const isTablet = useMediaQuery(theme.breakpoints.down("md")); + + const sidebarOpenOnTablet = isTablet && sideBarOpen; + + return ( +
    + + {viewer && viewer.open && !sidebarOpenOnTablet && ( + dispatch(closeImageViewer())} viewer={viewer} /> + )} + + {editorState && editorState.open && } +
    + ); +}; + +export default ImageViewer; diff --git a/src/component/Viewers/ImageViewer/Lightbox.tsx b/src/component/Viewers/ImageViewer/Lightbox.tsx new file mode 100755 index 0000000..3611ec3 --- /dev/null +++ b/src/component/Viewers/ImageViewer/Lightbox.tsx @@ -0,0 +1,167 @@ +import { Backdrop, Box, ThemeProvider } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useGeneratedTheme } from "../../../App.tsx"; +import { setSelected } from "../../../redux/fileManagerSlice.ts"; +import { ImageViewerState } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { onImageViewerIndexChange } from "../../../redux/thunks/viewer.ts"; +import { fileExtension } from "../../../util"; +import { FileManagerIndex } from "../../FileManager/FileManager.tsx"; +import { usePaginationState } from "../../FileManager/Pagination/PaginationFooter.tsx"; +import Sidebar from "../../FileManager/Sidebar/Sidebar.tsx"; +import ImageOffOutlined from "../../Icons/ImageOffOutlined.tsx"; +import { PhotoSlider } from "./react-photo-view"; +import type { DataType } from "./react-photo-view/types.ts"; + +export interface LightboxProps { + viewer?: ImageViewerState; + onClose: () => void; +} + +const Lightbox = ({ viewer, onClose }: LightboxProps) => { + const theme = useGeneratedTheme(true, true); + const container = useRef(undefined); + const { enqueueSnackbar, closeSnackbar } = useSnackbar(); + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const [open, setOpen] = useState(true); + const [current, setCurrent] = useState(viewer?.index ?? 0); + const indexChanged = useRef(false); + const files = useAppSelector((state) => state.fileManager[FileManagerIndex.main].list?.files ?? []); + const paginationState = usePaginationState(FileManagerIndex.main); + + const viewerFiles = useMemo(() => { + if (!viewer) return []; + let imagesCount = 0; + const res: DataType[] = []; + let updatedCurrent: number | undefined = undefined; + if (viewer.version) { + setCurrent(imagesCount); + res.push({ + key: viewer.file.id + viewer.version, + file: viewer.file, + version: viewer.version, + }); + imagesCount++; + } else if (viewer.index == -1) { + setCurrent(imagesCount); + res.push({ + key: viewer.file.id, + file: viewer.file, + }); + imagesCount++; + } else { + files.forEach((f) => { + if (!indexChanged.current && f.path == viewer.file.path) { + updatedCurrent = imagesCount; + setCurrent(imagesCount); + res.push({ + key: f.id, + file: f, + }); + imagesCount++; + } else if (viewer.exts.includes(fileExtension(f.name) ?? "")) { + imagesCount++; + res.push({ + key: f.id, + file: f, + }); + } + }); + } + + if (paginationState.moreItems) { + res.push({ + loadMorePlaceholder: true, + key: `${paginationState.currentPage} - ${paginationState.nextToken}`, + }); + } + + if (paginationState.useEndlessLoading && (updatedCurrent ?? current) >= res.length) { + setCurrent(res.length - 1); + } + + if (paginationState.usePagination && indexChanged.current) { + setCurrent(0); + if (res.length == 0 || res[0].loadMorePlaceholder) { + setOpen(false); + enqueueSnackbar(t("application:fileManager.noMoreImages"), { + variant: "warning", + preventDuplicate: true, + }); + } + } + + indexChanged.current = true; + return res; + }, [viewer?.file, viewer?.index, viewer?.version, viewer?.exts, files, paginationState.moreItems]); + + const onIndexChange = useCallback( + (index: number) => { + setCurrent(index); + }, + [setCurrent, dispatch, paginationState, viewerFiles], + ); + + useEffect(() => { + const file = viewerFiles[current]?.file; + if (file) { + dispatch(onImageViewerIndexChange(file)); + } + }, [viewerFiles[current]?.file]); + + useEffect(() => { + if (viewerFiles.length == 0) { + beforeClose(); + } + }, [viewerFiles.length]); + + const beforeClose = useCallback(() => { + const file = viewerFiles[current]?.file; + if (viewer?.index != -1 && file) { + dispatch(setSelected({ index: FileManagerIndex.main, value: [file] })); + } + onClose(); + }, [onClose, viewer?.index, viewerFiles, current, dispatch]); + + return ( + + setOpen(false)} + afterClose={beforeClose} + visible={open} + index={current} + onIndexChange={onIndexChange} + brokenElement={ + + + + } + portalContainer={container.current} + /> + theme.zIndex.drawer + 1, + }} + open={open} + > + + + + + + ); +}; + +export default Lightbox; diff --git a/src/component/Viewers/ImageViewer/editor.css b/src/component/Viewers/ImageViewer/editor.css new file mode 100755 index 0000000..98f1429 --- /dev/null +++ b/src/component/Viewers/ImageViewer/editor.css @@ -0,0 +1,9 @@ +.SfxModal-Wrapper { + z-index: 20001 !important; +} +.SfxPopper-wrapper { + z-index: 20002 !important; +} +.FIE_root { + border-radius: 0 !important; +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/LICENSE.txt b/src/component/Viewers/ImageViewer/react-photo-view/LICENSE.txt new file mode 100755 index 0000000..261eeb9 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/component/Viewers/ImageViewer/react-photo-view/Photo.less b/src/component/Viewers/ImageViewer/react-photo-view/Photo.less new file mode 100755 index 0000000..6c5f6c0 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/Photo.less @@ -0,0 +1,23 @@ +.PhotoView__Photo { + max-width: initial; + cursor: grab; + + &:active { + cursor: grabbing; + } +} + +.PhotoView__icon { + position: absolute; + top: 0; + left: 0; + display: inline-block; + transform: translate(-50%, -50%); +} + +.lpk-badge { + right: 10px !important; + bottom: 10px !important; + top: initial !important; + left: initial !important; +} \ No newline at end of file diff --git a/src/component/Viewers/ImageViewer/react-photo-view/Photo.tsx b/src/component/Viewers/ImageViewer/react-photo-view/Photo.tsx new file mode 100755 index 0000000..c309139 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/Photo.tsx @@ -0,0 +1,267 @@ +import { grey } from "@mui/material/colors"; +import { heicTo, isHeic } from "heic-to"; +import * as LivePhotosKit from "livephotoskit"; +import React, { useEffect } from "react"; +import { getFileEntityUrl, getFileInfo } from "../../../../api/api.ts"; +import { EntityType, FileResponse, Metadata } from "../../../../api/explorer.ts"; +import { useAppDispatch } from "../../../../redux/hooks.ts"; +import { getFileLinkedUri } from "../../../../util"; +import { LRUCache } from "../../../../util/lru.ts"; +import FacebookCircularProgress from "../../../Common/CircularProgress.tsx"; +import useMountedRef from "./hooks/useMountedRef"; +import "./Photo.less"; +import type { BrokenElementParams } from "./types"; + +export interface IPhotoLoadedParams { + loaded?: boolean; + naturalWidth?: number; + naturalHeight?: number; + broken?: boolean; +} + +export interface IPhotoProps extends React.HTMLAttributes { + file: FileResponse; + version?: string; + loaded: boolean; + broken: boolean; + onPhotoLoad: (params: IPhotoLoadedParams) => void; + loadingElement?: JSX.Element; + brokenElement?: JSX.Element | ((photoProps: BrokenElementParams) => JSX.Element); +} + +// Global LRU cache for HEIC conversions (capacity: 50 images) +const heicConversionCache = new LRUCache(50); + +export default function Photo({ + file, + version, + loaded, + broken, + className, + onPhotoLoad, + loadingElement, + brokenElement, + ...restProps +}: IPhotoProps) { + const mountedRef = useMountedRef(); + const dispatch = useAppDispatch(); + const [imageSrc, setImageSrc] = React.useState(undefined); + const playerRef = React.useRef(null); + + // Helper function to check if file is HEIC/HEIF based on extension + const isHeicFile = (fileName: string): boolean => { + const extension = fileName.toLowerCase().split(".").pop(); + return extension === "heic" || extension === "heif"; + }; + + // Helper function to convert HEIC to JPG with caching + const convertHeicToJpg = async (imageUrl: string, cacheKey: string): Promise => { + // Check cache first + const cachedUrl = heicConversionCache.get(cacheKey); + if (cachedUrl) { + return cachedUrl; + } + + try { + // Fetch the image as blob + const response = await fetch(imageUrl); + const blob = await response.blob(); + + // Convert blob to File object for isHeic check + const file = new File([blob], "image", { type: blob.type }); + + // Check if it's actually a HEIC file + const isHeicBlob = await isHeic(file); + + if (isHeicBlob) { + // Convert HEIC to JPG + const jpgBlob = await heicTo({ + blob: blob, + type: "image/jpeg", + quality: 1, + }); + + // Create object URL for the converted image + const convertedUrl = URL.createObjectURL(jpgBlob); + + // Cache the converted URL + heicConversionCache.set(cacheKey, convertedUrl); + + return convertedUrl; + } else { + // If not HEIC, cache and return original URL + heicConversionCache.set(cacheKey, imageUrl); + return imageUrl; + } + } catch (error) { + console.error("Error converting HEIC to JPG:", error); + throw error; + } + }; + + useEffect(() => { + dispatch( + getFileEntityUrl({ + uris: [getFileLinkedUri(file)], + entity: version, + }), + ) + .then(async (res) => { + const originalUrl = res.urls[0].url; + const cacheKey = `${file.id}-${version || "default"}`; + + // Check if the file is HEIC/HEIF and convert if needed + if (isHeicFile(file.name)) { + try { + const convertedUrl = await convertHeicToJpg(originalUrl, cacheKey); + setImageSrc(convertedUrl); + if (file.metadata?.[Metadata.live_photo]) { + loadLivePhoto(file, convertedUrl); + } + } catch (error) { + console.error("Failed to convert HEIC image:", error); + if (mountedRef.current) { + onPhotoLoad({ + broken: true, + }); + } + } + } else { + setImageSrc(originalUrl); + loadLivePhoto(file, originalUrl); + } + }) + .catch((e) => { + if (mountedRef.current) { + onPhotoLoad({ + broken: true, + }); + } + }); + }, []); + + const loadLivePhoto = async (file: FileResponse, imgUrl: string) => { + if (!file.metadata?.[Metadata.live_photo]) { + return; + } + + try { + const fileExtended = await dispatch( + getFileInfo( + { + uri: getFileLinkedUri(file), + extended: true, + }, + true, + ), + ); + + // find live photo entity + const livePhotoEntity = fileExtended?.extended_info?.entities?.find( + (entity) => entity.type === EntityType.live_photo, + ); + + // get live photo entity url + const livePhotoEntityUrl = await dispatch( + getFileEntityUrl({ + uris: [getFileLinkedUri(file)], + entity: livePhotoEntity?.id, + }), + ); + + const imgElement = document.getElementById(file.id); + if (imgElement) { + const player = LivePhotosKit.Player(imgElement as HTMLElement); + playerRef.current = player; + player.photoSrc = imgUrl; + player.videoSrc = livePhotoEntityUrl.urls[0].url; + player.proactivelyLoadsVideo = true; + } + } catch (e) { + console.error("Failed to load live photo:", e); + } + }; + + function handleImageLoaded(e: React.SyntheticEvent) { + const { naturalWidth, naturalHeight } = e.target as HTMLImageElement; + if (mountedRef.current) { + onPhotoLoad({ + loaded: true, + naturalWidth, + naturalHeight, + }); + } + } + + function handleImageBroken(e: React.SyntheticEvent) { + console.log("handleImageBroken", e); + if (mountedRef.current) { + onPhotoLoad({ + broken: true, + }); + } + } + + // Clean up object URL when component unmounts or imageSrc changes + useEffect(() => { + return () => { + if (imageSrc && imageSrc.startsWith("blob:")) { + // Don't revoke cached URLs, let the cache handle cleanup + // URL.revokeObjectURL(imageSrc); + } + }; + }, [imageSrc]); + + const { onMouseDown, onTouchStart, style, ...rest } = restProps; + + // Extract width and height from style if available + const { width, height, ...restStyle } = style || {}; + + useEffect(() => { + if (playerRef.current) { + // Convert width and height to numbers, defaulting to 0 if not valid + const numWidth = typeof width === "number" ? width : 0; + const numHeight = typeof height === "number" ? height : 0; + playerRef.current.updateSize(numWidth, numHeight); + } + }, [width, height]); + + if (file && !broken) { + return ( + <> + {imageSrc && ( +
    + +
    + )} + {!loaded && ( + + )} + + ); + } + + if (brokenElement) { + return ( + + {typeof brokenElement === "function" ? brokenElement({ src: imageSrc ?? "" }) : brokenElement} + + ); + } + + return null; +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/PhotoBox.less b/src/component/Viewers/ImageViewer/react-photo-view/PhotoBox.less new file mode 100755 index 0000000..87f691d --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/PhotoBox.less @@ -0,0 +1,22 @@ +.PhotoView { + &__PhotoWrap, + &__PhotoBox { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100%; + touch-action: none; + direction: ltr; + } + + &__PhotoWrap { + z-index: 10; + overflow: hidden; + } + + &__PhotoBox { + transform-origin: left top; + } +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/PhotoBox.tsx b/src/component/Viewers/ImageViewer/react-photo-view/PhotoBox.tsx new file mode 100755 index 0000000..4e2ac8e --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/PhotoBox.tsx @@ -0,0 +1,516 @@ +import { grey } from "@mui/material/colors"; +import React, { useEffect, useRef } from "react"; +import { useInView } from "react-intersection-observer"; +import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; +import { loadMorePages } from "../../../../redux/thunks/filemanager.ts"; +import FacebookCircularProgress from "../../../Common/CircularProgress.tsx"; +import { FileManagerIndex } from "../../../FileManager/FileManager.tsx"; +import useAnimationPosition from "./hooks/useAnimationPosition"; +import useContinuousTap from "./hooks/useContinuousTap"; +import useDebounceCallback from "./hooks/useDebounceCallback"; +import useEventListener from "./hooks/useEventListener"; +import useIsomorphicLayoutEffect from "./hooks/useIsomorphicLayoutEffect"; +import useMethods from "./hooks/useMethods"; +import useMountedRef from "./hooks/useMountedRef"; +import useScrollPosition from "./hooks/useScrollPosition"; +import useSetState from "./hooks/useSetState"; +import type { IPhotoLoadedParams } from "./Photo"; +import Photo from "./Photo"; +import "./PhotoBox.less"; +import type { + BrokenElementParams, + DataType, + ExposedProperties, + PhotoTapFunction, + ReachFunction, + ReachMoveFunction, + ReachType, + TouchStartType, +} from "./types"; +import { computePositionEdge, getReachType } from "./utils/edgeHandle"; +import getMultipleTouchPosition from "./utils/getMultipleTouchPosition"; +import getPositionOnMoveOrScale from "./utils/getPositionOnMoveOrScale"; +import getRotateSize from "./utils/getRotateSize"; +import getSuitableImageSize from "./utils/getSuitableImageSize"; +import isTouchDevice from "./utils/isTouchDevice"; +import { limitScale } from "./utils/limitTarget"; +import { minStartTouchOffset, scaleBuffer } from "./variables"; + +export interface PhotoBoxProps { + // å›¾ç‰‡ä¿¡æ¯ + item: DataType; + // 是å¦å¯è§ + visible: boolean; + // 动画时间 + speed: number; + // 动画函数 + easing: string; + // 容器类å + wrapClassName?: string; + // 图片类å + className?: string; + // style + style?: object; + // 自定义 loading + loadingElement?: JSX.Element; + // 加载失败 Element + brokenElement?: JSX.Element | ((photoProps: BrokenElementParams) => JSX.Element); + + // Photo 点击事件 + onPhotoTap: PhotoTapFunction; + // Mask 点击事件 + onMaskTap: PhotoTapFunction; + // 到达边缘滑动事件 + onReachMove: ReachMoveFunction; + // 触摸解除事件 + onReachUp: ReachFunction; + // Resize 事件 + onPhotoResize: () => void; + // å‘父组件导出属性 + expose: (state: ExposedProperties) => void; + // 是å¦åœ¨å½“剿“作中 + isActive: boolean; +} + +const initialState = { + // 真实宽度 + naturalWidth: undefined as number | undefined, + // 真实高度 + naturalHeight: undefined as number | undefined, + // 宽度 + width: undefined as number | undefined, + // 高度 + height: undefined as number | undefined, + // 加载æˆåŠŸçŠ¶æ€ + loaded: undefined as boolean | undefined, + // ç ´ç¢ŽçŠ¶æ€ + broken: false, + + // 图片 X åç§»é‡ + x: 0, + // 图片 y åç§»é‡ + y: 0, + // å›¾ç‰‡å¤„äºŽè§¦æ‘¸çš„çŠ¶æ€ + touched: false, + // èƒŒæ™¯å¤„äºŽè§¦æ‘¸çŠ¶æ€ + maskTouched: false, + // æ—‹è½¬çŠ¶æ€ + rotate: 0, + // æ”¾å¤§ç¼©å° + scale: 1, + + // 触摸开始时 x åŽŸå§‹åæ ‡ + CX: 0, + // 触摸开始时 y åŽŸå§‹åæ ‡ + CY: 0, + + // 触摸开始时图片 x åç§»é‡ + lastX: 0, + // 触摸开始时图片 y åç§»é‡ + lastY: 0, + // ä¸Šä¸€ä¸ªè§¦æ‘¸çŠ¶æ€ x åŽŸå§‹åæ ‡ + lastCX: 0, + // ä¸Šä¸€ä¸ªè§¦æ‘¸çŠ¶æ€ y åŽŸå§‹åæ ‡ + lastCY: 0, + // 上一个触摸状æ€çš„ scale + lastScale: 1, + + // 触摸开始时时间 + touchTime: 0, + // å¤šæŒ‡è§¦æŽ§é—´è· + touchLength: 0, + // æ˜¯å¦æš‚åœ transition + pause: true, + // åœæ­¢ Raf + stopRaf: true, + // 当å‰è¾¹ç¼˜è§¦å‘çŠ¶æ€ + reach: undefined as ReachType, +}; + +export default function PhotoBox({ + item: { + render, + file, + version, + width: customWidth = 0, + height: customHeight = 0, + originRef, + loadMorePlaceholder, + key, + }, + visible, + speed, + easing, + wrapClassName, + className, + style, + loadingElement, + brokenElement, + + onPhotoTap, + onMaskTap, + onReachMove, + onReachUp, + onPhotoResize, + isActive, + expose, +}: PhotoBoxProps) { + const [state, updateState] = useSetState(initialState); + const initialTouchRef = useRef(0); + const mounted = useMountedRef(); + + const { + naturalWidth = customWidth, + naturalHeight = customHeight, + width = customWidth, + height = customHeight, + loaded = !file, + broken, + x, + y, + touched, + stopRaf, + maskTouched, + rotate, + scale, + CX, + CY, + lastX, + lastY, + lastCX, + lastCY, + lastScale, + touchTime, + touchLength, + pause, + reach, + } = state; + + const sideBarOpen = useAppSelector((state) => state.globalState.sidebarOpen); + const dynamicInnerWidth = sideBarOpen ? window.innerWidth - 300 : window.innerWidth; + + const fn = useMethods({ + onScale: (current: number) => onScale(limitScale(current)), + onRotate(current: number) { + if (rotate !== current) { + expose({ rotate: current }); + updateState({ + rotate: current, + ...getSuitableImageSize(naturalWidth, naturalHeight, current, dynamicInnerWidth), + }); + } + }, + }); + + // 默认为å±å¹•中心缩放 + function onScale(current: number, clientX?: number, clientY?: number) { + if (scale !== current) { + expose({ scale: current }); + updateState({ + scale: current, + ...getPositionOnMoveOrScale(x, y, width, height, scale, current, clientX, clientY), + ...(current <= 1 && { x: 0, y: 0 }), + }); + } + } + + const handleMove = useDebounceCallback( + (nextClientX: number, nextClientY: number, currentTouchLength: number = 0) => { + if ((touched || maskTouched) && isActive) { + // 通过旋转调æ¢å®½é«˜ + const [currentWidth, currentHeight] = getRotateSize(rotate, width, height); + // 啿Œ‡æœ€å°ç¼©æ”¾ä¸‹ï¼Œä»¥åˆå§‹ç§»åЍè·ç¦»æ¥åˆ¤æ–­æ„图 + if (currentTouchLength === 0 && initialTouchRef.current === 0) { + const isStillX = Math.abs(nextClientX - CX) <= minStartTouchOffset; + const isStillY = Math.abs(nextClientY - CY) <= minStartTouchOffset; + // åˆå§‹ç§»åЍè·ç¦»ä¸è¶³ + if (isStillX && isStillY) { + // æ–¹å‘记录上次移动è·ç¦»ï¼Œä»¥ä¾¿å¹³æ»‘过渡 + updateState({ lastCX: nextClientX, lastCY: nextClientY }); + return; + } + // 设置å“åº”çŠ¶æ€ + initialTouchRef.current = !isStillX ? 1 : nextClientY > CY ? 3 : 2; + } + + const offsetX = nextClientX - lastCX; + const offsetY = nextClientY - lastCY; + // 边缘触å‘çŠ¶æ€ + let currentReach: ReachType; + if (currentTouchLength === 0) { + // è¾¹ç¼˜è¶…å‡ºçŠ¶æ€ + const [horizontalCloseEdge] = computePositionEdge(offsetX + lastX, scale, currentWidth, dynamicInnerWidth); + const [verticalCloseEdge] = computePositionEdge(offsetY + lastY, scale, currentHeight, innerHeight); + // è¾¹ç¼˜è§¦å‘æ£€æµ‹ + currentReach = getReachType(initialTouchRef.current, horizontalCloseEdge, verticalCloseEdge, reach); + + // 接触边缘 + if (currentReach !== undefined) { + onReachMove(currentReach, nextClientX, nextClientY, scale); + } + } + // 横å‘边缘触å‘ã€èƒŒæ™¯è§¦å‘ç¦ç”¨å½“剿»‘动 + if (currentReach === "x" || maskTouched) { + updateState({ reach: "x" }); + return; + } + // ç›®æ ‡å€æ•° + const toScale = limitScale( + scale + ((currentTouchLength - touchLength) / 100 / 2) * scale, + naturalWidth / width, + scaleBuffer, + ); + // 导出å˜é‡ + expose({ scale: toScale }); + updateState({ + touchLength: currentTouchLength, + reach: currentReach, + scale: toScale, + ...getPositionOnMoveOrScale(x, y, width, height, scale, toScale, nextClientX, nextClientY, offsetX, offsetY), + }); + } + }, + { + maxWait: 8, + }, + ); + + function updateRaf(position: { x?: number; y?: number }) { + if (stopRaf || touched) { + return false; + } + if (mounted.current) { + // 下拉关闭时å¯ä»¥æœ‰åŠ¨ç”» + updateState({ ...position, pause: visible }); + } + return mounted.current; + } + + const slideToPosition = useScrollPosition( + (nextX) => updateRaf({ x: nextX }), + (nextY) => updateRaf({ y: nextY }), + (nextScale) => { + if (mounted.current) { + expose({ scale: nextScale }); + updateState({ scale: nextScale }); + } + return !touched && mounted.current; + }, + dynamicInnerWidth, + ); + + const handlePhotoTap = useContinuousTap(onPhotoTap, (currentClientX: number, currentClientY: number) => { + if (!reach) { + // è‹¥å›¾ç‰‡è¶³å¤Ÿå¤§ï¼Œåˆ™æ”¾å¤§é€‚åº”çš„å€æ•° + const endScale = scale !== 1 ? 1 : Math.max(2, naturalWidth / width); + onScale(endScale, currentClientX, currentClientY); + } + }); + + function handleUp(nextClientX: number, nextClientY: number) { + // é‡ç½®å“åº”çŠ¶æ€ + initialTouchRef.current = 0; + if ((touched || maskTouched) && isActive) { + updateState({ + touched: false, + maskTouched: false, + pause: false, + stopRaf: false, + reach: undefined, + }); + const safeScale = limitScale(scale, naturalWidth / width); + // Go + slideToPosition(x, y, lastX, lastY, width, height, scale, safeScale, lastScale, rotate, touchTime); + + onReachUp(nextClientX, nextClientY); + // è§¦å‘ Tap 事件 + if (CX === nextClientX && CY === nextClientY) { + if (touched) { + handlePhotoTap(nextClientX, nextClientY); + return; + } + if (maskTouched) { + onMaskTap(nextClientX, nextClientY); + } + } + } + } + + useEventListener(isTouchDevice ? undefined : "mousemove", (e) => { + handleMove(e.clientX, e.clientY); + }); + useEventListener(isTouchDevice ? undefined : "mouseup", (e) => { + handleUp(e.clientX, e.clientY); + }); + useEventListener( + isTouchDevice ? "touchmove" : undefined, + (e) => { + const position = getMultipleTouchPosition(e); + handleMove(...position); + }, + { passive: false }, + ); + useEventListener( + isTouchDevice ? "touchend" : undefined, + ({ changedTouches }) => { + const touch = changedTouches[0]; + handleUp(touch.clientX, touch.clientY); + }, + { passive: false }, + ); + useEventListener( + "resize", + useDebounceCallback( + () => { + if (loaded && !touched) { + updateState(getSuitableImageSize(naturalWidth, naturalHeight, rotate, dynamicInnerWidth)); + onPhotoResize(); + } + }, + { maxWait: 8 }, + ), + ); + + useIsomorphicLayoutEffect(() => { + if (isActive) { + expose({ scale, rotate, ...fn }); + } + }, [isActive]); + + function handlePhotoLoad(params: IPhotoLoadedParams) { + updateState({ + ...params, + ...(params.loaded && + getSuitableImageSize(params.naturalWidth || 0, params.naturalHeight || 0, rotate, dynamicInnerWidth)), + }); + } + + function handleStart(currentClientX: number, currentClientY: number, currentTouchLength: number = 0) { + updateState({ + touched: true, + CX: currentClientX, + CY: currentClientY, + lastCX: currentClientX, + lastCY: currentClientY, + lastX: x, + lastY: y, + lastScale: scale, + touchLength: currentTouchLength, + touchTime: Date.now(), + }); + } + + function handleWheel(e: React.WheelEvent) { + if (!reach) { + // é™åˆ¶æœ€å¤§å€æ•°å’Œæœ€å°å€æ•° + const toScale = limitScale(scale - e.deltaY / 100 / 2, naturalWidth / width); + updateState({ stopRaf: true }); + onScale(toScale, e.clientX, e.clientY); + } + } + + function handleMaskStart(e: { clientX: number; clientY: number }) { + updateState({ + maskTouched: true, + CX: e.clientX, + CY: e.clientY, + lastX: x, + lastY: y, + }); + } + + function handleTouchStart(e: React.TouchEvent) { + e.stopPropagation(); + handleStart(...getMultipleTouchPosition(e)); + } + + function handleMouseDown(e: React.MouseEvent) { + e.stopPropagation(); + if (e.button === 0) { + handleStart(e.clientX, e.clientY, 0); + } + } + + // 计算ä½ç½® + const [translateX, translateY, currentWidth, currentHeight, currentScale, opacity, easingMode, FIT] = + useAnimationPosition( + dynamicInnerWidth, + visible, + originRef, + loaded, + x, + y, + width, + height, + scale, + speed, + (isPause: boolean) => updateState({ pause: isPause }), + ); + // 图片 objectFit æ¸å˜æ—¶é—´ + const transitionLayoutTime = easingMode < 4 ? speed / 2 : easingMode > 4 ? speed : 0; + const transitionCSS = `transform ${speed}ms ${easing}`; + + const attrs = { + className, + onMouseDown: isTouchDevice ? undefined : handleMouseDown, + onTouchStart: isTouchDevice ? handleTouchStart : undefined, + onWheel: handleWheel, + style: { + width: currentWidth, + height: currentHeight, + opacity, + objectFit: easingMode === 4 ? undefined : FIT, + transform: rotate ? `rotate(${rotate}deg)` : undefined, + transition: + // åˆå§‹çŠ¶æ€æ— æ¸å˜ + easingMode > 2 + ? `${transitionCSS}, opacity ${speed}ms ease, height ${transitionLayoutTime}ms ${easing}` + : undefined, + }, + }; + + return ( +
    handleMaskStart(e.touches[0]) : undefined} + > +
    + {file ? ( + + ) : ( + render && render({ attrs, scale: currentScale, rotate }) + )} + {loadMorePlaceholder && } +
    +
    + ); +} + +const LoadMorePlaceholder = () => { + const dispatch = useAppDispatch(); + const { ref, inView } = useInView({ triggerOnce: true }); + useEffect(() => { + if (inView) { + dispatch(loadMorePages(FileManagerIndex.main)); + } + }, [inView]); + return ; +}; diff --git a/src/component/Viewers/ImageViewer/react-photo-view/PhotoProvider.tsx b/src/component/Viewers/ImageViewer/react-photo-view/PhotoProvider.tsx new file mode 100755 index 0000000..45cd713 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/PhotoProvider.tsx @@ -0,0 +1,107 @@ +import React, { useMemo, useRef } from "react"; +import type { DataType, PhotoProviderBase } from "./types"; +import useMethods from "./hooks/useMethods"; +import useSetState from "./hooks/useSetState"; +import PhotoContext from "./photo-context"; +import PhotoSlider from "./PhotoSlider"; + +export interface PhotoProviderProps extends PhotoProviderBase { + children: React.ReactNode; + onIndexChange?: (index: number, state: PhotoProviderState) => void; + onVisibleChange?: (visible: boolean, index: number, state: PhotoProviderState) => void; +} + +type PhotoProviderState = { + images: DataType[]; + visible: boolean; + index: number; +}; + +const initialState: PhotoProviderState = { + images: [], + visible: false, + index: 0, +}; + +export default function PhotoProvider({ children, onIndexChange, onVisibleChange, ...restProps }: PhotoProviderProps) { + const [state, updateState] = useSetState(initialState); + const uniqueIdRef = useRef(0); + const { images, visible, index } = state; + + const methods = useMethods({ + nextId() { + return (uniqueIdRef.current += 1); + }, + update(imageItem: DataType) { + const currentIndex = images.findIndex((n) => n.key === imageItem.key); + if (currentIndex > -1) { + const nextImages = images.slice(); + nextImages.splice(currentIndex, 1, imageItem); + updateState({ + images: nextImages, + }); + return; + } + updateState((prev) => ({ + images: prev.images.concat(imageItem), + })); + }, + remove(key: number) { + updateState((prev) => { + const nextImages = prev.images.filter((item) => item.key !== key); + const nextEndIndex = nextImages.length - 1; + return { + images: nextImages, + index: Math.min(nextEndIndex, index), + }; + }); + }, + show(key: number) { + const currentIndex = images.findIndex((item) => item.key === key); + updateState({ + visible: true, + index: currentIndex, + }); + if (onVisibleChange) { + onVisibleChange(true, currentIndex, state); + } + }, + }); + + const fn = useMethods({ + close() { + updateState({ + visible: false, + }); + + if (onVisibleChange) { + onVisibleChange(false, index, state); + } + }, + changeIndex(nextIndex: number) { + updateState({ + index: nextIndex, + }); + + if (onIndexChange) { + onIndexChange(nextIndex, state); + } + }, + }); + + const value = useMemo(() => ({ ...state, ...methods }), [state, methods]); + + return ( + + {children} + + + ); +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/PhotoSlider.less b/src/component/Viewers/ImageViewer/react-photo-view/PhotoSlider.less new file mode 100755 index 0000000..237c24a --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/PhotoSlider.less @@ -0,0 +1,131 @@ +@keyframes PhotoView__fade { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +.PhotoView { + &-Slider__clean { + .PhotoView-Slider__BannerWrap, + .PhotoView-Slider__ArrowLeft, + .PhotoView-Slider__ArrowRight, + .PhotoView-Slider__Overlay { + opacity: 0; + } + } + + &-Slider__willClose { + .PhotoView-Slider__BannerWrap:hover { + opacity: 0; + } + } + + &-Slider__Backdrop { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 1); + transition-property: background-color; + z-index: -1; + } + + &-Slider__fadeIn { + opacity: 0; + animation: PhotoView__fade linear both; + } + + &-Slider__fadeOut { + opacity: 0; + animation: PhotoView__fade linear both reverse; + } + + &-Slider__BannerWrap { + position: absolute; + left: 0; + top: 0; + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + height: 52px; + background-image: linear-gradient(0deg, transparent, rgba(0, 0, 0, 0.38)); + color: white; + transition: opacity 0.2s ease-out; + z-index: 20; + + &:hover { + opacity: 1; + } + } + + &-Slider__Counter { + padding: 0 10px; + font-size: 14px; + opacity: 0.75; + } + + &-Slider__BannerRight { + display: flex; + align-items: center; + height: 100%; + margin: 4px 8px; + gap: 6px; + } + + &-Slider__toolbarIcon { + box-sizing: border-box; + padding: 10px; + fill: white; + opacity: 0.75; + cursor: pointer; + transition: opacity 0.2s linear; + + &:hover { + opacity: 1; + } + } + + &-Slider__ArrowLeft, + &-Slider__ArrowRight { + position: absolute; + top: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + width: 70px; + height: 100px; + margin: auto; + opacity: 0.75; + z-index: 20; + cursor: pointer; + user-select: none; + transition: opacity 0.2s linear; + + &:hover { + opacity: 1; + } + + svg { + box-sizing: content-box; + padding: 10px; + width: 24px; + height: 24px; + fill: white; + background: rgba(0, 0, 0, 0.3); + } + } + + &-Slider__ArrowLeft { + left: 0; + } + + &-Slider__ArrowRight { + right: 0; + } +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/PhotoSlider.tsx b/src/component/Viewers/ImageViewer/react-photo-view/PhotoSlider.tsx new file mode 100755 index 0000000..66e75d0 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/PhotoSlider.tsx @@ -0,0 +1,527 @@ +import { Box, IconButton, Tooltip, useMediaQuery, useTheme } from "@mui/material"; +import React, { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { setSidebar } from "../../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../../redux/hooks.ts"; +import { downloadSingleFile } from "../../../../redux/thunks/download.ts"; +import { openFileContextMenu } from "../../../../redux/thunks/file.ts"; +import { switchToImageEditor } from "../../../../redux/thunks/viewer.ts"; +import { fileExtension } from "../../../../util"; +import useActionDisplayOpt, { canUpdate } from "../../../FileManager/ContextMenu/useActionDisplayOpt.ts"; +import { FileManagerIndex } from "../../../FileManager/FileManager.tsx"; +import Dismiss from "../../../Icons/Dismiss.tsx"; +import Download from "../../../Icons/Download.tsx"; +import ImageEdit from "../../../Icons/ImageEdit.tsx"; +import Info from "../../../Icons/Info.tsx"; +import MoreHorizontal from "../../../Icons/MoreHorizontal.tsx"; +import { editorSupportedExt } from "../ImageEditor.tsx"; +import ArrowLeft from "./components/ArrowLeft"; +import ArrowRight from "./components/ArrowRight"; +import SlidePortal from "./components/SlidePortal"; +import useAdjacentImages from "./hooks/useAdjacentImages"; +import useAnimationVisible from "./hooks/useAnimationVisible"; +import useEventListener from "./hooks/useEventListener"; +import useIsomorphicLayoutEffect from "./hooks/useIsomorphicLayoutEffect"; +import useMethods from "./hooks/useMethods"; +import useSetState from "./hooks/useSetState"; +import PhotoBox from "./PhotoBox"; +import "./PhotoSlider.less"; +import type { DataType, OverlayRenderProps, PhotoProviderBase, ReachType } from "./types"; +import isTouchDevice from "./utils/isTouchDevice"; +import { limitNumber } from "./utils/limitTarget"; +import { defaultEasing, defaultOpacity, defaultSpeed, horizontalOffset, maxMoveOffset } from "./variables"; + +export interface IPhotoSliderProps extends PhotoProviderBase { + // 图片列表 + images: DataType[]; + // 图片当å‰ç´¢å¼• + index?: number; + // 索引改å˜å›žè°ƒ + onIndexChange?: (index: number) => void; + // å¯è§ + visible: boolean; + // 关闭回调 + onClose: (evt?: React.MouseEvent | React.TouchEvent) => void; + // 关闭动画结æŸåŽå›žè°ƒ + afterClose?: () => void; + moreFiles?: boolean; +} + +type PhotoSliderState = { + // åç§»é‡ + x: number; + // å›¾ç‰‡å¤„äºŽè§¦æ‘¸çš„çŠ¶æ€ + touched: boolean; + // æ˜¯å¦æš‚åœ transition + pause: boolean; + // Reach 开始时 x åæ ‡ + lastCX: number | undefined; + // Reach 开始时 y åæ ‡ + lastCY: number | undefined; + // èƒŒæ™¯é€æ˜Žåº¦ + bg: number | null | undefined; + // ä¸Šæ¬¡å…³é—­çš„èƒŒæ™¯é€æ˜Žåº¦ + lastBg: number | null | undefined; + // æ˜¯å¦æ˜¾ç¤º overlay + overlay: boolean; + // 是å¦ä¸ºæœ€å°çжæ€ï¼Œå¯ä¸‹æ‹‰å…³é—­ + minimal: boolean; + // 缩放 + scale: number; + // 旋转 + rotate: number; + // 缩放回调 + onScale?: (scale: number) => void; + // 旋转回调 + onRotate?: (rotate: number) => void; +}; + +const initialState: PhotoSliderState = { + x: 0, + touched: false, + pause: false, + lastCX: undefined, + lastCY: undefined, + bg: undefined, + lastBg: undefined, + overlay: true, + minimal: true, + scale: 1, + rotate: 0, +}; + +export default function PhotoSlider(props: IPhotoSliderProps) { + const { + loop = 3, + speed: speedFn, + easing: easingFn, + photoClosable, + maskClosable = true, + maskOpacity = defaultOpacity, + pullClosable = true, + bannerVisible = true, + overlayRender, + toolbarRender, + className, + maskClassName, + photoClassName, + photoWrapClassName, + loadingElement, + brokenElement, + images, + index: controlledIndex = 0, + onIndexChange: controlledIndexChange, + visible, + onClose, + afterClose, + portalContainer, + moreFiles, + } = props; + + const { t } = useTranslation(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const dispatch = useAppDispatch(); + const containerRef = useRef(null); + const sideBarOpen = useAppSelector((state) => state.globalState.sidebarOpen); + const [state, updateState] = useSetState(initialState); + const [innerIndex, updateInnerIndex] = useState(0); + const dynamicInnerWidth = sideBarOpen ? window.innerWidth - 300 : window.innerWidth; + + const { + x, + touched, + pause, + + lastCX, + lastCY, + + bg = maskOpacity, + lastBg, + overlay, + minimal, + + scale, + rotate, + onScale, + onRotate, + } = state; + + // 嗿ާ index + const isControlled = props.hasOwnProperty("index"); + const index = isControlled ? controlledIndex : innerIndex; + const onIndexChange = isControlled ? controlledIndexChange : updateInnerIndex; + // 内部虚拟 index + const virtualIndexRef = useRef(index); + + // 当å‰å›¾ç‰‡ + const imageLength = images.length; + const currentImage: DataType | undefined = images[index]; + + // 是å¦å¼€å¯ + // noinspection SuspiciousTypeOfGuard + const enableLoop = typeof loop === "boolean" ? loop : imageLength > loop; + + // æ˜¾ç¤ºåŠ¨ç”»å¤„ç† + const [realVisible, activeAnimation, onAnimationEnd] = useAnimationVisible(visible, afterClose); + + useEffect(() => { + setTimeout(() => { + document.body.style.overflow = "hidden"; + }, 500); + return () => { + setTimeout(() => { + document.body.style.overflow = isMobile ? "initial" : "hidden"; + }, 500); + }; + }, []); + + useIsomorphicLayoutEffect(() => { + // æ˜¾ç¤ºå¼¹å‡ºå±‚ï¼Œä¿®æ­£æ­£ç¡®çš„æŒ‡å‘ + if (realVisible) { + updateState({ + pause: true, + x: index * -(dynamicInnerWidth + horizontalOffset), + }); + virtualIndexRef.current = index; + return; + } + // å…³é—­åŽæ¸…ç©ºçŠ¶æ€ + updateState(initialState); + }, [realVisible]); + + const { close, changeIndex } = useMethods({ + close(evt?: React.MouseEvent | React.TouchEvent) { + if (onRotate) { + onRotate(0); + } + updateState({ + overlay: true, + // 记录当å‰å…³é—­æ—¶çš„逿˜Žåº¦ + lastBg: bg, + }); + onClose(evt); + }, + changeIndex(nextIndex: number, isPause: boolean = false) { + // 当å‰ç´¢å¼• + const currentIndex = enableLoop ? virtualIndexRef.current + (nextIndex - index) : nextIndex; + const max = imageLength - 1; + // 虚拟 index + // éžå¾ªçŽ¯æ¨¡å¼ï¼Œé™åˆ¶åŒºé—´ + const limitIndex = limitNumber(currentIndex, 0, max); + const nextVirtualIndex = enableLoop ? currentIndex : limitIndex; + // å•个å±å¹•宽度 + const singlePageWidth = dynamicInnerWidth + horizontalOffset; + + updateState({ + touched: false, + lastCX: undefined, + lastCY: undefined, + x: -singlePageWidth * nextVirtualIndex, + pause: isPause, + }); + + virtualIndexRef.current = nextVirtualIndex; + // 更新真实的 index + const realLoopIndex = nextIndex < 0 ? max : nextIndex > max ? 0 : nextIndex; + if (onIndexChange) { + onIndexChange(enableLoop ? realLoopIndex : limitIndex); + } + }, + }); + + useEventListener("keydown", (evt: KeyboardEvent) => { + if (visible) { + switch (evt.key) { + case "ArrowLeft": + changeIndex(index - 1, true); + break; + case "ArrowRight": + changeIndex(index + 1, true); + break; + case "Escape": + close(); + break; + default: + } + } + }); + + function handlePhotoTap(closeable: boolean | undefined) { + return closeable ? close() : updateState({ overlay: !overlay }); + } + + const handleResize = (dynamicInnerWidth: number) => () => { + updateState({ + x: -(dynamicInnerWidth + horizontalOffset) * index, + lastCX: undefined, + lastCY: undefined, + pause: true, + }); + virtualIndexRef.current = index; + }; + + function handleReachVerticalMove(clientY: number, nextScale?: number) { + if (lastCY === undefined) { + updateState({ + touched: true, + lastCY: clientY, + bg, + minimal: true, + }); + return; + } + const opacity = + maskOpacity === null ? null : limitNumber(maskOpacity, 0.01, maskOpacity - Math.abs(clientY - lastCY) / 100 / 4); + + updateState({ + touched: true, + lastCY, + bg: nextScale === 1 ? opacity : maskOpacity, + minimal: nextScale === 1, + }); + } + + function handleReachHorizontalMove(clientX: number) { + if (lastCX === undefined) { + updateState({ + touched: true, + lastCX: clientX, + x, + pause: false, + }); + return; + } + const originOffsetClientX = clientX - lastCX; + let offsetClientX = originOffsetClientX; + + // 第一张和最åŽä¸€å¼ è¶…出è·ç¦»å‡åŠ + if ( + !enableLoop && + ((index === 0 && originOffsetClientX > 0) || (index === imageLength - 1 && originOffsetClientX < 0)) + ) { + offsetClientX = originOffsetClientX / 2; + } + + updateState({ + touched: true, + lastCX, + x: -(dynamicInnerWidth + horizontalOffset) * virtualIndexRef.current + offsetClientX, + pause: false, + }); + } + + function handleReachMove(reachPosition: ReachType, clientX: number, clientY: number, nextScale?: number) { + if (reachPosition === "x") { + handleReachHorizontalMove(clientX); + } else if (reachPosition === "y") { + handleReachVerticalMove(clientY, nextScale); + } + } + + function handleReachUp(clientX: number, clientY: number) { + const offsetClientX = clientX - (lastCX ?? clientX); + const offsetClientY = clientY - (lastCY ?? clientY); + let willClose = false; + // 下一张 + if (offsetClientX < -maxMoveOffset) { + changeIndex(index + 1); + return; + } + // 上一张 + if (offsetClientX > maxMoveOffset) { + changeIndex(index - 1); + return; + } + const singlePageWidth = dynamicInnerWidth + horizontalOffset; + // 当å‰åç§» + const currentTranslateX = -singlePageWidth * virtualIndexRef.current; + + if (Math.abs(offsetClientY) > 100 && minimal && pullClosable) { + willClose = true; + close(); + } + updateState({ + touched: false, + x: currentTranslateX, + lastCX: undefined, + lastCY: undefined, + bg: maskOpacity, + overlay: willClose ? true : overlay, + }); + } + // 截å–相邻的图片 + const adjacentImages = useAdjacentImages(images, index, enableLoop); + const currentFile = images[index]?.file; + const displayOpt = useActionDisplayOpt(currentFile ? [currentFile] : []); + + useEffect(() => { + //handleReachMove("x", 0, 0); + handleReachUp(0, 0); + }, [sideBarOpen]); + + if (!realVisible) { + return null; + } + + const currentOverlayVisible = overlay && !activeAnimation; + // 关闭过程中使用下拉ä¿å­˜çš„逿˜Žåº¦ + const currentOpacity = visible ? bg : lastBg; + // è¦†ç›–ç‰©å‚æ•° + const overlayParams: OverlayRenderProps | undefined = onScale && + onRotate && { + images, + index, + visible, + onClose: close, + onIndexChange: changeIndex, + overlayVisible: currentOverlayVisible, + overlay: currentImage && currentImage.overlay, + scale, + rotate, + onScale, + onRotate, + }; + // 动画时间 + const currentSpeed = speedFn ? speedFn(activeAnimation) : defaultSpeed; + const currentEasing = easingFn ? easingFn(activeAnimation) : defaultEasing; + const slideSpeed = speedFn ? speedFn(3) : defaultSpeed + 200; + const slideEasing = easingFn ? easingFn(3) : defaultEasing; + + return ( + + e.stopPropagation()} + container={portalContainer} + > +
    + {bannerVisible && ( +
    +
    + {index + 1} / {moreFiles ? imageLength - 1 : imageLength} + {moreFiles ? "+" : ""} +
    +
    + {toolbarRender && overlayParams && toolbarRender(overlayParams)} + {currentFile && displayOpt.showDownload && ( + + dispatch(downloadSingleFile(currentFile, images[index]?.version))}> + + + + )} + {currentFile && displayOpt.showInfo && ( + + + dispatch( + setSidebar({ + open: true, + target: images[index].file, + }), + ) + } + > + + + + )} + {currentFile && + displayOpt && + canUpdate(displayOpt) && + editorSupportedExt.includes(fileExtension(currentFile.name) ?? "") && ( + + dispatch(switchToImageEditor(currentFile, images[index]?.version))}> + + + + )} + {currentFile && ( + + dispatch(openFileContextMenu(FileManagerIndex.main, currentFile, false, e))} + > + + + + )} + + + +
    +
    + )} + {adjacentImages.map((item: DataType, currentIndex) => { + // 截å–之å‰çš„索引ä½ç½® + const nextIndex = + !enableLoop && index === 0 ? index + currentIndex : virtualIndexRef.current - 1 + currentIndex; + + return ( + handlePhotoTap(photoClosable)} + onMaskTap={() => handlePhotoTap(maskClosable)} + wrapClassName={photoWrapClassName} + className={photoClassName} + style={{ + left: `${(dynamicInnerWidth + horizontalOffset) * nextIndex}px`, + transform: `translate3d(${x}px, 0px, 0)`, + transition: touched || pause ? undefined : `transform ${slideSpeed}ms ${slideEasing}`, + }} + loadingElement={loadingElement} + brokenElement={brokenElement} + onPhotoResize={handleResize(dynamicInnerWidth)} + isActive={virtualIndexRef.current === nextIndex} + expose={updateState} + /> + ); + })} + {!isTouchDevice && bannerVisible && ( + <> + {(enableLoop || index !== 0) && ( +
    changeIndex(index - 1, true)}> + +
    + )} + {(enableLoop || index + 1 < imageLength) && ( +
    changeIndex(index + 1, true)}> + +
    + )} + + )} + {overlayRender && overlayParams && ( +
    {overlayRender(overlayParams)}
    + )} + + + ); +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/PhotoView.tsx b/src/component/Viewers/ImageViewer/react-photo-view/PhotoView.tsx new file mode 100755 index 0000000..8e2e711 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/PhotoView.tsx @@ -0,0 +1,106 @@ +import type React from "react"; +import { useImperativeHandle, Children, cloneElement, useContext, useEffect, useMemo, useRef } from "react"; +import useInitial from "./hooks/useInitial"; +import useMethods from "./hooks/useMethods"; +import type { PhotoContextType } from "./photo-context"; +import PhotoContext from "./photo-context"; +import type { PhotoRenderParams } from "./types"; + +export interface PhotoViewProps { + /** + * å›¾ç‰‡åœ°å€ + */ + src?: string; + /** + * 自定义渲染,优先级比 src 低 + */ + render?: (props: PhotoRenderParams) => React.ReactNode; + /** + * 自定义覆盖节点 + */ + overlay?: React.ReactNode; + /** + * 自定义渲染节点宽度 + */ + width?: number; + /** + * 自定义渲染节点高度 + */ + height?: number; + /** + * å­èŠ‚ç‚¹ï¼Œä¸€èˆ¬ä¸ºç¼©ç•¥å›¾ + */ + children?: React.ReactElement; + /** + * 触å‘的事件 + */ + triggers?: ("onClick" | "onDoubleClick")[]; +} + +const PhotoView: React.FC = ({ + src, + render, + overlay, + width, + height, + triggers = ["onClick"], + children, +}) => { + const photoContext = useContext(PhotoContext); + const key = useInitial(() => photoContext.nextId()); + const originRef = useRef(null); + + useImperativeHandle((children as React.FunctionComponentElement)?.ref, () => originRef.current); + + useEffect(() => { + return () => { + photoContext.remove(key); + }; + }, []); + + function invokeChildrenFn(eventName: string, e: React.SyntheticEvent) { + if (children) { + const eventFn = children.props[eventName]; + if (eventFn) { + eventFn(e); + } + } + } + + const fn = useMethods({ + render(props: PhotoRenderParams) { + return render && render(props); + }, + show(eventName: string, e: React.MouseEvent) { + photoContext.show(key); + invokeChildrenFn(eventName, e); + }, + }); + + const eventListeners = useMemo(() => { + const listener = {}; + triggers.forEach((eventName) => { + listener[eventName] = fn.show.bind(null, eventName); + }); + return listener; + }, []); + + useEffect(() => { + photoContext.update({ + key, + src, + originRef, + render: fn.render, + overlay, + width, + height, + }); + }, [src]); + + if (children) { + return Children.only(cloneElement(children, { ...eventListeners, ref: originRef })); + } + return null; +}; + +export default PhotoView; diff --git a/src/component/Viewers/ImageViewer/react-photo-view/components/ArrowLeft.tsx b/src/component/Viewers/ImageViewer/react-photo-view/components/ArrowLeft.tsx new file mode 100755 index 0000000..982cb4a --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/components/ArrowLeft.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +function ArrowLeft(props: React.HTMLAttributes) { + return ( + + + + ); +} + +export default ArrowLeft; diff --git a/src/component/Viewers/ImageViewer/react-photo-view/components/ArrowRight.tsx b/src/component/Viewers/ImageViewer/react-photo-view/components/ArrowRight.tsx new file mode 100755 index 0000000..1965d61 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/components/ArrowRight.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +function ArrowRight(props: React.HTMLAttributes) { + return ( + + + + ); +} + +export default ArrowRight; diff --git a/src/component/Viewers/ImageViewer/react-photo-view/components/CloseIcon.tsx b/src/component/Viewers/ImageViewer/react-photo-view/components/CloseIcon.tsx new file mode 100755 index 0000000..3281acc --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/components/CloseIcon.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +function CloseIcon(props: React.HTMLAttributes) { + return ( + + + + ); +} + +export default CloseIcon; diff --git a/src/component/Viewers/ImageViewer/react-photo-view/components/PreventScroll.tsx b/src/component/Viewers/ImageViewer/react-photo-view/components/PreventScroll.tsx new file mode 100755 index 0000000..a9cb9a0 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/components/PreventScroll.tsx @@ -0,0 +1,18 @@ +import { useTheme } from "@mui/material"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import { useEffect } from "react"; + +export default function PreventScroll() { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + useEffect(() => { + document.body.style.overflow = "hidden"; + + return () => { + alert(isMobile); + document.body.style.overflow = isMobile ? "initial" : "hidden"; + }; + }, []); + + return null; +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/components/SlidePortal.less b/src/component/Viewers/ImageViewer/react-photo-view/components/SlidePortal.less new file mode 100755 index 0000000..96b7079 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/components/SlidePortal.less @@ -0,0 +1,12 @@ +.PhotoView { + &-Portal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1250; + overflow: hidden; + touch-action: none; + } +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/components/SlidePortal.tsx b/src/component/Viewers/ImageViewer/react-photo-view/components/SlidePortal.tsx new file mode 100755 index 0000000..f1393d9 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/components/SlidePortal.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import { createPortal } from "react-dom"; +import "./SlidePortal.less"; + +export interface ISliderPortalProps extends React.HTMLAttributes { + container?: HTMLElement; +} + +function SlidePortal({ container = document.body, ...rest }: ISliderPortalProps) { + return createPortal(
    , container); +} + +export default SlidePortal; diff --git a/src/component/Viewers/ImageViewer/react-photo-view/components/Spinner.less b/src/component/Viewers/ImageViewer/react-photo-view/components/Spinner.less new file mode 100755 index 0000000..6d8a958 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/components/Spinner.less @@ -0,0 +1,30 @@ +@keyframes PhotoView__rotate { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} + +@keyframes PhotoView__delayIn { + 0%, + 50% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +.PhotoView { + &__Spinner { + animation: PhotoView__delayIn 0.4s linear both; + + svg { + animation: PhotoView__rotate 0.6s linear infinite; + } + } +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/components/Spinner.tsx b/src/component/Viewers/ImageViewer/react-photo-view/components/Spinner.tsx new file mode 100755 index 0000000..0bc1e9e --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/components/Spinner.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import "./Spinner.less"; + +// eslint-disable-next-line react/prop-types +function Spinner({ className = "", ...props }: React.HTMLAttributes) { + return ( +
    + + + + +
    + ); +} + +export default Spinner; diff --git a/src/component/Viewers/ImageViewer/react-photo-view/hooks/useAdjacentImages.ts b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useAdjacentImages.ts new file mode 100755 index 0000000..4a9f469 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useAdjacentImages.ts @@ -0,0 +1,16 @@ +import { useMemo } from "react"; +import type { DataType } from "../types"; + +/** + * 截å–相邻三张图片 + */ +export default function useAdjacentImages(images: DataType[], index: number, loop: boolean) { + return useMemo(() => { + const imageLength = images.length; + if (loop) { + const connected = images.concat(images).concat(images); + return connected.slice(imageLength + index - 1, imageLength + index + 2); + } + return images.slice(Math.max(index - 1, 0), Math.min(index + 2, imageLength + 1)); + }, [images, index, loop]); +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/hooks/useAnimationOrigin.tsx b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useAnimationOrigin.tsx new file mode 100755 index 0000000..8041465 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useAnimationOrigin.tsx @@ -0,0 +1,95 @@ +import type { Dispatch, MutableRefObject, SetStateAction } from "react"; +import { useState, useEffect, useRef } from "react"; +import type { EasingMode, OriginRectType } from "../types"; +import useMethods from "./useMethods"; +import { maxWaitAnimationTime } from "../variables"; + +const initialRect: OriginRectType = { + T: 0, + L: 0, + W: 0, + H: 0, + // 图åƒå¡«å……æ–¹å¼ + FIT: undefined, +}; + +export default function useAnimationOrigin( + visible: boolean | undefined, + originRef: MutableRefObject | undefined, + loaded: boolean, + speed: number, + updateEasing: (pause: boolean) => void, +): [ + // åŠ¨ç”»çŠ¶æ€ + easingMode: EasingMode, + originRect: OriginRectType, +] { + const [originRect, updateOriginRect] = useState(initialRect); + // åŠ¨ç”»çŠ¶æ€ + const [easingMode, updateEasingMode] = useState(0); + const initialTime = useRef(); + + const fn = useMethods({ + OK: () => visible && updateEasingMode(4), + }); + + useEffect(() => { + // 记录åˆå§‹æ‰“开的时间 + if (!initialTime.current) { + initialTime.current = Date.now(); + } + if (!loaded) { + return; + } + handleUpdateOrigin(originRef, updateOriginRect); + // æ‰“å¼€åŠ¨ç”»å¤„ç† + if (visible) { + // å°äºŽæœ€å¤§å…许动画时间,则执行缩放动画 + if (Date.now() - initialTime.current < maxWaitAnimationTime) { + updateEasingMode(1); + // å»¶æ—¶æ‰§è¡ŒåŠ¨ç”»ï¼Œä¿æŒ transition 生效 + requestAnimationFrame(() => { + updateEasingMode(2); + requestAnimationFrame(() => handleToShape(3)); + }); + setTimeout(fn.OK, speed); + return; + } + // è¶…å‡ºåˆ™ä¸æ‰§è¡Œ + updateEasingMode(4); + return; + } + // å…³é—­åŠ¨ç”»å¤„ç† + handleToShape(5); + }, [visible, loaded]); + + function handleToShape(currentShape: EasingMode) { + updateEasing(false); + updateEasingMode(currentShape); + } + + return [easingMode, originRect]; +} + +/** + * 更新缩略图ä½ç½®ä¿¡æ¯ + */ +function handleUpdateOrigin( + originRef: MutableRefObject | undefined, + updateOriginRect: Dispatch>, +) { + const element = originRef && originRef.current; + + if (element && element.nodeType === 1) { + // 获å–è§¦å‘æ—¶èŠ‚ç‚¹ä½ç½® + const { top, left, width, height } = element.getBoundingClientRect(); + const isImage = element.tagName === "IMG"; + updateOriginRect({ + T: top, + L: left, + W: width, + H: height, + FIT: isImage ? (getComputedStyle(element).objectFit as "contain" | "cover" | "fill" | undefined) : undefined, + }); + } +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/hooks/useAnimationPosition.ts b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useAnimationPosition.ts new file mode 100755 index 0000000..764beb3 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useAnimationPosition.ts @@ -0,0 +1,46 @@ +import type { MutableRefObject } from "react"; +import useAnimationOrigin from "./useAnimationOrigin"; +import useTargetScale from "./useTargetScale"; + +export default function useAnimationPosition( + dynamicInnerWidth: number, + visible: boolean | undefined, + originRef: MutableRefObject | undefined, + loaded: boolean, + x: number, + y: number, + width: number, + height: number, + scale: number, + speed: number, + updateEasing: (pause: boolean) => void, +) { + // 延迟更新 width/height + const [autoWidth, autoHeight, autoScale] = useTargetScale(width, height, scale, speed, updateEasing); + // 动画æºå¤„ç† + const [easingMode, originRect] = useAnimationOrigin(visible, originRef, loaded, speed, updateEasing); + + // 计算动画ä½ç½® + const { T, L, W, H, FIT } = originRect; + // åç§»é‡ï¼Œx: 0, y: 0 居中为åˆå§‹ + const centerWidth = dynamicInnerWidth / 2; + const centerHeight = innerHeight / 2; + const offsetX = centerWidth - (width * scale) / 2; + const offsetY = centerHeight - (height * scale) / 2; + // ç¼©ç•¥å›¾çŠ¶æ€ + const miniMode = easingMode < 3 || easingMode > 4; + // 有缩略图时,则为缩略图的ä½ç½®ï¼Œå¦åˆ™å±…中 + const translateX = miniMode ? (W ? L : centerWidth) : x + offsetX; + const translateY = miniMode ? (W ? T : centerHeight) : y + offsetY; + + // 最å°å€¼ç¼©æ”¾ + const minScale = W / (width * scale) || 0.01; + + // 适应 objectFit ä¿æŒç¼©ç•¥å›¾å®½é«˜æ¯” + const currentHeight = miniMode && FIT ? autoWidth * (H / W) : autoHeight; + // åˆå§‹åŠ è½½æƒ…å†µæ— ç¼©æ”¾ + const currentScale = easingMode === 0 ? autoScale : miniMode ? minScale : autoScale; + const opacity = miniMode ? (FIT ? 1 : 0) : 1; + + return [translateX, translateY, autoWidth, currentHeight, currentScale, opacity, easingMode, FIT] as const; +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/hooks/useAnimationVisible.tsx b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useAnimationVisible.tsx new file mode 100755 index 0000000..7a987cd --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useAnimationVisible.tsx @@ -0,0 +1,57 @@ +import { useReducer, useRef } from "react"; +import type { ActiveAnimationType } from "../types"; +import useForkedVariable from "./useForkedVariable"; + +/** + * 动画关闭处ç†çœŸå®žå…³é—­çŠ¶æ€ + * 通过 onAnimationEnd 回调实现 leaveCallback + */ +export default function useAnimationVisible( + visible: boolean | undefined, + afterClose?: () => void, +): [realVisible: boolean | undefined, activeAnimation: ActiveAnimationType, onAnimationEnd: () => void] { + const [, handleRender] = useReducer((c) => !c, false); + + const activeAnimation = useRef(0); + + // å¯è§çжæ€åˆ†æ”¯ + const [realVisible, modifyRealVisible] = useForkedVariable(visible, (modify) => { + // å¯è§çжæ€ï¼šè®¾ç½®è¿›å…¥åŠ¨ç”» + if (visible) { + modify(visible); + activeAnimation.current = 1; + } else { + activeAnimation.current = 2; + } + }); + + function onAnimationEnd() { + // 动画结æŸåŽè§¦å‘渲染 + handleRender(); + // 结æŸåŠ¨ç”»ï¼šè®¾ç½®éšè—çŠ¶æ€ + if (activeAnimation.current === 2) { + modifyRealVisible(false); + // 触å‘éšè—回调 + if (afterClose) { + afterClose(); + } + } + // é‡ç½®çŠ¶æ€ + activeAnimation.current = 0; + } + + return [ + /** + * 真实å¯è§çŠ¶æ€ + */ + realVisible, + /** + * 正在进行的动画 + */ + activeAnimation.current, + /** + * 动画结æŸåŽå›žè°ƒ + */ + onAnimationEnd, + ]; +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/hooks/useContinuousTap.ts b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useContinuousTap.ts new file mode 100755 index 0000000..b57de63 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useContinuousTap.ts @@ -0,0 +1,36 @@ +import { useRef } from "react"; +import useDebounceCallback from "./useDebounceCallback"; + +export type TapFuncType = (...args: T[]) => void; + +/** + * å•击和åŒå‡»äº‹ä»¶å¤„ç† + * @param singleTap - å•击事件 + * @param doubleTap - åŒå‡»äº‹ä»¶ + * @return invokeTap + */ +export default function useContinuousTap(singleTap: TapFuncType, doubleTap: TapFuncType): TapFuncType { + // 当å‰è¿žç»­ç‚¹å‡»æ¬¡æ•° + const continuousClick = useRef(0); + + const debounceTap = useDebounceCallback( + (...args) => { + continuousClick.current = 0; + singleTap(...args); + }, + { + wait: 300, + }, + ); + + return function invokeTap(...args) { + continuousClick.current += 1; + debounceTap(...args); + // åŒå‡» + if (continuousClick.current >= 2) { + debounceTap.cancel(); + continuousClick.current = 0; + doubleTap(...args); + } + }; +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/hooks/useDebounceCallback.ts b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useDebounceCallback.ts new file mode 100755 index 0000000..24ed533 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useDebounceCallback.ts @@ -0,0 +1,68 @@ +import { useCallback, useRef } from "react"; + +interface DebounceCallback { + (...args: CallbackArguments): void; + cancel: () => void; +} + +export default function useDebounceCallback( + callback: (...args: CallbackArguments) => void, + { + leading = false, + maxWait, + wait = maxWait || 0, + }: { + leading?: boolean; + maxWait?: number; + wait?: number; + }, +): DebounceCallback { + const callbackRef = useRef(callback); + callbackRef.current = callback; + + const prev = useRef(0); + const trailingTimeout = useRef>(); + const clearTrailing = () => trailingTimeout.current && clearTimeout(trailingTimeout.current); + + const fn = useCallback( + (...args: CallbackArguments) => { + const now = Date.now(); + + function call() { + prev.current = now; + clearTrailing(); + callbackRef.current.apply(null, args); + } + const last = prev.current; + const offset = now - last; + // leading + if (last === 0) { + if (leading) { + call(); + } + prev.current = now; + } + + // body + if (maxWait !== undefined) { + if (offset > maxWait) { + call(); + return; + } + } else if (offset < wait) { + prev.current = now; + } + + // trailing + clearTrailing(); + trailingTimeout.current = setTimeout(() => { + call(); + prev.current = 0; + }, wait); + }, + [wait, maxWait, leading], + ); + (fn as DebounceCallback).cancel = clearTrailing; + + return fn as DebounceCallback; +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/hooks/useEventListener.ts b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useEventListener.ts new file mode 100755 index 0000000..0fcc605 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useEventListener.ts @@ -0,0 +1,24 @@ +import { useEffect, useRef } from "react"; + +export default function useEventListener( + type: K | undefined, + fn: (evt: WindowEventMap[K]) => void, + options?: AddEventListenerOptions, +) { + const latest = useRef(fn); + latest.current = fn; + + useEffect(() => { + function wrapper(evt: WindowEventMap[K]) { + latest.current(evt); + } + if (type) { + window.addEventListener(type, wrapper, options); + } + return () => { + if (type) { + window.removeEventListener(type, wrapper); + } + }; + }, [type]); +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/hooks/useForkedVariable.ts b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useForkedVariable.ts new file mode 100755 index 0000000..c5bbcc3 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useForkedVariable.ts @@ -0,0 +1,21 @@ +import { useRef, useMemo } from "react"; + +/** + * 逻辑分å‰å˜é‡å¤„ç† + * æ­¤ hook ä¸è§¦å‘é¢å¤–渲染 + */ +export default function useForkedVariable(initial: T, updater: (modify: (variable: T) => void) => void) { + // åˆå§‹åˆ†å‰å˜é‡ + const forkedRef = useRef(initial); + + function modify(next: T) { + forkedRef.current = next; + } + + useMemo(() => { + // 傿•°å˜åŒ–之åŽåŒæ­¥å†…部分å‰å˜é‡ + updater(modify); + }, [initial]); + + return [forkedRef.current, modify] as const; +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/hooks/useInitial.ts b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useInitial.ts new file mode 100755 index 0000000..90414b6 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useInitial.ts @@ -0,0 +1,10 @@ +import { useRef } from "react"; + +export default function useInitial any>(callback: T) { + const { current } = useRef({ sign: false, fn: undefined as ReturnType }); + if (!current.sign) { + current.sign = true; + current.fn = callback(); + } + return current.fn; +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/hooks/useIsomorphicLayoutEffect.ts b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useIsomorphicLayoutEffect.ts new file mode 100755 index 0000000..4d2f1bc --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useIsomorphicLayoutEffect.ts @@ -0,0 +1,5 @@ +import { useEffect, useLayoutEffect } from "react"; + +const isSSR = typeof window === "undefined" || /ServerSideRendering/.test(navigator && navigator.userAgent); + +export default isSSR ? useEffect : useLayoutEffect; diff --git a/src/component/Viewers/ImageViewer/react-photo-view/hooks/useMethods.ts b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useMethods.ts new file mode 100755 index 0000000..191eeb2 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useMethods.ts @@ -0,0 +1,22 @@ +import { useRef } from "react"; + +/** + * Hook of persistent methods + */ +export default function useMethods any>>(fn: T) { + const { current } = useRef({ + fn, + curr: undefined as T | undefined, + }); + current.fn = fn; + + if (!current.curr) { + const curr = Object.create(null); + Object.keys(fn).forEach((key) => { + curr[key] = (...args: unknown[]) => current.fn[key].call(current.fn, ...args); + }); + current.curr = curr; + } + + return current.curr as T; +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/hooks/useMountedRef.ts b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useMountedRef.ts new file mode 100755 index 0000000..e66e93a --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useMountedRef.ts @@ -0,0 +1,14 @@ +import { useEffect, useRef } from "react"; + +const useMountedRef = () => { + const mountedRef = useRef(false); + useEffect(() => { + mountedRef.current = true; + return () => { + mountedRef.current = false; + }; + }, []); + return mountedRef; +}; + +export default useMountedRef; diff --git a/src/component/Viewers/ImageViewer/react-photo-view/hooks/useScrollPosition.ts b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useScrollPosition.ts new file mode 100755 index 0000000..ce5061d --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useScrollPosition.ts @@ -0,0 +1,207 @@ +import { computePositionEdge } from "../utils/edgeHandle"; +import getPositionOnMoveOrScale from "../utils/getPositionOnMoveOrScale"; +import getRotateSize from "../utils/getRotateSize"; +import { defaultSpeed, maxTouchTime } from "../variables"; +import useMethods from "./useMethods"; + +// 触边è¿åЍå馈 +const rebound = (start: number, bound: number, callback: (spatial: number) => boolean) => + easeOutMove( + start, + bound, + callback, + defaultSpeed / 4, + (t) => t, + () => easeOutMove(bound, start, callback), + ); + +/** + * ç‰©ç†æ»šåŠ¨åˆ°å…·ä½“ä½ç½® + */ +export default function useScrollPosition boolean>( + callbackX: C, + callbackY: C, + callbackS: C, + dynamicInnerWidth: number, +) { + const callback = useMethods({ + X: (spatial: number) => callbackX(spatial), + Y: (spatial: number) => callbackY(spatial), + S: (spatial: number) => callbackS(spatial), + }); + + return ( + x: number, + y: number, + lastX: number, + lastY: number, + width: number, + height: number, + scale: number, + safeScale: number, + lastScale: number, + rotate: number, + touchedTime: number, + ) => { + const [currentWidth, currentHeight] = getRotateSize(rotate, width, height); + // 开始状æ€ä¸‹è¾¹ç¼˜è§¦å‘çŠ¶æ€ + const [beginEdgeX, beginX] = computePositionEdge(x, safeScale, currentWidth, dynamicInnerWidth); + const [beginEdgeY, beginY] = computePositionEdge(y, safeScale, currentHeight, innerHeight); + const moveTime = Date.now() - touchedTime; + + // 时间过长ã€è¶…å‡ºå®‰å…¨èŒƒå›´çš„æƒ…å†µä¸‹ä¸æ‰§è¡Œæ»šåŠ¨é€»è¾‘ï¼Œæ¢å¤å®‰å…¨èŒƒå›´ + if (moveTime >= maxTouchTime || safeScale !== scale || Math.abs(lastScale - scale) > 1) { + // 计算中心缩放点 + const { x: nextX, y: nextY } = getPositionOnMoveOrScale(x, y, width, height, scale, safeScale); + const targetX = beginEdgeX ? beginX : nextX !== x ? nextX : null; + const targetY = beginEdgeY ? beginY : nextY !== y ? nextY : null; + + if (targetX !== null) { + easeOutMove(x, targetX, callback.X); + } + if (targetY !== null) { + easeOutMove(y, targetY, callback.Y); + } + if (safeScale !== scale) { + easeOutMove(scale, safeScale, callback.S); + } + return; + } + + // åˆå§‹é€Ÿåº¦ + const speedX = (x - lastX) / moveTime; + const speedY = (y - lastY) / moveTime; + const speedT = Math.sqrt(speedX ** 2 + speedY ** 2); + // æ˜¯å¦æŽ¥è§¦åˆ°è¾¹ç¼˜ + let edgeX = false; + let edgeY = false; + + scrollMove(speedT, (spatial) => { + const nextX = x + spatial * (speedX / speedT); + const nextY = y + spatial * (speedY / speedT); + + const [isEdgeX, currentX] = computePositionEdge(nextX, scale, currentWidth, innerWidth); + const [isEdgeY, currentY] = computePositionEdge(nextY, scale, currentHeight, innerHeight); + + if (isEdgeX && !edgeX) { + edgeX = true; + if (beginEdgeX) { + easeOutMove(nextX, currentX, callback.X); + } else { + rebound(currentX, nextX + (nextX - currentX), callback.X); + } + } + + if (isEdgeY && !edgeY) { + edgeY = true; + if (beginEdgeY) { + easeOutMove(nextY, currentY, callback.Y); + } else { + rebound(currentY, nextY + (nextY - currentY), callback.Y); + } + } + // åŒæ—¶æŽ¥è§¦è¾¹ç¼˜çš„æƒ…å†µä¸‹åœæ­¢æ»šåЍ + if (edgeX && edgeY) { + return false; + } + + const resultX = edgeX || callback.X(currentX); + const resultY = edgeY || callback.Y(currentY); + return resultX && resultY; + }); + }; +} + +// 加速度 +const acceleration = -0.001; +// 阻力 +const resistance = 0.0002; + +/** + * é€šè¿‡é€Ÿåº¦æ»šåŠ¨åˆ°åœæ­¢ + */ +function scrollMove(initialSpeed: number, callback: (spatial: number) => boolean) { + let v = initialSpeed; + let s = 0; + let lastTime: number | undefined; + let frameId = 0; + + const calcMove = (now: number) => { + if (!lastTime) { + lastTime = now; + } + const dt = now - lastTime; + const direction = Math.sign(initialSpeed); + const a = direction * acceleration; + const f = Math.sign(-v) * v ** 2 * resistance; + const ds = v * dt + ((a + f) * dt ** 2) / 2; + v += (a + f) * dt; + + s += ds; + // move to s + lastTime = now; + + if (direction * v <= 0) { + caf(); + return; + } + + if (callback(s)) { + raf(); + return; + } + caf(); + }; + raf(); + + function raf() { + frameId = requestAnimationFrame(calcMove); + } + function caf() { + cancelAnimationFrame(frameId); + } +} + +/** + * 缓动函数 + */ +const easeOutQuart = (x: number) => 1 - (1 - x) ** 4; + +/** + * 缓动回调 + */ +function easeOutMove( + start: number, + end: number, + callback: (spatial: number) => boolean, + speed = defaultSpeed, + easing = easeOutQuart, + complete?: () => void, +) { + const distance = end - start; + if (distance === 0) { + return; + } + + const startTime = Date.now(); + let frameId = 0; + + const calcMove = () => { + const time = Math.min(1, (Date.now() - startTime) / speed); + const result = callback(start + easing(time) * distance); + + if (result && time < 1) { + raf(); + return; + } + cancelAnimationFrame(frameId); + if (time >= 1 && complete) { + complete(); + } + }; + raf(); + + function raf() { + frameId = requestAnimationFrame(calcMove); + } +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/hooks/useSetState.ts b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useSetState.ts new file mode 100755 index 0000000..59e8236 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useSetState.ts @@ -0,0 +1,11 @@ +import { useReducer } from "react"; + +export default function useSetState>(initialState: S) { + return useReducer( + (state: S, action: Partial | ((state: S) => Partial)) => ({ + ...state, + ...(typeof action === "function" ? action(state) : action), + }), + initialState, + ); +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/hooks/useTargetScale.ts b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useTargetScale.ts new file mode 100755 index 0000000..0fa484e --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/hooks/useTargetScale.ts @@ -0,0 +1,46 @@ +import { useRef } from "react"; +import useSetState from "./useSetState"; +import useIsomorphicLayoutEffect from "./useIsomorphicLayoutEffect"; +import useDebounceCallback from "./useDebounceCallback"; + +/** + * ç›®æ ‡ç¼©æ”¾å»¶è¿Ÿå¤„ç† + */ +export default function useTargetScale( + realWidth: number, + realHeight: number, + realScale: number, + speed: number, + updateEasing: (pause: boolean) => void, +) { + const execRef = useRef(false); + + const [{ lead, scale }, updateState] = useSetState({ lead: true, scale: realScale }); + + const moveScale = useDebounceCallback( + async (current: number) => { + updateEasing(true); + updateState({ lead: false, scale: current }); + }, + { wait: speed }, + ); + + useIsomorphicLayoutEffect(() => { + if (!execRef.current) { + execRef.current = true; + return; + } + updateEasing(false); + updateState({ lead: true }); + + moveScale(realScale); + }, [realScale]); + + // è¿åЍ开始 + if (lead) { + return [realWidth * scale, realHeight * scale, realScale / scale] as const; + } + + // è¿åŠ¨ç»“æŸ + return [realWidth * realScale, realHeight * realScale, 1] as const; +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/index.ts b/src/component/Viewers/ImageViewer/react-photo-view/index.ts new file mode 100755 index 0000000..3543970 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/index.ts @@ -0,0 +1,5 @@ +import PhotoProvider from "./PhotoProvider"; +import PhotoView from "./PhotoView"; +import PhotoSlider from "./PhotoSlider"; + +export { PhotoProvider, PhotoView, PhotoSlider }; diff --git a/src/component/Viewers/ImageViewer/react-photo-view/photo-context.ts b/src/component/Viewers/ImageViewer/react-photo-view/photo-context.ts new file mode 100755 index 0000000..7a9943d --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/photo-context.ts @@ -0,0 +1,13 @@ +import { createContext } from "react"; +import type { DataType } from "./types"; + +export type UpdateItemType = (dataType: DataType) => void; + +export interface PhotoContextType { + show: (key: number) => void; + update: UpdateItemType; + remove: (key: number) => void; + nextId: () => number; +} + +export default createContext(undefined as unknown as PhotoContextType); diff --git a/src/component/Viewers/ImageViewer/react-photo-view/types.ts b/src/component/Viewers/ImageViewer/react-photo-view/types.ts new file mode 100755 index 0000000..cd5bee4 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/types.ts @@ -0,0 +1,258 @@ +import type React from "react"; +import { FileResponse } from "../../../../api/explorer.ts"; + +/** + * èµ„æºæ•°æ®ç±»åž‹ + */ +export interface DataType { + /** + * 唯一标识 + */ + key: number | string; + /** + * 资æºåœ°å€ + */ + src?: string; + /** + * 自定义渲染,优先级比 src 低 + */ + render?: (props: PhotoRenderParams) => React.ReactNode; + /** + * 自定义覆盖节点 + */ + overlay?: React.ReactNode; + /** + * 指定渲染节点宽度 + */ + width?: number; + /** + * 指定渲染节点高度 + */ + height?: number; + /** + * è§¦å‘ ref + */ + originRef?: React.MutableRefObject; + + // Cloudreve specific + file?: FileResponse; + version?: string; + + loadMorePlaceholder?: boolean; +} + +export interface PhotoProviderBase { + /** + * 是å¦å¾ªçŽ¯é¢„è§ˆï¼Œè¾¾åˆ°è¯¥æ•°é‡åˆ™å¯ç”¨ + * @defaultValue 3 + */ + loop?: boolean | number; + /** + * 动画速度 + * @defaultValue 400 + */ + speed?: (type: ActiveAnimationType) => number; + /** + * 动画函数 + * @defaultValue 'cubic-bezier(0.25, 0.8, 0.25, 1)' + */ + easing?: (type: ActiveAnimationType) => string; + /** + * 图片点击是å¦å¯å…³é—­ + */ + photoClosable?: boolean; + /** + * 背景点击是å¦å¯å…³é—­ + * @defaultValue true + */ + maskClosable?: boolean; + /** + * é»˜è®¤èƒŒæ™¯é€æ˜Žåº¦ + * 设置 null 背景ä¸å“应下拉å˜åŒ– + * @defaultValue 1 + */ + maskOpacity?: number | null; + /** + * 下拉是å¦å¯å…³é—­ + * @defaultValue true + */ + pullClosable?: boolean; + /** + * å¯¼èˆªæ¡ visible + * @defaultValue true + */ + bannerVisible?: boolean; + /** + * 自定义渲染覆盖物 + */ + overlayRender?: (overlayProps: OverlayRenderProps) => React.ReactNode; + /** + * è‡ªå®šä¹‰æ¸²æŸ“å·¥å…·æ  + */ + toolbarRender?: (overlayProps: OverlayRenderProps) => React.ReactNode; + className?: string; + maskClassName?: string; + photoWrapClassName?: string; + photoClassName?: string; + /** + * 自定义 loading + */ + loadingElement?: JSX.Element; + /** + * 自定义加载失败渲染 + */ + brokenElement?: JSX.Element | ((photoProps: BrokenElementParams) => JSX.Element); + /** + * @defaultValue document.body + */ + portalContainer?: HTMLElement; +} + +export type PhotoRenderParams = { + /** + * 自定义渲染 DOM 属性 + */ + attrs: Partial>; + scale: number; + rotate: number; +}; + +/** + * brokenElement å‡½æ•°å‚æ•° + */ +export interface BrokenElementParams { + src: string; +} + +export interface OverlayRenderProps { + /** + * 图片列表 + */ + images: DataType[]; + /** + * 当å‰ç´¢å¼• + */ + index: number; + /** + * 索引改å˜å›žè°ƒ + */ + onIndexChange: (index: number) => void; + /** + * 是å¦å¯è§ + */ + visible: boolean; + /** + * 关闭事件回调 + */ + onClose: (evt?: React.MouseEvent | React.TouchEvent) => void; + /** + * 覆盖物是å¦å¯è§ + */ + overlayVisible: boolean; + /** + * 自定义覆盖节点 + */ + overlay?: React.ReactNode; + /** + * 当剿—‹è½¬è§’度 + */ + rotate: number; + /** + * 旋转事件回调 + */ + onRotate: (rotate: number) => void; + /** + * 当å‰ç¼©æ”¾ + */ + scale: number; + /** + * 缩放事件回调 + */ + onScale: (scale: number) => void; +} + +export interface ExposedProperties { + // 缩放 + scale?: number; + // 旋转 + rotate?: number; + // 缩放回调 + onScale?: (scale: number) => void; + // 旋转回调 + onRotate?: (rotate: number) => void; +} + +export type ReachMoveFunction = (reachPosition: ReachType, clientX: number, clientY: number, scale?: number) => void; + +export type ReachFunction = (clientX: number, clientY: number) => void; + +export type PhotoTapFunction = (clientX: number, clientY: number) => void; + +/** + * è¾¹ç¼˜è¶…å‡ºçŠ¶æ€ + */ +export type CloseEdgeType = + | 1 // å°äºŽå±å¹•宽度 + | 2 // 抵触左边/上边 + | 3 // 抵触å³è¾¹/下边 + | undefined; // 正常滑动 + +/** + * 边缘触å‘çŠ¶æ€ + */ +export type ReachType = + | "x" // x è½´ + | "y" // y è½´ + | undefined; // æœªè§¦å‘ + +/** + * åˆå§‹å“åº”çŠ¶æ€ + */ +export type TouchStartType = + | 0 // æœªè§¦å‘ + | 1 // X 轴优先 + | 2 // Y 轴往上 push + | 3; // Y 轴往下 pull + +export type OriginRectType = { + // top + T: number; + // left + L: number; + // width + W: number; + // height + H: number; + // object-fit + FIT: "contain" | "cover" | "fill" | undefined; +}; + +/** + * åŠ¨ç”»çŠ¶æ€ + */ +export type EasingMode = + // 未åˆå§‹åŒ– + | 0 + // 进入:开始 + | 1 + // 进入:动画开始 + | 2 + // 进入:动画第二帧 + | 3 + // 正常 + | 4 + // 关闭 + | 5; + +/** + * 进行中的动画 + */ +export type ActiveAnimationType = + // 未åˆå§‹åŒ– + | 0 + // 进入 + | 1 + // 离开 + | 2 + // åˆ‡æ¢ + | 3; diff --git a/src/component/Viewers/ImageViewer/react-photo-view/utils/edgeHandle.ts b/src/component/Viewers/ImageViewer/react-photo-view/utils/edgeHandle.ts new file mode 100755 index 0000000..ce89f46 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/utils/edgeHandle.ts @@ -0,0 +1,48 @@ +import type { CloseEdgeType, ReachType, TouchStartType } from "../types"; + +/** + * èŽ·å–æŽ¥è§¦è¾¹ç¼˜ç±»åž‹ + */ +export const getReachType = ( + initialTouchState: TouchStartType, + horizontalCloseEdge: CloseEdgeType, + verticalCloseEdge: CloseEdgeType, + reachPosition: ReachType, +): ReachType => { + if ((horizontalCloseEdge && initialTouchState === 1) || reachPosition === "x") { + return "x"; + } + if ((verticalCloseEdge && initialTouchState > 1) || reachPosition === "y") { + return "y"; + } + return undefined; +}; + +/** + * 计算接触边缘ä½ç½® + * @param position - x/y + * @param scale + * @param size - width/height + * @param innerSize - innerWidth/innerHeight + * @return [CloseEdgeType, position] + */ +export const computePositionEdge = (position: number, scale: number, size: number, innerSize: number) => { + const currentWidth = size * scale; + // 图片超出的宽度 + const outOffset = (currentWidth - innerSize) / 2; + let closedEdge: CloseEdgeType; + + let current = position; + if (currentWidth <= innerSize) { + closedEdge = 1; + current = 0; + } else if (position > 0 && outOffset - position <= 0) { + closedEdge = 2; + current = outOffset; + } else if (position < 0 && outOffset + position <= 0) { + closedEdge = 3; + current = -outOffset; + } + + return [closedEdge, current] as const; +}; diff --git a/src/component/Viewers/ImageViewer/react-photo-view/utils/getMultipleTouchPosition.ts b/src/component/Viewers/ImageViewer/react-photo-view/utils/getMultipleTouchPosition.ts new file mode 100755 index 0000000..0800281 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/utils/getMultipleTouchPosition.ts @@ -0,0 +1,19 @@ +import type React from "react"; + +/** + * 从 Touch 事件中获å–两个触控中心ä½ç½® + */ +export default function getMultipleTouchPosition( + evt: TouchEvent | React.TouchEvent, +): [clientX: number, clientY: number, touchLength: number] { + const { clientX, clientY } = evt.touches[0]; + if (evt.touches.length >= 2) { + const { clientX: nextClientX, clientY: nextClientY } = evt.touches[1]; + return [ + (clientX + nextClientX) / 2, + (clientY + nextClientY) / 2, + Math.sqrt((nextClientX - clientX) ** 2 + (nextClientY - clientY) ** 2), + ]; + } + return [clientX, clientY, 0]; +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/utils/getPositionOnMoveOrScale.ts b/src/component/Viewers/ImageViewer/react-photo-view/utils/getPositionOnMoveOrScale.ts new file mode 100755 index 0000000..6ffbb4c --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/utils/getPositionOnMoveOrScale.ts @@ -0,0 +1,42 @@ +import { longModeRatio } from "../variables"; +import { computePositionEdge } from "./edgeHandle"; + +/** + * 获å–移动或缩放之åŽçš„中心点 + */ +export default function getPositionOnMoveOrScale( + x: number, + y: number, + width: number, + height: number, + scale: number, + toScale: number, + clientX: number = innerWidth / 2, + clientY: number = innerHeight / 2, + offsetX: number = 0, + offsetY: number = 0, +) { + // æ˜¯å¦æŽ¥è§¦è¾¹ç¼˜ + const [closedEdgeX] = computePositionEdge(x, toScale, width, innerWidth); + const [closedEdgeY] = computePositionEdge(y, toScale, height, innerHeight); + + const centerClientX = innerWidth / 2; + const centerClientY = innerHeight / 2; + + // åæ ‡åç§» + const lastPositionX = centerClientX + x; + const lastPositionY = centerClientY + y; + + // åç§»ä½ç½® + const originX = clientX - (clientX - lastPositionX) * (toScale / scale) - centerClientX; + const originY = clientY - (clientY - lastPositionY) * (toScale / scale) - centerClientY; + // é•¿å›¾æ¨¡å¼æ— å·¦å³å馈 + const longModeEdge = height / width >= longModeRatio && width * toScale === innerWidth; + // 超出边缘è·ç¦»å‡åŠ + return { + x: originX + (longModeEdge ? 0 : closedEdgeX ? offsetX / 2 : offsetX), + y: originY + (closedEdgeY ? offsetY / 2 : offsetY), + lastCX: clientX, + lastCY: clientY, + }; +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/utils/getRotateSize.ts b/src/component/Viewers/ImageViewer/react-photo-view/utils/getRotateSize.ts new file mode 100755 index 0000000..aa8564d --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/utils/getRotateSize.ts @@ -0,0 +1,13 @@ +/** + * èŽ·å–æ—‹è½¬åŽçš„宽高 + */ +export default function getRotateSize(rotate: number, width: number, height: number) { + const isVertical = rotate % 180 !== 0; + + // è‹¥å›¾ç‰‡ä¸æ˜¯æ°´å¹³åˆ™è°ƒæ¢å±žæ€§ + if (isVertical) { + return [height, width, isVertical] as const; + } + + return [width, height, isVertical] as const; +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/utils/getSuitableImageSize.ts b/src/component/Viewers/ImageViewer/react-photo-view/utils/getSuitableImageSize.ts new file mode 100755 index 0000000..089a405 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/utils/getSuitableImageSize.ts @@ -0,0 +1,47 @@ +import { longModeRatio } from "../variables"; +import getRotateSize from "./getRotateSize"; + +/** + * 获å–图片åˆé€‚çš„å¤§å° + */ +export default function getSuitableImageSize( + naturalWidth: number, + naturalHeight: number, + rotate: number, + dynamicInnerWidth: number, +) { + const [currentWidth, currentHeight, isVertical] = getRotateSize(rotate, dynamicInnerWidth, innerHeight); + + let y = 0; + let width = currentWidth; + let height = currentHeight; + + // 自适应宽高 + const autoWidth = (naturalWidth / naturalHeight) * currentHeight; + const autoHeight = (naturalHeight / naturalWidth) * currentWidth; + + if (naturalWidth < currentWidth && naturalHeight < currentHeight) { + width = naturalWidth; + height = naturalHeight; + } else if (naturalWidth < currentWidth && naturalHeight >= currentHeight) { + width = autoWidth; + } else if (naturalWidth >= currentWidth && naturalHeight < currentHeight) { + height = autoHeight; + } else if (naturalWidth / naturalHeight > currentWidth / currentHeight) { + height = autoHeight; + } + // é•¿å›¾æ¨¡å¼ + else if (naturalHeight / naturalWidth >= longModeRatio && !isVertical) { + height = autoHeight; + y = (height - currentHeight) / 2; + } else { + width = autoWidth; + } + return { + width, + height, + x: 0, + y, + pause: true, + }; +} diff --git a/src/component/Viewers/ImageViewer/react-photo-view/utils/isTouchDevice.ts b/src/component/Viewers/ImageViewer/react-photo-view/utils/isTouchDevice.ts new file mode 100755 index 0000000..6c8b9a4 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/utils/isTouchDevice.ts @@ -0,0 +1,6 @@ +/** + * æ˜¯å¦æ”¯æŒè§¦æ‘¸è®¾å¤‡ + */ +const isTouchDevice = typeof window !== "undefined" && "ontouchstart" in window; + +export default isTouchDevice; diff --git a/src/component/Viewers/ImageViewer/react-photo-view/utils/limitTarget.ts b/src/component/Viewers/ImageViewer/react-photo-view/utils/limitTarget.ts new file mode 100755 index 0000000..db0e5b4 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/utils/limitTarget.ts @@ -0,0 +1,12 @@ +import { maxScale, minScale } from "../variables"; + +export const limitNumber = (value: number, min: number, max: number) => { + return Math.max(Math.min(value, max), min); +}; + +/** + * é™åˆ¶æœ€å¤§/最å°ç¼©æ”¾ + */ +export const limitScale = (scale: number, max: number = 0, buffer: number = 0) => { + return limitNumber(scale, minScale * (1 - buffer), Math.max(maxScale, max) * (1 + buffer)); +}; diff --git a/src/component/Viewers/ImageViewer/react-photo-view/variables.ts b/src/component/Viewers/ImageViewer/react-photo-view/variables.ts new file mode 100755 index 0000000..8487585 --- /dev/null +++ b/src/component/Viewers/ImageViewer/react-photo-view/variables.ts @@ -0,0 +1,59 @@ +/** + * 最大触摸时间 + */ +export const maxTouchTime = 200; + +/** + * 默认动画速度 + */ +export const defaultSpeed = 400; + +/** + * 默认动画函数 + */ +export const defaultEasing = "cubic-bezier(0.25, 0.8, 0.25, 1)"; + +/** + * 最大滑动切æ¢å›¾ç‰‡è·ç¦» + */ +export const maxMoveOffset = 40; + +/** + * 图片的间隔 + */ +export const horizontalOffset = 20; + +/** + * 最å°åˆå§‹å“应è·ç¦» + */ +export const minStartTouchOffset = 20; + +/** + * é»˜è®¤èƒŒæ™¯é€æ˜Žåº¦ + */ +export const defaultOpacity = 1; + +/** + * 最å°ç¼©æ”¾åº¦ + */ +export const minScale = 1; + +/** + * 最大缩放度(若图片足够大,则会超出) + */ +export const maxScale = 6; + +/** + * 最å°é•¿å›¾æ¨¡å¼æ¯”例 + */ +export const longModeRatio = 3; + +/** + * 缩放弹性缓冲 + */ +export const scaleBuffer = 0.2; + +/** + * 最大等待动画时间 + */ +export const maxWaitAnimationTime = 250; diff --git a/src/component/Viewers/MarkdownEditor/Editor.tsx b/src/component/Viewers/MarkdownEditor/Editor.tsx new file mode 100755 index 0000000..7d53afb --- /dev/null +++ b/src/component/Viewers/MarkdownEditor/Editor.tsx @@ -0,0 +1,268 @@ +import { + AdmonitionDirectiveDescriptor, + BlockTypeSelect, + BoldItalicUnderlineToggles, + ChangeAdmonitionType, + ChangeCodeMirrorLanguage, + codeBlockPlugin, + CodeMirrorEditor, + codeMirrorPlugin, + CodeToggle, + ConditionalContents, + CreateLink, + diffSourcePlugin, + DiffSourceToggleWrapper, + DirectiveNode, + directivesPlugin, + EditorInFocus, + frontmatterPlugin, + headingsPlugin, + imagePlugin, + InsertAdmonition, + InsertCodeBlock, + InsertFrontmatter, + InsertImage, + InsertTable, + InsertThematicBreak, + linkDialogPlugin, + linkPlugin, + listsPlugin, + ListsToggle, + markdownShortcutPlugin, + MDXEditor, + quotePlugin, + Separator, + ShowSandpackInfo, + StrikeThroughSupSubToggles, + tablePlugin, + thematicBreakPlugin, + toolbarPlugin, + UndoRedo, +} from "@mdxeditor/editor"; +import "@mdxeditor/editor/style.css"; +import { Box } from "@mui/material"; +import i18next from "i18next"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import "./editor.css"; + +export interface MarkdownEditorProps { + value: string; + darkMode?: boolean; + initialValue: string; + onChange: (value: string) => void; + readOnly?: boolean; + displayOnly?: boolean; + onSaveShortcut?: () => void; + imageAutocompleteSuggestions?: string[] | null; + imagePreviewHandler?: (imageSource: string) => Promise; + imageUploadHandler?: ((image: File) => Promise) | null; +} + +function whenInAdmonition(editorInFocus: EditorInFocus | null) { + const node = editorInFocus?.rootNode; + if (!node || node.getType() !== "directive") { + return false; + } + + return ["note", "tip", "danger", "info", "caution"].includes((node as DirectiveNode).getMdastNode().name); +} + +function setEndOfContenteditable(contentEditableElement: HTMLElement) { + let range: Range | null; + let selection: Selection | null; + if (document.createRange) { + range = document.createRange(); //Create a range (a range is a like the selection but invisible) + range.selectNodeContents(contentEditableElement); //Select the entire contents of the element with the range + range.collapse(false); //collapse the range to the end point. false means collapse to end rather than the start + selection = window.getSelection(); //get the selection object (allows you to change selection) + selection?.removeAllRanges(); //remove any selections already made + selection?.addRange(range); //make the range you have just created the visible selection + } +} + +const MarkdownEditor = (props: MarkdownEditorProps) => { + const { t } = useTranslation(); + const [nsLoaded, setNsLoaded] = useState(false); + useEffect(() => { + i18next.loadNamespaces(["markdown_editor"]).then(() => { + setNsLoaded(true); + }); + }, []); + return ( + { + if ((e.ctrlKey || e.metaKey) && e.key === "s") { + e.preventDefault(); + props.onSaveShortcut?.(); + } + }} + > + {nsLoaded && ( + { + return t("markdown_editor:" + key, interpolations); + }} + readOnly={props.readOnly} + onChange={props.onChange} + plugins={[ + diffSourcePlugin({ + diffMarkdown: props.initialValue, + viewMode: "rich-text", + }), + ...(props.displayOnly + ? [] + : [ + toolbarPlugin({ + toolbarContents: () => ( + editor?.editorType === "codeblock", + contents: () => , + }, + { + when: (editor) => editor?.editorType === "sandpack", + contents: () => , + }, + { + fallback: () => ( + + + + + + + + + + + + , + }, + { fallback: () => }, + ]} + /> + + + + + + + + + + + + + + + !whenInAdmonition(editorInFocus), + contents: () => ( + <> + + + + ), + }, + ]} + /> + + + + + ), + }, + ]} + /> + ), + }), + ]), + listsPlugin(), + quotePlugin(), + headingsPlugin({ allowedHeadingLevels: [1, 2, 3] }), + linkPlugin(), + linkDialogPlugin(), + imagePlugin({ + imageUploadHandler: props.imageUploadHandler, + imagePreviewHandler: props.imagePreviewHandler, + imageAutocompleteSuggestions: props.imageAutocompleteSuggestions ?? undefined, + }), + tablePlugin(), + thematicBreakPlugin(), + frontmatterPlugin(), + codeBlockPlugin({ + defaultCodeBlockLanguage: "", + codeBlockEditorDescriptors: [{ priority: -10, match: (_) => true, Editor: CodeMirrorEditor }], + }), + codeMirrorPlugin({ + codeBlockLanguages: { + js: "JavaScript", + jsx: "JSX", + css: "CSS", + txt: "Plain Text", + tsx: "TSX", + ts: "TypeScript", + html: "HTML", + json: "JSON", + sh: "Shell", + bash: "Bash", + yaml: "YAML", + markdown: "Markdown", + dockerfile: "Dockerfile", + sql: "SQL", + python: "Python", + go: "Go", + java: "Java", + c: "C", + cpp: "C++", + php: "PHP", + ruby: "Ruby", + perl: "Perl", + swift: "Swift", + r: "R", + rust: "Rust", + kotlin: "Kotlin", + scala: "Scala", + "": "Unspecified", + }, + }), + directivesPlugin({ + directiveDescriptors: [AdmonitionDirectiveDescriptor], + }), + markdownShortcutPlugin(), + ]} + contentEditableClassName={props.darkMode ? "markdown-body-dark" : "markdown-body-light"} + markdown={props.value} + /> + )} + {!nsLoaded &&
    } + { + setEndOfContenteditable( + document.querySelector(props.darkMode ? ".markdown-body-dark" : ".markdown-body-light") as HTMLElement, + ); + }} + sx={{ + cursor: "text", + flexGrow: 1, + }} + /> + + ); +}; + +export default MarkdownEditor; diff --git a/src/component/Viewers/MarkdownEditor/MarkdownViewer.tsx b/src/component/Viewers/MarkdownEditor/MarkdownViewer.tsx new file mode 100755 index 0000000..e14b4fb --- /dev/null +++ b/src/component/Viewers/MarkdownEditor/MarkdownViewer.tsx @@ -0,0 +1,198 @@ +import { LoadingButton } from "@mui/lab"; +import { Box, Button, ButtonGroup, ListItemText, Menu, useTheme } from "@mui/material"; +import React, { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { closeMarkdownViewer } from "../../../redux/globalStateSlice.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import { getEntityContent } from "../../../redux/thunks/file.ts"; +import { + markdownImageAutocompleteSuggestions, + markdownImagePreviewHandler, + saveMarkdown, + uploadMarkdownImage, +} from "../../../redux/thunks/viewer.ts"; +import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx"; +import useActionDisplayOpt, { canUpdate } from "../../FileManager/ContextMenu/useActionDisplayOpt.ts"; +import CaretDown from "../../Icons/CaretDown.tsx"; +import ViewerDialog, { ViewerLoading } from "../ViewerDialog.tsx"; + +const MarkdownEditor = lazy(() => import("./Editor.tsx")); + +const MarkdownViewer = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const theme = useTheme(); + const viewerState = useAppSelector((state) => state.globalState.markdownViewer); + + const displayOpt = useActionDisplayOpt(viewerState?.file ? [viewerState?.file] : []); + const supportUpdate = canUpdate(displayOpt); + const [loading, setLoading] = useState(false); + const [value, setValue] = useState(""); + const [changedValue, setChangedValue] = useState(""); + const [loaded, setLoaded] = useState(false); + const [saved, setSaved] = useState(true); + const [anchorEl, setAnchorEl] = useState(null); + const [optionAnchorEl, setOptionAnchorEl] = useState(null); + const saveFunction = useRef(() => {}); + + const loadContent = useCallback(() => { + if (!viewerState || !viewerState.open) { + return; + } + + setLoaded(false); + setOptionAnchorEl(null); + dispatch(getEntityContent(viewerState.file, viewerState.version)) + .then((res) => { + const content = new TextDecoder().decode(res); + setValue(content); + setChangedValue(content); + setLoaded(true); + }) + .catch(() => { + onClose(); + }); + }, [viewerState]); + + useEffect(() => { + if (!viewerState || !viewerState.open) { + return; + } + + setSaved(true); + loadContent(); + }, [viewerState?.open]); + + const imageAutocompleteSuggestions = useMemo(() => { + if (!viewerState?.open) { + return null; + } + return dispatch(markdownImageAutocompleteSuggestions()); + }, [viewerState?.open]); + + const onClose = useCallback(() => { + dispatch(closeMarkdownViewer()); + }, [dispatch]); + + const openMore = useCallback( + (e: React.MouseEvent) => { + setAnchorEl(e.currentTarget); + }, + [dispatch], + ); + + const onSave = useCallback( + (saveAs?: boolean) => { + if (!viewerState?.file) { + return; + } + + setLoading(true); + dispatch(saveMarkdown(changedValue, viewerState.file, viewerState.version, saveAs)) + .then(() => { + setSaved(true); + }) + .finally(() => { + setLoading(false); + }); + }, + [changedValue, viewerState], + ); + + const onChange = useCallback((v: string) => { + setChangedValue(v); + setSaved(false); + }, []); + + const onSaveShortcut = useCallback(() => { + if (!saved && supportUpdate) { + onSave(false); + } + }, [saved, supportUpdate, onSave]); + + useEffect(() => { + saveFunction.current = () => { + if (!saved && supportUpdate) { + onSave(false); + } + }; + }, [saved, supportUpdate, onSave]); + + const imagePreviewHandler = useCallback( + (imageSource: string) => { + return dispatch(markdownImagePreviewHandler(imageSource, viewerState?.file?.path ?? "")); + }, + [dispatch, viewerState?.file?.path], + ); + + const onImageUpload = useCallback( + async (file: File): Promise => { + return dispatch(uploadMarkdownImage(file)); + }, + [dispatch], + ); + + return ( + + {supportUpdate && ( + + onSave(false)}> + {t("fileManager.save")} + + + + )} +
    + } + fullScreenToggle + dialogProps={{ + open: !!(viewerState && viewerState.open), + onClose: onClose, + fullWidth: true, + maxWidth: "lg", + }} + > + setAnchorEl(null)} + slotProps={{ + paper: { + sx: { + minWidth: 150, + }, + }, + }} + > + onSave(true)} dense> + {t("modals.saveAs")} + + + {!loaded && } + {loaded && ( + }> + onChange(v as string)} + onSaveShortcut={onSaveShortcut} + imagePreviewHandler={imagePreviewHandler} + imageAutocompleteSuggestions={imageAutocompleteSuggestions} + imageUploadHandler={onImageUpload} + /> + + )} + + ); +}; + +export default MarkdownViewer; diff --git a/src/component/Viewers/MarkdownEditor/editor.css b/src/component/Viewers/MarkdownEditor/editor.css new file mode 100755 index 0000000..66bc017 --- /dev/null +++ b/src/component/Viewers/MarkdownEditor/editor.css @@ -0,0 +1,2238 @@ +.mdxeditor { + width: 100%; + height: 100%; + overflow-y: auto; +} + +._toolbarRoot_yms4a_160 { + border-radius: 0; +} + +.mdxeditor-popup-container { + z-index: 9999; +} + +.dark-editor { + --basePageBg: #0d1117; +} + +.markdown-body-dark { + color-scheme: dark; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + margin: 0; + color: #e6edf3; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji"; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; +} + +.markdown-body-dark .octicon { + display: inline-block; + fill: currentColor; + vertical-align: text-bottom; +} + +.markdown-body-dark h1:hover .anchor .octicon-link:before, +.markdown-body-dark h2:hover .anchor .octicon-link:before, +.markdown-body-dark h3:hover .anchor .octicon-link:before, +.markdown-body-dark h4:hover .anchor .octicon-link:before, +.markdown-body-dark h5:hover .anchor .octicon-link:before, +.markdown-body-dark h6:hover .anchor .octicon-link:before { + width: 16px; + height: 16px; + content: " "; + display: inline-block; + background-color: currentColor; + -webkit-mask-image: url("data:image/svg+xml,"); + mask-image: url("data:image/svg+xml,"); +} + +.markdown-body-dark details, +.markdown-body-dark figcaption, +.markdown-body-dark figure { + display: block; +} + +.markdown-body-dark summary { + display: list-item; +} + +.markdown-body-dark [hidden] { + display: none !important; +} + +.markdown-body-dark a { + background-color: transparent; + color: #2f81f7; + text-decoration: none; +} + +.markdown-body-dark abbr[title] { + border-bottom: none; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +.markdown-body-dark b, +.markdown-body-dark strong { + font-weight: 600; +} + +.markdown-body-dark dfn { + font-style: italic; +} + +.markdown-body-dark h1 { + margin: 0.67em 0; + font-weight: 600; + padding-bottom: 0.3em; + font-size: 2em; + border-bottom: 1px solid #21262d; +} + +.markdown-body-dark mark { + background-color: rgba(187, 128, 9, 0.15); + color: #e6edf3; +} + +.markdown-body-dark small { + font-size: 90%; +} + +.markdown-body-dark sub, +.markdown-body-dark sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +.markdown-body-dark sub { + bottom: -0.25em; +} + +.markdown-body-dark sup { + top: -0.5em; +} + +.markdown-body-dark img { + border-style: none; + max-width: 100%; + box-sizing: content-box; + background-color: #0d1117; +} + +.markdown-body-dark code, +.markdown-body-dark kbd, +.markdown-body-dark pre, +.markdown-body-dark samp { + font-family: monospace; + font-size: 1em; +} + +.markdown-body-dark figure { + margin: 1em 40px; +} + +.markdown-body-dark hr { + box-sizing: content-box; + overflow: hidden; + background: transparent; + border-bottom: 1px solid #21262d; + height: 0.25em; + padding: 0; + margin: 24px 0; + background-color: #30363d; + border: 0; +} + +.markdown-body-dark input { + font: inherit; + margin: 0; + overflow: visible; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +.markdown-body-dark [type="button"], +.markdown-body-dark [type="reset"], +.markdown-body-dark [type="submit"] { + -webkit-appearance: button; + appearance: button; +} + +.markdown-body-dark [type="checkbox"], +.markdown-body-dark [type="radio"] { + box-sizing: border-box; + padding: 0; +} + +.markdown-body-dark [type="number"]::-webkit-inner-spin-button, +.markdown-body-dark [type="number"]::-webkit-outer-spin-button { + height: auto; +} + +.markdown-body-dark [type="search"]::-webkit-search-cancel-button, +.markdown-body-dark [type="search"]::-webkit-search-decoration { + -webkit-appearance: none; + appearance: none; +} + +.markdown-body-dark ::-webkit-input-placeholder { + color: inherit; + opacity: 0.54; +} + +.markdown-body-dark ::-webkit-file-upload-button { + -webkit-appearance: button; + appearance: button; + font: inherit; +} + +.markdown-body-dark a:hover { + text-decoration: underline; +} + +.markdown-body-dark ::placeholder { + color: #6e7681; + opacity: 1; +} + +.markdown-body-dark hr::before { + display: table; + content: ""; +} + +.markdown-body-dark hr::after { + display: table; + clear: both; + content: ""; +} + +.markdown-body-dark table { + border-spacing: 0; + border-collapse: collapse; + display: block; + width: max-content; + max-width: 100%; + overflow: auto; +} + +.markdown-body-dark td, +.markdown-body-dark th { + padding: 0; +} + +.markdown-body-dark details summary { + cursor: pointer; +} + +.markdown-body-dark details:not([open]) > *:not(summary) { + display: none !important; +} + +.markdown-body-dark a:focus, +.markdown-body-dark [role="button"]:focus, +.markdown-body-dark input[type="radio"]:focus, +.markdown-body-dark input[type="checkbox"]:focus { + outline: 2px solid #2f81f7; + outline-offset: -2px; + box-shadow: none; +} + +.markdown-body-dark a:focus:not(:focus-visible), +.markdown-body-dark [role="button"]:focus:not(:focus-visible), +.markdown-body-dark input[type="radio"]:focus:not(:focus-visible), +.markdown-body-dark input[type="checkbox"]:focus:not(:focus-visible) { + outline: solid 1px transparent; +} + +.markdown-body-dark a:focus-visible, +.markdown-body-dark [role="button"]:focus-visible, +.markdown-body-dark input[type="radio"]:focus-visible, +.markdown-body-dark input[type="checkbox"]:focus-visible { + outline: 2px solid #2f81f7; + outline-offset: -2px; + box-shadow: none; +} + +.markdown-body-dark a:not([class]):focus, +.markdown-body-dark a:not([class]):focus-visible, +.markdown-body-dark input[type="radio"]:focus, +.markdown-body-dark input[type="radio"]:focus-visible, +.markdown-body-dark input[type="checkbox"]:focus, +.markdown-body-dark input[type="checkbox"]:focus-visible { + outline-offset: 0; +} + +.markdown-body-dark kbd { + display: inline-block; + padding: 3px 5px; + font: + 11px ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; + line-height: 10px; + color: #e6edf3; + vertical-align: middle; + background-color: #161b22; + border: solid 1px rgba(110, 118, 129, 0.4); + border-bottom-color: rgba(110, 118, 129, 0.4); + border-radius: 6px; + box-shadow: inset 0 -1px 0 rgba(110, 118, 129, 0.4); +} + +.markdown-body-dark h1, +.markdown-body-dark h2, +.markdown-body-dark h3, +.markdown-body-dark h4, +.markdown-body-dark h5, +.markdown-body-dark h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; +} + +.markdown-body-dark h2 { + font-weight: 600; + padding-bottom: 0.3em; + font-size: 1.5em; + border-bottom: 1px solid #21262d; +} + +.markdown-body-dark h3 { + font-weight: 600; + font-size: 1.25em; +} + +.markdown-body-dark h4 { + font-weight: 600; + font-size: 1em; +} + +.markdown-body-dark h5 { + font-weight: 600; + font-size: 0.875em; +} + +.markdown-body-dark h6 { + font-weight: 600; + font-size: 0.85em; + color: #848d97; +} + +.markdown-body-dark p { + margin-top: 0; + margin-bottom: 10px; +} + +.markdown-body-dark blockquote { + margin: 0; + padding: 0 1em; + color: #848d97; + border-left: 0.25em solid #30363d; +} + +.markdown-body-dark ul, +.markdown-body-dark ol { + margin-top: 0; + margin-bottom: 0; + padding-left: 2em; +} + +.markdown-body-dark ol ol, +.markdown-body-dark ul ol { + list-style-type: lower-roman; +} + +.markdown-body-dark ul ul ol, +.markdown-body-dark ul ol ol, +.markdown-body-dark ol ul ol, +.markdown-body-dark ol ol ol { + list-style-type: lower-alpha; +} + +.markdown-body-dark dd { + margin-left: 0; +} + +.markdown-body-dark tt, +.markdown-body-dark code, +.markdown-body-dark samp { + font-family: + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; + font-size: 12px; +} + +.markdown-body-dark pre { + margin-top: 0; + margin-bottom: 0; + font-family: + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; + font-size: 12px; + word-wrap: normal; +} + +.markdown-body-dark .octicon { + display: inline-block; + overflow: visible !important; + vertical-align: text-bottom; + fill: currentColor; +} + +.markdown-body-dark input::-webkit-outer-spin-button, +.markdown-body-dark input::-webkit-inner-spin-button { + margin: 0; + -webkit-appearance: none; + appearance: none; +} + +.markdown-body-dark .mr-2 { + margin-right: 8px !important; +} + +.markdown-body::before { + display: table; + content: ""; +} + +.markdown-body::after { + display: table; + clear: both; + content: ""; +} + +.markdown-body > *:first-child { + margin-top: 0 !important; +} + +.markdown-body > *:last-child { + margin-bottom: 0 !important; +} + +.markdown-body-dark a:not([href]) { + color: inherit; + text-decoration: none; +} + +.markdown-body-dark .absent { + color: #f85149; +} + +.markdown-body-dark .anchor { + float: left; + padding-right: 4px; + margin-left: -20px; + line-height: 1; +} + +.markdown-body-dark .anchor:focus { + outline: none; +} + +.markdown-body-dark p, +.markdown-body-dark blockquote, +.markdown-body-dark ul, +.markdown-body-dark ol, +.markdown-body-dark dl, +.markdown-body-dark table, +.markdown-body-dark pre, +.markdown-body-dark details { + margin-top: 0; + margin-bottom: 16px; +} + +.markdown-body-dark blockquote > :first-child { + margin-top: 0; +} + +.markdown-body-dark blockquote > :last-child { + margin-bottom: 0; +} + +.markdown-body-dark h1 .octicon-link, +.markdown-body-dark h2 .octicon-link, +.markdown-body-dark h3 .octicon-link, +.markdown-body-dark h4 .octicon-link, +.markdown-body-dark h5 .octicon-link, +.markdown-body-dark h6 .octicon-link { + color: #e6edf3; + vertical-align: middle; + visibility: hidden; +} + +.markdown-body-dark h1:hover .anchor, +.markdown-body-dark h2:hover .anchor, +.markdown-body-dark h3:hover .anchor, +.markdown-body-dark h4:hover .anchor, +.markdown-body-dark h5:hover .anchor, +.markdown-body-dark h6:hover .anchor { + text-decoration: none; +} + +.markdown-body-dark h1:hover .anchor .octicon-link, +.markdown-body-dark h2:hover .anchor .octicon-link, +.markdown-body-dark h3:hover .anchor .octicon-link, +.markdown-body-dark h4:hover .anchor .octicon-link, +.markdown-body-dark h5:hover .anchor .octicon-link, +.markdown-body-dark h6:hover .anchor .octicon-link { + visibility: visible; +} + +.markdown-body-dark h1 tt, +.markdown-body-dark h1 code, +.markdown-body-dark h2 tt, +.markdown-body-dark h2 code, +.markdown-body-dark h3 tt, +.markdown-body-dark h3 code, +.markdown-body-dark h4 tt, +.markdown-body-dark h4 code, +.markdown-body-dark h5 tt, +.markdown-body-dark h5 code, +.markdown-body-dark h6 tt, +.markdown-body-dark h6 code { + padding: 0 0.2em; + font-size: inherit; +} + +.markdown-body-dark summary h1, +.markdown-body-dark summary h2, +.markdown-body-dark summary h3, +.markdown-body-dark summary h4, +.markdown-body-dark summary h5, +.markdown-body-dark summary h6 { + display: inline-block; +} + +.markdown-body-dark summary h1 .anchor, +.markdown-body-dark summary h2 .anchor, +.markdown-body-dark summary h3 .anchor, +.markdown-body-dark summary h4 .anchor, +.markdown-body-dark summary h5 .anchor, +.markdown-body-dark summary h6 .anchor { + margin-left: -40px; +} + +.markdown-body-dark summary h1, +.markdown-body-dark summary h2 { + padding-bottom: 0; + border-bottom: 0; +} + +.markdown-body-dark ul.no-list, +.markdown-body-dark ol.no-list { + padding: 0; + list-style-type: none; +} + +.markdown-body-dark ol[type="a s"] { + list-style-type: lower-alpha; +} + +.markdown-body-dark ol[type="A s"] { + list-style-type: upper-alpha; +} + +.markdown-body-dark ol[type="i s"] { + list-style-type: lower-roman; +} + +.markdown-body-dark ol[type="I s"] { + list-style-type: upper-roman; +} + +.markdown-body-dark ol[type="1"] { + list-style-type: decimal; +} + +.markdown-body-dark div > ol:not([type]) { + list-style-type: decimal; +} + +.markdown-body-dark ul ul, +.markdown-body-dark ul ol, +.markdown-body-dark ol ol, +.markdown-body-dark ol ul { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body-dark li > p { + margin-top: 16px; +} + +.markdown-body-dark li + li { + margin-top: 0.25em; +} + +.markdown-body-dark dl { + padding: 0; +} + +.markdown-body-dark dl dt { + padding: 0; + margin-top: 16px; + font-size: 1em; + font-style: italic; + font-weight: 600; +} + +.markdown-body-dark dl dd { + padding: 0 16px; + margin-bottom: 16px; +} + +.markdown-body-dark table th { + font-weight: 600; +} + +.markdown-body-dark table th, +.markdown-body-dark table td { + padding: 6px 13px; + border: 1px solid #30363d; +} + +.markdown-body-dark table td > :last-child { + margin-bottom: 0; +} + +.markdown-body-dark table tr { + background-color: #0d1117; + border-top: 1px solid #21262d; +} + +.markdown-body-dark table tr:nth-child(2n) { + background-color: #161b22; +} + +.markdown-body-dark table img { + background-color: transparent; +} + +.markdown-body-dark img[align="right"] { + padding-left: 20px; +} + +.markdown-body-dark img[align="left"] { + padding-right: 20px; +} + +.markdown-body-dark .emoji { + max-width: none; + vertical-align: text-top; + background-color: transparent; +} + +.markdown-body-dark span.frame { + display: block; + overflow: hidden; +} + +.markdown-body-dark span.frame > span { + display: block; + float: left; + width: auto; + padding: 7px; + margin: 13px 0 0; + overflow: hidden; + border: 1px solid #30363d; +} + +.markdown-body-dark span.frame span img { + display: block; + float: left; +} + +.markdown-body-dark span.frame span span { + display: block; + padding: 5px 0 0; + clear: both; + color: #e6edf3; +} + +.markdown-body-dark span.align-center { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-body-dark span.align-center > span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: center; +} + +.markdown-body-dark span.align-center span img { + margin: 0 auto; + text-align: center; +} + +.markdown-body-dark span.align-right { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-body-dark span.align-right > span { + display: block; + margin: 13px 0 0; + overflow: hidden; + text-align: right; +} + +.markdown-body-dark span.align-right span img { + margin: 0; + text-align: right; +} + +.markdown-body-dark span.float-left { + display: block; + float: left; + margin-right: 13px; + overflow: hidden; +} + +.markdown-body-dark span.float-left span { + margin: 13px 0 0; +} + +.markdown-body-dark span.float-right { + display: block; + float: right; + margin-left: 13px; + overflow: hidden; +} + +.markdown-body-dark span.float-right > span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: right; +} + +.markdown-body-dark code, +.markdown-body-dark tt { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + white-space: break-spaces; + background-color: rgba(110, 118, 129, 0.4); + border-radius: 6px; +} + +.markdown-body-dark code br, +.markdown-body-dark tt br { + display: none; +} + +.markdown-body-dark del code { + text-decoration: inherit; +} + +.markdown-body-dark samp { + font-size: 85%; +} + +.markdown-body-dark pre code { + font-size: 100%; +} + +.markdown-body-dark pre > code { + padding: 0; + margin: 0; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; +} + +.markdown-body-dark .highlight { + margin-bottom: 16px; +} + +.markdown-body-dark .highlight pre { + margin-bottom: 0; + word-break: normal; +} + +.markdown-body-dark .highlight pre, +.markdown-body-dark pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + color: #e6edf3; + background-color: #161b22; + border-radius: 6px; +} + +.markdown-body-dark pre code, +.markdown-body-dark pre tt { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; +} + +.markdown-body-dark .csv-data td, +.markdown-body-dark .csv-data th { + padding: 5px; + overflow: hidden; + font-size: 12px; + line-height: 1; + text-align: left; + white-space: nowrap; +} + +.markdown-body-dark .csv-data .blob-num { + padding: 10px 8px 9px; + text-align: right; + background: #0d1117; + border: 0; +} + +.markdown-body-dark .csv-data tr { + border-top: 0; +} + +.markdown-body-dark .csv-data th { + font-weight: 600; + background: #161b22; + border-top: 0; +} + +.markdown-body-dark [data-footnote-ref]::before { + content: "["; +} + +.markdown-body-dark [data-footnote-ref]::after { + content: "]"; +} + +.markdown-body-dark .footnotes { + font-size: 12px; + color: #848d97; + border-top: 1px solid #30363d; +} + +.markdown-body-dark .footnotes ol { + padding-left: 16px; +} + +.markdown-body-dark .footnotes ol ul { + display: inline-block; + padding-left: 16px; + margin-top: 16px; +} + +.markdown-body-dark .footnotes li { + position: relative; +} + +.markdown-body-dark .footnotes li:target::before { + position: absolute; + top: -8px; + right: -8px; + bottom: -8px; + left: -24px; + pointer-events: none; + content: ""; + border: 2px solid #1f6feb; + border-radius: 6px; +} + +.markdown-body-dark .footnotes li:target { + color: #e6edf3; +} + +.markdown-body-dark .footnotes .data-footnote-backref g-emoji { + font-family: monospace; +} + +.markdown-body-dark .pl-c { + color: #8b949e; +} + +.markdown-body-dark .pl-c1, +.markdown-body-dark .pl-s .pl-v { + color: #79c0ff; +} + +.markdown-body-dark .pl-e, +.markdown-body-dark .pl-en { + color: #d2a8ff; +} + +.markdown-body-dark .pl-smi, +.markdown-body-dark .pl-s .pl-s1 { + color: #c9d1d9; +} + +.markdown-body-dark .pl-ent { + color: #7ee787; +} + +.markdown-body-dark .pl-k { + color: #ff7b72; +} + +.markdown-body-dark .pl-s, +.markdown-body-dark .pl-pds, +.markdown-body-dark .pl-s .pl-pse .pl-s1, +.markdown-body-dark .pl-sr, +.markdown-body-dark .pl-sr .pl-cce, +.markdown-body-dark .pl-sr .pl-sre, +.markdown-body-dark .pl-sr .pl-sra { + color: #a5d6ff; +} + +.markdown-body-dark .pl-v, +.markdown-body-dark .pl-smw { + color: #ffa657; +} + +.markdown-body-dark .pl-bu { + color: #f85149; +} + +.markdown-body-dark .pl-ii { + color: #f0f6fc; + background-color: #8e1519; +} + +.markdown-body-dark .pl-c2 { + color: #f0f6fc; + background-color: #b62324; +} + +.markdown-body-dark .pl-sr .pl-cce { + font-weight: bold; + color: #7ee787; +} + +.markdown-body-dark .pl-ml { + color: #f2cc60; +} + +.markdown-body-dark .pl-mh, +.markdown-body-dark .pl-mh .pl-en, +.markdown-body-dark .pl-ms { + font-weight: bold; + color: #1f6feb; +} + +.markdown-body-dark .pl-mi { + font-style: italic; + color: #c9d1d9; +} + +.markdown-body-dark .pl-mb { + font-weight: bold; + color: #c9d1d9; +} + +.markdown-body-dark .pl-md { + color: #ffdcd7; + background-color: #67060c; +} + +.markdown-body-dark .pl-mi1 { + color: #aff5b4; + background-color: #033a16; +} + +.markdown-body-dark .pl-mc { + color: #ffdfb6; + background-color: #5a1e02; +} + +.markdown-body-dark .pl-mi2 { + color: #c9d1d9; + background-color: #1158c7; +} + +.markdown-body-dark .pl-mdr { + font-weight: bold; + color: #d2a8ff; +} + +.markdown-body-dark .pl-ba { + color: #8b949e; +} + +.markdown-body-dark .pl-sg { + color: #484f58; +} + +.markdown-body-dark .pl-corl { + text-decoration: underline; + color: #a5d6ff; +} + +.markdown-body-dark g-emoji { + display: inline-block; + min-width: 1ch; + font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 1em; + font-style: normal !important; + font-weight: 400; + line-height: 1; + vertical-align: -0.075em; +} + +.markdown-body-dark g-emoji img { + width: 1em; + height: 1em; +} + +.markdown-body-dark .task-list-item { + list-style-type: none; +} + +.markdown-body-dark .task-list-item label { + font-weight: 400; +} + +.markdown-body-dark .task-list-item.enabled label { + cursor: pointer; +} + +.markdown-body-dark .task-list-item + .task-list-item { + margin-top: 4px; +} + +.markdown-body-dark .task-list-item .handle { + display: none; +} + +.markdown-body-dark .task-list-item-checkbox { + margin: 0 0.2em 0.25em -1.4em; + vertical-align: middle; +} + +.markdown-body-dark .contains-task-list:dir(rtl) .task-list-item-checkbox { + margin: 0 -1.6em 0.25em 0.2em; +} + +.markdown-body-dark .contains-task-list { + position: relative; +} + +.markdown-body-dark .contains-task-list:hover .task-list-item-convert-container, +.markdown-body-dark .contains-task-list:focus-within .task-list-item-convert-container { + display: block; + width: auto; + height: 24px; + overflow: visible; + clip: auto; +} + +.markdown-body-dark ::-webkit-calendar-picker-indicator { + filter: invert(50%); +} + +.markdown-body-dark .markdown-alert { + padding: 8px 16px; + margin-bottom: 16px; + color: inherit; + border-left: 0.25em solid #30363d; +} + +.markdown-body-dark .markdown-alert > :first-child { + margin-top: 0; +} + +.markdown-body-dark .markdown-alert > :last-child { + margin-bottom: 0; +} + +.markdown-body-dark .markdown-alert .markdown-alert-title { + display: flex; + font-weight: 500; + align-items: center; + line-height: 1; +} + +.markdown-body-dark .markdown-alert.markdown-alert-note { + border-left-color: #1f6feb; +} + +.markdown-body-dark .markdown-alert.markdown-alert-note .markdown-alert-title { + color: #2f81f7; +} + +.markdown-body-dark .markdown-alert.markdown-alert-important { + border-left-color: #8957e5; +} + +.markdown-body-dark .markdown-alert.markdown-alert-important .markdown-alert-title { + color: #a371f7; +} + +.markdown-body-dark .markdown-alert.markdown-alert-warning { + border-left-color: #9e6a03; +} + +.markdown-body-dark .markdown-alert.markdown-alert-warning .markdown-alert-title { + color: #d29922; +} + +.markdown-body-dark .markdown-alert.markdown-alert-tip { + border-left-color: #238636; +} + +.markdown-body-dark .markdown-alert.markdown-alert-tip .markdown-alert-title { + color: #3fb950; +} + +.markdown-body-dark .markdown-alert.markdown-alert-caution { + border-left-color: #da3633; +} + +.markdown-body-dark .markdown-alert.markdown-alert-caution .markdown-alert-title { + color: #f85149; +} + +/*light*/ + +.markdown-body-light { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + margin: 0; + color: #1f2328; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji"; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; +} + +.markdown-body-light .octicon { + display: inline-block; + fill: currentColor; + vertical-align: text-bottom; +} + +.markdown-body-light h1:hover .anchor .octicon-link:before, +.markdown-body-light h2:hover .anchor .octicon-link:before, +.markdown-body-light h3:hover .anchor .octicon-link:before, +.markdown-body-light h4:hover .anchor .octicon-link:before, +.markdown-body-light h5:hover .anchor .octicon-link:before, +.markdown-body-light h6:hover .anchor .octicon-link:before { + width: 16px; + height: 16px; + content: " "; + display: inline-block; + background-color: currentColor; + -webkit-mask-image: url("data:image/svg+xml,"); + mask-image: url("data:image/svg+xml,"); +} + +.markdown-body-light details, +.markdown-body-light figcaption, +.markdown-body-light figure { + display: block; +} + +.markdown-body-light summary { + display: list-item; +} + +.markdown-body-light [hidden] { + display: none !important; +} + +.markdown-body-light a { + background-color: transparent; + color: #0969da; + text-decoration: none; +} + +.markdown-body-light abbr[title] { + border-bottom: none; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +.markdown-body-light b, +.markdown-body-light strong { + font-weight: 600; +} + +.markdown-body-light dfn { + font-style: italic; +} + +.markdown-body-light h1 { + margin: 0.67em 0; + font-weight: 600; + padding-bottom: 0.3em; + font-size: 2em; + border-bottom: 1px solid hsla(210, 18%, 87%, 1); +} + +.markdown-body-light mark { + background-color: #fff8c5; + color: #1f2328; +} + +.markdown-body-light small { + font-size: 90%; +} + +.markdown-body-light sub, +.markdown-body-light sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +.markdown-body-light sub { + bottom: -0.25em; +} + +.markdown-body-light sup { + top: -0.5em; +} + +.markdown-body-light img { + border-style: none; + max-width: 100%; + box-sizing: content-box; + background-color: #ffffff; +} + +.markdown-body-light code, +.markdown-body-light kbd, +.markdown-body-light pre, +.markdown-body-light samp { + font-family: monospace; + font-size: 1em; +} + +.markdown-body-light figure { + margin: 1em 40px; +} + +.markdown-body-light hr { + box-sizing: content-box; + overflow: hidden; + background: transparent; + border-bottom: 1px solid hsla(210, 18%, 87%, 1); + height: 0.25em; + padding: 0; + margin: 24px 0; + background-color: #d0d7de; + border: 0; +} + +.markdown-body-light input { + font: inherit; + margin: 0; + overflow: visible; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +.markdown-body-light [type="button"], +.markdown-body-light [type="reset"], +.markdown-body-light [type="submit"] { + -webkit-appearance: button; + appearance: button; +} + +.markdown-body-light [type="checkbox"], +.markdown-body-light [type="radio"] { + box-sizing: border-box; + padding: 0; +} + +.markdown-body-light [type="number"]::-webkit-inner-spin-button, +.markdown-body-light [type="number"]::-webkit-outer-spin-button { + height: auto; +} + +.markdown-body-light [type="search"]::-webkit-search-cancel-button, +.markdown-body-light [type="search"]::-webkit-search-decoration { + -webkit-appearance: none; + appearance: none; +} + +.markdown-body-light ::-webkit-input-placeholder { + color: inherit; + opacity: 0.54; +} + +.markdown-body-light ::-webkit-file-upload-button { + -webkit-appearance: button; + appearance: button; + font: inherit; +} + +.markdown-body-light a:hover { + text-decoration: underline; +} + +.markdown-body-light ::placeholder { + color: #6e7781; + opacity: 1; +} + +.markdown-body-light hr::before { + display: table; + content: ""; +} + +.markdown-body-light hr::after { + display: table; + clear: both; + content: ""; +} + +.markdown-body-light table { + border-spacing: 0; + border-collapse: collapse; + display: block; + width: max-content; + max-width: 100%; + overflow: auto; +} + +.markdown-body-light td, +.markdown-body-light th { + padding: 0; +} + +.markdown-body-light details summary { + cursor: pointer; +} + +.markdown-body-light details:not([open]) > *:not(summary) { + display: none !important; +} + +.markdown-body-light a:focus, +.markdown-body-light [role="button"]:focus, +.markdown-body-light input[type="radio"]:focus, +.markdown-body-light input[type="checkbox"]:focus { + outline: 2px solid #0969da; + outline-offset: -2px; + box-shadow: none; +} + +.markdown-body-light a:focus:not(:focus-visible), +.markdown-body-light [role="button"]:focus:not(:focus-visible), +.markdown-body-light input[type="radio"]:focus:not(:focus-visible), +.markdown-body-light input[type="checkbox"]:focus:not(:focus-visible) { + outline: solid 1px transparent; +} + +.markdown-body-light a:focus-visible, +.markdown-body-light [role="button"]:focus-visible, +.markdown-body-light input[type="radio"]:focus-visible, +.markdown-body-light input[type="checkbox"]:focus-visible { + outline: 2px solid #0969da; + outline-offset: -2px; + box-shadow: none; +} + +.markdown-body-light a:not([class]):focus, +.markdown-body-light a:not([class]):focus-visible, +.markdown-body-light input[type="radio"]:focus, +.markdown-body-light input[type="radio"]:focus-visible, +.markdown-body-light input[type="checkbox"]:focus, +.markdown-body-light input[type="checkbox"]:focus-visible { + outline-offset: 0; +} + +.markdown-body-light kbd { + display: inline-block; + padding: 3px 5px; + font: + 11px ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; + line-height: 10px; + color: #1f2328; + vertical-align: middle; + background-color: #f6f8fa; + border: solid 1px rgba(175, 184, 193, 0.2); + border-bottom-color: rgba(175, 184, 193, 0.2); + border-radius: 6px; + box-shadow: inset 0 -1px 0 rgba(175, 184, 193, 0.2); +} + +.markdown-body-light h1, +.markdown-body-light h2, +.markdown-body-light h3, +.markdown-body-light h4, +.markdown-body-light h5, +.markdown-body-light h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; +} + +.markdown-body-light h2 { + font-weight: 600; + padding-bottom: 0.3em; + font-size: 1.5em; + border-bottom: 1px solid hsla(210, 18%, 87%, 1); +} + +.markdown-body-light h3 { + font-weight: 600; + font-size: 1.25em; +} + +.markdown-body-light h4 { + font-weight: 600; + font-size: 1em; +} + +.markdown-body-light h5 { + font-weight: 600; + font-size: 0.875em; +} + +.markdown-body-light h6 { + font-weight: 600; + font-size: 0.85em; + color: #656d76; +} + +.markdown-body-light p { + margin-top: 0; + margin-bottom: 10px; +} + +.markdown-body-light blockquote { + margin: 0; + padding: 0 1em; + color: #656d76; + border-left: 0.25em solid #d0d7de; +} + +.markdown-body-light ul, +.markdown-body-light ol { + margin-top: 0; + margin-bottom: 0; + padding-left: 2em; +} + +.markdown-body-light ol ol, +.markdown-body-light ul ol { + list-style-type: lower-roman; +} + +.markdown-body-light ul ul ol, +.markdown-body-light ul ol ol, +.markdown-body-light ol ul ol, +.markdown-body-light ol ol ol { + list-style-type: lower-alpha; +} + +.markdown-body-light dd { + margin-left: 0; +} + +.markdown-body-light tt, +.markdown-body-light code, +.markdown-body-light samp { + font-family: + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; + font-size: 12px; +} + +.markdown-body-light pre { + margin-top: 0; + margin-bottom: 0; + font-family: + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; + font-size: 12px; + word-wrap: normal; +} + +.markdown-body-light .octicon { + display: inline-block; + overflow: visible !important; + vertical-align: text-bottom; + fill: currentColor; +} + +.markdown-body-light input::-webkit-outer-spin-button, +.markdown-body-light input::-webkit-inner-spin-button { + margin: 0; + -webkit-appearance: none; + appearance: none; +} + +.markdown-body-light .mr-2 { + margin-right: 8px !important; +} + +.markdown-body::before { + display: table; + content: ""; +} + +.markdown-body::after { + display: table; + clear: both; + content: ""; +} + +.markdown-body > *:first-child { + margin-top: 0 !important; +} + +.markdown-body > *:last-child { + margin-bottom: 0 !important; +} + +.markdown-body-light a:not([href]) { + color: inherit; + text-decoration: none; +} + +.markdown-body-light .absent { + color: #d1242f; +} + +.markdown-body-light .anchor { + float: left; + padding-right: 4px; + margin-left: -20px; + line-height: 1; +} + +.markdown-body-light .anchor:focus { + outline: none; +} + +.markdown-body-light p, +.markdown-body-light blockquote, +.markdown-body-light ul, +.markdown-body-light ol, +.markdown-body-light dl, +.markdown-body-light table, +.markdown-body-light pre, +.markdown-body-light details { + margin-top: 0; + margin-bottom: 16px; +} + +.markdown-body-light blockquote > :first-child { + margin-top: 0; +} + +.markdown-body-light blockquote > :last-child { + margin-bottom: 0; +} + +.markdown-body-light h1 .octicon-link, +.markdown-body-light h2 .octicon-link, +.markdown-body-light h3 .octicon-link, +.markdown-body-light h4 .octicon-link, +.markdown-body-light h5 .octicon-link, +.markdown-body-light h6 .octicon-link { + color: #1f2328; + vertical-align: middle; + visibility: hidden; +} + +.markdown-body-light h1:hover .anchor, +.markdown-body-light h2:hover .anchor, +.markdown-body-light h3:hover .anchor, +.markdown-body-light h4:hover .anchor, +.markdown-body-light h5:hover .anchor, +.markdown-body-light h6:hover .anchor { + text-decoration: none; +} + +.markdown-body-light h1:hover .anchor .octicon-link, +.markdown-body-light h2:hover .anchor .octicon-link, +.markdown-body-light h3:hover .anchor .octicon-link, +.markdown-body-light h4:hover .anchor .octicon-link, +.markdown-body-light h5:hover .anchor .octicon-link, +.markdown-body-light h6:hover .anchor .octicon-link { + visibility: visible; +} + +.markdown-body-light h1 tt, +.markdown-body-light h1 code, +.markdown-body-light h2 tt, +.markdown-body-light h2 code, +.markdown-body-light h3 tt, +.markdown-body-light h3 code, +.markdown-body-light h4 tt, +.markdown-body-light h4 code, +.markdown-body-light h5 tt, +.markdown-body-light h5 code, +.markdown-body-light h6 tt, +.markdown-body-light h6 code { + padding: 0 0.2em; + font-size: inherit; +} + +.markdown-body-light summary h1, +.markdown-body-light summary h2, +.markdown-body-light summary h3, +.markdown-body-light summary h4, +.markdown-body-light summary h5, +.markdown-body-light summary h6 { + display: inline-block; +} + +.markdown-body-light summary h1 .anchor, +.markdown-body-light summary h2 .anchor, +.markdown-body-light summary h3 .anchor, +.markdown-body-light summary h4 .anchor, +.markdown-body-light summary h5 .anchor, +.markdown-body-light summary h6 .anchor { + margin-left: -40px; +} + +.markdown-body-light summary h1, +.markdown-body-light summary h2 { + padding-bottom: 0; + border-bottom: 0; +} + +.markdown-body-light ul.no-list, +.markdown-body-light ol.no-list { + padding: 0; + list-style-type: none; +} + +.markdown-body-light ol[type="a s"] { + list-style-type: lower-alpha; +} + +.markdown-body-light ol[type="A s"] { + list-style-type: upper-alpha; +} + +.markdown-body-light ol[type="i s"] { + list-style-type: lower-roman; +} + +.markdown-body-light ol[type="I s"] { + list-style-type: upper-roman; +} + +.markdown-body-light ol[type="1"] { + list-style-type: decimal; +} + +.markdown-body-light div > ol:not([type]) { + list-style-type: decimal; +} + +.markdown-body-light ul ul, +.markdown-body-light ul ol, +.markdown-body-light ol ol, +.markdown-body-light ol ul { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body-light li > p { + margin-top: 16px; +} + +.markdown-body-light li + li { + margin-top: 0.25em; +} + +.markdown-body-light dl { + padding: 0; +} + +.markdown-body-light dl dt { + padding: 0; + margin-top: 16px; + font-size: 1em; + font-style: italic; + font-weight: 600; +} + +.markdown-body-light dl dd { + padding: 0 16px; + margin-bottom: 16px; +} + +.markdown-body-light table th { + font-weight: 600; +} + +.markdown-body-light table th, +.markdown-body-light table td { + padding: 6px 13px; + border: 1px solid #d0d7de; +} + +.markdown-body-light table td > :last-child { + margin-bottom: 0; +} + +.markdown-body-light table tr { + background-color: #ffffff; + border-top: 1px solid hsla(210, 18%, 87%, 1); +} + +.markdown-body-light table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.markdown-body-light table img { + background-color: transparent; +} + +.markdown-body-light img[align="right"] { + padding-left: 20px; +} + +.markdown-body-light img[align="left"] { + padding-right: 20px; +} + +.markdown-body-light .emoji { + max-width: none; + vertical-align: text-top; + background-color: transparent; +} + +.markdown-body-light span.frame { + display: block; + overflow: hidden; +} + +.markdown-body-light span.frame > span { + display: block; + float: left; + width: auto; + padding: 7px; + margin: 13px 0 0; + overflow: hidden; + border: 1px solid #d0d7de; +} + +.markdown-body-light span.frame span img { + display: block; + float: left; +} + +.markdown-body-light span.frame span span { + display: block; + padding: 5px 0 0; + clear: both; + color: #1f2328; +} + +.markdown-body-light span.align-center { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-body-light span.align-center > span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: center; +} + +.markdown-body-light span.align-center span img { + margin: 0 auto; + text-align: center; +} + +.markdown-body-light span.align-right { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-body-light span.align-right > span { + display: block; + margin: 13px 0 0; + overflow: hidden; + text-align: right; +} + +.markdown-body-light span.align-right span img { + margin: 0; + text-align: right; +} + +.markdown-body-light span.float-left { + display: block; + float: left; + margin-right: 13px; + overflow: hidden; +} + +.markdown-body-light span.float-left span { + margin: 13px 0 0; +} + +.markdown-body-light span.float-right { + display: block; + float: right; + margin-left: 13px; + overflow: hidden; +} + +.markdown-body-light span.float-right > span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: right; +} + +.markdown-body-light code, +.markdown-body-light tt { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + white-space: break-spaces; + background-color: rgba(175, 184, 193, 0.2); + border-radius: 6px; +} + +.markdown-body-light code br, +.markdown-body-light tt br { + display: none; +} + +.markdown-body-light del code { + text-decoration: inherit; +} + +.markdown-body-light samp { + font-size: 85%; +} + +.markdown-body-light pre code { + font-size: 100%; +} + +.markdown-body-light pre > code { + padding: 0; + margin: 0; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; +} + +.markdown-body-light .highlight { + margin-bottom: 16px; +} + +.markdown-body-light .highlight pre { + margin-bottom: 0; + word-break: normal; +} + +.markdown-body-light .highlight pre, +.markdown-body-light pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + color: #1f2328; + background-color: #f6f8fa; + border-radius: 6px; +} + +.markdown-body-light pre code, +.markdown-body-light pre tt { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; +} + +.markdown-body-light .csv-data td, +.markdown-body-light .csv-data th { + padding: 5px; + overflow: hidden; + font-size: 12px; + line-height: 1; + text-align: left; + white-space: nowrap; +} + +.markdown-body-light .csv-data .blob-num { + padding: 10px 8px 9px; + text-align: right; + background: #ffffff; + border: 0; +} + +.markdown-body-light .csv-data tr { + border-top: 0; +} + +.markdown-body-light .csv-data th { + font-weight: 600; + background: #f6f8fa; + border-top: 0; +} + +.markdown-body-light [data-footnote-ref]::before { + content: "["; +} + +.markdown-body-light [data-footnote-ref]::after { + content: "]"; +} + +.markdown-body-light .footnotes { + font-size: 12px; + color: #656d76; + border-top: 1px solid #d0d7de; +} + +.markdown-body-light .footnotes ol { + padding-left: 16px; +} + +.markdown-body-light .footnotes ol ul { + display: inline-block; + padding-left: 16px; + margin-top: 16px; +} + +.markdown-body-light .footnotes li { + position: relative; +} + +.markdown-body-light .footnotes li:target::before { + position: absolute; + top: -8px; + right: -8px; + bottom: -8px; + left: -24px; + pointer-events: none; + content: ""; + border: 2px solid #0969da; + border-radius: 6px; +} + +.markdown-body-light .footnotes li:target { + color: #1f2328; +} + +.markdown-body-light .footnotes .data-footnote-backref g-emoji { + font-family: monospace; +} + +.markdown-body-light .pl-c { + color: #57606a; +} + +.markdown-body-light .pl-c1, +.markdown-body-light .pl-s .pl-v { + color: #0550ae; +} + +.markdown-body-light .pl-e, +.markdown-body-light .pl-en { + color: #6639ba; +} + +.markdown-body-light .pl-smi, +.markdown-body-light .pl-s .pl-s1 { + color: #24292f; +} + +.markdown-body-light .pl-ent { + color: #116329; +} + +.markdown-body-light .pl-k { + color: #cf222e; +} + +.markdown-body-light .pl-s, +.markdown-body-light .pl-pds, +.markdown-body-light .pl-s .pl-pse .pl-s1, +.markdown-body-light .pl-sr, +.markdown-body-light .pl-sr .pl-cce, +.markdown-body-light .pl-sr .pl-sre, +.markdown-body-light .pl-sr .pl-sra { + color: #0a3069; +} + +.markdown-body-light .pl-v, +.markdown-body-light .pl-smw { + color: #953800; +} + +.markdown-body-light .pl-bu { + color: #82071e; +} + +.markdown-body-light .pl-ii { + color: #f6f8fa; + background-color: #82071e; +} + +.markdown-body-light .pl-c2 { + color: #f6f8fa; + background-color: #cf222e; +} + +.markdown-body-light .pl-sr .pl-cce { + font-weight: bold; + color: #116329; +} + +.markdown-body-light .pl-ml { + color: #3b2300; +} + +.markdown-body-light .pl-mh, +.markdown-body-light .pl-mh .pl-en, +.markdown-body-light .pl-ms { + font-weight: bold; + color: #0550ae; +} + +.markdown-body-light .pl-mi { + font-style: italic; + color: #24292f; +} + +.markdown-body-light .pl-mb { + font-weight: bold; + color: #24292f; +} + +.markdown-body-light .pl-md { + color: #82071e; + background-color: #ffebe9; +} + +.markdown-body-light .pl-mi1 { + color: #116329; + background-color: #dafbe1; +} + +.markdown-body-light .pl-mc { + color: #953800; + background-color: #ffd8b5; +} + +.markdown-body-light .pl-mi2 { + color: #eaeef2; + background-color: #0550ae; +} + +.markdown-body-light .pl-mdr { + font-weight: bold; + color: #8250df; +} + +.markdown-body-light .pl-ba { + color: #57606a; +} + +.markdown-body-light .pl-sg { + color: #8c959f; +} + +.markdown-body-light .pl-corl { + text-decoration: underline; + color: #0a3069; +} + +.markdown-body-light g-emoji { + display: inline-block; + min-width: 1ch; + font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 1em; + font-style: normal !important; + font-weight: 400; + line-height: 1; + vertical-align: -0.075em; +} + +.markdown-body-light g-emoji img { + width: 1em; + height: 1em; +} + +.markdown-body-light .task-list-item { + list-style-type: none; +} + +.markdown-body-light .task-list-item label { + font-weight: 400; +} + +.markdown-body-light .task-list-item.enabled label { + cursor: pointer; +} + +.markdown-body-light .task-list-item + .task-list-item { + margin-top: 4px; +} + +.markdown-body-light .task-list-item .handle { + display: none; +} + +.markdown-body-light .task-list-item-checkbox { + margin: 0 0.2em 0.25em -1.4em; + vertical-align: middle; +} + +.markdown-body-light .contains-task-list:dir(rtl) .task-list-item-checkbox { + margin: 0 -1.6em 0.25em 0.2em; +} + +.markdown-body-light .contains-task-list { + position: relative; +} + +.markdown-body-light .contains-task-list:hover .task-list-item-convert-container, +.markdown-body-light .contains-task-list:focus-within .task-list-item-convert-container { + display: block; + width: auto; + height: 24px; + overflow: visible; + clip: auto; +} + +.markdown-body-light ::-webkit-calendar-picker-indicator { + filter: invert(50%); +} + +.markdown-body-light .markdown-alert { + padding: 8px 16px; + margin-bottom: 16px; + color: inherit; + border-left: 0.25em solid #d0d7de; +} + +.markdown-body-light .markdown-alert > :first-child { + margin-top: 0; +} + +.markdown-body-light .markdown-alert > :last-child { + margin-bottom: 0; +} + +.markdown-body-light .markdown-alert .markdown-alert-title { + display: flex; + font-weight: 500; + align-items: center; + line-height: 1; +} + +.markdown-body-light .markdown-alert.markdown-alert-note { + border-left-color: #0969da; +} + +.markdown-body-light .markdown-alert.markdown-alert-note .markdown-alert-title { + color: #0969da; +} + +.markdown-body-light .markdown-alert.markdown-alert-important { + border-left-color: #8250df; +} + +.markdown-body-light .markdown-alert.markdown-alert-important .markdown-alert-title { + color: #8250df; +} + +.markdown-body-light .markdown-alert.markdown-alert-warning { + border-left-color: #9a6700; +} + +.markdown-body-light .markdown-alert.markdown-alert-warning .markdown-alert-title { + color: #9a6700; +} + +.markdown-body-light .markdown-alert.markdown-alert-tip { + border-left-color: #1f883d; +} + +.markdown-body-light .markdown-alert.markdown-alert-tip .markdown-alert-title { + color: #1a7f37; +} + +.markdown-body-light .markdown-alert.markdown-alert-caution { + border-left-color: #cf222e; +} + +.markdown-body-light .markdown-alert.markdown-alert-caution .markdown-alert-title { + color: #d1242f; +} + +._listItemUnchecked_1tncs_74:before, +._listItemChecked_1tncs_73:before { + top: 5px; +} +._listItemUnchecked_1tncs_74:focus:before, +._listItemChecked_1tncs_73:focus:before { + top: 5px; +} +._listItemChecked_1tncs_73:after { + top: 7px; +} diff --git a/src/component/Viewers/MusicPlayer/MusicPlayer.tsx b/src/component/Viewers/MusicPlayer/MusicPlayer.tsx new file mode 100755 index 0000000..ecd2582 --- /dev/null +++ b/src/component/Viewers/MusicPlayer/MusicPlayer.tsx @@ -0,0 +1,209 @@ +import { IconButton, Tooltip } from "@mui/material"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { getFileEntityUrl } from "../../../api/api.ts"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts"; +import SessionManager, { UserSettings } from "../../../session"; +import { getFileLinkedUri } from "../../../util"; +import MusicNote2 from "../../Icons/MusicNote2.tsx"; +import MusicNote2Play from "../../Icons/MusicNote2Play.tsx"; +import PlayerPopup from "./PlayerPopup.tsx"; + +export const LoopMode = { + list_repeat: 0, + single_repeat: 1, + shuffle: 2, +}; + +const MusicPlayer = () => { + const dispatch = useAppDispatch(); + const playerState = useAppSelector((state) => state.globalState.musicPlayer); + const audio = useRef(null); + const icon = useRef(null); + const [playing, setPlaying] = useState(false); + const [volume, setVolume] = useState(0.2); + const [index, setIndex] = useState(undefined); + const [loading, setLoading] = useState(false); + const [popoverOpen, setPopoverOpen] = useState(false); + const [duration, setDuration] = useState(0); + const [current, setCurrent] = useState(0); + const [loopMode, setLoopMode] = useState(LoopMode.list_repeat); + const [playbackSpeed, setPlaybackSpeed] = useState(1); + const playHistory = useRef([]); + + useEffect(() => { + if (playerState) { + playHistory.current = []; + setPlaying(true); + setPopoverOpen(true); + const volume = SessionManager.getWithFallback(UserSettings.MusicVolume); + setVolume(volume); + playIndex(playerState.startIndex, volume); + } + + audio.current?.addEventListener("timeupdate", timeUpdate); + return () => { + setPlaying(false); + audio.current?.removeEventListener("timeupdate", timeUpdate); + }; + }, [playerState]); + + const playIndex = useCallback( + async (index: number, latestVolume?: number) => { + if (audio.current && playerState) { + audio.current.pause(); + setIndex(index); + try { + const res = await dispatch( + getFileEntityUrl({ + uris: [getFileLinkedUri(playerState.files[index])], + entity: playerState.version, + }), + ); + audio.current.src = res.urls[0].url; + audio.current.currentTime = 0; + audio.current.play(); + audio.current.volume = latestVolume ?? volume; + audio.current.playbackRate = playbackSpeed; + } catch (e) { + console.error(e); + } + } + }, + [playerState, volume, playbackSpeed], + ); + + const loopProceed = useCallback( + (isNext: boolean) => { + if (!playerState) { + return; + } + + playHistory.current.push(index ?? 0); + + switch (loopMode) { + case LoopMode.list_repeat: + if (isNext) { + playIndex(((index ?? 0) + 1) % playerState?.files.length); + } else { + playIndex(((index ?? 0) - 1 + playerState?.files.length) % playerState?.files.length); + } + break; + case LoopMode.single_repeat: + playIndex(index ?? 0); + break; + case LoopMode.shuffle: + if (isNext) { + const nextIndex = Math.floor(Math.random() * playerState?.files.length); + playIndex(nextIndex); + } else { + playHistory.current.pop(); + playIndex(playHistory.current.pop() ?? index ?? 0); + } + break; + } + }, + [loopMode, playIndex, playerState, index], + ); + + const onPlayEnded = useCallback(() => { + loopProceed(true); + }, []); + + const timeUpdate = useCallback(() => { + setCurrent(Math.floor(audio.current?.currentTime || 0)); + setDuration(Math.floor(audio.current?.duration || 0)); + }, []); + + const seek = useCallback((time: number) => { + if (audio.current) { + audio.current.currentTime = time; + } + }, []); + + const playingTooltip = playerState + ? `[${(index ?? 0) + 1}/${playerState.files.length}] ${playerState?.files[index ?? 0]?.name}` + : ""; + + const onPlayerPopoverClose = useCallback(() => { + setPopoverOpen(false); + }, []); + + const onPlayerPopoverOpen = useCallback(() => { + setPopoverOpen(true); + }, []); + + const togglePause = useCallback(() => { + if (audio.current) { + if (audio.current.paused) { + audio.current.play(); + setPlaying(true); + } else { + audio.current.pause(); + setPlaying(false); + } + } + }, []); + + const setVolumeLevel = useCallback((volume: number) => { + if (audio.current) { + audio.current.volume = volume; + setVolume(volume); + } + }, []); + + const toggleLoopMode = useCallback(() => { + setLoopMode((loopMode) => (loopMode + 1) % 3); + }, []); + + const setLoopModeHandler = useCallback((mode: number) => { + setLoopMode(mode); + }, []); + + const setPlaybackSpeedHandler = useCallback((speed: number) => { + setPlaybackSpeed(speed); + if (audio.current) { + audio.current.playbackRate = speed; + } + }, []); + + return ( + <> +