Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0a2b1d3f2 | ||
|
|
5b6eca28ba |
@@ -59,8 +59,8 @@ def find_duplicate_keys(file_path, keys=None, prefix=""):
|
||||
return duplicates
|
||||
|
||||
|
||||
# Maximum size for TOML files (e.g., 1 MB)
|
||||
MAX_FILE_SIZE = 1000 * 1024
|
||||
# Maximum size for TOML files (e.g., 570 KB)
|
||||
MAX_FILE_SIZE = 570 * 1024
|
||||
|
||||
|
||||
def parse_toml_file(file_path):
|
||||
|
||||
@@ -97,9 +97,9 @@ cffi==2.0.0 \
|
||||
--hash=sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453 \
|
||||
--hash=sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf
|
||||
# via weasyprint
|
||||
cfgv==3.5.0 \
|
||||
--hash=sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0 \
|
||||
--hash=sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132
|
||||
cfgv==3.4.0 \
|
||||
--hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \
|
||||
--hash=sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560
|
||||
# via pre-commit
|
||||
cssselect2==0.8.0 \
|
||||
--hash=sha256:46fc70ebc41ced7a32cd42d58b1884d72ade23d21e5a4eaaf022401c13f0e76e \
|
||||
@@ -109,269 +109,259 @@ distlib==0.4.0 \
|
||||
--hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \
|
||||
--hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d
|
||||
# via virtualenv
|
||||
filelock==3.20.3 \
|
||||
--hash=sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1 \
|
||||
--hash=sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1
|
||||
filelock==3.20.0 \
|
||||
--hash=sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2 \
|
||||
--hash=sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4
|
||||
# via virtualenv
|
||||
fonttools==4.61.1 \
|
||||
--hash=sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87 \
|
||||
--hash=sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796 \
|
||||
--hash=sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75 \
|
||||
--hash=sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d \
|
||||
--hash=sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371 \
|
||||
--hash=sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b \
|
||||
--hash=sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b \
|
||||
--hash=sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2 \
|
||||
--hash=sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3 \
|
||||
--hash=sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9 \
|
||||
--hash=sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd \
|
||||
--hash=sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c \
|
||||
--hash=sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c \
|
||||
--hash=sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56 \
|
||||
--hash=sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37 \
|
||||
--hash=sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0 \
|
||||
--hash=sha256:5fe9fd43882620017add5eabb781ebfbc6998ee49b35bd7f8f79af1f9f99a958 \
|
||||
--hash=sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5 \
|
||||
--hash=sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118 \
|
||||
--hash=sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69 \
|
||||
--hash=sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9 \
|
||||
--hash=sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261 \
|
||||
--hash=sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb \
|
||||
--hash=sha256:78a7d3ab09dc47ac1a363a493e6112d8cabed7ba7caad5f54dbe2f08676d1b47 \
|
||||
--hash=sha256:7c7db70d57e5e1089a274cbb2b1fd635c9a24de809a231b154965d415d6c6d24 \
|
||||
--hash=sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c \
|
||||
--hash=sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba \
|
||||
--hash=sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c \
|
||||
--hash=sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91 \
|
||||
--hash=sha256:a13fc8aeb24bad755eea8f7f9d409438eb94e82cf86b08fe77a03fbc8f6a96b1 \
|
||||
--hash=sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19 \
|
||||
--hash=sha256:a76d4cb80f41ba94a6691264be76435e5f72f2cb3cab0b092a6212855f71c2f6 \
|
||||
--hash=sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5 \
|
||||
--hash=sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2 \
|
||||
--hash=sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d \
|
||||
--hash=sha256:b846a1fcf8beadeb9ea4f44ec5bdde393e2f1569e17d700bfc49cd69bde75881 \
|
||||
--hash=sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063 \
|
||||
--hash=sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7 \
|
||||
--hash=sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09 \
|
||||
--hash=sha256:d8db08051fc9e7d8bc622f2112511b8107d8f27cd89e2f64ec45e9825e8288da \
|
||||
--hash=sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e \
|
||||
--hash=sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e \
|
||||
--hash=sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8 \
|
||||
--hash=sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa \
|
||||
--hash=sha256:eff1ac3cc66c2ac7cda1e64b4e2f3ffef474b7335f92fc3833fc632d595fcee6 \
|
||||
--hash=sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e \
|
||||
--hash=sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a \
|
||||
--hash=sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c \
|
||||
--hash=sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7 \
|
||||
--hash=sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd
|
||||
fonttools==4.60.1 \
|
||||
--hash=sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c \
|
||||
--hash=sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc \
|
||||
--hash=sha256:0b0835ed15dd5b40d726bb61c846a688f5b4ce2208ec68779bc81860adb5851a \
|
||||
--hash=sha256:0eae96373e4b7c9e45d099d7a523444e3554360927225c1cdae221a58a45b856 \
|
||||
--hash=sha256:122e1a8ada290423c493491d002f622b1992b1ab0b488c68e31c413390dc7eb2 \
|
||||
--hash=sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259 \
|
||||
--hash=sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c \
|
||||
--hash=sha256:1525796c3ffe27bb6268ed2a1bb0dcf214d561dfaf04728abf01489eb5339dce \
|
||||
--hash=sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003 \
|
||||
--hash=sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272 \
|
||||
--hash=sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77 \
|
||||
--hash=sha256:268ecda8ca6cb5c4f044b1fb9b3b376e8cd1b361cef275082429dc4174907038 \
|
||||
--hash=sha256:282dafa55f9659e8999110bd8ed422ebe1c8aecd0dc396550b038e6c9a08b8ea \
|
||||
--hash=sha256:2ee06fc57512144d8b0445194c2da9f190f61ad51e230f14836286470c99f854 \
|
||||
--hash=sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2 \
|
||||
--hash=sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258 \
|
||||
--hash=sha256:4ba4bd646e86de16160f0fb72e31c3b9b7d0721c3e5b26b9fa2fc931dfdb2652 \
|
||||
--hash=sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08 \
|
||||
--hash=sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99 \
|
||||
--hash=sha256:596ecaca36367027d525b3b426d8a8208169d09edcf8c7506aceb3a38bfb55c7 \
|
||||
--hash=sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914 \
|
||||
--hash=sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6 \
|
||||
--hash=sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed \
|
||||
--hash=sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb \
|
||||
--hash=sha256:7473a8ed9ed09aeaa191301244a5a9dbe46fe0bf54f9d6cd21d83044c3321217 \
|
||||
--hash=sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc \
|
||||
--hash=sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f \
|
||||
--hash=sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c \
|
||||
--hash=sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877 \
|
||||
--hash=sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801 \
|
||||
--hash=sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85 \
|
||||
--hash=sha256:8b4eb332f9501cb1cd3d4d099374a1e1306783ff95489a1026bde9eb02ccc34a \
|
||||
--hash=sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb \
|
||||
--hash=sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383 \
|
||||
--hash=sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401 \
|
||||
--hash=sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28 \
|
||||
--hash=sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01 \
|
||||
--hash=sha256:a140761c4ff63d0cb9256ac752f230460ee225ccef4ad8f68affc723c88e2036 \
|
||||
--hash=sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc \
|
||||
--hash=sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac \
|
||||
--hash=sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903 \
|
||||
--hash=sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3 \
|
||||
--hash=sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6 \
|
||||
--hash=sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c \
|
||||
--hash=sha256:b42d86938e8dda1cd9a1a87a6d82f1818eaf933348429653559a458d027446da \
|
||||
--hash=sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299 \
|
||||
--hash=sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15 \
|
||||
--hash=sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199 \
|
||||
--hash=sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf \
|
||||
--hash=sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1 \
|
||||
--hash=sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537 \
|
||||
--hash=sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d \
|
||||
--hash=sha256:ee0c0b3b35b34f782afc673d503167157094a16f442ace7c6c5e0ca80b08f50c \
|
||||
--hash=sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4 \
|
||||
--hash=sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9 \
|
||||
--hash=sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed \
|
||||
--hash=sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987 \
|
||||
--hash=sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa
|
||||
# via weasyprint
|
||||
identify==2.6.16 \
|
||||
--hash=sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0 \
|
||||
--hash=sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980
|
||||
identify==2.6.15 \
|
||||
--hash=sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757 \
|
||||
--hash=sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf
|
||||
# via pre-commit
|
||||
nodeenv==1.10.0 \
|
||||
--hash=sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827 \
|
||||
--hash=sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb
|
||||
nodeenv==1.9.1 \
|
||||
--hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \
|
||||
--hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9
|
||||
# via pre-commit
|
||||
numpy==2.4.1 \
|
||||
--hash=sha256:0093e85df2960d7e4049664b26afc58b03236e967fb942354deef3208857a04c \
|
||||
--hash=sha256:09aa8a87e45b55a1c2c205d42e2808849ece5c484b2aab11fecabec3841cafba \
|
||||
--hash=sha256:0cce2a669e3c8ba02ee563c7835f92c153cf02edff1ae05e1823f1dde21b16a5 \
|
||||
--hash=sha256:0e6e8f9d9ecf95399982019c01223dc130542960a12edfa8edd1122dfa66a8a8 \
|
||||
--hash=sha256:0f118ce6b972080ba0758c6087c3617b5ba243d806268623dc34216d69099ba0 \
|
||||
--hash=sha256:178de8f87948163d98a4c9ab5bee4ce6519ca918926ec8df195af582de28544d \
|
||||
--hash=sha256:18e14c4d09d55eef39a6ab5b08406e84bc6869c1e34eef45564804f90b7e0574 \
|
||||
--hash=sha256:2023ef86243690c2791fd6353e5b4848eedaa88ca8a2d129f462049f6d484696 \
|
||||
--hash=sha256:20d4649c773f66cc2fc36f663e091f57c3b7655f936a4c681b4250855d1da8f5 \
|
||||
--hash=sha256:2302dc0224c1cbc49bb94f7064f3f923a971bfae45c33870dcbff63a2a550505 \
|
||||
--hash=sha256:26f0bcd9c79a00e339565b303badc74d3ea2bd6d52191eeca5f95936cad107d0 \
|
||||
--hash=sha256:297c72b1b98100c2e8f873d5d35fb551fce7040ade83d67dd51d38c8d42a2162 \
|
||||
--hash=sha256:2f44de05659b67d20499cbc96d49f2650769afcb398b79b324bb6e297bfe3844 \
|
||||
--hash=sha256:2ffd257026eb1b34352e749d7cc1678b5eeec3e329ad8c9965a797e08ccba205 \
|
||||
--hash=sha256:382ad67d99ef49024f11d1ce5dcb5ad8432446e4246a4b014418ba3a1175a1f4 \
|
||||
--hash=sha256:3869ea1ee1a1edc16c29bbe3a2f2a4e515cc3a44d43903ad41e0cacdbaf733dc \
|
||||
--hash=sha256:3d1a100e48cb266090a031397863ff8a30050ceefd798f686ff92c67a486753d \
|
||||
--hash=sha256:423797bdab2eeefbe608d7c1ec7b2b4fd3c58d51460f1ee26c7500a1d9c9ee93 \
|
||||
--hash=sha256:42d7dd5fa36d16d52a84f821eb96031836fd405ee6955dd732f2023724d0aa01 \
|
||||
--hash=sha256:49e792ec351315e16da54b543db06ca8a86985ab682602d90c60ef4ff4db2a9c \
|
||||
--hash=sha256:4e53170557d37ae404bf8d542ca5b7c629d6efa1117dac6a83e394142ea0a43f \
|
||||
--hash=sha256:4f1b68ff47680c2925f8063402a693ede215f0257f02596b1318ecdfb1d79e33 \
|
||||
--hash=sha256:4f9c360ecef085e5841c539a9a12b883dff005fbd7ce46722f5e9cef52634d82 \
|
||||
--hash=sha256:529050522e983e00a6c1c6b67411083630de8b57f65e853d7b03d9281b8694d2 \
|
||||
--hash=sha256:52b5f61bdb323b566b528899cc7db2ba5d1015bda7ea811a8bcf3c89c331fa42 \
|
||||
--hash=sha256:538bf4ec353709c765ff75ae616c34d3c3dca1a68312727e8f2676ea644f8509 \
|
||||
--hash=sha256:5adf01965456a664fc727ed69cc71848f28d063217c63e1a0e200a118d5eec9a \
|
||||
--hash=sha256:5b55aa56165b17aaf15520beb9cbd33c9039810e0d9643dd4379e44294c7303e \
|
||||
--hash=sha256:5d558123217a83b2d1ba316b986e9248a1ed1971ad495963d555ccd75dcb1556 \
|
||||
--hash=sha256:5de60946f14ebe15e713a6f22850c2372fa72f4ff9a432ab44aa90edcadaa65a \
|
||||
--hash=sha256:62fea415f83ad8fdb6c20840578e5fbaf5ddd65e0ec6c3c47eda0f69da172510 \
|
||||
--hash=sha256:6436cffb4f2bf26c974344439439c95e152c9a527013f26b3577be6c2ca64295 \
|
||||
--hash=sha256:6461de5113088b399d655d45c3897fa188766415d0f568f175ab071c8873bd73 \
|
||||
--hash=sha256:69e7419c9012c4aaf695109564e3387f1259f001b4326dfa55907b098af082d3 \
|
||||
--hash=sha256:71abbea030f2cfc3092a0ff9f8c8fdefdc5e0bf7d9d9c99663538bb0ecdac0b9 \
|
||||
--hash=sha256:7211b95ca365519d3596a1d8688a95874cc94219d417504d9ecb2df99fa7bfa8 \
|
||||
--hash=sha256:727c6c3275ddefa0dc078524a85e064c057b4f4e71ca5ca29a19163c607be745 \
|
||||
--hash=sha256:79e9e06c4c2379db47f3f6fc7a8652e7498251789bf8ff5bd43bf478ef314ca2 \
|
||||
--hash=sha256:7ad270f438cbdd402c364980317fb6b117d9ec5e226fff5b4148dd9aa9fc6e02 \
|
||||
--hash=sha256:7d5d7999df434a038d75a748275cd6c0094b0ecdb0837342b332a82defc4dc4d \
|
||||
--hash=sha256:8097529164c0f3e32bb89412a0905d9100bf434d9692d9fc275e18dcf53c9344 \
|
||||
--hash=sha256:82c55962006156aeef1629b953fd359064aa47e4d82cfc8e67f0918f7da3344f \
|
||||
--hash=sha256:8361ea4220d763e54cff2fbe7d8c93526b744f7cd9ddab47afeff7e14e8503be \
|
||||
--hash=sha256:899d2c18024984814ac7e83f8f49d8e8180e2fbe1b2e252f2e7f1d06bea92425 \
|
||||
--hash=sha256:8ad35f20be147a204e28b6a0575fbf3540c5e5f802634d4258d55b1ff5facce1 \
|
||||
--hash=sha256:8f085da926c0d491ffff3096f91078cc97ea67e7e6b65e490bc8dcda65663be2 \
|
||||
--hash=sha256:9171a42fcad32dcf3fa86f0a4faa5e9f8facefdb276f54b8b390d90447cff4e2 \
|
||||
--hash=sha256:92a0e65272fd60bfa0d9278e0484c2f52fe03b97aedc02b357f33fe752c52ffb \
|
||||
--hash=sha256:941c2a93313d030f219f3a71fd3d91a728b82979a5e8034eb2e60d394a2b83f9 \
|
||||
--hash=sha256:98b35775e03ab7f868908b524fc0a84d38932d8daf7b7e1c3c3a1b6c7a2c9f15 \
|
||||
--hash=sha256:a1ceafc5042451a858231588a104093474c6a5c57dcc724841f5c888d237d690 \
|
||||
--hash=sha256:a73044b752f5d34d4232f25f18160a1cc418ea4507f5f11e299d8ac36875f8a0 \
|
||||
--hash=sha256:a7870e8c5fc11aef57d6fea4b4085e537a3a60ad2cdd14322ed531fdca68d261 \
|
||||
--hash=sha256:a92f227dbcdc9e4c3e193add1a189a9909947d4f8504c576f4a732fd0b54240a \
|
||||
--hash=sha256:ac08c63cb7779b85e9d5318e6c3518b424bc1f364ac4cb2c6136f12e5ff2dccc \
|
||||
--hash=sha256:b6bcf39112e956594b3331316d90c90c90fb961e39696bda97b89462f5f3943f \
|
||||
--hash=sha256:c0faba4a331195bfa96f93dd9dfaa10b2c7aa8cda3a02b7fd635e588fe821bf5 \
|
||||
--hash=sha256:ce9ce141a505053b3c7bce3216071f3bf5c182b8b28930f14cd24d43932cd2df \
|
||||
--hash=sha256:cf6470d91d34bf669f61d515499859fa7a4c2f7c36434afb70e82df7217933f9 \
|
||||
--hash=sha256:d3703409aac693fa82c0aee023a1ae06a6e9d065dba10f5e8e80f642f1e9d0a2 \
|
||||
--hash=sha256:d3e3087f53e2b4428766b54932644d148613c5a595150533ae7f00dab2f319a8 \
|
||||
--hash=sha256:d3f8f0df9f4b8be57b3bf74a1d087fec68f927a2fab68231fdb442bf2c12e426 \
|
||||
--hash=sha256:d797454e37570cfd61143b73b8debd623c3c0952959adb817dd310a483d58a1b \
|
||||
--hash=sha256:e1a27bb1b2dee45a2a53f5ca6ff2d1a7f135287883a1689e930d44d1ff296c87 \
|
||||
--hash=sha256:e3bd2cb07841166420d2fa7146c96ce00cb3410664cbc1a6be028e456c4ee220 \
|
||||
--hash=sha256:e7b6b5e28bbd47b7532698e5db2fe1db693d84b58c254e4389d99a27bb9b8f6b \
|
||||
--hash=sha256:e867df947d427cdd7a60e3e271729090b0f0df80f5f10ab7dd436f40811699c3 \
|
||||
--hash=sha256:ea66d2b41ca4a1630aae5507ee0a71647d3124d1741980138aa8f28f44dac36e \
|
||||
--hash=sha256:edee228f76ee2dab4579fad6f51f6a305de09d444280109e0f75df247ff21501 \
|
||||
--hash=sha256:f0a90aba7d521e6954670550e561a4cb925713bd944445dbe9e729b71f6cabee \
|
||||
--hash=sha256:f93bc6892fe7b0663e5ffa83b61aab510aacffd58c16e012bb9352d489d90cb7 \
|
||||
--hash=sha256:fb1461c99de4d040666ca0444057b06541e5642f800b71c56e6ea92d6a853a0c
|
||||
numpy==2.2.6 \
|
||||
--hash=sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff \
|
||||
--hash=sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47 \
|
||||
--hash=sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84 \
|
||||
--hash=sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d \
|
||||
--hash=sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6 \
|
||||
--hash=sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f \
|
||||
--hash=sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b \
|
||||
--hash=sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49 \
|
||||
--hash=sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163 \
|
||||
--hash=sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571 \
|
||||
--hash=sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42 \
|
||||
--hash=sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff \
|
||||
--hash=sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491 \
|
||||
--hash=sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4 \
|
||||
--hash=sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566 \
|
||||
--hash=sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf \
|
||||
--hash=sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40 \
|
||||
--hash=sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd \
|
||||
--hash=sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06 \
|
||||
--hash=sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282 \
|
||||
--hash=sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680 \
|
||||
--hash=sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db \
|
||||
--hash=sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3 \
|
||||
--hash=sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90 \
|
||||
--hash=sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1 \
|
||||
--hash=sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289 \
|
||||
--hash=sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab \
|
||||
--hash=sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c \
|
||||
--hash=sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d \
|
||||
--hash=sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb \
|
||||
--hash=sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d \
|
||||
--hash=sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a \
|
||||
--hash=sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf \
|
||||
--hash=sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1 \
|
||||
--hash=sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2 \
|
||||
--hash=sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a \
|
||||
--hash=sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543 \
|
||||
--hash=sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00 \
|
||||
--hash=sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c \
|
||||
--hash=sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f \
|
||||
--hash=sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd \
|
||||
--hash=sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868 \
|
||||
--hash=sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303 \
|
||||
--hash=sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83 \
|
||||
--hash=sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3 \
|
||||
--hash=sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d \
|
||||
--hash=sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87 \
|
||||
--hash=sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa \
|
||||
--hash=sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f \
|
||||
--hash=sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae \
|
||||
--hash=sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda \
|
||||
--hash=sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915 \
|
||||
--hash=sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249 \
|
||||
--hash=sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de \
|
||||
--hash=sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8
|
||||
# via opencv-python-headless
|
||||
opencv-python-headless==4.13.0.90 \
|
||||
--hash=sha256:0e0c8c9f620802fddc4fa7f471a1d263c7b0dca16cd9e7e2f996bb8bd2128c0c \
|
||||
--hash=sha256:12a28674f215542c9bf93338de1b5bffd76996d32da9acb9e739fdb9c8bbd738 \
|
||||
--hash=sha256:32255203040dc98803be96362e13f9e4bce20146898222d2e5c242f80de50da5 \
|
||||
--hash=sha256:96060fc57a1abb1144b0b8129e2ff3bfcdd0ccd8e8bd05bd85256ff4ed587d3b \
|
||||
--hash=sha256:dbc1f4625e5af3a80ebdbd84380227c0f445228588f2521b11af47710caca1ba \
|
||||
--hash=sha256:e13790342591557050157713af17a7435ac1b50c65282715093c9297fa045d8f \
|
||||
--hash=sha256:eba38bc255d0b7d1969c5bcc90a060ca2b61a3403b613872c750bfa5dfe9e03b \
|
||||
--hash=sha256:f46b17ea0aa7e4124ca6ad71143f89233ae9557f61d2326bcdb34329a1ddf9bd
|
||||
opencv-python-headless==4.12.0.88 \
|
||||
--hash=sha256:1e58d664809b3350c1123484dd441e1667cd7bed3086db1b9ea1b6f6cb20b50e \
|
||||
--hash=sha256:236c8df54a90f4d02076e6f9c1cc763d794542e886c576a6fee46ec8ff75a7a9 \
|
||||
--hash=sha256:365bb2e486b50feffc2d07a405b953a8f3e8eaa63865bc650034e5c71e7a5154 \
|
||||
--hash=sha256:86b413bdd6c6bf497832e346cd5371995de148e579b9774f8eba686dee3f5528 \
|
||||
--hash=sha256:aeb4b13ecb8b4a0beb2668ea07928160ea7c2cd2d9b5ef571bbee6bafe9cc8d0 \
|
||||
--hash=sha256:cfdc017ddf2e59b6c2f53bc12d74b6b0be7ded4ec59083ea70763921af2b6c09 \
|
||||
--hash=sha256:fde2cf5c51e4def5f2132d78e0c08f9c14783cd67356922182c6845b9af87dbd
|
||||
# via -r .github/scripts/requirements_dev.in
|
||||
pdf2image==1.17.0 \
|
||||
--hash=sha256:eaa959bc116b420dd7ec415fcae49b98100dda3dd18cd2fdfa86d09f112f6d57 \
|
||||
--hash=sha256:ecdd58d7afb810dffe21ef2b1bbc057ef434dabbac6c33778a38a3f7744a27e2
|
||||
# via -r .github/scripts/requirements_dev.in
|
||||
pillow==12.1.0 \
|
||||
--hash=sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d \
|
||||
--hash=sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc \
|
||||
--hash=sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84 \
|
||||
--hash=sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de \
|
||||
--hash=sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0 \
|
||||
--hash=sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef \
|
||||
--hash=sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4 \
|
||||
--hash=sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82 \
|
||||
--hash=sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9 \
|
||||
--hash=sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030 \
|
||||
--hash=sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0 \
|
||||
--hash=sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18 \
|
||||
--hash=sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a \
|
||||
--hash=sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef \
|
||||
--hash=sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b \
|
||||
--hash=sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6 \
|
||||
--hash=sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179 \
|
||||
--hash=sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e \
|
||||
--hash=sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72 \
|
||||
--hash=sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64 \
|
||||
--hash=sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451 \
|
||||
--hash=sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd \
|
||||
--hash=sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924 \
|
||||
--hash=sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616 \
|
||||
--hash=sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a \
|
||||
--hash=sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94 \
|
||||
--hash=sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc \
|
||||
--hash=sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8 \
|
||||
--hash=sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9 \
|
||||
--hash=sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91 \
|
||||
--hash=sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a \
|
||||
--hash=sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c \
|
||||
--hash=sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670 \
|
||||
--hash=sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea \
|
||||
--hash=sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91 \
|
||||
--hash=sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c \
|
||||
--hash=sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc \
|
||||
--hash=sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0 \
|
||||
--hash=sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b \
|
||||
--hash=sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65 \
|
||||
--hash=sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661 \
|
||||
--hash=sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19 \
|
||||
--hash=sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1 \
|
||||
--hash=sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0 \
|
||||
--hash=sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e \
|
||||
--hash=sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75 \
|
||||
--hash=sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4 \
|
||||
--hash=sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8 \
|
||||
--hash=sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd \
|
||||
--hash=sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7 \
|
||||
--hash=sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61 \
|
||||
--hash=sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51 \
|
||||
--hash=sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551 \
|
||||
--hash=sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45 \
|
||||
--hash=sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1 \
|
||||
--hash=sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644 \
|
||||
--hash=sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796 \
|
||||
--hash=sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587 \
|
||||
--hash=sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304 \
|
||||
--hash=sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b \
|
||||
--hash=sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8 \
|
||||
--hash=sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17 \
|
||||
--hash=sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171 \
|
||||
--hash=sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3 \
|
||||
--hash=sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7 \
|
||||
--hash=sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988 \
|
||||
--hash=sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a \
|
||||
--hash=sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0 \
|
||||
--hash=sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c \
|
||||
--hash=sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2 \
|
||||
--hash=sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14 \
|
||||
--hash=sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5 \
|
||||
--hash=sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a \
|
||||
--hash=sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377 \
|
||||
--hash=sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0 \
|
||||
--hash=sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5 \
|
||||
--hash=sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b \
|
||||
--hash=sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d \
|
||||
--hash=sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac \
|
||||
--hash=sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c \
|
||||
--hash=sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554 \
|
||||
--hash=sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643 \
|
||||
--hash=sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13 \
|
||||
--hash=sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09 \
|
||||
--hash=sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208 \
|
||||
--hash=sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda \
|
||||
--hash=sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea \
|
||||
--hash=sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e \
|
||||
--hash=sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0 \
|
||||
--hash=sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831 \
|
||||
--hash=sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd
|
||||
pillow==12.0.0 \
|
||||
--hash=sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643 \
|
||||
--hash=sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e \
|
||||
--hash=sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e \
|
||||
--hash=sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc \
|
||||
--hash=sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642 \
|
||||
--hash=sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6 \
|
||||
--hash=sha256:1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1 \
|
||||
--hash=sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b \
|
||||
--hash=sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399 \
|
||||
--hash=sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba \
|
||||
--hash=sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad \
|
||||
--hash=sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47 \
|
||||
--hash=sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739 \
|
||||
--hash=sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b \
|
||||
--hash=sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f \
|
||||
--hash=sha256:32ed80ea8a90ee3e6fa08c21e2e091bba6eda8eccc83dbc34c95169507a91f10 \
|
||||
--hash=sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52 \
|
||||
--hash=sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d \
|
||||
--hash=sha256:3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b \
|
||||
--hash=sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a \
|
||||
--hash=sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9 \
|
||||
--hash=sha256:455247ac8a4cfb7b9bc45b7e432d10421aea9fc2e74d285ba4072688a74c2e9d \
|
||||
--hash=sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098 \
|
||||
--hash=sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905 \
|
||||
--hash=sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b \
|
||||
--hash=sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3 \
|
||||
--hash=sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371 \
|
||||
--hash=sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953 \
|
||||
--hash=sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01 \
|
||||
--hash=sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca \
|
||||
--hash=sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e \
|
||||
--hash=sha256:6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7 \
|
||||
--hash=sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27 \
|
||||
--hash=sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082 \
|
||||
--hash=sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e \
|
||||
--hash=sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d \
|
||||
--hash=sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8 \
|
||||
--hash=sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a \
|
||||
--hash=sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad \
|
||||
--hash=sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3 \
|
||||
--hash=sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a \
|
||||
--hash=sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d \
|
||||
--hash=sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353 \
|
||||
--hash=sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee \
|
||||
--hash=sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b \
|
||||
--hash=sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b \
|
||||
--hash=sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a \
|
||||
--hash=sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7 \
|
||||
--hash=sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef \
|
||||
--hash=sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a \
|
||||
--hash=sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a \
|
||||
--hash=sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257 \
|
||||
--hash=sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07 \
|
||||
--hash=sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4 \
|
||||
--hash=sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c \
|
||||
--hash=sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c \
|
||||
--hash=sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4 \
|
||||
--hash=sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe \
|
||||
--hash=sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8 \
|
||||
--hash=sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5 \
|
||||
--hash=sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6 \
|
||||
--hash=sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e \
|
||||
--hash=sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8 \
|
||||
--hash=sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e \
|
||||
--hash=sha256:bd87e140e45399c818fac4247880b9ce719e4783d767e030a883a970be632275 \
|
||||
--hash=sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3 \
|
||||
--hash=sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76 \
|
||||
--hash=sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227 \
|
||||
--hash=sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9 \
|
||||
--hash=sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5 \
|
||||
--hash=sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79 \
|
||||
--hash=sha256:c7b2a63fd6d5246349f3d3f37b14430d73ee7e8173154461785e43036ffa96ca \
|
||||
--hash=sha256:c828a1ae702fc712978bda0320ba1b9893d99be0badf2647f693cc01cf0f04fa \
|
||||
--hash=sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b \
|
||||
--hash=sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e \
|
||||
--hash=sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197 \
|
||||
--hash=sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab \
|
||||
--hash=sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79 \
|
||||
--hash=sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2 \
|
||||
--hash=sha256:d49e2314c373f4c2b39446fb1a45ed333c850e09d0c59ac79b72eb3b95397363 \
|
||||
--hash=sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0 \
|
||||
--hash=sha256:d64317d2587c70324b79861babb9c09f71fbb780bad212018874b2c013d8600e \
|
||||
--hash=sha256:d77153e14b709fd8b8af6f66a3afbb9ed6e9fc5ccf0b6b7e1ced7b036a228782 \
|
||||
--hash=sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925 \
|
||||
--hash=sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0 \
|
||||
--hash=sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b \
|
||||
--hash=sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced \
|
||||
--hash=sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c \
|
||||
--hash=sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344 \
|
||||
--hash=sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9 \
|
||||
--hash=sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1
|
||||
# via
|
||||
# -r .github/scripts/requirements_dev.in
|
||||
# pdf2image
|
||||
# weasyprint
|
||||
platformdirs==4.5.1 \
|
||||
--hash=sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda \
|
||||
--hash=sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31
|
||||
platformdirs==4.5.0 \
|
||||
--hash=sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312 \
|
||||
--hash=sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3
|
||||
# via virtualenv
|
||||
pre-commit==4.5.1 \
|
||||
--hash=sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77 \
|
||||
--hash=sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61
|
||||
pre-commit==4.3.0 \
|
||||
--hash=sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8 \
|
||||
--hash=sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16
|
||||
# via -r .github/scripts/requirements_dev.in
|
||||
pycparser==3.0 \
|
||||
--hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \
|
||||
--hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992
|
||||
pycparser==2.23 \
|
||||
--hash=sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2 \
|
||||
--hash=sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934
|
||||
# via cffi
|
||||
pydyf==0.12.1 \
|
||||
--hash=sha256:ea25b4e1fe7911195cb57067560daaa266639184e8335365cc3ee5214e7eaadc \
|
||||
--hash=sha256:fbd7e759541ac725c29c506612003de393249b94310ea78ae44cb1d04b220095
|
||||
pydyf==0.11.0 \
|
||||
--hash=sha256:0aaf9e2ebbe786ec7a78ec3fbffa4cdcecde53fd6f563221d53c6bc1328848a3 \
|
||||
--hash=sha256:394dddf619cca9d0c55715e3c55ea121a9bf9cbc780cdc1201a2427917b86b64
|
||||
# via weasyprint
|
||||
pyphen==0.17.2 \
|
||||
--hash=sha256:3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd \
|
||||
@@ -452,9 +442,9 @@ pyyaml==6.0.3 \
|
||||
--hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \
|
||||
--hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0
|
||||
# via pre-commit
|
||||
tinycss2==1.5.1 \
|
||||
--hash=sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661 \
|
||||
--hash=sha256:d339d2b616ba90ccce58da8495a78f46e55d4d25f9fd71dfd526f07e7d53f957
|
||||
tinycss2==1.4.0 \
|
||||
--hash=sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7 \
|
||||
--hash=sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289
|
||||
# via
|
||||
# cssselect2
|
||||
# weasyprint
|
||||
@@ -462,17 +452,17 @@ tinyhtml5==2.0.0 \
|
||||
--hash=sha256:086f998833da24c300c414d9fe81d9b368fd04cb9d2596a008421cbc705fcfcc \
|
||||
--hash=sha256:13683277c5b176d070f82d099d977194b7a1e26815b016114f581a74bbfbf47e
|
||||
# via weasyprint
|
||||
unoserver==3.6 \
|
||||
--hash=sha256:25c360fa194396a89cb79b4edd2735f8e4f0fd8531e59db3952114585bd7df05 \
|
||||
--hash=sha256:e446bcb3638c51880f002aaeecab1cf74dfa9df81035f027f7ff2e081b6d7015
|
||||
unoserver==3.4 \
|
||||
--hash=sha256:3dcf2204013def1d1ddd3671f38b11346bdf349fef9728277462666a8a634419 \
|
||||
--hash=sha256:64c24d33d4f65d680a2d9f676518cb28e7fd6c1f9d9a745c33e4a4cb59afdfcd
|
||||
# via -r .github/scripts/requirements_dev.in
|
||||
virtualenv==20.36.1 \
|
||||
--hash=sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f \
|
||||
--hash=sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba
|
||||
virtualenv==20.35.4 \
|
||||
--hash=sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c \
|
||||
--hash=sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b
|
||||
# via pre-commit
|
||||
weasyprint==68.0 \
|
||||
--hash=sha256:447f40898b747cb44ac31a5d493d512e7441fd56e13f63744c099383bbf9cda9 \
|
||||
--hash=sha256:c2cb40c71b50837c5971f00171c9e4078e8c9912dd7c217f3e90e068f11e8aa1
|
||||
weasyprint==66.0 \
|
||||
--hash=sha256:82b0783b726fcd318e2c977dcdddca76515b30044bc7a830cc4fbe717582a6d0 \
|
||||
--hash=sha256:da71dc87dc129ac9cffdc65e5477e90365ab9dbae45c744014ec1d06303dde40
|
||||
# via -r .github/scripts/requirements_dev.in
|
||||
webencodings==0.5.1 \
|
||||
--hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
|
||||
@@ -481,27 +471,86 @@ webencodings==0.5.1 \
|
||||
# cssselect2
|
||||
# tinycss2
|
||||
# tinyhtml5
|
||||
zopfli==0.4.0 \
|
||||
--hash=sha256:03181d48e719fcb6cf8340189c61e8f9883d8bbbdf76bf5212a74457f7d083c1 \
|
||||
--hash=sha256:18b5f1570f64d4988482e4466f10ef5f2a30f687c19ad62a64560f2152dc89eb \
|
||||
--hash=sha256:25e4863b8dc30e5d5309f87c106b0b7d3da4ed0e340b8a52b36d4471e797589f \
|
||||
--hash=sha256:7d66337be6d5613dec55213e9ac28f378c41e2cc04fbad4a10748e4df774ca85 \
|
||||
--hash=sha256:9097e8e1dfdb7f5aea5464e469946857e80502b6d29ba1b232450916bd4a74d1 \
|
||||
--hash=sha256:a8ee992b2549e090cd3f0178bf606dd41a29e0613a04cdf5054224662c72dce6 \
|
||||
--hash=sha256:b72a010d205d00b2855acc2302772067362f9ab5a012e3550662aec60d28e6b3 \
|
||||
--hash=sha256:b8bdb41fbfdc4738b7bdc09ed7c1e951579fae192391a5e694d59bb186cdbec7 \
|
||||
--hash=sha256:c3ba02a9a6ca90481d2b2f68bab038b310d63a1e3b5ae305e95a6599787ed941 \
|
||||
--hash=sha256:d1b98ad47c434ef213444a03ef2f826eeec100144d64f6a57504b9893d3931ce \
|
||||
--hash=sha256:f67d04280065e24cb9a4174cb6b3d1f763687f8cb2963aa135ad8f57c6995f5a \
|
||||
--hash=sha256:f94e4dd7d76b4fe9f5d9229372be20d7f786164eea5152d1af1c34298c3d5975
|
||||
zopfli==0.2.3.post1 \
|
||||
--hash=sha256:0aa5f90d6298bda02a95bc8dc8c3c19004d5a4e44bda00b67ca7431d857b4b54 \
|
||||
--hash=sha256:0cc20b02a9531559945324c38302fd4ba763311632d0ec8a1a0aa9c10ea363e6 \
|
||||
--hash=sha256:1d8cc06605519e82b16df090e17cb3990d1158861b2872c3117f1168777b81e4 \
|
||||
--hash=sha256:1f990634fd5c5c8ced8edddd8bd45fab565123b4194d6841e01811292650acae \
|
||||
--hash=sha256:2345e713260a350bea0b01a816a469ea356bc2d63d009a0d777691ecbbcf7493 \
|
||||
--hash=sha256:2768c877f76c8a0e7519b1c86c93757f3c01492ddde55751e9988afb7eff64e1 \
|
||||
--hash=sha256:29ea74e72ffa6e291b8c6f2504ce6c146b4fe990c724c1450eb8e4c27fd31431 \
|
||||
--hash=sha256:34a99592f3d9eb6f737616b5bd74b48a589fdb3cb59a01a50d636ea81d6af272 \
|
||||
--hash=sha256:3654bfc927bc478b1c3f3ff5056ed7b20a1a37fa108ca503256d0a699c03bbb1 \
|
||||
--hash=sha256:3657e416ffb8f31d9d3424af12122bb251befae109f2e271d87d825c92fc5b7b \
|
||||
--hash=sha256:37d011e92f7b9622742c905fdbed9920a1d0361df84142807ea2a528419dea7f \
|
||||
--hash=sha256:3827170de28faf144992d3d4dcf8f3998fe3c8a6a6f4a08f1d42c2ec6119d2bb \
|
||||
--hash=sha256:39e576f93576c5c223b41d9c780bbb91fd6db4babf3223d2a4fe7bf568e2b5a8 \
|
||||
--hash=sha256:3a89277ed5f8c0fb2d0b46d669aa0633123aa7381f1f6118c12f15e0fb48f8ca \
|
||||
--hash=sha256:3c163911f8bad94b3e1db0a572e7c28ba681a0c91d0002ea1e4fa9264c21ef17 \
|
||||
--hash=sha256:3f0197b6aa6eb3086ae9e66d6dd86c4d502b6c68b0ec490496348ae8c05ecaef \
|
||||
--hash=sha256:48dba9251060289101343110ab47c0756f66f809bb4d1ddbb6d5c7e7752115c5 \
|
||||
--hash=sha256:4915a41375bdee4db749ecd07d985a0486eb688a6619f713b7bf6fbfd145e960 \
|
||||
--hash=sha256:4c1226a7e2c7105ac31503a9bb97454743f55d88164d6d46bc138051b77f609b \
|
||||
--hash=sha256:4e50ffac74842c1c1018b9b73875a0d0a877c066ab06bf7cccbaa84af97e754f \
|
||||
--hash=sha256:518f1f4ed35dd69ce06b552f84e6d081f07c552b4c661c5312d950a0b764a58a \
|
||||
--hash=sha256:5aad740b4d4fcbaaae4887823925166ffd062db3b248b3f432198fc287381d1a \
|
||||
--hash=sha256:5f272186e03ad55e7af09ab78055535c201b1a0bcc2944edb1768298d9c483a4 \
|
||||
--hash=sha256:5fcfc0dc2761e4fcc15ad5d273b4d58c2e8e059d3214a7390d4d3c8e2aee644e \
|
||||
--hash=sha256:60db20f06c3d4c5934b16cfa62a2cc5c3f0686bffe0071ed7804d3c31ab1a04e \
|
||||
--hash=sha256:615a8ac9dda265e9cc38b2a76c3142e4a9f30fea4a79c85f670850783bc6feb4 \
|
||||
--hash=sha256:6482db9876c68faac2d20a96b566ffbf65ddaadd97b222e4e73641f4f8722fc4 \
|
||||
--hash=sha256:6617fb10f9e4393b331941861d73afb119cd847e88e4974bdbe8068ceef3f73f \
|
||||
--hash=sha256:676919fba7311125244eb0c4393679ac5fe856e5864a15d122bd815205369fa0 \
|
||||
--hash=sha256:6c2d2bc8129707e34c51f9352c4636ca313b52350bbb7e04637c46c1818a2a70 \
|
||||
--hash=sha256:71390dbd3fbf6ebea9a5d85ffed8c26ee1453ee09248e9b88486e30e0397b775 \
|
||||
--hash=sha256:716cdbfc57bfd3d3e31a58e6246e8190e6849b7dbb7c4ce39ef8bbf0edb8f6d5 \
|
||||
--hash=sha256:75a26a2307b10745a83b660c404416e984ee6fca515ec7f0765f69af3ce08072 \
|
||||
--hash=sha256:7be5cc6732eb7b4df17305d8a7b293223f934a31783a874a01164703bc1be6cd \
|
||||
--hash=sha256:7cce242b5df12b2b172489daf19c32e5577dd2fac659eb4b17f6a6efb446fd5c \
|
||||
--hash=sha256:81c341d9bb87a6dbbb0d45d6e272aca80c7c97b4b210f9b6e233bf8b87242f29 \
|
||||
--hash=sha256:89899641d4de97dbad8e0cde690040d078b6aea04066dacaab98e0b5a23573f2 \
|
||||
--hash=sha256:8d5ab297d660b75c159190ce6d73035502310e40fd35170aed7d1a1aea7ddd65 \
|
||||
--hash=sha256:8fbe5bcf10d01aab3513550f284c09fef32f342b36f56bfae2120a9c4d12c130 \
|
||||
--hash=sha256:91a2327a4d7e77471fa4fbb26991c6de4a738c6fc6a33e09bb25f56a870a4b7b \
|
||||
--hash=sha256:95a260cafd56b8fffa679918937401c80bb38e1681c448b988022e4c3610965d \
|
||||
--hash=sha256:96484dc0f48be1c5d7ae9f38ed1ce41e3675fd506b27c11a6607f14b49101e99 \
|
||||
--hash=sha256:9a6aec38a989bad7ddd1ef53f1265699e49e294d08231b5313d61293f3cd6237 \
|
||||
--hash=sha256:9ba214f4f45bec195ee8559651154d3ac2932470b9d91c5715fc29c013349f8c \
|
||||
--hash=sha256:9f4a7ec2770e6af05f5a02733fd3900f30a9cd58e5d6d3727e14c5bcd6e7d587 \
|
||||
--hash=sha256:a1cf720896d2ce998bc8e051d4b4ce0d8bec007aab6243102e8e1d22a0b2fb3f \
|
||||
--hash=sha256:a241a68581d34d67b40c425cce3d1fd211c092f99d9250947824ccba9f491949 \
|
||||
--hash=sha256:a53b18797cdef27e019db595d66c4b077325afe2fd62145953275f53d84ce40c \
|
||||
--hash=sha256:a82fc2dbebe6eb908b9c665e71496f8525c1bc4d2e3a7a7722ef2b128b6227c8 \
|
||||
--hash=sha256:a86eb88e06bd87e1fff31dac878965c26b0c26db59ddcf78bb0379a954b120de \
|
||||
--hash=sha256:aa588b21044f8a74e423d8c8a4c7fc9988501878aacced793467010039c50734 \
|
||||
--hash=sha256:b05296e8bc88c92e2b21e0a9bae4740c1551ee613c1d93a51fd28a7a0b2b6fbb \
|
||||
--hash=sha256:b0ec13f352ea5ae0fc91f98a48540512eed0767d0ec4f7f3cb92d92797983d18 \
|
||||
--hash=sha256:b3df42f52502438ee973042cc551877d24619fa1cd38ef7b7e9ac74200daca8b \
|
||||
--hash=sha256:b78008a69300d929ca2efeffec951b64a312e9a811e265ea4a907ab546d79fa6 \
|
||||
--hash=sha256:b9026a21b6d41eb0e2e63f5bc1242c3fcc43ecb770963cda99a4307863dac12e \
|
||||
--hash=sha256:bbe429fc50686bb2a2608a30843e36fbaa123462a5284f136c7d9e0145220bfd \
|
||||
--hash=sha256:bfa1eb759e07d8b7aa7a310a2bc535e127ee70addf90dc8d4b946b593c3e51a8 \
|
||||
--hash=sha256:c1e0ed5d84ffa2d677cc9582fc01e61dab2e7ef8b8996e055f0a76167b1b94df \
|
||||
--hash=sha256:c4278d1873ce6e803e5d4f8d702fd3026bd67fca744aa98881324d1157ddf748 \
|
||||
--hash=sha256:cac2b37ab21c2b36a10b685b1893ebd6b0f83ae26004838ac817680881576567 \
|
||||
--hash=sha256:cbe6df25807227519debd1a57ab236f5f6bad441500e85b13903e51f93a43214 \
|
||||
--hash=sha256:cd2c002f160502608dcc822ed2441a0f4509c52e86fcfd1a09e937278ed1ca14 \
|
||||
--hash=sha256:e0137dd64a493ba6a4be37405cfd6febe650a98cc1e9dca8f6b8c63b1db11b41 \
|
||||
--hash=sha256:e63d558847166543c2c9789e6f985400a520b7eacc4b99181668b2c3aeadd352 \
|
||||
--hash=sha256:eb45a34f23da4f8bc712b6376ca5396914b0b7c09adbb001dad964eb7f3132f8 \
|
||||
--hash=sha256:ecb7572df5372abce8073df078207d9d1749f20b8b136089916a4a0868d56051 \
|
||||
--hash=sha256:f12000a6accdd4bf0a3fa6eaa1b1c7a7bc80af0a2edf3f89d770d3dcce1d0e22 \
|
||||
--hash=sha256:f7d69c1a7168ad0e9cb864e8663acb232986a0c9c9cb9801f56bf6214f53a54d \
|
||||
--hash=sha256:f815fcc2b2a457977724bad97fb4854022980f51ce7b136925e336b530545ae1 \
|
||||
--hash=sha256:fc39f5c27f962ec8660d8d20c24762431131b5d8c672b44b0a54cf2b5bcde9b9
|
||||
# via fonttools
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
pip==26.0 \
|
||||
--hash=sha256:3ce220a0a17915972fbf1ab451baae1521c4539e778b28127efa79b974aff0fa \
|
||||
--hash=sha256:98436feffb9e31bc9339cf369fd55d3331b1580b6a6f1173bacacddcf9c34754
|
||||
pip==25.3 \
|
||||
--hash=sha256:8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343 \
|
||||
--hash=sha256:9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd
|
||||
# via -r .github/scripts/requirements_dev.in
|
||||
setuptools==80.10.2 \
|
||||
--hash=sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70 \
|
||||
--hash=sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173
|
||||
setuptools==80.9.0 \
|
||||
--hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \
|
||||
--hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c
|
||||
# via -r .github/scripts/requirements_dev.in
|
||||
|
||||
@@ -12,25 +12,25 @@ distlib==0.4.0 \
|
||||
--hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \
|
||||
--hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d
|
||||
# via virtualenv
|
||||
filelock==3.20.3 \
|
||||
--hash=sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1 \
|
||||
--hash=sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1
|
||||
filelock==3.20.0 \
|
||||
--hash=sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2 \
|
||||
--hash=sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4
|
||||
# via virtualenv
|
||||
identify==2.6.16 \
|
||||
--hash=sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0 \
|
||||
--hash=sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980
|
||||
identify==2.6.15 \
|
||||
--hash=sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757 \
|
||||
--hash=sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf
|
||||
# via pre-commit
|
||||
nodeenv==1.10.0 \
|
||||
--hash=sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827 \
|
||||
--hash=sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb
|
||||
nodeenv==1.9.1 \
|
||||
--hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \
|
||||
--hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9
|
||||
# via pre-commit
|
||||
platformdirs==4.5.1 \
|
||||
--hash=sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda \
|
||||
--hash=sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31
|
||||
platformdirs==4.5.0 \
|
||||
--hash=sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312 \
|
||||
--hash=sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3
|
||||
# via virtualenv
|
||||
pre-commit==4.5.1 \
|
||||
--hash=sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77 \
|
||||
--hash=sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61
|
||||
pre-commit==4.5.0 \
|
||||
--hash=sha256:25e2ce09595174d9c97860a95609f9f852c0614ba602de3561e267547f2335e1 \
|
||||
--hash=sha256:dc5a065e932b19fc1d4c653c6939068fe54325af8e741e74e88db4d28a4dd66b
|
||||
# via -r .github/scripts/requirements_pre_commit.in
|
||||
pyyaml==6.0.3 \
|
||||
--hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \
|
||||
@@ -107,7 +107,7 @@ pyyaml==6.0.3 \
|
||||
--hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \
|
||||
--hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0
|
||||
# via pre-commit
|
||||
virtualenv==20.36.1 \
|
||||
--hash=sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f \
|
||||
--hash=sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba
|
||||
virtualenv==20.35.4 \
|
||||
--hash=sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c \
|
||||
--hash=sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b
|
||||
# via pre-commit
|
||||
|
||||
@@ -8,7 +8,7 @@ tomli-w==1.2.0 \
|
||||
--hash=sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90 \
|
||||
--hash=sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021
|
||||
# via -r .github/scripts/requirements_sync_readme.in
|
||||
tomlkit==0.14.0 \
|
||||
--hash=sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680 \
|
||||
--hash=sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064
|
||||
tomlkit==0.13.3 \
|
||||
--hash=sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1 \
|
||||
--hash=sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0
|
||||
# via -r .github/scripts/requirements_sync_readme.in
|
||||
|
||||
@@ -196,6 +196,104 @@ jobs:
|
||||
path: frontend/dist/
|
||||
retention-days: 3
|
||||
|
||||
sync-versions:
|
||||
if: needs.files-changed.outputs.build == 'true'
|
||||
needs: [files-changed, build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup GitHub App Bot
|
||||
id: setup-bot
|
||||
uses: ./.github/actions/setup-bot
|
||||
with:
|
||||
app-id: ${{ secrets.GH_APP_ID }}
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||
with:
|
||||
java-version: "21"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
|
||||
with:
|
||||
gradle-version: 8.14
|
||||
|
||||
- name: Run version sync
|
||||
run: ./gradlew syncAppVersion --build-cache
|
||||
|
||||
- name: Check if version files have changed
|
||||
id: git-check
|
||||
run: |
|
||||
if [[ -n $(git status --porcelain) ]]; then
|
||||
echo "changes=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "changes=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Post comment on PR
|
||||
if: steps.git-check.outputs.changes == 'true' && github.event_name != 'workflow_dispatch'
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||
script: |
|
||||
const { GITHUB_REPOSITORY } = process.env;
|
||||
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
|
||||
const issueNumber = context.issue.number;
|
||||
|
||||
// Find existing comment
|
||||
const comments = await github.rest.issues.listComments({
|
||||
owner: repoOwner,
|
||||
repo: repoName,
|
||||
issue_number: issueNumber
|
||||
});
|
||||
|
||||
const comment = comments.data.find(c => c.body.includes("## ⚠️ Version Differences"));
|
||||
|
||||
const commentBody = `## ⚠️ Version Differences
|
||||
|
||||
The versions in the following files do not match the version defined in \`build.gradle\`.
|
||||
|
||||
Running \`./gradlew syncAppVersion\` or \`./gradlew build\` will automatically update these files:
|
||||
|
||||
\`frontend/src-tauri/tauri.conf.json\`, \`frontend/src/proprietary/testing/serverExperienceSimulations.ts\`, \`frontend/src/core/testing/serverExperienceSimulations.ts\`
|
||||
|
||||
Please run \`./gradlew syncAppVersion\` or \`./gradlew build\`!`;
|
||||
|
||||
// Only update or create comments by the action user
|
||||
const expectedActor = "${{ steps.setup-bot.outputs.app-slug }}[bot]";
|
||||
|
||||
if (comment && comment.user.login === expectedActor) {
|
||||
// Update existing comment
|
||||
await github.rest.issues.updateComment({
|
||||
owner: repoOwner,
|
||||
repo: repoName,
|
||||
comment_id: comment.id,
|
||||
body: commentBody
|
||||
});
|
||||
console.log("Updated existing comment.");
|
||||
} else if (!comment) {
|
||||
// Create new comment if no existing comment is found
|
||||
await github.rest.issues.createComment({
|
||||
owner: repoOwner,
|
||||
repo: repoName,
|
||||
issue_number: issueNumber,
|
||||
body: commentBody
|
||||
});
|
||||
} else {
|
||||
console.log("Comment update attempt denied. Actor does not match.");
|
||||
}
|
||||
|
||||
check-licence:
|
||||
if: needs.files-changed.outputs.build == 'true'
|
||||
needs: [files-changed, build]
|
||||
|
||||
@@ -130,7 +130,7 @@ jobs:
|
||||
|
||||
- name: Setup Node.js
|
||||
if: matrix.variant.build_frontend == true
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
@@ -185,14 +185,14 @@ jobs:
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libjavascriptcoregtk-4.0-dev libsoup2.4-dev libjavascriptcoregtk-4.1-dev libsoup-3.0-dev
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: ${{ (matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel') && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
@@ -291,7 +291,7 @@ jobs:
|
||||
- name: Setup DigiCert KeyLocker
|
||||
id: digicert-setup
|
||||
if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && (github.event_name == 'release' || github.ref == 'refs/heads/V2-master') }}
|
||||
uses: digicert/ssm-code-signing@9476ceec3ea1c63298d4403b983e1ccf2556ff4c # v1.1.0
|
||||
uses: digicert/ssm-code-signing@v1.1.0
|
||||
env:
|
||||
SM_API_KEY: ${{ secrets.SM_API_KEY }}
|
||||
SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }}
|
||||
@@ -399,7 +399,7 @@ jobs:
|
||||
echo "Certificate imported successfully."
|
||||
|
||||
- name: Build Tauri app
|
||||
uses: tauri-apps/tauri-action@51a9f1156b33df106d827c3a78f8f894946c5faa # v0.5.25
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
|
||||
@@ -33,11 +33,6 @@ jobs:
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Determine build matrix
|
||||
id: set-matrix
|
||||
run: |
|
||||
@@ -86,14 +81,14 @@ jobs:
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libjavascriptcoregtk-4.0-dev libsoup2.4-dev libjavascriptcoregtk-4.1-dev libsoup-3.0-dev
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
cache-dependency-path: frontend/package-lock.json
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: ${{ (matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel') && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
@@ -193,7 +188,7 @@ jobs:
|
||||
- name: Setup DigiCert KeyLocker
|
||||
id: digicert-setup
|
||||
if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && github.ref == 'refs/heads/main' }}
|
||||
uses: digicert/ssm-code-signing@9476ceec3ea1c63298d4403b983e1ccf2556ff4c # v1.1.0
|
||||
uses: digicert/ssm-code-signing@v1.1.0
|
||||
env:
|
||||
SM_API_KEY: ${{ secrets.SM_API_KEY }}
|
||||
SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }}
|
||||
@@ -312,7 +307,7 @@ jobs:
|
||||
ls -la /usr/bin/hd* || echo "No hd* tools found"
|
||||
|
||||
- name: Build Tauri app
|
||||
uses: tauri-apps/tauri-action@51a9f1156b33df106d827c3a78f8f894946c5faa # v0.5.25
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
@@ -644,11 +639,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Report build results
|
||||
run: |
|
||||
if [ "${{ needs.build.result }}" = "success" ]; then
|
||||
|
||||
@@ -128,11 +128,6 @@ jobs:
|
||||
outputs:
|
||||
frontend: ${{ steps.changes.outputs.frontend }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Check for file changes
|
||||
|
||||
@@ -199,6 +199,7 @@ return useToolOperation({
|
||||
|
||||
- **Java Version**: Minimum JDK 17, supports and recommends JDK 21
|
||||
- **Lombok**: Used extensively - ensure IDE plugin is installed
|
||||
- **Desktop Mode**: Set `STIRLING_PDF_DESKTOP_UI=true` for desktop application mode
|
||||
- **File Persistence**:
|
||||
- **Backend**: Designed to be stateless - files are processed in memory/temp locations only
|
||||
- **Frontend**: Uses IndexedDB for client-side file storage and caching (with thumbnails)
|
||||
|
||||
@@ -303,23 +303,22 @@ public class EndpointConfiguration {
|
||||
// Adding endpoints to "PageOps" group
|
||||
addEndpointToGroup("PageOps", "remove-pages");
|
||||
addEndpointToGroup("PageOps", "merge-pdfs");
|
||||
addEndpointToGroup("PageOps", "split-pages");
|
||||
addEndpointToGroup("PageOps", "rearrange-pages");
|
||||
addEndpointToGroup("PageOps", "split-pdfs");
|
||||
addEndpointToGroup("PageOps", "pdf-organizer");
|
||||
addEndpointToGroup("PageOps", "rotate-pdf");
|
||||
addEndpointToGroup("PageOps", "multi-page-layout");
|
||||
addEndpointToGroup("PageOps", "booklet-imposition");
|
||||
addEndpointToGroup("PageOps", "scale-pages");
|
||||
addEndpointToGroup("PageOps", "crop");
|
||||
addEndpointToGroup("PageOps", "extract-page");
|
||||
addEndpointToGroup("PageOps", "pdf-to-single-page");
|
||||
addEndpointToGroup("PageOps", "auto-split-pdf");
|
||||
addEndpointToGroup("PageOps", "split-by-size-or-count");
|
||||
addEndpointToGroup("PageOps", "overlay-pdf");
|
||||
addEndpointToGroup("PageOps", "split-pdf-by-sections");
|
||||
addEndpointToGroup("PageOps", "split-pdf-by-chapters");
|
||||
addEndpointToGroup("PageOps", "add-page-numbers");
|
||||
addEndpointToGroup("PageOps", "extract-pages");
|
||||
|
||||
// Adding endpoints to "Convert" group (Frontend has 15 convert endpoints)
|
||||
// Adding endpoints to "Convert" group
|
||||
addEndpointToGroup("Convert", "pdf-to-img");
|
||||
addEndpointToGroup("Convert", "img-to-pdf");
|
||||
addEndpointToGroup("Convert", "pdf-to-pdfa");
|
||||
@@ -335,8 +334,6 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Convert", "pdf-to-csv");
|
||||
addEndpointToGroup("Convert", "pdf-to-markdown");
|
||||
addEndpointToGroup("Convert", "eml-to-pdf");
|
||||
addEndpointToGroup("Convert", "pdf-to-epub");
|
||||
// Backend-only endpoints (not in frontend tool registry)
|
||||
addEndpointToGroup("Convert", "pdf-to-vector");
|
||||
addEndpointToGroup("Convert", "vector-to-pdf");
|
||||
addEndpointToGroup("Convert", "pdf-to-video");
|
||||
@@ -344,7 +341,6 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Convert", "pdf-to-cbz");
|
||||
addEndpointToGroup("Convert", "pdf-to-json");
|
||||
addEndpointToGroup("Convert", "json-to-pdf");
|
||||
addEndpointToGroup("Convert", "pdf-to-rtf");
|
||||
|
||||
// Adding endpoints to "Security" group
|
||||
addEndpointToGroup("Security", "add-password");
|
||||
@@ -355,49 +351,51 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Security", "remove-cert-sign");
|
||||
addEndpointToGroup("Security", "sanitize-pdf");
|
||||
addEndpointToGroup("Security", "auto-redact");
|
||||
addEndpointToGroup("Security", "validate-signature");
|
||||
addEndpointToGroup("Security", "add-stamp");
|
||||
addEndpointToGroup("Security", "unlock-pdf-forms");
|
||||
// Backend-only endpoints (not in frontend tool registry endpoints)
|
||||
addEndpointToGroup("Security", "redact");
|
||||
addEndpointToGroup("Security", "validate-signature");
|
||||
addEndpointToGroup("Security", "verify-pdf");
|
||||
addEndpointToGroup("Security", "stamp");
|
||||
addEndpointToGroup("Security", "sign");
|
||||
|
||||
// Adding endpoints to "Other" group
|
||||
addEndpointToGroup("Other", "ocr-pdf");
|
||||
addEndpointToGroup("Other", "add-image");
|
||||
addEndpointToGroup("Other", "extract-images");
|
||||
addEndpointToGroup("Other", "update-metadata");
|
||||
addEndpointToGroup("Other", "change-metadata");
|
||||
addEndpointToGroup("Other", "flatten");
|
||||
addEndpointToGroup("Other", "unlock-pdf-forms");
|
||||
addEndpointToGroup("Other", REMOVE_BLANKS);
|
||||
addEndpointToGroup("Other", "remove-annotations");
|
||||
addEndpointToGroup("Other", "compare");
|
||||
addEndpointToGroup("Other", "add-page-numbers");
|
||||
addEndpointToGroup("Other", "get-info-on-pdf");
|
||||
addEndpointToGroup("Other", "remove-image-pdf");
|
||||
addEndpointToGroup("Other", "add-attachments");
|
||||
addEndpointToGroup("Other", "replace-invert-pdf");
|
||||
addEndpointToGroup("Other", "edit-table-of-contents");
|
||||
addEndpointToGroup("Other", "text-editor-pdf");
|
||||
// Backend-only endpoints (not in frontend tool registry endpoints)
|
||||
addEndpointToGroup("Other", "add-image");
|
||||
addEndpointToGroup("Other", "compare");
|
||||
addEndpointToGroup("Other", "view-pdf");
|
||||
addEndpointToGroup("Other", "replace-and-invert-color-pdf");
|
||||
addEndpointToGroup("Other", "multi-tool");
|
||||
|
||||
// Adding form-related endpoints to "Other" group
|
||||
addEndpointToGroup("Other", "fields");
|
||||
addEndpointToGroup("Other", "modify-fields");
|
||||
addEndpointToGroup("Other", "delete-fields");
|
||||
addEndpointToGroup("Other", "fill");
|
||||
|
||||
// Adding endpoints to "Advance" group
|
||||
addEndpointToGroup("Advance", "adjust-contrast");
|
||||
addEndpointToGroup("Advance", "compress-pdf");
|
||||
addEndpointToGroup("Advance", "extract-image-scans");
|
||||
addEndpointToGroup("Advance", "repair");
|
||||
addEndpointToGroup("Advance", "auto-rename");
|
||||
addEndpointToGroup("Advance", "handleData");
|
||||
addEndpointToGroup("Advance", "scanner-effect");
|
||||
addEndpointToGroup("Advance", "show-javascript");
|
||||
addEndpointToGroup("Advance", "overlay-pdf");
|
||||
// Backend-only endpoints
|
||||
addEndpointToGroup("Advance", "adjust-contrast");
|
||||
addEndpointToGroup("Advance", "pipeline");
|
||||
addEndpointToGroup("Advance", "scanner-effect");
|
||||
addEndpointToGroup("Advance", "auto-split-pdf");
|
||||
addEndpointToGroup("Advance", "show-javascript");
|
||||
addEndpointToGroup("Advance", "split-by-size-or-count");
|
||||
addEndpointToGroup("Advance", "overlay-pdf");
|
||||
addEndpointToGroup("Advance", "split-pdf-by-sections");
|
||||
addEndpointToGroup("Advance", "edit-table-of-contents");
|
||||
addEndpointToGroup("Advance", "split-pdf-by-chapters");
|
||||
|
||||
// CLI
|
||||
addEndpointToGroup("CLI", "compress-pdf");
|
||||
@@ -438,8 +436,8 @@ public class EndpointConfiguration {
|
||||
// Java
|
||||
addEndpointToGroup("Java", "merge-pdfs");
|
||||
addEndpointToGroup("Java", "remove-pages");
|
||||
addEndpointToGroup("Java", "split-pages");
|
||||
addEndpointToGroup("Java", "rearrange-pages");
|
||||
addEndpointToGroup("Java", "split-pdfs");
|
||||
addEndpointToGroup("Java", "pdf-organizer");
|
||||
addEndpointToGroup("Java", "rotate-pdf");
|
||||
addEndpointToGroup("Java", "pdf-to-img");
|
||||
addEndpointToGroup("Java", "img-to-pdf");
|
||||
@@ -447,10 +445,9 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Java", "remove-password");
|
||||
addEndpointToGroup("Java", "change-permissions");
|
||||
addEndpointToGroup("Java", "add-watermark");
|
||||
addEndpointToGroup("Java", "add-stamp");
|
||||
addEndpointToGroup("Java", "add-image");
|
||||
addEndpointToGroup("Java", "extract-images");
|
||||
addEndpointToGroup("Java", "update-metadata");
|
||||
addEndpointToGroup("Java", "change-metadata");
|
||||
addEndpointToGroup("Java", "cert-sign");
|
||||
addEndpointToGroup("Java", "remove-cert-sign");
|
||||
addEndpointToGroup("Java", "multi-page-layout");
|
||||
@@ -462,6 +459,7 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Java", "sanitize-pdf");
|
||||
addEndpointToGroup("Java", "crop");
|
||||
addEndpointToGroup("Java", "get-info-on-pdf");
|
||||
addEndpointToGroup("Java", "extract-page");
|
||||
addEndpointToGroup("Java", "pdf-to-single-page");
|
||||
addEndpointToGroup("Java", "markdown-to-pdf");
|
||||
addEndpointToGroup("Java", "show-javascript");
|
||||
@@ -471,9 +469,7 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Java", "split-by-size-or-count");
|
||||
addEndpointToGroup("Java", "overlay-pdf");
|
||||
addEndpointToGroup("Java", "split-pdf-by-sections");
|
||||
addEndpointToGroup("Java", "split-pdf-by-chapters");
|
||||
addEndpointToGroup("Java", REMOVE_BLANKS);
|
||||
addEndpointToGroup("Java", "remove-annotations");
|
||||
addEndpointToGroup("Java", "pdf-to-text");
|
||||
addEndpointToGroup("Java", "remove-image-pdf");
|
||||
addEndpointToGroup("Java", "pdf-to-markdown");
|
||||
@@ -483,24 +479,15 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Java", "pdf-to-cbz");
|
||||
addEndpointToGroup("Java", "pdf-to-json");
|
||||
addEndpointToGroup("Java", "json-to-pdf");
|
||||
addEndpointToGroup("rar", "pdf-to-cbr");
|
||||
addEndpointToGroup("Java", "pdf-to-video");
|
||||
addEndpointToGroup("Java", "verify-pdf");
|
||||
addEndpointToGroup("Java", "flatten");
|
||||
addEndpointToGroup("Java", "unlock-pdf-forms");
|
||||
addEndpointToGroup("Java", "validate-signature");
|
||||
addEndpointToGroup("Java", "text-editor-pdf");
|
||||
addEndpointToGroup("Java", "edit-table-of-contents");
|
||||
addEndpointToGroup("Java", "pdf-to-epub");
|
||||
addEndpointToGroup("Java", "eml-to-pdf");
|
||||
addEndpointToGroup("Java", "handleData");
|
||||
addEndpointToGroup("rar", "pdf-to-cbr");
|
||||
|
||||
// Javascript
|
||||
addEndpointToGroup("Javascript", "rearrange-pages");
|
||||
addEndpointToGroup("Javascript", "pdf-organizer");
|
||||
addEndpointToGroup("Javascript", "sign");
|
||||
addEndpointToGroup("Javascript", "compare");
|
||||
addEndpointToGroup("Javascript", "adjust-contrast");
|
||||
addEndpointToGroup("Javascript", "text-editor-pdf");
|
||||
|
||||
/* qpdf */
|
||||
addEndpointToGroup("qpdf", "repair");
|
||||
@@ -511,7 +498,6 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("Ghostscript", "compress-pdf");
|
||||
addEndpointToGroup("Ghostscript", "crop");
|
||||
addEndpointToGroup("Ghostscript", "replace-invert-pdf");
|
||||
addEndpointToGroup("Ghostscript", "scanner-effect");
|
||||
addEndpointToGroup("Ghostscript", "pdf-to-vector");
|
||||
addEndpointToGroup("Ghostscript", "vector-to-pdf");
|
||||
|
||||
@@ -530,8 +516,6 @@ public class EndpointConfiguration {
|
||||
addEndpointAlternative("compress-pdf", "qpdf");
|
||||
addEndpointAlternative("compress-pdf", "Ghostscript");
|
||||
addEndpointAlternative("compress-pdf", "Java");
|
||||
addEndpointAlternative("crop", "Ghostscript");
|
||||
addEndpointAlternative("crop", "Java");
|
||||
addEndpointAlternative("ocr-pdf", "tesseract");
|
||||
addEndpointAlternative("ocr-pdf", "OCRmyPDF");
|
||||
|
||||
@@ -560,9 +544,6 @@ public class EndpointConfiguration {
|
||||
// Pdftohtml dependent endpoints
|
||||
addEndpointToGroup("Pdftohtml", "pdf-to-html");
|
||||
addEndpointToGroup("Pdftohtml", "pdf-to-markdown");
|
||||
|
||||
// Calibre dependent endpoints
|
||||
addEndpointToGroup("Calibre", "pdf-to-epub");
|
||||
}
|
||||
|
||||
private void processEnvironmentConfigs() {
|
||||
|
||||
+24
@@ -1,6 +1,8 @@
|
||||
package stirling.software.common.configuration;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Locale;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@@ -59,6 +61,28 @@ public class InstallationPathConfig {
|
||||
}
|
||||
|
||||
private static String initializeBasePath() {
|
||||
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
||||
String os = System.getProperty("os.name").toLowerCase(Locale.ROOT);
|
||||
if (os.contains("win")) {
|
||||
return Paths.get(
|
||||
System.getenv("APPDATA"), // parent path
|
||||
"Stirling-PDF")
|
||||
+ File.separator;
|
||||
} else if (os.contains("mac")) {
|
||||
return Paths.get(
|
||||
System.getProperty("user.home"),
|
||||
"Library",
|
||||
"Application Support",
|
||||
"Stirling-PDF")
|
||||
+ File.separator;
|
||||
} else {
|
||||
return Paths.get(
|
||||
System.getProperty("user.home"), // parent path
|
||||
".config",
|
||||
"Stirling-PDF")
|
||||
+ File.separator;
|
||||
}
|
||||
}
|
||||
return "." + File.separator;
|
||||
}
|
||||
|
||||
|
||||
+6
-155
@@ -1,14 +1,10 @@
|
||||
package stirling.software.common.configuration;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -45,7 +41,6 @@ public class RuntimePathConfig {
|
||||
|
||||
// Pipeline paths
|
||||
private final String pipelineWatchedFoldersPath;
|
||||
private final List<String> pipelineWatchedFoldersPaths;
|
||||
private final String pipelineFinishedFoldersPath;
|
||||
private final String pipelineDefaultWebUiConfigs;
|
||||
private final String pipelinePath;
|
||||
@@ -54,27 +49,20 @@ public class RuntimePathConfig {
|
||||
this.properties = properties;
|
||||
this.basePath = InstallationPathConfig.getPath();
|
||||
|
||||
this.pipelinePath = Path.of(basePath, "pipeline").toString();
|
||||
String defaultWatchedFolders = Path.of(this.pipelinePath, "watchedFolders").toString();
|
||||
String defaultFinishedFolders = Path.of(this.pipelinePath, "finishedFolders").toString();
|
||||
String defaultWebUIConfigs = Path.of(this.pipelinePath, "defaultWebUIConfigs").toString();
|
||||
|
||||
System system = properties.getSystem();
|
||||
CustomPaths customPaths = system.getCustomPaths();
|
||||
|
||||
Pipeline pipeline = customPaths.getPipeline();
|
||||
|
||||
this.pipelinePath =
|
||||
this.pipelineWatchedFoldersPath =
|
||||
resolvePath(
|
||||
Path.of(basePath, "pipeline").toString(),
|
||||
pipeline != null ? pipeline.getPipelineDir() : null);
|
||||
String defaultWatchedFolders = Path.of(this.pipelinePath, "watchedFolders").toString();
|
||||
String defaultFinishedFolders = Path.of(this.pipelinePath, "finishedFolders").toString();
|
||||
String defaultWebUIConfigs = Path.of(this.pipelinePath, "defaultWebUIConfigs").toString();
|
||||
|
||||
List<String> watchedFoldersDirs =
|
||||
sanitizePathList(pipeline != null ? pipeline.getWatchedFoldersDirs() : null);
|
||||
this.pipelineWatchedFoldersPaths =
|
||||
resolveWatchedFolderPaths(
|
||||
defaultWatchedFolders,
|
||||
watchedFoldersDirs,
|
||||
pipeline != null ? pipeline.getWatchedFoldersDir() : null);
|
||||
this.pipelineWatchedFoldersPath = this.pipelineWatchedFoldersPaths.get(0);
|
||||
this.pipelineFinishedFoldersPath =
|
||||
resolvePath(
|
||||
defaultFinishedFolders,
|
||||
@@ -84,9 +72,6 @@ public class RuntimePathConfig {
|
||||
defaultWebUIConfigs,
|
||||
pipeline != null ? pipeline.getWebUIConfigsDir() : null);
|
||||
|
||||
// Validate path conflicts after all paths are resolved
|
||||
validatePipelinePaths();
|
||||
|
||||
boolean isDocker = isRunningInDocker();
|
||||
|
||||
// Initialize Operation paths
|
||||
@@ -144,140 +129,6 @@ public class RuntimePathConfig {
|
||||
return StringUtils.isNotBlank(customPath) ? customPath : defaultPath;
|
||||
}
|
||||
|
||||
private List<String> resolveWatchedFolderPaths(
|
||||
String defaultPath, List<String> watchedFoldersDirs, String legacyWatchedFolder) {
|
||||
List<String> rawPaths = new ArrayList<>();
|
||||
|
||||
// Collect paths from new config
|
||||
if (watchedFoldersDirs != null && !watchedFoldersDirs.isEmpty()) {
|
||||
rawPaths.addAll(watchedFoldersDirs);
|
||||
}
|
||||
// Fall back to legacy config
|
||||
else if (StringUtils.isNotBlank(legacyWatchedFolder)) {
|
||||
rawPaths.add(legacyWatchedFolder);
|
||||
}
|
||||
// Fall back to default
|
||||
else {
|
||||
rawPaths.add(defaultPath);
|
||||
}
|
||||
|
||||
// Validate, normalize, and deduplicate paths
|
||||
List<String> validatedPaths = validateAndNormalizePaths(rawPaths);
|
||||
|
||||
// Ensure we have at least one valid path (critical for system to function)
|
||||
if (validatedPaths.isEmpty()) {
|
||||
log.warn(
|
||||
"No valid watched folder paths configured, falling back to default: {}",
|
||||
defaultPath);
|
||||
validatedPaths.add(defaultPath);
|
||||
}
|
||||
|
||||
// Detect overlapping paths (warning only, not blocking)
|
||||
detectOverlappingPaths(validatedPaths);
|
||||
|
||||
return validatedPaths;
|
||||
}
|
||||
|
||||
private List<String> sanitizePathList(List<String> paths) {
|
||||
if (paths == null || paths.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> sanitized = new ArrayList<>();
|
||||
for (String path : paths) {
|
||||
if (StringUtils.isNotBlank(path)) {
|
||||
sanitized.add(path.trim());
|
||||
}
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
private List<String> validateAndNormalizePaths(List<String> paths) {
|
||||
Set<String> normalizedPaths = new LinkedHashSet<>(); // Preserves order, prevents duplicates
|
||||
|
||||
for (String pathStr : paths) {
|
||||
if (StringUtils.isBlank(pathStr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Normalize to absolute path
|
||||
Path path = Paths.get(pathStr.trim()).toAbsolutePath().normalize();
|
||||
String normalizedPath = path.toString();
|
||||
|
||||
// Check for duplicates
|
||||
if (normalizedPaths.contains(normalizedPath)) {
|
||||
log.debug("Skipping duplicate watched folder path: {}", pathStr);
|
||||
continue;
|
||||
}
|
||||
|
||||
normalizedPaths.add(normalizedPath);
|
||||
log.info("Registered watched folder path: {}", normalizedPath);
|
||||
|
||||
} catch (InvalidPathException e) {
|
||||
log.error(
|
||||
"Invalid watched folder path '{}' - skipping: {}", pathStr, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayList<>(normalizedPaths);
|
||||
}
|
||||
|
||||
private void detectOverlappingPaths(List<String> paths) {
|
||||
for (int i = 0; i < paths.size(); i++) {
|
||||
Path path1 = Paths.get(paths.get(i));
|
||||
for (int j = i + 1; j < paths.size(); j++) {
|
||||
Path path2 = Paths.get(paths.get(j));
|
||||
|
||||
// Check if one path is a parent of the other
|
||||
if (path1.startsWith(path2)) {
|
||||
log.warn(
|
||||
"Watched folder path '{}' is nested inside '{}' - this may cause duplicate processing",
|
||||
path1,
|
||||
path2);
|
||||
} else if (path2.startsWith(path1)) {
|
||||
log.warn(
|
||||
"Watched folder path '{}' is nested inside '{}' - this may cause duplicate processing",
|
||||
path2,
|
||||
path1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePipelinePaths() {
|
||||
try {
|
||||
Path finishedPath = Paths.get(pipelineFinishedFoldersPath).toAbsolutePath().normalize();
|
||||
|
||||
for (String watchedPathStr : pipelineWatchedFoldersPaths) {
|
||||
Path watchedPath = Paths.get(watchedPathStr).toAbsolutePath().normalize();
|
||||
|
||||
// Check if watched folder is same as finished folder
|
||||
if (watchedPath.equals(finishedPath)) {
|
||||
log.error(
|
||||
"CRITICAL: Watched folder '{}' is the same as finished folder '{}' - this will cause processing loops!",
|
||||
watchedPath,
|
||||
finishedPath);
|
||||
}
|
||||
// Check if watched folder contains finished folder
|
||||
else if (finishedPath.startsWith(watchedPath)) {
|
||||
log.warn(
|
||||
"Finished folder '{}' is nested inside watched folder '{}' - this may cause issues",
|
||||
finishedPath,
|
||||
watchedPath);
|
||||
}
|
||||
// Check if finished folder contains watched folder
|
||||
else if (watchedPath.startsWith(finishedPath)) {
|
||||
log.error(
|
||||
"CRITICAL: Watched folder '{}' is nested inside finished folder '{}' - this will cause processing loops!",
|
||||
watchedPath,
|
||||
finishedPath);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error validating pipeline paths: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRunningInDocker() {
|
||||
return Files.exists(Path.of("/.dockerenv"));
|
||||
}
|
||||
|
||||
@@ -164,7 +164,6 @@ public class ApplicationProperties {
|
||||
private String customGlobalAPIKey;
|
||||
private Jwt jwt = new Jwt();
|
||||
private Validation validation = new Validation();
|
||||
private String xFrameOptions = "DENY";
|
||||
|
||||
public Boolean isAltLogin() {
|
||||
return saml2.getEnabled() || oauth2.getEnabled();
|
||||
@@ -459,9 +458,7 @@ public class ApplicationProperties {
|
||||
|
||||
@Data
|
||||
public static class Pipeline {
|
||||
private String pipelineDir;
|
||||
private String watchedFoldersDir;
|
||||
private List<String> watchedFoldersDirs = new ArrayList<>();
|
||||
private String finishedFoldersDir;
|
||||
private String webUIConfigsDir;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public class FileMonitor {
|
||||
private final ConcurrentHashMap.KeySetView<Path, Boolean> readyForProcessingFiles;
|
||||
private final WatchService watchService;
|
||||
private final Predicate<Path> pathFilter;
|
||||
private final List<Path> rootDirs;
|
||||
private final Path rootDir;
|
||||
private Set<Path> stagingFiles;
|
||||
|
||||
/**
|
||||
@@ -47,28 +47,8 @@ public class FileMonitor {
|
||||
this.pathFilter = pathFilter;
|
||||
this.readyForProcessingFiles = ConcurrentHashMap.newKeySet();
|
||||
this.watchService = FileSystems.getDefault().newWatchService();
|
||||
|
||||
List<String> watchedFoldersDirs = runtimePathConfig.getPipelineWatchedFoldersPaths();
|
||||
List<Path> validRootDirs = new ArrayList<>();
|
||||
|
||||
for (String pathStr : watchedFoldersDirs) {
|
||||
try {
|
||||
Path path = Path.of(pathStr);
|
||||
validRootDirs.add(path);
|
||||
log.info("Monitoring directory: {}", path);
|
||||
} catch (Exception e) {
|
||||
log.error(
|
||||
"Failed to initialize monitoring for path '{}': {}",
|
||||
pathStr,
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
this.rootDirs = Collections.unmodifiableList(validRootDirs);
|
||||
|
||||
if (this.rootDirs.isEmpty()) {
|
||||
log.error("No valid directories to monitor - FileMonitor will not function");
|
||||
}
|
||||
log.info("Monitoring directory: {}", runtimePathConfig.getPipelineWatchedFoldersPath());
|
||||
this.rootDir = Path.of(runtimePathConfig.getPipelineWatchedFoldersPath());
|
||||
}
|
||||
|
||||
private boolean shouldNotProcess(Path path) {
|
||||
@@ -105,15 +85,13 @@ public class FileMonitor {
|
||||
readyForProcessingFiles.clear();
|
||||
|
||||
if (path2KeyMapping.isEmpty()) {
|
||||
log.warn("Not monitoring any directories; attempting to re-register root paths.");
|
||||
for (Path rootDir : rootDirs) {
|
||||
if (Files.exists(
|
||||
rootDir)) { // if the root directory exists, re-register the root directory
|
||||
try {
|
||||
recursivelyRegisterEntry(rootDir);
|
||||
} catch (IOException e) {
|
||||
log.error("unable to register monitoring for {}", rootDir, e);
|
||||
}
|
||||
log.warn("not monitoring any directory, even the root directory itself: {}", rootDir);
|
||||
if (Files.exists(
|
||||
rootDir)) { // if the root directory exists, re-register the root directory
|
||||
try {
|
||||
recursivelyRegisterEntry(rootDir);
|
||||
} catch (IOException e) {
|
||||
log.error("unable to register monitoring", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,6 +337,33 @@ public class GeneralUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a URL is safe for server-side fetches without making a network request.
|
||||
*
|
||||
* @param urlStr the URL to validate
|
||||
* @return {@code true} if the URL uses http/https and resolves to a non-sensitive address
|
||||
*/
|
||||
public boolean isUrlAllowedForServerFetch(String urlStr) {
|
||||
if (urlStr == null || urlStr.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
URL url = URI.create(urlStr).toURL();
|
||||
String protocol = url.getProtocol();
|
||||
if (!"http".equals(protocol) && !"https".equals(protocol)) {
|
||||
return false;
|
||||
}
|
||||
String host = url.getHost();
|
||||
if (host == null || host.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
return !isDisallowedNetworkLocation(host);
|
||||
} catch (Exception e) {
|
||||
log.debug("URL {} is not allowed for server fetch: {}", urlStr, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the specified host resolves to a disallowed network location, such as
|
||||
* local, private, multicast, or reserved ranges. Excessive DNS results are also blocked.
|
||||
@@ -567,6 +594,7 @@ public class GeneralUtils {
|
||||
default -> toBytes(value, 2); // Default to MB
|
||||
};
|
||||
}
|
||||
return bytes;
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("Failed to parse size string '{}': {}", sizeStr, e.getMessage());
|
||||
return null;
|
||||
@@ -613,6 +641,44 @@ public class GeneralUtils {
|
||||
return unit != null && VALID_SIZE_UNITS.contains(unit.toUpperCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
private Long parseSizeToBytes(String sizeStr, String defaultUnit) {
|
||||
if (sizeStr.endsWith("TB")) {
|
||||
return convertDecimalToBytes(sizeStr.substring(0, sizeStr.length() - 2), 1024L * 1024L
|
||||
* 1024L * 1024L);
|
||||
} else if (sizeStr.endsWith("GB")) {
|
||||
return convertDecimalToBytes(
|
||||
sizeStr.substring(0, sizeStr.length() - 2), 1024L * 1024L * 1024L);
|
||||
} else if (sizeStr.endsWith("MB")) {
|
||||
return convertDecimalToBytes(sizeStr.substring(0, sizeStr.length() - 2), 1024L * 1024L);
|
||||
} else if (sizeStr.endsWith("KB")) {
|
||||
return convertDecimalToBytes(sizeStr.substring(0, sizeStr.length() - 2), 1024L);
|
||||
} else if (!sizeStr.isEmpty() && sizeStr.charAt(sizeStr.length() - 1) == 'B') {
|
||||
return convertDecimalToBytes(sizeStr.substring(0, sizeStr.length() - 1), 1L);
|
||||
}
|
||||
// Use provided default unit or fall back to MB
|
||||
String unit = defaultUnit != null ? defaultUnit.toUpperCase(Locale.ROOT) : "MB";
|
||||
return switch (unit) {
|
||||
case "TB" -> convertDecimalToBytes(sizeStr, 1024L * 1024L * 1024L * 1024L);
|
||||
case "GB" -> convertDecimalToBytes(sizeStr, 1024L * 1024L * 1024L);
|
||||
case "MB" -> convertDecimalToBytes(sizeStr, 1024L * 1024L);
|
||||
case "KB" -> convertDecimalToBytes(sizeStr, 1024L);
|
||||
case "B" -> convertDecimalToBytes(sizeStr, 1L);
|
||||
default -> convertDecimalToBytes(sizeStr, 1024L * 1024L); // Default to MB
|
||||
};
|
||||
}
|
||||
|
||||
private Long convertDecimalToBytes(String value, long multiplier) {
|
||||
BigDecimal parsed = new BigDecimal(value);
|
||||
if (parsed.signum() < 0) {
|
||||
return null;
|
||||
}
|
||||
BigDecimal bytes = parsed.multiply(BigDecimal.valueOf(multiplier));
|
||||
if (bytes.compareTo(BigDecimal.valueOf(Long.MAX_VALUE)) > 0) {
|
||||
return null;
|
||||
}
|
||||
return bytes.setScale(0, RoundingMode.DOWN).longValue();
|
||||
}
|
||||
|
||||
/* Enhanced byte formatting with TB/PB support and better precision. */
|
||||
public String formatBytes(long bytes) {
|
||||
if (bytes < 0) {
|
||||
|
||||
@@ -9,7 +9,6 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -35,8 +34,7 @@ class FileMonitorTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
when(runtimePathConfig.getPipelineWatchedFoldersPaths())
|
||||
.thenReturn(List.of(tempDir.toString()));
|
||||
when(runtimePathConfig.getPipelineWatchedFoldersPath()).thenReturn(tempDir.toString());
|
||||
|
||||
// This mock is used in all tests except testPathFilter
|
||||
// We use lenient to avoid UnnecessaryStubbingException in that test
|
||||
|
||||
+18
-1
@@ -38,6 +38,13 @@ spotless {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
if (System.getenv('STIRLING_PDF_DESKTOP_UI') != 'false'
|
||||
|| (project.hasProperty('STIRLING_PDF_DESKTOP_UI')
|
||||
&& project.getProperty('STIRLING_PDF_DESKTOP_UI') != 'false')) {
|
||||
implementation 'org.openjfx:javafx-controls:21'
|
||||
implementation 'org.openjfx:javafx-swing:21'
|
||||
}
|
||||
|
||||
if (System.getenv('DISABLE_ADDITIONAL_FEATURES') != 'true'
|
||||
|| (project.hasProperty('DISABLE_ADDITIONAL_FEATURES')
|
||||
&& System.getProperty('DISABLE_ADDITIONAL_FEATURES') != 'true')) {
|
||||
@@ -108,8 +115,19 @@ sourceSets {
|
||||
resources {
|
||||
srcDirs += ['../configs']
|
||||
}
|
||||
java {
|
||||
if (System.getenv('STIRLING_PDF_DESKTOP_UI') == 'false') {
|
||||
exclude 'stirling/software/SPDF/UI/impl/**'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
if (System.getenv('STIRLING_PDF_DESKTOP_UI') == 'false') {
|
||||
exclude 'stirling/software/SPDF/UI/impl/**'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -221,7 +239,6 @@ tasks.register('npmBuild', Exec) {
|
||||
commandLine = Os.isFamily(Os.FAMILY_WINDOWS) ? ['cmd', '/c', 'npm', 'run', 'build'] : ['npm', 'run', 'build']
|
||||
dependsOn npmInstall
|
||||
inputs.dir(new File(frontendDir, 'src'))
|
||||
inputs.dir(new File(frontendDir, 'public'))
|
||||
inputs.file(new File(frontendDir, 'package.json'))
|
||||
outputs.dir(frontendDistDir)
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import io.github.pixee.security.SystemCommand;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@@ -27,6 +28,7 @@ import stirling.software.common.configuration.AppConfig;
|
||||
import stirling.software.common.configuration.ConfigInitializer;
|
||||
import stirling.software.common.configuration.InstallationPathConfig;
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.common.util.UrlUtils;
|
||||
|
||||
@Slf4j
|
||||
@EnableScheduling
|
||||
@@ -58,6 +60,19 @@ public class SPDFApplication {
|
||||
|
||||
Properties props = new Properties();
|
||||
|
||||
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
||||
System.setProperty("java.awt.headless", "false");
|
||||
app.setHeadless(false);
|
||||
props.put("java.awt.headless", "false");
|
||||
props.put("spring.main.web-application-type", "servlet");
|
||||
|
||||
int desiredPort = 8080;
|
||||
String port = UrlUtils.findAvailablePort(desiredPort);
|
||||
props.put("server.port", port);
|
||||
System.setProperty("server.port", port);
|
||||
log.info("Desktop UI mode: Using port {}", port);
|
||||
}
|
||||
|
||||
app.setAdditionalProfiles(getActiveProfile(args));
|
||||
|
||||
ConfigInitializer initializer = new ConfigInitializer();
|
||||
@@ -138,6 +153,13 @@ public class SPDFApplication {
|
||||
"Running in Tauri mode. Parent process PID: {}",
|
||||
parentPid != null ? parentPid : "not set");
|
||||
}
|
||||
// Desktop UI initialization removed - webBrowser dependency eliminated
|
||||
// Keep backwards compatibility for STIRLING_PDF_DESKTOP_UI system property
|
||||
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
||||
log.info("Desktop UI mode enabled, but WebBrowser functionality has been removed");
|
||||
// webBrowser.initWebUI(url); // Removed - desktop UI eliminated
|
||||
}
|
||||
|
||||
// Standard browser opening logic
|
||||
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
|
||||
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
||||
@@ -170,6 +192,14 @@ public class SPDFApplication {
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void cleanup() {
|
||||
// webBrowser cleanup removed - desktop UI eliminated
|
||||
// if (webBrowser != null) {
|
||||
// webBrowser.cleanup();
|
||||
// }
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void onWebServerInitialized(WebServerInitializedEvent event) {
|
||||
int actualPort = event.getWebServer().getPort();
|
||||
|
||||
@@ -118,8 +118,7 @@ public class ExternalAppDepConfig {
|
||||
|
||||
for (String group : affectedGroups) {
|
||||
List<String> affectedFeatures = getAffectedFeatures(group);
|
||||
endpointConfiguration.disableGroup(
|
||||
group, EndpointConfiguration.DisableReason.DEPENDENCY);
|
||||
endpointConfiguration.disableGroup(group);
|
||||
log.warn(
|
||||
"Missing dependency: {} - Disabling group: {} (Affected features: {})",
|
||||
command,
|
||||
@@ -144,8 +143,7 @@ public class ExternalAppDepConfig {
|
||||
commandToGroupMapping.getOrDefault(
|
||||
command, List.of("Weasyprint"));
|
||||
for (String group : affectedGroups) {
|
||||
endpointConfiguration.disableGroup(
|
||||
group, EndpointConfiguration.DisableReason.DEPENDENCY);
|
||||
endpointConfiguration.disableGroup(group);
|
||||
}
|
||||
log.warn(
|
||||
"WeasyPrint version {} is below required {} - disabling"
|
||||
@@ -174,8 +172,7 @@ public class ExternalAppDepConfig {
|
||||
List<String> affectedGroups =
|
||||
commandToGroupMapping.getOrDefault(command, List.of("qpdf"));
|
||||
for (String group : affectedGroups) {
|
||||
endpointConfiguration.disableGroup(
|
||||
group, EndpointConfiguration.DisableReason.DEPENDENCY);
|
||||
endpointConfiguration.disableGroup(group);
|
||||
}
|
||||
log.warn(
|
||||
"qpdf version {} is below required {} - disabling group(s): {}",
|
||||
@@ -229,8 +226,7 @@ public class ExternalAppDepConfig {
|
||||
int ec = runAndWait(List.of(python, "-c", "import cv2"), DEFAULT_TIMEOUT).exitCode();
|
||||
if (ec != 0) {
|
||||
List<String> openCVFeatures = getAffectedFeatures("OpenCV");
|
||||
endpointConfiguration.disableGroup(
|
||||
"OpenCV", EndpointConfiguration.DisableReason.DEPENDENCY);
|
||||
endpointConfiguration.disableGroup("OpenCV");
|
||||
log.warn(
|
||||
"OpenCV not available in Python - Disabling OpenCV features: {}",
|
||||
String.join(", ", openCVFeatures));
|
||||
@@ -240,10 +236,8 @@ public class ExternalAppDepConfig {
|
||||
private void disablePythonAndOpenCV(String reason) {
|
||||
List<String> pythonFeatures = getAffectedFeatures("Python");
|
||||
List<String> openCVFeatures = getAffectedFeatures("OpenCV");
|
||||
endpointConfiguration.disableGroup(
|
||||
"Python", EndpointConfiguration.DisableReason.DEPENDENCY);
|
||||
endpointConfiguration.disableGroup(
|
||||
"OpenCV", EndpointConfiguration.DisableReason.DEPENDENCY);
|
||||
endpointConfiguration.disableGroup("Python");
|
||||
endpointConfiguration.disableGroup("OpenCV");
|
||||
log.warn(
|
||||
"Missing dependency: Python (reason: {}) - Disabling Python features: {} and OpenCV"
|
||||
+ " features: {}",
|
||||
|
||||
@@ -106,29 +106,16 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
||||
.allowCredentials(true)
|
||||
.maxAge(3600);
|
||||
} else if (hasConfiguredOrigins) {
|
||||
// Use user-configured origins + always include Tauri origins for desktop app support
|
||||
// Use user-configured origins
|
||||
logger.info(
|
||||
"Configuring CORS with allowed origins: {}",
|
||||
applicationProperties.getSystem().getCorsAllowedOrigins());
|
||||
|
||||
// Combine user-configured origins with Tauri origins
|
||||
java.util.List<String> allOrigins =
|
||||
new java.util.ArrayList<>(
|
||||
applicationProperties.getSystem().getCorsAllowedOrigins());
|
||||
|
||||
// Always include Tauri origins for desktop app compatibility
|
||||
// Tauri v1 uses tauri://localhost, v2 uses http(s)://tauri.localhost
|
||||
if (!allOrigins.contains("tauri://localhost")) {
|
||||
allOrigins.add("tauri://localhost");
|
||||
}
|
||||
if (!allOrigins.contains("http://tauri.localhost")) {
|
||||
allOrigins.add("http://tauri.localhost");
|
||||
}
|
||||
if (!allOrigins.contains("https://tauri.localhost")) {
|
||||
allOrigins.add("https://tauri.localhost");
|
||||
}
|
||||
|
||||
String[] allowedOrigins = allOrigins.toArray(new String[0]);
|
||||
String[] allowedOrigins =
|
||||
applicationProperties
|
||||
.getSystem()
|
||||
.getCorsAllowedOrigins()
|
||||
.toArray(new String[0]);
|
||||
|
||||
registry.addMapping("/**")
|
||||
.allowedOriginPatterns(allowedOrigins)
|
||||
|
||||
+5
-5
@@ -46,18 +46,18 @@ public class EditTableOfContentsController {
|
||||
@Operation(
|
||||
summary = "Extract PDF Bookmarks",
|
||||
description = "Extracts bookmarks/table of contents from a PDF document as JSON.")
|
||||
public ResponseEntity<List<Map<String, Object>>> extractBookmarks(
|
||||
@RequestParam("file") MultipartFile file) throws Exception {
|
||||
@ResponseBody
|
||||
public List<Map<String, Object>> extractBookmarks(@RequestParam("file") MultipartFile file)
|
||||
throws Exception {
|
||||
try (PDDocument document = pdfDocumentFactory.load(file)) {
|
||||
PDDocumentOutline outline = document.getDocumentCatalog().getDocumentOutline();
|
||||
|
||||
if (outline == null) {
|
||||
log.info("No outline/bookmarks found in PDF");
|
||||
return ResponseEntity.ok(new ArrayList<>());
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
List<Map<String, Object>> bookmarks = extractBookmarkItems(document, outline);
|
||||
return ResponseEntity.ok(bookmarks);
|
||||
return extractBookmarkItems(document, outline);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -82,7 +82,13 @@ public class SplitPdfBySizeController {
|
||||
|
||||
if (type == 0) {
|
||||
log.debug("Processing split by size");
|
||||
long maxBytes = GeneralUtils.convertSizeToBytes(value);
|
||||
Long maxBytes = GeneralUtils.convertSizeToBytes(value);
|
||||
if (maxBytes == null || maxBytes <= 0) {
|
||||
throw ExceptionUtils.createIllegalArgumentException(
|
||||
"error.invalidArgument",
|
||||
"Invalid argument: {0}",
|
||||
"split value: " + value);
|
||||
}
|
||||
log.debug("Max bytes per document: {}", maxBytes);
|
||||
handleSplitBySize(sourceDocument, maxBytes, zipOut, filename);
|
||||
} else if (type == 1) {
|
||||
|
||||
+8
-13
@@ -479,22 +479,15 @@ public class ConvertPDFToPDFA {
|
||||
command.add("-dCompatibilityLevel=" + profile.getCompatibilityLevel());
|
||||
command.add("-sDEVICE=pdfwrite");
|
||||
command.add("-sColorConversionStrategy=RGB");
|
||||
command.add("-dProcessColorModel=/DeviceRGB");
|
||||
command.add("-sOutputICCProfile=" + colorProfiles.rgb().toAbsolutePath());
|
||||
command.add("-sDefaultRGBProfile=" + colorProfiles.rgb().toAbsolutePath());
|
||||
command.add("-sDefaultGrayProfile=" + colorProfiles.gray().toAbsolutePath());
|
||||
command.add("-dEmbedAllFonts=true");
|
||||
command.add("-dSubsetFonts=true");
|
||||
command.add("-dSubsetFonts=false"); // Embed complete fonts to avoid incomplete glyphs
|
||||
command.add("-dCompressFonts=true");
|
||||
command.add("-dNOSUBSTFONTS=false"); // Allow font substitution for problematic fonts
|
||||
|
||||
// Explicitly tune downsampling/compression for high-quality print
|
||||
command.add("-dColorImageDownsampleType=/Bicubic");
|
||||
command.add("-dColorImageResolution=300");
|
||||
command.add("-dGrayImageDownsampleType=/Bicubic");
|
||||
command.add("-dGrayImageResolution=300");
|
||||
command.add("-dMonoImageDownsampleType=/Bicubic");
|
||||
command.add("-dMonoImageResolution=1200");
|
||||
|
||||
command.add("-dPDFSETTINGS=/prepress");
|
||||
command.add("-dNOPAUSE");
|
||||
command.add("-dBATCH");
|
||||
command.add("-dNOOUTERSAVE");
|
||||
@@ -2452,7 +2445,9 @@ public class ConvertPDFToPDFA {
|
||||
|
||||
@Getter
|
||||
private enum PdfXProfile {
|
||||
PDF_X("PDF/X", "_PDFX.pdf", "1.6", "2008", "pdfx");
|
||||
PDF_X_1("PDF/X-1", "_PDFX-1.pdf", "1.3", "2001", "pdfx-1", "pdfx"),
|
||||
PDF_X_3("PDF/X-3", "_PDFX-3.pdf", "1.3", "2003", "pdfx-3"),
|
||||
PDF_X_4("PDF/X-4", "_PDFX-4.pdf", "1.4", "2008", "pdfx-4");
|
||||
|
||||
private final String displayName;
|
||||
private final String suffix;
|
||||
@@ -2478,7 +2473,7 @@ public class ConvertPDFToPDFA {
|
||||
|
||||
static PdfXProfile fromRequest(String requestToken) {
|
||||
if (requestToken == null) {
|
||||
return PDF_X;
|
||||
return PDF_X_4;
|
||||
}
|
||||
String normalized = requestToken.trim().toLowerCase(Locale.ROOT);
|
||||
Optional<PdfXProfile> match =
|
||||
@@ -2486,7 +2481,7 @@ public class ConvertPDFToPDFA {
|
||||
.filter(profile -> profile.requestTokens.contains(normalized))
|
||||
.findFirst();
|
||||
|
||||
return match.orElse(PDF_X);
|
||||
return match.orElse(PDF_X_4);
|
||||
}
|
||||
|
||||
String outputSuffix() {
|
||||
|
||||
+54
-16
@@ -54,6 +54,7 @@ public class ConvertWebsiteToPDF {
|
||||
Pattern.compile("(?<![a-z0-9_])file\\s*:(?:/{1,3}|%2f|%5c|%3a|/|/)");
|
||||
|
||||
private static final Pattern NUMERIC_HTML_ENTITY_PATTERN = Pattern.compile("&#(x?[0-9a-f]+);");
|
||||
private static final int MAX_REDIRECTS = 5;
|
||||
|
||||
@AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/url/pdf")
|
||||
@Operation(
|
||||
@@ -86,6 +87,12 @@ public class ConvertWebsiteToPDF {
|
||||
.queryParam("error", "error.invalidUrlFormat")
|
||||
.build()
|
||||
.toUri();
|
||||
} else if (!GeneralUtils.isUrlAllowedForServerFetch(URL)) {
|
||||
location =
|
||||
uriComponentsBuilder
|
||||
.queryParam("error", "error.invalidUrlFormat")
|
||||
.build()
|
||||
.toUri();
|
||||
} else if (!GeneralUtils.isURLReachable(URL)) {
|
||||
// validate the URL is reachable
|
||||
location =
|
||||
@@ -166,29 +173,60 @@ public class ConvertWebsiteToPDF {
|
||||
private String fetchRemoteHtml(String url) throws IOException, InterruptedException {
|
||||
HttpClient client =
|
||||
HttpClient.newBuilder()
|
||||
.followRedirects(HttpClient.Redirect.NORMAL)
|
||||
.followRedirects(HttpClient.Redirect.NEVER)
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
|
||||
HttpRequest request =
|
||||
HttpRequest.newBuilder(URI.create(url))
|
||||
.timeout(Duration.ofSeconds(20))
|
||||
.GET()
|
||||
.header("User-Agent", "Stirling-PDF/URL-to-PDF")
|
||||
.build();
|
||||
URI currentUri = URI.create(url);
|
||||
for (int redirectCount = 0; redirectCount <= MAX_REDIRECTS; redirectCount++) {
|
||||
String currentUrl = currentUri.toString();
|
||||
if (!GeneralUtils.isUrlAllowedForServerFetch(currentUrl)) {
|
||||
throw ExceptionUtils.createIOException(
|
||||
"error.invalidUrlFormat",
|
||||
"Disallowed URL target: {0}",
|
||||
null,
|
||||
currentUrl);
|
||||
}
|
||||
|
||||
HttpResponse<String> response =
|
||||
client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||
HttpRequest request =
|
||||
HttpRequest.newBuilder(currentUri)
|
||||
.timeout(Duration.ofSeconds(20))
|
||||
.GET()
|
||||
.header("User-Agent", "Stirling-PDF/URL-to-PDF")
|
||||
.build();
|
||||
|
||||
if (response.statusCode() >= 400 || response.body() == null) {
|
||||
throw ExceptionUtils.createIOException(
|
||||
"error.httpRequestFailed",
|
||||
"Failed to retrieve remote HTML. Status: {0}",
|
||||
null,
|
||||
response.statusCode());
|
||||
HttpResponse<String> response =
|
||||
client.send(
|
||||
request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||
|
||||
int statusCode = response.statusCode();
|
||||
if (statusCode >= 300 && statusCode < 400) {
|
||||
String location = response.headers().firstValue("location").orElse(null);
|
||||
if (location == null || location.isBlank()) {
|
||||
throw ExceptionUtils.createIOException(
|
||||
"error.httpRequestFailed",
|
||||
"Redirect response missing Location header",
|
||||
null);
|
||||
}
|
||||
currentUri = currentUri.resolve(location);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (statusCode >= 400 || response.body() == null) {
|
||||
throw ExceptionUtils.createIOException(
|
||||
"error.httpRequestFailed",
|
||||
"Failed to retrieve remote HTML. Status: {0}",
|
||||
null,
|
||||
statusCode);
|
||||
}
|
||||
|
||||
return response.body();
|
||||
}
|
||||
|
||||
return response.body();
|
||||
throw ExceptionUtils.createIOException(
|
||||
"error.httpRequestFailed",
|
||||
"Too many redirects while retrieving remote HTML",
|
||||
null);
|
||||
}
|
||||
|
||||
private boolean containsDisallowedUriScheme(String htmlContent) {
|
||||
|
||||
+6
@@ -948,6 +948,12 @@ public class CompressController {
|
||||
boolean autoMode = false;
|
||||
if (expectedOutputSizeString != null && expectedOutputSizeString.length() > 1) {
|
||||
expectedOutputSize = GeneralUtils.convertSizeToBytes(expectedOutputSizeString);
|
||||
if (expectedOutputSize == null || expectedOutputSize <= 0) {
|
||||
throw ExceptionUtils.createIllegalArgumentException(
|
||||
"error.invalidArgument",
|
||||
"Invalid argument: {0}",
|
||||
"expected output size: " + expectedOutputSizeString);
|
||||
}
|
||||
autoMode = true;
|
||||
}
|
||||
|
||||
|
||||
-10
@@ -112,16 +112,6 @@ public class ConfigController {
|
||||
"showSettingsWhenNoLogin",
|
||||
applicationProperties.getSystem().isShowSettingsWhenNoLogin());
|
||||
|
||||
// SSO Provider settings
|
||||
boolean enableOAuth =
|
||||
applicationProperties.getSecurity().getOauth2() != null
|
||||
&& applicationProperties.getSecurity().getOauth2().getEnabled();
|
||||
boolean enableSaml =
|
||||
applicationProperties.getSecurity().getSaml2() != null
|
||||
&& applicationProperties.getSecurity().getSaml2().getEnabled();
|
||||
configData.put("enableOAuth", enableOAuth);
|
||||
configData.put("enableSaml", enableSaml);
|
||||
|
||||
// Mail settings - check both SMTP enabled AND invites enabled
|
||||
boolean smtpEnabled = applicationProperties.getMail().isEnabled();
|
||||
boolean invitesEnabled = applicationProperties.getMail().isEnableInvites();
|
||||
|
||||
+1
-10
@@ -52,17 +52,9 @@ public class PageNumbersController {
|
||||
int pageNumber = request.getStartingNumber();
|
||||
String pagesToNumber = request.getPagesToNumber();
|
||||
String customText = request.getCustomText();
|
||||
int zeroPad = request.getZeroPad();
|
||||
float fontSize = request.getFontSize();
|
||||
String fontType = request.getFontType();
|
||||
String fontColor = request.getFontColor();
|
||||
// compute padded number string where requested
|
||||
String formatN;
|
||||
if (zeroPad > 0) {
|
||||
formatN = String.format("%%0%dd", Math.max(0, zeroPad));
|
||||
} else {
|
||||
formatN = "%d";
|
||||
}
|
||||
|
||||
Color color = Color.BLACK;
|
||||
if (fontColor != null && !fontColor.trim().isEmpty()) {
|
||||
@@ -101,10 +93,9 @@ public class PageNumbersController {
|
||||
PDPage page = document.getPage(i);
|
||||
PDRectangle pageSize = page.getMediaBox();
|
||||
|
||||
String nFormatted = String.format(formatN, pageNumber);
|
||||
String text =
|
||||
customText
|
||||
.replace("{n}", nFormatted)
|
||||
.replace("{n}", String.valueOf(pageNumber))
|
||||
.replace("{total}", String.valueOf(document.getNumberOfPages()))
|
||||
.replace(
|
||||
"{filename}",
|
||||
|
||||
+57
-198
@@ -7,13 +7,11 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
@@ -60,13 +58,6 @@ public class StampController {
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final TempFileManager tempFileManager;
|
||||
|
||||
private static final int MAX_DATE_FORMAT_LENGTH = 50;
|
||||
private static final Pattern SAFE_DATE_FORMAT_PATTERN =
|
||||
Pattern.compile("^[yMdHhmsS/\\-:\\s.,'+EGuwWDFzZXa]+$");
|
||||
private static final Pattern CUSTOM_DATE_PATTERN = Pattern.compile("@date\\{([^}]{1,50})\\}");
|
||||
// Placeholder for escaped @ symbol (using Unicode private use area)
|
||||
private static final String ESCAPED_AT_PLACEHOLDER = "\uE000ESCAPED_AT\uE000";
|
||||
|
||||
/**
|
||||
* Initialize data binder for multipart file uploads. This method registers a custom editor for
|
||||
* MultipartFile to handle file uploads. It sets the MultipartFile to null if the uploaded file
|
||||
@@ -175,9 +166,7 @@ public class StampController {
|
||||
overrideX,
|
||||
overrideY,
|
||||
margin,
|
||||
customColor,
|
||||
pageIndex,
|
||||
pdfFileName);
|
||||
customColor);
|
||||
} else if ("image".equalsIgnoreCase(stampType)) {
|
||||
addImageStamp(
|
||||
contentStream,
|
||||
@@ -212,11 +201,9 @@ public class StampController {
|
||||
float fontSize,
|
||||
String alphabet,
|
||||
float overrideX, // X override
|
||||
float overrideY, // Y override
|
||||
float overrideY,
|
||||
float margin,
|
||||
String colorString,
|
||||
int currentPageNumber,
|
||||
String filename)
|
||||
String colorString) // Y override
|
||||
throws IOException {
|
||||
String resourceDir;
|
||||
PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
|
||||
@@ -244,6 +231,8 @@ public class StampController {
|
||||
}
|
||||
}
|
||||
|
||||
contentStream.setFont(font, fontSize);
|
||||
|
||||
Color redactColor;
|
||||
try {
|
||||
if (!colorString.startsWith("#")) {
|
||||
@@ -251,54 +240,45 @@ public class StampController {
|
||||
}
|
||||
redactColor = Color.decode(colorString);
|
||||
} catch (NumberFormatException e) {
|
||||
|
||||
redactColor = Color.LIGHT_GRAY;
|
||||
}
|
||||
|
||||
contentStream.setNonStrokingColor(redactColor);
|
||||
|
||||
PDRectangle pageSize = page.getMediaBox();
|
||||
float x, y;
|
||||
|
||||
if (overrideX >= 0 && overrideY >= 0) {
|
||||
// Use override values if provided
|
||||
x = overrideX;
|
||||
y = overrideY;
|
||||
} else {
|
||||
x = calculatePositionX(pageSize, position, fontSize, font, fontSize, stampText, margin);
|
||||
y =
|
||||
calculatePositionY(
|
||||
pageSize, position, calculateTextCapHeight(font, fontSize), margin);
|
||||
}
|
||||
|
||||
String currentDate = LocalDate.now().toString();
|
||||
String currentTime = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
|
||||
|
||||
int pageCount = document.getNumberOfPages();
|
||||
|
||||
String processedStampText =
|
||||
processStampText(stampText, currentPageNumber, pageCount, filename, document);
|
||||
stampText
|
||||
.replace("@date", currentDate)
|
||||
.replace("@time", currentTime)
|
||||
.replace("@page_count", String.valueOf(pageCount));
|
||||
|
||||
String normalizedText =
|
||||
RegexPatternUtils.getInstance()
|
||||
.getEscapedNewlinePattern()
|
||||
.matcher(processedStampText)
|
||||
.replaceAll("\n");
|
||||
String[] lines = normalizedText.split("\\r?\\n");
|
||||
|
||||
PDRectangle pageSize = page.getMediaBox();
|
||||
|
||||
// Use fontSize directly (default 40 if not specified)
|
||||
float effectiveFontSize = fontSize > 0 ? fontSize : 40f;
|
||||
|
||||
contentStream.setFont(font, effectiveFontSize);
|
||||
// Split the stampText into multiple lines
|
||||
String[] lines =
|
||||
RegexPatternUtils.getInstance().getEscapedNewlinePattern().split(stampText);
|
||||
|
||||
// Calculate dynamic line height based on font ascent and descent
|
||||
float ascent = font.getFontDescriptor().getAscent();
|
||||
float descent = font.getFontDescriptor().getDescent();
|
||||
float lineHeight = ((ascent - descent) / 1000) * effectiveFontSize;
|
||||
|
||||
float maxLineWidth = 0;
|
||||
for (String line : lines) {
|
||||
float lineWidth = calculateTextWidth(line, font, effectiveFontSize);
|
||||
if (lineWidth > maxLineWidth) {
|
||||
maxLineWidth = lineWidth;
|
||||
}
|
||||
}
|
||||
|
||||
float totalTextHeight = lines.length * lineHeight;
|
||||
|
||||
float x, y;
|
||||
|
||||
if (overrideX >= 0 && overrideY >= 0) {
|
||||
x = overrideX;
|
||||
y = overrideY;
|
||||
} else {
|
||||
x = calculatePositionX(pageSize, position, maxLineWidth, margin);
|
||||
y = calculatePositionY(pageSize, position, totalTextHeight, margin);
|
||||
}
|
||||
float lineHeight = ((ascent - descent) / 1000) * fontSize;
|
||||
|
||||
contentStream.beginText();
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
@@ -311,140 +291,6 @@ public class StampController {
|
||||
contentStream.endText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process stamp text by replacing all @commands with their actual values. Supported commands:
|
||||
*
|
||||
* <p>Date & Time:
|
||||
*
|
||||
* <ul>
|
||||
* <li>@date - Current date (YYYY-MM-DD)
|
||||
* <li>@time - Current time (HH:mm:ss)
|
||||
* <li>@datetime - Current date and time (YYYY-MM-DD HH:mm:ss)
|
||||
* <li>@date{format} - Custom date/time format (e.g., @date{dd/MM/yyyy})
|
||||
* <li>@year - Current year (4 digits)
|
||||
* <li>@month - Current month (01-12)
|
||||
* <li>@day - Current day of month (01-31)
|
||||
* </ul>
|
||||
*
|
||||
* <p>Page Information:
|
||||
*
|
||||
* <ul>
|
||||
* <li>@page_number or @page - Current page number
|
||||
* <li>@total_pages or @page_count - Total number of pages
|
||||
* </ul>
|
||||
*
|
||||
* <p>File Information:
|
||||
*
|
||||
* <ul>
|
||||
* <li>@filename - Original filename (without extension)
|
||||
* <li>@filename_full - Original filename (with extension)
|
||||
* </ul>
|
||||
*
|
||||
* <p>Document Metadata:
|
||||
*
|
||||
* <ul>
|
||||
* <li>@author - Document author (from PDF metadata)
|
||||
* <li>@title - Document title (from PDF metadata)
|
||||
* <li>@subject - Document subject (from PDF metadata)
|
||||
* </ul>
|
||||
*
|
||||
* <p>Other:
|
||||
*
|
||||
* <ul>
|
||||
* <li>@uuid - Short unique identifier (8 characters)
|
||||
* </ul>
|
||||
*/
|
||||
private String processStampText(
|
||||
String stampText,
|
||||
int currentPageNumber,
|
||||
int totalPages,
|
||||
String filename,
|
||||
PDDocument document) {
|
||||
if (stampText == null || stampText.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Handle escaped @@ sequences first - replace with placeholder to preserve literal @
|
||||
String result = stampText.replace("@@", ESCAPED_AT_PLACEHOLDER);
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
String currentDate = now.toLocalDate().toString();
|
||||
String currentTime = now.toLocalTime().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
|
||||
String currentDateTime = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
|
||||
String filenameWithoutExt = filename != null ? filename : "";
|
||||
if (filename != null && filename.contains(".")) {
|
||||
int lastDot = filename.lastIndexOf('.');
|
||||
if (lastDot > 0) { // Ensure there's actually a name before the dot
|
||||
filenameWithoutExt = filename.substring(0, lastDot);
|
||||
}
|
||||
}
|
||||
|
||||
String author = "";
|
||||
String title = "";
|
||||
String subject = "";
|
||||
if (document != null && document.getDocumentInformation() != null) {
|
||||
var info = document.getDocumentInformation();
|
||||
author = info.getAuthor() != null ? info.getAuthor() : "";
|
||||
title = info.getTitle() != null ? info.getTitle() : "";
|
||||
subject = info.getSubject() != null ? info.getSubject() : "";
|
||||
}
|
||||
|
||||
String uuid = UUID.randomUUID().toString().substring(0, 8);
|
||||
|
||||
// Process @date{format} with custom format first (must be before simple @date)
|
||||
Matcher matcher = CUSTOM_DATE_PATTERN.matcher(result);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (matcher.find()) {
|
||||
String format = matcher.group(1);
|
||||
String replacement = processCustomDateFormat(format, now);
|
||||
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
|
||||
}
|
||||
matcher.appendTail(sb);
|
||||
result = sb.toString();
|
||||
|
||||
result =
|
||||
result.replace("@datetime", currentDateTime)
|
||||
.replace("@date", currentDate)
|
||||
.replace("@time", currentTime)
|
||||
.replace("@year", String.valueOf(now.getYear()))
|
||||
.replace("@month", String.format("%02d", now.getMonthValue()))
|
||||
.replace("@day", String.format("%02d", now.getDayOfMonth()))
|
||||
.replace("@page_number", String.valueOf(currentPageNumber))
|
||||
.replace(
|
||||
"@page_count", String.valueOf(totalPages)) // Must come before @page
|
||||
.replace("@total_pages", String.valueOf(totalPages))
|
||||
.replace(
|
||||
"@page",
|
||||
String.valueOf(currentPageNumber)) // Must come after @page_count
|
||||
.replace("@filename_full", filename != null ? filename : "")
|
||||
.replace("@filename", filenameWithoutExt)
|
||||
.replace("@author", author)
|
||||
.replace("@title", title)
|
||||
.replace("@subject", subject)
|
||||
.replace("@uuid", uuid);
|
||||
|
||||
result = result.replace(ESCAPED_AT_PLACEHOLDER, "@");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private String processCustomDateFormat(String format, LocalDateTime now) {
|
||||
if (format == null || format.length() > MAX_DATE_FORMAT_LENGTH) {
|
||||
return "[invalid format: too long]";
|
||||
}
|
||||
|
||||
if (!SAFE_DATE_FORMAT_PATTERN.matcher(format).matches()) {
|
||||
return "[invalid format]";
|
||||
}
|
||||
|
||||
try {
|
||||
return now.format(DateTimeFormatter.ofPattern(format));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return "[invalid format: " + format + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private void addImageStamp(
|
||||
PDPageContentStream contentStream,
|
||||
MultipartFile stampImage,
|
||||
@@ -481,8 +327,8 @@ public class StampController {
|
||||
x = overrideX;
|
||||
y = overrideY;
|
||||
} else {
|
||||
x = calculatePositionX(pageSize, position, desiredPhysicalWidth, margin);
|
||||
y = calculatePositionY(pageSize, position, desiredPhysicalHeight, margin);
|
||||
x = calculatePositionX(pageSize, position, desiredPhysicalWidth, null, 0, null, margin);
|
||||
y = calculatePositionY(pageSize, position, fontSize, margin);
|
||||
}
|
||||
|
||||
contentStream.saveGraphicsState();
|
||||
@@ -493,14 +339,23 @@ public class StampController {
|
||||
}
|
||||
|
||||
private float calculatePositionX(
|
||||
PDRectangle pageSize, int position, float contentWidth, float margin) {
|
||||
PDRectangle pageSize,
|
||||
int position,
|
||||
float contentWidth,
|
||||
PDFont font,
|
||||
float fontSize,
|
||||
String text,
|
||||
float margin)
|
||||
throws IOException {
|
||||
float actualWidth =
|
||||
(text != null) ? calculateTextWidth(text, font, fontSize) : contentWidth;
|
||||
return switch (position % 3) {
|
||||
case 1: // Left
|
||||
yield pageSize.getLowerLeftX() + margin;
|
||||
case 2: // Center
|
||||
yield (pageSize.getWidth() - contentWidth) / 2;
|
||||
yield (pageSize.getWidth() - actualWidth) / 2;
|
||||
case 0: // Right
|
||||
yield pageSize.getUpperRightX() - contentWidth - margin;
|
||||
yield pageSize.getUpperRightX() - actualWidth - margin;
|
||||
default:
|
||||
yield 0;
|
||||
};
|
||||
@@ -509,12 +364,12 @@ public class StampController {
|
||||
private float calculatePositionY(
|
||||
PDRectangle pageSize, int position, float height, float margin) {
|
||||
return switch ((position - 1) / 3) {
|
||||
case 0: // Top - first line near the top
|
||||
yield pageSize.getUpperRightY() - margin;
|
||||
case 1: // Middle - center of text block at page center
|
||||
yield (pageSize.getHeight() + height) / 2;
|
||||
case 2: // Bottom - first line positioned so last line is at bottom margin
|
||||
yield pageSize.getLowerLeftY() + margin + height;
|
||||
case 0: // Top
|
||||
yield pageSize.getUpperRightY() - height - margin;
|
||||
case 1: // Middle
|
||||
yield (pageSize.getHeight() - height) / 2;
|
||||
case 2: // Bottom
|
||||
yield pageSize.getLowerLeftY() + margin;
|
||||
default:
|
||||
yield 0;
|
||||
};
|
||||
@@ -523,4 +378,8 @@ public class StampController {
|
||||
private float calculateTextWidth(String text, PDFont font, float fontSize) throws IOException {
|
||||
return font.getStringWidth(text) / 1000 * fontSize;
|
||||
}
|
||||
|
||||
private float calculateTextCapHeight(PDFont font, float fontSize) {
|
||||
return font.getFontDescriptor().getCapHeight() / 1000 * fontSize;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-59
@@ -3,7 +3,6 @@ package stirling.software.SPDF.controller.api.pipeline;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystemException;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -15,7 +14,6 @@ import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -43,20 +41,14 @@ import stirling.software.common.util.FileMonitor;
|
||||
@Slf4j
|
||||
public class PipelineDirectoryProcessor {
|
||||
|
||||
private static final int MAX_DIRECTORY_DEPTH = 50; // Prevent excessive recursion
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
private final ApiDocService apiDocService;
|
||||
private final PipelineProcessor processor;
|
||||
private final FileMonitor fileMonitor;
|
||||
private final PostHogService postHogService;
|
||||
private final List<String> watchedFoldersDirs;
|
||||
private final String watchedFoldersDir;
|
||||
private final String finishedFoldersDir;
|
||||
|
||||
// Track processed directories in current scan to prevent duplicates
|
||||
private final ThreadLocal<java.util.Set<Path>> processedDirsInScan =
|
||||
ThreadLocal.withInitial(java.util.HashSet::new);
|
||||
|
||||
public PipelineDirectoryProcessor(
|
||||
ObjectMapper objectMapper,
|
||||
ApiDocService apiDocService,
|
||||
@@ -69,26 +61,13 @@ public class PipelineDirectoryProcessor {
|
||||
this.processor = processor;
|
||||
this.fileMonitor = fileMonitor;
|
||||
this.postHogService = postHogService;
|
||||
this.watchedFoldersDirs = runtimePathConfig.getPipelineWatchedFoldersPaths();
|
||||
this.watchedFoldersDir = runtimePathConfig.getPipelineWatchedFoldersPath();
|
||||
this.finishedFoldersDir = runtimePathConfig.getPipelineFinishedFoldersPath();
|
||||
}
|
||||
|
||||
@Scheduled(fixedRate = 60000)
|
||||
public void scanFolders() {
|
||||
// Clear the processed directories set for this scan cycle
|
||||
processedDirsInScan.get().clear();
|
||||
|
||||
try {
|
||||
for (String watchedFoldersDir : watchedFoldersDirs) {
|
||||
scanWatchedFolder(Paths.get(watchedFoldersDir).toAbsolutePath());
|
||||
}
|
||||
} finally {
|
||||
// Clean up ThreadLocal to prevent memory leaks
|
||||
processedDirsInScan.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private void scanWatchedFolder(Path watchedFolderPath) {
|
||||
Path watchedFolderPath = Paths.get(watchedFoldersDir).toAbsolutePath();
|
||||
if (!Files.exists(watchedFolderPath)) {
|
||||
try {
|
||||
Files.createDirectories(watchedFolderPath);
|
||||
@@ -99,34 +78,16 @@ public class PipelineDirectoryProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the path is a directory and readable
|
||||
if (!Files.isDirectory(watchedFolderPath)) {
|
||||
log.error("Path is not a directory: {}", watchedFolderPath);
|
||||
return;
|
||||
}
|
||||
if (!Files.isReadable(watchedFolderPath)) {
|
||||
log.error("Directory is not readable: {}", watchedFolderPath);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Use FOLLOW_LINKS to follow symlinks, with max depth to prevent infinite loops
|
||||
Files.walkFileTree(
|
||||
watchedFolderPath,
|
||||
EnumSet.of(FileVisitOption.FOLLOW_LINKS),
|
||||
MAX_DIRECTORY_DEPTH,
|
||||
new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(
|
||||
Path dir, BasicFileAttributes attrs) {
|
||||
try {
|
||||
String dirName =
|
||||
dir.getFileName() != null
|
||||
? dir.getFileName().toString()
|
||||
: "";
|
||||
// Skip root directory and "processing" subdirectories
|
||||
if (!dir.equals(watchedFolderPath)
|
||||
&& !"processing".equals(dirName)) {
|
||||
if (!dir.equals(watchedFolderPath) && !dir.endsWith("processing")) {
|
||||
handleDirectory(dir);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -137,11 +98,8 @@ public class PipelineDirectoryProcessor {
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFileFailed(Path path, IOException exc) {
|
||||
// Handle broken symlinks, permission issues, or inaccessible
|
||||
// directories
|
||||
if (exc != null) {
|
||||
log.debug("Cannot access path '{}': {}", path, exc.getMessage());
|
||||
}
|
||||
// Handle broken symlinks or inaccessible directories
|
||||
log.error("Error accessing path: {}", path, exc);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
@@ -151,17 +109,6 @@ public class PipelineDirectoryProcessor {
|
||||
}
|
||||
|
||||
public void handleDirectory(Path dir) throws IOException {
|
||||
// Normalize path to absolute to prevent duplicate processing from different path
|
||||
// representations
|
||||
Path normalizedDir = dir.toAbsolutePath().normalize();
|
||||
|
||||
// Check if we've already processed this directory in this scan cycle
|
||||
java.util.Set<Path> processedDirs = processedDirsInScan.get();
|
||||
if (!processedDirs.add(normalizedDir)) {
|
||||
log.debug("Directory already processed in this scan cycle: {}", normalizedDir);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Handling directory: {}", dir);
|
||||
Path processingDir = createProcessingDirectory(dir);
|
||||
Optional<Path> jsonFileOptional = findJsonFile(dir);
|
||||
|
||||
+271
-120
@@ -3,15 +3,21 @@ package stirling.software.SPDF.controller.api.security;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.pdfbox.cos.COSInputStream;
|
||||
import org.apache.pdfbox.cos.COSName;
|
||||
import org.apache.pdfbox.cos.COSString;
|
||||
import org.apache.pdfbox.io.RandomAccessRead;
|
||||
import org.apache.pdfbox.io.RandomAccessReadBufferedFile;
|
||||
import org.apache.pdfbox.pdmodel.*;
|
||||
import org.apache.pdfbox.pdmodel.common.PDMetadata;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
@@ -29,7 +35,8 @@ import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentGroup;
|
||||
import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentProperties;
|
||||
import org.apache.pdfbox.pdmodel.interactive.action.*;
|
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
|
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationFileAttachment;
|
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
|
||||
@@ -37,8 +44,14 @@ import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlin
|
||||
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineNode;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
||||
import org.apache.pdfbox.preflight.PreflightDocument;
|
||||
import org.apache.pdfbox.preflight.ValidationResult;
|
||||
import org.apache.pdfbox.preflight.exception.SyntaxValidationException;
|
||||
import org.apache.pdfbox.preflight.exception.ValidationException;
|
||||
import org.apache.pdfbox.preflight.parser.PreflightParser;
|
||||
import org.apache.pdfbox.text.PDFTextStripper;
|
||||
import org.apache.xmpbox.XMPMetadata;
|
||||
import org.apache.xmpbox.schema.PDFAIdentificationSchema;
|
||||
import org.apache.xmpbox.xml.DomXmpParser;
|
||||
import org.apache.xmpbox.xml.XmpParsingException;
|
||||
import org.apache.xmpbox.xml.XmpSerializer;
|
||||
@@ -56,8 +69,6 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.SPDF.model.api.security.PDFVerificationResult;
|
||||
import stirling.software.SPDF.service.VeraPDFService;
|
||||
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||
import stirling.software.common.annotations.api.SecurityApi;
|
||||
import stirling.software.common.model.api.PDFFile;
|
||||
@@ -80,7 +91,6 @@ public class GetInfoOnPDF {
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
private final VeraPDFService veraPDFService;
|
||||
|
||||
private static void addOutlinesToArray(PDOutlineItem outline, ArrayNode arrayNode) {
|
||||
if (outline == null) return;
|
||||
@@ -97,8 +107,214 @@ public class GetInfoOnPDF {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean checkForStandard(PDDocument document, String standardKeyword) {
|
||||
if ("PDF/A".equalsIgnoreCase(standardKeyword)) {
|
||||
return getPdfAConformanceLevel(document) != null;
|
||||
}
|
||||
|
||||
return checkStandardInMetadata(document, standardKeyword);
|
||||
}
|
||||
|
||||
public static String getPdfAConformanceLevel(PDDocument document) {
|
||||
if (document == null || document.isEncrypted()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getPdfAVersionFromMetadata(document);
|
||||
}
|
||||
|
||||
private static String getPdfAVersionFromMetadata(PDDocument document) {
|
||||
try {
|
||||
PDMetadata pdMetadata = document.getDocumentCatalog().getMetadata();
|
||||
if (pdMetadata != null) {
|
||||
try (COSInputStream metaStream = pdMetadata.createInputStream()) {
|
||||
DomXmpParser domXmpParser = new DomXmpParser();
|
||||
XMPMetadata xmpMeta = domXmpParser.parse(metaStream);
|
||||
|
||||
PDFAIdentificationSchema pdfId = xmpMeta.getPDFAIdentificationSchema();
|
||||
if (pdfId != null) {
|
||||
Integer part = pdfId.getPart();
|
||||
String conformance = pdfId.getConformance();
|
||||
|
||||
if (part != null && conformance != null) {
|
||||
return part + conformance.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
} else {
|
||||
try (COSInputStream rawStream = pdMetadata.createInputStream()) {
|
||||
byte[] metadataBytes = rawStream.readAllBytes();
|
||||
String rawMetadata = new String(metadataBytes, StandardCharsets.UTF_8);
|
||||
String extracted = extractPdfAVersionFromRawXml(rawMetadata);
|
||||
if (extracted != null) {
|
||||
return extracted;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (XmpParsingException e) {
|
||||
log.debug("XMP parsing failed, trying raw metadata search: {}", e.getMessage());
|
||||
try (COSInputStream metaStream = pdMetadata.createInputStream()) {
|
||||
byte[] metadataBytes = metaStream.readAllBytes();
|
||||
String rawMetadata = new String(metadataBytes, StandardCharsets.UTF_8);
|
||||
String extracted = extractPdfAVersionFromRawXml(rawMetadata);
|
||||
if (extracted != null) {
|
||||
return extracted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("Error reading PDF/A metadata: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String extractPdfAVersionFromRawXml(String rawXml) {
|
||||
if (rawXml == null || rawXml.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
Pattern partPattern = RegexPatternUtils.getInstance().getPdfAidPartPattern();
|
||||
Pattern confPattern = RegexPatternUtils.getInstance().getPdfAidConformancePattern();
|
||||
|
||||
Matcher partMatcher = partPattern.matcher(rawXml);
|
||||
Matcher confMatcher = confPattern.matcher(rawXml);
|
||||
|
||||
if (partMatcher.find() && confMatcher.find()) {
|
||||
String part = partMatcher.group(1);
|
||||
String conformance = confMatcher.group(1).toUpperCase(Locale.ROOT);
|
||||
return part + conformance;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("Error parsing raw XMP for PDF/A version: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean validatePdfAWithPreflight(PDDocument document, String version) {
|
||||
if (document == null || document.isEncrypted()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use Stream-to-File pattern: save to temp file instead of loading into memory
|
||||
// This prevents OutOfMemoryError on large PDFs
|
||||
Path tempFile = null;
|
||||
try {
|
||||
tempFile = Files.createTempFile("preflight-", ".pdf");
|
||||
|
||||
// Save document to temp file (avoids loading entire document into memory)
|
||||
try (var outputStream = Files.newOutputStream(tempFile)) {
|
||||
document.save(outputStream);
|
||||
}
|
||||
|
||||
// Use RandomAccessReadBufferedFile for efficient file-based reading
|
||||
// This avoids Windows file locking issues that occur with memory-mapped files
|
||||
try (RandomAccessRead source = new RandomAccessReadBufferedFile(tempFile.toFile())) {
|
||||
PreflightParser parser = new PreflightParser(source);
|
||||
|
||||
try (PDDocument parsedDocument = parser.parse()) {
|
||||
if (!(parsedDocument instanceof PreflightDocument preflightDocument)) {
|
||||
log.debug(
|
||||
"Parsed document is not a PreflightDocument; unable to validate claimed PDF/A {}",
|
||||
version);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
ValidationResult result = preflightDocument.validate();
|
||||
if (!result.isValid() && log.isDebugEnabled()) {
|
||||
log.debug(
|
||||
"PDF/A validation found {} errors for claimed version {}",
|
||||
result.getErrorsList().size(),
|
||||
version);
|
||||
int logged = 0;
|
||||
for (ValidationResult.ValidationError error : result.getErrorsList()) {
|
||||
log.debug(
|
||||
" Error {}: {}", error.getErrorCode(), error.getDetails());
|
||||
if (++logged >= MAX_LOGGED_ERRORS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.isValid();
|
||||
} catch (ValidationException e) {
|
||||
log.debug(
|
||||
"Validation exception during PDF/A validation: {}", e.getMessage());
|
||||
}
|
||||
} catch (SyntaxValidationException e) {
|
||||
log.debug(
|
||||
"Syntax validation failed during PDF/A validation: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.debug("IOException during PDF/A validation: {}", e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.debug("Unexpected error during PDF/A validation: {}", e.getMessage());
|
||||
} finally {
|
||||
// Explicitly clean up temp file to prevent disk exhaustion
|
||||
// This must be in finally block to ensure cleanup even on exceptions
|
||||
if (tempFile != null) {
|
||||
try {
|
||||
Files.deleteIfExists(tempFile);
|
||||
} catch (IOException e) {
|
||||
log.warn(
|
||||
"Failed to delete temp file during PDF/A validation cleanup: {}",
|
||||
tempFile,
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean checkStandardInMetadata(PDDocument document, String standardKeyword) {
|
||||
// Check XMP Metadata
|
||||
try {
|
||||
PDMetadata pdMetadata = document.getDocumentCatalog().getMetadata();
|
||||
if (pdMetadata != null) {
|
||||
try (COSInputStream metaStream = pdMetadata.createInputStream()) {
|
||||
// First try to read raw metadata as string to check for standard keywords
|
||||
byte[] metadataBytes = metaStream.readAllBytes();
|
||||
String rawMetadata = new String(metadataBytes, StandardCharsets.UTF_8);
|
||||
|
||||
if (rawMetadata.contains(standardKeyword)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If raw check doesn't find it, try parsing with XMP parser
|
||||
try (COSInputStream metaStream = pdMetadata.createInputStream()) {
|
||||
try {
|
||||
DomXmpParser domXmpParser = new DomXmpParser();
|
||||
XMPMetadata xmpMeta = domXmpParser.parse(metaStream);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
new XmpSerializer().serialize(xmpMeta, baos, true);
|
||||
String xmpString = baos.toString(StandardCharsets.UTF_8);
|
||||
|
||||
if (xmpString.contains(standardKeyword)) {
|
||||
return true;
|
||||
}
|
||||
} catch (XmpParsingException e) {
|
||||
// XMP parsing failed, but we already checked raw metadata above
|
||||
log.debug(
|
||||
"XMP parsing failed for standard check, but raw metadata was already checked: {}",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ExceptionUtils.logException("PDF standard checking", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ObjectNode generatePDFSummaryData(
|
||||
PDDocument document, List<PDFVerificationResult> verificationResults) {
|
||||
PDDocument document, String pdfaConformanceLevel, Boolean pdfaValidationPassed) {
|
||||
ObjectNode summaryData = objectMapper.createObjectNode();
|
||||
|
||||
// Check if encrypted
|
||||
@@ -126,16 +342,24 @@ public class GetInfoOnPDF {
|
||||
}
|
||||
|
||||
// Check standard compliance
|
||||
if (verificationResults != null && !verificationResults.isEmpty()) {
|
||||
ArrayNode complianceArray = objectMapper.createArrayNode();
|
||||
for (PDFVerificationResult result : verificationResults) {
|
||||
ObjectNode complianceNode = objectMapper.createObjectNode();
|
||||
complianceNode.put("Standard", result.getStandard());
|
||||
complianceNode.put("Compliant", result.isCompliant());
|
||||
complianceNode.put("Summary", result.getComplianceSummary());
|
||||
complianceArray.add(complianceNode);
|
||||
if (pdfaConformanceLevel != null) {
|
||||
summaryData.put("standardCompliance", "PDF/A-" + pdfaConformanceLevel);
|
||||
summaryData.put("standardPurpose", "long-term archiving");
|
||||
if (pdfaValidationPassed != null) {
|
||||
summaryData.put("standardValidationPassed", pdfaValidationPassed);
|
||||
}
|
||||
summaryData.set("Compliance", complianceArray);
|
||||
} else if (checkForStandard(document, "PDF/X")) {
|
||||
summaryData.put("standardCompliance", "PDF/X");
|
||||
summaryData.put("standardPurpose", "graphic exchange");
|
||||
} else if (checkForStandard(document, "PDF/UA")) {
|
||||
summaryData.put("standardCompliance", "PDF/UA");
|
||||
summaryData.put("standardPurpose", "universal accessibility");
|
||||
} else if (checkForStandard(document, "PDF/E")) {
|
||||
summaryData.put("standardCompliance", "PDF/E");
|
||||
summaryData.put("standardPurpose", "engineering workflows");
|
||||
} else if (checkForStandard(document, "PDF/VT")) {
|
||||
summaryData.put("standardCompliance", "PDF/VT");
|
||||
summaryData.put("standardPurpose", "variable and transactional printing");
|
||||
}
|
||||
|
||||
return summaryData;
|
||||
@@ -363,113 +587,40 @@ public class GetInfoOnPDF {
|
||||
return docInfoNode;
|
||||
}
|
||||
|
||||
private static ObjectNode extractComplianceInfo(
|
||||
PDDocument doc, List<PDFVerificationResult> verificationResults) {
|
||||
private static ObjectNode extractComplianceInfo(PDDocument document) {
|
||||
ObjectNode compliancy = objectMapper.createObjectNode();
|
||||
|
||||
boolean isPdfA = false;
|
||||
boolean isPdfUA = false;
|
||||
boolean isPdfX = false;
|
||||
boolean isPdfE = false;
|
||||
boolean isPdfB = false;
|
||||
String pdfAConformanceLevel = null;
|
||||
try {
|
||||
String pdfaConformanceLevel = getPdfAConformanceLevel(document);
|
||||
boolean isPdfACompliant = pdfaConformanceLevel != null;
|
||||
boolean isPdfXCompliant = checkForStandard(document, "PDF/X");
|
||||
boolean isPdfECompliant = checkForStandard(document, "PDF/E");
|
||||
boolean isPdfVTCompliant = checkForStandard(document, "PDF/VT");
|
||||
boolean isPdfUACompliant = checkForStandard(document, "PDF/UA");
|
||||
boolean isPdfBCompliant = checkForStandard(document, "PDF/B");
|
||||
boolean isPdfSECCompliant = checkForStandard(document, "PDF/SEC");
|
||||
|
||||
if (verificationResults != null) {
|
||||
for (PDFVerificationResult result : verificationResults) {
|
||||
if (result == null) continue;
|
||||
if (result.isCompliant()) {
|
||||
String std = result.getStandard().toLowerCase();
|
||||
if (std.contains("pdf_a") || std.contains("pdfa")) {
|
||||
isPdfA = true;
|
||||
String profile = result.getValidationProfile();
|
||||
if (profile != null) {
|
||||
if (profile.contains("1b")
|
||||
|| profile.contains("2b")
|
||||
|| profile.contains("3b")) {
|
||||
isPdfB = true;
|
||||
}
|
||||
// Simple extraction: remove "pdfa-" prefix
|
||||
pdfAConformanceLevel = profile.replace("pdfa-", "");
|
||||
}
|
||||
}
|
||||
if (std.contains("pdf_ua") || std.contains("pdfua")) isPdfUA = true;
|
||||
if (std.contains("pdf_x") || std.contains("pdfx")) isPdfX = true;
|
||||
if (std.contains("pdf_e") || std.contains("pdfe")) isPdfE = true;
|
||||
}
|
||||
compliancy.put("IsPDF/ACompliant", isPdfACompliant);
|
||||
if (pdfaConformanceLevel != null) {
|
||||
compliancy.put("PDF/AConformanceLevel", pdfaConformanceLevel);
|
||||
Boolean pdfaValidationPassed =
|
||||
validatePdfAWithPreflight(document, pdfaConformanceLevel);
|
||||
compliancy.put("IsPDF/AValidated", pdfaValidationPassed);
|
||||
}
|
||||
}
|
||||
compliancy.put("IsPDF/XCompliant", isPdfXCompliant);
|
||||
compliancy.put("IsPDF/ECompliant", isPdfECompliant);
|
||||
compliancy.put("IsPDF/VTCompliant", isPdfVTCompliant);
|
||||
compliancy.put("IsPDF/UACompliant", isPdfUACompliant);
|
||||
compliancy.put("IsPDF/BCompliant", isPdfBCompliant);
|
||||
compliancy.put("IsPDF/SECCompliant", isPdfSECCompliant);
|
||||
|
||||
compliancy.put("IsPDF/ACompliant", isPdfA);
|
||||
compliancy.put("IsPDF/UACompliant", isPdfUA);
|
||||
compliancy.put("IsPDF/ECompliant", isPdfE);
|
||||
compliancy.put("IsPDF/VTCompliant", false); // Not currently implemented
|
||||
compliancy.put("IsPDF/BCompliant", isPdfB);
|
||||
if (pdfAConformanceLevel != null) {
|
||||
compliancy.put("PDF/AConformanceLevel", pdfAConformanceLevel);
|
||||
}
|
||||
|
||||
compliancy.put("IsPDF/SECCompliant", isSECCompliant(doc));
|
||||
|
||||
if (verificationResults != null && !verificationResults.isEmpty()) {
|
||||
// Keep original simple structure as backup or extra info
|
||||
for (PDFVerificationResult result : verificationResults) {
|
||||
if (result == null) continue;
|
||||
String standard = result.getStandard();
|
||||
boolean isCompliant = result.isCompliant();
|
||||
|
||||
if (standard != null) {
|
||||
// Avoid overwriting specific keys if collision, but here keys are distinct
|
||||
// enough
|
||||
compliancy.put(standard, isCompliant);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error extracting compliance info: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return compliancy;
|
||||
}
|
||||
|
||||
private static boolean isSECCompliant(PDDocument doc) {
|
||||
try {
|
||||
// 1. Check Encryption
|
||||
if (doc.isEncrypted()) return false;
|
||||
|
||||
PDDocumentCatalog catalog = doc.getDocumentCatalog();
|
||||
|
||||
// 2. Check for JavaScript (Active Content)
|
||||
if (catalog.getOpenAction() instanceof PDActionJavaScript) return false;
|
||||
if (catalog.getNames() != null && catalog.getNames().getJavaScript() != null)
|
||||
return false;
|
||||
|
||||
// Check for AcroForm
|
||||
if (catalog.getAcroForm() != null) return false;
|
||||
|
||||
// 3. Check for Embedded Files
|
||||
if (catalog.getNames() != null && catalog.getNames().getEmbeddedFiles() != null)
|
||||
return false;
|
||||
|
||||
// 4. Check for External Links or Navigation Actions
|
||||
for (PDPage page : doc.getPages()) {
|
||||
for (PDAnnotation annotation : page.getAnnotations()) {
|
||||
if (annotation instanceof PDAnnotationLink) {
|
||||
PDAnnotationLink link = (PDAnnotationLink) annotation;
|
||||
PDAction action = link.getAction();
|
||||
if (action instanceof PDActionURI
|
||||
|| action instanceof PDActionLaunch
|
||||
|| action instanceof PDActionRemoteGoTo
|
||||
|| action instanceof PDActionSubmitForm) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Error checking SEC compliance: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static ObjectNode extractEncryptionInfo(PDDocument document) {
|
||||
ObjectNode encryption = objectMapper.createObjectNode();
|
||||
|
||||
@@ -1076,13 +1227,6 @@ public class GetInfoOnPDF {
|
||||
return createErrorResponse("Invalid PDF file: " + e.getMessage());
|
||||
}
|
||||
|
||||
List<PDFVerificationResult> verificationResults = null;
|
||||
try {
|
||||
verificationResults = veraPDFService.validatePDF(inputFile.getInputStream());
|
||||
} catch (Exception e) {
|
||||
log.error("VeraPDF validation failed", e);
|
||||
}
|
||||
|
||||
boolean readonly = true;
|
||||
|
||||
try (PDDocument pdfBoxDoc = pdfDocumentFactory.load(inputFile, readonly)) {
|
||||
@@ -1091,13 +1235,20 @@ public class GetInfoOnPDF {
|
||||
ObjectNode metadata = extractMetadata(pdfBoxDoc);
|
||||
ObjectNode basicInfo = extractBasicInfo(pdfBoxDoc, inputFile.getSize());
|
||||
ObjectNode docInfoNode = extractDocumentInfo(pdfBoxDoc);
|
||||
ObjectNode compliancy = extractComplianceInfo(pdfBoxDoc, verificationResults);
|
||||
ObjectNode compliancy = extractComplianceInfo(pdfBoxDoc);
|
||||
ObjectNode encryption = extractEncryptionInfo(pdfBoxDoc);
|
||||
ObjectNode permissionsNode = extractPermissions(pdfBoxDoc);
|
||||
ObjectNode other = extractOtherInfo(pdfBoxDoc);
|
||||
ObjectNode formFieldsNode = extractFormFields(pdfBoxDoc);
|
||||
|
||||
ObjectNode summaryData = generatePDFSummaryData(pdfBoxDoc, verificationResults);
|
||||
// Generate summary data
|
||||
String pdfaConformanceLevel = getPdfAConformanceLevel(pdfBoxDoc);
|
||||
Boolean pdfaValidationPassed = null;
|
||||
if (pdfaConformanceLevel != null) {
|
||||
pdfaValidationPassed = validatePdfAWithPreflight(pdfBoxDoc, pdfaConformanceLevel);
|
||||
}
|
||||
ObjectNode summaryData =
|
||||
generatePDFSummaryData(pdfBoxDoc, pdfaConformanceLevel, pdfaValidationPassed);
|
||||
|
||||
// Extract per-page information
|
||||
ObjectNode pageInfoParent = extractPerPageInfo(pdfBoxDoc);
|
||||
|
||||
+4
-1
@@ -14,6 +14,9 @@ public class PdfToPdfARequest extends PDFFile {
|
||||
@Schema(
|
||||
description = "The output format type (PDF/A or PDF/X)",
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
allowableValues = {"pdfa", "pdfa-1", "pdfa-2", "pdfa-2b", "pdfa-3", "pdfa-3b", "pdfx"})
|
||||
allowableValues = {
|
||||
"pdfa", "pdfa-1", "pdfa-2", "pdfa-2b", "pdfa-3", "pdfa-3b", "pdfx", "pdfx-1",
|
||||
"pdfx-3", "pdfx-4"
|
||||
})
|
||||
private String outputFormat;
|
||||
}
|
||||
|
||||
-8
@@ -39,14 +39,6 @@ public class AddPageNumbersRequest extends PDFWithPageNums {
|
||||
requiredMode = RequiredMode.NOT_REQUIRED)
|
||||
private String fontColor;
|
||||
|
||||
@Schema(
|
||||
description =
|
||||
"Zero-padding width for page numbers (Bates Stamping). Set to 0 to disable padding",
|
||||
minimum = "0",
|
||||
defaultValue = "0",
|
||||
requiredMode = RequiredMode.NOT_REQUIRED)
|
||||
private int zeroPad = 0;
|
||||
|
||||
@Schema(
|
||||
description =
|
||||
"Position: 1-9 representing positions on the page (1=top-left, 2=top-center,"
|
||||
|
||||
@@ -32,8 +32,8 @@ public class AddStampRequest extends PDFWithPageNums {
|
||||
private String alphabet = "roman";
|
||||
|
||||
@Schema(
|
||||
description = "The font size of the stamp text and image in points.",
|
||||
defaultValue = "40",
|
||||
description = "The font size of the stamp text and image",
|
||||
defaultValue = "30",
|
||||
requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private float fontSize;
|
||||
|
||||
|
||||
@@ -81,7 +81,6 @@ security:
|
||||
revocation:
|
||||
mode: none # Revocation checking mode: 'none' (disabled), 'ocsp' (OCSP only), 'crl' (CRL only), 'ocsp+crl' (OCSP with CRL fallback)
|
||||
hardFail: false # Fail validation if revocation status cannot be determined (true=strict, false=soft-fail)
|
||||
xFrameOptions: DENY # X-Frame-Options header value. Options: 'DENY' (default, prevents all framing), 'SAMEORIGIN' (allows framing from same domain), 'DISABLED' (no X-Frame-Options header sent). Note: automatically set to DISABLED when login is disabled
|
||||
|
||||
premium:
|
||||
key: 00000000-0000-0000-0000-000000000000
|
||||
@@ -203,9 +202,7 @@ system:
|
||||
name: postgres # set the name of your database. Should match the name of the database you create
|
||||
customPaths:
|
||||
pipeline:
|
||||
pipelineDir: "" # Defaults to /pipeline
|
||||
watchedFoldersDir: "" # Defaults to /pipeline/watchedFolders
|
||||
watchedFoldersDirs: [] # List of watched folder directories. Defaults to watchedFoldersDir or /pipeline/watchedFolders.
|
||||
finishedFoldersDir: "" # Defaults to /pipeline/finishedFolders
|
||||
operations:
|
||||
weasyprint: "" # Defaults to /opt/venv/bin/weasyprint
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
{
|
||||
"moduleName": "ch.qos.logback:logback-classic",
|
||||
"moduleUrl": "http://www.qos.ch",
|
||||
"moduleVersion": "1.5.25",
|
||||
"moduleVersion": "1.5.23",
|
||||
"moduleLicense": "GNU Lesser General Public License",
|
||||
"moduleLicenseUrl": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "ch.qos.logback:logback-core",
|
||||
"moduleUrl": "http://www.qos.ch",
|
||||
"moduleVersion": "1.5.25",
|
||||
"moduleVersion": "1.5.23",
|
||||
"moduleLicense": "GNU Lesser General Public License",
|
||||
"moduleLicenseUrl": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html"
|
||||
},
|
||||
@@ -684,7 +684,7 @@
|
||||
{
|
||||
"moduleName": "io.micrometer:micrometer-core",
|
||||
"moduleUrl": "https://github.com/micrometer-metrics/micrometer",
|
||||
"moduleVersion": "1.16.2",
|
||||
"moduleVersion": "1.16.1",
|
||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
@@ -1596,6 +1596,30 @@
|
||||
"moduleLicense": "Mozilla Public License, Version 2.0",
|
||||
"moduleLicenseUrl": "http://www.mozilla.org/MPL/2.0/index.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.openjfx:javafx-base",
|
||||
"moduleVersion": "21",
|
||||
"moduleLicense": "GPLv2+CE",
|
||||
"moduleLicenseUrl": "https://openjdk.java.net/legal/gplv2+ce.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.openjfx:javafx-controls",
|
||||
"moduleVersion": "21",
|
||||
"moduleLicense": "GPLv2+CE",
|
||||
"moduleLicenseUrl": "https://openjdk.java.net/legal/gplv2+ce.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.openjfx:javafx-graphics",
|
||||
"moduleVersion": "21",
|
||||
"moduleLicense": "GPLv2+CE",
|
||||
"moduleLicenseUrl": "https://openjdk.java.net/legal/gplv2+ce.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.openjfx:javafx-swing",
|
||||
"moduleVersion": "21",
|
||||
"moduleLicense": "GPLv2+CE",
|
||||
"moduleLicenseUrl": "https://openjdk.java.net/legal/gplv2+ce.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.opensaml:opensaml-core",
|
||||
"moduleVersion": "4.3.2",
|
||||
@@ -2174,4 +2198,4 @@
|
||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+8
-20
@@ -85,13 +85,10 @@ class EditTableOfContentsControllerTest {
|
||||
when(mockOutlineItem.getNextSibling()).thenReturn(null);
|
||||
|
||||
// When
|
||||
ResponseEntity<List<Map<String, Object>>> response =
|
||||
editTableOfContentsController.extractBookmarks(mockFile);
|
||||
List<Map<String, Object>> result = editTableOfContentsController.extractBookmarks(mockFile);
|
||||
|
||||
// Then
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getBody());
|
||||
List<Map<String, Object>> result = response.getBody();
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.size());
|
||||
|
||||
Map<String, Object> bookmark = result.get(0);
|
||||
@@ -110,13 +107,10 @@ class EditTableOfContentsControllerTest {
|
||||
when(mockCatalog.getDocumentOutline()).thenReturn(null);
|
||||
|
||||
// When
|
||||
ResponseEntity<List<Map<String, Object>>> response =
|
||||
editTableOfContentsController.extractBookmarks(mockFile);
|
||||
List<Map<String, Object>> result = editTableOfContentsController.extractBookmarks(mockFile);
|
||||
|
||||
// Then
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getBody());
|
||||
List<Map<String, Object>> result = response.getBody();
|
||||
assertNotNull(result);
|
||||
assertTrue(result.isEmpty());
|
||||
verify(mockDocument).close();
|
||||
}
|
||||
@@ -147,13 +141,10 @@ class EditTableOfContentsControllerTest {
|
||||
when(childItem.getNextSibling()).thenReturn(null);
|
||||
|
||||
// When
|
||||
ResponseEntity<List<Map<String, Object>>> response =
|
||||
editTableOfContentsController.extractBookmarks(mockFile);
|
||||
List<Map<String, Object>> result = editTableOfContentsController.extractBookmarks(mockFile);
|
||||
|
||||
// Then
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getBody());
|
||||
List<Map<String, Object>> result = response.getBody();
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.size());
|
||||
|
||||
Map<String, Object> parentBookmark = result.get(0);
|
||||
@@ -186,13 +177,10 @@ class EditTableOfContentsControllerTest {
|
||||
when(mockOutlineItem.getNextSibling()).thenReturn(null);
|
||||
|
||||
// When
|
||||
ResponseEntity<List<Map<String, Object>>> response =
|
||||
editTableOfContentsController.extractBookmarks(mockFile);
|
||||
List<Map<String, Object>> result = editTableOfContentsController.extractBookmarks(mockFile);
|
||||
|
||||
// Then
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getBody());
|
||||
List<Map<String, Object>> result = response.getBody();
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.size());
|
||||
|
||||
Map<String, Object> bookmark = result.get(0);
|
||||
|
||||
-567
@@ -1,567 +0,0 @@
|
||||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class StampControllerTest {
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private TempFileManager tempFileManager;
|
||||
|
||||
@InjectMocks private StampController stampController;
|
||||
|
||||
private Method processStampTextMethod;
|
||||
private Method processCustomDateFormatMethod;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws NoSuchMethodException {
|
||||
processStampTextMethod =
|
||||
StampController.class.getDeclaredMethod(
|
||||
"processStampText",
|
||||
String.class,
|
||||
int.class,
|
||||
int.class,
|
||||
String.class,
|
||||
PDDocument.class);
|
||||
processStampTextMethod.setAccessible(true);
|
||||
|
||||
processCustomDateFormatMethod =
|
||||
StampController.class.getDeclaredMethod(
|
||||
"processCustomDateFormat", String.class, LocalDateTime.class);
|
||||
processCustomDateFormatMethod.setAccessible(true);
|
||||
}
|
||||
|
||||
private String invokeProcessStampText(
|
||||
String stampText, int pageNumber, int totalPages, String filename, PDDocument document)
|
||||
throws Exception {
|
||||
try {
|
||||
return (String)
|
||||
processStampTextMethod.invoke(
|
||||
stampController, stampText, pageNumber, totalPages, filename, document);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw (Exception) e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
private String invokeProcessCustomDateFormat(String format, LocalDateTime now)
|
||||
throws Exception {
|
||||
try {
|
||||
return (String) processCustomDateFormatMethod.invoke(stampController, format, now);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw (Exception) e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Basic Variable Substitution Tests")
|
||||
class BasicVariableTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @page_number with current page")
|
||||
void testPageNumberReplacement() throws Exception {
|
||||
String result = invokeProcessStampText("Page @page_number", 5, 20, "test.pdf", null);
|
||||
assertEquals("Page 5", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @total_pages with total page count")
|
||||
void testTotalPagesReplacement() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText("of @total_pages pages", 1, 100, "test.pdf", null);
|
||||
assertEquals("of 100 pages", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace combined page variables")
|
||||
void testCombinedPageVariables() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText(
|
||||
"Page @page_number of @total_pages", 5, 20, "test.pdf", null);
|
||||
assertEquals("Page 5 of 20", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @page alias")
|
||||
void testPageAlias() throws Exception {
|
||||
String result = invokeProcessStampText("Page @page", 7, 10, "test.pdf", null);
|
||||
assertEquals("Page 7", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @page_count alias for total pages")
|
||||
void testPageCountAlias() throws Exception {
|
||||
String result = invokeProcessStampText("Total: @page_count", 1, 50, "test.pdf", null);
|
||||
assertEquals("Total: 50", result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Filename Variable Tests")
|
||||
class FilenameTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @filename with filename without extension")
|
||||
void testFilenameWithoutExtension() throws Exception {
|
||||
String result = invokeProcessStampText("File: @filename", 1, 1, "document.pdf", null);
|
||||
assertEquals("File: document", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @filename_full with full filename")
|
||||
void testFilenameWithExtension() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText("File: @filename_full", 1, 1, "document.pdf", null);
|
||||
assertEquals("File: document.pdf", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle filename without extension")
|
||||
void testFilenameWithoutDot() throws Exception {
|
||||
String result = invokeProcessStampText("@filename", 1, 1, "document", null);
|
||||
assertEquals("document", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle null filename")
|
||||
void testNullFilename() throws Exception {
|
||||
String result = invokeProcessStampText("File: @filename", 1, 1, null, null);
|
||||
assertEquals("File: ", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle filename with multiple dots")
|
||||
void testFilenameMultipleDots() throws Exception {
|
||||
String result = invokeProcessStampText("@filename", 1, 1, "my.document.v2.pdf", null);
|
||||
assertEquals("my.document.v2", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle hidden file (starts with dot)")
|
||||
void testHiddenFile() throws Exception {
|
||||
String result = invokeProcessStampText("@filename", 1, 1, ".hidden.pdf", null);
|
||||
assertEquals(".hidden", result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Date/Time Variable Tests")
|
||||
class DateTimeTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @date with current date")
|
||||
void testDateReplacement() throws Exception {
|
||||
String result = invokeProcessStampText("Date: @date", 1, 1, "test.pdf", null);
|
||||
assertTrue(
|
||||
result.matches("Date: \\d{4}-\\d{2}-\\d{2}"),
|
||||
"Date should match YYYY-MM-DD format");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @time with current time")
|
||||
void testTimeReplacement() throws Exception {
|
||||
String result = invokeProcessStampText("Time: @time", 1, 1, "test.pdf", null);
|
||||
assertTrue(
|
||||
result.matches("Time: \\d{2}:\\d{2}:\\d{2}"),
|
||||
"Time should match HH:mm:ss format");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @datetime with combined date and time")
|
||||
void testDateTimeReplacement() throws Exception {
|
||||
String result = invokeProcessStampText("@datetime", 1, 1, "test.pdf", null);
|
||||
// DateTime format: YYYY-MM-DD HH:mm:ss
|
||||
assertTrue(
|
||||
result.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}"),
|
||||
"DateTime should match YYYY-MM-DD HH:mm:ss format");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @year with current year")
|
||||
void testYearReplacement() throws Exception {
|
||||
String result = invokeProcessStampText("© @year", 1, 1, "test.pdf", null);
|
||||
int currentYear = LocalDateTime.now().getYear();
|
||||
assertEquals("© " + currentYear, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @month with zero-padded month")
|
||||
void testMonthReplacement() throws Exception {
|
||||
String result = invokeProcessStampText("Month: @month", 1, 1, "test.pdf", null);
|
||||
assertTrue(result.matches("Month: \\d{2}"), "Month should be zero-padded");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @day with zero-padded day")
|
||||
void testDayReplacement() throws Exception {
|
||||
String result = invokeProcessStampText("Day: @day", 1, 1, "test.pdf", null);
|
||||
assertTrue(result.matches("Day: \\d{2}"), "Day should be zero-padded");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Custom Date Format Tests")
|
||||
class CustomDateFormatTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle custom date format dd/MM/yyyy")
|
||||
void testCustomDateFormatSlash() throws Exception {
|
||||
String result = invokeProcessStampText("@date{dd/MM/yyyy}", 1, 1, "test.pdf", null);
|
||||
assertTrue(
|
||||
result.matches("\\d{2}/\\d{2}/\\d{4}"),
|
||||
"Should match dd/MM/yyyy format: " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle custom date format with time")
|
||||
void testCustomDateFormatWithTime() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText("@date{yyyy-MM-dd HH:mm}", 1, 1, "test.pdf", null);
|
||||
assertTrue(
|
||||
result.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}"),
|
||||
"Should match yyyy-MM-dd HH:mm format: " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle multiple custom date formats in same text")
|
||||
void testMultipleCustomDateFormats() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText(
|
||||
"Start: @date{dd/MM/yyyy} End: @date{yyyy}", 1, 1, "test.pdf", null);
|
||||
assertTrue(result.contains("/"), "Should contain slash from first format");
|
||||
// Should have year twice (once with slashes, once alone)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Custom Date Format Security Tests")
|
||||
class CustomDateFormatSecurityTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should not match format that is too long - regex won't capture it")
|
||||
void testFormatTooLong() throws Exception {
|
||||
String longFormat = "y".repeat(51); // 51 chars, over the 50 char regex limit
|
||||
String result =
|
||||
invokeProcessStampText("@date{" + longFormat + "}", 1, 1, "test.pdf", null);
|
||||
// The CUSTOM_DATE_PATTERN only captures up to 50 chars, so this won't match
|
||||
// The @date part will be replaced by simple replacement, leaving {yyy...}
|
||||
assertTrue(
|
||||
result.contains("{"), "Should contain { because regex didn't match: " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should reject format with unsafe characters - shell injection attempt")
|
||||
void testShellInjectionAttempt() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText("@date{yyyy-MM-dd$(rm -rf /)}", 1, 1, "test.pdf", null);
|
||||
assertEquals("[invalid format]", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should reject format with unsafe characters - semicolon")
|
||||
void testSemicolonInjection() throws Exception {
|
||||
String result = invokeProcessStampText("@date{yyyy;rm}", 1, 1, "test.pdf", null);
|
||||
assertEquals("[invalid format]", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should reject format with unsafe characters - backticks")
|
||||
void testBacktickInjection() throws Exception {
|
||||
String result = invokeProcessStampText("@date{`whoami`}", 1, 1, "test.pdf", null);
|
||||
assertEquals("[invalid format]", result);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"$(cmd)", "`cmd`", ";cmd", "|cmd", "&cmd", "<cmd", ">cmd"})
|
||||
@DisplayName("Should reject various injection attempts")
|
||||
void testVariousInjectionAttempts(String injection) throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText("@date{yyyy" + injection + "}", 1, 1, "test.pdf", null);
|
||||
assertEquals("[invalid format]", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should accept valid format characters")
|
||||
void testValidFormatCharacters() throws Exception {
|
||||
// All these should be valid based on SAFE_DATE_FORMAT_PATTERN: yMdHhmsS/-:.,
|
||||
// '+EGuwWDFzZXa and space
|
||||
String result =
|
||||
invokeProcessStampText("@date{yyyy-MM-dd HH:mm:ss}", 1, 1, "test.pdf", null);
|
||||
assertFalse(
|
||||
result.startsWith("[invalid"), "Valid format should be accepted: " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle invalid DateTimeFormatter pattern gracefully")
|
||||
void testInvalidFormatterPattern() throws Exception {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
// Use 'sssss' - too many seconds digits will throw IllegalArgumentException from
|
||||
// DateTimeFormatter
|
||||
// Note: The pattern 'sssss' passes the SAFE_DATE_FORMAT_PATTERN but fails
|
||||
// DateTimeFormatter.ofPattern()
|
||||
String result = invokeProcessCustomDateFormat("sssss", now);
|
||||
assertTrue(
|
||||
result.startsWith("[invalid format:"),
|
||||
"Invalid pattern should return error message: " + result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Escape Sequence Tests")
|
||||
class EscapeSequenceTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should convert @@ to literal @")
|
||||
void testDoubleAtEscape() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText("Email: test@@example.com", 1, 1, "test.pdf", null);
|
||||
assertEquals("Email: test@example.com", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should preserve @@ before variable")
|
||||
void testEscapeBeforeVariable() throws Exception {
|
||||
String result = invokeProcessStampText("@@date is @date", 1, 1, "test.pdf", null);
|
||||
// @@date should become @date, and @date should be replaced with actual date
|
||||
assertTrue(result.startsWith("@date is "), "Should start with literal @date");
|
||||
assertTrue(
|
||||
result.matches("@date is \\d{4}-\\d{2}-\\d{2}"),
|
||||
"Should have date after: " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle multiple escape sequences")
|
||||
void testMultipleEscapes() throws Exception {
|
||||
String result = invokeProcessStampText("@@one @@two @@three", 1, 1, "test.pdf", null);
|
||||
assertEquals("@one @two @three", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle escape at end of string")
|
||||
void testEscapeAtEnd() throws Exception {
|
||||
String result = invokeProcessStampText("Contact: user@@", 1, 1, "test.pdf", null);
|
||||
assertEquals("Contact: user@", result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Document Metadata Tests")
|
||||
class DocumentMetadataTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @author with document author")
|
||||
void testAuthorReplacement() throws Exception {
|
||||
PDDocument doc = new PDDocument();
|
||||
PDDocumentInformation info = new PDDocumentInformation();
|
||||
info.setAuthor("John Doe");
|
||||
doc.setDocumentInformation(info);
|
||||
|
||||
try {
|
||||
String result = invokeProcessStampText("Author: @author", 1, 1, "test.pdf", doc);
|
||||
assertEquals("Author: John Doe", result);
|
||||
} finally {
|
||||
doc.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @title with document title")
|
||||
void testTitleReplacement() throws Exception {
|
||||
PDDocument doc = new PDDocument();
|
||||
PDDocumentInformation info = new PDDocumentInformation();
|
||||
info.setTitle("My Document Title");
|
||||
doc.setDocumentInformation(info);
|
||||
|
||||
try {
|
||||
String result = invokeProcessStampText("Title: @title", 1, 1, "test.pdf", doc);
|
||||
assertEquals("Title: My Document Title", result);
|
||||
} finally {
|
||||
doc.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should replace @subject with document subject")
|
||||
void testSubjectReplacement() throws Exception {
|
||||
PDDocument doc = new PDDocument();
|
||||
PDDocumentInformation info = new PDDocumentInformation();
|
||||
info.setSubject("Important Subject");
|
||||
doc.setDocumentInformation(info);
|
||||
|
||||
try {
|
||||
String result = invokeProcessStampText("Subject: @subject", 1, 1, "test.pdf", doc);
|
||||
assertEquals("Subject: Important Subject", result);
|
||||
} finally {
|
||||
doc.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle null metadata gracefully")
|
||||
void testNullMetadata() throws Exception {
|
||||
PDDocument doc = new PDDocument();
|
||||
// Don't set any document information
|
||||
|
||||
try {
|
||||
String result =
|
||||
invokeProcessStampText("@author @title @subject", 1, 1, "test.pdf", doc);
|
||||
assertEquals(" ", result); // All should be empty strings
|
||||
} finally {
|
||||
doc.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle null document gracefully")
|
||||
void testNullDocument() throws Exception {
|
||||
String result = invokeProcessStampText("Author: @author", 1, 1, "test.pdf", null);
|
||||
assertEquals("Author: ", result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("UUID Variable Tests")
|
||||
class UuidTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should generate 8-character UUID")
|
||||
void testUuidLength() throws Exception {
|
||||
String result = invokeProcessStampText("ID: @uuid", 1, 1, "test.pdf", null);
|
||||
// UUID format: "ID: " + 8 chars
|
||||
assertEquals(12, result.length(), "Should be 'ID: ' + 8 char UUID");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should generate different UUIDs for each call")
|
||||
void testUuidUniqueness() throws Exception {
|
||||
String result1 = invokeProcessStampText("@uuid", 1, 1, "test.pdf", null);
|
||||
String result2 = invokeProcessStampText("@uuid", 1, 1, "test.pdf", null);
|
||||
assertNotEquals(result1, result2, "UUIDs should be unique");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("UUID should contain only hex characters")
|
||||
void testUuidFormat() throws Exception {
|
||||
String result = invokeProcessStampText("@uuid", 1, 1, "test.pdf", null);
|
||||
assertTrue(result.matches("[0-9a-f]{8}"), "UUID should be 8 hex characters: " + result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Edge Cases and Error Handling")
|
||||
class EdgeCaseTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle null stamp text")
|
||||
void testNullStampText() throws Exception {
|
||||
String result = invokeProcessStampText(null, 1, 1, "test.pdf", null);
|
||||
assertEquals("", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle empty stamp text")
|
||||
void testEmptyStampText() throws Exception {
|
||||
String result = invokeProcessStampText("", 1, 1, "test.pdf", null);
|
||||
assertEquals("", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle text with no variables")
|
||||
void testNoVariables() throws Exception {
|
||||
String result = invokeProcessStampText("Just plain text", 1, 1, "test.pdf", null);
|
||||
assertEquals("Just plain text", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle unknown variables")
|
||||
void testUnknownVariable() throws Exception {
|
||||
String result = invokeProcessStampText("@unknown_var", 1, 1, "test.pdf", null);
|
||||
assertEquals("@unknown_var", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should preserve text around variables")
|
||||
void testPreservesSurroundingText() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText("Before @page_number After", 5, 10, "test.pdf", null);
|
||||
assertEquals("Before 5 After", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle multiple same variables")
|
||||
void testMultipleSameVariables() throws Exception {
|
||||
String result =
|
||||
invokeProcessStampText("@page_number / @page_number", 3, 10, "test.pdf", null);
|
||||
assertEquals("3 / 3", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle variables adjacent to each other")
|
||||
void testAdjacentVariables() throws Exception {
|
||||
String result = invokeProcessStampText("@page@page_number", 5, 10, "test.pdf", null);
|
||||
// @page should be replaced first (it's in the order), then @page_number
|
||||
// Since @page_number is longer and comes first in replace chain, should work
|
||||
assertEquals("55", result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Complex Scenario Tests")
|
||||
class ComplexScenarioTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle legal footer template")
|
||||
void testLegalFooterTemplate() throws Exception {
|
||||
String template = "© @year - All Rights Reserved\\n@filename - Page @page_number";
|
||||
String result = invokeProcessStampText(template, 3, 15, "contract.pdf", null);
|
||||
|
||||
int year = LocalDateTime.now().getYear();
|
||||
String expected = "© " + year + " - All Rights Reserved\\ncontract - Page 3";
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle Brazilian date format template")
|
||||
void testBrazilianDateFormat() throws Exception {
|
||||
String template = "Documento criado em @date{dd/MM/yyyy} às @time";
|
||||
String result = invokeProcessStampText(template, 1, 1, "doc.pdf", null);
|
||||
|
||||
assertTrue(result.startsWith("Documento criado em "));
|
||||
assertTrue(result.contains("/"));
|
||||
assertTrue(result.contains(":"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"'Page @page_number of @total_pages', 1, 10, 'Page 1 of 10'",
|
||||
"'Page @page_number of @total_pages', 5, 20, 'Page 5 of 20'",
|
||||
"'Page @page_number of @total_pages', 100, 1000, 'Page 100 of 1000'"
|
||||
})
|
||||
@DisplayName("Should handle page number template with various values")
|
||||
void testPageNumberTemplates(String template, int page, int total, String expected)
|
||||
throws Exception {
|
||||
String result = invokeProcessStampText(template, page, total, "test.pdf", null);
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
-123
@@ -1,30 +1,22 @@
|
||||
package stirling.software.SPDF.controller.api.security;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.*;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
|
||||
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
|
||||
import org.apache.pdfbox.pdmodel.encryption.ProtectionPolicy;
|
||||
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
|
||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
|
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
|
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionLaunch;
|
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
|
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
@@ -43,8 +35,6 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import stirling.software.SPDF.model.api.security.PDFVerificationResult;
|
||||
import stirling.software.SPDF.service.VeraPDFService;
|
||||
import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
|
||||
@@ -53,7 +43,6 @@ import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||
class GetInfoOnPDFTest {
|
||||
|
||||
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||
@Mock private VeraPDFService veraPDFService;
|
||||
|
||||
@InjectMocks private GetInfoOnPDF getInfoOnPDF;
|
||||
|
||||
@@ -672,6 +661,41 @@ class GetInfoOnPDFTest {
|
||||
void testGetPageSize(float width, float height, String expected) {
|
||||
Assertions.assertEquals(expected, GetInfoOnPDF.getPageSize(width, height));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should check for PDF/A standard")
|
||||
void testCheckForStandard_PdfA() throws IOException {
|
||||
// This would require a real PDF/A document or mocking
|
||||
PDDocument document = createSimplePdfWithText("Test");
|
||||
boolean result = GetInfoOnPDF.checkForStandard(document, "PDF/A");
|
||||
Assertions.assertFalse(result); // Simple PDF is not PDF/A compliant
|
||||
document.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle null document in checkForStandard")
|
||||
void testCheckForStandard_NullDocument() {
|
||||
boolean result = GetInfoOnPDF.checkForStandard(null, "PDF/A");
|
||||
Assertions.assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should get PDF/A conformance level")
|
||||
void testGetPdfAConformanceLevel() throws IOException {
|
||||
PDDocument document = createSimplePdfWithText("Test");
|
||||
String level = GetInfoOnPDF.getPdfAConformanceLevel(document);
|
||||
Assertions.assertNull(level);
|
||||
document.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should handle encrypted document in getPdfAConformanceLevel")
|
||||
void testGetPdfAConformanceLevel_EncryptedDocument() throws IOException {
|
||||
PDDocument document = createEncryptedPdf();
|
||||
String level = GetInfoOnPDF.getPdfAConformanceLevel(document);
|
||||
Assertions.assertNull(level); // Encrypted documents return null
|
||||
document.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@@ -752,8 +776,8 @@ class GetInfoOnPDFTest {
|
||||
class ComplianceTests {
|
||||
|
||||
@Test
|
||||
@DisplayName("Should extract compliance info using VeraPDF")
|
||||
void testCompliance_PdfA() throws Exception {
|
||||
@DisplayName("Should check PDF/A compliance")
|
||||
void testCompliance_PdfA() throws IOException {
|
||||
PDDocument document = createSimplePdfWithText("Test PDF/A");
|
||||
MockMultipartFile mockFile = documentToMultipartFile(document, "pdfa.pdf");
|
||||
|
||||
@@ -767,22 +791,16 @@ class GetInfoOnPDFTest {
|
||||
ArgumentMatchers.anyBoolean()))
|
||||
.thenReturn(loadedDoc);
|
||||
|
||||
// Mock VeraPDFService
|
||||
PDFVerificationResult result = new PDFVerificationResult();
|
||||
result.setStandard("pdfa-1b");
|
||||
result.setCompliant(true);
|
||||
result.setComplianceSummary("PDF/A-1b compliant");
|
||||
Mockito.when(veraPDFService.validatePDF(ArgumentMatchers.any(InputStream.class)))
|
||||
.thenReturn(List.of(result));
|
||||
|
||||
ResponseEntity<byte[]> response = getInfoOnPDF.getPdfInfo(request);
|
||||
|
||||
String jsonResponse = new String(response.getBody(), StandardCharsets.UTF_8);
|
||||
JsonNode jsonNode = objectMapper.readTree(jsonResponse);
|
||||
JsonNode compliancy = jsonNode.get("Compliancy");
|
||||
|
||||
Assertions.assertTrue(compliancy.has("pdfa-1b"));
|
||||
Assertions.assertTrue(compliancy.get("pdfa-1b").asBoolean());
|
||||
Assertions.assertTrue(compliancy.has("IsPDF/ACompliant"));
|
||||
Assertions.assertTrue(compliancy.has("IsPDF/XCompliant"));
|
||||
Assertions.assertTrue(compliancy.has("IsPDF/ECompliant"));
|
||||
Assertions.assertTrue(compliancy.has("IsPDF/UACompliant"));
|
||||
|
||||
loadedDoc.close();
|
||||
}
|
||||
@@ -822,104 +840,4 @@ class GetInfoOnPDFTest {
|
||||
loadedDoc.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SEC Compliance: Clean document should pass")
|
||||
void testSecCompliance_Clean() throws Exception {
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
doc.addPage(new PDPage());
|
||||
checkSecCompliance(doc, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SEC Compliance: JavaScript action should fail")
|
||||
void testSecCompliance_JavaScript() throws Exception {
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
doc.addPage(new PDPage());
|
||||
PDActionJavaScript jsAction = new PDActionJavaScript("app.alert('Hi')");
|
||||
doc.getDocumentCatalog().setOpenAction(jsAction);
|
||||
checkSecCompliance(doc, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SEC Compliance: External URI Link should fail")
|
||||
void testSecCompliance_ExternalLink() throws Exception {
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
PDPage page = new PDPage();
|
||||
doc.addPage(page);
|
||||
|
||||
PDAnnotationLink link = new PDAnnotationLink();
|
||||
PDActionURI action = new PDActionURI();
|
||||
action.setURI("http://google.com");
|
||||
link.setAction(action);
|
||||
|
||||
page.getAnnotations().add(link);
|
||||
checkSecCompliance(doc, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SEC Compliance: Launch Action should fail")
|
||||
void testSecCompliance_LaunchAction() throws Exception {
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
PDPage page = new PDPage();
|
||||
doc.addPage(page);
|
||||
|
||||
PDAnnotationLink link = new PDAnnotationLink();
|
||||
PDActionLaunch action = new PDActionLaunch();
|
||||
link.setAction(action);
|
||||
|
||||
page.getAnnotations().add(link);
|
||||
checkSecCompliance(doc, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SEC Compliance: Embedded File should fail")
|
||||
void testSecCompliance_EmbeddedFile() throws Exception {
|
||||
try (PDDocument doc = new PDDocument()) {
|
||||
doc.addPage(new PDPage());
|
||||
|
||||
PDComplexFileSpecification fs = new PDComplexFileSpecification();
|
||||
fs.setFile("test.txt");
|
||||
PDEmbeddedFile ef =
|
||||
new PDEmbeddedFile(doc, new ByteArrayInputStream("test".getBytes()));
|
||||
fs.setEmbeddedFile(ef);
|
||||
|
||||
PDEmbeddedFilesNameTreeNode efTree = new PDEmbeddedFilesNameTreeNode();
|
||||
efTree.setNames(Collections.singletonMap("test", fs));
|
||||
|
||||
PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog());
|
||||
names.setEmbeddedFiles(efTree);
|
||||
doc.getDocumentCatalog().setNames(names);
|
||||
|
||||
checkSecCompliance(doc, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkSecCompliance(PDDocument doc, boolean expected) throws Exception {
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
doc.save(baos);
|
||||
byte[] bytes = baos.toByteArray();
|
||||
|
||||
Mockito.when(
|
||||
pdfDocumentFactory.load(
|
||||
ArgumentMatchers.any(MultipartFile.class),
|
||||
ArgumentMatchers.anyBoolean()))
|
||||
.thenReturn(Loader.loadPDF(bytes));
|
||||
|
||||
PDFFile request = new PDFFile();
|
||||
request.setFileInput(
|
||||
new MockMultipartFile("file", "test.pdf", "application/pdf", bytes));
|
||||
ResponseEntity<byte[]> response = getInfoOnPDF.getPdfInfo(request);
|
||||
|
||||
String jsonResponse = new String(response.getBody(), StandardCharsets.UTF_8);
|
||||
JsonNode jsonNode = objectMapper.readTree(jsonResponse);
|
||||
boolean actual = jsonNode.get("Compliancy").get("IsPDF/SECCompliant").asBoolean();
|
||||
|
||||
Assertions.assertEquals(expected, actual, "SEC Compliance check failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-35
@@ -49,7 +49,6 @@ import stirling.software.proprietary.security.model.User;
|
||||
import stirling.software.proprietary.security.repository.TeamRepository;
|
||||
import stirling.software.proprietary.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
||||
import stirling.software.proprietary.security.service.DatabaseService;
|
||||
import stirling.software.proprietary.security.service.MfaService;
|
||||
import stirling.software.proprietary.security.service.TeamService;
|
||||
import stirling.software.proprietary.security.session.SessionPersistentRegistry;
|
||||
import stirling.software.proprietary.service.UserLicenseSettingsService;
|
||||
@@ -69,7 +68,6 @@ public class ProprietaryUIDataController {
|
||||
private final ObjectMapper objectMapper;
|
||||
private final UserLicenseSettingsService licenseSettingsService;
|
||||
private final PersistentAuditEventRepository auditRepository;
|
||||
private final MfaService mfaService;
|
||||
|
||||
public ProprietaryUIDataController(
|
||||
ApplicationProperties applicationProperties,
|
||||
@@ -82,8 +80,7 @@ public class ProprietaryUIDataController {
|
||||
ObjectMapper objectMapper,
|
||||
@Qualifier("runningEE") boolean runningEE,
|
||||
UserLicenseSettingsService licenseSettingsService,
|
||||
PersistentAuditEventRepository auditRepository,
|
||||
MfaService mfaService) {
|
||||
PersistentAuditEventRepository auditRepository) {
|
||||
this.applicationProperties = applicationProperties;
|
||||
this.auditConfig = auditConfig;
|
||||
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||
@@ -95,7 +92,6 @@ public class ProprietaryUIDataController {
|
||||
this.runningEE = runningEE;
|
||||
this.licenseSettingsService = licenseSettingsService;
|
||||
this.auditRepository = auditRepository;
|
||||
this.mfaService = mfaService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,14 +245,12 @@ public class ProprietaryUIDataController {
|
||||
|
||||
Map<String, Boolean> userSessions = new HashMap<>();
|
||||
Map<String, Date> userLastRequest = new HashMap<>();
|
||||
Map<String, Map<String, String>> userSettings = new HashMap<>();
|
||||
int activeUsers = 0;
|
||||
int disabledUsers = 0;
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
User user = iterator.next();
|
||||
if (user != null) {
|
||||
String username = user.getUsername();
|
||||
boolean shouldRemove = false;
|
||||
|
||||
// Check if user is an INTERNAL_API_USER
|
||||
@@ -270,7 +264,7 @@ public class ProprietaryUIDataController {
|
||||
|
||||
// Check if user is part of the Internal team
|
||||
if (user.getTeam() != null
|
||||
&& TeamService.INTERNAL_TEAM_NAME.equals(user.getTeam().getName())) {
|
||||
&& user.getTeam().getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
|
||||
shouldRemove = true;
|
||||
}
|
||||
|
||||
@@ -284,7 +278,7 @@ public class ProprietaryUIDataController {
|
||||
boolean hasActiveSession = false;
|
||||
Date lastRequest = null;
|
||||
Optional<SessionEntity> latestSession =
|
||||
sessionPersistentRegistry.findLatestSession(username);
|
||||
sessionPersistentRegistry.findLatestSession(user.getUsername());
|
||||
|
||||
if (latestSession.isPresent()) {
|
||||
SessionEntity sessionEntity = latestSession.get();
|
||||
@@ -305,21 +299,8 @@ public class ProprietaryUIDataController {
|
||||
lastRequest = new Date(0);
|
||||
}
|
||||
|
||||
User userWithSettings =
|
||||
userRepository.findByIdWithSettings(user.getId()).orElse(user);
|
||||
|
||||
// Mask mfaSecret if present in settings
|
||||
Map<String, String> originalSettings = userWithSettings.getSettings();
|
||||
Map<String, String> settingsCopy =
|
||||
originalSettings != null
|
||||
? new HashMap<>(originalSettings)
|
||||
: new HashMap<>();
|
||||
if (settingsCopy.containsKey("mfaSecret")) {
|
||||
settingsCopy.put("mfaSecret", "********");
|
||||
}
|
||||
userSettings.put(username, settingsCopy);
|
||||
userSessions.put(username, hasActiveSession);
|
||||
userLastRequest.put(username, lastRequest);
|
||||
userSessions.put(user.getUsername(), hasActiveSession);
|
||||
userLastRequest.put(user.getUsername(), lastRequest);
|
||||
|
||||
if (hasActiveSession) activeUsers++;
|
||||
if (!user.isEnabled()) disabledUsers++;
|
||||
@@ -348,7 +329,7 @@ public class ProprietaryUIDataController {
|
||||
|
||||
List<Team> allTeams =
|
||||
teamRepository.findAll().stream()
|
||||
.filter(team -> !TeamService.INTERNAL_TEAM_NAME.equals(team.getName()))
|
||||
.filter(team -> !team.getName().equals(TeamService.INTERNAL_TEAM_NAME))
|
||||
.toList();
|
||||
|
||||
// Calculate license limits
|
||||
@@ -375,7 +356,6 @@ public class ProprietaryUIDataController {
|
||||
data.setLicenseMaxUsers(licenseMaxUsers);
|
||||
data.setPremiumEnabled(premiumEnabled);
|
||||
data.setMailEnabled(applicationProperties.getMail().isEnabled());
|
||||
data.setUserSettings(userSettings);
|
||||
|
||||
return ResponseEntity.ok(data);
|
||||
}
|
||||
@@ -427,8 +407,6 @@ public class ProprietaryUIDataController {
|
||||
data.setChangeCredsFlag(user.get().isFirstLogin() || user.get().isForcePasswordChange());
|
||||
data.setOAuth2Login(isOAuth2Login);
|
||||
data.setSaml2Login(isSaml2Login);
|
||||
data.setMfaEnabled(mfaService.isMfaEnabled(user.get()));
|
||||
data.setMfaRequired(mfaService.isMfaRequired(user.get()));
|
||||
|
||||
return ResponseEntity.ok(data);
|
||||
}
|
||||
@@ -440,7 +418,7 @@ public class ProprietaryUIDataController {
|
||||
List<TeamWithUserCountDTO> allTeamsWithCounts = teamRepository.findAllTeamsWithUserCount();
|
||||
List<TeamWithUserCountDTO> teamsWithCounts =
|
||||
allTeamsWithCounts.stream()
|
||||
.filter(team -> !TeamService.INTERNAL_TEAM_NAME.equals(team.getName()))
|
||||
.filter(team -> !team.getName().equals(TeamService.INTERNAL_TEAM_NAME))
|
||||
.toList();
|
||||
|
||||
List<Object[]> teamActivities = sessionRepository.findLatestActivityByTeam();
|
||||
@@ -468,7 +446,7 @@ public class ProprietaryUIDataController {
|
||||
.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Team not found"));
|
||||
|
||||
if (TeamService.INTERNAL_TEAM_NAME.equals(team.getName())) {
|
||||
if (team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
|
||||
return ResponseEntity.status(403).build();
|
||||
}
|
||||
|
||||
@@ -481,8 +459,11 @@ public class ProprietaryUIDataController {
|
||||
(user.getTeam() == null
|
||||
|| !user.getTeam().getId().equals(id))
|
||||
&& (user.getTeam() == null
|
||||
|| !TeamService.INTERNAL_TEAM_NAME.equals(
|
||||
user.getTeam().getName())))
|
||||
|| !user.getTeam()
|
||||
.getName()
|
||||
.equals(
|
||||
TeamService
|
||||
.INTERNAL_TEAM_NAME)))
|
||||
.toList();
|
||||
|
||||
List<Object[]> userSessions = sessionRepository.findLatestSessionByTeamId(id);
|
||||
@@ -560,7 +541,6 @@ public class ProprietaryUIDataController {
|
||||
private int licenseMaxUsers;
|
||||
private boolean premiumEnabled;
|
||||
private boolean mailEnabled;
|
||||
private Map<String, Map<String, String>> userSettings;
|
||||
}
|
||||
|
||||
@Data
|
||||
@@ -571,8 +551,6 @@ public class ProprietaryUIDataController {
|
||||
private boolean changeCredsFlag;
|
||||
private boolean oAuth2Login;
|
||||
private boolean saml2Login;
|
||||
private boolean mfaEnabled;
|
||||
private boolean mfaRequired;
|
||||
}
|
||||
|
||||
@Data
|
||||
|
||||
+1
-2
@@ -33,8 +33,7 @@ public class PersistentAuditEvent {
|
||||
private String principal;
|
||||
private String type;
|
||||
|
||||
@Column(columnDefinition = "text")
|
||||
private String data; // JSON blob
|
||||
@Lob private String data; // JSON blob
|
||||
|
||||
private Instant timestamp;
|
||||
}
|
||||
|
||||
+10
-25
@@ -19,7 +19,6 @@ import stirling.software.common.model.exception.UnsupportedProviderException;
|
||||
import stirling.software.proprietary.model.Team;
|
||||
import stirling.software.proprietary.security.model.User;
|
||||
import stirling.software.proprietary.security.service.DatabaseServiceInterface;
|
||||
import stirling.software.proprietary.security.service.SaveUserRequest;
|
||||
import stirling.software.proprietary.security.service.TeamService;
|
||||
import stirling.software.proprietary.security.service.UserService;
|
||||
import stirling.software.proprietary.service.UserLicenseSettingsService;
|
||||
@@ -114,14 +113,8 @@ public class InitialSecuritySetup {
|
||||
&& userService.findByUsernameIgnoreCase(initialUsername).isEmpty()) {
|
||||
|
||||
Team team = teamService.getOrCreateDefaultTeam();
|
||||
SaveUserRequest.Builder builder =
|
||||
SaveUserRequest.builder()
|
||||
.username(initialUsername)
|
||||
.password(initialPassword)
|
||||
.team(team)
|
||||
.role(Role.ADMIN.getRoleId())
|
||||
.firstLogin(false);
|
||||
userService.saveUserCore(builder.build());
|
||||
userService.saveUser(
|
||||
initialUsername, initialPassword, team, Role.ADMIN.getRoleId(), false);
|
||||
log.info("Admin user created: {}", initialUsername);
|
||||
} else {
|
||||
createDefaultAdminUser();
|
||||
@@ -134,14 +127,8 @@ public class InitialSecuritySetup {
|
||||
|
||||
if (userService.findByUsernameIgnoreCase(defaultUsername).isEmpty()) {
|
||||
Team team = teamService.getOrCreateDefaultTeam();
|
||||
SaveUserRequest.Builder builder =
|
||||
SaveUserRequest.builder()
|
||||
.username(defaultUsername)
|
||||
.password(defaultPassword)
|
||||
.team(team)
|
||||
.role(Role.ADMIN.getRoleId())
|
||||
.firstLogin(true);
|
||||
userService.saveUserCore(builder.build());
|
||||
userService.saveUser(
|
||||
defaultUsername, defaultPassword, team, Role.ADMIN.getRoleId(), true);
|
||||
log.info("Default admin user created: {}", defaultUsername);
|
||||
}
|
||||
}
|
||||
@@ -150,14 +137,12 @@ public class InitialSecuritySetup {
|
||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||
if (!userService.usernameExistsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) {
|
||||
Team team = teamService.getOrCreateInternalTeam();
|
||||
SaveUserRequest.Builder builder =
|
||||
SaveUserRequest.builder()
|
||||
.username(Role.INTERNAL_API_USER.getRoleId())
|
||||
.password(UUID.randomUUID().toString())
|
||||
.team(team)
|
||||
.role(Role.INTERNAL_API_USER.getRoleId())
|
||||
.firstLogin(false);
|
||||
userService.saveUserCore(builder.build());
|
||||
userService.saveUser(
|
||||
Role.INTERNAL_API_USER.getRoleId(),
|
||||
UUID.randomUUID().toString(),
|
||||
team,
|
||||
Role.INTERNAL_API_USER.getRoleId(),
|
||||
false);
|
||||
userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
|
||||
log.info("Internal API user created: {}", Role.INTERNAL_API_USER.getRoleId());
|
||||
} else {
|
||||
|
||||
+3
-54
@@ -8,7 +8,6 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.authentication.ProviderManager;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
@@ -186,39 +185,11 @@ public class SecurityConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
public SecurityFilterChain samlFilterChain(
|
||||
HttpSecurity http,
|
||||
@Lazy IPRateLimitingFilter rateLimitingFilter,
|
||||
@Lazy JwtAuthenticationFilter jwtAuthenticationFilter)
|
||||
throws Exception {
|
||||
http.securityMatcher("/saml2/**", "/login/saml2/**");
|
||||
|
||||
SessionCreationPolicy sessionPolicy =
|
||||
(securityProperties.isSaml2Active() && runningProOrHigher)
|
||||
? SessionCreationPolicy.IF_REQUIRED
|
||||
: SessionCreationPolicy.STATELESS;
|
||||
|
||||
return configureSecurity(http, rateLimitingFilter, jwtAuthenticationFilter, sessionPolicy);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(2)
|
||||
public SecurityFilterChain filterChain(
|
||||
HttpSecurity http,
|
||||
@Lazy IPRateLimitingFilter rateLimitingFilter,
|
||||
@Lazy JwtAuthenticationFilter jwtAuthenticationFilter)
|
||||
throws Exception {
|
||||
SessionCreationPolicy sessionPolicy = SessionCreationPolicy.STATELESS;
|
||||
return configureSecurity(http, rateLimitingFilter, jwtAuthenticationFilter, sessionPolicy);
|
||||
}
|
||||
|
||||
private SecurityFilterChain configureSecurity(
|
||||
HttpSecurity http,
|
||||
@Lazy IPRateLimitingFilter rateLimitingFilter,
|
||||
@Lazy JwtAuthenticationFilter jwtAuthenticationFilter,
|
||||
SessionCreationPolicy sessionPolicy)
|
||||
throws Exception {
|
||||
// Enable CORS only if we have configured origins
|
||||
CorsConfigurationSource corsSource = corsConfigurationSource();
|
||||
if (corsSource != null) {
|
||||
@@ -230,30 +201,6 @@ public class SecurityConfiguration {
|
||||
|
||||
http.csrf(CsrfConfigurer::disable);
|
||||
|
||||
// Configure X-Frame-Options based on settings.yml configuration
|
||||
// When login is disabled, automatically disable X-Frame-Options to allow embedding
|
||||
if (!loginEnabledValue) {
|
||||
http.headers(headers -> headers.frameOptions(frameOptions -> frameOptions.disable()));
|
||||
} else {
|
||||
String xFrameOption = securityProperties.getXFrameOptions();
|
||||
if (xFrameOption != null) {
|
||||
http.headers(
|
||||
headers -> {
|
||||
if ("DISABLED".equalsIgnoreCase(xFrameOption)) {
|
||||
headers.frameOptions(frameOptions -> frameOptions.disable());
|
||||
} else if ("SAMEORIGIN".equalsIgnoreCase(xFrameOption)) {
|
||||
headers.frameOptions(frameOptions -> frameOptions.sameOrigin());
|
||||
} else {
|
||||
// Default to DENY
|
||||
headers.frameOptions(frameOptions -> frameOptions.deny());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// If not configured, use default DENY
|
||||
http.headers(headers -> headers.frameOptions(frameOptions -> frameOptions.deny()));
|
||||
}
|
||||
}
|
||||
|
||||
if (loginEnabledValue) {
|
||||
|
||||
http.addFilterBefore(
|
||||
@@ -262,7 +209,9 @@ public class SecurityConfiguration {
|
||||
.addFilterBefore(jwtAuthenticationFilter, UserAuthenticationFilter.class);
|
||||
|
||||
http.sessionManagement(
|
||||
sessionManagement -> sessionManagement.sessionCreationPolicy(sessionPolicy));
|
||||
sessionManagement ->
|
||||
sessionManagement.sessionCreationPolicy(
|
||||
SessionCreationPolicy.STATELESS));
|
||||
http.authenticationProvider(daoAuthenticationProvider());
|
||||
http.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()));
|
||||
|
||||
|
||||
-55
@@ -186,13 +186,6 @@ public class AdminSettingsController {
|
||||
+ HtmlUtils.htmlEscape(key)));
|
||||
}
|
||||
|
||||
// Validate pipeline path settings
|
||||
String validationError = validatePipelinePathSetting(key, value);
|
||||
if (validationError != null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("error", HtmlUtils.htmlEscape(validationError)));
|
||||
}
|
||||
|
||||
log.info("Admin updating setting: {} = {}", key, value);
|
||||
GeneralUtils.saveKeyToSettings(key, value);
|
||||
|
||||
@@ -649,54 +642,6 @@ public class AdminSettingsController {
|
||||
return true;
|
||||
}
|
||||
|
||||
private String validatePipelinePathSetting(String key, Object value) {
|
||||
// Validate pipeline path settings
|
||||
if (key.startsWith("system.customPaths.pipeline.watchedFoldersDirs")
|
||||
&& value instanceof java.util.List) {
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.List<String> paths = (java.util.List<String>) value;
|
||||
|
||||
// Check for empty or all-blank paths
|
||||
if (paths.isEmpty()) {
|
||||
return null; // Empty is OK, will use default
|
||||
}
|
||||
|
||||
// Validate each path
|
||||
java.util.Set<String> normalizedPaths = new java.util.HashSet<>();
|
||||
for (String path : paths) {
|
||||
if (path != null && !path.trim().isEmpty()) {
|
||||
try {
|
||||
java.nio.file.Path normalized =
|
||||
java.nio.file.Paths.get(path.trim()).toAbsolutePath().normalize();
|
||||
String normalizedStr = normalized.toString();
|
||||
|
||||
// Check for duplicates
|
||||
if (normalizedPaths.contains(normalizedStr)) {
|
||||
return "Duplicate path detected: " + path;
|
||||
}
|
||||
normalizedPaths.add(normalizedStr);
|
||||
} catch (java.nio.file.InvalidPathException e) {
|
||||
return "Invalid path: " + path + " - " + e.getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for overlapping paths
|
||||
java.util.List<String> pathList = new java.util.ArrayList<>(normalizedPaths);
|
||||
for (int i = 0; i < pathList.size(); i++) {
|
||||
java.nio.file.Path path1 = java.nio.file.Paths.get(pathList.get(i));
|
||||
for (int j = i + 1; j < pathList.size(); j++) {
|
||||
java.nio.file.Path path2 = java.nio.file.Paths.get(pathList.get(j));
|
||||
if (path1.startsWith(path2) || path2.startsWith(path1)) {
|
||||
return "Overlapping paths detected: " + path1 + " and " + path2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null; // Valid
|
||||
}
|
||||
|
||||
private Object getSettingByKey(String key) {
|
||||
if (key == null || key.trim().isEmpty()) {
|
||||
return null;
|
||||
|
||||
+5
-276
@@ -11,12 +11,7 @@ import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
@@ -32,13 +27,10 @@ import stirling.software.proprietary.audit.AuditLevel;
|
||||
import stirling.software.proprietary.audit.Audited;
|
||||
import stirling.software.proprietary.security.model.AuthenticationType;
|
||||
import stirling.software.proprietary.security.model.User;
|
||||
import stirling.software.proprietary.security.model.api.user.MfaCodeRequest;
|
||||
import stirling.software.proprietary.security.model.api.user.UsernameAndPassMfa;
|
||||
import stirling.software.proprietary.security.model.api.user.UsernameAndPass;
|
||||
import stirling.software.proprietary.security.service.CustomUserDetailsService;
|
||||
import stirling.software.proprietary.security.service.JwtServiceInterface;
|
||||
import stirling.software.proprietary.security.service.LoginAttemptService;
|
||||
import stirling.software.proprietary.security.service.MfaService;
|
||||
import stirling.software.proprietary.security.service.TotpService;
|
||||
import stirling.software.proprietary.security.service.UserService;
|
||||
|
||||
/** REST API Controller for authentication operations. */
|
||||
@@ -53,8 +45,6 @@ public class AuthController {
|
||||
private final JwtServiceInterface jwtService;
|
||||
private final CustomUserDetailsService userDetailsService;
|
||||
private final LoginAttemptService loginAttemptService;
|
||||
private final MfaService mfaService;
|
||||
private final TotpService totpService;
|
||||
private final ApplicationProperties.Security securityProperties;
|
||||
|
||||
/**
|
||||
@@ -68,7 +58,7 @@ public class AuthController {
|
||||
@PostMapping("/login")
|
||||
@Audited(type = AuditEventType.USER_LOGIN, level = AuditLevel.BASIC)
|
||||
public ResponseEntity<?> login(
|
||||
@RequestBody UsernameAndPassMfa request,
|
||||
@RequestBody UsernameAndPass request,
|
||||
HttpServletRequest httpRequest,
|
||||
HttpServletResponse response) {
|
||||
try {
|
||||
@@ -126,47 +116,6 @@ public class AuthController {
|
||||
.body(Map.of("error", "User account is disabled"));
|
||||
}
|
||||
|
||||
if (mfaService.isMfaEnabled(user)) {
|
||||
String code = request.getMfaCode();
|
||||
if (code == null || code.isBlank()) {
|
||||
log.warn(
|
||||
"MFA required but no code provided for user: {} from IP: {}",
|
||||
username,
|
||||
ip);
|
||||
// loginAttemptService.loginFailed(username);
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(
|
||||
Map.of(
|
||||
"error", "mfa_required",
|
||||
"message", "Two-factor code required"));
|
||||
}
|
||||
String secret = mfaService.getSecret(user);
|
||||
if (secret == null || secret.isBlank()) {
|
||||
log.error("MFA enabled but no secret stored for user: {}", username);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(Map.of("error", "MFA configuration error"));
|
||||
}
|
||||
Long timeStep = totpService.getValidTimeStep(secret, code);
|
||||
if (timeStep == null) {
|
||||
log.warn("Invalid MFA code for user: {} from IP: {}", username, ip);
|
||||
loginAttemptService.loginFailed(username);
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(
|
||||
Map.of(
|
||||
"error", "invalid_mfa_code",
|
||||
"message", "Invalid two-factor code"));
|
||||
}
|
||||
if (!mfaService.markTotpStepUsed(user, timeStep)) {
|
||||
log.warn("Replay MFA code detected for user: {} from IP: {}", username, ip);
|
||||
loginAttemptService.loginFailed(username);
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(
|
||||
Map.of(
|
||||
"error", "invalid_mfa_code",
|
||||
"message", "Invalid two-factor code"));
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("authType", AuthenticationType.WEB.toString());
|
||||
claims.put("role", user.getRolesAsString());
|
||||
@@ -214,7 +163,7 @@ public class AuthController {
|
||||
|
||||
if (auth == null
|
||||
|| !auth.isAuthenticated()
|
||||
|| "anonymousUser".equals(auth.getPrincipal())) {
|
||||
|| auth.getPrincipal().equals("anonymousUser")) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(Map.of("error", "Not authenticated"));
|
||||
}
|
||||
@@ -286,10 +235,7 @@ public class AuthController {
|
||||
|
||||
log.debug("Token refreshed for user: {}", username);
|
||||
|
||||
return ResponseEntity.ok(
|
||||
Map.of(
|
||||
"user", buildUserResponse(user),
|
||||
"session", Map.of("access_token", newToken, "expires_in", 3600)));
|
||||
return ResponseEntity.ok(Map.of("access_token", newToken, "expires_in", 3600));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Token refresh error", e);
|
||||
@@ -298,210 +244,6 @@ public class AuthController {
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated() && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@GetMapping("/mfa/setup")
|
||||
public ResponseEntity<?> setupMfa(Authentication authentication) {
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(Map.of("error", "Not authenticated"));
|
||||
}
|
||||
|
||||
String username = authentication.getName();
|
||||
User user =
|
||||
userService
|
||||
.findByUsernameIgnoreCaseWithSettings(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
ResponseEntity<?> authTypeResponse = ensureWebAuth(user);
|
||||
if (authTypeResponse != null) {
|
||||
return authTypeResponse;
|
||||
}
|
||||
|
||||
if (mfaService.isMfaEnabled(user)) {
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT)
|
||||
.body(Map.of("error", "MFA already enabled"));
|
||||
}
|
||||
|
||||
try {
|
||||
String secret = totpService.generateSecret();
|
||||
mfaService.setSecret(user, secret);
|
||||
String otpAuthUri = totpService.buildOtpAuthUri(username, secret);
|
||||
|
||||
return ResponseEntity.ok(Map.of("secret", secret, "otpauthUri", otpAuthUri));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to setup MFA for user: {}", username, e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(Map.of("error", "Failed to setup MFA"));
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated() && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/mfa/enable")
|
||||
public ResponseEntity<?> enableMfa(
|
||||
@RequestBody MfaCodeRequest request, Authentication authentication) {
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(Map.of("error", "Not authenticated"));
|
||||
}
|
||||
|
||||
String username = authentication.getName();
|
||||
User user =
|
||||
userService
|
||||
.findByUsernameIgnoreCaseWithSettings(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
ResponseEntity<?> authTypeResponse = ensureWebAuth(user);
|
||||
if (authTypeResponse != null) {
|
||||
return authTypeResponse;
|
||||
}
|
||||
|
||||
String secret = mfaService.getSecret(user);
|
||||
if (secret == null || secret.isBlank()) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(Map.of("error", "MFA setup required"));
|
||||
}
|
||||
|
||||
if (request == null || request.getCode() == null) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(Map.of("error", "MFA code is required"));
|
||||
}
|
||||
|
||||
Long timeStep = totpService.getValidTimeStep(secret, request.getCode());
|
||||
if (timeStep == null) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(Map.of("error", "Invalid two-factor code"));
|
||||
}
|
||||
|
||||
try {
|
||||
if (!mfaService.isTotpStepUsable(user, timeStep)) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(Map.of("error", "Invalid two-factor code"));
|
||||
}
|
||||
mfaService.enableMfa(user);
|
||||
mfaService.markTotpStepUsed(user, timeStep);
|
||||
mfaService.setMfaRequired(user, false);
|
||||
return ResponseEntity.ok(Map.of("enabled", true));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to enable MFA for user: {}", username, e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(Map.of("error", "Failed to enable MFA"));
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated() && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/mfa/disable")
|
||||
public ResponseEntity<?> disableMfa(
|
||||
@RequestBody MfaCodeRequest request, Authentication authentication) {
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(Map.of("error", "Not authenticated"));
|
||||
}
|
||||
|
||||
String username = authentication.getName();
|
||||
User user =
|
||||
userService
|
||||
.findByUsernameIgnoreCaseWithSettings(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
ResponseEntity<?> authTypeResponse = ensureWebAuth(user);
|
||||
if (authTypeResponse != null) {
|
||||
return authTypeResponse;
|
||||
}
|
||||
|
||||
if (!mfaService.isMfaEnabled(user)) {
|
||||
return ResponseEntity.ok(Map.of("enabled", false));
|
||||
}
|
||||
|
||||
String secret = mfaService.getSecret(user);
|
||||
if (secret == null || secret.isBlank()) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(Map.of("error", "MFA configuration missing"));
|
||||
}
|
||||
|
||||
if (request == null || request.getCode() == null) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(Map.of("error", "MFA code is required"));
|
||||
}
|
||||
|
||||
Long timeStep = totpService.getValidTimeStep(secret, request.getCode());
|
||||
if (timeStep == null) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(Map.of("error", "Invalid two-factor code"));
|
||||
}
|
||||
|
||||
try {
|
||||
if (!mfaService.isTotpStepUsable(user, timeStep)) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(Map.of("error", "Invalid two-factor code"));
|
||||
}
|
||||
mfaService.disableMfa(user);
|
||||
mfaService.markTotpStepUsed(user, timeStep);
|
||||
return ResponseEntity.ok(Map.of("enabled", false));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to disable MFA for user: {}", username, e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(Map.of("error", "Failed to disable MFA"));
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated() && !hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/mfa/setup/cancel")
|
||||
public ResponseEntity<?> cancelMfaSetup(Authentication authentication) {
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(Map.of("error", "Not authenticated"));
|
||||
}
|
||||
|
||||
String username = authentication.getName();
|
||||
User user =
|
||||
userService
|
||||
.findByUsernameIgnoreCaseWithSettings(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
|
||||
if (mfaService.isMfaEnabled(user)) {
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT)
|
||||
.body(Map.of("error", "MFA already enabled"));
|
||||
}
|
||||
|
||||
try {
|
||||
mfaService.clearPendingSecret(user);
|
||||
return ResponseEntity.ok(Map.of("cleared", true));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to clear MFA setup for user: {}", username, e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(Map.of("error", "Failed to clear MFA setup"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin endpoint to disable MFA for a user
|
||||
*
|
||||
* @param username Username of the user to disable MFA for
|
||||
* @return Response indicating success or failure
|
||||
*/
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@PostMapping("/mfa/disable/admin/{username}")
|
||||
public ResponseEntity<?> disableMfaByAdmin(@PathVariable String username) {
|
||||
try {
|
||||
User user =
|
||||
userService
|
||||
.findByUsernameIgnoreCaseWithSettings(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
|
||||
if (!mfaService.isMfaEnabled(user)) {
|
||||
return ResponseEntity.ok(Map.of("enabled", false));
|
||||
}
|
||||
|
||||
mfaService.disableMfa(user);
|
||||
return ResponseEntity.ok(Map.of("enabled", false));
|
||||
} catch (UsernameNotFoundException e) {
|
||||
log.warn("User not found for MFA disable: {}", username);
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||
.body(Map.of("error", "User not found"));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to disable MFA for user: {}", username, e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(Map.of("error", "Failed to disable MFA"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to build user response object
|
||||
*
|
||||
@@ -524,19 +266,6 @@ public class AuthController {
|
||||
appMetadata.put("provider", user.getAuthenticationType());
|
||||
userMap.put("app_metadata", appMetadata);
|
||||
|
||||
// Add user metadata
|
||||
Map<String, Object> userMetadata = new HashMap<>();
|
||||
userMetadata.put("firstLogin", user.isFirstLogin());
|
||||
userMap.put("user_metadata", userMetadata);
|
||||
|
||||
return userMap;
|
||||
}
|
||||
|
||||
private ResponseEntity<?> ensureWebAuth(User user) {
|
||||
if (!AuthenticationType.WEB.name().equalsIgnoreCase(user.getAuthenticationType())) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN)
|
||||
.body(Map.of("error", "MFA settings are only available for web accounts"));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-12
@@ -23,7 +23,6 @@ import stirling.software.proprietary.security.model.InviteToken;
|
||||
import stirling.software.proprietary.security.repository.InviteTokenRepository;
|
||||
import stirling.software.proprietary.security.repository.TeamRepository;
|
||||
import stirling.software.proprietary.security.service.EmailService;
|
||||
import stirling.software.proprietary.security.service.SaveUserRequest;
|
||||
import stirling.software.proprietary.security.service.TeamService;
|
||||
import stirling.software.proprietary.security.service.UserService;
|
||||
|
||||
@@ -91,8 +90,7 @@ public class InviteLinkController {
|
||||
.body(
|
||||
Map.of(
|
||||
"error",
|
||||
"An active invite already exists for this email"
|
||||
+ " address"));
|
||||
"An active invite already exists for this email address"));
|
||||
}
|
||||
|
||||
// If sendEmail is requested but no email provided, reject
|
||||
@@ -125,8 +123,7 @@ public class InviteLinkController {
|
||||
+ (currentUserCount + activeInvites)
|
||||
+ "/"
|
||||
+ maxUsers
|
||||
+ " users). Contact your administrator to"
|
||||
+ " upgrade your license."));
|
||||
+ " users). Contact your administrator to upgrade your license."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,13 +457,12 @@ public class InviteLinkController {
|
||||
}
|
||||
|
||||
// Create the user account
|
||||
SaveUserRequest.Builder builder =
|
||||
SaveUserRequest.builder()
|
||||
.username(effectiveEmail)
|
||||
.password(password)
|
||||
.teamId(invite.getTeamId())
|
||||
.role(invite.getRole());
|
||||
userService.saveUserCore(builder.build());
|
||||
userService.saveUser(
|
||||
effectiveEmail,
|
||||
password,
|
||||
invite.getTeamId(),
|
||||
invite.getRole(),
|
||||
false); // Don't force password change
|
||||
|
||||
// Mark invite as used
|
||||
invite.setUsed(true);
|
||||
|
||||
+30
-84
@@ -18,10 +18,7 @@ import org.springframework.security.core.session.SessionInformation;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -46,7 +43,6 @@ import stirling.software.proprietary.security.model.api.user.UsernameAndPass;
|
||||
import stirling.software.proprietary.security.repository.TeamRepository;
|
||||
import stirling.software.proprietary.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
||||
import stirling.software.proprietary.security.service.EmailService;
|
||||
import stirling.software.proprietary.security.service.SaveUserRequest;
|
||||
import stirling.software.proprietary.security.service.TeamService;
|
||||
import stirling.software.proprietary.security.service.UserService;
|
||||
import stirling.software.proprietary.security.session.SessionPersistentRegistry;
|
||||
@@ -70,24 +66,26 @@ public class UserController {
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<?> register(@RequestBody UsernameAndPass usernameAndPass)
|
||||
throws SQLException, UnsupportedProviderException {
|
||||
String username = usernameAndPass.getUsername();
|
||||
String password = usernameAndPass.getPassword();
|
||||
try {
|
||||
log.debug("Registration attempt for user: {}", username);
|
||||
log.debug("Registration attempt for user: {}", usernameAndPass.getUsername());
|
||||
|
||||
if (userService.usernameExistsIgnoreCase(username)) {
|
||||
log.warn("Registration failed: username already exists: {}", username);
|
||||
if (userService.usernameExistsIgnoreCase(usernameAndPass.getUsername())) {
|
||||
log.warn(
|
||||
"Registration failed: username already exists: {}",
|
||||
usernameAndPass.getUsername());
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(Map.of("error", "User already exists"));
|
||||
}
|
||||
|
||||
if (!userService.isUsernameValid(username)) {
|
||||
log.warn("Registration failed: invalid username format: {}", username);
|
||||
if (!userService.isUsernameValid(usernameAndPass.getUsername())) {
|
||||
log.warn(
|
||||
"Registration failed: invalid username format: {}",
|
||||
usernameAndPass.getUsername());
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(Map.of("error", "Invalid username format"));
|
||||
}
|
||||
|
||||
if (password == null || password.isEmpty()) {
|
||||
if (usernameAndPass.getPassword() == null || usernameAndPass.getPassword().isEmpty()) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(Map.of("error", "Password is required"));
|
||||
}
|
||||
@@ -104,16 +102,17 @@ public class UserController {
|
||||
+ ", Available slots: "
|
||||
+ availableSlots));
|
||||
}
|
||||
Team team = teamRepository.findByName(TeamService.DEFAULT_TEAM_NAME).orElse(null);
|
||||
SaveUserRequest.Builder builder =
|
||||
SaveUserRequest.builder()
|
||||
.username(username)
|
||||
.password(password)
|
||||
.team(team)
|
||||
.enabled(false);
|
||||
User user = userService.saveUserCore(builder.build());
|
||||
|
||||
log.info("User registered successfully: {}", username);
|
||||
Team team = teamRepository.findByName(TeamService.DEFAULT_TEAM_NAME).orElse(null);
|
||||
User user =
|
||||
userService.saveUser(
|
||||
usernameAndPass.getUsername(),
|
||||
usernameAndPass.getPassword(),
|
||||
team,
|
||||
Role.USER.getRoleId(),
|
||||
false);
|
||||
|
||||
log.info("User registered successfully: {}", usernameAndPass.getUsername());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.CREATED)
|
||||
.body(
|
||||
@@ -128,7 +127,7 @@ public class UserController {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(Map.of("error", e.getMessage()));
|
||||
} catch (Exception e) {
|
||||
log.error("Registration error for user: {}", username, e);
|
||||
log.error("Registration error for user: {}", usernameAndPass.getUsername(), e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(Map.of("error", "Registration failed: " + e.getMessage()));
|
||||
}
|
||||
@@ -223,7 +222,6 @@ public class UserController {
|
||||
Principal principal,
|
||||
@RequestParam(name = "currentPassword") String currentPassword,
|
||||
@RequestParam(name = "newPassword") String newPassword,
|
||||
@RequestParam(name = "confirmPassword") String confirmPassword,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
throws SQLException, UnsupportedProviderException {
|
||||
@@ -236,43 +234,6 @@ public class UserController {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||
.body(Map.of("error", "userNotFound", "message", "User not found"));
|
||||
}
|
||||
|
||||
if (currentPassword == null
|
||||
|| currentPassword.isEmpty()
|
||||
|| newPassword == null
|
||||
|| newPassword.isEmpty()
|
||||
|| confirmPassword == null
|
||||
|| confirmPassword.isEmpty()) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(
|
||||
Map.of(
|
||||
"error",
|
||||
"missingParameters",
|
||||
"message",
|
||||
"Current password, new password, and confirmation are"
|
||||
+ " required"));
|
||||
}
|
||||
|
||||
if (!newPassword.equals(confirmPassword)) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(
|
||||
Map.of(
|
||||
"error",
|
||||
"passwordMismatch",
|
||||
"message",
|
||||
"New password and confirmation do not match"));
|
||||
}
|
||||
|
||||
if (newPassword.equals(currentPassword)) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(
|
||||
Map.of(
|
||||
"error",
|
||||
"passwordUnchanged",
|
||||
"message",
|
||||
"New password must be different from the current password"));
|
||||
}
|
||||
|
||||
User user = userOpt.get();
|
||||
if (!userService.isPasswordCorrect(user, currentPassword)) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
@@ -365,9 +326,7 @@ public class UserController {
|
||||
@RequestParam(name = "teamId", required = false) Long teamId,
|
||||
@RequestParam(name = "authType") String authType,
|
||||
@RequestParam(name = "forceChange", required = false, defaultValue = "false")
|
||||
boolean forceChange,
|
||||
@RequestParam(name = "forceMFA", required = false, defaultValue = "false")
|
||||
boolean forceMFA)
|
||||
boolean forceChange)
|
||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||
if (!userService.isUsernameValid(username)) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
@@ -389,9 +348,8 @@ public class UserController {
|
||||
+ availableSlots));
|
||||
}
|
||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
||||
User user = null;
|
||||
if (userOpt.isPresent()) {
|
||||
user = userOpt.get();
|
||||
User user = userOpt.get();
|
||||
if (user.getUsername().equalsIgnoreCase(username)) {
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT)
|
||||
.body(Map.of("error", "Username already exists."));
|
||||
@@ -433,9 +391,6 @@ public class UserController {
|
||||
}
|
||||
}
|
||||
|
||||
SaveUserRequest.Builder builder =
|
||||
SaveUserRequest.builder().username(username).teamId(effectiveTeamId).role(role);
|
||||
|
||||
AuthenticationType requestedAuthType;
|
||||
if ("SSO".equalsIgnoreCase(authType)) {
|
||||
requestedAuthType = AuthenticationType.OAUTH2;
|
||||
@@ -447,7 +402,6 @@ public class UserController {
|
||||
.body(Map.of("error", "Invalid authentication type specified."));
|
||||
}
|
||||
}
|
||||
builder.authenticationType(requestedAuthType);
|
||||
|
||||
if (requestedAuthType == AuthenticationType.WEB) {
|
||||
if (password == null || password.isBlank()) {
|
||||
@@ -458,9 +412,10 @@ public class UserController {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(Map.of("error", "Password must be at least 6 characters."));
|
||||
}
|
||||
builder.password(password).firstLogin(forceChange).requireMfa(forceMFA);
|
||||
userService.saveUser(username, password, effectiveTeamId, role, forceChange);
|
||||
} else {
|
||||
userService.saveUser(username, requestedAuthType, effectiveTeamId, role);
|
||||
}
|
||||
userService.saveUserCore(builder.build());
|
||||
return ResponseEntity.ok(Map.of("message", "User created successfully"));
|
||||
}
|
||||
|
||||
@@ -485,8 +440,7 @@ public class UserController {
|
||||
.body(
|
||||
Map.of(
|
||||
"error",
|
||||
"Email service is not configured. Please configure SMTP"
|
||||
+ " settings."));
|
||||
"Email service is not configured. Please configure SMTP settings."));
|
||||
}
|
||||
|
||||
// Parse comma-separated email addresses
|
||||
@@ -704,8 +658,7 @@ public class UserController {
|
||||
.body(
|
||||
Map.of(
|
||||
"error",
|
||||
"User's email is not a valid email address. Notifications"
|
||||
+ " are disabled."));
|
||||
"User's email is not a valid email address. Notifications are disabled."));
|
||||
}
|
||||
|
||||
String loginUrl = buildLoginUrl(request);
|
||||
@@ -886,14 +839,7 @@ public class UserController {
|
||||
String temporaryPassword = java.util.UUID.randomUUID().toString().substring(0, 12);
|
||||
|
||||
// Create user with forceChange=true
|
||||
SaveUserRequest.Builder builder =
|
||||
SaveUserRequest.builder()
|
||||
.username(email)
|
||||
.password(temporaryPassword)
|
||||
.teamId(teamId)
|
||||
.role(role)
|
||||
.firstLogin(true);
|
||||
userService.saveUserCore(builder.build());
|
||||
userService.saveUser(email, temporaryPassword, teamId, role, true);
|
||||
|
||||
// Send invite email
|
||||
try {
|
||||
|
||||
-16
@@ -4,7 +4,6 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
@@ -19,9 +18,6 @@ public interface UserRepository extends JpaRepository<User, Long> {
|
||||
@Query("FROM User u LEFT JOIN FETCH u.settings where upper(u.username) = upper(:username)")
|
||||
Optional<User> findByUsernameIgnoreCaseWithSettings(@Param("username") String username);
|
||||
|
||||
@Query("FROM User u LEFT JOIN FETCH u.settings where u.id = :id")
|
||||
Optional<User> findByIdWithSettings(@Param("id") Long id);
|
||||
|
||||
Optional<User> findByUsername(String username);
|
||||
|
||||
Optional<User> findByApiKey(String apiKey);
|
||||
@@ -80,16 +76,4 @@ public interface UserRepository extends JpaRepository<User, Long> {
|
||||
"SELECT COUNT(u) FROM User u WHERE u.ssoProvider IS NOT NULL "
|
||||
+ "OR LOWER(u.authenticationType) IN ('sso', 'oauth2', 'saml2')")
|
||||
long countSsoUsers();
|
||||
|
||||
@Query(
|
||||
"SELECT COUNT(u) FROM User u JOIN u.settings settings "
|
||||
+ "WHERE KEY(settings) = :key AND settings = :value")
|
||||
long countUsersBySetting(@Param("key") String key, @Param("value") String value);
|
||||
|
||||
@Modifying
|
||||
@Query(
|
||||
value = "DELETE FROM user_settings WHERE user_id = :userId AND setting_key IN (:keys)",
|
||||
nativeQuery = true)
|
||||
void deleteSettingsByUserIdAndKeys(
|
||||
@Param("userId") Long userId, @Param("keys") List<String> keys);
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ public class User implements UserDetails, Serializable {
|
||||
|
||||
@ElementCollection
|
||||
@MapKeyColumn(name = "setting_key")
|
||||
@Lob
|
||||
@Column(name = "setting_value", columnDefinition = "text")
|
||||
@CollectionTable(name = "user_settings", joinColumns = @JoinColumn(name = "user_id"))
|
||||
@JsonIgnore
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
package stirling.software.proprietary.security.model.api.user;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class MfaCodeRequest {
|
||||
|
||||
@Schema(description = "6-digit authentication code from your authenticator app")
|
||||
private String code;
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
package stirling.software.proprietary.security.model.api.user;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class UsernameAndPassMfa extends UsernameAndPass {
|
||||
|
||||
@Schema(description = "6-digit authentication code from authenticator app", example = "123456")
|
||||
private String mfaCode;
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
package stirling.software.proprietary.security.saml2;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.proprietary.security.service.JwtServiceInterface;
|
||||
|
||||
@Slf4j
|
||||
public class JwtSaml2AuthenticationRequestRepository
|
||||
implements Saml2AuthenticationRequestRepository<Saml2PostAuthenticationRequest> {
|
||||
private final Map<String, String> tokenStore;
|
||||
private final JwtServiceInterface jwtService;
|
||||
private final RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
|
||||
|
||||
private static final String SAML_REQUEST_TOKEN = "stirling_saml_request_token";
|
||||
|
||||
public JwtSaml2AuthenticationRequestRepository(
|
||||
Map<String, String> tokenStore,
|
||||
JwtServiceInterface jwtService,
|
||||
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
|
||||
this.tokenStore = tokenStore;
|
||||
this.jwtService = jwtService;
|
||||
this.relyingPartyRegistrationRepository = relyingPartyRegistrationRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAuthenticationRequest(
|
||||
Saml2PostAuthenticationRequest authRequest,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
if (!jwtService.isJwtEnabled()) {
|
||||
log.debug("V2 is not enabled, skipping SAMLRequest token storage");
|
||||
return;
|
||||
}
|
||||
|
||||
if (authRequest == null) {
|
||||
removeAuthenticationRequest(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> claims = serializeSamlRequest(authRequest);
|
||||
String token = jwtService.generateToken("", claims);
|
||||
String relayState = authRequest.getRelayState();
|
||||
|
||||
tokenStore.put(relayState, token);
|
||||
request.setAttribute(SAML_REQUEST_TOKEN, relayState);
|
||||
response.addHeader(SAML_REQUEST_TOKEN, relayState);
|
||||
|
||||
log.debug("Saved SAMLRequest token with RelayState: {}", relayState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Saml2PostAuthenticationRequest loadAuthenticationRequest(HttpServletRequest request) {
|
||||
String token = extractTokenFromStore(request);
|
||||
|
||||
if (token == null) {
|
||||
log.debug("No SAMLResponse token found in RelayState");
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Object> claims = jwtService.extractClaims(token);
|
||||
return deserializeSamlRequest(claims);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Saml2PostAuthenticationRequest removeAuthenticationRequest(
|
||||
HttpServletRequest request, HttpServletResponse response) {
|
||||
Saml2PostAuthenticationRequest authRequest = loadAuthenticationRequest(request);
|
||||
|
||||
String relayStateId = request.getParameter("RelayState");
|
||||
if (relayStateId != null) {
|
||||
tokenStore.remove(relayStateId);
|
||||
log.debug("Removed SAMLRequest token for RelayState ID: {}", relayStateId);
|
||||
}
|
||||
|
||||
return authRequest;
|
||||
}
|
||||
|
||||
private String extractTokenFromStore(HttpServletRequest request) {
|
||||
String authnRequestId = request.getParameter("RelayState");
|
||||
|
||||
if (authnRequestId != null && !authnRequestId.isEmpty()) {
|
||||
String token = tokenStore.get(authnRequestId);
|
||||
|
||||
if (token != null) {
|
||||
tokenStore.remove(authnRequestId);
|
||||
log.debug("Retrieved SAMLRequest token for RelayState ID: {}", authnRequestId);
|
||||
return token;
|
||||
} else {
|
||||
log.warn("No SAMLRequest token found for RelayState ID: {}", authnRequestId);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<String, Object> serializeSamlRequest(Saml2PostAuthenticationRequest authRequest) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
|
||||
claims.put("id", authRequest.getId());
|
||||
claims.put("relyingPartyRegistrationId", authRequest.getRelyingPartyRegistrationId());
|
||||
claims.put("authenticationRequestUri", authRequest.getAuthenticationRequestUri());
|
||||
claims.put("samlRequest", authRequest.getSamlRequest());
|
||||
claims.put("relayState", authRequest.getRelayState());
|
||||
|
||||
return claims;
|
||||
}
|
||||
|
||||
private Saml2PostAuthenticationRequest deserializeSamlRequest(Map<String, Object> claims) {
|
||||
String relyingPartyRegistrationId = (String) claims.get("relyingPartyRegistrationId");
|
||||
RelyingPartyRegistration relyingPartyRegistration =
|
||||
relyingPartyRegistrationRepository.findByRegistrationId(relyingPartyRegistrationId);
|
||||
|
||||
if (relyingPartyRegistration == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Saml2PostAuthenticationRequest.withRelyingPartyRegistration(relyingPartyRegistration)
|
||||
.id((String) claims.get("id"))
|
||||
.authenticationRequestUri((String) claims.get("authenticationRequestUri"))
|
||||
.samlRequest((String) claims.get("samlRequest"))
|
||||
.relayState((String) claims.get("relayState"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+39
-3
@@ -3,6 +3,7 @@ package stirling.software.proprietary.security.saml2;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@@ -11,10 +12,12 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -24,6 +27,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.common.model.ApplicationProperties.Security.SAML2;
|
||||
import stirling.software.proprietary.security.service.JwtServiceInterface;
|
||||
|
||||
@Configuration
|
||||
@Slf4j
|
||||
@@ -149,10 +153,22 @@ public class Saml2Configuration {
|
||||
return new InMemoryRelyingPartyRegistrationRepository(rp);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
||||
public Saml2AuthenticationRequestRepository<Saml2PostAuthenticationRequest>
|
||||
saml2AuthenticationRequestRepository(
|
||||
JwtServiceInterface jwtService,
|
||||
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
|
||||
return new JwtSaml2AuthenticationRequestRepository(
|
||||
new ConcurrentHashMap<>(), jwtService, relyingPartyRegistrationRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true")
|
||||
public OpenSaml4AuthenticationRequestResolver authenticationRequestResolver(
|
||||
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
|
||||
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository,
|
||||
Saml2AuthenticationRequestRepository<Saml2PostAuthenticationRequest>
|
||||
saml2AuthenticationRequestRepository) {
|
||||
OpenSaml4AuthenticationRequestResolver resolver =
|
||||
new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository);
|
||||
|
||||
@@ -160,10 +176,30 @@ public class Saml2Configuration {
|
||||
customizer -> {
|
||||
HttpServletRequest request = customizer.getRequest();
|
||||
AuthnRequest authnRequest = customizer.getAuthnRequest();
|
||||
Saml2PostAuthenticationRequest saml2AuthenticationRequest =
|
||||
saml2AuthenticationRequestRepository.loadAuthenticationRequest(request);
|
||||
|
||||
// Generate a unique AuthnRequest ID for each SAML request
|
||||
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
|
||||
if (saml2AuthenticationRequest != null) {
|
||||
String sessionId = request.getSession(false).getId();
|
||||
|
||||
log.debug(
|
||||
"Retrieving SAML 2 authentication request ID from the current HTTP session {}",
|
||||
sessionId);
|
||||
|
||||
String authenticationRequestId = saml2AuthenticationRequest.getId();
|
||||
|
||||
if (!authenticationRequestId.isBlank()) {
|
||||
authnRequest.setID(authenticationRequestId);
|
||||
} else {
|
||||
log.warn(
|
||||
"No authentication request found for HTTP session {}. Generating new ID",
|
||||
sessionId);
|
||||
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
|
||||
}
|
||||
} else {
|
||||
log.debug("Generating new authentication request ID");
|
||||
authnRequest.setID("ARQ" + UUID.randomUUID().toString().substring(1));
|
||||
}
|
||||
logAuthnRequestDetails(authnRequest);
|
||||
logHttpRequestDetails(request);
|
||||
});
|
||||
|
||||
+2
-6
@@ -21,7 +21,6 @@ import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
@@ -266,17 +265,14 @@ public class DatabaseService implements DatabaseServiceInterface {
|
||||
String checksum = bytesToHex(digest.digest(content));
|
||||
log.info("Checksum for {}: {}", backupPath.getFileName(), checksum);
|
||||
|
||||
String verifyDbUrl = "jdbc:h2:mem:backupVerify_" + UUID.randomUUID();
|
||||
// Use a fresh in-memory database per verification to avoid leftover objects between
|
||||
// runs.
|
||||
try (Connection conn = DriverManager.getConnection(verifyDbUrl);
|
||||
try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:backupVerify");
|
||||
PreparedStatement stmt = conn.prepareStatement("RUNSCRIPT FROM ?")) {
|
||||
stmt.setString(1, backupPath.toString());
|
||||
stmt.execute();
|
||||
}
|
||||
return true;
|
||||
} catch (IOException | NoSuchAlgorithmException | SQLException e) {
|
||||
log.error("Backup verification failed for {}: {}", backupPath, e.getMessage());
|
||||
log.error("Backup verification failed for {}: {}", backupPath, e.getMessage(), e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
-247
@@ -1,247 +0,0 @@
|
||||
package stirling.software.proprietary.security.service;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.common.model.exception.UnsupportedProviderException;
|
||||
import stirling.software.proprietary.security.database.repository.UserRepository;
|
||||
import stirling.software.proprietary.security.model.User;
|
||||
|
||||
/**
|
||||
* Service for managing multi-factor authentication (MFA) settings for users.
|
||||
*
|
||||
* <p>This service reads and writes MFA-related settings such as secrets, enablement flags, and
|
||||
* last-used TOTP steps.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class MfaService {
|
||||
|
||||
public static final String MFA_ENABLED_KEY = "mfaEnabled";
|
||||
public static final String MFA_SECRET_KEY = "mfaSecret";
|
||||
public static final String MFA_LAST_USED_STEP_KEY = "mfaLastUsedStep";
|
||||
public static final String MFA_REQUIRED_KEY = "mfaRequired";
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final DatabaseServiceInterface databaseService;
|
||||
|
||||
/**
|
||||
* Determines whether MFA is enabled for the given user.
|
||||
*
|
||||
* @param user target user
|
||||
* @return {@code true} if MFA is enabled
|
||||
*/
|
||||
public boolean isMfaEnabled(User user) {
|
||||
String value = getSetting(user, MFA_ENABLED_KEY);
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the MFA secret for the given user.
|
||||
*
|
||||
* @param user target user
|
||||
* @return Base32-encoded secret, or {@code null} if not set
|
||||
*/
|
||||
public String getSecret(User user) {
|
||||
return getSetting(user, MFA_SECRET_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a new MFA secret and marks MFA as pending (disabled) for the user.
|
||||
*
|
||||
* @param user target user
|
||||
* @param secret Base32-encoded secret to store
|
||||
* @throws SQLException when database persistence fails
|
||||
* @throws UnsupportedProviderException when the database provider is unsupported
|
||||
*/
|
||||
@Transactional
|
||||
public void setSecret(User user, String secret)
|
||||
throws SQLException, UnsupportedProviderException {
|
||||
User managedUser = getUserWithSettings(user);
|
||||
Map<String, String> settings = ensureSettings(managedUser);
|
||||
settings.put(MFA_ENABLED_KEY, "false");
|
||||
// Clear existing values and flush the removals before inserting a new secret. This keeps
|
||||
// the (user_id, setting_key) PK satisfied even when the persistence context re-inserts the
|
||||
// same keys within a single transaction.
|
||||
settings.remove(MFA_SECRET_KEY);
|
||||
settings.remove(MFA_LAST_USED_STEP_KEY);
|
||||
if (managedUser != null && managedUser.getId() != null) {
|
||||
userRepository.deleteSettingsByUserIdAndKeys(
|
||||
managedUser.getId(), Arrays.asList(MFA_SECRET_KEY, MFA_LAST_USED_STEP_KEY));
|
||||
userRepository.flush();
|
||||
}
|
||||
settings.put(MFA_SECRET_KEY, secret);
|
||||
persist(managedUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables MFA for the given user.
|
||||
*
|
||||
* @param user target user
|
||||
* @throws SQLException when database persistence fails
|
||||
* @throws UnsupportedProviderException when the database provider is unsupported
|
||||
*/
|
||||
public void enableMfa(User user) throws SQLException, UnsupportedProviderException {
|
||||
User managedUser = getUserWithSettings(user);
|
||||
Map<String, String> settings = ensureSettings(managedUser);
|
||||
settings.put(MFA_ENABLED_KEY, "true");
|
||||
persist(managedUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any pending MFA setup data for the user.
|
||||
*
|
||||
* @param user target user
|
||||
* @throws SQLException when database persistence fails
|
||||
* @throws UnsupportedProviderException when the database provider is unsupported
|
||||
*/
|
||||
public void clearPendingSecret(User user) throws SQLException, UnsupportedProviderException {
|
||||
User managedUser = getUserWithSettings(user);
|
||||
Map<String, String> settings = ensureSettings(managedUser);
|
||||
settings.put(MFA_ENABLED_KEY, "false");
|
||||
settings.remove(MFA_SECRET_KEY);
|
||||
settings.remove(MFA_LAST_USED_STEP_KEY);
|
||||
persist(managedUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables MFA and clears stored secrets for the user.
|
||||
*
|
||||
* @param user target user
|
||||
* @throws SQLException when database persistence fails
|
||||
* @throws UnsupportedProviderException when the database provider is unsupported
|
||||
*/
|
||||
public void disableMfa(User user) throws SQLException, UnsupportedProviderException {
|
||||
User managedUser = getUserWithSettings(user);
|
||||
Map<String, String> settings = ensureSettings(managedUser);
|
||||
settings.put(MFA_ENABLED_KEY, "false");
|
||||
settings.remove(MFA_SECRET_KEY);
|
||||
settings.remove(MFA_LAST_USED_STEP_KEY);
|
||||
persist(managedUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a TOTP time step has not been used before.
|
||||
*
|
||||
* @param user target user
|
||||
* @param timeStep candidate TOTP time step
|
||||
* @return {@code true} if the time step is usable
|
||||
*/
|
||||
public boolean isTotpStepUsable(User user, long timeStep) {
|
||||
User managedUser = getUserWithSettings(user);
|
||||
Map<String, String> settings = managedUser.getSettings();
|
||||
if (settings == null) {
|
||||
return true;
|
||||
}
|
||||
String lastUsed = settings.get(MFA_LAST_USED_STEP_KEY);
|
||||
if (lastUsed == null) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
long lastUsedStep = Long.parseLong(lastUsed);
|
||||
return timeStep > lastUsedStep;
|
||||
} catch (NumberFormatException ignored) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a TOTP time step as used, preventing replay.
|
||||
*
|
||||
* @param user target user
|
||||
* @param timeStep time step to mark as used
|
||||
* @return {@code true} if the time step was marked, {@code false} if it was already used
|
||||
* @throws SQLException when database persistence fails
|
||||
* @throws UnsupportedProviderException when the database provider is unsupported
|
||||
*/
|
||||
public boolean markTotpStepUsed(User user, long timeStep)
|
||||
throws SQLException, UnsupportedProviderException {
|
||||
User managedUser = getUserWithSettings(user);
|
||||
Map<String, String> settings = ensureSettings(managedUser);
|
||||
String lastUsed = settings.get(MFA_LAST_USED_STEP_KEY);
|
||||
if (lastUsed != null) {
|
||||
try {
|
||||
long lastUsedStep = Long.parseLong(lastUsed);
|
||||
if (timeStep <= lastUsedStep) {
|
||||
return false;
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
// treat malformed value as unused
|
||||
}
|
||||
}
|
||||
settings.put(MFA_LAST_USED_STEP_KEY, Long.toString(timeStep));
|
||||
persist(managedUser);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether MFA is required for the given user.
|
||||
*
|
||||
* @param user target user
|
||||
* @return {@code true} if MFA is required
|
||||
*/
|
||||
public boolean isMfaRequired(User user) {
|
||||
String value = getSetting(user, MFA_REQUIRED_KEY);
|
||||
if (value == null) {
|
||||
value = "false";
|
||||
}
|
||||
log.info("MFA required for user {}: {}", user.getUsername(), value);
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether MFA is required for the given user.
|
||||
*
|
||||
* @param user target user
|
||||
* @param required {@code true} to require MFA
|
||||
* @throws SQLException when database persistence fails
|
||||
* @throws UnsupportedProviderException when the database provider is unsupported
|
||||
*/
|
||||
public void setMfaRequired(User user, boolean required)
|
||||
throws SQLException, UnsupportedProviderException {
|
||||
User managedUser = getUserWithSettings(user);
|
||||
Map<String, String> settings = ensureSettings(managedUser);
|
||||
settings.put(MFA_REQUIRED_KEY, Boolean.toString(required));
|
||||
log.info("Set MFA required={} for user {}", required, managedUser.getUsername());
|
||||
persist(managedUser);
|
||||
}
|
||||
|
||||
private String getSetting(User user, String key) {
|
||||
User managedUser = getUserWithSettings(user);
|
||||
Map<String, String> settings = managedUser.getSettings();
|
||||
if (settings == null) {
|
||||
return null;
|
||||
}
|
||||
return settings.get(key);
|
||||
}
|
||||
|
||||
private User getUserWithSettings(User user) {
|
||||
if (user == null || user.getId() == null) {
|
||||
return user;
|
||||
}
|
||||
return userRepository.findByIdWithSettings(user.getId()).orElse(user);
|
||||
}
|
||||
|
||||
private Map<String, String> ensureSettings(User user) {
|
||||
Map<String, String> settings = user.getSettings();
|
||||
if (settings == null) {
|
||||
settings = new HashMap<>();
|
||||
user.setSettings(settings);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
private void persist(User user) throws SQLException, UnsupportedProviderException {
|
||||
userRepository.save(user);
|
||||
databaseService.exportDatabase();
|
||||
}
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
package stirling.software.proprietary.security.service;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
import stirling.software.common.model.enumeration.Role;
|
||||
import stirling.software.proprietary.model.Team;
|
||||
import stirling.software.proprietary.security.model.AuthenticationType;
|
||||
|
||||
/**
|
||||
* Carries all attributes required to create or update a user account, including credentials,
|
||||
* SSO/provider details, team association, role and MFA configuration. Used by the security service
|
||||
* layer to persist or update users.
|
||||
*
|
||||
* <p>Defaults:
|
||||
*
|
||||
* <ul>
|
||||
* <li>password: null
|
||||
* <li>ssoProviderId: null
|
||||
* <li>ssoProvider: null
|
||||
* <li>authenticationType: {@code AuthenticationType.WEB}
|
||||
* <li>teamId: null
|
||||
* <li>team: null
|
||||
* <li>role: {@code Role.USER.getRoleId()}
|
||||
* <li>firstLogin: false
|
||||
* <li>enabled: true
|
||||
* <li>requireMfa: false
|
||||
* <li>mfaEnabled: false
|
||||
* <li>mfaSecret: null
|
||||
* <li>mfaLastUsedStep: null
|
||||
* </ul>
|
||||
*/
|
||||
@Getter
|
||||
@Builder(builderClassName = "Builder")
|
||||
public class SaveUserRequest {
|
||||
private final String username;
|
||||
@Builder.Default private final String password = null;
|
||||
@Builder.Default private final String ssoProviderId = null;
|
||||
@Builder.Default private final String ssoProvider = null;
|
||||
@Builder.Default private final AuthenticationType authenticationType = AuthenticationType.WEB;
|
||||
@Builder.Default private final Long teamId = null;
|
||||
@Builder.Default private final Team team = null;
|
||||
@Builder.Default private final String role = Role.USER.getRoleId();
|
||||
@Builder.Default private final boolean firstLogin = false;
|
||||
@Builder.Default private final boolean enabled = true;
|
||||
@Builder.Default private final boolean requireMfa = false;
|
||||
@Builder.Default private final boolean mfaEnabled = false;
|
||||
@Builder.Default private final String mfaSecret = null;
|
||||
@Builder.Default private final Long mfaLastUsedStep = null;
|
||||
}
|
||||
-159
@@ -1,159 +0,0 @@
|
||||
package stirling.software.proprietary.security.service;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.Instant;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.proprietary.security.util.Base32Codec;
|
||||
|
||||
/**
|
||||
* Service for generating and validating TOTP secrets and codes for MFA authentication.
|
||||
*
|
||||
* <p>This service handles secret generation, code validation across time steps, and building
|
||||
* otpauth:// URIs to provision authenticator apps.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TotpService {
|
||||
|
||||
private static final int SECRET_LENGTH_BYTES = 20;
|
||||
private static final int CODE_DIGITS = 6;
|
||||
private static final int PERIOD_SECONDS = 30;
|
||||
private static final String HMAC_ALGORITHM = "HmacSHA1";
|
||||
private static final String DEFAULT_ISSUER = "Stirling PDF";
|
||||
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||
|
||||
private final ApplicationProperties applicationProperties;
|
||||
|
||||
/**
|
||||
* Generates a new random TOTP secret encoded in Base32.
|
||||
*
|
||||
* @return Base32-encoded secret suitable for provisioning an authenticator app
|
||||
*/
|
||||
public String generateSecret() {
|
||||
byte[] secret = new byte[SECRET_LENGTH_BYTES];
|
||||
SECURE_RANDOM.nextBytes(secret);
|
||||
return Base32Codec.encode(secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a submitted TOTP code is valid for the current time window.
|
||||
*
|
||||
* @param secret Base32-encoded shared secret
|
||||
* @param code six-digit code supplied by the user
|
||||
* @return {@code true} if the code is valid for the current time window
|
||||
*/
|
||||
public boolean isValidCode(String secret, String code) {
|
||||
return getValidTimeStep(secret, code) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a TOTP code and returns the time step it matches.
|
||||
*
|
||||
* @param secret Base32-encoded shared secret
|
||||
* @param code six-digit code supplied by the user
|
||||
* @return matching time step, or {@code null} if the code is invalid
|
||||
*/
|
||||
public Long getValidTimeStep(String secret, String code) {
|
||||
if (secret == null || secret.isBlank() || code == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String normalizedCode = code.replace(" ", "");
|
||||
if (!normalizedCode.matches("\\d{6}")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] secretKey;
|
||||
try {
|
||||
secretKey = Base32Codec.decode(secret);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
long timeStep = Instant.now().getEpochSecond() / PERIOD_SECONDS;
|
||||
byte[] normalizedCodeBytes = normalizedCode.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
for (int offset = -1; offset <= 1; offset++) {
|
||||
long candidate = timeStep + offset;
|
||||
String generatedCode = generateCode(secretKey, candidate);
|
||||
if (MessageDigest.isEqual(
|
||||
generatedCode.getBytes(StandardCharsets.UTF_8), normalizedCodeBytes)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an otpauth:// URI for configuring TOTP in authenticator apps.
|
||||
*
|
||||
* @param username account identifier to embed in the label
|
||||
* @param secret Base32-encoded secret to embed in the URI
|
||||
* @return otpauth URI that can be encoded as a QR code
|
||||
*/
|
||||
public String buildOtpAuthUri(String username, String secret) {
|
||||
String issuer = resolveIssuer();
|
||||
String label = encodeForOtpAuth(issuer + ":" + username);
|
||||
String encodedIssuer = encodeForOtpAuth(issuer);
|
||||
|
||||
return "otpauth://totp/"
|
||||
+ label
|
||||
+ "?secret="
|
||||
+ secret
|
||||
+ "&issuer="
|
||||
+ encodedIssuer
|
||||
+ "&algorithm=SHA1&digits="
|
||||
+ CODE_DIGITS
|
||||
+ "&period="
|
||||
+ PERIOD_SECONDS;
|
||||
}
|
||||
|
||||
private String encodeForOtpAuth(String value) {
|
||||
return URLEncoder.encode(value, StandardCharsets.UTF_8).replace("+", "%20");
|
||||
}
|
||||
|
||||
private String resolveIssuer() {
|
||||
if (applicationProperties.getUi() != null) {
|
||||
String appName = applicationProperties.getUi().getAppNameNavbar();
|
||||
if (appName != null && !appName.isBlank()) {
|
||||
return appName.trim();
|
||||
}
|
||||
}
|
||||
return DEFAULT_ISSUER;
|
||||
}
|
||||
|
||||
private String generateCode(byte[] secret, long timeStep) {
|
||||
try {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(8);
|
||||
buffer.putLong(timeStep);
|
||||
|
||||
Mac mac = Mac.getInstance(HMAC_ALGORITHM);
|
||||
mac.init(new SecretKeySpec(secret, HMAC_ALGORITHM));
|
||||
byte[] hash = mac.doFinal(buffer.array());
|
||||
|
||||
int offset = hash[hash.length - 1] & 0x0F;
|
||||
int binary =
|
||||
((hash[offset] & 0x7F) << 24)
|
||||
| ((hash[offset + 1] & 0xFF) << 16)
|
||||
| ((hash[offset + 2] & 0xFF) << 8)
|
||||
| (hash[offset + 3] & 0xFF);
|
||||
|
||||
int otp = binary % (int) Math.pow(10, CODE_DIGITS);
|
||||
return String.format("%0" + CODE_DIGITS + "d", otp);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to generate TOTP code", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+200
-62
@@ -1,10 +1,5 @@
|
||||
package stirling.software.proprietary.security.service;
|
||||
|
||||
import static stirling.software.proprietary.security.service.MfaService.MFA_ENABLED_KEY;
|
||||
import static stirling.software.proprietary.security.service.MfaService.MFA_LAST_USED_STEP_KEY;
|
||||
import static stirling.software.proprietary.security.service.MfaService.MFA_REQUIRED_KEY;
|
||||
import static stirling.software.proprietary.security.service.MfaService.MFA_SECRET_KEY;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -67,7 +62,6 @@ public class UserService implements UserServiceInterface {
|
||||
|
||||
private final ApplicationProperties.Security.OAUTH2 oAuth2;
|
||||
|
||||
@Transactional
|
||||
public void processSSOPostLogin(
|
||||
String username,
|
||||
String ssoProviderId,
|
||||
@@ -107,13 +101,7 @@ public class UserService implements UserServiceInterface {
|
||||
}
|
||||
|
||||
if (autoCreateUser) {
|
||||
SaveUserRequest.Builder builder =
|
||||
SaveUserRequest.builder()
|
||||
.username(username)
|
||||
.ssoProviderId(ssoProviderId)
|
||||
.ssoProvider(ssoProvider)
|
||||
.authenticationType(type);
|
||||
saveUserCore(builder.build());
|
||||
saveUser(username, ssoProviderId, ssoProvider, type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,14 +141,6 @@ public class UserService implements UserServiceInterface {
|
||||
return user;
|
||||
}
|
||||
|
||||
private User saveUser(Optional<User> user, String apiKey) {
|
||||
if (user.isPresent()) {
|
||||
user.get().setApiKey(apiKey);
|
||||
return userRepository.save(user.get());
|
||||
}
|
||||
throw new UsernameNotFoundException("User not found");
|
||||
}
|
||||
|
||||
public User refreshApiKeyForUser(String username) {
|
||||
// reuse the add API key method for refreshing
|
||||
return addApiKeyToUser(username);
|
||||
@@ -199,6 +179,162 @@ public class UserService implements UserServiceInterface {
|
||||
return userOpt.isPresent() && apiKey.equals(userOpt.get().getApiKey());
|
||||
}
|
||||
|
||||
public void saveUser(String username, AuthenticationType authenticationType)
|
||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||
saveUser(username, authenticationType, (Long) null, Role.USER.getRoleId());
|
||||
}
|
||||
|
||||
public void saveUser(
|
||||
String username,
|
||||
String ssoProviderId,
|
||||
String ssoProvider,
|
||||
AuthenticationType authenticationType)
|
||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||
saveUser(
|
||||
username,
|
||||
ssoProviderId,
|
||||
ssoProvider,
|
||||
authenticationType,
|
||||
(Long) null,
|
||||
Role.USER.getRoleId());
|
||||
}
|
||||
|
||||
private User saveUser(Optional<User> user, String apiKey) {
|
||||
if (user.isPresent()) {
|
||||
user.get().setApiKey(apiKey);
|
||||
return userRepository.save(user.get());
|
||||
}
|
||||
throw new UsernameNotFoundException("User not found");
|
||||
}
|
||||
|
||||
public User saveUser(
|
||||
String username, AuthenticationType authenticationType, Long teamId, String role)
|
||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||
return saveUserCore(
|
||||
username, // username
|
||||
null, // password
|
||||
null, // ssoProviderId
|
||||
null, // ssoProvider
|
||||
authenticationType, // authenticationType
|
||||
teamId, // teamId
|
||||
null, // team
|
||||
role, // role
|
||||
false, // firstLogin
|
||||
true // enabled
|
||||
);
|
||||
}
|
||||
|
||||
public User saveUser(
|
||||
String username,
|
||||
String ssoProviderId,
|
||||
String ssoProvider,
|
||||
AuthenticationType authenticationType,
|
||||
Long teamId,
|
||||
String role)
|
||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||
return saveUserCore(
|
||||
username, // username
|
||||
null, // password
|
||||
ssoProviderId, // ssoProviderId
|
||||
ssoProvider, // ssoProvider
|
||||
authenticationType, // authenticationType
|
||||
teamId, // teamId
|
||||
null, // team
|
||||
role, // role
|
||||
false, // firstLogin
|
||||
true // enabled
|
||||
);
|
||||
}
|
||||
|
||||
public User saveUser(
|
||||
String username, AuthenticationType authenticationType, Team team, String role)
|
||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||
return saveUserCore(
|
||||
username, // username
|
||||
null, // password
|
||||
null, // ssoProviderId
|
||||
null, // ssoProvider
|
||||
authenticationType, // authenticationType
|
||||
null, // teamId
|
||||
team, // team
|
||||
role, // role
|
||||
false, // firstLogin
|
||||
true // enabled
|
||||
);
|
||||
}
|
||||
|
||||
public User saveUser(String username, String password, Long teamId)
|
||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||
return saveUserCore(
|
||||
username, // username
|
||||
password, // password
|
||||
null, // ssoProviderId
|
||||
null, // ssoProvider
|
||||
AuthenticationType.WEB, // authenticationType
|
||||
teamId, // teamId
|
||||
null, // team
|
||||
Role.USER.getRoleId(), // role
|
||||
false, // firstLogin
|
||||
true // enabled
|
||||
);
|
||||
}
|
||||
|
||||
public User saveUser(
|
||||
String username, String password, Team team, String role, boolean firstLogin)
|
||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||
return saveUserCore(
|
||||
username, // username
|
||||
password, // password
|
||||
null, // ssoProviderId
|
||||
null, // ssoProvider
|
||||
AuthenticationType.WEB, // authenticationType
|
||||
null, // teamId
|
||||
team, // team
|
||||
role, // role
|
||||
firstLogin, // firstLogin
|
||||
true // enabled
|
||||
);
|
||||
}
|
||||
|
||||
public User saveUser(
|
||||
String username, String password, Long teamId, String role, boolean firstLogin)
|
||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||
return saveUserCore(
|
||||
username, // username
|
||||
password, // password
|
||||
null, // ssoProviderId
|
||||
null, // ssoProvider
|
||||
AuthenticationType.WEB, // authenticationType
|
||||
teamId, // teamId
|
||||
null, // team
|
||||
role, // role
|
||||
firstLogin, // firstLogin
|
||||
true // enabled
|
||||
);
|
||||
}
|
||||
|
||||
public void saveUser(String username, String password, Long teamId, String role)
|
||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||
saveUser(username, password, teamId, role, false);
|
||||
}
|
||||
|
||||
public void saveUser(
|
||||
String username, String password, Long teamId, boolean firstLogin, boolean enabled)
|
||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||
saveUserCore(
|
||||
username, // username
|
||||
password, // password
|
||||
null, // ssoProviderId
|
||||
null, // ssoProvider
|
||||
AuthenticationType.WEB, // authenticationType
|
||||
teamId, // teamId
|
||||
null, // team
|
||||
Role.USER.getRoleId(), // role
|
||||
firstLogin, // firstLogin
|
||||
enabled // enabled
|
||||
);
|
||||
}
|
||||
|
||||
public void deleteUser(String username) {
|
||||
Optional<User> userOpt = findByUsernameIgnoreCase(username);
|
||||
if (userOpt.isPresent()) {
|
||||
@@ -349,71 +485,75 @@ public class UserService implements UserServiceInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Core method to save a user based on the provided SaveUserRequest.
|
||||
* Core implementation for saving a user with all possible parameters. This method centralizes
|
||||
* the common logic for all saveUser variants.
|
||||
*
|
||||
* @param request The SaveUserRequest containing user details
|
||||
* @param username Username for the new user
|
||||
* @param password Password for the user (may be null for SSO/OAuth users)
|
||||
* @param ssoProviderId Unique identifier from SSO provider (may be null for non-SSO users)
|
||||
* @param ssoProvider Name of the SSO provider (may be null for non-SSO users)
|
||||
* @param authenticationType Type of authentication (WEB, SSO, etc.)
|
||||
* @param teamId ID of the team to assign (may be null to use default)
|
||||
* @param team Team object to assign (takes precedence over teamId if both provided)
|
||||
* @param role Role to assign to the user
|
||||
* @param firstLogin Whether this is the user's first login
|
||||
* @param enabled Whether the user account is enabled
|
||||
* @return The saved User object
|
||||
* @throws IllegalArgumentException If the username is invalid
|
||||
* @throws SQLException If a database error occurs
|
||||
* @throws UnsupportedProviderException If an unsupported provider is specified
|
||||
* @throws IllegalArgumentException If username is invalid or team is invalid
|
||||
* @throws SQLException If database operation fails
|
||||
* @throws UnsupportedProviderException If provider is not supported
|
||||
*/
|
||||
public User saveUserCore(SaveUserRequest request)
|
||||
private User saveUserCore(
|
||||
String username,
|
||||
String password,
|
||||
String ssoProviderId,
|
||||
String ssoProvider,
|
||||
AuthenticationType authenticationType,
|
||||
Long teamId,
|
||||
Team team,
|
||||
String role,
|
||||
boolean firstLogin,
|
||||
boolean enabled)
|
||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||
|
||||
if (!isUsernameValid(request.getUsername())) {
|
||||
if (!isUsernameValid(username)) {
|
||||
throw new IllegalArgumentException(getInvalidUsernameMessage());
|
||||
}
|
||||
|
||||
User user = new User();
|
||||
user.setUsername(request.getUsername());
|
||||
user.setUsername(username);
|
||||
|
||||
// Set password if provided
|
||||
if (request.getPassword() != null && !request.getPassword().isEmpty()) {
|
||||
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
||||
if (password != null && !password.isEmpty()) {
|
||||
user.setPassword(passwordEncoder.encode(password));
|
||||
}
|
||||
|
||||
// Set SSO provider details if provided
|
||||
if (request.getSsoProviderId() != null && request.getSsoProvider() != null) {
|
||||
user.setSsoProviderId(request.getSsoProviderId());
|
||||
user.setSsoProvider(request.getSsoProvider());
|
||||
if (ssoProviderId != null && ssoProvider != null) {
|
||||
user.setSsoProviderId(ssoProviderId);
|
||||
user.setSsoProvider(ssoProvider);
|
||||
}
|
||||
|
||||
// Set authentication type
|
||||
user.setAuthenticationType(request.getAuthenticationType());
|
||||
user.setAuthenticationType(authenticationType);
|
||||
|
||||
// Set enabled status
|
||||
user.setEnabled(request.isEnabled());
|
||||
user.setEnabled(enabled);
|
||||
|
||||
// Set first login flag
|
||||
user.setFirstLogin(request.isFirstLogin());
|
||||
|
||||
// Set MFA requirement
|
||||
Map<String, String> settings = user.getSettings();
|
||||
settings.put(MFA_REQUIRED_KEY, String.valueOf(request.isRequireMfa()));
|
||||
settings.put(MFA_ENABLED_KEY, String.valueOf(request.isMfaEnabled()));
|
||||
if (request.getMfaSecret() != null && !request.getMfaSecret().isEmpty()) {
|
||||
settings.put(MFA_SECRET_KEY, request.getMfaSecret());
|
||||
} else {
|
||||
settings.remove(MFA_SECRET_KEY);
|
||||
}
|
||||
if (request.getMfaLastUsedStep() != null) {
|
||||
settings.put(MFA_LAST_USED_STEP_KEY, String.valueOf(request.getMfaLastUsedStep()));
|
||||
} else {
|
||||
settings.remove(MFA_LAST_USED_STEP_KEY);
|
||||
}
|
||||
log.info(
|
||||
"MFA required set to true for user {} {}",
|
||||
request.getUsername(),
|
||||
user.getSettings().toString());
|
||||
user.setFirstLogin(firstLogin);
|
||||
|
||||
// Set role (authority)
|
||||
user.addAuthority(new Authority(request.getRole(), user));
|
||||
if (role == null) {
|
||||
role = Role.USER.getRoleId();
|
||||
}
|
||||
user.addAuthority(new Authority(role, user));
|
||||
|
||||
// Resolve and set team
|
||||
if (request.getTeam() != null) {
|
||||
user.setTeam(request.getTeam());
|
||||
if (team != null) {
|
||||
user.setTeam(team);
|
||||
} else {
|
||||
user.setTeam(resolveTeam(request.getTeamId(), this::getDefaultTeam));
|
||||
user.setTeam(resolveTeam(teamId, this::getDefaultTeam));
|
||||
}
|
||||
|
||||
// Save user
|
||||
@@ -538,7 +678,6 @@ public class UserService implements UserServiceInterface {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrentUserAdmin() {
|
||||
try {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
@@ -554,7 +693,6 @@ public class UserService implements UserServiceInterface {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrentUserFirstLogin() {
|
||||
try {
|
||||
String username = getCurrentUsername();
|
||||
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
package stirling.software.proprietary.security.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* RFC 4648 Base32 encoder/decoder for handling TOTP secrets.
|
||||
*
|
||||
* <p>This implementation is used to encode binary secrets into Base32 strings and decode them back
|
||||
* into byte arrays for TOTP processing.
|
||||
*/
|
||||
public final class Base32Codec {
|
||||
|
||||
private static final char[] ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".toCharArray();
|
||||
private static final int[] LOOKUP_TABLE = new int[128];
|
||||
|
||||
static {
|
||||
Arrays.fill(LOOKUP_TABLE, -1);
|
||||
for (int i = 0; i < ALPHABET.length; i++) {
|
||||
LOOKUP_TABLE[ALPHABET[i]] = i;
|
||||
}
|
||||
}
|
||||
|
||||
private Base32Codec() {}
|
||||
|
||||
/**
|
||||
* Encodes the provided bytes into a Base32 string.
|
||||
*
|
||||
* @param data the raw bytes to encode
|
||||
* @return Base32-encoded representation of the input, or an empty string for null/empty input
|
||||
*/
|
||||
public static String encode(byte[] data) {
|
||||
if (data == null || data.length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder((data.length * 8 + 4) / 5);
|
||||
int buffer = data[0] & 0xFF;
|
||||
int bitsLeft = 8;
|
||||
int index = 1;
|
||||
|
||||
while (bitsLeft > 0 || index < data.length) {
|
||||
if (bitsLeft < 5) {
|
||||
if (index < data.length) {
|
||||
buffer = (buffer << 8) | (data[index++] & 0xFF);
|
||||
bitsLeft += 8;
|
||||
} else {
|
||||
int padding = 5 - bitsLeft;
|
||||
buffer <<= padding;
|
||||
bitsLeft += padding;
|
||||
}
|
||||
}
|
||||
|
||||
int digit = (buffer >> (bitsLeft - 5)) & 0x1F;
|
||||
bitsLeft -= 5;
|
||||
result.append(ALPHABET[digit]);
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a Base32 string into raw bytes.
|
||||
*
|
||||
* @param value Base32-encoded string (padding and whitespace are ignored)
|
||||
* @return decoded byte array, or an empty array for null/blank input
|
||||
* @throws IllegalArgumentException when the input contains invalid Base32 characters
|
||||
*/
|
||||
public static byte[] decode(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
String normalized = value.replace("=", "").replace(" ", "").toUpperCase(Locale.ROOT);
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream(normalized.length());
|
||||
int buffer = 0;
|
||||
int bitsLeft = 0;
|
||||
|
||||
for (char character : normalized.toCharArray()) {
|
||||
if (character >= LOOKUP_TABLE.length || LOOKUP_TABLE[character] == -1) {
|
||||
throw new IllegalArgumentException("Invalid Base32 character: " + character);
|
||||
}
|
||||
|
||||
buffer = (buffer << 5) | LOOKUP_TABLE[character];
|
||||
bitsLeft += 5;
|
||||
|
||||
if (bitsLeft >= 8) {
|
||||
output.write((buffer >> (bitsLeft - 8)) & 0xFF);
|
||||
bitsLeft -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
return output.toByteArray();
|
||||
}
|
||||
}
|
||||
-137
@@ -1,137 +0,0 @@
|
||||
package stirling.software.proprietary.controller.api;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.common.model.enumeration.Role;
|
||||
import stirling.software.proprietary.config.AuditConfigurationProperties;
|
||||
import stirling.software.proprietary.controller.api.ProprietaryUIDataController.AccountData;
|
||||
import stirling.software.proprietary.controller.api.ProprietaryUIDataController.DatabaseData;
|
||||
import stirling.software.proprietary.controller.api.ProprietaryUIDataController.LoginData;
|
||||
import stirling.software.proprietary.repository.PersistentAuditEventRepository;
|
||||
import stirling.software.proprietary.security.database.repository.SessionRepository;
|
||||
import stirling.software.proprietary.security.database.repository.UserRepository;
|
||||
import stirling.software.proprietary.security.model.Authority;
|
||||
import stirling.software.proprietary.security.model.User;
|
||||
import stirling.software.proprietary.security.repository.TeamRepository;
|
||||
import stirling.software.proprietary.security.service.DatabaseService;
|
||||
import stirling.software.proprietary.security.service.MfaService;
|
||||
import stirling.software.proprietary.security.session.SessionPersistentRegistry;
|
||||
import stirling.software.proprietary.service.UserLicenseSettingsService;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ProprietaryUIDataControllerTest {
|
||||
|
||||
@Mock private SessionPersistentRegistry sessionPersistentRegistry;
|
||||
@Mock private UserRepository userRepository;
|
||||
@Mock private TeamRepository teamRepository;
|
||||
@Mock private SessionRepository sessionRepository;
|
||||
@Mock private DatabaseService databaseService;
|
||||
@Mock private UserLicenseSettingsService licenseSettingsService;
|
||||
@Mock private PersistentAuditEventRepository auditRepository;
|
||||
@Mock private MfaService mfaService;
|
||||
|
||||
private ApplicationProperties applicationProperties;
|
||||
private AuditConfigurationProperties auditConfig;
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
private ProprietaryUIDataController controller;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
applicationProperties = new ApplicationProperties();
|
||||
applicationProperties.getUi().setLanguages(List.of("en", "de"));
|
||||
applicationProperties.getSystem().setDefaultLocale("en");
|
||||
applicationProperties.getSecurity().setEnableLogin(true);
|
||||
applicationProperties.getSecurity().getOauth2().setEnabled(false);
|
||||
applicationProperties.getSecurity().getSaml2().setEnabled(false);
|
||||
|
||||
auditConfig = new AuditConfigurationProperties(applicationProperties);
|
||||
objectMapper = new ObjectMapper();
|
||||
|
||||
controller =
|
||||
new ProprietaryUIDataController(
|
||||
applicationProperties,
|
||||
auditConfig,
|
||||
sessionPersistentRegistry,
|
||||
userRepository,
|
||||
teamRepository,
|
||||
sessionRepository,
|
||||
databaseService,
|
||||
objectMapper,
|
||||
false,
|
||||
licenseSettingsService,
|
||||
auditRepository,
|
||||
mfaService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginDataFlagsFirstTimeSetupWhenNoUsers() {
|
||||
when(userRepository.findAll()).thenReturn(Collections.emptyList());
|
||||
|
||||
ResponseEntity<LoginData> response = controller.getLoginData();
|
||||
|
||||
assertThat(response.getStatusCode().is2xxSuccessful()).isTrue();
|
||||
LoginData body = response.getBody();
|
||||
assertThat(body.isFirstTimeSetup()).isTrue();
|
||||
assertThat(body.isShowDefaultCredentials()).isTrue();
|
||||
assertThat(body.getLanguages()).containsExactly("en", "de");
|
||||
assertThat(body.getDefaultLocale()).isEqualTo("en");
|
||||
}
|
||||
|
||||
@Test
|
||||
void accountDataReturnsUserSettingsAndMfaFlags() {
|
||||
User user = new User();
|
||||
user.setUsername("user@example.com");
|
||||
Authority authority = new Authority();
|
||||
authority.setAuthority(Role.USER.getRoleId());
|
||||
user.addAuthority(authority);
|
||||
user.setSettings(Map.of("theme", "dark"));
|
||||
|
||||
when(userRepository.findByUsernameIgnoreCaseWithSettings("user@example.com"))
|
||||
.thenReturn(Optional.of(user));
|
||||
when(mfaService.isMfaEnabled(user)).thenReturn(true);
|
||||
when(mfaService.isMfaRequired(user)).thenReturn(false);
|
||||
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
|
||||
|
||||
ResponseEntity<AccountData> response = controller.getAccountData(authentication);
|
||||
|
||||
assertThat(response.getStatusCode().is2xxSuccessful()).isTrue();
|
||||
AccountData data = response.getBody();
|
||||
assertThat(data.getUsername()).isEqualTo("user@example.com");
|
||||
assertThat(data.isMfaEnabled()).isTrue();
|
||||
assertThat(data.isMfaRequired()).isFalse();
|
||||
assertThat(data.getSettings()).contains("\"theme\":\"dark\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void databaseDataMarksUnknownVersion() {
|
||||
when(databaseService.getBackupList()).thenReturn(List.of());
|
||||
when(databaseService.getH2Version()).thenReturn("Unknown");
|
||||
|
||||
ResponseEntity<DatabaseData> response = controller.getDatabaseData();
|
||||
|
||||
assertThat(response.getStatusCode().is2xxSuccessful()).isTrue();
|
||||
DatabaseData data = response.getBody();
|
||||
assertThat(data.getBackupFiles()).isEmpty();
|
||||
assertThat(data.isVersionUnknown()).isTrue();
|
||||
}
|
||||
}
|
||||
-115
@@ -1,115 +0,0 @@
|
||||
package stirling.software.proprietary.security;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.common.model.enumeration.Role;
|
||||
import stirling.software.common.model.exception.UnsupportedProviderException;
|
||||
import stirling.software.proprietary.model.Team;
|
||||
import stirling.software.proprietary.security.model.User;
|
||||
import stirling.software.proprietary.security.service.DatabaseServiceInterface;
|
||||
import stirling.software.proprietary.security.service.SaveUserRequest;
|
||||
import stirling.software.proprietary.security.service.TeamService;
|
||||
import stirling.software.proprietary.security.service.UserService;
|
||||
import stirling.software.proprietary.service.UserLicenseSettingsService;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class InitialSecuritySetupTest {
|
||||
|
||||
@Mock private UserService userService;
|
||||
@Mock private TeamService teamService;
|
||||
@Mock private DatabaseServiceInterface databaseService;
|
||||
@Mock private UserLicenseSettingsService licenseSettingsService;
|
||||
|
||||
private ApplicationProperties applicationProperties;
|
||||
private InitialSecuritySetup initialSecuritySetup;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
applicationProperties = new ApplicationProperties();
|
||||
applicationProperties.getSecurity().getInitialLogin().setUsername("admin");
|
||||
applicationProperties.getSecurity().getInitialLogin().setPassword("password");
|
||||
Team internalTeam = new Team();
|
||||
internalTeam.setName(TeamService.INTERNAL_TEAM_NAME);
|
||||
User internalUser = new User();
|
||||
internalUser.setUsername(Role.INTERNAL_API_USER.getRoleId());
|
||||
internalUser.setTeam(internalTeam);
|
||||
when(userService.findByUsernameIgnoreCase(Role.INTERNAL_API_USER.getRoleId()))
|
||||
.thenReturn(Optional.of(internalUser));
|
||||
when(teamService.getOrCreateInternalTeam()).thenReturn(internalTeam);
|
||||
initialSecuritySetup =
|
||||
new InitialSecuritySetup(
|
||||
userService,
|
||||
teamService,
|
||||
applicationProperties,
|
||||
databaseService,
|
||||
licenseSettingsService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void initImportsBackupWhenPresent() throws SQLException, UnsupportedProviderException {
|
||||
when(userService.hasUsers()).thenReturn(false);
|
||||
when(databaseService.hasBackup()).thenReturn(true);
|
||||
when(userService.getUsersWithoutTeam()).thenReturn(Collections.emptyList());
|
||||
when(userService.usernameExistsIgnoreCase(Role.INTERNAL_API_USER.getRoleId()))
|
||||
.thenReturn(true);
|
||||
|
||||
initialSecuritySetup.init();
|
||||
|
||||
verify(databaseService).importDatabase();
|
||||
verify(userService, never()).saveUserCore(any());
|
||||
verify(licenseSettingsService).initializeGrandfatheredCount();
|
||||
verify(licenseSettingsService).updateLicenseMaxUsers();
|
||||
}
|
||||
|
||||
@Test
|
||||
void initCreatesConfiguredAdminWhenNoBackup()
|
||||
throws SQLException, UnsupportedProviderException {
|
||||
when(userService.hasUsers()).thenReturn(false);
|
||||
when(databaseService.hasBackup()).thenReturn(false);
|
||||
when(userService.findByUsernameIgnoreCase("admin")).thenReturn(Optional.empty());
|
||||
Team defaultTeam = new Team();
|
||||
defaultTeam.setName(TeamService.DEFAULT_TEAM_NAME);
|
||||
when(teamService.getOrCreateDefaultTeam()).thenReturn(defaultTeam);
|
||||
when(userService.getUsersWithoutTeam()).thenReturn(Collections.emptyList());
|
||||
when(userService.usernameExistsIgnoreCase(Role.INTERNAL_API_USER.getRoleId()))
|
||||
.thenReturn(true);
|
||||
|
||||
initialSecuritySetup.init();
|
||||
|
||||
ArgumentCaptor<SaveUserRequest> captor = ArgumentCaptor.forClass(SaveUserRequest.class);
|
||||
verify(userService).saveUserCore(captor.capture());
|
||||
SaveUserRequest saved = captor.getValue();
|
||||
assertThat(saved.getUsername()).isEqualTo("admin");
|
||||
assertThat(saved.getPassword()).isEqualTo("password");
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureJwtSettingsDisablesKeyCleanupWhenJwtDisabled() {
|
||||
ReflectionTestUtils.setField(initialSecuritySetup, "v2Enabled", true);
|
||||
applicationProperties.getSecurity().getJwt().setEnableKeystore(false);
|
||||
when(userService.hasUsers()).thenReturn(true);
|
||||
when(userService.getUsersWithoutTeam()).thenReturn(Collections.emptyList());
|
||||
when(userService.usernameExistsIgnoreCase(any())).thenReturn(true);
|
||||
|
||||
initialSecuritySetup.init();
|
||||
|
||||
assertThat(applicationProperties.getSecurity().getJwt().isEnableKeyCleanup()).isFalse();
|
||||
}
|
||||
}
|
||||
-235
@@ -1,235 +0,0 @@
|
||||
package stirling.software.proprietary.security.controller.api;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.common.model.enumeration.Role;
|
||||
import stirling.software.proprietary.security.model.AuthenticationType;
|
||||
import stirling.software.proprietary.security.model.Authority;
|
||||
import stirling.software.proprietary.security.model.User;
|
||||
import stirling.software.proprietary.security.model.api.user.UsernameAndPassMfa;
|
||||
import stirling.software.proprietary.security.service.CustomUserDetailsService;
|
||||
import stirling.software.proprietary.security.service.JwtServiceInterface;
|
||||
import stirling.software.proprietary.security.service.LoginAttemptService;
|
||||
import stirling.software.proprietary.security.service.MfaService;
|
||||
import stirling.software.proprietary.security.service.TotpService;
|
||||
import stirling.software.proprietary.security.service.UserService;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AuthControllerLoginTest {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private MockMvc mockMvc;
|
||||
private ApplicationProperties.Security securityProperties;
|
||||
|
||||
@Mock private UserService userService;
|
||||
@Mock private JwtServiceInterface jwtService;
|
||||
@Mock private CustomUserDetailsService userDetailsService;
|
||||
@Mock private LoginAttemptService loginAttemptService;
|
||||
@Mock private MfaService mfaService;
|
||||
@Mock private TotpService totpService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
securityProperties = new ApplicationProperties.Security();
|
||||
securityProperties.setLoginMethod("all");
|
||||
|
||||
AuthController controller =
|
||||
new AuthController(
|
||||
userService,
|
||||
jwtService,
|
||||
userDetailsService,
|
||||
loginAttemptService,
|
||||
mfaService,
|
||||
totpService,
|
||||
securityProperties);
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginRejectsWhenUserPassDisabled() throws Exception {
|
||||
securityProperties.setLoginMethod(
|
||||
ApplicationProperties.Security.LoginMethods.OAUTH2.toString());
|
||||
UsernameAndPassMfa payload = buildPayload(null);
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/auth/login")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(payload)))
|
||||
.andExpect(status().isForbidden())
|
||||
.andExpect(
|
||||
jsonPath("$.error")
|
||||
.value(
|
||||
"Username/password authentication is not enabled. Please use the configured authentication method."));
|
||||
|
||||
verify(userDetailsService, never()).loadUserByUsername(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginBlockedAccountReturnsUnauthorized() throws Exception {
|
||||
UsernameAndPassMfa payload = buildPayload(null);
|
||||
when(loginAttemptService.isBlocked("user@example.com")).thenReturn(true);
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/auth/login")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(payload)))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(
|
||||
jsonPath("$.error")
|
||||
.value("Account is locked due to too many failed attempts"));
|
||||
|
||||
verify(loginAttemptService, never()).loginSucceeded(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginRequiresMfaCodeWhenEnabled() throws Exception {
|
||||
UsernameAndPassMfa payload = buildPayload(null);
|
||||
User user = buildUser();
|
||||
when(userDetailsService.loadUserByUsername("user@example.com")).thenReturn(user);
|
||||
when(userService.isPasswordCorrect(user, "pw")).thenReturn(true);
|
||||
when(mfaService.isMfaEnabled(user)).thenReturn(true);
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/auth/login")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(payload)))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(jsonPath("$.error").value("mfa_required"));
|
||||
|
||||
verify(loginAttemptService, never()).loginSucceeded(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginFailsWhenPasswordIncorrect() throws Exception {
|
||||
UsernameAndPassMfa payload = buildPayload(null);
|
||||
User user = buildUser();
|
||||
when(userDetailsService.loadUserByUsername("user@example.com")).thenReturn(user);
|
||||
when(userService.isPasswordCorrect(user, "pw")).thenReturn(false);
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/auth/login")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(payload)))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(jsonPath("$.error").value("Invalid credentials"));
|
||||
|
||||
verify(loginAttemptService).loginFailed("user@example.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginSucceedsAndGeneratesToken() throws Exception {
|
||||
UsernameAndPassMfa payload = buildPayload(null);
|
||||
User user = buildUser();
|
||||
when(userDetailsService.loadUserByUsername("user@example.com")).thenReturn(user);
|
||||
when(userService.isPasswordCorrect(user, "pw")).thenReturn(true);
|
||||
when(mfaService.isMfaEnabled(user)).thenReturn(false);
|
||||
when(jwtService.generateToken(eq("user@example.com"), any(Map.class)))
|
||||
.thenReturn("token-123");
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/auth/login")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(payload)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.session.access_token").value("token-123"))
|
||||
.andExpect(jsonPath("$.user.username").value("user@example.com"));
|
||||
|
||||
verify(loginAttemptService).loginSucceeded("user@example.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshReturnsUnauthorizedWhenTokenMissing() throws Exception {
|
||||
when(jwtService.extractToken(any())).thenReturn(null);
|
||||
mockMvc.perform(post("/api/v1/auth/refresh"))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(jsonPath("$.error").value("No token found"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshReturnsNewTokenWhenValid() throws Exception {
|
||||
User user = buildUser();
|
||||
when(jwtService.extractToken(any())).thenReturn("old");
|
||||
when(jwtService.extractUsername("old")).thenReturn("user@example.com");
|
||||
when(userDetailsService.loadUserByUsername("user@example.com")).thenReturn(user);
|
||||
when(jwtService.generateToken(eq("user@example.com"), any(Map.class)))
|
||||
.thenReturn("new-token");
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/refresh"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.user").exists())
|
||||
.andExpect(jsonPath("$.session.access_token").value("new-token"))
|
||||
.andExpect(jsonPath("$.session.expires_in").value(3600));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCurrentUserReturnsUnauthorizedWhenAnonymous() throws Exception {
|
||||
SecurityContextHolder.clearContext();
|
||||
|
||||
mockMvc.perform(get("/api/v1/auth/me"))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(jsonPath("$.error").value("Not authenticated"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCurrentUserReturnsUserDetails() throws Exception {
|
||||
User user = buildUser();
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
|
||||
mockMvc.perform(get("/api/v1/auth/me"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.user.username").value("user@example.com"))
|
||||
.andExpect(
|
||||
jsonPath("$.user.authenticationType")
|
||||
.value(AuthenticationType.WEB.name().toLowerCase()));
|
||||
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
private User buildUser() {
|
||||
User user = new User();
|
||||
user.setUsername("user@example.com");
|
||||
user.setEnabled(true);
|
||||
user.setAuthenticationType(AuthenticationType.WEB);
|
||||
|
||||
Authority authority = new Authority();
|
||||
authority.setAuthority(Role.USER.getRoleId());
|
||||
user.addAuthorities(Set.of(authority));
|
||||
return user;
|
||||
}
|
||||
|
||||
private UsernameAndPassMfa buildPayload(String mfaCode) {
|
||||
UsernameAndPassMfa payload = new UsernameAndPassMfa();
|
||||
payload.setUsername("user@example.com");
|
||||
payload.setPassword("pw");
|
||||
payload.setMfaCode(mfaCode);
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
-218
@@ -1,218 +0,0 @@
|
||||
package stirling.software.proprietary.security.controller.api;
|
||||
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import stirling.software.proprietary.security.model.AuthenticationType;
|
||||
import stirling.software.proprietary.security.model.User;
|
||||
import stirling.software.proprietary.security.service.CustomUserDetailsService;
|
||||
import stirling.software.proprietary.security.service.JwtServiceInterface;
|
||||
import stirling.software.proprietary.security.service.LoginAttemptService;
|
||||
import stirling.software.proprietary.security.service.MfaService;
|
||||
import stirling.software.proprietary.security.service.TotpService;
|
||||
import stirling.software.proprietary.security.service.UserService;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AuthControllerMfaTest {
|
||||
|
||||
private static final String USERNAME = "user@example.com";
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private MockMvc mockMvc;
|
||||
private Authentication authentication;
|
||||
private User user;
|
||||
|
||||
@Mock private UserService userService;
|
||||
@Mock private JwtServiceInterface jwtService;
|
||||
@Mock private CustomUserDetailsService userDetailsService;
|
||||
@Mock private LoginAttemptService loginAttemptService;
|
||||
@Mock private MfaService mfaService;
|
||||
@Mock private TotpService totpService;
|
||||
|
||||
@InjectMocks private AuthController authController;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(authController).build();
|
||||
authentication = new UsernamePasswordAuthenticationToken(USERNAME, "password", List.of());
|
||||
user = new User();
|
||||
user.setUsername(USERNAME);
|
||||
user.setAuthenticationType(AuthenticationType.WEB);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setupMfaRequiresAuthentication() throws Exception {
|
||||
mockMvc.perform(get("/api/v1/auth/mfa/setup"))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(content().json("{\"error\":\"Not authenticated\"}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setupMfaReturnsSecretAndUri() throws Exception {
|
||||
when(userService.findByUsernameIgnoreCaseWithSettings(USERNAME))
|
||||
.thenReturn(Optional.of(user));
|
||||
when(mfaService.isMfaEnabled(user)).thenReturn(false);
|
||||
when(totpService.generateSecret()).thenReturn("SECRET");
|
||||
when(totpService.buildOtpAuthUri(USERNAME, "SECRET")).thenReturn("otpauth://test");
|
||||
|
||||
mockMvc.perform(get("/api/v1/auth/mfa/setup").principal(authentication))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.secret").value("SECRET"))
|
||||
.andExpect(jsonPath("$.otpauthUri").value("otpauth://test"));
|
||||
|
||||
verify(mfaService).setSecret(user, "SECRET");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setupMfaReturnsConflictWhenAlreadyEnabled() throws Exception {
|
||||
when(userService.findByUsernameIgnoreCaseWithSettings(USERNAME))
|
||||
.thenReturn(Optional.of(user));
|
||||
when(mfaService.isMfaEnabled(user)).thenReturn(true);
|
||||
|
||||
mockMvc.perform(get("/api/v1/auth/mfa/setup").principal(authentication))
|
||||
.andExpect(status().isConflict())
|
||||
.andExpect(content().json("{\"error\":\"MFA already enabled\"}"));
|
||||
|
||||
verify(totpService, never()).generateSecret();
|
||||
}
|
||||
|
||||
@Test
|
||||
void setupMfaRejectsNonWebAuthenticationType() throws Exception {
|
||||
user.setAuthenticationType(AuthenticationType.OAUTH2);
|
||||
when(userService.findByUsernameIgnoreCaseWithSettings(USERNAME))
|
||||
.thenReturn(Optional.of(user));
|
||||
|
||||
mockMvc.perform(get("/api/v1/auth/mfa/setup").principal(authentication))
|
||||
.andExpect(status().isForbidden())
|
||||
.andExpect(
|
||||
content()
|
||||
.json(
|
||||
"{\"error\":\"MFA settings are only available for web accounts\"}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableMfaRejectsMissingCode() throws Exception {
|
||||
when(userService.findByUsernameIgnoreCaseWithSettings(USERNAME))
|
||||
.thenReturn(Optional.of(user));
|
||||
when(mfaService.getSecret(user)).thenReturn("SECRET");
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/auth/mfa/enable")
|
||||
.principal(authentication)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{}"))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(content().json("{\"error\":\"MFA code is required\"}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableMfaCompletesWorkflow() throws Exception {
|
||||
when(userService.findByUsernameIgnoreCaseWithSettings(USERNAME))
|
||||
.thenReturn(Optional.of(user));
|
||||
when(mfaService.getSecret(user)).thenReturn("SECRET");
|
||||
when(totpService.getValidTimeStep("SECRET", "123456")).thenReturn(42L);
|
||||
when(mfaService.isTotpStepUsable(user, 42L)).thenReturn(true);
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/auth/mfa/enable")
|
||||
.principal(authentication)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(Map.of("code", "123456"))))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.enabled").value(true));
|
||||
|
||||
verify(mfaService).enableMfa(user);
|
||||
verify(mfaService).markTotpStepUsed(user, 42L);
|
||||
verify(mfaService).setMfaRequired(user, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void disableMfaCompletesWorkflow() throws Exception {
|
||||
when(userService.findByUsernameIgnoreCaseWithSettings(USERNAME))
|
||||
.thenReturn(Optional.of(user));
|
||||
when(mfaService.isMfaEnabled(user)).thenReturn(true);
|
||||
when(mfaService.getSecret(user)).thenReturn("SECRET");
|
||||
when(totpService.getValidTimeStep("SECRET", "654321")).thenReturn(7L);
|
||||
when(mfaService.isTotpStepUsable(user, 7L)).thenReturn(true);
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/auth/mfa/disable")
|
||||
.principal(authentication)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(Map.of("code", "654321"))))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.enabled").value(false));
|
||||
|
||||
verify(mfaService).disableMfa(user);
|
||||
verify(mfaService).markTotpStepUsed(user, 7L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void disableMfaReturnsDisabledWhenNotEnabled() throws Exception {
|
||||
when(userService.findByUsernameIgnoreCaseWithSettings(USERNAME))
|
||||
.thenReturn(Optional.of(user));
|
||||
when(mfaService.isMfaEnabled(user)).thenReturn(false);
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/auth/mfa/disable")
|
||||
.principal(authentication)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(Map.of("code", "654321"))))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.enabled").value(false));
|
||||
|
||||
verify(mfaService, never()).getSecret(user);
|
||||
verifyNoInteractions(totpService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelMfaSetupClearsPendingSecret() throws Exception {
|
||||
when(userService.findByUsernameIgnoreCaseWithSettings(USERNAME))
|
||||
.thenReturn(Optional.of(user));
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/mfa/setup/cancel").principal(authentication))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.cleared").value(true));
|
||||
|
||||
verify(mfaService).clearPendingSecret(user);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelMfaSetupReturnsConflictWhenEnabled() throws Exception {
|
||||
when(userService.findByUsernameIgnoreCaseWithSettings(USERNAME))
|
||||
.thenReturn(Optional.of(user));
|
||||
when(mfaService.isMfaEnabled(user)).thenReturn(true);
|
||||
|
||||
mockMvc.perform(post("/api/v1/auth/mfa/setup/cancel").principal(authentication))
|
||||
.andExpect(status().isConflict())
|
||||
.andExpect(content().json("{\"error\":\"MFA already enabled\"}"));
|
||||
|
||||
verify(mfaService, never()).clearPendingSecret(user);
|
||||
}
|
||||
}
|
||||
-162
@@ -1,162 +0,0 @@
|
||||
package stirling.software.proprietary.security.controller.api;
|
||||
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.common.model.enumeration.Role;
|
||||
import stirling.software.proprietary.model.Team;
|
||||
import stirling.software.proprietary.security.model.InviteToken;
|
||||
import stirling.software.proprietary.security.repository.InviteTokenRepository;
|
||||
import stirling.software.proprietary.security.repository.TeamRepository;
|
||||
import stirling.software.proprietary.security.service.EmailService;
|
||||
import stirling.software.proprietary.security.service.TeamService;
|
||||
import stirling.software.proprietary.security.service.UserService;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class InviteLinkControllerTest {
|
||||
|
||||
@Mock private InviteTokenRepository inviteTokenRepository;
|
||||
@Mock private TeamRepository teamRepository;
|
||||
@Mock private UserService userService;
|
||||
@Mock private EmailService emailService;
|
||||
|
||||
private ApplicationProperties applicationProperties;
|
||||
private MockMvc mockMvc;
|
||||
private Principal adminPrincipal;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
applicationProperties = new ApplicationProperties();
|
||||
applicationProperties.getMail().setEnableInvites(true);
|
||||
applicationProperties.getMail().setInviteLinkExpiryHours(24);
|
||||
applicationProperties.getSystem().setFrontendUrl("https://frontend.example.com");
|
||||
|
||||
adminPrincipal = () -> "admin";
|
||||
|
||||
InviteLinkController controller =
|
||||
new InviteLinkController(
|
||||
inviteTokenRepository,
|
||||
teamRepository,
|
||||
userService,
|
||||
applicationProperties,
|
||||
Optional.of(emailService));
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateInviteLinkRejectsWhenInvitesDisabled() throws Exception {
|
||||
applicationProperties.getMail().setEnableInvites(false);
|
||||
|
||||
mockMvc.perform(post("/api/v1/invite/generate").principal(adminPrincipal))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.error").value("Email invites are not enabled"));
|
||||
|
||||
verify(inviteTokenRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateInviteLinkRejectsInvalidEmail() throws Exception {
|
||||
applicationProperties.getMail().setEnableInvites(true);
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/invite/generate")
|
||||
.principal(adminPrincipal)
|
||||
.param("email", "not-an-email"))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.error").value("Invalid email address"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateInviteLinkBlocksOnLicenseLimit() throws Exception {
|
||||
applicationProperties.getPremium().setEnabled(true);
|
||||
applicationProperties.getPremium().setMaxUsers(1);
|
||||
when(userService.getTotalUsersCount()).thenReturn(1L);
|
||||
when(inviteTokenRepository.countActiveInvites(any(LocalDateTime.class))).thenReturn(0L);
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/invite/generate")
|
||||
.principal(adminPrincipal)
|
||||
.param("email", "new@ex.com"))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.error").value(startsWith("License limit reached")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateInviteLinkBuildsFrontendUrl() throws Exception {
|
||||
Team defaultTeam = new Team();
|
||||
defaultTeam.setId(5L);
|
||||
defaultTeam.setName(TeamService.DEFAULT_TEAM_NAME);
|
||||
when(teamRepository.findByName(TeamService.DEFAULT_TEAM_NAME))
|
||||
.thenReturn(Optional.of(defaultTeam));
|
||||
when(userService.usernameExistsIgnoreCase("new@example.com")).thenReturn(false);
|
||||
when(inviteTokenRepository.findByEmail("new@example.com")).thenReturn(Optional.empty());
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/invite/generate")
|
||||
.principal(adminPrincipal)
|
||||
.param("email", "new@example.com"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(
|
||||
jsonPath("$.inviteUrl")
|
||||
.value(startsWith("https://frontend.example.com/invite?token=")))
|
||||
.andExpect(jsonPath("$.email").value("new@example.com"));
|
||||
|
||||
verify(inviteTokenRepository).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateInviteTokenReturnsGoneWhenExpired() throws Exception {
|
||||
InviteToken expired = new InviteToken();
|
||||
expired.setToken("abc");
|
||||
expired.setExpiresAt(LocalDateTime.now().minusHours(1));
|
||||
expired.setRole(Role.USER.getRoleId());
|
||||
when(inviteTokenRepository.findByToken("abc")).thenReturn(Optional.of(expired));
|
||||
|
||||
mockMvc.perform(get("/api/v1/invite/validate/abc"))
|
||||
.andExpect(status().isGone())
|
||||
.andExpect(jsonPath("$.error").value("This invite link has expired"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptInviteCreatesUserWhenEmailProvided() throws Exception {
|
||||
InviteToken invite = new InviteToken();
|
||||
invite.setToken("abc");
|
||||
invite.setExpiresAt(LocalDateTime.now().plusHours(2));
|
||||
invite.setRole(Role.USER.getRoleId());
|
||||
invite.setUsed(false);
|
||||
invite.setEmail(null); // email required from request
|
||||
when(inviteTokenRepository.findByToken("abc")).thenReturn(Optional.of(invite));
|
||||
when(userService.usernameExistsIgnoreCase("new@example.com")).thenReturn(false);
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/invite/accept/abc")
|
||||
.param("email", "new@example.com")
|
||||
.param("password", "password123"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.message").value("Account created successfully"))
|
||||
.andExpect(jsonPath("$.username").value("new@example.com"));
|
||||
|
||||
verify(userService).saveUserCore(any());
|
||||
verify(inviteTokenRepository).save(invite);
|
||||
}
|
||||
}
|
||||
-140
@@ -1,140 +0,0 @@
|
||||
package stirling.software.proprietary.security.controller.api;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.proprietary.model.Team;
|
||||
import stirling.software.proprietary.security.database.repository.UserRepository;
|
||||
import stirling.software.proprietary.security.model.User;
|
||||
import stirling.software.proprietary.security.model.api.user.UsernameAndPass;
|
||||
import stirling.software.proprietary.security.repository.TeamRepository;
|
||||
import stirling.software.proprietary.security.service.EmailService;
|
||||
import stirling.software.proprietary.security.service.TeamService;
|
||||
import stirling.software.proprietary.security.service.UserService;
|
||||
import stirling.software.proprietary.security.session.SessionPersistentRegistry;
|
||||
import stirling.software.proprietary.service.UserLicenseSettingsService;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class UserControllerTest {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Mock private UserService userService;
|
||||
@Mock private SessionPersistentRegistry sessionRegistry;
|
||||
@Mock private TeamRepository teamRepository;
|
||||
@Mock private UserRepository userRepository;
|
||||
@Mock private EmailService emailService;
|
||||
@Mock private UserLicenseSettingsService licenseSettingsService;
|
||||
|
||||
private ApplicationProperties applicationProperties;
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
applicationProperties = new ApplicationProperties();
|
||||
applicationProperties.getPremium().setMaxUsers(10);
|
||||
applicationProperties.getMail().setEnabled(true);
|
||||
|
||||
UserController controller =
|
||||
new UserController(
|
||||
userService,
|
||||
sessionRegistry,
|
||||
applicationProperties,
|
||||
teamRepository,
|
||||
userRepository,
|
||||
Optional.of(emailService),
|
||||
licenseSettingsService);
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerRejectsExistingUser() throws Exception {
|
||||
UsernameAndPass payload = new UsernameAndPass();
|
||||
payload.setUsername("existing@example.com");
|
||||
payload.setPassword("pw");
|
||||
when(userService.usernameExistsIgnoreCase("existing@example.com")).thenReturn(true);
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/user/register")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(payload)))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.error").value("User already exists"));
|
||||
|
||||
verify(userService, never()).saveUserCore(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerCreatesUserWhenValid() throws Exception {
|
||||
UsernameAndPass payload = new UsernameAndPass();
|
||||
payload.setUsername("new@example.com");
|
||||
payload.setPassword("pw");
|
||||
Team defaultTeam = new Team();
|
||||
defaultTeam.setName(TeamService.DEFAULT_TEAM_NAME);
|
||||
|
||||
when(userService.usernameExistsIgnoreCase("new@example.com")).thenReturn(false);
|
||||
when(userService.isUsernameValid("new@example.com")).thenReturn(true);
|
||||
when(licenseSettingsService.wouldExceedLimit(1)).thenReturn(false);
|
||||
when(teamRepository.findByName(TeamService.DEFAULT_TEAM_NAME))
|
||||
.thenReturn(Optional.of(defaultTeam));
|
||||
|
||||
User savedUser = new User();
|
||||
savedUser.setUsername("new@example.com");
|
||||
savedUser.setEnabled(false);
|
||||
when(userService.saveUserCore(any())).thenReturn(savedUser);
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/user/register")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(payload)))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.user.username").value("new@example.com"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void changeUserEnabledPreventsSelfDisable() throws Exception {
|
||||
User user = new User();
|
||||
user.setUsername("admin");
|
||||
when(userService.usernameExistsIgnoreCase("admin")).thenReturn(true);
|
||||
when(userService.findByUsernameIgnoreCase("admin")).thenReturn(Optional.of(user));
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken("admin", "pw");
|
||||
|
||||
mockMvc.perform(
|
||||
post("/api/v1/user/admin/changeUserEnabled/admin")
|
||||
.param("enabled", "false")
|
||||
.principal(authentication))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.error").value("Cannot disable your own account."));
|
||||
}
|
||||
|
||||
@Test
|
||||
void changePasswordRejectsMissingUser() throws Exception {
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken("ghost", "pw");
|
||||
when(userService.usernameExistsIgnoreCase("ghost")).thenReturn(false);
|
||||
|
||||
mockMvc.perform(post("/api/v1/user/admin/deleteUser/ghost").principal(authentication))
|
||||
.andExpect(status().isNotFound())
|
||||
.andExpect(jsonPath("$.error").value("User not found."));
|
||||
}
|
||||
}
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
package stirling.software.proprietary.security.saml2;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyMap;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.NullAndEmptySource;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import stirling.software.proprietary.security.service.JwtServiceInterface;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class JwtSaml2AuthenticationRequestRepositoryTest {
|
||||
|
||||
private static final String SAML_REQUEST_TOKEN = "stirling_saml_request_token";
|
||||
|
||||
private Map<String, String> tokenStore;
|
||||
|
||||
@Mock private JwtServiceInterface jwtService;
|
||||
|
||||
@Mock private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
|
||||
|
||||
private JwtSaml2AuthenticationRequestRepository jwtSaml2AuthenticationRequestRepository;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
tokenStore = new ConcurrentHashMap<>();
|
||||
jwtSaml2AuthenticationRequestRepository =
|
||||
new JwtSaml2AuthenticationRequestRepository(
|
||||
tokenStore, jwtService, relyingPartyRegistrationRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveAuthenticationRequest() {
|
||||
var authRequest = mock(Saml2PostAuthenticationRequest.class);
|
||||
var request = mock(MockHttpServletRequest.class);
|
||||
var response = mock(MockHttpServletResponse.class);
|
||||
String token = "testToken";
|
||||
String id = "testId";
|
||||
String relayState = "testRelayState";
|
||||
String authnRequestUri = "example.com/authnRequest";
|
||||
String samlRequest = "testSamlRequest";
|
||||
String relyingPartyRegistrationId = "stirling-pdf";
|
||||
|
||||
when(jwtService.isJwtEnabled()).thenReturn(true);
|
||||
when(authRequest.getRelayState()).thenReturn(relayState);
|
||||
when(authRequest.getId()).thenReturn(id);
|
||||
when(authRequest.getAuthenticationRequestUri()).thenReturn(authnRequestUri);
|
||||
when(authRequest.getSamlRequest()).thenReturn(samlRequest);
|
||||
when(authRequest.getRelyingPartyRegistrationId()).thenReturn(relyingPartyRegistrationId);
|
||||
when(jwtService.generateToken(eq(""), anyMap())).thenReturn(token);
|
||||
|
||||
jwtSaml2AuthenticationRequestRepository.saveAuthenticationRequest(
|
||||
authRequest, request, response);
|
||||
|
||||
verify(request).setAttribute(SAML_REQUEST_TOKEN, relayState);
|
||||
verify(response).addHeader(SAML_REQUEST_TOKEN, relayState);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveAuthenticationRequestWithNullRequest() {
|
||||
var request = mock(MockHttpServletRequest.class);
|
||||
var response = mock(MockHttpServletResponse.class);
|
||||
|
||||
jwtSaml2AuthenticationRequestRepository.saveAuthenticationRequest(null, request, response);
|
||||
|
||||
assertTrue(tokenStore.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadAuthenticationRequest() {
|
||||
var request = mock(MockHttpServletRequest.class);
|
||||
var relyingPartyRegistration = mock(RelyingPartyRegistration.class);
|
||||
var assertingPartyMetadata = mock(AssertingPartyMetadata.class);
|
||||
String relayState = "testRelayState";
|
||||
String token = "testToken";
|
||||
Map<String, Object> claims =
|
||||
Map.of(
|
||||
"id", "testId",
|
||||
"relyingPartyRegistrationId", "stirling-pdf",
|
||||
"authenticationRequestUri", "example.com/authnRequest",
|
||||
"samlRequest", "testSamlRequest",
|
||||
"relayState", relayState);
|
||||
|
||||
when(request.getParameter("RelayState")).thenReturn(relayState);
|
||||
when(jwtService.extractClaims(token)).thenReturn(claims);
|
||||
when(relyingPartyRegistrationRepository.findByRegistrationId("stirling-pdf"))
|
||||
.thenReturn(relyingPartyRegistration);
|
||||
when(relyingPartyRegistration.getRegistrationId()).thenReturn("stirling-pdf");
|
||||
when(relyingPartyRegistration.getAssertingPartyMetadata())
|
||||
.thenReturn(assertingPartyMetadata);
|
||||
when(assertingPartyMetadata.getSingleSignOnServiceLocation())
|
||||
.thenReturn("https://example.com/sso");
|
||||
tokenStore.put(relayState, token);
|
||||
|
||||
var result = jwtSaml2AuthenticationRequestRepository.loadAuthenticationRequest(request);
|
||||
|
||||
assertNotNull(result);
|
||||
assertFalse(tokenStore.containsKey(relayState));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@NullAndEmptySource
|
||||
void loadAuthenticationRequestWithInvalidRelayState(String relayState) {
|
||||
var request = mock(MockHttpServletRequest.class);
|
||||
when(request.getParameter("RelayState")).thenReturn(relayState);
|
||||
|
||||
var result = jwtSaml2AuthenticationRequestRepository.loadAuthenticationRequest(request);
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadAuthenticationRequestWithNonExistentToken() {
|
||||
var request = mock(MockHttpServletRequest.class);
|
||||
when(request.getParameter("RelayState")).thenReturn("nonExistentRelayState");
|
||||
|
||||
var result = jwtSaml2AuthenticationRequestRepository.loadAuthenticationRequest(request);
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadAuthenticationRequestWithNullRelyingPartyRegistration() {
|
||||
var request = mock(MockHttpServletRequest.class);
|
||||
String relayState = "testRelayState";
|
||||
String token = "testToken";
|
||||
Map<String, Object> claims =
|
||||
Map.of(
|
||||
"id", "testId",
|
||||
"relyingPartyRegistrationId", "stirling-pdf",
|
||||
"authenticationRequestUri", "example.com/authnRequest",
|
||||
"samlRequest", "testSamlRequest",
|
||||
"relayState", relayState);
|
||||
|
||||
when(request.getParameter("RelayState")).thenReturn(relayState);
|
||||
when(jwtService.extractClaims(token)).thenReturn(claims);
|
||||
when(relyingPartyRegistrationRepository.findByRegistrationId("stirling-pdf"))
|
||||
.thenReturn(null);
|
||||
tokenStore.put(relayState, token);
|
||||
|
||||
var result = jwtSaml2AuthenticationRequestRepository.loadAuthenticationRequest(request);
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeAuthenticationRequest() {
|
||||
var request = mock(HttpServletRequest.class);
|
||||
var response = mock(HttpServletResponse.class);
|
||||
var relyingPartyRegistration = mock(RelyingPartyRegistration.class);
|
||||
var assertingPartyMetadata = mock(AssertingPartyMetadata.class);
|
||||
String relayState = "testRelayState";
|
||||
String token = "testToken";
|
||||
Map<String, Object> claims =
|
||||
Map.of(
|
||||
"id", "testId",
|
||||
"relyingPartyRegistrationId", "stirling-pdf",
|
||||
"authenticationRequestUri", "example.com/authnRequest",
|
||||
"samlRequest", "testSamlRequest",
|
||||
"relayState", relayState);
|
||||
|
||||
when(request.getParameter("RelayState")).thenReturn(relayState);
|
||||
when(jwtService.extractClaims(token)).thenReturn(claims);
|
||||
when(relyingPartyRegistrationRepository.findByRegistrationId("stirling-pdf"))
|
||||
.thenReturn(relyingPartyRegistration);
|
||||
when(relyingPartyRegistration.getRegistrationId()).thenReturn("stirling-pdf");
|
||||
when(relyingPartyRegistration.getAssertingPartyMetadata())
|
||||
.thenReturn(assertingPartyMetadata);
|
||||
when(assertingPartyMetadata.getSingleSignOnServiceLocation())
|
||||
.thenReturn("https://example.com/sso");
|
||||
tokenStore.put(relayState, token);
|
||||
|
||||
var result =
|
||||
jwtSaml2AuthenticationRequestRepository.removeAuthenticationRequest(
|
||||
request, response);
|
||||
|
||||
assertNotNull(result);
|
||||
assertFalse(tokenStore.containsKey(relayState));
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeAuthenticationRequestWithNullRelayState() {
|
||||
var request = mock(HttpServletRequest.class);
|
||||
var response = mock(HttpServletResponse.class);
|
||||
when(request.getParameter("RelayState")).thenReturn(null);
|
||||
|
||||
var result =
|
||||
jwtSaml2AuthenticationRequestRepository.removeAuthenticationRequest(
|
||||
request, response);
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeAuthenticationRequestWithNonExistentToken() {
|
||||
var request = mock(HttpServletRequest.class);
|
||||
var response = mock(HttpServletResponse.class);
|
||||
when(request.getParameter("RelayState")).thenReturn("nonExistentRelayState");
|
||||
|
||||
var result =
|
||||
jwtSaml2AuthenticationRequestRepository.removeAuthenticationRequest(
|
||||
request, response);
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeAuthenticationRequestWithOnlyRelayState() {
|
||||
var request = mock(HttpServletRequest.class);
|
||||
var response = mock(HttpServletResponse.class);
|
||||
String relayState = "testRelayState";
|
||||
|
||||
when(request.getParameter("RelayState")).thenReturn(relayState);
|
||||
|
||||
var result =
|
||||
jwtSaml2AuthenticationRequestRepository.removeAuthenticationRequest(
|
||||
request, response);
|
||||
|
||||
assertNull(result);
|
||||
assertFalse(tokenStore.containsKey(relayState));
|
||||
}
|
||||
}
|
||||
-131
@@ -1,131 +0,0 @@
|
||||
package stirling.software.proprietary.security.service;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.common.model.FileInfo;
|
||||
import stirling.software.proprietary.security.database.DatabaseNotificationServiceInterface;
|
||||
|
||||
class DatabaseServiceTest {
|
||||
|
||||
@TempDir Path tempDir;
|
||||
|
||||
@Mock private DatabaseNotificationServiceInterface notificationService;
|
||||
|
||||
private DatabaseService databaseService;
|
||||
private ApplicationProperties.Datasource datasourceProps;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
datasourceProps = new ApplicationProperties.Datasource();
|
||||
datasourceProps.setType(ApplicationProperties.Driver.H2.name());
|
||||
datasourceProps.setCustomDatabaseUrl("jdbc:h2:mem:test");
|
||||
datasourceProps.setEnableCustomDatabase(false);
|
||||
|
||||
DataSource dataSource =
|
||||
new DriverManagerDataSource(
|
||||
"jdbc:h2:mem:" + UUID.randomUUID() + ";DB_CLOSE_DELAY=-1", "sa", "");
|
||||
|
||||
databaseService = new DatabaseService(datasourceProps, dataSource, notificationService);
|
||||
ReflectionTestUtils.setField(databaseService, "BACKUP_DIR", tempDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasBackupReturnsFalseWhenEmpty() {
|
||||
assertThat(databaseService.hasBackup()).isFalse();
|
||||
assertThat(Files.exists(tempDir)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBackupListReturnsEntries() throws IOException {
|
||||
Path backup =
|
||||
tempDir.resolve(
|
||||
"backup_"
|
||||
+ LocalDateTime.now()
|
||||
.format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"))
|
||||
+ ".sql");
|
||||
Files.writeString(backup, "CREATE TABLE TEST(ID INT);");
|
||||
|
||||
List<stirling.software.common.model.FileInfo> backups = databaseService.getBackupList();
|
||||
|
||||
assertThat(backups).hasSize(1);
|
||||
assertThat(backups.get(0).getFileName()).isEqualTo(backup.getFileName().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void importDatabaseFromUICopiesBackupAndDeletesTemp() throws IOException {
|
||||
Path script = Files.createTempFile("script", ".sql");
|
||||
Files.writeString(script, "CREATE TABLE SAMPLE(ID INT PRIMARY KEY);\n");
|
||||
|
||||
boolean result = databaseService.importDatabaseFromUI(script);
|
||||
|
||||
assertThat(result).isTrue();
|
||||
assertThat(Files.exists(script)).isFalse();
|
||||
try (var stream = Files.list(tempDir)) {
|
||||
assertThat(stream.toList()).isNotEmpty();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBackupFilePathPreventsTraversal() {
|
||||
assertThatThrownBy(() -> databaseService.getBackupFilePath("../evil.sql"))
|
||||
.isInstanceOf(SecurityException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteBackupFileRejectsInvalidName() throws Exception {
|
||||
boolean deleted = databaseService.deleteBackupFile("..bad.sql");
|
||||
assertThat(deleted).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteAllBackupsRemovesFiles() throws Exception {
|
||||
Path first = tempDir.resolve("backup_first.sql");
|
||||
Path second = tempDir.resolve("backup_second.sql");
|
||||
Files.writeString(first, "SELECT 1;");
|
||||
Files.writeString(second, "SELECT 1;");
|
||||
|
||||
List<org.apache.commons.lang3.tuple.Pair<FileInfo, Boolean>> results =
|
||||
databaseService.deleteAllBackups();
|
||||
|
||||
assertThat(results).hasSize(2);
|
||||
assertThat(results.stream().allMatch(org.apache.commons.lang3.tuple.Pair::getRight))
|
||||
.isTrue();
|
||||
assertThat(Files.exists(first)).isFalse();
|
||||
assertThat(Files.exists(second)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void exportDatabaseCreatesScript() {
|
||||
databaseService.exportDatabase();
|
||||
|
||||
assertThat(tempDir.toFile().list()).isNotEmpty();
|
||||
verify(notificationService)
|
||||
.notifyBackupsSuccess(
|
||||
org.mockito.ArgumentMatchers.anyString(),
|
||||
org.mockito.ArgumentMatchers.anyString());
|
||||
}
|
||||
}
|
||||
-146
@@ -1,146 +0,0 @@
|
||||
package stirling.software.proprietary.security.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import stirling.software.proprietary.security.database.repository.UserRepository;
|
||||
import stirling.software.proprietary.security.model.User;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class MfaServiceTest {
|
||||
|
||||
@Mock private UserRepository userRepository;
|
||||
@Mock private DatabaseServiceInterface databaseService;
|
||||
|
||||
@InjectMocks private MfaService mfaService;
|
||||
|
||||
@Test
|
||||
void setSecretStoresSecretAndDisablesMfa() throws Exception {
|
||||
User user = new User();
|
||||
user.getSettings().put(MfaService.MFA_LAST_USED_STEP_KEY, "10");
|
||||
when(userRepository.save(any(User.class))).thenAnswer(i -> i.getArgument(0));
|
||||
|
||||
mfaService.setSecret(user, "NEWSECRET");
|
||||
|
||||
assertEquals("NEWSECRET", user.getSettings().get(MfaService.MFA_SECRET_KEY));
|
||||
assertEquals("false", user.getSettings().get(MfaService.MFA_ENABLED_KEY));
|
||||
assertNull(user.getSettings().get(MfaService.MFA_LAST_USED_STEP_KEY));
|
||||
verify(databaseService).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableMfaSetsEnabledFlag() throws Exception {
|
||||
User user = new User();
|
||||
when(userRepository.save(any(User.class))).thenAnswer(i -> i.getArgument(0));
|
||||
|
||||
mfaService.enableMfa(user);
|
||||
|
||||
assertEquals("true", user.getSettings().get(MfaService.MFA_ENABLED_KEY));
|
||||
verify(databaseService).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void disableMfaClearsSecretAndUsage() throws Exception {
|
||||
User user = new User();
|
||||
user.getSettings().put(MfaService.MFA_SECRET_KEY, "SECRET");
|
||||
user.getSettings().put(MfaService.MFA_LAST_USED_STEP_KEY, "20");
|
||||
when(userRepository.save(any(User.class))).thenAnswer(i -> i.getArgument(0));
|
||||
|
||||
mfaService.disableMfa(user);
|
||||
|
||||
assertEquals("false", user.getSettings().get(MfaService.MFA_ENABLED_KEY));
|
||||
assertNull(user.getSettings().get(MfaService.MFA_SECRET_KEY));
|
||||
assertNull(user.getSettings().get(MfaService.MFA_LAST_USED_STEP_KEY));
|
||||
verify(databaseService).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void markTotpStepUsedTracksNewestStep() throws Exception {
|
||||
User user = new User();
|
||||
when(userRepository.save(any(User.class))).thenAnswer(i -> i.getArgument(0));
|
||||
|
||||
assertTrue(mfaService.markTotpStepUsed(user, 100L));
|
||||
assertEquals("100", user.getSettings().get(MfaService.MFA_LAST_USED_STEP_KEY));
|
||||
verify(databaseService).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void markTotpStepUsedRejectsReplays() throws Exception {
|
||||
User user = new User();
|
||||
user.getSettings().put(MfaService.MFA_LAST_USED_STEP_KEY, "200");
|
||||
|
||||
assertFalse(mfaService.markTotpStepUsed(user, 199L));
|
||||
assertFalse(mfaService.markTotpStepUsed(user, 200L));
|
||||
verify(databaseService, never()).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void markTotpStepUsedIgnoresMalformedStoredValue() throws Exception {
|
||||
User user = new User();
|
||||
user.getSettings().put(MfaService.MFA_LAST_USED_STEP_KEY, "not-a-number");
|
||||
when(userRepository.save(any(User.class))).thenAnswer(i -> i.getArgument(0));
|
||||
|
||||
assertTrue(mfaService.markTotpStepUsed(user, 5L));
|
||||
assertEquals("5", user.getSettings().get(MfaService.MFA_LAST_USED_STEP_KEY));
|
||||
verify(databaseService, times(1)).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isTotpStepUsableRespectsLastStep() {
|
||||
User user = new User();
|
||||
user.getSettings().put(MfaService.MFA_LAST_USED_STEP_KEY, "50");
|
||||
|
||||
assertFalse(mfaService.isTotpStepUsable(user, 50));
|
||||
assertFalse(mfaService.isTotpStepUsable(user, 40));
|
||||
assertTrue(mfaService.isTotpStepUsable(user, 51));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMfaRequiredDefaultsToFalse() {
|
||||
User user = new User();
|
||||
|
||||
assertFalse(mfaService.isMfaRequired(user));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setMfaRequiredStoresFlag() throws Exception {
|
||||
User user = new User();
|
||||
when(userRepository.save(any(User.class))).thenAnswer(i -> i.getArgument(0));
|
||||
|
||||
mfaService.setMfaRequired(user, true);
|
||||
|
||||
assertEquals("true", user.getSettings().get(MfaService.MFA_REQUIRED_KEY));
|
||||
verify(databaseService).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearPendingSecretResetsValues() throws Exception {
|
||||
User user = new User();
|
||||
user.getSettings().put(MfaService.MFA_SECRET_KEY, "SECRET");
|
||||
user.getSettings().put(MfaService.MFA_LAST_USED_STEP_KEY, "12");
|
||||
when(userRepository.save(any(User.class))).thenAnswer(i -> i.getArgument(0));
|
||||
|
||||
mfaService.clearPendingSecret(user);
|
||||
|
||||
assertEquals("false", user.getSettings().get(MfaService.MFA_ENABLED_KEY));
|
||||
assertNull(user.getSettings().get(MfaService.MFA_SECRET_KEY));
|
||||
assertNull(user.getSettings().get(MfaService.MFA_LAST_USED_STEP_KEY));
|
||||
verify(databaseService).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMfaEnabledAndGetSecretReadSettings() {
|
||||
User user = new User();
|
||||
user.getSettings().put(MfaService.MFA_ENABLED_KEY, "true");
|
||||
user.getSettings().put(MfaService.MFA_SECRET_KEY, "SECRET");
|
||||
|
||||
assertTrue(mfaService.isMfaEnabled(user));
|
||||
assertEquals("SECRET", mfaService.getSecret(user));
|
||||
}
|
||||
}
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
package stirling.software.proprietary.security.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.proprietary.security.util.Base32Codec;
|
||||
|
||||
class TotpServiceTest {
|
||||
|
||||
private TotpService buildService(String appName) {
|
||||
ApplicationProperties properties = new ApplicationProperties();
|
||||
ApplicationProperties.Ui ui = new ApplicationProperties.Ui();
|
||||
ui.setAppNameNavbar(appName);
|
||||
properties.setUi(ui);
|
||||
return new TotpService(properties);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateSecretReturnsBase32String() {
|
||||
TotpService service = buildService("Test App");
|
||||
|
||||
String secret = service.generateSecret();
|
||||
|
||||
assertNotNull(secret);
|
||||
assertEquals(32, secret.length());
|
||||
assertTrue(secret.matches("[A-Z2-7]+"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildOtpAuthUriIncludesIssuerAndUsername() {
|
||||
TotpService service = buildService("Stirling Test");
|
||||
|
||||
String uri = service.buildOtpAuthUri("user@example.com", "SECRET");
|
||||
|
||||
assertTrue(uri.contains("issuer=Stirling%20Test"));
|
||||
assertTrue(uri.contains("Stirling%20Test%3Auser%40example.com"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isValidCodeAcceptsCurrentAndAdjacentTimeSteps() throws Exception {
|
||||
TotpService service = buildService("Test App");
|
||||
byte[] secretBytes = "super-secret".getBytes(StandardCharsets.UTF_8);
|
||||
String secret = Base32Codec.encode(secretBytes);
|
||||
|
||||
long timeStep = Instant.now().getEpochSecond() / 30;
|
||||
String currentCode = generateCode(service, secretBytes, timeStep);
|
||||
String nextCode = generateCode(service, secretBytes, timeStep + 1);
|
||||
|
||||
assertTrue(service.isValidCode(secret, currentCode));
|
||||
assertEquals(timeStep, service.getValidTimeStep(secret, currentCode));
|
||||
assertTrue(service.isValidCode(secret, nextCode));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isValidCodeRejectsInvalidFormats() {
|
||||
TotpService service = buildService("Test App");
|
||||
|
||||
assertFalse(service.isValidCode("SECRET", "ABCDEF"));
|
||||
assertFalse(service.isValidCode("SECRET", "12345"));
|
||||
assertFalse(service.isValidCode(null, "123456"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isValidCodeRejectsInvalidSecrets() {
|
||||
TotpService service = buildService("Test App");
|
||||
|
||||
assertFalse(service.isValidCode("INVALID*", "123456"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildOtpAuthUriUsesDefaultIssuerWhenMissing() {
|
||||
ApplicationProperties properties = new ApplicationProperties();
|
||||
TotpService service = new TotpService(properties);
|
||||
|
||||
String uri = service.buildOtpAuthUri("user@example.com", "SECRET");
|
||||
|
||||
assertTrue(uri.contains("issuer=Stirling%20PDF"));
|
||||
}
|
||||
|
||||
private String generateCode(TotpService service, byte[] secretBytes, long timeStep)
|
||||
throws Exception {
|
||||
Method generateCode =
|
||||
TotpService.class.getDeclaredMethod("generateCode", byte[].class, long.class);
|
||||
generateCode.setAccessible(true);
|
||||
return (String) generateCode.invoke(service, secretBytes, timeStep);
|
||||
}
|
||||
}
|
||||
+230
-118
@@ -1,26 +1,22 @@
|
||||
package stirling.software.proprietary.security.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
import stirling.software.common.model.enumeration.Role;
|
||||
import stirling.software.common.model.exception.UnsupportedProviderException;
|
||||
import stirling.software.proprietary.model.Team;
|
||||
import stirling.software.proprietary.security.database.repository.AuthorityRepository;
|
||||
import stirling.software.proprietary.security.database.repository.UserRepository;
|
||||
@@ -33,156 +29,272 @@ import stirling.software.proprietary.security.session.SessionPersistentRegistry;
|
||||
class UserServiceTest {
|
||||
|
||||
@Mock private UserRepository userRepository;
|
||||
|
||||
@Mock private TeamRepository teamRepository;
|
||||
|
||||
@Mock private AuthorityRepository authorityRepository;
|
||||
|
||||
@Mock private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Mock private MessageSource messageSource;
|
||||
@Mock private SessionPersistentRegistry sessionRegistry;
|
||||
|
||||
@Mock private SessionPersistentRegistry sessionPersistentRegistry;
|
||||
|
||||
@Mock private DatabaseServiceInterface databaseService;
|
||||
@Mock private ApplicationProperties.Security.OAUTH2 oAuth2;
|
||||
|
||||
@Spy @InjectMocks private UserService userService;
|
||||
@Mock private ApplicationProperties.Security.OAUTH2 oauth2Properties;
|
||||
|
||||
@InjectMocks private UserService userService;
|
||||
|
||||
private Team mockTeam;
|
||||
private User mockUser;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
mockTeam = new Team();
|
||||
mockTeam.setId(1L);
|
||||
mockTeam.setName("Test Team");
|
||||
|
||||
mockUser = new User();
|
||||
mockUser.setId(1L);
|
||||
mockUser.setUsername("testuser");
|
||||
mockUser.setEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUserCore_populatesFieldsAndPersists()
|
||||
throws SQLException, UnsupportedProviderException {
|
||||
Long teamId = 42L;
|
||||
Team team = new Team();
|
||||
team.setId(teamId);
|
||||
when(teamRepository.findById(teamId)).thenReturn(Optional.of(team));
|
||||
when(passwordEncoder.encode("plain")).thenReturn("encoded");
|
||||
when(userRepository.save(any(User.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
void testSaveUser_WithUsernameAndAuthenticationType_Success() throws Exception {
|
||||
// Given
|
||||
String username = "testuser";
|
||||
AuthenticationType authType = AuthenticationType.WEB;
|
||||
|
||||
SaveUserRequest request =
|
||||
SaveUserRequest.builder()
|
||||
.username("validUser")
|
||||
.password("plain")
|
||||
.ssoProviderId("sso-id")
|
||||
.ssoProvider("provider-x")
|
||||
.authenticationType(AuthenticationType.OAUTH2)
|
||||
.teamId(teamId)
|
||||
.role(Role.ADMIN.getRoleId())
|
||||
.firstLogin(true)
|
||||
.enabled(false)
|
||||
.requireMfa(true)
|
||||
.mfaEnabled(true)
|
||||
.mfaSecret("top-secret")
|
||||
.mfaLastUsedStep(5L)
|
||||
.build();
|
||||
when(teamRepository.findByName("Default")).thenReturn(Optional.of(mockTeam));
|
||||
when(userRepository.save(any(User.class))).thenReturn(mockUser);
|
||||
doNothing().when(databaseService).exportDatabase();
|
||||
|
||||
User saved = userService.saveUserCore(request);
|
||||
// When
|
||||
userService.saveUser(username, authType);
|
||||
|
||||
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
|
||||
verify(userRepository).save(userCaptor.capture());
|
||||
// Then
|
||||
verify(userRepository).save(any(User.class));
|
||||
verify(databaseService).exportDatabase();
|
||||
|
||||
User persisted = userCaptor.getValue();
|
||||
assertEquals("validUser", persisted.getUsername());
|
||||
assertEquals("encoded", persisted.getPassword());
|
||||
assertEquals("provider-x", persisted.getSsoProvider());
|
||||
assertEquals("sso-id", persisted.getSsoProviderId());
|
||||
assertEquals(team, persisted.getTeam());
|
||||
assertEquals("oauth2", persisted.getAuthenticationType());
|
||||
assertFalse(persisted.isEnabled());
|
||||
assertTrue(persisted.isFirstLogin());
|
||||
assertTrue(
|
||||
persisted.getAuthorities().stream()
|
||||
.anyMatch(a -> Role.ADMIN.getRoleId().equals(a.getAuthority())));
|
||||
assertEquals(
|
||||
"true",
|
||||
persisted.getSettings().get(MfaService.MFA_REQUIRED_KEY),
|
||||
"MFA requirement should be stored");
|
||||
assertEquals(
|
||||
"true",
|
||||
persisted.getSettings().get(MfaService.MFA_ENABLED_KEY),
|
||||
"MFA enabled flag should be stored");
|
||||
assertEquals(
|
||||
"top-secret",
|
||||
persisted.getSettings().get(MfaService.MFA_SECRET_KEY),
|
||||
"MFA secret should be stored");
|
||||
assertEquals(
|
||||
"5",
|
||||
persisted.getSettings().get(MfaService.MFA_LAST_USED_STEP_KEY),
|
||||
"MFA last used step should be stored");
|
||||
assertSame(saved, persisted, "Returned user should be the persisted instance");
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveUserCore_withoutTeam_usesDefaultTeam()
|
||||
throws SQLException, UnsupportedProviderException {
|
||||
Team defaultTeam = new Team();
|
||||
defaultTeam.setName("Default");
|
||||
when(teamRepository.findByName("Default")).thenReturn(Optional.of(defaultTeam));
|
||||
when(userRepository.save(any(User.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
void testSaveUser_WithUsernamePasswordAndTeamId_Success() throws Exception {
|
||||
// Given
|
||||
String username = "testuser";
|
||||
String password = "password123";
|
||||
Long teamId = 1L;
|
||||
String encodedPassword = "encodedPassword123";
|
||||
|
||||
SaveUserRequest request = SaveUserRequest.builder().username("anotherUser").build();
|
||||
when(passwordEncoder.encode(password)).thenReturn(encodedPassword);
|
||||
when(teamRepository.findById(teamId)).thenReturn(Optional.of(mockTeam));
|
||||
when(userRepository.save(any(User.class))).thenReturn(mockUser);
|
||||
doNothing().when(databaseService).exportDatabase();
|
||||
|
||||
User saved = userService.saveUserCore(request);
|
||||
// When
|
||||
User result = userService.saveUser(username, password, teamId);
|
||||
|
||||
verify(teamRepository).findByName("Default");
|
||||
verify(teamRepository, never()).findById(anyLong());
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
verify(passwordEncoder).encode(password);
|
||||
verify(teamRepository).findById(teamId);
|
||||
verify(userRepository).save(any(User.class));
|
||||
verify(databaseService).exportDatabase();
|
||||
assertEquals(defaultTeam, saved.getTeam(), "Default team should be applied");
|
||||
}
|
||||
|
||||
@Test
|
||||
void processSSOPostLogin_autoCreatesUserWhenMissing()
|
||||
throws IllegalArgumentException, SQLException, UnsupportedProviderException {
|
||||
String username = "autoUser";
|
||||
doReturn(true).when(userService).isUsernameValid(username);
|
||||
when(userRepository.findBySsoProviderAndSsoProviderId("prov", "id"))
|
||||
.thenReturn(Optional.empty());
|
||||
when(userRepository.findByUsernameIgnoreCase(username)).thenReturn(Optional.empty());
|
||||
User created = new User();
|
||||
doReturn(created).when(userService).saveUserCore(any(SaveUserRequest.class));
|
||||
void testSaveUser_WithTeamAndRole_Success() throws Exception {
|
||||
// Given
|
||||
String username = "testuser";
|
||||
String password = "password123";
|
||||
String role = Role.ADMIN.getRoleId();
|
||||
boolean firstLogin = true;
|
||||
String encodedPassword = "encodedPassword123";
|
||||
|
||||
userService.processSSOPostLogin(username, "id", "prov", true, AuthenticationType.SAML2);
|
||||
when(passwordEncoder.encode(password)).thenReturn(encodedPassword);
|
||||
when(userRepository.save(any(User.class))).thenReturn(mockUser);
|
||||
doNothing().when(databaseService).exportDatabase();
|
||||
|
||||
ArgumentCaptor<SaveUserRequest> reqCaptor = ArgumentCaptor.forClass(SaveUserRequest.class);
|
||||
verify(userService).saveUserCore(reqCaptor.capture());
|
||||
SaveUserRequest captured = reqCaptor.getValue();
|
||||
// When
|
||||
User result = userService.saveUser(username, password, mockTeam, role, firstLogin);
|
||||
|
||||
assertEquals(username, captured.getUsername());
|
||||
assertEquals("id", captured.getSsoProviderId());
|
||||
assertEquals("prov", captured.getSsoProvider());
|
||||
assertEquals(AuthenticationType.SAML2, captured.getAuthenticationType());
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
verify(passwordEncoder).encode(password);
|
||||
verify(userRepository).save(any(User.class));
|
||||
verify(databaseService).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void addApiKeyToUserGeneratesAndPersists() {
|
||||
User user = new User();
|
||||
user.setUsername("user");
|
||||
when(userRepository.findByUsernameIgnoreCase("user")).thenReturn(Optional.of(user));
|
||||
when(userRepository.findByApiKey(any())).thenReturn(Optional.empty());
|
||||
when(userRepository.save(any(User.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
void testSaveUser_WithInvalidUsername_ThrowsException() throws Exception {
|
||||
// Given
|
||||
String invalidUsername = "ab"; // Too short (less than 3 characters)
|
||||
AuthenticationType authType = AuthenticationType.WEB;
|
||||
|
||||
User updated = userService.addApiKeyToUser("user");
|
||||
// When & Then
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> userService.saveUser(invalidUsername, authType));
|
||||
|
||||
assertNotNull(updated.getApiKey());
|
||||
verify(userRepository).save(user);
|
||||
verify(userRepository, never()).save(any(User.class));
|
||||
verify(databaseService, never()).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getApiKeyForUserCreatesWhenMissing() {
|
||||
User user = new User();
|
||||
user.setUsername("user");
|
||||
when(userRepository.findByUsernameIgnoreCase("user")).thenReturn(Optional.of(user));
|
||||
when(userRepository.findByApiKey(any())).thenReturn(Optional.empty());
|
||||
when(userRepository.save(any(User.class)))
|
||||
.thenAnswer(invocation -> invocation.getArgument(0));
|
||||
void testSaveUser_WithNullPassword_Success() throws Exception {
|
||||
// Given
|
||||
String username = "testuser";
|
||||
Long teamId = 1L;
|
||||
|
||||
String apiKey = userService.getApiKeyForUser("user");
|
||||
when(teamRepository.findById(teamId)).thenReturn(Optional.of(mockTeam));
|
||||
when(userRepository.save(any(User.class))).thenReturn(mockUser);
|
||||
doNothing().when(databaseService).exportDatabase();
|
||||
|
||||
assertNotNull(apiKey);
|
||||
verify(userRepository).save(user);
|
||||
// When
|
||||
User result = userService.saveUser(username, null, teamId);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
verify(passwordEncoder, never()).encode(anyString());
|
||||
verify(userRepository).save(any(User.class));
|
||||
verify(databaseService).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isUsernameValidRejectsReservedAndAcceptsEmail() {
|
||||
assertFalse(userService.isUsernameValid("ALL_USERS"));
|
||||
assertTrue(userService.isUsernameValid("valid@example.com"));
|
||||
void testSaveUser_WithEmptyPassword_Success() throws Exception {
|
||||
// Given
|
||||
String username = "testuser";
|
||||
String emptyPassword = "";
|
||||
Long teamId = 1L;
|
||||
|
||||
when(teamRepository.findById(teamId)).thenReturn(Optional.of(mockTeam));
|
||||
when(userRepository.save(any(User.class))).thenReturn(mockUser);
|
||||
doNothing().when(databaseService).exportDatabase();
|
||||
|
||||
// When
|
||||
User result = userService.saveUser(username, emptyPassword, teamId);
|
||||
|
||||
// Then
|
||||
assertNotNull(result);
|
||||
verify(passwordEncoder, never()).encode(anyString());
|
||||
verify(userRepository).save(any(User.class));
|
||||
verify(databaseService).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSaveUser_WithValidEmail_Success() throws Exception {
|
||||
// Given
|
||||
String emailUsername = "test@example.com";
|
||||
AuthenticationType authType = AuthenticationType.OAUTH2;
|
||||
|
||||
when(teamRepository.findByName("Default")).thenReturn(Optional.of(mockTeam));
|
||||
when(userRepository.save(any(User.class))).thenReturn(mockUser);
|
||||
doNothing().when(databaseService).exportDatabase();
|
||||
|
||||
// When
|
||||
userService.saveUser(emailUsername, authType);
|
||||
|
||||
// Then
|
||||
verify(userRepository).save(any(User.class));
|
||||
verify(databaseService).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSaveUser_WithReservedUsername_ThrowsException() throws Exception {
|
||||
// Given
|
||||
String reservedUsername = "all_users";
|
||||
AuthenticationType authType = AuthenticationType.WEB;
|
||||
|
||||
// When & Then
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> userService.saveUser(reservedUsername, authType));
|
||||
|
||||
verify(userRepository, never()).save(any(User.class));
|
||||
verify(databaseService, never()).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSaveUser_WithAnonymousUser_ThrowsException() throws Exception {
|
||||
// Given
|
||||
String anonymousUsername = "anonymoususer";
|
||||
AuthenticationType authType = AuthenticationType.WEB;
|
||||
|
||||
// When & Then
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> userService.saveUser(anonymousUsername, authType));
|
||||
|
||||
verify(userRepository, never()).save(any(User.class));
|
||||
verify(databaseService, never()).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSaveUser_DatabaseExportThrowsException_StillSavesUser() throws Exception {
|
||||
// Given
|
||||
String username = "testuser";
|
||||
String password = "password123";
|
||||
Long teamId = 1L;
|
||||
String encodedPassword = "encodedPassword123";
|
||||
|
||||
when(passwordEncoder.encode(password)).thenReturn(encodedPassword);
|
||||
when(teamRepository.findById(teamId)).thenReturn(Optional.of(mockTeam));
|
||||
when(userRepository.save(any(User.class))).thenReturn(mockUser);
|
||||
doThrow(new SQLException("Database export failed")).when(databaseService).exportDatabase();
|
||||
|
||||
// When & Then
|
||||
assertThrows(SQLException.class, () -> userService.saveUser(username, password, teamId));
|
||||
|
||||
// Verify user was still saved before the exception
|
||||
verify(userRepository).save(any(User.class));
|
||||
verify(databaseService).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSaveUser_WithFirstLoginFlag_Success() throws Exception {
|
||||
// Given
|
||||
String username = "testuser";
|
||||
String password = "password123";
|
||||
Long teamId = 1L;
|
||||
boolean firstLogin = true;
|
||||
boolean enabled = false;
|
||||
String encodedPassword = "encodedPassword123";
|
||||
|
||||
when(passwordEncoder.encode(password)).thenReturn(encodedPassword);
|
||||
when(teamRepository.findById(teamId)).thenReturn(Optional.of(mockTeam));
|
||||
when(userRepository.save(any(User.class))).thenReturn(mockUser);
|
||||
doNothing().when(databaseService).exportDatabase();
|
||||
|
||||
// When
|
||||
userService.saveUser(username, password, teamId, firstLogin, enabled);
|
||||
|
||||
// Then
|
||||
verify(passwordEncoder).encode(password);
|
||||
verify(userRepository).save(any(User.class));
|
||||
verify(databaseService).exportDatabase();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSaveUser_WithCustomRole_Success() throws Exception {
|
||||
// Given
|
||||
String username = "testuser";
|
||||
String password = "password123";
|
||||
Long teamId = 1L;
|
||||
String customRole = Role.LIMITED_API_USER.getRoleId();
|
||||
String encodedPassword = "encodedPassword123";
|
||||
|
||||
when(passwordEncoder.encode(password)).thenReturn(encodedPassword);
|
||||
when(teamRepository.findById(teamId)).thenReturn(Optional.of(mockTeam));
|
||||
when(userRepository.save(any(User.class))).thenReturn(mockUser);
|
||||
doNothing().when(databaseService).exportDatabase();
|
||||
|
||||
// When
|
||||
userService.saveUser(username, password, teamId, customRole);
|
||||
|
||||
// Then
|
||||
verify(passwordEncoder).encode(password);
|
||||
verify(userRepository).save(any(User.class));
|
||||
verify(databaseService).exportDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
package stirling.software.proprietary.security.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class Base32CodecTest {
|
||||
|
||||
@Test
|
||||
void encodeReturnsEmptyStringForEmptyInput() {
|
||||
assertEquals("", Base32Codec.encode(new byte[0]));
|
||||
assertEquals("", Base32Codec.encode(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void decodeReturnsEmptyArrayForBlankInput() {
|
||||
assertArrayEquals(new byte[0], Base32Codec.decode(null));
|
||||
assertArrayEquals(new byte[0], Base32Codec.decode(""));
|
||||
assertArrayEquals(new byte[0], Base32Codec.decode(" "));
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeDecodeRoundTrip() {
|
||||
byte[] input = "hello world".getBytes(StandardCharsets.UTF_8);
|
||||
String encoded = Base32Codec.encode(input);
|
||||
byte[] decoded = Base32Codec.decode(encoded);
|
||||
|
||||
assertArrayEquals(input, decoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
void decodeAcceptsPaddingSpacesAndLowercase() {
|
||||
byte[] decoded = Base32Codec.decode("mzxw6y tboi====");
|
||||
assertEquals("foobar", new String(decoded, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void decodeRejectsInvalidCharacters() {
|
||||
assertThrows(IllegalArgumentException.class, () -> Base32Codec.decode("MZXW6$"));
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -67,7 +67,7 @@ springBoot {
|
||||
|
||||
allprojects {
|
||||
group = 'stirling.software'
|
||||
version = '2.4.4'
|
||||
version = '2.3.1'
|
||||
|
||||
configurations.configureEach {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
@@ -537,4 +537,4 @@ tasks.register('buildRestartHelper', Jar) {
|
||||
doLast {
|
||||
println "restart-helper.jar created at: ${destinationDirectory.get()}/restart-helper.jar"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ COPY . .
|
||||
|
||||
# Build server-only JAR (no frontend, includes security features controlled by DOCKER_ENABLE_SECURITY at runtime)
|
||||
RUN DISABLE_ADDITIONAL_FEATURES=false \
|
||||
STIRLING_PDF_DESKTOP_UI=false \
|
||||
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||
|
||||
# Stage 3: Final unified image
|
||||
|
||||
@@ -30,6 +30,7 @@ WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
RUN DISABLE_ADDITIONAL_FEATURES=true \
|
||||
STIRLING_PDF_DESKTOP_UI=false \
|
||||
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||
|
||||
# Stage 3: Final unified ultra-lite image
|
||||
|
||||
@@ -25,6 +25,7 @@ COPY . .
|
||||
|
||||
# Build the application (server-only JAR - no UI, includes security features controlled at runtime)
|
||||
RUN DISABLE_ADDITIONAL_FEATURES=false \
|
||||
STIRLING_PDF_DESKTOP_UI=false \
|
||||
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||
|
||||
# ========================================
|
||||
|
||||
@@ -25,6 +25,7 @@ COPY . .
|
||||
|
||||
# Build the application (server-only JAR - no UI, includes security features controlled at runtime)
|
||||
RUN DISABLE_ADDITIONAL_FEATURES=false \
|
||||
STIRLING_PDF_DESKTOP_UI=false \
|
||||
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||
|
||||
# ========================================
|
||||
|
||||
@@ -21,6 +21,7 @@ COPY . .
|
||||
|
||||
# Build the application with DISABLE_ADDITIONAL_FEATURES=true
|
||||
RUN DISABLE_ADDITIONAL_FEATURES=true \
|
||||
STIRLING_PDF_DESKTOP_UI=false \
|
||||
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||
|
||||
# ========================================
|
||||
|
||||
@@ -32,6 +32,7 @@ COPY . .
|
||||
|
||||
# Build JAR with embedded frontend (includes security features controlled at runtime)
|
||||
RUN DISABLE_ADDITIONAL_FEATURES=false \
|
||||
STIRLING_PDF_DESKTOP_UI=false \
|
||||
./gradlew clean build -PbuildWithFrontend=true -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||
|
||||
# Stage 2: Runtime image based on Debian stable-slim
|
||||
|
||||
@@ -32,6 +32,7 @@ COPY . .
|
||||
|
||||
# Build JAR with embedded frontend (includes security features controlled at runtime)
|
||||
RUN DISABLE_ADDITIONAL_FEATURES=false \
|
||||
STIRLING_PDF_DESKTOP_UI=false \
|
||||
./gradlew clean build -PbuildWithFrontend=true -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||
|
||||
# Stage 2: Runtime image based on Debian stable-slim
|
||||
|
||||
@@ -32,6 +32,7 @@ COPY . .
|
||||
|
||||
# Build ultra-lite JAR with embedded frontend (minimal features)
|
||||
RUN DISABLE_ADDITIONAL_FEATURES=true \
|
||||
STIRLING_PDF_DESKTOP_UI=false \
|
||||
./gradlew clean build -PbuildWithFrontend=true -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||
|
||||
# Stage 2: Runtime image
|
||||
@@ -58,7 +59,6 @@ LABEL org.opencontainers.image.keywords="PDF, manipulation, ultra-lite, API, Spr
|
||||
# Copy scripts
|
||||
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
||||
COPY scripts/installFonts.sh /scripts/installFonts.sh
|
||||
COPY scripts/stirling-diagnostics.sh /scripts/stirling-diagnostics.sh
|
||||
|
||||
# Copy built JAR from build stage
|
||||
COPY --from=build /app/app/core/build/libs/*.jar /app.jar
|
||||
|
||||
Generated
+217
-259
@@ -11,28 +11,27 @@
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@embedpdf/core": "^2.3.0",
|
||||
"@embedpdf/engines": "^2.3.0",
|
||||
"@embedpdf/models": "^2.3.0",
|
||||
"@embedpdf/plugin-annotation": "^2.3.0",
|
||||
"@embedpdf/plugin-bookmark": "^2.3.0",
|
||||
"@embedpdf/plugin-document-manager": "^2.3.0",
|
||||
"@embedpdf/plugin-export": "^2.3.0",
|
||||
"@embedpdf/plugin-history": "^2.3.0",
|
||||
"@embedpdf/plugin-interaction-manager": "^2.3.0",
|
||||
"@embedpdf/plugin-pan": "^2.3.0",
|
||||
"@embedpdf/plugin-print": "^2.3.0",
|
||||
"@embedpdf/plugin-redaction": "^2.3.0",
|
||||
"@embedpdf/plugin-render": "^2.3.0",
|
||||
"@embedpdf/plugin-rotate": "^2.3.0",
|
||||
"@embedpdf/plugin-scroll": "^2.3.0",
|
||||
"@embedpdf/plugin-search": "^2.3.0",
|
||||
"@embedpdf/plugin-selection": "^2.3.0",
|
||||
"@embedpdf/plugin-spread": "^2.3.0",
|
||||
"@embedpdf/plugin-thumbnail": "^2.3.0",
|
||||
"@embedpdf/plugin-tiling": "^2.3.0",
|
||||
"@embedpdf/plugin-viewport": "^2.3.0",
|
||||
"@embedpdf/plugin-zoom": "^2.3.0",
|
||||
"@embedpdf/core": "^1.5.0",
|
||||
"@embedpdf/engines": "^1.5.0",
|
||||
"@embedpdf/plugin-annotation": "^1.5.0",
|
||||
"@embedpdf/plugin-bookmark": "^1.5.0",
|
||||
"@embedpdf/plugin-export": "^1.5.0",
|
||||
"@embedpdf/plugin-history": "^1.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "^1.5.0",
|
||||
"@embedpdf/plugin-loader": "^1.5.0",
|
||||
"@embedpdf/plugin-pan": "^1.5.0",
|
||||
"@embedpdf/plugin-print": "^1.5.0",
|
||||
"@embedpdf/plugin-redaction": "^1.5.0",
|
||||
"@embedpdf/plugin-render": "^1.5.0",
|
||||
"@embedpdf/plugin-rotate": "^1.5.0",
|
||||
"@embedpdf/plugin-scroll": "^1.5.0",
|
||||
"@embedpdf/plugin-search": "^1.5.0",
|
||||
"@embedpdf/plugin-selection": "^1.5.0",
|
||||
"@embedpdf/plugin-spread": "^1.5.0",
|
||||
"@embedpdf/plugin-thumbnail": "^1.5.0",
|
||||
"@embedpdf/plugin-tiling": "^1.5.0",
|
||||
"@embedpdf/plugin-viewport": "^1.5.0",
|
||||
"@embedpdf/plugin-zoom": "^1.5.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@iconify/react": "^6.0.2",
|
||||
@@ -48,13 +47,13 @@
|
||||
"@supabase/supabase-js": "^2.47.13",
|
||||
"@tailwindcss/postcss": "^4.1.13",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@tauri-apps/api": "^2.9.1",
|
||||
"@tauri-apps/plugin-fs": "^2.4.5",
|
||||
"@tauri-apps/plugin-http": "^2.5.6",
|
||||
"@tauri-apps/plugin-shell": "^2.3.4",
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.0",
|
||||
"@tauri-apps/plugin-http": "^2.5.4",
|
||||
"@tauri-apps/plugin-shell": "^2.3.3",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"axios": "^1.13.2",
|
||||
"globals": "^17.1.0",
|
||||
"axios": "^1.12.2",
|
||||
"globals": "^17.0.0",
|
||||
"i18next": "^25.5.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"jszip": "^3.10.1",
|
||||
@@ -63,7 +62,7 @@
|
||||
"pdfjs-dist": "^5.4.149",
|
||||
"peerjs": "^1.5.5",
|
||||
"posthog-js": "^1.268.0",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"qrcode.react": "^4.1.0",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-i18next": "^15.7.3",
|
||||
@@ -76,10 +75,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@iconify-json/material-symbols": "^1.2.53",
|
||||
"@iconify/utils": "^3.1.0",
|
||||
"@iconify-json/material-symbols": "^1.2.48",
|
||||
"@iconify/utils": "^3.0.2",
|
||||
"@playwright/test": "^1.55.0",
|
||||
"@tauri-apps/cli": "^2.9.6",
|
||||
"@tauri-apps/cli": "^2.5.0",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
@@ -553,13 +552,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/core": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-2.3.0.tgz",
|
||||
"integrity": "sha512-aPD7lNSCOLc5Nos9xGA3qAT5jFZdrTT7IVcpxtM1BOKa1FI0XmotJ8vgzcRxH/FLwUASC4xwR9QxzTKp2aLsZQ==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.5.0.tgz",
|
||||
"integrity": "sha512-Yrh9XoVaT8cUgzgqpJ7hx5wg6BqQrCFirqqlSwVb+Ly9oNn4fZbR9GycIWmzJOU5XBnaOJjXfQSaDyoNP0woNA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/engines": "2.3.0",
|
||||
"@embedpdf/models": "2.3.0"
|
||||
"@embedpdf/engines": "1.5.0",
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"preact": "^10.26.4",
|
||||
@@ -570,20 +569,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/engines": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-2.3.0.tgz",
|
||||
"integrity": "sha512-QxNY58E2HgNgnbsTt5TnDUNvKoyabkf5IniGsiN5+rx6f4SFDpCnz3h1VJxNReWDyn9e16QlkUfgXX0qQWd3iQ==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-1.5.0.tgz",
|
||||
"integrity": "sha512-/GzhjHFHWfOaX7vjgFJX/pyq668wYjoda1bZ9MpwF/EF000Wwy2Q0AOhprjldPFz8ASKjwKwqsXmaqrK99yOAQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/fonts-arabic": "1.0.0",
|
||||
"@embedpdf/fonts-hebrew": "1.0.0",
|
||||
"@embedpdf/fonts-jp": "1.0.0",
|
||||
"@embedpdf/fonts-kr": "1.0.0",
|
||||
"@embedpdf/fonts-latin": "1.0.0",
|
||||
"@embedpdf/fonts-sc": "1.0.0",
|
||||
"@embedpdf/fonts-tc": "1.0.0",
|
||||
"@embedpdf/models": "2.3.0",
|
||||
"@embedpdf/pdfium": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0",
|
||||
"@embedpdf/pdfium": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"preact": "^10.26.4",
|
||||
@@ -593,125 +585,64 @@
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/fonts-arabic": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-arabic/-/fonts-arabic-1.0.0.tgz",
|
||||
"integrity": "sha512-SnGvQb+LwPZQO2WjjvlmXrJZolJUfLYbLZQSaYUw1vrQyMyJKT4LewvJGG+hZ+Yz2fz7OMIQ+4Gc98mGODZtOg==",
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@embedpdf/fonts-hebrew": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-hebrew/-/fonts-hebrew-1.0.0.tgz",
|
||||
"integrity": "sha512-5HVAKGL7VqPeTxxADDrSqAFBxfmAXdP8fIqrPwJIKkqdK2643bOer8CqnnpO3/nPoFhkzxhttWMB9BGiqSW62w==",
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@embedpdf/fonts-jp": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-jp/-/fonts-jp-1.0.0.tgz",
|
||||
"integrity": "sha512-BY2tv/mcICUUKf+M/bizf3RU65PMqKClJ/e5o9mgMibxyML0OQvEDwYMRPODQkKgJKXCO3ScHmVvcmXp6kt+fA==",
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@embedpdf/fonts-kr": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-kr/-/fonts-kr-1.0.0.tgz",
|
||||
"integrity": "sha512-bh88HXSvOBS581kgmihWY7Ijp9hBsvlmXogFG5LSNx9UBAobRcakZiFMGieRBc06hUSkpo7WhjaFM/z/SfQ8dQ==",
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@embedpdf/fonts-latin": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-latin/-/fonts-latin-1.0.0.tgz",
|
||||
"integrity": "sha512-LLYysdr8O6sRNzhmW3PbF3AeA8xnqvOi4XLFfIfNlW5uEZ+qsJdcfd78Q78sFJMhlaOAYFMziMMsnOzmx463rA==",
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@embedpdf/fonts-sc": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-sc/-/fonts-sc-1.0.0.tgz",
|
||||
"integrity": "sha512-ETXl7XCwaQLSSvMO3EUDwMNqtL64kX2LlFxarTRi/NsIGGOIxUurGfKtrkmtnKHrWy1jAJSt6oxK2uJhvdvQIw==",
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@embedpdf/fonts-tc": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-tc/-/fonts-tc-1.0.0.tgz",
|
||||
"integrity": "sha512-rGZJbVD6DYS5BbXdpEMnWkpVF0Knar+bsiyb2o3+YRx7O8eyFubEBQUSUInirQk69HA6fc3GhYCg7TyC/oD76Q==",
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@embedpdf/models": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-2.3.0.tgz",
|
||||
"integrity": "sha512-YAH3YdXl/UOhVcvMPd6mtU+tJ3veh24Q5swRDfuWUsJ3L2CcAG2P+4pjj4EAwvWUQcmN/HlVOjVQL0PkbkytKw==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-1.5.0.tgz",
|
||||
"integrity": "sha512-x/1li3jdag+IzfZkcfRLKLqASLep4v6dgVi3z0JArwaicFra8k1IY2xaVTrwcZyx7pRb/rxvoO9yLHW0Y34NFw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@embedpdf/pdfium": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-2.3.0.tgz",
|
||||
"integrity": "sha512-AIWHDDG24we1r8sWVO9Uae6V2ISXji2gIkZS3+CjtYowaBCpMTSu4QEQRnjQam2EWrEMVIJOXwBfx11TZKrxWA==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-1.5.0.tgz",
|
||||
"integrity": "sha512-PI32t2U4ThZC907n2Iwr8E5WqmC574G83u3V9ysNFl29N9kasrY9RiLSzU4W/yQvXPjIbpQHBsbMKXLjCFBI9w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-annotation": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-2.3.0.tgz",
|
||||
"integrity": "sha512-TIN/OiDTg5tCNsebp1SWnS6aa7nnDvRrrZe3jx7Sg5IMEiZc6P3z+0aOjJtvoz0cp3Xi7Bb0PQsTLwo+bdfpVg==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-1.5.0.tgz",
|
||||
"integrity": "sha512-mxEPI6xYwOGaf9fYfoywuj6nwA10eHFPBuN066MzwphDk6DOHJGZ3Vq8zNQBXh20c/Lb25PL718D7MZWxZLUHg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0",
|
||||
"@embedpdf/utils": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0",
|
||||
"@embedpdf/utils": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/plugin-history": "2.3.0",
|
||||
"@embedpdf/plugin-interaction-manager": "2.3.0",
|
||||
"@embedpdf/plugin-selection": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-history": "1.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "1.5.0",
|
||||
"@embedpdf/plugin-selection": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-bookmark": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-bookmark/-/plugin-bookmark-2.3.0.tgz",
|
||||
"integrity": "sha512-7XO2NntgRb/Jk1XN/EOf7+yVaOPVVFvBuF0xlCqnz2BGAnMNrTn8QE73FtluJBgNhuK9LwDT2C4W+BTD2gd59Q==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-bookmark/-/plugin-bookmark-1.5.0.tgz",
|
||||
"integrity": "sha512-s3C9PtVesy5X8Ds/C9TEElFiqfKGRklG/uNPTROpNoolfpi0h7qX2xqqh/9+FzKH2nHjVcPB7Pp432v16h7eRA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-document-manager": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-document-manager/-/plugin-document-manager-2.3.0.tgz",
|
||||
"integrity": "sha512-hdKaWU1sjlLgXo2iWF4N734lklCfSO5Tj1xqk+0omxOpnVL1Ed5fzFO2N584pMkfFn1xo9Y2JPHSUtCdzF7/EQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-export": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-2.3.0.tgz",
|
||||
"integrity": "sha512-Xa048lKnc1jehWbaWv5qER1RVIHhHqt+JhgzAlqFSURXmzowbUzVEDBZ7fYImXRkpqp+ZeyBhWfZ60DBNE55Cw==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-1.5.0.tgz",
|
||||
"integrity": "sha512-luk68mNW9l2X31qk4b02phKaqDl9aDXUAgHVz1EWrgwXQ3Oz9WEdu60utYARYDiepDo3Caadll8RwctYSf/anA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -720,15 +651,31 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-history": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-2.3.0.tgz",
|
||||
"integrity": "sha512-+fr/kjK2Z9BiC53IMlUZvWjkD6iilcI3XCUKQPXRgS5MDAuwpVlgdAtc+3VAMlG3IddElxVFdvvxRO9R89k5Mg==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.5.0.tgz",
|
||||
"integrity": "sha512-p7PTNNaIr4gH3jLwX+eLJe1DeUXgi21kVGN6SRx/pocH8esg4jqoOeD/YiRRZoZnPOiy0jBXVhkPkwSmY7a2hQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-interaction-manager": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.5.0.tgz",
|
||||
"integrity": "sha512-ckHgTfvkW6c5Ta7Mc+Dl9C2foVnvEpqEJ84wyBnqrU0OWbe/jsiPhyKBVeartMGqNI/kVfaQTXupyrKhekAVmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -736,16 +683,16 @@
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-interaction-manager": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-2.3.0.tgz",
|
||||
"integrity": "sha512-1/tDLPoQm6skNe/WOd6QD7SA0XRKphbJHi/s9XY4fhGgBvlD5XHFrYxtmrsaheYjqIBFtAWWZ3m5lAXRaO/igA==",
|
||||
"node_modules/@embedpdf/plugin-loader": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.5.0.tgz",
|
||||
"integrity": "sha512-P4YpIZfaW69etYIjphyaL4cGl2pB14h3OdTE0tRQ2pZYZHFLTvlt4q9B3PVSdhlSrHK5nob7jfLGon2U7xCslg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -754,17 +701,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-pan": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-2.3.0.tgz",
|
||||
"integrity": "sha512-5yGxLpn28PHKCYx3tjzeVir7D5vHZ0Fk9HJRJr4K+Uqbg8pYFavb9tseXzPE4FcqpejqZo2DZyfo54ErQFXEyQ==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-1.5.0.tgz",
|
||||
"integrity": "sha512-EMQ08dHqLkZmFVuLOO6h3AAinFPQoA1r6OlL9z+p0sswq31JAgd4X7+xjYIpI01z/V3+cTzPHzp7qwob5E4tbA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/plugin-interaction-manager": "2.3.0",
|
||||
"@embedpdf/plugin-viewport": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "1.5.0",
|
||||
"@embedpdf/plugin-viewport": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -773,15 +720,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-print": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-print/-/plugin-print-2.3.0.tgz",
|
||||
"integrity": "sha512-LNxvXm3rZkRXXC41IArBDiwPLzSflmBmxxi+L+91xvw8n/FWUeXfWwQn7oQEAGq9Ha/3pEVHTls48QSFZN0mhg==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-print/-/plugin-print-1.5.0.tgz",
|
||||
"integrity": "sha512-rjorvNxAZfO9X4cFZVU9fHnldMWqMceJGmr3mH+yj7KdHePvNDDP+omyZyZKtxlUZENaeDI2h6k5z0GbhBz6sQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=18.0.0",
|
||||
"react-dom": ">=18.0.0",
|
||||
@@ -790,35 +737,34 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-redaction": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-redaction/-/plugin-redaction-2.3.0.tgz",
|
||||
"integrity": "sha512-un6AQL5Pqcm9v1tCV9Mb3NeowsGUtlCT/198k4nd+SWOMWNsbuFqI+rWOGV3auqXRGSzKj0gnt29t8aaeLpLeA==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-redaction/-/plugin-redaction-1.5.0.tgz",
|
||||
"integrity": "sha512-txiukr5UKAGvJzl6dVBmmIT1v3r/t4e2qYm1hqU2faGgNCa2dwk79x9mDBlvWwxlJXCDFuFE+7Ps9/nU6qmU2w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0",
|
||||
"@embedpdf/utils": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0",
|
||||
"@embedpdf/utils": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/plugin-interaction-manager": "2.3.0",
|
||||
"@embedpdf/plugin-selection": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "1.5.0",
|
||||
"@embedpdf/plugin-selection": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-render": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-2.3.0.tgz",
|
||||
"integrity": "sha512-UyQncK5NTokuEVISUcxPOXpZP4SItn4MjfeEaPsTXJkSRjHL4g3mU3iWy0nXJMCOT10OB+5m7qQ0/KkF4f+b5w==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.5.0.tgz",
|
||||
"integrity": "sha512-ywwSj0ByrlkvrJIHKRzqxARkOZriki8VJUC+T4MV8fGyF4CzvCRJyKlPktahFz+VxhoodqTh7lBCib68dH+GvA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -827,15 +773,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-rotate": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-2.3.0.tgz",
|
||||
"integrity": "sha512-vibDXHA0L2LlMrmkSuanmdtUpc2JPBuQybiGwf9F4wlleKN3f7uSWxZsHdVAxWdzsaG+/26QTGl75otZLnVuig==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-1.5.0.tgz",
|
||||
"integrity": "sha512-5EmBCsq0VfrE3xWY6ofuVm8S6aK95EbAycRIk1wczcmTdvpsuXZ6P2ZaECUgYMcpZ6uAg4/kGf8X8VVZuCihSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -844,16 +790,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-scroll": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-2.3.0.tgz",
|
||||
"integrity": "sha512-8pdaSY9QuqdX22Ykw2jKn07Rx6FIsDdj/O0+mlbccY/ISofj9WEFNeQgnOY64OUTDyurJYqpYvq6QqvgbGLs+A==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.5.0.tgz",
|
||||
"integrity": "sha512-RNmTZCZ8X1mA8cw9M7TMDuhO9GtkOalGha2bBL3En3D1IlDRS7PzNNMSMV7eqT7OQICSTltlpJ8p8Qi5esvL/Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/plugin-viewport": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-viewport": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -862,15 +808,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-search": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-2.3.0.tgz",
|
||||
"integrity": "sha512-VNXmNf7fIIRWGVwf2kIUeUeLkUTJlq9AGjUO2TyuYJTWTsmfT4LEqPDDpwC6NDVFhzWE6xwbb3bxvY/9bqBMzw==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-1.5.0.tgz",
|
||||
"integrity": "sha512-TB5b0H8Iobx/azVUBIlG2ClaKtf0y3/Xi3E/iB8BwvkIE2+g6EGfp8IMXIn8WDXST6bbvJEP31Ab0Ilp6SVkiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-loader": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -879,17 +826,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-selection": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-2.3.0.tgz",
|
||||
"integrity": "sha512-+emaY4vff3ynAf5C3PfCOlleQIqiImbBpb6zkG5SVUa9Vn5x0SfYGT4Jumtbzq8XBknC1QIRKVlplC9BcnjcmQ==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.5.0.tgz",
|
||||
"integrity": "sha512-zrxLBAZQoPswDuf9q9DrYaQc6B0Ysc2U1hueTjNH/4+ydfl0BFXZkKR63C2e3YmWtXvKjkoIj0GyPzsiBORLUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0",
|
||||
"@embedpdf/utils": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/plugin-interaction-manager": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "1.5.0",
|
||||
"@embedpdf/plugin-viewport": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -898,15 +845,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-spread": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-2.3.0.tgz",
|
||||
"integrity": "sha512-sFqYKwzKGPaCXn6hAyv6GHdVTlL2vg3poxRNd2W5kLQo07YtHlSjXr/XAhaGT/a4GtR9rtbSJ4hWNJjzIcwE0g==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-1.5.0.tgz",
|
||||
"integrity": "sha512-3EU5Cp+fPQSiMjvMR/P2kXxXry/RlnxHLs4JeskAaH95QcqWW3VD+DrHkWSiLFkdhI18rNNGNlMc5RvDGvbXGQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-loader": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -915,16 +863,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-thumbnail": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-2.3.0.tgz",
|
||||
"integrity": "sha512-CAOnipeBtdKSHGBuIm5420GykUw7k2rB7Z9GwouTbbycS7Cw+kiaGpOfHfenoKPTlWMkHYAwFcZiWKV3XG/nRQ==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-1.5.0.tgz",
|
||||
"integrity": "sha512-Z2qpyyr5s2M6460KDGu1Vk6rdbQFIoCpnyFAT6e7UaTIKkqJSNpmjqMsBU5PosYCFu/cClpHPvS7tg9/IKAk6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/plugin-render": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-render": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -933,18 +881,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-tiling": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-2.3.0.tgz",
|
||||
"integrity": "sha512-6VJ042WksIyZVWyvXq1nf0Ct+U4Pl6+QUDy1ThJefwk/HKDfWU2zEr/+1STJKVWgfUx5QRdipf6Jghd+HnOg3Q==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-1.5.0.tgz",
|
||||
"integrity": "sha512-0Vx9elHNpMM+zv8hEoZXBEm8Q0+4kU52LxOlTYRr1A5FskF836sUct6g1ngwK1bmfbAfpz+62PnYI2EeilDZig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/plugin-render": "2.3.0",
|
||||
"@embedpdf/plugin-scroll": "2.3.0",
|
||||
"@embedpdf/plugin-viewport": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-render": "1.5.0",
|
||||
"@embedpdf/plugin-scroll": "1.5.0",
|
||||
"@embedpdf/plugin-viewport": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -953,15 +901,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-viewport": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-2.3.0.tgz",
|
||||
"integrity": "sha512-3NQp3hVfRF7DMUPNAVOfZsqQQrugEfY0voRUrQI90eyi16GFntN3CP9Mc5cOp2jnUICMYlirQ/om+KCseMHS2Q==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.5.0.tgz",
|
||||
"integrity": "sha512-G8GDyYRhfehw72+r4qKkydnA5+AU8qH67g01Y12b0DzI0VIzymh/05Z4dK8DsY3jyWPXJfw2hlg5+KDHaMBHgQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -970,17 +918,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-zoom": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-2.3.0.tgz",
|
||||
"integrity": "sha512-wnBqK02ku0zCViqQfSD1Vohy+aBUogXrqUTwo1/1QFEphmgnCHnHbEUduh9M0ghcT4s26pBgbJqCrShpGYAdvQ==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-1.5.0.tgz",
|
||||
"integrity": "sha512-LiDkCd5/IXg2CRORl1Yikan2op+AYXSxhHzCFatyBdwzVj+n4y9I74OwCI62Mar8WDAIMyXZDCQxGPToSm+zDw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.3.0"
|
||||
"@embedpdf/models": "1.5.0",
|
||||
"hammerjs": "^2.0.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.3.0",
|
||||
"@embedpdf/plugin-scroll": "2.3.0",
|
||||
"@embedpdf/plugin-viewport": "2.3.0",
|
||||
"@embedpdf/core": "1.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "1.5.0",
|
||||
"@embedpdf/plugin-scroll": "1.5.0",
|
||||
"@embedpdf/plugin-viewport": "1.5.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -989,15 +939,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/utils": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-2.3.0.tgz",
|
||||
"integrity": "sha512-9DV+tu+GsnijchNSG/NzslnxTGIUH6j2MxBR8QOoZLsWETEVaMLkHtbvzXPyMOx/5RlvBn8wR0jNKTNptOCnXQ==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-1.5.0.tgz",
|
||||
"integrity": "sha512-L6jsAPQPGM8ne+MMFAd5gqXb1RNEgNyh16VvVUVKcVnJlBhwil59nVeEQ0cwPhjF5qVeY6MQDIOjBzJqkgXOYg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
@@ -1949,9 +1898,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@iconify-json/material-symbols": {
|
||||
"version": "1.2.53",
|
||||
"resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.2.53.tgz",
|
||||
"integrity": "sha512-2jXBKFdNzL9zy6chnJqubykL9WZno7rEP6/isSzpp6fKJJMXXhRtVkaGw1Clle0RlXGWzVkd/eiYUH8f9/ILrQ==",
|
||||
"version": "1.2.50",
|
||||
"resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.2.50.tgz",
|
||||
"integrity": "sha512-71tjHR70h46LHtBFab3fAd2V/wPTO7JMV5lKnRn3IcF303LaFgAlO0BZeTJDcmCv9d0snRZmnoLZAJVD7/eisw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -4179,27 +4128,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-fs": {
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.4.5.tgz",
|
||||
"integrity": "sha512-dVxWWGE6VrOxC7/jlhyE+ON/Cc2REJlM35R3PJX3UvFw2XwYhLGQVAIyrehenDdKjotipjYEVc4YjOl3qq90fA==",
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.4.4.tgz",
|
||||
"integrity": "sha512-MTorXxIRmOnOPT1jZ3w96vjSuScER38ryXY88vl5F0uiKdnvTKKTtaEjTEo8uPbl4e3gnUtfsDVwC7h77GQLvQ==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-http": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-http/-/plugin-http-2.5.6.tgz",
|
||||
"integrity": "sha512-KhCK3TDNDF4vdz75/j+KNQipYKf+295Visa8r32QcXScg0+D3JwShcCM6D+FN8WuDF24X3KSiAB8QtRxW6jKRA==",
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-http/-/plugin-http-2.5.4.tgz",
|
||||
"integrity": "sha512-/i4U/9za3mrytTgfRn5RHneKubZE/dwRmshYwyMvNRlkWjvu1m4Ma72kcbVJMZFGXpkbl+qLyWMGrihtWB76Zg==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-shell": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.4.tgz",
|
||||
"integrity": "sha512-ktsRWf8wHLD17aZEyqE8c5x98eNAuTizR1FSX475zQ4TxaiJnhwksLygQz+AGwckJL5bfEP13nWrlTNQJUpKpA==",
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.3.tgz",
|
||||
"integrity": "sha512-Xod+pRcFxmOWFWEnqH5yZcA7qwAMuaaDkMR1Sply+F8VfBj++CGnj2xf5UoialmjZ2Cvd8qrvSCbU+7GgNVsKQ==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
@@ -7084,9 +7033,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/devalue": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz",
|
||||
"integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==",
|
||||
"version": "5.6.1",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.1.tgz",
|
||||
"integrity": "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
@@ -8607,9 +8556,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "17.1.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.1.0.tgz",
|
||||
"integrity": "sha512-8HoIcWI5fCvG5NADj4bDav+er9B9JMj2vyL2pI8D0eismKyUvPLTSs+Ln3wqhwcp306i73iyVnEKx3F6T47TGw==",
|
||||
"version": "17.0.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.0.0.tgz",
|
||||
"integrity": "sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -8711,6 +8660,15 @@
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/hammerjs": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
|
||||
"integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/has-bigints": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
||||
@@ -12218,9 +12176,9 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz",
|
||||
"integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.11.0.tgz",
|
||||
"integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
@@ -12240,12 +12198,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz",
|
||||
"integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.11.0.tgz",
|
||||
"integrity": "sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.13.0"
|
||||
"react-router": "7.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -13490,9 +13448,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/svelte": {
|
||||
"version": "5.48.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.48.2.tgz",
|
||||
"integrity": "sha512-VPWD+UyoSFZ7Nxix5K/F8yWiKWOiROkLlWYXOZReE0TUycw+58YWB3D6lAKT+57xmN99wRX4H3oZmw0NPy7y3Q==",
|
||||
"version": "5.46.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.46.1.tgz",
|
||||
"integrity": "sha512-ynjfCHD3nP2el70kN5Pmg37sSi0EjOm9FgHYQdC4giWG/hzO3AatzXXJJgP305uIhGQxSufJLuYWtkY8uK/8RA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -13504,7 +13462,7 @@
|
||||
"aria-query": "^5.3.1",
|
||||
"axobject-query": "^4.1.0",
|
||||
"clsx": "^2.1.1",
|
||||
"devalue": "^5.6.2",
|
||||
"devalue": "^5.5.0",
|
||||
"esm-env": "^1.2.1",
|
||||
"esrap": "^2.2.1",
|
||||
"is-reference": "^3.0.3",
|
||||
|
||||
+31
-32
@@ -7,28 +7,27 @@
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@embedpdf/core": "^2.3.0",
|
||||
"@embedpdf/engines": "^2.3.0",
|
||||
"@embedpdf/models": "^2.3.0",
|
||||
"@embedpdf/plugin-annotation": "^2.3.0",
|
||||
"@embedpdf/plugin-bookmark": "^2.3.0",
|
||||
"@embedpdf/plugin-export": "^2.3.0",
|
||||
"@embedpdf/plugin-history": "^2.3.0",
|
||||
"@embedpdf/plugin-document-manager": "^2.3.0",
|
||||
"@embedpdf/plugin-interaction-manager": "^2.3.0",
|
||||
"@embedpdf/plugin-pan": "^2.3.0",
|
||||
"@embedpdf/plugin-print": "^2.3.0",
|
||||
"@embedpdf/plugin-redaction": "^2.3.0",
|
||||
"@embedpdf/plugin-render": "^2.3.0",
|
||||
"@embedpdf/plugin-rotate": "^2.3.0",
|
||||
"@embedpdf/plugin-scroll": "^2.3.0",
|
||||
"@embedpdf/plugin-search": "^2.3.0",
|
||||
"@embedpdf/plugin-selection": "^2.3.0",
|
||||
"@embedpdf/plugin-spread": "^2.3.0",
|
||||
"@embedpdf/plugin-thumbnail": "^2.3.0",
|
||||
"@embedpdf/plugin-tiling": "^2.3.0",
|
||||
"@embedpdf/plugin-viewport": "^2.3.0",
|
||||
"@embedpdf/plugin-zoom": "^2.3.0",
|
||||
"@embedpdf/core": "^1.5.0",
|
||||
"@embedpdf/engines": "^1.5.0",
|
||||
"@embedpdf/plugin-annotation": "^1.5.0",
|
||||
"@embedpdf/plugin-bookmark": "^1.5.0",
|
||||
"@embedpdf/plugin-export": "^1.5.0",
|
||||
"@embedpdf/plugin-history": "^1.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "^1.5.0",
|
||||
"@embedpdf/plugin-loader": "^1.5.0",
|
||||
"@embedpdf/plugin-pan": "^1.5.0",
|
||||
"@embedpdf/plugin-print": "^1.5.0",
|
||||
"@embedpdf/plugin-redaction": "^1.5.0",
|
||||
"@embedpdf/plugin-render": "^1.5.0",
|
||||
"@embedpdf/plugin-rotate": "^1.5.0",
|
||||
"@embedpdf/plugin-scroll": "^1.5.0",
|
||||
"@embedpdf/plugin-search": "^1.5.0",
|
||||
"@embedpdf/plugin-selection": "^1.5.0",
|
||||
"@embedpdf/plugin-spread": "^1.5.0",
|
||||
"@embedpdf/plugin-thumbnail": "^1.5.0",
|
||||
"@embedpdf/plugin-tiling": "^1.5.0",
|
||||
"@embedpdf/plugin-viewport": "^1.5.0",
|
||||
"@embedpdf/plugin-zoom": "^1.5.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@iconify/react": "^6.0.2",
|
||||
@@ -44,13 +43,13 @@
|
||||
"@supabase/supabase-js": "^2.47.13",
|
||||
"@tailwindcss/postcss": "^4.1.13",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@tauri-apps/api": "^2.9.1",
|
||||
"@tauri-apps/plugin-fs": "^2.4.5",
|
||||
"@tauri-apps/plugin-http": "^2.5.6",
|
||||
"@tauri-apps/plugin-shell": "^2.3.4",
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.0",
|
||||
"@tauri-apps/plugin-http": "^2.5.4",
|
||||
"@tauri-apps/plugin-shell": "^2.3.3",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"axios": "^1.13.2",
|
||||
"globals": "^17.1.0",
|
||||
"axios": "^1.12.2",
|
||||
"globals": "^17.0.0",
|
||||
"i18next": "^25.5.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"jszip": "^3.10.1",
|
||||
@@ -59,7 +58,7 @@
|
||||
"pdfjs-dist": "^5.4.149",
|
||||
"peerjs": "^1.5.5",
|
||||
"posthog-js": "^1.268.0",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"qrcode.react": "^4.1.0",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-i18next": "^15.7.3",
|
||||
@@ -127,10 +126,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@iconify-json/material-symbols": "^1.2.53",
|
||||
"@iconify/utils": "^3.1.0",
|
||||
"@iconify-json/material-symbols": "^1.2.48",
|
||||
"@iconify/utils": "^3.0.2",
|
||||
"@playwright/test": "^1.55.0",
|
||||
"@tauri-apps/cli": "^2.9.6",
|
||||
"@tauri-apps/cli": "^2.5.0",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "اختر العملية"
|
||||
addOperationButton = "إضافة عملية"
|
||||
pipelineHeader = "خط الأنابيب:"
|
||||
saveButton = "تنزيل"
|
||||
saveForFolderScanning = "حفظ لمسح المجلد"
|
||||
validateButton = "تحقق"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "ملء الشاشة"
|
||||
[settings.general.updates]
|
||||
title = "تحديثات البرنامج"
|
||||
description = "التحقق من التحديثات وعرض معلومات الإصدار"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "الإصدار الحالي"
|
||||
latestVersion = "أحدث إصدار"
|
||||
checkForUpdates = "التحقق من التحديثات"
|
||||
viewDetails = "عرض التفاصيل"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "إعداد متصفح الويب"
|
||||
syncToBrowser = "مزامنة الحساب -> المتصفح"
|
||||
syncToAccount = "مزامنة الحساب <- المتصفح"
|
||||
|
||||
[account.mfa]
|
||||
title = "المصادقة ذات العاملين"
|
||||
setupFailed = "تعذّر بدء إعداد المصادقة ذات العاملين. يُرجى المحاولة مجددًا."
|
||||
codeRequired = "أدخل رمز المصادقة للمتابعة."
|
||||
enabled = "تم تمكين المصادقة ذات العاملين."
|
||||
enableFailed = "تعذّر تمكين المصادقة ذات العاملين. تحقّق من الرمز وحاول مرة أخرى."
|
||||
disabled = "تم تعطيل المصادقة ذات العاملين."
|
||||
disableFailed = "تعذّر تعطيل المصادقة ذات العاملين. تحقّق من الرمز وحاول مرة أخرى."
|
||||
description = "أضِف طبقة أمان إضافية إلى حسابك."
|
||||
enableButton = "تمكين المصادقة ذات العاملين"
|
||||
disableButton = "تعطيل المصادقة ذات العاملين"
|
||||
setupTitle = "إعداد المصادقة ذات العاملين"
|
||||
setupDescription = "امسح رمز QR باستخدام تطبيق المصادِق لديك، ثم أدخل الرمز المكوَّن من 6 أرقام للتأكيد."
|
||||
manualKey = "مفتاح الإعداد اليدوي"
|
||||
secretWarning = "احفظ هذا المفتاح سريًا. أي شخص لديه وصول يستطيع إنشاء رموز مصادقة صالحة."
|
||||
codePlaceholder = "أدخل رمزًا مكوّنًا من 6 أرقام"
|
||||
confirmEnable = "تمكين"
|
||||
disableTitle = "تعطيل المصادقة ذات العاملين"
|
||||
disableDescription = "أدخل رمز مصادقة صالحًا لتعطيل المصادقة ذات العاملين."
|
||||
codeLabel = "رمز المصادقة"
|
||||
confirmDisable = "تعطيل"
|
||||
ssoDescription = "تدار المصادقة ذات العاملين من قِبل موفّر الهوية الخاص بك لحسابات تسجيل الدخول الموحّد."
|
||||
ssoManaged = "قم بإعداد MFA عبر موفّر هويتك."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "إعدادات التحكم في المستخدم"
|
||||
header = "إعدادات التحكم في المستخدم للمسؤول"
|
||||
@@ -952,7 +925,6 @@ title = "تقسيم تلقائي بالحجم/العدد"
|
||||
desc = "تقسيم ملف PDF واحد إلى مستندات متعددة بناءً على الحجم أو عدد الصفحات أو عدد المستندات"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "استبدال وعكس الألوان"
|
||||
desc = "استبدال الألوان أو عكسها في مستندات PDF"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "المسح الآلي للمجلدات"
|
||||
desc = "رابط إلى دليل المسح الآلي للمجلدات"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "دليل SSO"
|
||||
desc = "رابط إلى دليل SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "إعداد معزول"
|
||||
desc = "رابط إلى دليل الإعداد المعزول"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "إضافة كلمة مرور"
|
||||
desc = "تشفير مستند PDF الخاص بك بكلمة مرور."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "تغيير الأذونات"
|
||||
desc = "تغيير قيود المستند وأذوناته"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "أتمتة"
|
||||
desc = "ابنِ تدفّقات عمل متعددة الخطوات بسلسلة إجراءات PDF. مثالي للمهام المتكررة."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "تراكب ملف PDF فوق آخر"
|
||||
title = "تراكب ملفات PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "محرر نصوص PDF"
|
||||
desc = "مراجعة وتحرير صادرات Stirling PDF بصيغة JSON مع تحرير نصوص مجمّعة وإعادة إنشاء PDF"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "تنزيل ملف HTML الوسيط بدلًا من PDF"
|
||||
pdfaOptions = "خيارات PDF/A"
|
||||
outputFormat = "صيغة الناتج"
|
||||
pdfaNote = "PDF/A-1b أكثر توافقًا، وPDF/A-2b يدعم مزيدًا من الميزات."
|
||||
pdfaFormat = "تنسيق PDF/A"
|
||||
pdfaDigitalSignatureWarning = "يحتوي PDF على توقيع رقمي. سيتم إزالة هذا في الخطوة التالية."
|
||||
pdfxDigitalSignatureWarning = "يحتوي ملف PDF على توقيع رقمي. سيتم إزالة ذلك في الخطوة التالية."
|
||||
pdfxDescription = "PDF/X هو مجموعة فرعية قياسية من PDF وفق ISO يوفّر طباعة موثوقة وتبادُل الرسومات."
|
||||
fileFormat = "صيغة الملف"
|
||||
wordDoc = "مستند Word"
|
||||
wordDocExt = "مستند Word (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "لا يعمل حاليًا لمدخلات متعددة في وقت واح
|
||||
outputFormat = "تنسيق الإخراج"
|
||||
pdfWithDigitalSignature = "يحتوي PDF على توقيع رقمي. سيتم إزالة هذا في الخطوة التالية."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "طباعة,معيار,تحويل,إنتاج,ما قبل الطباعة,أرشفة"
|
||||
title = "PDF إلى PDF/X"
|
||||
header = "PDF إلى PDF/X"
|
||||
credit = "تستخدم هذه الخدمة Ghostscript لتحويل PDF/X"
|
||||
submit = "تحويل"
|
||||
tip = "لا يعمل حاليًا مع مدخلات متعددة دفعة واحدة"
|
||||
outputFormat = "تنسيق الإخراج"
|
||||
pdfWithDigitalSignature = "يحتوي ملف PDF على توقيع رقمي. سيتم إزالة ذلك في الخطوة التالية."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc,docx,odt,وورد,تحويل,تنسيق,تحويل,مكتب,مايكروسوفت,ملف المستند"
|
||||
title = "تحويل PDF إلى Word"
|
||||
@@ -3200,18 +3153,6 @@ other = "أخرى"
|
||||
perPageInfo = "معلومات لكل صفحة"
|
||||
tableOfContents = "جدول المحتويات"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "غير مُكتشف"
|
||||
passed = "تم الاجتياز"
|
||||
failed = "فشل"
|
||||
compliant = "متوافق"
|
||||
nonCompliant = "غير متوافق"
|
||||
none = "لم يتم اكتشاف أي معايير"
|
||||
passedCount = "ناجحة"
|
||||
failedCount = "فاشلة"
|
||||
noVerification = "لم يتم إجراء تحقق"
|
||||
noVerificationDesc = "لم يتم التحقق من التوافق مع معايير PDF لهذا المستند."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "المرفقات"
|
||||
embeddedFiles = "ملفات مضمنة"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} قيود"
|
||||
permsMixed = "بعض الأذونات مقيّدة"
|
||||
hasCompliance = "يتضمن معايير امتثال"
|
||||
noCompliance = "لا توجد معايير امتثال"
|
||||
compliancePassed = "{{standards}} متوافق"
|
||||
complianceChecked = "تم التحقق من المعايير ({{failed}} فاشلة)"
|
||||
basic = "معلومات أساسية"
|
||||
documentInfo = "معلومات المستند"
|
||||
securityTitle = "حالة الأمان"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "حجم الصورة"
|
||||
margin = "الهامش"
|
||||
positionAndFormatting = "الموضع والتنسيق"
|
||||
quickPosition = "اختر موضعًا على الصفحة لوضع الختم."
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "حدث خطأ أثناء إضافة الختم إلى ملف PDF."
|
||||
@@ -3594,14 +3508,6 @@ failed = "حدث خطأ أثناء إضافة الختم إلى ملف PDF."
|
||||
[AddStampRequest.results]
|
||||
title = "نتائج إضافة الختم"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "إزالة الصورة,عمليات الصفحة,الخلفية,جانب الخادم"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "تم تغيير كلمة المرور بنجاح! ير
|
||||
credentialsUpdated = "تم تحديث بيانات الاعتماد الخاصة بك. يرجى تسجيل الدخول مجددًا."
|
||||
defaultCredentials = "بيانات اعتماد تسجيل الدخول الافتراضية"
|
||||
changePasswordWarning = "يرجى تغيير كلمة المرور بعد تسجيل الدخول لأول مرة"
|
||||
mfaRequired = "رمز المصادقة ذات العاملين مطلوب"
|
||||
mfaCode = "رمز المصادقة"
|
||||
enterMfaCode = "أدخل رمزًا مكوّنًا من 6 أرقام"
|
||||
mfaPromptTitle = "المصادقة ذات العاملين"
|
||||
mfaPromptBody = "أدخل رمز المصادقة من تطبيق المصادِق الخاص بك للمتابعة."
|
||||
verifyingMfa = "جارٍ التحقق..."
|
||||
verifyMfa = "تحقّق من الرمز"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "نظرة عامة على Stirling PDF"
|
||||
@@ -4425,10 +4324,6 @@ title = "مطلوب وضع تسجيل الدخول"
|
||||
message = "يجب تمكين وضع تسجيل الدخول لتعديل إعدادات المسؤول. يرجى ضبط SECURITY_ENABLELOGIN=true في بيئتك أو security.enableLogin: true في settings.yml، ثم أعد تشغيل الخادم."
|
||||
readOnly = "تعرض الإعدادات أدناه قيمًا نموذجية للمرجع. فعّل وضع تسجيل الدخول لعرض التهيئة الفعلية وتحريرها."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "مطلوب ترخيص Enterprise"
|
||||
message = "يلزم توفر ترخيص Enterprise للوصول إلى {{featureName}}. أنت تشاهد بيانات تجريبية للمرجع."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "مطلوب إعادة تشغيل"
|
||||
message = "تم حفظ الإعدادات بنجاح. يلزم إعادة تشغيل الخادم لتطبيق التغييرات."
|
||||
@@ -4568,13 +4463,6 @@ description = "الحد الأقصى لعدد محاولات تسجيل الدخ
|
||||
label = "وقت إعادة التعيين لتسجيل الدخول (بالدقائق)"
|
||||
description = "المدة قبل إعادة تعيين عدد المحاولات الفاشلة"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "تعطيل حماية CSRF"
|
||||
description = "تعطيل حماية تزوير طلبات المواقع (غير مستحسن)"
|
||||
@@ -4883,48 +4771,6 @@ description = "إظهار رسائل خطأ مفصّلة للمستخدمين."
|
||||
title = "قاعدة البيانات"
|
||||
description = "تهيئة إعدادات اتصال قاعدة بيانات مخصصة لعمليات النشر المؤسسية."
|
||||
configuration = "تهيئة قاعدة البيانات"
|
||||
backupTitle = "النسخ الاحتياطية والاستعادة"
|
||||
backupDescription = "إدارة نسخ H2 الاحتياطية مباشرةً من وحدة تحكم المسؤول."
|
||||
loadError = "فشل تحميل النسخ الاحتياطية لقاعدة البيانات"
|
||||
backupCreated = "تم إنشاء النسخة الاحتياطية بنجاح"
|
||||
backupFailed = "فشل إنشاء النسخة الاحتياطية"
|
||||
deleteTitle = "حذف النسخة الاحتياطية"
|
||||
deleteConfirm = "هل تريد حذف هذه النسخة الاحتياطية؟ لا يمكن التراجع عن ذلك."
|
||||
deleteSuccess = "تم حذف النسخة الاحتياطية"
|
||||
deleteFailed = "فشل حذف النسخة الاحتياطية"
|
||||
deleteConfirmAction = "حذف النسخة الاحتياطية"
|
||||
downloadFailed = "فشل تنزيل النسخة الاحتياطية"
|
||||
version = "إصدار H2"
|
||||
embedded = "H2 مضمنة"
|
||||
external = "قاعدة بيانات خارجية"
|
||||
h2Only = "النسخ الاحتياطية متاحة فقط لقاعدة بيانات H2 المضمنة."
|
||||
h2Hint = "قم بتعيين نوع قاعدة البيانات إلى H2 وتعطيل قاعدة البيانات المخصصة لتمكين النسخ الاحتياطي والاستعادة."
|
||||
manageBackups = "إدارة النسخ الاحتياطية"
|
||||
refresh = "تحديث"
|
||||
createBackup = "إنشاء نسخة احتياطية"
|
||||
uploadTitle = "رفع واستيراد"
|
||||
chooseFile = "اختر ملف نسخ احتياطي بصيغة .sql"
|
||||
importFromUpload = "استيراد الملف المرفوع"
|
||||
confirmImportTitle = "تأكيد استيراد قاعدة البيانات"
|
||||
overwriteWarning = "تحذير: سيؤدي هذا إلى الكتابة فوق قاعدة البيانات الحالية."
|
||||
overwriteWarningBody = "سيتم استبدال جميع البيانات الحالية بالنسخة الاحتياطية المرفوعة. لا يمكن التراجع عن هذا الإجراء."
|
||||
confirmCodeLabel = "أدخل رمز التأكيد للمتابعة"
|
||||
enterCode = "أدخل الرمز الموضح أعلاه"
|
||||
confirmImport = "تأكيد الاستيراد"
|
||||
codeMismatch = "رمز التأكيد غير متطابق"
|
||||
codeMismatchBody = "يرجى إدخال الرمز تمامًا كما هو موضح للمتابعة."
|
||||
selectFile = "يرجى اختيار ملف .sql لاستيراده"
|
||||
importSuccess = "تم استيراد النسخة الاحتياطية بنجاح"
|
||||
importFailed = "فشل استيراد النسخة الاحتياطية"
|
||||
noBackups = "لا توجد نسخ احتياطية حتى الآن."
|
||||
unavailable = "قائمة النسخ الاحتياطية غير متاحة لتكوين قاعدة البيانات الحالي."
|
||||
fileName = "الملف"
|
||||
created = "تاريخ الإنشاء"
|
||||
size = "الحجم"
|
||||
actions = "إجراءات"
|
||||
download = "تنزيل"
|
||||
import = "استيراد"
|
||||
delete = "حذف"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "تمكين قاعدة بيانات مخصصة"
|
||||
@@ -5002,26 +4848,6 @@ description = "الحد الأقصى لـ DPI لمعالجة الصور (0 = غ
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "دليل tessdata"
|
||||
description = "المسار إلى دليل tessdata لملفات لغات OCR"
|
||||
downloadMissingTitle = "لم يتم اختيار أي لغة"
|
||||
downloadMissingBody = "يرجى اختيار لغة واحدة على الأقل للتنزيل."
|
||||
downloadSuccessTitle = "تم تنزيل اللغات"
|
||||
downloadSuccessBody = "تم حفظ لغات tessdata المحددة."
|
||||
downloadErrorTitle = "فشل التنزيل"
|
||||
loadingLanguages = "جارٍ تحميل لغات tessdata المثبتة..."
|
||||
installedLanguages = "لغات tessdata المثبتة"
|
||||
noLanguages = "لم يتم العثور على لغات tessdata في الدليل المُكوَّن."
|
||||
downloadLabel = "تنزيل لغات tessdata إضافية"
|
||||
downloadPlaceholder = "اختر اللغات"
|
||||
downloadNothingFound = "لم يتم العثور على لغات إضافية"
|
||||
permissionNotice = "مسار tessdata غير قابل للكتابة. سيتم فتح التنزيلات في المتصفح؛ يرجى حفظ ملفات .traineddata يدويًا في مجلد tessdata."
|
||||
manualLinks = "تنزيلات يدوية: انقر على الروابط وضع الملفات في مجلد tessdata."
|
||||
downloadButton = "تنزيل اللغات المحددة"
|
||||
downloadInvalidTitle = "اختيار غير صالح"
|
||||
downloadInvalidBody = "بعض اللغات المحددة غير متاحة للتنزيل. يرجى التحديث والاختيار من القائمة."
|
||||
downloadErrorNetwork = "فشل التنزيل بسبب خطأ في الشبكة. يرجى التحقق من الاتصال والمحاولة مرة أخرى."
|
||||
downloadErrorServer = "حدث خطأ في الخادم أثناء تنزيل لغات tessdata. يرجى المحاولة لاحقًا."
|
||||
downloadErrorPermission = "دليل tessdata غير قابل للكتابة: {{message}}. يرجى اختيار دليل قابل للكتابة (مثلاً ضمن مجلد بيانات التطبيق) أو ضبط الأذونات."
|
||||
downloadErrorGeneric = "فشل التنزيل: {{message}}. يرجى المحاولة لاحقًا."
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "تعطيل تنقية HTML"
|
||||
@@ -5525,7 +5351,6 @@ desc = "ابنِ مهامًا متعددة الخطوات عبر ربط إجرا
|
||||
invalidStep = "خطوة غير صالحة"
|
||||
reviewTitle = "نتائج الأتمتة"
|
||||
copyToSaved = "نسخ إلى المحفوظ"
|
||||
exportForFolderScanning = "تصدير لمسح المجلد"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "اختر ملفات لمعالجتها بهذه الأتمتة"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "إنشاء أتمتة"
|
||||
editTitle = "تحرير الأتمتة"
|
||||
intro = "تشغّل الأتمتات الأدوات بالتتابع. للبدء، أضف الأدوات بالترتيب الذي تريد تشغيلها."
|
||||
save = "حفظ الأتمتة"
|
||||
exportForFolderScanning = "تصدير لمسح المجلد"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "اسم الأتمتة"
|
||||
@@ -5922,11 +5746,9 @@ username = "اسم المستخدم (البريد الإلكتروني)"
|
||||
usernamePlaceholder = "user@example.com"
|
||||
password = "كلمة المرور"
|
||||
passwordPlaceholder = "أدخل كلمة المرور"
|
||||
passwordRequired = "كلمة المرور مطلوبة"
|
||||
role = "الدور"
|
||||
team = "الفريق (اختياري)"
|
||||
teamPlaceholder = "اختر فريقًا"
|
||||
authType = "نوع المصادقة"
|
||||
forcePasswordChange = "فرض تغيير كلمة المرور عند أول تسجيل دخول"
|
||||
cancel = "إلغاء"
|
||||
submit = "إضافة عضو"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "اسم المستخدم وكلمة المرور مطلوبا
|
||||
passwordTooShort = "يجب أن تتكون كلمة المرور من 6 أحرف على الأقل"
|
||||
success = "تم إنشاء المستخدم بنجاح"
|
||||
error = "فشل إنشاء المستخدم"
|
||||
forceMFA = "فرض إعداد MFA عند تسجيل الدخول التالي"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "كلمة المرور"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "سيتم مصادقة المستخدم عبر موفّر SSO"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "تحرير العضو"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "متاح {{count}} مقعد مستخدم"
|
||||
noSlotsAvailable = "لا توجد مقاعد متاحة"
|
||||
currentUsage = "تستخدم حالياً {{current}} من {{max}} تراخيص المستخدمين"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "تم تعطيل MFA بنجاح للمستخدم"
|
||||
adminDisableError = "فشل تعطيل MFA للمستخدم"
|
||||
disableByAdmin = "تعطيل MFA"
|
||||
|
||||
[workspace.teams]
|
||||
title = "الفرق"
|
||||
description = "إدارة الفرق وتنظيم أعضاء مساحة العمل"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "نقطة النهاية"
|
||||
visits = "الزيارات"
|
||||
percentage = "النسبة المئوية"
|
||||
noData = "لا توجد بيانات متاحة"
|
||||
unknownEndpoint = "نقطة نهاية غير معروفة"
|
||||
|
||||
[backendHealth]
|
||||
checking = "جارٍ التحقق من حالة الواجهة الخلفية..."
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "Əməliyyat seç"
|
||||
addOperationButton = "Əməliyyat əlavə et"
|
||||
pipelineHeader = "Boru xətti:"
|
||||
saveButton = "Endir"
|
||||
saveForFolderScanning = "Qovluq skanı üçün saxla"
|
||||
validateButton = "Doğrula"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "Tam ekran"
|
||||
[settings.general.updates]
|
||||
title = "Proqram yeniləmələri"
|
||||
description = "Yeniləmələri yoxlayın və versiya məlumatlarını görün"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "Cari versiya"
|
||||
latestVersion = "Ən son versiya"
|
||||
checkForUpdates = "Yeniləmələri yoxla"
|
||||
viewDetails = "Ətraflı bax"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "Veb Brauzer Parametrləri"
|
||||
syncToBrowser = "Hesabı Sinxronlaşdır -> Brauzer"
|
||||
syncToAccount = "Hesabı Sinxronlaşdır <- Brauzer"
|
||||
|
||||
[account.mfa]
|
||||
title = "İki faktorlu doğrulama"
|
||||
setupFailed = "İki faktorlu doğrulamanın quraşdırılmasına başlamaq mümkün olmadı. Zəhmət olmasa yenidən cəhd edin."
|
||||
codeRequired = "Davam etmək üçün doğrulama kodunu daxil edin."
|
||||
enabled = "İki faktorlu doğrulama aktiv edildi."
|
||||
enableFailed = "İki faktorlu doğrulamanı aktiv etmək mümkün olmadı. Kodu yoxlayın və yenidən cəhd edin."
|
||||
disabled = "İki faktorlu doğrulama deaktiv edildi."
|
||||
disableFailed = "İki faktorlu doğrulamanı deaktiv etmək mümkün olmadı. Kodu yoxlayın və yenidən cəhd edin."
|
||||
description = "Hesabınıza əlavə təhlükəsizlik qatı əlavə edin."
|
||||
enableButton = "İki faktorlu doğrulamanı aktiv et"
|
||||
disableButton = "İki faktorlu doğrulamanı deaktiv et"
|
||||
setupTitle = "İki faktorlu doğrulamanı quraşdırın"
|
||||
setupDescription = "Doğrulama tətbiqinizlə QR kodunu skan edin, sonra təsdiqləmək üçün 6 rəqəmli kodu daxil edin."
|
||||
manualKey = "Əl ilə quraşdırma açarı"
|
||||
secretWarning = "Bu açarı məxfi saxlayın. Girişi olan hər kəs etibarlı doğrulama kodları yarada bilər."
|
||||
codePlaceholder = "6 rəqəmli kodu daxil edin"
|
||||
confirmEnable = "Aktiv et"
|
||||
disableTitle = "İki faktorlu doğrulamanı deaktiv et"
|
||||
disableDescription = "İki faktorlu doğrulamanı deaktiv etmək üçün etibarlı doğrulama kodunu daxil edin."
|
||||
codeLabel = "Doğrulama kodu"
|
||||
confirmDisable = "Deaktiv et"
|
||||
ssoDescription = "Single sign-on hesabları üçün iki faktorlu doğrulama şəxsiyyət provayderiniz tərəfindən idarə olunur."
|
||||
ssoManaged = "MFA-nı şəxsiyyət provayderiniz vasitəsilə konfiqurasiya edin."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "İstifadəçi İdarəetmə Parametrləri"
|
||||
header = "Admin İstifadəçi Nəzarəti Parametrləri"
|
||||
@@ -952,7 +925,6 @@ title = "Say/Ölçüyə Əsasən Avtomatik Ayır"
|
||||
desc = "PDF-i ölçüyə, səhifə sayına və ya sənəd sayına əsasən bir neçə PDF-ə ayır."
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Rəngi əvəz et və invert et"
|
||||
desc = "PDF sənədlərində rəngləri əvəz edin və ya invert edin"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "Avtomatik qovluq skanı"
|
||||
desc = "Avtomatlaşdırılmış qovluq skan təlimatına keçid"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO təlimatı"
|
||||
desc = "SSO təlimatına keçid"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Şəbəkəsiz quraşdırma"
|
||||
desc = "Təcrid edilmiş quraşdırma təlimatına keçid"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Şifr Əlavə Et"
|
||||
desc = "Sənədini şifr ilə kilidlə."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "İcazələri Dəyişdir"
|
||||
desc = "Sənəd məhdudiyyətlərini və icazələrini dəyişin"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "Avtomatlaşdır"
|
||||
desc = "PDF əməliyyatlarını zəncirləyərək çoxaddımlı iş axınları qurun. Təkrarlanan tapşırıqlar üçün idealdır."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Bir PDF-i digərinin üstünə qoyur"
|
||||
title = "Üst-Üstə Qoy"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF Mətn Redaktoru"
|
||||
desc = "Qruplaşdırılmış mətn redaktəsi və PDF yenidən yaradılması ilə Stirling PDF JSON ixraclarını nəzərdən keçirin və redaktə edin"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "PDF əvəzinə ara HTML faylını endir"
|
||||
pdfaOptions = "PDF/A seçimləri"
|
||||
outputFormat = "Çıxış formatı"
|
||||
pdfaNote = "PDF/A-1b daha uyğundur, PDF/A-2b daha çox funksiyanı dəstəkləyir."
|
||||
pdfaFormat = "PDF/A formatı"
|
||||
pdfaDigitalSignatureWarning = "PDF Rəqəmsal İmza Ehtiva Edir.Bu, növbəti addımda silinəcək."
|
||||
pdfxDigitalSignatureWarning = "PDF rəqəmsal imza ehtiva edir. Bu imza növbəti addımda silinəcək."
|
||||
pdfxDescription = "PDF/X etibarlı çap və qrafika mübadiləsi üçün ISO standartlı PDF alt dəstidir."
|
||||
fileFormat = "Fayl formatı"
|
||||
wordDoc = "Word sənədi"
|
||||
wordDocExt = "Word sənədi (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "Hazırda Birdən Çox Giriş Üçün İşləmir"
|
||||
outputFormat = "Çıxış Formatı"
|
||||
pdfWithDigitalSignature = "PDF Rəqəmsal İmza Ehtiva Edir.Bu, növbəti addımda silinəcək."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "çap,standart,çevrilmə,istehsal,prepress,arxiv"
|
||||
title = "PDF-dən PDF/X-ə"
|
||||
header = "PDF-dən PDF/X-ə"
|
||||
credit = "Bu xidmət PDF/X çevrilməsi üçün Ghostscript-dən istifadə edir"
|
||||
submit = "Çevir"
|
||||
tip = "Hazırda eyni anda bir neçə girişi dəstəkləmir"
|
||||
outputFormat = "Çıxış formatı"
|
||||
pdfWithDigitalSignature = "PDF rəqəmsal imza ehtiva edir. Bu imza növbəti addımda silinəcək."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc,docx,odt,word,dəyişmə,format,çevirmə,ofis,microsoft,docfile"
|
||||
title = "PDF-i Word-ə"
|
||||
@@ -3200,18 +3153,6 @@ other = "Digər"
|
||||
perPageInfo = "Hər səhifə üzrə məlumat"
|
||||
tableOfContents = "Mündəricat"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "Aşkarlanmadı"
|
||||
passed = "Keçdi"
|
||||
failed = "Keçmədi"
|
||||
compliant = "Uyğun"
|
||||
nonCompliant = "Uyğun deyil"
|
||||
none = "Heç bir standart aşkarlanmadı"
|
||||
passedCount = "keçdi"
|
||||
failedCount = "keçmədi"
|
||||
noVerification = "Yoxlama aparılmadı"
|
||||
noVerificationDesc = "Bu sənəd üçün PDF standartlarına uyğunluq yoxlanılmadı."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "Əlavələr"
|
||||
embeddedFiles = "Gömülü fayllar"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} məhdudiyyət"
|
||||
permsMixed = "Bəzi icazələr məhdudlaşdırılıb"
|
||||
hasCompliance = "Uyğunluq standartları mövcuddur"
|
||||
noCompliance = "Uyğunluq standartları yoxdur"
|
||||
compliancePassed = "{{standards}} uyğundur"
|
||||
complianceChecked = "Standartlar yoxlanıldı ({{failed}} keçmədi)"
|
||||
basic = "Əsas Məlumat"
|
||||
documentInfo = "Sənəd Məlumatı"
|
||||
securityTitle = "Təhlükəsizlik Vəziyyəti"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "Şəkil ölçüsü"
|
||||
margin = "Kənar boşluğu"
|
||||
positionAndFormatting = "Mövqe və formatlama"
|
||||
quickPosition = "Möhürü yerləşdirmək üçün səhifədə bir mövqe seçin."
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "PDF-ə möhür əlavə edilərkən xəta baş verdi."
|
||||
@@ -3594,14 +3508,6 @@ failed = "PDF-ə möhür əlavə edilərkən xəta baş verdi."
|
||||
[AddStampRequest.results]
|
||||
title = "Möhür nəticələri"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "Şəkil Sil,Səhifə Əməliyyatları,Back end,server-tərəf"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "Parol uğurla dəyişdirildi! Zəhmət olmasa yeni par
|
||||
credentialsUpdated = "Giriş məlumatlarınız yeniləndi. Zəhmət olmasa yenidən daxil olun."
|
||||
defaultCredentials = "Standart giriş məlumatları"
|
||||
changePasswordWarning = "İlk dəfə daxil olduqdan sonra şifrənizi dəyişin"
|
||||
mfaRequired = "İki faktorlu doğrulama kodu tələb olunur"
|
||||
mfaCode = "Doğrulama kodu"
|
||||
enterMfaCode = "6 rəqəmli kodu daxil edin"
|
||||
mfaPromptTitle = "İki faktorlu doğrulama"
|
||||
mfaPromptBody = "Davam etmək üçün doğrulama tətbiqinizdən doğrulama kodunu daxil edin."
|
||||
verifyingMfa = "Yoxlanılır..."
|
||||
verifyMfa = "Kodu doğrula"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "Stirling PDF icmalı"
|
||||
@@ -4425,10 +4324,6 @@ title = "Giriş rejimi tələb olunur"
|
||||
message = "Admin ayarlarını dəyişmək üçün giriş rejimi aktiv olmalıdır. Zəhmət olmasa mühitinizdə SECURITY_ENABLELOGIN=true və ya settings.yml faylında security.enableLogin: true olaraq təyin edin, sonra serveri yenidən başladın."
|
||||
readOnly = "Aşağıdakı ayarlar yalnız nümunə dəyərlər üçün göstərilir. Giriş rejimini aktiv edin ki, real konfiqurasiyaya baxa və redaktə edə biləsiniz."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "Enterprise lisenziyası tələb olunur"
|
||||
message = "{{featureName}} funksiyasına daxil olmaq üçün Enterprise lisenziyası tələb olunur. Siz istinad üçün demo məlumatlarına baxırsınız."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "Yenidən başlatmaq tələb olunur"
|
||||
message = "Ayarlar uğurla yadda saxlanıldı. Dəyişikliklərin qüvvəyə minməsi üçün serverin yenidən başlaması tələb olunur."
|
||||
@@ -4568,13 +4463,6 @@ description = "Hesabın bloklanmasından əvvəl maksimal uğursuz giriş cəhdl
|
||||
label = "Girişi sıfırlama vaxtı (dəqiqə)"
|
||||
description = "Uğursuz giriş cəhdlərinin sıfırlanmasına qədər olan vaxt"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF mühafizəsini söndür"
|
||||
description = "Cross-Site Request Forgery mühafizəsini söndür (tövsiyə olunmur)"
|
||||
@@ -4883,48 +4771,6 @@ description = "İstifadəçilər üçün ətraflı xəta mesajlarını göstəri
|
||||
title = "Məlumat Bazası"
|
||||
description = "Müəssisə yerləşdirmələri üçün fərdi məlumat bazası bağlantı ayarlarını konfiqurasiya edin."
|
||||
configuration = "Məlumat Bazası Konfiqurasiyası"
|
||||
backupTitle = "Ehtiyat nüsxələr və Bərpa"
|
||||
backupDescription = "H2 ehtiyat nüsxələrini birbaşa admin konsolundan idarə edin."
|
||||
loadError = "Məlumat bazası ehtiyat nüsxələrini yükləmək alınmadı"
|
||||
backupCreated = "Ehtiyat nüsxə uğurla yaradıldı"
|
||||
backupFailed = "Ehtiyat nüsxə yaratmaq alınmadı"
|
||||
deleteTitle = "Ehtiyat nüsxəni sil"
|
||||
deleteConfirm = "Bu ehtiyat nüsxəni silmək istəyirsiniz? Bu əməliyyat geri alına bilməz."
|
||||
deleteSuccess = "Ehtiyat nüsxə silindi"
|
||||
deleteFailed = "Ehtiyat nüsxəni silmək alınmadı"
|
||||
deleteConfirmAction = "Ehtiyat nüsxəni sil"
|
||||
downloadFailed = "Ehtiyat nüsxəni endirmək alınmadı"
|
||||
version = "H2 Versiyası"
|
||||
embedded = "Gömülü H2"
|
||||
external = "Xarici DB"
|
||||
h2Only = "Ehtiyat nüsxələr yalnız gömülü H2 məlumat bazası üçün mövcuddur."
|
||||
h2Hint = "Ehtiyat nüsxə və bərpanı aktivləşdirmək üçün məlumat bazası növünü H2 olaraq təyin edin və xüsusi məlumat bazasını söndürün."
|
||||
manageBackups = "Ehtiyat nüsxələri idarə et"
|
||||
refresh = "Yenilə"
|
||||
createBackup = "Ehtiyat nüsxə yarat"
|
||||
uploadTitle = "Yüklə və idxal et"
|
||||
chooseFile = ".sql ehtiyat nüsxə faylı seçin"
|
||||
importFromUpload = "Yüklənəni idxal et"
|
||||
confirmImportTitle = "Məlumat bazasının idxalını təsdiqlə"
|
||||
overwriteWarning = "Xəbərdarlıq: Bu, cari məlumat bazasının üzərinə yazacaq."
|
||||
overwriteWarningBody = "Mövcud bütün məlumatlar yüklənən ehtiyat nüsxə ilə əvəz olunacaq. Bu əməl geri alına bilməz."
|
||||
confirmCodeLabel = "Davam etmək üçün təsdiq kodunu daxil edin"
|
||||
enterCode = "Yuxarıda göstərilən kodu daxil edin"
|
||||
confirmImport = "İdxalı təsdiqlə"
|
||||
codeMismatch = "Təsdiq kodu uyğun gəlmir"
|
||||
codeMismatchBody = "Davam etmək üçün kodu göstərildiyi kimi daxil edin."
|
||||
selectFile = "İdxal üçün .sql faylı seçin"
|
||||
importSuccess = "Ehtiyat nüsxə uğurla idxal edildi"
|
||||
importFailed = "Ehtiyat nüsxənin idxalı alınmadı"
|
||||
noBackups = "Hələ ehtiyat nüsxə tapılmadı."
|
||||
unavailable = "Cari məlumat bazası konfiqurasiyası üçün ehtiyat nüsxə siyahısı əlçatan deyil."
|
||||
fileName = "Fayl"
|
||||
created = "Yaradılma"
|
||||
size = "Ölçü"
|
||||
actions = "Əməliyyatlar"
|
||||
download = "Endir"
|
||||
import = "İdxal et"
|
||||
delete = "Sil"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "Fərdi Məlumat Bazasını aktiv et"
|
||||
@@ -5002,26 +4848,6 @@ description = "Şəkil emalı üçün maksimal DPI (0 = limitsiz)"
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "Tessdata Qovluğu"
|
||||
description = "OCR dil faylları üçün tessdata qovluğuna yol"
|
||||
downloadMissingTitle = "Heç bir dil seçilməyib"
|
||||
downloadMissingBody = "Endirmək üçün ən azı bir dil seçin, zəhmət olmasa."
|
||||
downloadSuccessTitle = "Dillər endirildi"
|
||||
downloadSuccessBody = "Seçilmiş tessdata dilləri yadda saxlanıldı."
|
||||
downloadErrorTitle = "Endirmə alınmadı"
|
||||
loadingLanguages = "Quraşdırılmış tessdata dilləri yüklənir..."
|
||||
installedLanguages = "Quraşdırılmış tessdata dilləri"
|
||||
noLanguages = "Konfiqurasiya edilmiş qovluqda tessdata dilləri tapılmadı."
|
||||
downloadLabel = "Əlavə tessdata dillərini endir"
|
||||
downloadPlaceholder = "Dilləri seçin"
|
||||
downloadNothingFound = "Əlavə dillər tapılmadı"
|
||||
permissionNotice = "tessdata yolu yazıla bilən deyil. Endirmələr brauzerdə açılacaq; .traineddata fayllarını əl ilə tessdata qovluğuna yadda saxlayın."
|
||||
manualLinks = "Əl ilə endirmələr: keçidlərə klikləyin və faylları tessdata qovluğuna yerləşdirin."
|
||||
downloadButton = "Seçilmiş dilləri endir"
|
||||
downloadInvalidTitle = "Etibarsız seçim"
|
||||
downloadInvalidBody = "Seçilən dillərin bəziləri endirmə üçün əlçatan deyil. Zəhmət olmasa yeniləyin və siyahıdan seçin."
|
||||
downloadErrorNetwork = "Şəbəkə xətasına görə endirmə alınmadı. Bağlantınızı yoxlayın və yenidən cəhd edin."
|
||||
downloadErrorServer = "tessdata dillərini endirərkən server xətası baş verdi. Zəhmət olmasa bir az sonra yenidən cəhd edin."
|
||||
downloadErrorPermission = "tessdata qovluğu yazıla bilən deyil: {{message}}. Yazıla bilən qovluq seçin (məs., tətbiqin məlumat qovluğu altında) və ya icazələri tənzimləyin."
|
||||
downloadErrorGeneric = "Endirmə alınmadı: {{message}}. Zəhmət olmasa bir az sonra yenidən cəhd edin."
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "HTML Təmizlənməsini söndür"
|
||||
@@ -5525,7 +5351,6 @@ desc = "PDF əməliyyatlarını birləşdirərək çoxaddımlı iş axınları y
|
||||
invalidStep = "Yanlış addım"
|
||||
reviewTitle = "Avtomatlaşdırma Nəticələri"
|
||||
copyToSaved = "Saxlananlara kopyala"
|
||||
exportForFolderScanning = "Qovluq skanı üçün ixrac et"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "Bu avtomatlaşdırma ilə emal etmək üçün faylları seçin"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "Avtomatlaşdırma Yaradın"
|
||||
editTitle = "Avtomatlaşdırmanı Redaktə edin"
|
||||
intro = "Avtomatlaşdırmalar alətləri ardıcıl işlədir. Başlamaq üçün, işləməsini istədiyiniz ardıcıllıqla alətlər əlavə edin."
|
||||
save = "Avtomatlaşdırmanı Yadda saxla"
|
||||
exportForFolderScanning = "Qovluq skanı üçün ixrac et"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "Avtomatlaşdırmanın Adı"
|
||||
@@ -5922,11 +5746,9 @@ username = "İstifadəçi adı (E-poçt)"
|
||||
usernamePlaceholder = "user@example.com"
|
||||
password = "Parol"
|
||||
passwordPlaceholder = "Parolu daxil edin"
|
||||
passwordRequired = "Şifrə tələb olunur"
|
||||
role = "Rol"
|
||||
team = "Komanda (İstəyə bağlı)"
|
||||
teamPlaceholder = "Komanda seçin"
|
||||
authType = "Doğrulama növü"
|
||||
forcePasswordChange = "İlk girişdə parol dəyişikliyi tələb olunsun"
|
||||
cancel = "Ləğv et"
|
||||
submit = "Üzv əlavə et"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "İstifadəçi adı və parol tələb olunur"
|
||||
passwordTooShort = "Parol ən azı 6 simvol olmalıdır"
|
||||
success = "İstifadəçi uğurla yaradıldı"
|
||||
error = "İstifadəçi yaratmaq alınmadı"
|
||||
forceMFA = "Növbəti daxilolmada MFA quraşdırılmasını məcbur et"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "Şifrə"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "İstifadəçi SSO provayderi vasitəsilə doğrulanacaq"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "Üzvü redaktə et"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "{{count}} istifadəçi yeri mövcuddur"
|
||||
noSlotsAvailable = "Mövcud yer yoxdur"
|
||||
currentUsage = "Hazırda {{max}} istifadəçi lisenziyasından {{current}} istifadə olunur"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "İstifadəçi üçün MFA uğurla deaktiv edildi"
|
||||
adminDisableError = "İstifadəçi üçün MFA-nı deaktiv etmək mümkün olmadı"
|
||||
disableByAdmin = "MFA-nı deaktiv et"
|
||||
|
||||
[workspace.teams]
|
||||
title = "Komandalar"
|
||||
description = "Komandaları idarə edin və iş sahəsi üzvlərini təşkil edin"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "Endpoint"
|
||||
visits = "Ziyarətlər"
|
||||
percentage = "Faiz"
|
||||
noData = "Məlumat yoxdur"
|
||||
unknownEndpoint = "Naməlum endpoint"
|
||||
|
||||
[backendHealth]
|
||||
checking = "Backend vəziyyəti yoxlanılır..."
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "Избор на операция"
|
||||
addOperationButton = "Добавяне на операция"
|
||||
pipelineHeader = "Пайплайн:"
|
||||
saveButton = "Изтегли"
|
||||
saveForFolderScanning = "Запазване за сканиране на папки"
|
||||
validateButton = "Валидирай"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "Цял екран"
|
||||
[settings.general.updates]
|
||||
title = "Актуализации на софтуера"
|
||||
description = "Проверете за актуализации и вижте информация за версията"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "Текуща версия"
|
||||
latestVersion = "Последна версия"
|
||||
checkForUpdates = "Провери за актуализации"
|
||||
viewDetails = "Виж подробности"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "Уеб-браузър настройки"
|
||||
syncToBrowser = "Синхронизиране на акаунт -> Браузър"
|
||||
syncToAccount = "Синхронизиране на акаунт <- Браузър"
|
||||
|
||||
[account.mfa]
|
||||
title = "Двуфакторно удостоверяване"
|
||||
setupFailed = "Не може да се стартира настройването на двуфакторното удостоверяване. Моля, опитайте отново."
|
||||
codeRequired = "Въведете кода за удостоверяване, за да продължите."
|
||||
enabled = "Двуфакторното удостоверяване е активирано."
|
||||
enableFailed = "Не може да се активира двуфакторното удостоверяване. Проверете кода и опитайте отново."
|
||||
disabled = "Двуфакторното удостоверяване е деактивирано."
|
||||
disableFailed = "Не може да се деактивира двуфакторното удостоверяване. Проверете кода и опитайте отново."
|
||||
description = "Добавете допълнителен слой защита към акаунта си."
|
||||
enableButton = "Активиране на двуфакторното удостоверяване"
|
||||
disableButton = "Деактивиране на двуфакторното удостоверяване"
|
||||
setupTitle = "Настройване на двуфакторното удостоверяване"
|
||||
setupDescription = "Сканирайте QR кода с вашето приложение за удостоверяване, след това въведете 6-цифрения код, за да потвърдите."
|
||||
manualKey = "Ключ за ръчна настройка"
|
||||
secretWarning = "Пазете този ключ в тайна. Всеки с достъп може да генерира валидни кодове за удостоверяване."
|
||||
codePlaceholder = "Въведете 6-цифрен код"
|
||||
confirmEnable = "Активирай"
|
||||
disableTitle = "Деактивиране на двуфакторното удостоверяване"
|
||||
disableDescription = "Въведете валиден код за удостоверяване, за да деактивирате двуфакторното удостоверяване."
|
||||
codeLabel = "Код за удостоверяване"
|
||||
confirmDisable = "Деактивирай"
|
||||
ssoDescription = "Двуфакторното удостоверяване се управлява от вашия доставчик на идентичност за акаунти със Single Sign-On."
|
||||
ssoManaged = "Конфигурирайте MFA чрез вашия доставчик на идентичност."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "Настройки за потребителски контрол"
|
||||
header = "Настройки за администраторски потребителски контрол"
|
||||
@@ -952,7 +925,6 @@ title = "Авто делене по размер/брой"
|
||||
desc = "Разделете един PDF на множество документи въз основа на размер, брой страници или брой документи"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Смени/обърни цветове"
|
||||
desc = "Заместване или инвертиране на цветове в PDF документи"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "Авто сканиране на папки"
|
||||
desc = "Връзка към ръководство за автоматизирано сканиране на папки"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Ръководство за SSO"
|
||||
desc = "Връзка към SSO ръководство"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Настройка за air‑gapped среда"
|
||||
desc = "Връзка към ръководство за air‑gapped настройка"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Добавете парола"
|
||||
desc = "Шифровайте вашия PDF документ с парола."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Промяна на правата"
|
||||
desc = "Промяна на ограниченията и разрешенията на документа"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "Автоматизация"
|
||||
desc = "Създавайте многостъпкови работни процеси чрез свързване на PDF действия. Идеално за повтарящи се задачи."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Наслагва PDF файлове върху друг PDF"
|
||||
title = "Наслагване PDF-и"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF текстов редактор"
|
||||
desc = "Преглеждайте и редактирайте JSON експорти на Stirling PDF с групово редактиране на текст и повторно генериране на PDF"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "Изтегляне на междинния HTML файл вме
|
||||
pdfaOptions = "Опции за PDF/A"
|
||||
outputFormat = "Изходен формат"
|
||||
pdfaNote = "PDF/A-1b е по-съвместим, PDF/A-2b поддържа повече функции."
|
||||
pdfaFormat = "Формат PDF/A"
|
||||
pdfaDigitalSignatureWarning = "PDF файлът съдържа цифров подпис. Това ще бъде премахнато в следващата стъпка."
|
||||
pdfxDigitalSignatureWarning = "PDF файлът съдържа цифров подпис. Той ще бъде премахнат на следващата стъпка."
|
||||
pdfxDescription = "PDF/X е подмножество на PDF по стандарта ISO за надежден печат и обмен на графики."
|
||||
fileFormat = "Файлов формат"
|
||||
wordDoc = "Word документ"
|
||||
wordDocExt = "Word документ (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "В момента не работи за няколко входа нав
|
||||
outputFormat = "Изходен формат"
|
||||
pdfWithDigitalSignature = "PDF файлът съдържа цифров подпис. Това ще бъде премахнато в следващата стъпка."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "печат,стандарт,конвертиране,производство,предпечат,архив"
|
||||
title = "PDF към PDF/X"
|
||||
header = "PDF към PDF/X"
|
||||
credit = "Тази услуга използва Ghostscript за конвертиране към PDF/X"
|
||||
submit = "Конвертирай"
|
||||
tip = "В момента не работи за множество входове едновременно"
|
||||
outputFormat = "Изходен формат"
|
||||
pdfWithDigitalSignature = "PDF файлът съдържа цифров подпис. Той ще бъде премахнат на следващата стъпка."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc,docx,odt,word,трансформация,формат,преобразуване,офис,microsoft,docfile"
|
||||
title = "PDF към Word"
|
||||
@@ -3200,18 +3153,6 @@ other = "Друго"
|
||||
perPageInfo = "Информация по страници"
|
||||
tableOfContents = "Съдържание"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "Не е открито"
|
||||
passed = "Преминато"
|
||||
failed = "Неуспешно"
|
||||
compliant = "Съвместим"
|
||||
nonCompliant = "Несъвместим"
|
||||
none = "Не са открити стандарти"
|
||||
passedCount = "преминали"
|
||||
failedCount = "неуспешни"
|
||||
noVerification = "Не е извършена проверка"
|
||||
noVerificationDesc = "Съответствието със стандартите на PDF не беше проверено за този документ."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "Прикачени файлове"
|
||||
embeddedFiles = "Вградени файлове"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} ограничения"
|
||||
permsMixed = "Някои разрешения са ограничени"
|
||||
hasCompliance = "Има стандарти за съответствие"
|
||||
noCompliance = "Няма стандарти за съответствие"
|
||||
compliancePassed = "Съвместим с {{standards}}"
|
||||
complianceChecked = "Проверени стандарти ({{failed}} неуспешни)"
|
||||
basic = "Основна информация"
|
||||
documentInfo = "Информация за документа"
|
||||
securityTitle = "Състояние на сигурността"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "Размер на изображението"
|
||||
margin = "Отстъп"
|
||||
positionAndFormatting = "Позиция и форматиране"
|
||||
quickPosition = "Изберете позиция на страницата, където да поставите печата."
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "Възникна грешка при добавяне на печат към PDF."
|
||||
@@ -3594,14 +3508,6 @@ failed = "Възникна грешка при добавяне на печат
|
||||
[AddStampRequest.results]
|
||||
title = "Резултати от печата"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "Премахване на изображение, операции на страници, админ страна, страна на сървъра"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "Паролата е променена успешно!
|
||||
credentialsUpdated = "Данните ви за вход бяха обновени. Моля, влезте отново."
|
||||
defaultCredentials = "Данни за вход по подразбиране"
|
||||
changePasswordWarning = "Моля, сменете паролата си след първото влизане"
|
||||
mfaRequired = "Изисква се двуфакторен код"
|
||||
mfaCode = "Код за удостоверяване"
|
||||
enterMfaCode = "Въведете 6-цифрен код"
|
||||
mfaPromptTitle = "Двуфакторно удостоверяване"
|
||||
mfaPromptBody = "Въведете кода за удостоверяване от вашето приложение за удостоверяване, за да продължите."
|
||||
verifyingMfa = "Проверка..."
|
||||
verifyMfa = "Провери кода"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "Преглед на Stirling PDF"
|
||||
@@ -4425,10 +4324,6 @@ title = "Необходим е режим за вход"
|
||||
message = "Режимът за вход трябва да е активиран, за да променяте административните настройки. Моля, задайте SECURITY_ENABLELOGIN=true във вашата среда или security.enableLogin: true в settings.yml, след което рестартирайте сървъра."
|
||||
readOnly = "Настройките по-долу показват примерни стойности за ориентиране. Активирайте режим за вход, за да видите и редактирате реалната конфигурация."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "Изисква се Enterprise лиценз"
|
||||
message = "Необходим е Enterprise лиценз за достъп до {{featureName}}. Преглеждате демонстрационни данни за справка."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "Изисква се рестарт"
|
||||
message = "Настройките са запазени успешно. Необходим е рестарт на сървъра, за да влязат промените в сила."
|
||||
@@ -4568,13 +4463,6 @@ description = "Максимален брой неуспешни опити за
|
||||
label = "Време за нулиране на входа (минути)"
|
||||
description = "Време, преди неуспешните опити за вход да се нулират"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Деактивирай CSRF защита"
|
||||
description = "Деактивира защита срещу Cross-Site Request Forgery (не се препоръчва)"
|
||||
@@ -4883,48 +4771,6 @@ description = "Показване на подробни съобщения за
|
||||
title = "База данни"
|
||||
description = "Конфигурирайте персонализирани настройки за връзка към база данни за корпоративни внедрявания."
|
||||
configuration = "Конфигурация на база данни"
|
||||
backupTitle = "Резервни копия и възстановяване"
|
||||
backupDescription = "Управлявайте резервни копия на H2 директно от административната конзола."
|
||||
loadError = "Неуспешно зареждане на резервни копия на базата данни"
|
||||
backupCreated = "Резервното копие е създадено успешно"
|
||||
backupFailed = "Неуспешно създаване на резервно копие"
|
||||
deleteTitle = "Изтриване на резервно копие"
|
||||
deleteConfirm = "Да се изтрие ли това резервно копие? Действието е необратимо."
|
||||
deleteSuccess = "Резервното копие е изтрито"
|
||||
deleteFailed = "Неуспешно изтриване на резервно копие"
|
||||
deleteConfirmAction = "Изтриване на резервно копие"
|
||||
downloadFailed = "Неуспешно изтегляне на резервно копие"
|
||||
version = "Версия на H2"
|
||||
embedded = "Вградено H2"
|
||||
external = "Външна база данни"
|
||||
h2Only = "Резервните копия са налични само за вградената база данни H2."
|
||||
h2Hint = "Задайте типа база данни на H2 и деактивирайте персонализираната база данни, за да активирате архивиране и възстановяване."
|
||||
manageBackups = "Управление на резервни копия"
|
||||
refresh = "Опресняване"
|
||||
createBackup = "Създаване на резервно копие"
|
||||
uploadTitle = "Качване и импортиране"
|
||||
chooseFile = "Изберете .sql файл с резервно копие"
|
||||
importFromUpload = "Импортиране на каченото"
|
||||
confirmImportTitle = "Потвърждаване на импортирането на базата данни"
|
||||
overwriteWarning = "Внимание: Това ще презапише текущата база данни."
|
||||
overwriteWarningBody = "Всички съществуващи данни ще бъдат заменени от каченото резервно копие. Това действие е необратимо."
|
||||
confirmCodeLabel = "Въведете кода за потвърждение, за да продължите"
|
||||
enterCode = "Въведете кода, показан по-горе"
|
||||
confirmImport = "Потвърждаване на импортирането"
|
||||
codeMismatch = "Кодът за потвърждение не съвпада"
|
||||
codeMismatchBody = "Моля, въведете кода точно както е показан, за да продължите."
|
||||
selectFile = "Моля, изберете .sql файл за импортиране"
|
||||
importSuccess = "Резервното копие е импортирано успешно"
|
||||
importFailed = "Неуспешно импортиране на резервно копие"
|
||||
noBackups = "Все още няма намерени резервни копия."
|
||||
unavailable = "Списъкът с резервни копия не е наличен за текущата конфигурация на базата данни."
|
||||
fileName = "Файл"
|
||||
created = "Създадено"
|
||||
size = "Размер"
|
||||
actions = "Действия"
|
||||
download = "Изтегляне"
|
||||
import = "Импортиране"
|
||||
delete = "Изтриване"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "Активирай персонализирана база данни"
|
||||
@@ -5002,26 +4848,6 @@ description = "Максимален DPI за обработка на изобр
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "Директория Tessdata"
|
||||
description = "Път до директорията tessdata за OCR езикови файлове"
|
||||
downloadMissingTitle = "Няма избран език"
|
||||
downloadMissingBody = "Моля, изберете поне един език за изтегляне."
|
||||
downloadSuccessTitle = "Езиците са изтеглени"
|
||||
downloadSuccessBody = "Избраните tessdata езици са запазени."
|
||||
downloadErrorTitle = "Неуспешно изтегляне"
|
||||
loadingLanguages = "Зареждане на инсталираните tessdata езици..."
|
||||
installedLanguages = "Инсталирани tessdata езици"
|
||||
noLanguages = "Няма намерени tessdata езици в конфигурираната директория."
|
||||
downloadLabel = "Изтегляне на допълнителни tessdata езици"
|
||||
downloadPlaceholder = "Изберете езици"
|
||||
downloadNothingFound = "Не са намерени допълнителни езици"
|
||||
permissionNotice = "Пътят към tessdata няма права за запис. Изтеглянията ще се отварят в браузъра; моля, запишете файловете .traineddata ръчно в папката tessdata."
|
||||
manualLinks = "Ръчни изтегляния: щракнете върху връзките и поставете файловете в папката tessdata."
|
||||
downloadButton = "Изтегляне на избраните езици"
|
||||
downloadInvalidTitle = "Невалиден избор"
|
||||
downloadInvalidBody = "Някои от избраните езици не са налични за изтегляне. Моля, опреснете и изберете от списъка."
|
||||
downloadErrorNetwork = "Изтеглянето се провали поради мрежова грешка. Моля, проверете връзката си и опитайте отново."
|
||||
downloadErrorServer = "Сървърът срещна грешка при изтегляне на tessdata езици. Моля, опитайте по-късно."
|
||||
downloadErrorPermission = "Директорията tessdata няма права за запис: {{message}}. Моля, изберете директория с права за запис (напр. под папката с данни на приложението) или коригирайте правата."
|
||||
downloadErrorGeneric = "Неуспешно изтегляне: {{message}}. Моля, опитайте по-късно."
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "Деактивирай HTML санитизация"
|
||||
@@ -5525,7 +5351,6 @@ desc = "Създавайте многостъпкови работни пото
|
||||
invalidStep = "Невалидна стъпка"
|
||||
reviewTitle = "Резултати от автоматизация"
|
||||
copyToSaved = "Копиране в „Запазени“"
|
||||
exportForFolderScanning = "Експортиране за сканиране на папки"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "Изберете файлове за обработка с тази автоматизация"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "Създаване на автоматизация"
|
||||
editTitle = "Редактиране на автоматизация"
|
||||
intro = "Автоматизациите изпълняват инструментите последователно. За да започнете, добавете инструментите в реда, в който желаете да се изпълняват."
|
||||
save = "Запазване на автоматизацията"
|
||||
exportForFolderScanning = "Експортиране за сканиране на папки"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "Име на автоматизацията"
|
||||
@@ -5922,11 +5746,9 @@ username = "Потребителско име (имейл)"
|
||||
usernamePlaceholder = "user@example.com"
|
||||
password = "Парола"
|
||||
passwordPlaceholder = "Въведете парола"
|
||||
passwordRequired = "Изисква се парола"
|
||||
role = "Роля"
|
||||
team = "Екип (незадължително)"
|
||||
teamPlaceholder = "Изберете екип"
|
||||
authType = "Тип удостоверяване"
|
||||
forcePasswordChange = "Изисквай смяна на паролата при първи вход"
|
||||
cancel = "Отказ"
|
||||
submit = "Добавяне на член"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "Потребителско име и парола са за
|
||||
passwordTooShort = "Паролата трябва да е поне 6 знака"
|
||||
success = "Потребителят е създаден успешно"
|
||||
error = "Неуспешно създаване на потребител"
|
||||
forceMFA = "Задължително настройване на MFA при следващо влизане"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "Парола"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "Потребителят ще се удостоверява чрез SSO доставчик"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "Редактиране на член"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "Налични са {{count}} потребителски сл
|
||||
noSlotsAvailable = "Няма налични слотове"
|
||||
currentUsage = "В момента се използват {{current}} от {{max}} потребителски лицензи"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "MFA е деактивирана успешно за потребителя"
|
||||
adminDisableError = "Неуспешно деактивиране на MFA за потребителя"
|
||||
disableByAdmin = "Деактивиране на MFA"
|
||||
|
||||
[workspace.teams]
|
||||
title = "Екипи"
|
||||
description = "Управлявайте екипи и организирайте членовете на работното пространство"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "Крайна точка"
|
||||
visits = "Посещения"
|
||||
percentage = "Процент"
|
||||
noData = "Няма налични данни"
|
||||
unknownEndpoint = "Неизвестна крайна точка"
|
||||
|
||||
[backendHealth]
|
||||
checking = "Проверка на състоянието на бекенда..."
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "བཀོལ་སྤྱོད་འདེམས་པ།"
|
||||
addOperationButton = "བཀོལ་སྤྱོད།"
|
||||
pipelineHeader = "མདོང་ལམ།"
|
||||
saveButton = "ཕབ་ལེན"
|
||||
saveForFolderScanning = "ཡིག་སྣོད་ཞིབ་བཤེར་ཆེད་དུ་སྲུང"
|
||||
validateButton = "བདེན་དཔང་།"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "བརྙན་ཤེལ་ཆ་ཚང་།"
|
||||
[settings.general.updates]
|
||||
title = "མཉེན་ཆས་གསར་བརྗེ།"
|
||||
description = "གསར་བརྗེའི་གནས་ཚུལ་ལ་ཞིབ་བཤེར་དང་ཐོན་རིམ་གྱི་ཆ་འཕྲིན་ལ་ལྟོས།"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "ད་ལྟའི་ཐོན་རིམ།"
|
||||
latestVersion = "ཐོན་རིམ་གསར་ཤོས།"
|
||||
checkForUpdates = "གསར་བརྗེ་ལ་ཞིབ་བཤེར།"
|
||||
viewDetails = "ཞིབ་ཕྲའི་གནས་ཚུལ་ལ་ལྟོས།"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "དྲ་ངོས་དྲ་ངོས་སྒྲིག
|
||||
syncToBrowser = "མཉམ་འབྲེལ། -> དྲ་འབྱེད།"
|
||||
syncToAccount = "མཉམ་འབྲེལ། <- དྲ་འབྱེད།"
|
||||
|
||||
[account.mfa]
|
||||
title = "ཆ་རྐྱེན་གཉིས་ཀྱི་ངོས་འཛིན"
|
||||
setupFailed = "ཆ་རྐྱེན་གཉིས་སྒྲིག་འགོད་འགོ་འཛུགས་མི་ཐུབ། བསྐྱར་དུ་ཚོད་ལྟ་བྱེད།"
|
||||
codeRequired = "མུ་མཐུད་དུ་འགྲོ་བའི་ཆེད་ངོས་འཛིན་ཨང་རྟགས་བཙུགས།"
|
||||
enabled = "ཆ་རྐྱེན་གཉིས་ངོས་འཛིན་སྤྱོད་སྒོ་ཕྱེས།"
|
||||
enableFailed = "ཆ་རྐྱེན་གཉིས་ངོས་འཛིན་སྤྱོད་སྒོ་ཕྱེ་མི་ཐུབ། ཨང་རྟགས་ཞིབ་བཤེར་བྱས་ནས་བསྐྱར་དུ་ཚོད་ལྟ་བྱེད།"
|
||||
disabled = "ཆ་རྐྱེན་གཉིས་ངོས་འཛིན་སྤྱོད་སྒོ་བརྒྱབས།"
|
||||
disableFailed = "ཆ་རྐྱེན་གཉིས་ངོས་འཛིན་སྤྱོད་སྒོ་བརྒྱབ་མི་ཐུབ། ཨང་རྟགས་ཞིབ་བཤེར་བྱས་ནས་བསྐྱར་དུ་ཚོད་ལྟ་བྱེད།"
|
||||
description = "ཁྱེད་ཀྱི་རྩིས་ཐོའི་བདེ་འཇགས་རིམ་པ་ཞིག་ཁ་སྣོན་བྱས།"
|
||||
enableButton = "ཆ་རྐྱེན་གཉིས་ངོས་འཛིན་སྤྱོད་སྒོ་ཕྱེ"
|
||||
disableButton = "ཆ་རྐྱེན་གཉིས་ངོས་འཛིན་སྤྱོད་སྒོ་བརྒྱབ"
|
||||
setupTitle = "ཆ་རྐྱེན་གཉིས་ངོས་འཛིན་སྒྲིག་འགོད"
|
||||
setupDescription = "ངོས་འཛིན་app བེད་སྤྱད་ QR ཨང་རྟགས་ཞིབ་བཤེར་བྱས་ནས 6 ཨང་གྲངས་ཀྱི་ཨང་རྟགས་བཙུགས་ནས་ངེས་འཛིན་བྱོས།"
|
||||
manualKey = "ལག་སྟར་སྒྲིག་འགོད་ལྡེ་མིག"
|
||||
secretWarning = "ལྡེ་མིག་འདི་གསང་བཞག་། དབང་ཆ་ཡོད་པ་གང་རུང་གིས་ནུས་ལྡན་ངོས་འཛིན་ཨང་རྟགས་བཟོ་ཐུབ།"
|
||||
codePlaceholder = "6 ཨང་གྲངས་ཀྱི་ཨང་རྟགས་བཙུགས"
|
||||
confirmEnable = "སྤྱོད་སྒོ་ཕྱེ"
|
||||
disableTitle = "ཆ་རྐྱེན་གཉིས་ངོས་འཛིན་སྤྱོད་སྒོ་བརྒྱབ"
|
||||
disableDescription = "ཆ་རྐྱེན་གཉིས་ངོས་འཛིན་སྤྱོད་སྒོ་བརྒྱབ་བར་ནུས་ལྡན་ངོས་འཛིན་ཨང་རྟགས་བཙུགས།"
|
||||
codeLabel = "ངོས་འཛིན་ཨང་རྟགས"
|
||||
confirmDisable = "སྤྱོད་སྒོ་བརྒྱབ"
|
||||
ssoDescription = "Single Sign-On རྩིས་ཐོ་ལ་དོན་གྱི་ ཆ་རྐྱེན་གཉིས་ངོས་འཛིན་དེ་ཁྱེད་ཀྱི་ངོས་འཛིན་སྤྲོད་མཁན་གིས་དོ་དམ་བྱེད།"
|
||||
ssoManaged = "ཁྱེད་ཀྱི་ངོས་འཛིན་སྤྲོད་མཁན་བརྒྱུད་ནས MFA སྒྲིག་འགོད།"
|
||||
|
||||
[adminUserSettings]
|
||||
title = "སྤྱོད་མཁན་ཚོད་འཛིན་སྒྲིག་སྟངས།"
|
||||
header = "འཛིན་སྐྱོང་སྤྱོད་མཁན་ཚོད་འཛིན་སྒྲིག་སྟངས།"
|
||||
@@ -952,7 +925,6 @@ title = "ཆེ་ཆུང་/གྲངས་ཚད་ཀྱིས་རང་
|
||||
desc = "རང་འགུལ་གྱིས་ཡིག་ཆའི་ཆེ་ཆུང་དང་ཡང་ན་ཤོག་ངོས་གྲངས་ལ་གཞིགས་ནས་PDFsབགོད་དགོས།"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "ཁ་དོག་བརྗེ་ལེན་དང་བསྒྱུར་བ།"
|
||||
desc = "PDFཡིག་ཆའི་ནང་ཁ་དོག་བརྗེ་ལེན་བྱེད་པའམ་ཡང་ན་བསྒྱུར་བ།"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "རང་འགུལ་ཅན་གྱི་སྣོད་བཅུ
|
||||
desc = "རང་འགུལ་སྣོད་ཀྱི་པར་བཤེར་ལམ་སྟོན་ལ་འབྲེལ་མཐུད་བྱེད།"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO ལམ་སྟོན།"
|
||||
desc = "SSO ལམ་སྟོན་ལ་འབྲེལ་མཐུད་བྱེད།"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "མཁའ་དབྱིངས་ཀྱི་ཆ་སྒྲིག་སྒྲིག་བཀོད།"
|
||||
desc = "མཁའ་རླུང་གི་བར་ཐག་གིས་སྒྲིག་པའི་སྒྲིག་ལམ་ལ་འབྲེལ་མཐུད་བྱེད་པ།"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "གསང་གྲངས་ཁ་སྣོན་བྱེད།"
|
||||
desc = "གསང་གྲངས་སྲུང་སྐྱོབ་དང་ཚད་བཀག་PDFཡིག་ཆ་ལ་ཁ་སྣོན་བྱེད་པ།"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "འཕོ་འགྱུར།"
|
||||
desc = "ཡིག་ཆའི་ཚད་བཀག་དང་ཆོག་མཆན་བསྒྱུར་བ།"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "རང་འགུལ་ཅན།"
|
||||
desc = "གོམ་པ་མང་པོའི་ལས་ཀའི་འགྲོ་ལུགས་དེ་PDFབྱ་སྤྱོད་མཉམ་དུ་སྒྲིག་ནས་བཟོས། ལས་འགན་ཡང་བསྐྱར་འཚོག་པར་འཚམ་པོ་ཡོད།"
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "གཞན་ཞིག་གི་སྟེང་དུ་PDFགཅིག་བཀབ་དགོས།"
|
||||
title = "བཀབ་པའི་PDFs"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDFཡིག་འབྲིའི་རྩོམ་སྒྲིག་པ།"
|
||||
desc = "ད་ཡོད་ཀྱི་ཡིག་ཆ་དང་པར་རིས་PDFནང་དུ་རྩོམ་སྒྲིག་བྱས།"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "PDF ཡི་ཚབ་ཏུ་HTML བར་མའི་ཡི
|
||||
pdfaOptions = "PDF/A གདམ་ག།"
|
||||
outputFormat = "ཐོན་འབྲས་རྣམ་གྲངས།"
|
||||
pdfaNote = "PDF/A-1b མཐུན་སྒྲིལ་ཆེ་བ་དང་། PDF/A-2b ཡིས་ཁྱད་ཆོས་མང་བ་ལ་རྒྱབ་སྐྱོར་བྱེད་ཀྱི་ཡོད། PDF/A-3b ཡིས་བཙུགས་པའི་ཡིག་ཆ་ལ་རྒྱབ་སྐྱོར་བྱེད་ཀྱི་ཡོད།"
|
||||
pdfaFormat = "PDF/A རྣམ་གཞག"
|
||||
pdfaDigitalSignatureWarning = "PDF ནང་ཨང་གྲངས་ཀྱི་མཚན་རྟགས་ཡོད། འདི་གོམ་པ་རྗེས་མར་མེད་པར་བཟོ་རྒྱུ་རེད།"
|
||||
pdfxDigitalSignatureWarning = "དེབ PDF ནང་ གློག་སྒྲིག་མིང་རྟགས་ཡོད། རྗེས་སྟབས་ནས་འདི་བསུབ་བྱེད།"
|
||||
pdfxDescription = "PDF/X ནི ISO ཚད་ལྡན་PDF གྲངས་ཆ་ཞིག་ཡིན་པས་ ཡིད་ཆེན་པོར་པར་འདེབས་དང་ རི་མོ་བརྗེ་སོར་བྱེད་ཐུབ།"
|
||||
fileFormat = "ཡིག་ཆ།"
|
||||
wordDoc = "ཚིག་ཡིག་ཡིག་ཆ།"
|
||||
wordDocExt = "ཚིག་ཡིག་ཡིག་ཆ་ (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "མིག་སྔར་ཐེངས་གཅིག་ལ་ནང་འ
|
||||
outputFormat = "ཐོན་འབྲས་རྣམ་གྲངས།"
|
||||
pdfWithDigitalSignature = "PDF ནང་ཨང་གྲངས་ཀྱི་མཚན་རྟགས་ཡོད། འདི་གོམ་པ་རྗེས་མར་མེད་པར་བཟོ་རྒྱུ་རེད།"
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "པར་འདེབས,ཚད་ལྡན,བརྗེ་སྤོ,བཟོ་ལས,པར་སྔོན་འགྲིལ,མཛོད་སྣོད"
|
||||
title = "PDF ལས་ PDF/X ལ་བརྗེ་བ"
|
||||
header = "PDF ལས་ PDF/X ལ་བརྗེ་བ"
|
||||
credit = "ཞབས་ཞུ་འདིས PDF/X བརྗེ་བའི་ཆེད Ghostscript བེད་སྤྱོད་བྱེད།"
|
||||
submit = "བརྗེ་བ"
|
||||
tip = "ད་ལྟ་ཚུར་ནང་འཇུག་མང་པོ་དང་མཉམ་དུ་ལཱ་མི་བྱེད།"
|
||||
outputFormat = "ཕྱིར་འདོན་རྣམ་གཞག"
|
||||
pdfWithDigitalSignature = "དེབ PDF ནང་ གློག་སྒྲིག་མིང་རྟགས་ཡོད། རྗེས་སྟབས་ནས་འདི་བསུབ་བྱེད།"
|
||||
|
||||
[PDFToWord]
|
||||
tags = "ཌོག་དང་། ཌོ་ཀ་སི། ཚིག། བསྒྱུར་བཅོས། རྣམ་པ་བསྒྱུར་བཅོས། ཡིག་ཚང་། མའི་ཀོ་རོ་སོཕཊ། ཌོག་ཕི་ལ།"
|
||||
title = "PDF ནས་ Word བར།"
|
||||
@@ -3200,18 +3153,6 @@ other = "གཞན་པ"
|
||||
perPageInfo = "ཤོག་ངོས་རེ་རེར།"
|
||||
tableOfContents = "ནང་དོན་གྱི་རེའུ་མིག"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "མ་རྙེད"
|
||||
passed = "ཡོངས་འགྲུབ"
|
||||
failed = "ཕམ་པ"
|
||||
compliant = "ཚད་ལྡན"
|
||||
nonCompliant = "ཚད་མི་ལྡན"
|
||||
none = "ཚད་ལྡན་མ་རྙེད"
|
||||
passedCount = "ཡོངས་འགྲུབ"
|
||||
failedCount = "ཕམ་པ"
|
||||
noVerification = "ཞིབ་བཤེར་མ་བྱས"
|
||||
noVerificationDesc = "ཡིག་ཆ་འདིའི་ PDF ཚད་ལྡན་འཚམ་པ་ཞིབ་བཤེར་མ་བྱས།"
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "འབྲེལ་གཏུག་ཅན།"
|
||||
embeddedFiles = "ནང་འཇུག་བྱས་པའི་ཡིག་ཆ།"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "ཚད་བཀག {{count}}"
|
||||
permsMixed = "ཆོག་མཆན་ཁ་ཤས་ལ་བཀག་སྡོམ་བྱས་ཡོད།"
|
||||
hasCompliance = "སྒྲིག་ཁྲིམས་ཚད་གཞི་ལྡན་པ།"
|
||||
noCompliance = "སྒྲིག་ཁྲིམས་ཀྱི་ཚད་གཞི་མེད་པ།"
|
||||
compliancePassed = "{{standards}} ཚད་ལྡན"
|
||||
complianceChecked = "ཚད་ལྡན་ཞིབ་བཤེར་ཟིན ({{failed}} ཕམ་པ)"
|
||||
basic = "གཞི་རྩའི་གནས་ཚུལ།"
|
||||
documentInfo = "ཡིག་ཆའི་ཆ་འཕྲིན།"
|
||||
securityTitle = "བདེ་འཇགས་གནས་ཚུལ།"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "པར་རིས་ཚད་གཞི།"
|
||||
margin = "མཐའ་ཤོག"
|
||||
positionAndFormatting = "ལས་གནས་དང་རྩ་སྒྲིག།"
|
||||
quickPosition = "ཤོག་ངོས་སྟེང་དུ་གནས་ཡུལ་ཞིག་འདེམས་ནས་མཚོན་རྟགས་འཇོག་དགོས།"
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "PDF ལ་མཚོན་རྟགས་ཁ་སྣོན་བྱེད་སྐབས་ནོར་འཁྲུལ་བྱུང་ཡོད།"
|
||||
@@ -3594,14 +3508,6 @@ failed = "PDF ལ་མཚོན་རྟགས་ཁ་སྣོན་བྱེ
|
||||
[AddStampRequest.results]
|
||||
title = "མཚོན་རྟགས་གྲུབ་འབྲས།"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "པར་རིས་མེད་པར་བཟོས་པ། ཤོག་ངོས་བཀོལ་སྤྱོད། རྒྱབ་མཇུག་། ཞབས་ཞུ་པ།"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "གསང་གྲངས་ལེགས་འགྲུ
|
||||
credentialsUpdated = "ཁྱེད་ཀྱི་ཆོག་མཆན་ཡིག་ཆ་གསར་བརྗེ་བྱས་ཡོད། ཡང་བསྐྱར་མིང་རྟགས་བཀོད་རོགས་གནང་།"
|
||||
defaultCredentials = "སྔོན་སྒྲིག་ནང་འཇུག་ཡིག་ཆ།"
|
||||
changePasswordWarning = "ཐེངས་དང་པོར་ནང་འཛུལ་བྱས་རྗེས་གསང་གྲངས་བསྒྱུར་རོགས།"
|
||||
mfaRequired = "ཆ་རྐྱེན་གཉིས་ཨང་རྟགས་དགོས"
|
||||
mfaCode = "ངོས་འཛིན་ཨང་རྟགས"
|
||||
enterMfaCode = "6 ཨང་གྲངས་ཀྱི་ཨང་རྟགས་བཙུགས"
|
||||
mfaPromptTitle = "ཆ་རྐྱེན་གཉིས་ངོས་འཛིན"
|
||||
mfaPromptBody = "མུ་མཐུད་པར་ངོས་འཛིན་app ནས་ངོས་འཛིན་ཨང་རྟགས་བཙུགས།"
|
||||
verifyingMfa = "ཞིབ་བཤེར་བཞིན..."
|
||||
verifyMfa = "ཨང་རྟགས་ཞིབ་བཤེར"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "PDF སྤྱི་བསྡོམས།"
|
||||
@@ -4425,10 +4324,6 @@ title = "ནང་འཇུག་ཐབས་ལམ་དགོས་པ།"
|
||||
message = "འཛིན་སྐྱོང་སྒྲིག་སྟངས་བསྒྱུར་བཅོས་བྱེད་པར་ནང་འཇུག་ཐབས་ལམ་ལག་བསྟར་བྱེད་དགོས། ཁྱེད་རང་གི་ཁོར་ཡུག་ཡང་ན་བདེ་འཇགས་ལ་བདེ་འཇགས་ཀྱི་བདེ་འཇགས་སྒྲིག་འཇུག་བྱེད་རོགས། སྒྲིག་སྟངས་ནང་བདེན་པ་ཡིན།"
|
||||
readOnly = "གཤམ་གྱི་སྒྲིག་སྟངས་ཀྱིས་གཞི་གྲངས་ལ་དཔེ་མཚོན་གྱི་རིན་ཐང་སྟོན་གྱི་ཡོད། ནང་འཇུག་ཐབས་ལམ་ཤུགས་སྣོན་བྱས་ནས་སྒྲིག་བཀོད་ངོ་མ་ལྟ་རྒྱུ་དང་རྩོམ་སྒྲིག་བྱེད་དགོས།"
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "ཚོང་འབྲེལ་ཆོག་ཐམ་དགོས"
|
||||
message = "{{featureName}} ལ་ལྟ་སྤྱོད་བྱེད་པར་ཚོང་འབྲེལ་ཆོག་ཐམ་དགོས། ཁྱེད་རང་ད་ལྟ་སྟོན་མའི་གཞི་གྲངས་ལ་ལྟ་བཞིན་ཡོད།"
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "བསྐྱར་དུ་འགོ་འཛུགས་དགོས་པ།"
|
||||
message = "སྒྲིག་སྟངས་ལེགས་འགྲུབ་བྱུང་ཡོད། བསྒྱུར་བཅོས་ལ་ཕན་ནུས་ལྡན་པའི་ཆེད་དུ་སར་བར་ཡང་བསྐྱར་འགོ་འཛུགས་དགོས།"
|
||||
@@ -4568,13 +4463,6 @@ description = "རྩིས་ཁྲའི་བཀག་སྡོམ་མ་
|
||||
label = "ནང་འཇུག་བསྐྱར་སྒྲིག་དུས་ཚོད།"
|
||||
description = "ནང་འཇུག་ཚོད་ལྟ་འཐུས་ཤོར་མ་བྱུང་གོང་གི་དུས་ཚོད།"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF སྲུང་སྐྱོབ།"
|
||||
description = "ས་ཁོངས་བརྒལ་བའི་རེ་འདུན་རྫུན་བཟོ་སྲུང་སྐྱོབ།"
|
||||
@@ -4883,48 +4771,6 @@ description = "སྤྱོད་མཁན་ལ་ཞིབ་ཕྲ་ནོ
|
||||
title = "གཞི་གྲངས་རྟེན་གཞི།"
|
||||
description = "ཁེ་ལས་བཀོལ་སྤྱོད་ཀྱི་ཆེད་དུ་སྒེར་གྱི་གཞི་གྲངས་མཛོད་ཀྱི་མཐུད་ཁ་སྒྲིག་སྟངས་སྒྲིག་བཀོད་བྱེད།"
|
||||
configuration = "གཞི་གྲངས་མཛོད་སྒྲིག་བཀོད།"
|
||||
backupTitle = "གྲབས་ཉར་དང་བསྐྱར་གསོ"
|
||||
backupDescription = "དོ་དམ་སྒེའུ་ཁུང་ ནས་ H2 གྲབས་ཉར་ཐད་ཀར་དུ་འཛིན་སྐྱོང་བྱེད་པ།"
|
||||
loadError = "གཞི་གྲངས་མཛོད་གྲབས་ཉར་འཇུག་མ་ཐུབ།"
|
||||
backupCreated = "གྲབས་ཉར་བཟོས་ལེགས་འགྲུབ།"
|
||||
backupFailed = "གྲབས་ཉར་བཟོས་མ་ཐུབ།"
|
||||
deleteTitle = "གྲབས་ཉར་སུབ།"
|
||||
deleteConfirm = "གྲབས་ཉར་འདི་སུབ་དམ། འདི་ལོག་མི་ཐུབ།"
|
||||
deleteSuccess = "གྲབས་ཉར་སུབ་ཟིན།"
|
||||
deleteFailed = "གྲབས་ཉར་སུབ་མ་ཐུབ།"
|
||||
deleteConfirmAction = "གྲབས་ཉར་སུབ།"
|
||||
downloadFailed = "གྲབས་ཉར་ཕབ་ལེན་མ་ཐུབ།"
|
||||
version = "H2 ཐོན་རིམ།"
|
||||
embedded = "ནང་འཇུག H2"
|
||||
external = "ཕྱིའི་གཞི་གྲངས་མཛོད"
|
||||
h2Only = "གྲབས་ཉར་ནི་ ནང་འཇུག H2 གཞི་གྲངས་མཛོད་ཁོ་ནར་ཡོད།"
|
||||
h2Hint = "གྲབས་ཉར་དང་བསྐྱར་གསོ་ལྕོགས་གྲུབ་བྱེད་པར་ གཞི་གྲངས་མཛོད་མཚན་གྲངས་ H2 ལ་བཀོད་ནས་ རང་མོས་གཞི་གྲངས་མཛོད་བཀག་པ།"
|
||||
manageBackups = "གྲབས་ཉར་འཛིན་སྐྱོང་"
|
||||
refresh = "གསར་སྒྱུར"
|
||||
createBackup = "གྲབས་ཉར་བཟོ"
|
||||
uploadTitle = "ཡར་འཇུག་དང་ནང་འཇུག"
|
||||
chooseFile = ".sql གྲབས་ཉར་ཡིག་ཆ་ཞིག འདེམས།"
|
||||
importFromUpload = "ཡར་འཇུག་ནང་འཇུག"
|
||||
confirmImportTitle = "གཞི་གྲངས་མཛོད་ནང་འཇུག་གཏན་འཁེལ།"
|
||||
overwriteWarning = "ཉེན་བརྡ་: འདིས་མིག་སྔའི་གཞི་གྲངས་མཛོད་ལ་ཚབ་བྱེད་འགྲོ།"
|
||||
overwriteWarningBody = "ཡར་འཇུག་བྱས་པའི་གྲབས་ཉར་གྱིས་དངོས་ཡོད་གཞི་གྲངས་ཚང་མ་ཚབ་བྱེད་འགྲོ། འགུལ་སྤྱོད་འདི་ལོག་མི་ཐུབ།"
|
||||
confirmCodeLabel = "མུ་མཐུད་བྱེད་པར་གཏན་འཁེལ་ཨང་གྲངས་ནང་འཇུག"
|
||||
enterCode = "གོང་གི་མངོན་པའི་ཨང་གྲངས་ནང་འཇུག"
|
||||
confirmImport = "ནང་འཇུག་གཏན་འཁེལ"
|
||||
codeMismatch = "གཏན་འཁེལ་ཨང་གྲངས་མི་མཚུངས།"
|
||||
codeMismatchBody = "མུ་མཐུད་བྱེད་པར་ མངོན་པ་དང་དོ་མཉམ་ངེས་པར་ཨང་གྲངས་ནང་འཇུག་གནང་།"
|
||||
selectFile = "ནང་འཇུག་བྱེད་པར་ .sql ཡིག་ཆ་ཞིག་འདེམས།"
|
||||
importSuccess = "གྲབས་ཉར་ནང་འཇུག་ལེགས་འགྲུབ།"
|
||||
importFailed = "གྲབས་ཉར་ནང་འཇུག་མ་ཐུབ།"
|
||||
noBackups = "ད་དུང་གྲབས་ཉར་མ་རྙེད།"
|
||||
unavailable = "མིག་སྔའི་གཞི་གྲངས་མཛོད་སྒྲིག་བཀོད་ལ་ གྲབས་ཉར་ཐོ་ཡིག་བྱེད་མི་ཐུབ།"
|
||||
fileName = "ཡིག་ཆ།"
|
||||
created = "བཟོས་པ།"
|
||||
size = "ཆེ་ཆུང་།"
|
||||
actions = "བྱ་བ།"
|
||||
download = "ཕབ་ལེན།"
|
||||
import = "ནང་འཇུག།"
|
||||
delete = "སུབ།"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "སྒྲིག་གཞིའི་གྲངས་ཐོ་ལྕོགས་ཅན་བཟོ་བ།"
|
||||
@@ -5002,26 +4848,6 @@ description = "པར་རིས་བཟོ་བཅོས་བྱེད་
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "ཊེས་སི་ཌ་ཊ་ཐོ་གཞུང་།"
|
||||
description = "OCR སྐད་ཡིག་ཡིག་ཆའི་ཆེད་དུ་ཊེ་སི་ཌེ་ཊ་སྣོད་ཐོ་ལ་ལམ་ལུགས།"
|
||||
downloadMissingTitle = "སྐད་ཡིག་གཅིག་ཡང་མ་འདེམས།"
|
||||
downloadMissingBody = "ཉུང་མཐར་སྐད་ཡིག་གཅིག་རང་ཕབ་ལེན་བྱེད་པར་འདེམས་གནང་།"
|
||||
downloadSuccessTitle = "སྐད་ཡིག་ཕབ་ལེན་བྱས་ཟིན།"
|
||||
downloadSuccessBody = "འདེམས་པའི་ tessdata སྐད་ཡིག་ཉར་ཚགས་བྱས་ཟིན།"
|
||||
downloadErrorTitle = "ཕབ་ལེན་མ་ཐུབ།"
|
||||
loadingLanguages = "སྒྲིག་འཇུག་བྱས་པའི་ tessdata སྐད་ཡིག་འཇུག་བཞིན..."
|
||||
installedLanguages = "སྒྲིག་འཇུག་བྱས་པའི་ tessdata སྐད་ཡིག"
|
||||
noLanguages = "སྒྲིག་བཀོད་ཟིན་པའི་སྣོད་འཛིན་ནང་ tessdata སྐད་ཡིག་མ་རྙེད།"
|
||||
downloadLabel = "tessdata སྐད་ཡིག་ཁ་སྣོན་ཕབ་ལེན་བྱོས།"
|
||||
downloadPlaceholder = "སྐད་ཡིག་འདེམས།"
|
||||
downloadNothingFound = "སྐད་ཡིག་ཁ་སྣོན་མ་རྙེད།"
|
||||
permissionNotice = "tessdata འགྲུལ་ལམ་འུར་བྱེད་མི་ཐུབ། ཕབ་ལེན་ཚུལ་དེ་བརྡ་འཚོལ་ཆས་ནས་ཁ་ཕྱེའོ། .traineddata ཡིག་ཆ་དེ་རང་ལས་ tessdata སྣོད་འཛིན་ནང་ཉར་ཚགས་བྱོས།"
|
||||
manualLinks = "ལག་ཐོག་ཕབ་ལེན: འབྲེལ་མཐུད་དེ་ཚོ་སྣུན་ནས་ ཡིག་ཆ་དེ་ཚོ་ར་ tessdata སྣོད་འཛིན་ནང་བཞག།"
|
||||
downloadButton = "འདེམས་པའི་སྐད་ཡིག་ཕབ་ལེན།"
|
||||
downloadInvalidTitle = "འདེམས་པ་ནུས་མེད།"
|
||||
downloadInvalidBody = "འདེམས་པའི་སྐད་ཡིག་ཁ་ཤས་ཕབ་ལེན་འབྱོར་མི་ཐུབ། གསར་སྒྱུར་བྱས་ནས་ཐོ་ཡིག་ནས་འདེམས་རོགས།"
|
||||
downloadErrorNetwork = "དྲ་བ་ནོར་འཁྲུལ་ལས་ཕབ་ལེན་མ་ཐུབ། མཐུད་སྦྲེལ་ཞིབ་བཤེར་བྱས་ནས་ལོག་སྐྱར་ཚོད་ལྟ་བྱོས།"
|
||||
downloadErrorServer = "tessdata སྐད་ཡིག་ཕབ་ལེན་བྱེད་པའི་སྐབས་ server ནོར་འཁྲུལ་བྱུང་། འཕྲལ་བར་བསྒུག་ནས་ལོག་སྐྱར་ཚོད་ལྟ་བྱོས།"
|
||||
downloadErrorPermission = "tessdata སྣོད་འཛིན་འུར་བྱེད་མི་ཐུབ: {{message}} འུར་བྱེད་རུང་བའི་སྣོད་འཛིན་ཞིག་འདེམས་ (དཔེར་ན་ application data folder འོག་ཏུ) ཡང་ན་དབང་ཚད་བསྒྱུར།"
|
||||
downloadErrorGeneric = "ཕབ་ལེན་མ་ཐུབ: {{message}} འཕྲལ་བར་བསྒུག་ནས་ལོག་སྐྱར་ཚོད་ལྟ་བྱོས།"
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "HTML གཙང་སྦྲ་མེད་པར་བཟོ་བ།"
|
||||
@@ -5525,7 +5351,6 @@ desc = "གོམ་པ་མང་པོའི་ལས་ཀའི་འགྲ
|
||||
invalidStep = "གོམ་པ་མི་འདང་བ།"
|
||||
reviewTitle = "རང་འགུལ་ཅན་གྱི་གྲུབ་འབྲས།"
|
||||
copyToSaved = "སྐྱོབ་པའི་ངོ་བཤུས།"
|
||||
exportForFolderScanning = "ཡིག་སྣོད་ཞིབ་བཤེར་ཆེད་དུ་ཕྱིར་གཏོང"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "རང་འགུལ་ཅན་འདི་དང་ལས་སྣོན་བྱེད་པའི་ཡིག་ཆ་འདེམས།"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "རང་འགུལ་ཅན་གསར་གཏོད།"
|
||||
editTitle = "རང་འགུལ་སྒྲིག་བཅོས།"
|
||||
intro = "རང་འགུལ་གྱིས་ལག་ཆ་རིམ་བཞིན་སྤྱོད་ཀྱི་ཡོད། འགོ་འཛུགས་པར་ཁྱེད་ཀྱིས་ཁོང་ཚོ་རྒྱུག་དགོས་པའི་གོ་རིམ་ནང་ལག་ཆ་ཁ་སྣོན་བྱེད་དགོས།"
|
||||
save = "རང་འགུལ་ཅན་དུ་འགྱུར་བ།"
|
||||
exportForFolderScanning = "ཡིག་སྣོད་ཞིབ་བཤེར་ཆེད་དུ་ཕྱིར་གཏོང"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "རང་འགུལ་ཅན་མིང་།"
|
||||
@@ -5922,11 +5746,9 @@ username = "སྤྱོད་མཁན་གྱི་མིང་།(གློ
|
||||
usernamePlaceholder = "seters@example.com"
|
||||
password = "གསང་གྲངས"
|
||||
passwordPlaceholder = "གསང་གྲངས་བཙུགས།"
|
||||
passwordRequired = "གསང་ཨང་དགོས་ངེས།"
|
||||
role = "འཁྲབ་ཚན"
|
||||
team = "རུ་ཁག (གདམ་ག)"
|
||||
teamPlaceholder = "རུ་ཁག་འདེམས་པ།"
|
||||
authType = "ར་སྤྲོད་རིགས།"
|
||||
forcePasswordChange = "ཐོག་མར་ནང་འཇུག་ཐོག་གསང་གྲངས་བསྒྱུར་བ།"
|
||||
cancel = "འདོར་བ"
|
||||
submit = "འཐུས་མི།"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "སྤྱོད་མཁན་གྱི་མིང་དང
|
||||
passwordTooShort = "གསང་གྲངས་ཉུང་མཐར་ཡིག་འབྲུ་དྲུག་ཡིན་དགོས།"
|
||||
success = "བེད་སྤྱོད་པ་ལེགས་འགྲུབ་བྱུང་བ།"
|
||||
error = "སྤྱོད་མཁན་གསར་སྐྲུན་མ་ཐུབ་པ།"
|
||||
forceMFA = "ཤུལ་མའི་ནང་འཇུག་སྐབས MFA སྒྲིག་འགོད་ཁེར་བཙོང"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "གསང་ཨང་།"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "སྤྱོད་མཁན་ནི་ SSO སྤྲོད་མཁན་བརྒྱུད་དེ་ར་སྤྲོད་བྱེད།"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "ཚོགས་མིར་རྩོམ་སྒྲིག་པ།"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "{{count}} སྤྱོད་མཁན་གནས་ས་ཡ
|
||||
noSlotsAvailable = "ས་ཆ་མེད།"
|
||||
currentUsage = "ད་ལྟ་{{max}} ནས {{current}} སྤྱོད་མཁན་ཆོག་མཆན་བེད་སྤྱོད་བྱས་བཞིན།"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "སྤྱོད་མཁན་ལ་དོན་གྱི་ MFA སྤྱོད་སྒོ་བརྒྱབས་ཚར།"
|
||||
adminDisableError = "སྤྱོད་མཁན་ལ་དོན་གྱི་ MFA སྤྱོད་སྒོ་བརྒྱབ་མ་ཐུབ།"
|
||||
disableByAdmin = "MFA སྤྱོད་སྒོ་བརྒྱབ"
|
||||
|
||||
[workspace.teams]
|
||||
title = "རུ་ཁག"
|
||||
description = "རུ་ཁག་དོ་དམ་དང་ལས་སྒོའི་ཁོངས་མི་སྒྲིག་འཛུགས་བྱེད་པ།"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "མཇུག་ཕྱོགས།"
|
||||
visits = "གཟིགས་སྐོར་བ།"
|
||||
percentage = "བརྒྱ་ཆ།"
|
||||
noData = "གཞི་གྲངས་མེད།"
|
||||
unknownEndpoint = "མཇུག་སྣེ་མི་ཤེས"
|
||||
|
||||
[backendHealth]
|
||||
checking = "རྒྱབ་ཕྱོགས་ཀྱི་གནས་ཚུལ་ལ་ཞིབ་བཤེར་བྱེད་པ།"
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "Selecciona Operació"
|
||||
addOperationButton = "Afegeix operació"
|
||||
pipelineHeader = "Procés:"
|
||||
saveButton = "Descarrega"
|
||||
saveForFolderScanning = "Desa per a l'escaneig de carpetes"
|
||||
validateButton = "Valida"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "Pantalla completa"
|
||||
[settings.general.updates]
|
||||
title = "Actualitzacions de programari"
|
||||
description = "Comprova actualitzacions i informació de la versió"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "Versió actual"
|
||||
latestVersion = "Última versió"
|
||||
checkForUpdates = "Comprova actualitzacions"
|
||||
viewDetails = "Veure detalls"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "Opcions del Navegador"
|
||||
syncToBrowser = "Sincronitza Compte -> Navegador"
|
||||
syncToAccount = "Sincronitza Compte <- Navegador"
|
||||
|
||||
[account.mfa]
|
||||
title = "Autenticació de dos factors"
|
||||
setupFailed = "No s'ha pogut iniciar la configuració de l'autenticació de dos factors. Torneu-ho a provar."
|
||||
codeRequired = "Introduïu el codi d'autenticació per continuar."
|
||||
enabled = "Autenticació de dos factors activada."
|
||||
enableFailed = "No s'ha pogut activar l'autenticació de dos factors. Comproveu el codi i torneu-ho a provar."
|
||||
disabled = "Autenticació de dos factors desactivada."
|
||||
disableFailed = "No s'ha pogut desactivar l'autenticació de dos factors. Comproveu el codi i torneu-ho a provar."
|
||||
description = "Afegiu una capa addicional de seguretat al vostre compte."
|
||||
enableButton = "Activa l'autenticació de dos factors"
|
||||
disableButton = "Desactiva l'autenticació de dos factors"
|
||||
setupTitle = "Configura l'autenticació de dos factors"
|
||||
setupDescription = "Escanegeu el codi QR amb la vostra aplicació d'autenticació i, a continuació, introduïu el codi de 6 dígits per confirmar."
|
||||
manualKey = "Clau de configuració manual"
|
||||
secretWarning = "Manteniu aquesta clau en secret. Qualsevol persona amb accés pot generar codis d'autenticació vàlids."
|
||||
codePlaceholder = "Introduïu el codi de 6 dígits"
|
||||
confirmEnable = "Activa"
|
||||
disableTitle = "Desactiva l'autenticació de dos factors"
|
||||
disableDescription = "Introduïu un codi d'autenticació vàlid per desactivar l'autenticació de dos factors."
|
||||
codeLabel = "Codi d'autenticació"
|
||||
confirmDisable = "Desactiva"
|
||||
ssoDescription = "L'autenticació de dos factors és gestionada pel vostre proveïdor d'identitat per als comptes d'inici de sessió únic."
|
||||
ssoManaged = "Configureu l'MFA a través del vostre proveïdor d'identitat."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "Opcions de Control d'Usuari"
|
||||
header = "Opcions de Control d'Usuari Admin"
|
||||
@@ -952,7 +925,6 @@ title = "Divideix auto per mida/pàg."
|
||||
desc = "Divideix un únic PDF en múltiples documents basant-se en la mida, el nombre de pàgines o el nombre de documents"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Substitueix i inverteix el color"
|
||||
desc = "Substitueix o inverteix colors en documents PDF"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "Escaneig automàtic de carpeta"
|
||||
desc = "Enllaç a la guia d'escaneig automàtic de carpetes"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Guia d'SSO"
|
||||
desc = "Enllaç a la guia d'SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Configuració air-gapped"
|
||||
desc = "Enllaç a la guia de configuració en entorn air-gapped"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Afegir Contrasenya"
|
||||
desc = "Xifra el document PDF amb contrasenya."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Canviar Permissos"
|
||||
desc = "Canvia les restriccions i els permisos del document"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "Automatitza"
|
||||
desc = "Construeix fluxos de treball multietapa enllaçant accions PDF. Ideal per a tasques recurrents."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Superposa PDFs sobre un altre PDF"
|
||||
title = "Superposar PDFs"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Editor de text PDF"
|
||||
desc = "Revisa i edita exportacions JSON de Stirling PDF amb edició de text agrupada i regeneració del PDF"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "Baixa el fitxer HTML intermedi en lloc del PDF"
|
||||
pdfaOptions = "Opcions de PDF/A"
|
||||
outputFormat = "Format de sortida"
|
||||
pdfaNote = "PDF/A-1b és més compatible; PDF/A-2b admet més funcions."
|
||||
pdfaFormat = "Format PDF/A"
|
||||
pdfaDigitalSignatureWarning = "El PDF conté una signatura digital. Aquesta serà eliminada en el següent pas."
|
||||
pdfxDigitalSignatureWarning = "El PDF conté una signatura digital. Aquesta se suprimirà en el pas següent."
|
||||
pdfxDescription = "PDF/X és un subconjunt de PDF estàndard ISO per a una impressió fiable i un intercanvi de gràfics fiable."
|
||||
fileFormat = "Format de fitxer"
|
||||
wordDoc = "Document de Word"
|
||||
wordDocExt = "Document de Word (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "Actualment no funciona per a múltiples entrades al mateix temps"
|
||||
outputFormat = "Format de sortida"
|
||||
pdfWithDigitalSignature = "El PDF conté una signatura digital. Aquesta serà eliminada en el següent pas."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "impressió,estàndard,conversió,producció,preimpressió,arxiu"
|
||||
title = "PDF a PDF/X"
|
||||
header = "PDF a PDF/X"
|
||||
credit = "Aquest servei utilitza Ghostscript per a la conversió a PDF/X"
|
||||
submit = "Converteix"
|
||||
tip = "Actualment no funciona amb diverses entrades alhora"
|
||||
outputFormat = "Format de sortida"
|
||||
pdfWithDigitalSignature = "El PDF conté una signatura digital. Aquesta se suprimirà en el pas següent."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc,docx,odt,word,transformació,format,conversió,oficina,microsoft,fitxer doc"
|
||||
title = "PDF a Word"
|
||||
@@ -3200,18 +3153,6 @@ other = "Altres"
|
||||
perPageInfo = "Informació per pàgina"
|
||||
tableOfContents = "Taula de continguts"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "No detectat"
|
||||
passed = "Superat"
|
||||
failed = "Fallat"
|
||||
compliant = "Conforme"
|
||||
nonCompliant = "No conforme"
|
||||
none = "No s'han detectat estàndards"
|
||||
passedCount = "superats"
|
||||
failedCount = "fallats"
|
||||
noVerification = "No s'ha realitzat cap verificació"
|
||||
noVerificationDesc = "El compliment dels estàndards PDF no s'ha verificat per a aquest document."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "Fitxers adjunts"
|
||||
embeddedFiles = "Fitxers incrustats"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} restriccions"
|
||||
permsMixed = "Alguns permisos restringits"
|
||||
hasCompliance = "Té estàndards de conformitat"
|
||||
noCompliance = "Sense estàndards de conformitat"
|
||||
compliancePassed = "{{standards}} conforme"
|
||||
complianceChecked = "Estàndards verificats ({{failed}} fallats)"
|
||||
basic = "Informació bàsica"
|
||||
documentInfo = "Informació del document"
|
||||
securityTitle = "Estat de seguretat"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "Mida de la imatge"
|
||||
margin = "Marge"
|
||||
positionAndFormatting = "Posició i format"
|
||||
quickPosition = "Seleccioneu una posició a la pàgina per col·locar el segell."
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "S'ha produït un error en afegir el segell al PDF."
|
||||
@@ -3594,14 +3508,6 @@ failed = "S'ha produït un error en afegir el segell al PDF."
|
||||
[AddStampRequest.results]
|
||||
title = "Resultats del segell"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "Elimina imatge,Operacions de pàgina,Back-end,Servidor"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "Contrasenya canviada correctament! Inicieu sessió amb
|
||||
credentialsUpdated = "S'han actualitzat les vostres credencials. Torneu a iniciar sessió."
|
||||
defaultCredentials = "Credencials d'inici de sessió predeterminades"
|
||||
changePasswordWarning = "Canvia la contrasenya després d'iniciar sessió per primera vegada"
|
||||
mfaRequired = "Es requereix un codi de dos factors"
|
||||
mfaCode = "Codi d'autenticació"
|
||||
enterMfaCode = "Introduïu el codi de 6 dígits"
|
||||
mfaPromptTitle = "Autenticació de dos factors"
|
||||
mfaPromptBody = "Introduïu el codi d'autenticació de la vostra aplicació d'autenticació per continuar."
|
||||
verifyingMfa = "Verificant..."
|
||||
verifyMfa = "Verifica el codi"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "Visió general de Stirling PDF"
|
||||
@@ -4425,10 +4324,6 @@ title = "Cal el mode d'inici de sessió"
|
||||
message = "S'ha d'activar el mode d'inici de sessió per modificar la configuració d'administració. Si us plau, establiu SECURITY_ENABLELOGIN=true al vostre entorn o security.enableLogin: true a settings.yml, i després reinicieu el servidor."
|
||||
readOnly = "Els paràmetres següents mostren valors d'exemple com a referència. Activeu el mode d'inici de sessió per veure i editar la configuració real."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "Es requereix llicència Enterprise"
|
||||
message = "Es requereix una llicència Enterprise per accedir a {{featureName}}. Esteu veient dades de demostració com a referència."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "Cal reiniciar"
|
||||
message = "La configuració s'ha desat correctament. Cal reiniciar el servidor perquè els canvis tinguin efecte."
|
||||
@@ -4568,13 +4463,6 @@ description = "Nombre màxim d'intents d'inici de sessió fallits abans de bloqu
|
||||
label = "Temps de reinicialització d'inici de sessió (minuts)"
|
||||
description = "Temps abans que es restableixin els intents d'inici de sessió fallits"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Desactiva la protecció CSRF"
|
||||
description = "Desactiva la protecció contra Cross-Site Request Forgery (no recomanat)"
|
||||
@@ -4883,48 +4771,6 @@ description = "Mostra missatges d'error detallats per als usuaris."
|
||||
title = "Base de dades"
|
||||
description = "Configureu la configuració de connexió de base de dades personalitzada per a desplegaments d'empresa."
|
||||
configuration = "Configuració de la base de dades"
|
||||
backupTitle = "Còpies de seguretat i restauració"
|
||||
backupDescription = "Gestiona còpies de seguretat d'H2 directament des de la consola d'administració."
|
||||
loadError = "No s'han pogut carregar les còpies de seguretat de la base de dades"
|
||||
backupCreated = "La còpia de seguretat s'ha creat correctament"
|
||||
backupFailed = "No s'ha pogut crear la còpia de seguretat"
|
||||
deleteTitle = "Suprimeix la còpia de seguretat"
|
||||
deleteConfirm = "Voleu suprimir aquesta còpia de seguretat? Aquesta acció no es pot desfer."
|
||||
deleteSuccess = "S'ha suprimit la còpia de seguretat"
|
||||
deleteFailed = "No s'ha pogut suprimir la còpia de seguretat"
|
||||
deleteConfirmAction = "Suprimeix la còpia de seguretat"
|
||||
downloadFailed = "No s'ha pogut descarregar la còpia de seguretat"
|
||||
version = "Versió d'H2"
|
||||
embedded = "H2 integrat"
|
||||
external = "Base de dades externa"
|
||||
h2Only = "Les còpies de seguretat només estan disponibles per a la base de dades H2 integrada."
|
||||
h2Hint = "Establiu el tipus de base de dades a H2 i desactiveu la base de dades personalitzada per habilitar la còpia de seguretat i la restauració."
|
||||
manageBackups = "Gestiona les còpies de seguretat"
|
||||
refresh = "Actualitza"
|
||||
createBackup = "Crea una còpia de seguretat"
|
||||
uploadTitle = "Puja i importa"
|
||||
chooseFile = "Trieu un fitxer de còpia de seguretat .sql"
|
||||
importFromUpload = "Importa la pujada"
|
||||
confirmImportTitle = "Confirma la importació de la base de dades"
|
||||
overwriteWarning = "Avís: això sobreescriurà la base de dades actual."
|
||||
overwriteWarningBody = "Totes les dades existents se substituiran per la còpia de seguretat pujada. Aquesta acció no es pot desfer."
|
||||
confirmCodeLabel = "Introduïu el codi de confirmació per continuar"
|
||||
enterCode = "Introduïu el codi que es mostra a dalt"
|
||||
confirmImport = "Confirma la importació"
|
||||
codeMismatch = "El codi de confirmació no coincideix"
|
||||
codeMismatchBody = "Introduïu el codi exactament tal com es mostra per continuar."
|
||||
selectFile = "Seleccioneu un fitxer .sql per importar"
|
||||
importSuccess = "La còpia de seguretat s'ha importat correctament"
|
||||
importFailed = "No s'ha pogut importar la còpia de seguretat"
|
||||
noBackups = "Encara no s'han trobat còpies de seguretat."
|
||||
unavailable = "La llista de còpies de seguretat no està disponible per a la configuració actual de la base de dades."
|
||||
fileName = "Fitxer"
|
||||
created = "Creat"
|
||||
size = "Mida"
|
||||
actions = "Accions"
|
||||
download = "Descarrega"
|
||||
import = "Importa"
|
||||
delete = "Suprimeix"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "Habilita base de dades personalitzada"
|
||||
@@ -5002,26 +4848,6 @@ description = "DPI màxim per al processament d'imatges (0 = il·limitat)"
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "Directori tessdata"
|
||||
description = "Camí al directori tessdata per als fitxers d'idioma d'OCR"
|
||||
downloadMissingTitle = "No s'ha seleccionat cap idioma"
|
||||
downloadMissingBody = "Seleccioneu almenys un idioma per descarregar."
|
||||
downloadSuccessTitle = "Idiomes descarregats"
|
||||
downloadSuccessBody = "S'han desat els idiomes de tessdata seleccionats."
|
||||
downloadErrorTitle = "La descàrrega ha fallat"
|
||||
loadingLanguages = "S'estan carregant els idiomes de tessdata instal·lats..."
|
||||
installedLanguages = "Idiomes de tessdata instal·lats"
|
||||
noLanguages = "No s'ha trobat cap idioma de tessdata al directori configurat."
|
||||
downloadLabel = "Descarrega idiomes de tessdata addicionals"
|
||||
downloadPlaceholder = "Seleccioneu idiomes"
|
||||
downloadNothingFound = "No s'han trobat idiomes addicionals"
|
||||
permissionNotice = "El camí de tessdata no té permisos d'escriptura. Les descàrregues s'obriran al navegador; deseu manualment els fitxers .traineddata a la carpeta tessdata."
|
||||
manualLinks = "Descàrregues manuals: feu clic als enllaços i col·loqueu els fitxers a la carpeta tessdata."
|
||||
downloadButton = "Descarrega els idiomes seleccionats"
|
||||
downloadInvalidTitle = "Selecció no vàlida"
|
||||
downloadInvalidBody = "Alguns idiomes seleccionats no estan disponibles per descarregar. Actualitzeu i trieu de la llista."
|
||||
downloadErrorNetwork = "La descàrrega ha fallat per un error de xarxa. Comproveu la connexió i torneu-ho a provar."
|
||||
downloadErrorServer = "El servidor ha trobat un error en descarregar els idiomes de tessdata. Torneu-ho a provar més tard."
|
||||
downloadErrorPermission = "El directori de tessdata no és escrivible: {{message}}. Trieu un directori amb permisos d'escriptura (per exemple, dins de la carpeta de dades de l'aplicació) o ajusteu-ne els permisos."
|
||||
downloadErrorGeneric = "La descàrrega ha fallat: {{message}}. Torneu-ho a provar més tard."
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "Desactiva la sanitització d'HTML"
|
||||
@@ -5525,7 +5351,6 @@ desc = "Creeu fluxos de treball de diversos passos encadenant accions de PDF. Id
|
||||
invalidStep = "Pas no vàlid"
|
||||
reviewTitle = "Resultats de l’automatització"
|
||||
copyToSaved = "Copia a Desats"
|
||||
exportForFolderScanning = "Exporta per a l'escaneig de carpetes"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "Seleccioneu fitxers per processar amb aquesta automatització"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "Crea automatització"
|
||||
editTitle = "Edita automatització"
|
||||
intro = "Les automatitzacions executen les eines seqüencialment. Per començar, afegiu les eines en l’ordre que voleu que s’executin."
|
||||
save = "Desa l’automatització"
|
||||
exportForFolderScanning = "Exporta per a l'escaneig de carpetes"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "Nom de l’automatització"
|
||||
@@ -5922,11 +5746,9 @@ username = "Nom d’usuari (correu electrònic)"
|
||||
usernamePlaceholder = "user@example.com"
|
||||
password = "Contrasenya"
|
||||
passwordPlaceholder = "Introduïu la contrasenya"
|
||||
passwordRequired = "La contrasenya és obligatòria"
|
||||
role = "Rol"
|
||||
team = "Equip (opcional)"
|
||||
teamPlaceholder = "Seleccioneu un equip"
|
||||
authType = "Tipus d'autenticació"
|
||||
forcePasswordChange = "Obliga a canviar la contrasenya en el primer inici de sessió"
|
||||
cancel = "Cancel·la"
|
||||
submit = "Afegeix membre"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "Cal nom d’usuari i contrasenya"
|
||||
passwordTooShort = "La contrasenya ha de tenir almenys 6 caràcters"
|
||||
success = "Usuari creat correctament"
|
||||
error = "No s’ha pogut crear l’usuari"
|
||||
forceMFA = "Força la configuració de l'MFA en el proper inici de sessió"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "Contrasenya"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "L'usuari s'autenticarà a través del proveïdor SSO"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "Edita membre"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "{{count}} plaça(es) d’usuari disponibles"
|
||||
noSlotsAvailable = "No hi ha places disponibles"
|
||||
currentUsage = "Actualment s’estan utilitzant {{current}} de {{max}} llicències d’usuari"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "MFA desactivada correctament per a l'usuari"
|
||||
adminDisableError = "No s'ha pogut desactivar l'MFA per a l'usuari"
|
||||
disableByAdmin = "Desactiva l'MFA"
|
||||
|
||||
[workspace.teams]
|
||||
title = "Equips"
|
||||
description = "Gestioneu equips i organitzeu els membres de l’espai de treball"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "Endpoint"
|
||||
visits = "Visites"
|
||||
percentage = "Percentatge"
|
||||
noData = "No hi ha dades disponibles"
|
||||
unknownEndpoint = "Punt final desconegut"
|
||||
|
||||
[backendHealth]
|
||||
checking = "Comprovant l'estat del backend..."
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "Vybrat operaci"
|
||||
addOperationButton = "Přidat operaci"
|
||||
pipelineHeader = "Pipeline:"
|
||||
saveButton = "Stáhnout"
|
||||
saveForFolderScanning = "Uložit pro skenování složky"
|
||||
validateButton = "Ověřit"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "Celá obrazovka"
|
||||
[settings.general.updates]
|
||||
title = "Aktualizace softwaru"
|
||||
description = "Zkontrolujte aktualizace a zobrazte informace o verzi"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "Aktuální verze"
|
||||
latestVersion = "Nejnovější verze"
|
||||
checkForUpdates = "Zkontrolovat aktualizace"
|
||||
viewDetails = "Zobrazit podrobnosti"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "Nastavení webového prohlížeče"
|
||||
syncToBrowser = "Synchronizovat účet -> prohlížeč"
|
||||
syncToAccount = "Synchronizovat účet <- prohlížeč"
|
||||
|
||||
[account.mfa]
|
||||
title = "Dvoufázové ověření"
|
||||
setupFailed = "Nelze zahájit nastavení dvoufázového ověření. Zkuste to prosím znovu."
|
||||
codeRequired = "Pro pokračování zadejte ověřovací kód."
|
||||
enabled = "Dvoufázové ověření povoleno."
|
||||
enableFailed = "Dvoufázové ověření se nepodařilo povolit. Zkontrolujte kód a zkuste to znovu."
|
||||
disabled = "Dvoufázové ověření zakázáno."
|
||||
disableFailed = "Dvoufázové ověření se nepodařilo zakázat. Zkontrolujte kód a zkuste to znovu."
|
||||
description = "Přidejte k účtu další vrstvu zabezpečení."
|
||||
enableButton = "Povolit dvoufázové ověření"
|
||||
disableButton = "Zakázat dvoufázové ověření"
|
||||
setupTitle = "Nastavit dvoufázové ověření"
|
||||
setupDescription = "Naskenujte QR kód ve své ověřovací aplikaci a poté pro potvrzení zadejte 6místný kód."
|
||||
manualKey = "Klíč pro ruční nastavení"
|
||||
secretWarning = "Tento klíč uchovávejte v soukromí. Kdokoli s přístupem může generovat platné ověřovací kódy."
|
||||
codePlaceholder = "Zadejte 6místný kód"
|
||||
confirmEnable = "Povolit"
|
||||
disableTitle = "Zakázat dvoufázové ověření"
|
||||
disableDescription = "Zadejte platný ověřovací kód pro zakázání dvoufázového ověření."
|
||||
codeLabel = "Ověřovací kód"
|
||||
confirmDisable = "Zakázat"
|
||||
ssoDescription = "U účtů s jednotným přihlášením je dvoufázové ověření spravováno vaším poskytovatelem identity."
|
||||
ssoManaged = "Nastavte MFA prostřednictvím svého poskytovatele identity."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "Nastavení správy uživatelů"
|
||||
header = "Nastavení správy uživatelů"
|
||||
@@ -952,7 +925,6 @@ title = "Automaticky rozdělit podle velikosti/počtu"
|
||||
desc = "Rozdělí jeden PDF na více dokumentů podle velikosti, počtu stránek nebo počtu dokumentů"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Nahradit a invertovat barvy"
|
||||
desc = "Nahradit nebo invertovat barvy v dokumentech PDF"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "Autom. skenování složek"
|
||||
desc = "Odkaz na průvodce automatizovaným skenováním složek"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Průvodce SSO"
|
||||
desc = "Odkaz na průvodce SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Offline nastavení"
|
||||
desc = "Odkaz na průvodce nastavením v odpojeném prostředí"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Přidat heslo"
|
||||
desc = "Zašifrovat váš PDF dokument heslem."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Změnit oprávnění"
|
||||
desc = "Změnit omezení a oprávnění dokumentu"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "Automatizace"
|
||||
desc = "Vytvářejte vícekrokové workflow řetězením akcí PDF. Ideální pro opakující se úlohy."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Překryje PDF nad jiným PDF"
|
||||
title = "Překrýt PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Editor textu PDF"
|
||||
desc = "Prohlížejte a upravujte exporty JSON ze Stirling PDF se skupinovými úpravami textu a regenerací PDF"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "Stáhnout HTML mezisoubor místo PDF"
|
||||
pdfaOptions = "Možnosti PDF/A"
|
||||
outputFormat = "Výstupní formát"
|
||||
pdfaNote = "PDF/A-1b je kompatibilnější, PDF/A-2b podporuje více funkcí."
|
||||
pdfaFormat = "Formát PDF/A"
|
||||
pdfaDigitalSignatureWarning = "PDF obsahuje digitální podpis, který bude v dalším kroku odstraněn."
|
||||
pdfxDigitalSignatureWarning = "PDF obsahuje digitální podpis. Ten bude v dalším kroku odstraněn."
|
||||
pdfxDescription = "PDF/X je podmnožina PDF dle standardu ISO pro spolehlivý tisk a výměnu grafiky."
|
||||
fileFormat = "Formát souboru"
|
||||
wordDoc = "Dokument Word"
|
||||
wordDocExt = "Dokument Word (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "Momentálně nefunguje pro více vstupů najednou"
|
||||
outputFormat = "Výstupní formát"
|
||||
pdfWithDigitalSignature = "PDF obsahuje digitální podpis, který bude v dalším kroku odstraněn."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "tisk,standard,konverze,produkce,předtisk,archiv"
|
||||
title = "PDF na PDF/X"
|
||||
header = "PDF na PDF/X"
|
||||
credit = "Tato služba používá Ghostscript pro převod na PDF/X"
|
||||
submit = "Převést"
|
||||
tip = "Aktuálně nefunguje pro více vstupů najednou"
|
||||
outputFormat = "Výstupní formát"
|
||||
pdfWithDigitalSignature = "PDF obsahuje digitální podpis. Ten bude v dalším kroku odstraněn."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc,docx,odt,word,transformace,formát,převod,office,microsoft,docfile"
|
||||
title = "PDF na Word"
|
||||
@@ -3200,18 +3153,6 @@ other = "Ostatní"
|
||||
perPageInfo = "Informace po stránkách"
|
||||
tableOfContents = "Obsah"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "Nezjištěno"
|
||||
passed = "Prošlo"
|
||||
failed = "Neprošlo"
|
||||
compliant = "V souladu"
|
||||
nonCompliant = "Není v souladu"
|
||||
none = "Nezjištěny žádné standardy"
|
||||
passedCount = "prošlo"
|
||||
failedCount = "neprošlo"
|
||||
noVerification = "Ověření neproběhlo"
|
||||
noVerificationDesc = "Shoda se standardy PDF nebyla pro tento dokument ověřena."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "Přílohy"
|
||||
embeddedFiles = "Vložené soubory"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} omezení"
|
||||
permsMixed = "Některá oprávnění jsou omezena"
|
||||
hasCompliance = "Obsahuje standardy shody"
|
||||
noCompliance = "Žádné standardy shody"
|
||||
compliancePassed = "V souladu s {{standards}}"
|
||||
complianceChecked = "Standardy ověřeny ({{failed}} neprošlo)"
|
||||
basic = "Základní informace"
|
||||
documentInfo = "Informace o dokumentu"
|
||||
securityTitle = "Stav zabezpečení"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "Velikost obrázku"
|
||||
margin = "Okraj"
|
||||
positionAndFormatting = "Umístění a formátování"
|
||||
quickPosition = "Vyberte na stránce pozici pro umístění razítka."
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "Při přidávání razítka do PDF došlo k chybě."
|
||||
@@ -3594,14 +3508,6 @@ failed = "Při přidávání razítka do PDF došlo k chybě."
|
||||
[AddStampRequest.results]
|
||||
title = "Výsledky razítka"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "Odstranit obrázek,Operace stránek,zadní strana,serverová strana"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "Heslo bylo úspěšně změněno! Přihlaste se novým
|
||||
credentialsUpdated = "Vaše přihlašovací údaje byly aktualizovány. Přihlaste se prosím znovu."
|
||||
defaultCredentials = "Výchozí přihlašovací údaje"
|
||||
changePasswordWarning = "Po prvním přihlášení si prosím změňte heslo"
|
||||
mfaRequired = "Je vyžadován kód pro dvoufázové ověření"
|
||||
mfaCode = "Ověřovací kód"
|
||||
enterMfaCode = "Zadejte 6místný kód"
|
||||
mfaPromptTitle = "Dvoufázové ověření"
|
||||
mfaPromptBody = "Pro pokračování zadejte ověřovací kód ze své ověřovací aplikace."
|
||||
verifyingMfa = "Ověřování..."
|
||||
verifyMfa = "Ověřit kód"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "Přehled Stirling PDF"
|
||||
@@ -4425,10 +4324,6 @@ title = "Vyžadován režim přihlášení"
|
||||
message = "Režim přihlášení musí být povolen, abyste mohli upravovat nastavení správce. Nastavte prosím SECURITY_ENABLELOGIN=true ve vašem prostředí nebo security.enableLogin: true v souboru settings.yml a poté restartujte server."
|
||||
readOnly = "Níže uvedená nastavení zobrazují vzorové hodnoty pro orientaci. Povolte režim přihlášení pro zobrazení a úpravu skutečné konfigurace."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "Je vyžadována licence Enterprise"
|
||||
message = "Pro přístup k {{featureName}} je vyžadována licence Enterprise. Zobrazená data jsou pouze ukázková."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "Vyžadován restart"
|
||||
message = "Nastavení byla úspěšně uložena. Aby se změny projevily, je vyžadován restart serveru."
|
||||
@@ -4568,13 +4463,6 @@ description = "Maximální počet neúspěšných pokusů o přihlášení před
|
||||
label = "Čas pro reset přihlášení (minuty)"
|
||||
description = "Doba, po které se neúspěšné pokusy o přihlášení resetují"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Zakázat ochranu CSRF"
|
||||
description = "Zakázat ochranu proti Cross-Site Request Forgery (nedoporučuje se)"
|
||||
@@ -4883,48 +4771,6 @@ description = "Zobrazit podrobná chybová hlášení pro uživatele."
|
||||
title = "Databáze"
|
||||
description = "Nakonfigurujte vlastní nastavení připojení k databázi pro podniková nasazení."
|
||||
configuration = "Konfigurace databáze"
|
||||
backupTitle = "Zálohy a obnovení"
|
||||
backupDescription = "Spravujte zálohy H2 přímo z administrátorské konzole."
|
||||
loadError = "Nepodařilo se načíst zálohy databáze"
|
||||
backupCreated = "Záloha byla úspěšně vytvořena"
|
||||
backupFailed = "Nepodařilo se vytvořit zálohu"
|
||||
deleteTitle = "Smazat zálohu"
|
||||
deleteConfirm = "Smazat tuto zálohu? Tuto akci nelze vrátit zpět."
|
||||
deleteSuccess = "Záloha byla smazána"
|
||||
deleteFailed = "Nepodařilo se smazat zálohu"
|
||||
deleteConfirmAction = "Smazat zálohu"
|
||||
downloadFailed = "Nepodařilo se stáhnout zálohu"
|
||||
version = "Verze H2"
|
||||
embedded = "Vestavěná H2"
|
||||
external = "Externí DB"
|
||||
h2Only = "Zálohy jsou dostupné pouze pro vestavěnou databázi H2."
|
||||
h2Hint = "Nastavte typ databáze na H2 a zakažte vlastní databázi, aby bylo možné zálohování a obnovu."
|
||||
manageBackups = "Spravovat zálohy"
|
||||
refresh = "Obnovit"
|
||||
createBackup = "Vytvořit zálohu"
|
||||
uploadTitle = "Nahrát a importovat"
|
||||
chooseFile = "Vyberte soubor zálohy .sql"
|
||||
importFromUpload = "Importovat nahraný soubor"
|
||||
confirmImportTitle = "Potvrdit import databáze"
|
||||
overwriteWarning = "Varování: Tímto přepíšete aktuální databázi."
|
||||
overwriteWarningBody = "Všechna existující data budou nahrazena nahranou zálohou. Tuto akci nelze vrátit zpět."
|
||||
confirmCodeLabel = "Pro pokračování zadejte potvrzovací kód"
|
||||
enterCode = "Zadejte kód zobrazený výše"
|
||||
confirmImport = "Potvrdit import"
|
||||
codeMismatch = "Potvrzovací kód nesouhlasí"
|
||||
codeMismatchBody = "Pro pokračování zadejte kód přesně tak, jak je zobrazen."
|
||||
selectFile = "Vyberte soubor .sql k importu"
|
||||
importSuccess = "Záloha byla úspěšně importována"
|
||||
importFailed = "Nepodařilo se importovat zálohu"
|
||||
noBackups = "Zatím nebyly nalezeny žádné zálohy."
|
||||
unavailable = "Seznam záloh není k dispozici pro aktuální konfiguraci databáze."
|
||||
fileName = "Soubor"
|
||||
created = "Vytvořeno"
|
||||
size = "Velikost"
|
||||
actions = "Akce"
|
||||
download = "Stáhnout"
|
||||
import = "Importovat"
|
||||
delete = "Smazat"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "Povolit vlastní databázi"
|
||||
@@ -5002,26 +4848,6 @@ description = "Maximální DPI pro zpracování obrázků (0 = neomezeně)"
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "Adresář tessdata"
|
||||
description = "Cesta k adresáři tessdata pro jazykové soubory OCR"
|
||||
downloadMissingTitle = "Není vybrán žádný jazyk"
|
||||
downloadMissingBody = "Vyberte alespoň jeden jazyk ke stažení."
|
||||
downloadSuccessTitle = "Jazyky staženy"
|
||||
downloadSuccessBody = "Vybrané jazyky tessdata byly uloženy."
|
||||
downloadErrorTitle = "Stažení se nezdařilo"
|
||||
loadingLanguages = "Načítání nainstalovaných jazyků tessdata..."
|
||||
installedLanguages = "Nainstalované jazyky tessdata"
|
||||
noLanguages = "V nakonfigurovaném adresáři nebyly nalezeny žádné jazyky tessdata."
|
||||
downloadLabel = "Stáhnout další jazyky tessdata"
|
||||
downloadPlaceholder = "Vyberte jazyky"
|
||||
downloadNothingFound = "Nebyly nalezeny žádné další jazyky"
|
||||
permissionNotice = "Cesta k tessdata není zapisovatelná. Stažení se otevře v prohlížeči; soubory .traineddata prosím uložte ručně do složky tessdata."
|
||||
manualLinks = "Ruční stažení: klikněte na odkazy a umístěte soubory do složky tessdata."
|
||||
downloadButton = "Stáhnout vybrané jazyky"
|
||||
downloadInvalidTitle = "Neplatný výběr"
|
||||
downloadInvalidBody = "Některé vybrané jazyky nejsou k dispozici ke stažení. Obnovte prosím a vyberte ze seznamu."
|
||||
downloadErrorNetwork = "Stažení selhalo kvůli chybě sítě. Zkontrolujte prosím své připojení a zkuste to znovu."
|
||||
downloadErrorServer = "Při stahování jazyků tessdata došlo na serveru k chybě. Zkuste to prosím později."
|
||||
downloadErrorPermission = "Adresář tessdata není zapisovatelný: {{message}}. Vyberte prosím zapisovatelný adresář (např. v adresáři dat aplikace) nebo upravte oprávnění."
|
||||
downloadErrorGeneric = "Stažení selhalo: {{message}}. Zkuste to prosím později."
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "Zakázat sanitizaci HTML"
|
||||
@@ -5525,7 +5351,6 @@ desc = "Vytvářejte vícekrokové pracovní postupy řetězením akcí PDF. Ide
|
||||
invalidStep = "Neplatný krok"
|
||||
reviewTitle = "Výsledky automatizace"
|
||||
copyToSaved = "Zkopírovat do uložených"
|
||||
exportForFolderScanning = "Export pro skenování složky"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "Vyberte soubory pro zpracování touto automatizací"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "Vytvořit automatizaci"
|
||||
editTitle = "Upravit automatizaci"
|
||||
intro = "Automatizace spouštějí nástroje postupně. Začněte přidáním nástrojů v pořadí, v jakém je chcete spustit."
|
||||
save = "Uložit automatizaci"
|
||||
exportForFolderScanning = "Export pro skenování složky"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "Název automatizace"
|
||||
@@ -5922,11 +5746,9 @@ username = "Uživatelské jméno (e‑mail)"
|
||||
usernamePlaceholder = "uzivatel@priklad.cz"
|
||||
password = "Heslo"
|
||||
passwordPlaceholder = "Zadejte heslo"
|
||||
passwordRequired = "Heslo je povinné"
|
||||
role = "Role"
|
||||
team = "Tým (volitelné)"
|
||||
teamPlaceholder = "Vyberte tým"
|
||||
authType = "Typ ověřování"
|
||||
forcePasswordChange = "Vynutit změnu hesla při prvním přihlášení"
|
||||
cancel = "Zrušit"
|
||||
submit = "Přidat člena"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "Uživatelské jméno a heslo jsou povinné"
|
||||
passwordTooShort = "Heslo musí mít alespoň 6 znaků"
|
||||
success = "Uživatel úspěšně vytvořen"
|
||||
error = "Nepodařilo se vytvořit uživatele"
|
||||
forceMFA = "Vynutit nastavení MFA při příštím přihlášení"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "Heslo"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "Uživatel se bude ověřovat přes poskytovatele SSO"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "Upravit člena"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "{{count}} míst pro uživatele k dispozici"
|
||||
noSlotsAvailable = "Žádná volná místa"
|
||||
currentUsage = "Aktuálně používáte {{current}} z {{max}} uživatelských licencí"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "MFA bylo u uživatele úspěšně zakázáno"
|
||||
adminDisableError = "Nepodařilo se zakázat MFA u uživatele"
|
||||
disableByAdmin = "Zakázat MFA"
|
||||
|
||||
[workspace.teams]
|
||||
title = "Týmy"
|
||||
description = "Spravujte týmy a organizujte členy pracovního prostoru"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "Endpoint"
|
||||
visits = "Návštěvy"
|
||||
percentage = "Procenta"
|
||||
noData = "Žádná data nejsou k dispozici"
|
||||
unknownEndpoint = "Neznámý koncový bod"
|
||||
|
||||
[backendHealth]
|
||||
checking = "Kontrola stavu backendu..."
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "Vælg Operation"
|
||||
addOperationButton = "Tilføj operation"
|
||||
pipelineHeader = "Pipeline:"
|
||||
saveButton = "Download"
|
||||
saveForFolderScanning = "Gem til mappeovervågning"
|
||||
validateButton = "Validér"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "Fuldskærm"
|
||||
[settings.general.updates]
|
||||
title = "Softwareopdateringer"
|
||||
description = "Søg efter opdateringer og se versionsinfo"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "Nuværende version"
|
||||
latestVersion = "Seneste version"
|
||||
checkForUpdates = "Søg efter opdateringer"
|
||||
viewDetails = "Vis detaljer"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "Webbrowser Indstilling"
|
||||
syncToBrowser = "Synkroniser Konto -> Browser"
|
||||
syncToAccount = "Synkroniser Konto <- Browser"
|
||||
|
||||
[account.mfa]
|
||||
title = "Tofaktorgodkendelse"
|
||||
setupFailed = "Kunne ikke starte opsætning af tofaktorgodkendelse. Prøv igen."
|
||||
codeRequired = "Indtast godkendelseskoden for at fortsætte."
|
||||
enabled = "Tofaktorgodkendelse er aktiveret."
|
||||
enableFailed = "Kan ikke aktivere tofaktorgodkendelse. Kontroller koden, og prøv igen."
|
||||
disabled = "Tofaktorgodkendelse er deaktiveret."
|
||||
disableFailed = "Kan ikke deaktivere tofaktorgodkendelse. Kontroller koden, og prøv igen."
|
||||
description = "Tilføj et ekstra sikkerhedslag til din konto."
|
||||
enableButton = "Aktivér tofaktorgodkendelse"
|
||||
disableButton = "Deaktivér tofaktorgodkendelse"
|
||||
setupTitle = "Opsæt tofaktorgodkendelse"
|
||||
setupDescription = "Scan QR-koden med din godkendelsesapp, og indtast derefter den 6-cifrede kode for at bekræfte."
|
||||
manualKey = "Manuel opsætningsnøgle"
|
||||
secretWarning = "Hold denne nøgle privat. Alle med adgang kan generere gyldige godkendelseskoder."
|
||||
codePlaceholder = "Indtast 6-cifret kode"
|
||||
confirmEnable = "Aktivér"
|
||||
disableTitle = "Deaktivér tofaktorgodkendelse"
|
||||
disableDescription = "Indtast en gyldig godkendelseskode for at deaktivere tofaktorgodkendelse."
|
||||
codeLabel = "Godkendelseskode"
|
||||
confirmDisable = "Deaktivér"
|
||||
ssoDescription = "Tofaktorgodkendelse administreres af din identitetsudbyder for single sign-on-konti."
|
||||
ssoManaged = "Konfigurer MFA via din identitetsudbyder."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "Brugerkontrolindstillinger"
|
||||
header = "Admin Brugerkontrolindstillinger"
|
||||
@@ -952,7 +925,6 @@ title = "Auto-opdel størrelse/antal"
|
||||
desc = "Opdel en enkelt PDF i flere dokumenter baseret på størrelse, sideantal eller dokumentantal"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Erstat og inverter farver"
|
||||
desc = "Erstat eller inverter farver i PDF-dokumenter"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "Automatiseret mappescanning"
|
||||
desc = "Link til guide for automatiseret mappescanning"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO-vejledning"
|
||||
desc = "Link til SSO-vejledning"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Air-gapped opsætning"
|
||||
desc = "Link til guide for air-gapped opsætning"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Tilføj Adgangskode"
|
||||
desc = "Kryptér dit PDF-dokument med en adgangskode."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Ændre Tilladelser"
|
||||
desc = "Ændr dokumentbegrænsninger og tilladelser"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "Automatiser"
|
||||
desc = "Byg flertrins-workflows ved at kæde PDF-handlinger sammen. Ideelt til tilbagevendende opgaver."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Overlejrer PDF'er oven på en anden PDF"
|
||||
title = "Overlejr PDF'er"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF-teksteditor"
|
||||
desc = "Gennemse og rediger Stirling PDF JSON-eksporter med grupperet tekstredigering og regenerering af PDF"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "Download HTML-mellemfil i stedet for PDF"
|
||||
pdfaOptions = "PDF/A-indstillinger"
|
||||
outputFormat = "Outputformat"
|
||||
pdfaNote = "PDF/A-1b er mere kompatibel, PDF/A-2b understøtter flere funktioner."
|
||||
pdfaFormat = "PDF/A-format"
|
||||
pdfaDigitalSignatureWarning = "PDF'en indeholder en digital signatur. Dette vil blive fjernet i næste trin."
|
||||
pdfxDigitalSignatureWarning = "PDF'en indeholder en digital signatur. Denne fjernes i næste trin."
|
||||
pdfxDescription = "PDF/X er en ISO-standard PDF-undersæt til pålidelig udskrivning og grafikudveksling."
|
||||
fileFormat = "Filformat"
|
||||
wordDoc = "Word-dokument"
|
||||
wordDocExt = "Word-dokument (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "Fungerer i øjeblikket ikke for flere input på én gang"
|
||||
outputFormat = "Outputformat"
|
||||
pdfWithDigitalSignature = "PDF'en indeholder en digital signatur. Dette vil blive fjernet i næste trin."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "udskrivning,standard,konvertering,produktion,førtryk,arkiv"
|
||||
title = "PDF til PDF/X"
|
||||
header = "PDF til PDF/X"
|
||||
credit = "Denne tjeneste bruger Ghostscript til PDF/X-konvertering"
|
||||
submit = "Konverter"
|
||||
tip = "Fungerer i øjeblikket ikke med flere input ad gangen"
|
||||
outputFormat = "Outputformat"
|
||||
pdfWithDigitalSignature = "PDF'en indeholder en digital signatur. Denne fjernes i næste trin."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc,docx,odt,word,transformation,format,konvertering,kontor,microsoft,docfil"
|
||||
title = "PDF til Word"
|
||||
@@ -3200,18 +3153,6 @@ other = "Andet"
|
||||
perPageInfo = "Info pr. side"
|
||||
tableOfContents = "Indholdsfortegnelse"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "Ikke detekteret"
|
||||
passed = "Bestået"
|
||||
failed = "Fejlet"
|
||||
compliant = "I overensstemmelse"
|
||||
nonCompliant = "Ikke i overensstemmelse"
|
||||
none = "Ingen standarder registreret"
|
||||
passedCount = "bestået"
|
||||
failedCount = "fejlet"
|
||||
noVerification = "Ingen verificering udført"
|
||||
noVerificationDesc = "Overholdelse af PDF-standarder blev ikke verificeret for dette dokument."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "Vedhæftninger"
|
||||
embeddedFiles = "Indlejrede filer"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} begrænsninger"
|
||||
permsMixed = "Nogle tilladelser er begrænsede"
|
||||
hasCompliance = "Har overensstemmelsesstandarder"
|
||||
noCompliance = "Ingen overensstemmelsesstandarder"
|
||||
compliancePassed = "{{standards}} i overensstemmelse"
|
||||
complianceChecked = "Standarder verificeret ({{failed}} fejlet)"
|
||||
basic = "Grundlæggende oplysninger"
|
||||
documentInfo = "Dokumentoplysninger"
|
||||
securityTitle = "Sikkerhedsstatus"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "Billedstørrelse"
|
||||
margin = "Margen"
|
||||
positionAndFormatting = "Placering og formatering"
|
||||
quickPosition = "Vælg en placering på siden til stemplet."
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "Der opstod en fejl under tilføjelse af stempel til PDF'en."
|
||||
@@ -3594,14 +3508,6 @@ failed = "Der opstod en fejl under tilføjelse af stempel til PDF'en."
|
||||
[AddStampRequest.results]
|
||||
title = "Stempelresultater"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "Fjern Billede,Sideoperationer,Back end,server side"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "Adgangskoden blev ændret! Log ind med din nye adgangs
|
||||
credentialsUpdated = "Dine legitimationsoplysninger er opdateret. Log ind igen."
|
||||
defaultCredentials = "Standard loginoplysninger"
|
||||
changePasswordWarning = "Skift din adgangskode efter du har logget ind første gang"
|
||||
mfaRequired = "Tofaktorkode påkrævet"
|
||||
mfaCode = "Godkendelseskode"
|
||||
enterMfaCode = "Indtast 6-cifret kode"
|
||||
mfaPromptTitle = "Tofaktorgodkendelse"
|
||||
mfaPromptBody = "Indtast godkendelseskoden fra din godkendelsesapp for at fortsætte."
|
||||
verifyingMfa = "Verificerer..."
|
||||
verifyMfa = "Verificér kode"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "Stirling PDF oversigt"
|
||||
@@ -4425,10 +4324,6 @@ title = "Login-tilstand påkrævet"
|
||||
message = "Login-tilstand skal være aktiveret for at ændre adminindstillinger. Angiv SECURITY_ENABLELOGIN=true i dit miljø eller security.enableLogin: true i settings.yml, og genstart derefter serveren."
|
||||
readOnly = "Indstillingerne nedenfor viser eksempelværdier til reference. Aktivér login-tilstand for at se og redigere den faktiske konfiguration."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "Enterprise-licens påkrævet"
|
||||
message = "En Enterprise-licens er påkrævet for at få adgang til {{featureName}}. Du ser demodata til reference."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "Genstart påkrævet"
|
||||
message = "Indstillingerne er gemt. En servergenstart er påkrævet for at ændringerne træder i kraft."
|
||||
@@ -4568,13 +4463,6 @@ description = "Maksimalt antal mislykkede loginforsøg før kontolock"
|
||||
label = "Nulstil login (minutter)"
|
||||
description = "Tid før mislykkede loginforsøg nulstilles"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Deaktivér CSRF-beskyttelse"
|
||||
description = "Deaktivér Cross-Site Request Forgery-beskyttelse (anbefales ikke)"
|
||||
@@ -4883,48 +4771,6 @@ description = "Vis detaljerede fejlmeddelelser for brugere."
|
||||
title = "Database"
|
||||
description = "Konfigurer brugerdefinerede databaseforbindelser til virksomhedsinstallationer."
|
||||
configuration = "Databasekonfiguration"
|
||||
backupTitle = "Sikkerhedskopier & gendannelse"
|
||||
backupDescription = "Administrer H2-sikkerhedskopier direkte fra administratorkonsollen."
|
||||
loadError = "Kunne ikke indlæse database-sikkerhedskopier"
|
||||
backupCreated = "Sikkerhedskopi oprettet"
|
||||
backupFailed = "Kunne ikke oprette sikkerhedskopi"
|
||||
deleteTitle = "Slet sikkerhedskopi"
|
||||
deleteConfirm = "Slet denne sikkerhedskopi? Dette kan ikke fortrydes."
|
||||
deleteSuccess = "Sikkerhedskopi slettet"
|
||||
deleteFailed = "Kunne ikke slette sikkerhedskopi"
|
||||
deleteConfirmAction = "Slet sikkerhedskopi"
|
||||
downloadFailed = "Kunne ikke downloade sikkerhedskopi"
|
||||
version = "H2-version"
|
||||
embedded = "Indlejret H2"
|
||||
external = "Ekstern database"
|
||||
h2Only = "Sikkerhedskopier er kun tilgængelige for den indlejrede H2-database."
|
||||
h2Hint = "Sæt databasetypen til H2 og deaktivér brugerdefineret database for at aktivere sikkerhedskopiering og gendannelse."
|
||||
manageBackups = "Administrer sikkerhedskopier"
|
||||
refresh = "Opdater"
|
||||
createBackup = "Opret sikkerhedskopi"
|
||||
uploadTitle = "Upload & import"
|
||||
chooseFile = "Vælg en .sql-sikkerhedskopifil"
|
||||
importFromUpload = "Importer upload"
|
||||
confirmImportTitle = "Bekræft databaseimport"
|
||||
overwriteWarning = "Advarsel: Dette vil overskrive den aktuelle database."
|
||||
overwriteWarningBody = "Alle eksisterende data vil blive erstattet af den uploadede sikkerhedskopi. Denne handling kan ikke fortrydes."
|
||||
confirmCodeLabel = "Indtast bekræftelseskoden for at fortsætte"
|
||||
enterCode = "Indtast koden vist ovenfor"
|
||||
confirmImport = "Bekræft import"
|
||||
codeMismatch = "Bekræftelseskoden matcher ikke"
|
||||
codeMismatchBody = "Indtast koden præcis som vist for at fortsætte."
|
||||
selectFile = "Vælg en .sql-fil til import"
|
||||
importSuccess = "Sikkerhedskopi importeret"
|
||||
importFailed = "Kunne ikke importere sikkerhedskopi"
|
||||
noBackups = "Ingen sikkerhedskopier fundet endnu."
|
||||
unavailable = "Liste over sikkerhedskopier er ikke tilgængelig for den aktuelle databasekonfiguration."
|
||||
fileName = "Fil"
|
||||
created = "Oprettet"
|
||||
size = "Størrelse"
|
||||
actions = "Handlinger"
|
||||
download = "Download"
|
||||
import = "Importer"
|
||||
delete = "Slet"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "Aktivér brugerdefineret database"
|
||||
@@ -5002,26 +4848,6 @@ description = "Maksimal DPI for billedbehandling (0 = ubegrænset)"
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "Tessdata-mappe"
|
||||
description = "Sti til tessdata-mappen for OCR-sprogfiler"
|
||||
downloadMissingTitle = "Intet sprog valgt"
|
||||
downloadMissingBody = "Vælg mindst ét sprog til download."
|
||||
downloadSuccessTitle = "Sprog downloadet"
|
||||
downloadSuccessBody = "De valgte tessdata-sprog er blevet gemt."
|
||||
downloadErrorTitle = "Download mislykkedes"
|
||||
loadingLanguages = "Indlæser installerede tessdata-sprog..."
|
||||
installedLanguages = "Installerede tessdata-sprog"
|
||||
noLanguages = "Ingen tessdata-sprog fundet i den konfigurerede mappe."
|
||||
downloadLabel = "Download yderligere tessdata-sprog"
|
||||
downloadPlaceholder = "Vælg sprog"
|
||||
downloadNothingFound = "Ingen yderligere sprog fundet"
|
||||
permissionNotice = "Stien til tessdata er ikke skrivbar. Downloads åbnes i browseren; gem venligst .traineddata-filerne manuelt i tessdata-mappen."
|
||||
manualLinks = "Manuelle downloads: klik på linkene og placer filerne i tessdata-mappen."
|
||||
downloadButton = "Download valgte sprog"
|
||||
downloadInvalidTitle = "Ugyldigt valg"
|
||||
downloadInvalidBody = "Nogle valgte sprog er ikke tilgængelige til download. Opdater venligst og vælg fra listen."
|
||||
downloadErrorNetwork = "Download mislykkedes på grund af en netværksfejl. Kontroller din forbindelse og prøv igen."
|
||||
downloadErrorServer = "Serveren stødte på en fejl under download af tessdata-sprog. Prøv igen senere."
|
||||
downloadErrorPermission = "Tessdata-mappen er ikke skrivbar: {{message}}. Vælg en skrivbar mappe (f.eks. under programmets data-mappe), eller juster tilladelserne."
|
||||
downloadErrorGeneric = "Download mislykkedes: {{message}}. Prøv igen senere."
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "Deaktivér HTML-sanitization"
|
||||
@@ -5525,7 +5351,6 @@ desc = "Byg flertrins arbejdsgange ved at kæde PDF-handlinger sammen. Ideelt ti
|
||||
invalidStep = "Ugyldigt trin"
|
||||
reviewTitle = "Resultater af automatisering"
|
||||
copyToSaved = "Kopiér til gemte"
|
||||
exportForFolderScanning = "Eksporter til mappeovervågning"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "Vælg filer, der skal behandles med denne automatisering"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "Opret automatisering"
|
||||
editTitle = "Rediger automatisering"
|
||||
intro = "Automatiseringer kører værktøjer sekventielt. For at komme i gang skal du tilføje værktøjer i den rækkefølge, du vil have dem til at køre."
|
||||
save = "Gem automatisering"
|
||||
exportForFolderScanning = "Eksporter til mappeovervågning"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "Navn på automatisering"
|
||||
@@ -5922,11 +5746,9 @@ username = "Brugernavn (e-mail)"
|
||||
usernamePlaceholder = "user@example.com"
|
||||
password = "Adgangskode"
|
||||
passwordPlaceholder = "Indtast adgangskode"
|
||||
passwordRequired = "Adgangskode er påkrævet"
|
||||
role = "Rolle"
|
||||
team = "Team (valgfrit)"
|
||||
teamPlaceholder = "Vælg et team"
|
||||
authType = "Godkendelsestype"
|
||||
forcePasswordChange = "Gennemtving adgangskodeskift ved første login"
|
||||
cancel = "Annullér"
|
||||
submit = "Tilføj medlem"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "Brugernavn og adgangskode er påkrævet"
|
||||
passwordTooShort = "Adgangskoden skal være mindst 6 tegn"
|
||||
success = "Bruger oprettet"
|
||||
error = "Kunne ikke oprette bruger"
|
||||
forceMFA = "Gennemtving MFA-opsætning ved næste login"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "Adgangskode"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "Brugeren godkendes via SSO-udbyder"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "Redigér medlem"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "{{count}} ledig(e) brugerplads(er)"
|
||||
noSlotsAvailable = "Ingen pladser tilgængelige"
|
||||
currentUsage = "Bruger i øjeblikket {{current}} af {{max}} brugerlicenser"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "MFA blev deaktiveret for brugeren"
|
||||
adminDisableError = "Kunne ikke deaktivere MFA for brugeren"
|
||||
disableByAdmin = "Deaktivér MFA"
|
||||
|
||||
[workspace.teams]
|
||||
title = "Teams"
|
||||
description = "Administrér teams og organisér medlemmer i arbejdsområdet"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "Endpoint"
|
||||
visits = "Besøg"
|
||||
percentage = "Procent"
|
||||
noData = "Ingen data tilgængelige"
|
||||
unknownEndpoint = "Ukendt endepunkt"
|
||||
|
||||
[backendHealth]
|
||||
checking = "Kontrollerer backend-status..."
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "Vorgang auswählen"
|
||||
addOperationButton = "Vorgang hinzufügen"
|
||||
pipelineHeader = "Pipeline:"
|
||||
saveButton = "Herunterladen"
|
||||
saveForFolderScanning = "Für Ordnerscan speichern"
|
||||
validateButton = "Validieren"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "Vollbild"
|
||||
[settings.general.updates]
|
||||
title = "Software-Updates"
|
||||
description = "Nach Updates suchen und Versionsinformationen anzeigen"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "Aktuelle Version"
|
||||
latestVersion = "Neueste Version"
|
||||
checkForUpdates = "Nach Updates suchen"
|
||||
viewDetails = "Details anzeigen"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "Webbrowser-Einstellung"
|
||||
syncToBrowser = "Konto → Browser synchronisieren"
|
||||
syncToAccount = "Konto ← Browser synchronisieren"
|
||||
|
||||
[account.mfa]
|
||||
title = "Zwei-Faktor-Authentifizierung"
|
||||
setupFailed = "Zwei-Faktor-Einrichtung konnte nicht gestartet werden. Bitte versuchen Sie es erneut."
|
||||
codeRequired = "Geben Sie zur Fortsetzung den Authentifizierungscode ein."
|
||||
enabled = "Zwei-Faktor-Authentifizierung aktiviert."
|
||||
enableFailed = "Zwei-Faktor-Authentifizierung konnte nicht aktiviert werden. Bitte prüfen Sie den Code und versuchen Sie es erneut."
|
||||
disabled = "Zwei-Faktor-Authentifizierung deaktiviert."
|
||||
disableFailed = "Zwei-Faktor-Authentifizierung konnte nicht deaktiviert werden. Bitte prüfen Sie den Code und versuchen Sie es erneut."
|
||||
description = "Fügen Sie Ihrem Konto eine zusätzliche Sicherheitsebene hinzu."
|
||||
enableButton = "Zwei-Faktor-Authentifizierung aktivieren"
|
||||
disableButton = "Zwei-Faktor-Authentifizierung deaktivieren"
|
||||
setupTitle = "Zwei-Faktor-Authentifizierung einrichten"
|
||||
setupDescription = "Scannen Sie den QR-Code mit Ihrer Authenticator-App und geben Sie anschließend den 6-stelligen Code zur Bestätigung ein."
|
||||
manualKey = "Manueller Einrichtungsschlüssel"
|
||||
secretWarning = "Bewahren Sie diesen Schlüssel geheim auf. Jeder mit Zugriff kann gültige Authentifizierungscodes generieren."
|
||||
codePlaceholder = "6-stelligen Code eingeben"
|
||||
confirmEnable = "Aktivieren"
|
||||
disableTitle = "Zwei-Faktor-Authentifizierung deaktivieren"
|
||||
disableDescription = "Geben Sie einen gültigen Authentifizierungscode ein, um die Zwei-Faktor-Authentifizierung zu deaktivieren."
|
||||
codeLabel = "Authentifizierungscode"
|
||||
confirmDisable = "Deaktivieren"
|
||||
ssoDescription = "Für Single Sign-On-Konten wird die Zwei-Faktor-Authentifizierung von Ihrem Identitätsanbieter verwaltet."
|
||||
ssoManaged = "Konfigurieren Sie MFA über Ihren Identitätsanbieter."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "Benutzerkontrolle"
|
||||
header = "Administrator-Benutzerkontrolle"
|
||||
@@ -952,7 +925,6 @@ title = "Teilen nach Größe/Anzahl"
|
||||
desc = "Teilen Sie ein einzelnes PDF basierend auf Größe, Seitenanzahl oder Dokumentanzahl in mehrere Dokumente auf"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Farbe ersetzen & invertieren"
|
||||
desc = "Farben in PDF-Dokumenten ersetzen oder invertieren"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "Autom. Ordner-Scan"
|
||||
desc = "Link zum Leitfaden für automatisches Ordner-Scannen"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO-Anleitung"
|
||||
desc = "Link zum SSO-Leitfaden"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Offline-Setup"
|
||||
desc = "Link zum Air-Gap-Einrichtungsleitfaden"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Passwort hinzufügen"
|
||||
desc = "Das PDF mit einem Passwort verschlüsseln"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Berechtigungen ändern"
|
||||
desc = "Dokumentbeschränkungen und -berechtigungen ändern"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "Automatisieren"
|
||||
desc = "Mehrstufige Arbeitsabläufe durch Verkettung von PDF-Aktionen erstellen. Ideal für wiederkehrende Aufgaben."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Ein PDF über ein anderes legen"
|
||||
title = "PDFs überlagern"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF-Texteditor"
|
||||
desc = "Vorhandenen Text und Bilder in PDFs bearbeiten"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "HTML-Zwischendatei anstatt PDF herunterladen"
|
||||
pdfaOptions = "PDF/A-Optionen"
|
||||
outputFormat = "Ausgabeformat"
|
||||
pdfaNote = "PDF/A-1b ist kompatibler, PDF/A-2b unterstützt mehr Funktionen."
|
||||
pdfaFormat = "PDF/A-Format"
|
||||
pdfaDigitalSignatureWarning = "Das PDF enthält eine digitale Signatur. Sie wird im nächsten Schritt entfernt."
|
||||
pdfxDigitalSignatureWarning = "Das PDF enthält eine digitale Signatur. Diese wird im nächsten Schritt entfernt."
|
||||
pdfxDescription = "PDF/X ist eine ISO-Standard-PDF-Teilmenge für zuverlässigen Druck und Grafikaustausch."
|
||||
fileFormat = "Dateiformat"
|
||||
wordDoc = "Word-Dokument"
|
||||
wordDocExt = "Word-Dokument (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "Dieser Dienst kann nur einzelne Eingangsdateien verarbeiten."
|
||||
outputFormat = "Ausgabeformat"
|
||||
pdfWithDigitalSignature = "Das PDF enthält eine digitale Signatur. Sie wird im nächsten Schritt entfernt."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "druck,standard,konvertierung,produktion,vorstufe,archiv"
|
||||
title = "PDF in PDF/X"
|
||||
header = "PDF in PDF/X"
|
||||
credit = "Dieser Dienst verwendet Ghostscript für die PDF/X-Konvertierung"
|
||||
submit = "Konvertieren"
|
||||
tip = "Funktioniert derzeit nicht für mehrere Eingaben gleichzeitig"
|
||||
outputFormat = "Ausgabeformat"
|
||||
pdfWithDigitalSignature = "Das PDF enthält eine digitale Signatur. Diese wird im nächsten Schritt entfernt."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc,docx,odt,word,transformation,format,konvertierung,office,microsoft,docfile"
|
||||
title = "PDF zu Word"
|
||||
@@ -3200,18 +3153,6 @@ other = "Sonstiges"
|
||||
perPageInfo = "Informationen pro Seite"
|
||||
tableOfContents = "Inhaltsverzeichnis"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "Nicht erkannt"
|
||||
passed = "Bestanden"
|
||||
failed = "Fehlgeschlagen"
|
||||
compliant = "Konform"
|
||||
nonCompliant = "Nicht konform"
|
||||
none = "Keine Standards erkannt"
|
||||
passedCount = "bestanden"
|
||||
failedCount = "fehlgeschlagen"
|
||||
noVerification = "Keine Überprüfung durchgeführt"
|
||||
noVerificationDesc = "Die Konformität mit PDF-Standards wurde für dieses Dokument nicht überprüft."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "Anhänge"
|
||||
embeddedFiles = "Eingebettete Dateien"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} Einschränkungen"
|
||||
permsMixed = "Einige Berechtigungen eingeschränkt"
|
||||
hasCompliance = "Entspricht Compliance-Standards"
|
||||
noCompliance = "Keine Compliance-Standards"
|
||||
compliancePassed = "Mit {{standards}} konform"
|
||||
complianceChecked = "Standards überprüft ({{failed}} fehlgeschlagen)"
|
||||
basic = "Grundlegende Informationen"
|
||||
documentInfo = "Dokumentinformationen"
|
||||
securityTitle = "Sicherheitsstatus"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "Bildgröße"
|
||||
margin = "Rand"
|
||||
positionAndFormatting = "Position & Formatierung"
|
||||
quickPosition = "Wählen Sie eine Position auf der Seite, um den Stempel zu platzieren."
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "Beim Hinzufügen des Stempels zum PDF ist ein Fehler aufgetreten."
|
||||
@@ -3594,14 +3508,6 @@ failed = "Beim Hinzufügen des Stempels zum PDF ist ein Fehler aufgetreten."
|
||||
[AddStampRequest.results]
|
||||
title = "Stempel-Ergebnisse"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "bild entfernen,seitenoperationen,back end,server side"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "Passwort erfolgreich geändert! Bitte melden Sie sich
|
||||
credentialsUpdated = "Ihre Anmeldedaten wurden aktualisiert. Bitte melden Sie sich erneut an."
|
||||
defaultCredentials = "Standard-Login-Daten"
|
||||
changePasswordWarning = "Bitte ändern Sie Ihr Passwort nach dem ersten Login."
|
||||
mfaRequired = "Zwei-Faktor-Code erforderlich"
|
||||
mfaCode = "Authentifizierungscode"
|
||||
enterMfaCode = "6-stelligen Code eingeben"
|
||||
mfaPromptTitle = "Zwei-Faktor-Authentifizierung"
|
||||
mfaPromptBody = "Geben Sie den Authentifizierungscode aus Ihrer Authenticator-App ein, um fortzufahren."
|
||||
verifyingMfa = "Wird überprüft..."
|
||||
verifyMfa = "Code überprüfen"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "Stirling PDF Übersicht"
|
||||
@@ -4425,10 +4324,6 @@ title = "Anmeldemodus erforderlich"
|
||||
message = "Der Anmeldemodus muss aktiviert sein, um Admin-Einstellungen zu ändern. Bitte setzen Sie SECURITY_ENABLELOGIN=true in Ihrer Umgebung oder security.enableLogin: true in settings.yml und starten Sie dann den Server neu."
|
||||
readOnly = "Die folgenden Einstellungen zeigen Beispielwerte als Referenz. Aktivieren Sie den Anmeldemodus, um die tatsächliche Konfiguration anzuzeigen und zu bearbeiten."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "Enterprise-Lizenz erforderlich"
|
||||
message = "Für den Zugriff auf {{featureName}} ist eine Enterprise-Lizenz erforderlich. Sie sehen zu Referenzzwecken Demodaten."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "Neustart erforderlich"
|
||||
message = "Einstellungen wurden erfolgreich gespeichert. Ein Serverneustart ist erforderlich, damit die Änderungen wirksam werden."
|
||||
@@ -4568,13 +4463,6 @@ description = "Maximale Anzahl fehlgeschlagener Anmeldeversuche vor Kontosperre"
|
||||
label = "Zurücksetzungszeit für Anmeldungen (Minuten)"
|
||||
description = "Zeit, nach der fehlgeschlagene Anmeldeversuche zurückgesetzt werden"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF-Schutz deaktivieren"
|
||||
description = "Cross-Site Request Forgery-Schutz deaktivieren (nicht empfohlen)"
|
||||
@@ -4883,48 +4771,6 @@ description = "Show detailed error messages for users."
|
||||
title = "Datenbank"
|
||||
description = "Benutzerdefinierte Datenbankverbindungseinstellungen für Enterprise-Bereitstellungen konfigurieren."
|
||||
configuration = "Datenbankkonfiguration"
|
||||
backupTitle = "Backups & Wiederherstellung"
|
||||
backupDescription = "H2-Backups direkt über die Admin-Konsole verwalten."
|
||||
loadError = "Datenbank-Backups konnten nicht geladen werden"
|
||||
backupCreated = "Backup erfolgreich erstellt"
|
||||
backupFailed = "Erstellen des Backups fehlgeschlagen"
|
||||
deleteTitle = "Backup löschen"
|
||||
deleteConfirm = "Dieses Backup löschen? Dies kann nicht rückgängig gemacht werden."
|
||||
deleteSuccess = "Backup gelöscht"
|
||||
deleteFailed = "Löschen des Backups fehlgeschlagen"
|
||||
deleteConfirmAction = "Backup löschen"
|
||||
downloadFailed = "Herunterladen des Backups fehlgeschlagen"
|
||||
version = "H2-Version"
|
||||
embedded = "Eingebettetes H2"
|
||||
external = "Externe DB"
|
||||
h2Only = "Backups sind nur für die eingebettete H2-Datenbank verfügbar."
|
||||
h2Hint = "Setzen Sie den Datenbanktyp auf H2 und deaktivieren Sie die benutzerdefinierte Datenbank, um Backup und Wiederherstellung zu aktivieren."
|
||||
manageBackups = "Backups verwalten"
|
||||
refresh = "Aktualisieren"
|
||||
createBackup = "Backup erstellen"
|
||||
uploadTitle = "Hochladen & Importieren"
|
||||
chooseFile = "Eine .sql-Backup-Datei auswählen"
|
||||
importFromUpload = "Upload importieren"
|
||||
confirmImportTitle = "Datenbankimport bestätigen"
|
||||
overwriteWarning = "Warnung: Dies überschreibt die aktuelle Datenbank."
|
||||
overwriteWarningBody = "Alle vorhandenen Daten werden durch das hochgeladene Backup ersetzt. Diese Aktion kann nicht rückgängig gemacht werden."
|
||||
confirmCodeLabel = "Zum Fortfahren den Bestätigungscode eingeben"
|
||||
enterCode = "Den oben angezeigten Code eingeben"
|
||||
confirmImport = "Import bestätigen"
|
||||
codeMismatch = "Bestätigungscode stimmt nicht überein"
|
||||
codeMismatchBody = "Bitte den Code exakt wie angezeigt eingeben, um fortzufahren."
|
||||
selectFile = "Bitte eine .sql-Datei zum Importieren auswählen"
|
||||
importSuccess = "Backup erfolgreich importiert"
|
||||
importFailed = "Import des Backups fehlgeschlagen"
|
||||
noBackups = "Noch keine Backups gefunden."
|
||||
unavailable = "Die Backupliste ist für die aktuelle Datenbankkonfiguration nicht verfügbar."
|
||||
fileName = "Datei"
|
||||
created = "Erstellt"
|
||||
size = "Größe"
|
||||
actions = "Aktionen"
|
||||
download = "Herunterladen"
|
||||
import = "Importieren"
|
||||
delete = "Löschen"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "Benutzerdefinierte Datenbank aktivieren"
|
||||
@@ -5002,26 +4848,6 @@ description = "Maximale DPI für Bildverarbeitung (0 = unbegrenzt)"
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "Tessdata-Verzeichnis"
|
||||
description = "Pfad zum tessdata-Verzeichnis für OCR-Sprachdateien"
|
||||
downloadMissingTitle = "Keine Sprache ausgewählt"
|
||||
downloadMissingBody = "Bitte mindestens eine Sprache zum Herunterladen auswählen."
|
||||
downloadSuccessTitle = "Sprachen heruntergeladen"
|
||||
downloadSuccessBody = "Die ausgewählten tessdata-Sprachen wurden gespeichert."
|
||||
downloadErrorTitle = "Download fehlgeschlagen"
|
||||
loadingLanguages = "Installierte tessdata-Sprachen werden geladen..."
|
||||
installedLanguages = "Installierte tessdata-Sprachen"
|
||||
noLanguages = "Keine tessdata-Sprachen im konfigurierten Verzeichnis gefunden."
|
||||
downloadLabel = "Zusätzliche tessdata-Sprachen herunterladen"
|
||||
downloadPlaceholder = "Sprachen auswählen"
|
||||
downloadNothingFound = "Keine zusätzlichen Sprachen gefunden"
|
||||
permissionNotice = "Der tessdata-Pfad ist nicht beschreibbar. Downloads werden im Browser geöffnet; bitte die .traineddata-Dateien manuell in den tessdata-Ordner speichern."
|
||||
manualLinks = "Manuelle Downloads: Links anklicken und die Dateien in den tessdata-Ordner ablegen."
|
||||
downloadButton = "Ausgewählte Sprachen herunterladen"
|
||||
downloadInvalidTitle = "Ungültige Auswahl"
|
||||
downloadInvalidBody = "Einige ausgewählte Sprachen sind nicht zum Download verfügbar. Bitte aktualisieren und aus der Liste auswählen."
|
||||
downloadErrorNetwork = "Download aufgrund eines Netzwerkfehlers fehlgeschlagen. Bitte die Verbindung prüfen und erneut versuchen."
|
||||
downloadErrorServer = "Beim Herunterladen von tessdata-Sprachen ist ein Serverfehler aufgetreten. Bitte später erneut versuchen."
|
||||
downloadErrorPermission = "Tessdata-Verzeichnis ist nicht beschreibbar: {{message}}. Bitte ein beschreibbares Verzeichnis wählen (z. B. im Anwendungsdaten-Ordner) oder Berechtigungen anpassen."
|
||||
downloadErrorGeneric = "Download fehlgeschlagen: {{message}}. Bitte später erneut versuchen."
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "HTML-Bereinigung deaktivieren"
|
||||
@@ -5525,7 +5351,6 @@ desc = "Mehrstufige Arbeitsabläufe durch Verkettung von PDF-Aktionen erstellen.
|
||||
invalidStep = "Ungültiger Schritt"
|
||||
reviewTitle = "Automatisierungsergebnisse"
|
||||
copyToSaved = "In gespeicherte kopieren"
|
||||
exportForFolderScanning = "Für Ordnerscan exportieren"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "Wählen Sie Dateien aus, die mit dieser Automatisierung verarbeitet werden sollen"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "Automatisierung erstellen"
|
||||
editTitle = "Automatisierung bearbeiten"
|
||||
intro = "Automatisierungen führen Werkzeuge sequenziell aus. Fügen Sie Werkzeuge in der gewünschten Reihenfolge hinzu, um zu beginnen."
|
||||
save = "Automatisierung speichern"
|
||||
exportForFolderScanning = "Für Ordnerscan exportieren"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "Name der Automatisierung"
|
||||
@@ -5922,11 +5746,9 @@ username = "Benutzername (E-Mail)"
|
||||
usernamePlaceholder = "user@example.com"
|
||||
password = "Passwort"
|
||||
passwordPlaceholder = "Passwort eingeben"
|
||||
passwordRequired = "Passwort ist erforderlich"
|
||||
role = "Rolle"
|
||||
team = "Team (optional)"
|
||||
teamPlaceholder = "Team auswählen"
|
||||
authType = "Authentifizierungstyp"
|
||||
forcePasswordChange = "Passwortänderung bei erster Anmeldung erzwingen"
|
||||
cancel = "Abbrechen"
|
||||
submit = "Mitglied hinzufügen"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "Benutzername und Passwort sind erforderlich"
|
||||
passwordTooShort = "Passwort muss mindestens 6 Zeichen lang sein"
|
||||
success = "Benutzer erfolgreich erstellt"
|
||||
error = "Benutzer konnte nicht erstellt werden"
|
||||
forceMFA = "MFA-Einrichtung bei der nächsten Anmeldung erzwingen"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "Passwort"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "Der Benutzer authentifiziert sich über den SSO-Anbieter"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "Mitglied bearbeiten"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "{{count}} Benutzer-Slot(s) verfügbar"
|
||||
noSlotsAvailable = "Keine Slots verfügbar"
|
||||
currentUsage = "Derzeit werden {{current}} von {{max}} Benutzerlizenzen verwendet"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "MFA für Benutzer erfolgreich deaktiviert"
|
||||
adminDisableError = "MFA konnte für Benutzer nicht deaktiviert werden"
|
||||
disableByAdmin = "MFA deaktivieren"
|
||||
|
||||
[workspace.teams]
|
||||
title = "Teams"
|
||||
description = "Teams verwalten und Mitglieder des Arbeitsbereichs organisieren"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "Endpunkt"
|
||||
visits = "Aufrufe"
|
||||
percentage = "Prozentsatz"
|
||||
noData = "Keine Daten verfügbar"
|
||||
unknownEndpoint = "Unbekannter Endpunkt"
|
||||
|
||||
[backendHealth]
|
||||
checking = "Backend-Status wird geprüft..."
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "Επιλογή λειτουργίας"
|
||||
addOperationButton = "Προσθήκη λειτουργίας"
|
||||
pipelineHeader = "Pipeline:"
|
||||
saveButton = "Λήψη"
|
||||
saveForFolderScanning = "Αποθήκευση για σάρωση φακέλων"
|
||||
validateButton = "Επικύρωση"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "Πλήρης οθόνη"
|
||||
[settings.general.updates]
|
||||
title = "Ενημερώσεις λογισμικού"
|
||||
description = "Έλεγχος ενημερώσεων και προβολή πληροφοριών έκδοσης"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "Τρέχουσα έκδοση"
|
||||
latestVersion = "Νεότερη έκδοση"
|
||||
checkForUpdates = "Έλεγχος για ενημερώσεις"
|
||||
viewDetails = "Προβολή λεπτομερειών"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "Ρύθμιση περιηγητή"
|
||||
syncToBrowser = "Συγχρονισμός λογαριασμού -> περιηγητή"
|
||||
syncToAccount = "Συγχρονισμός λογαριασμού <- περιηγητή"
|
||||
|
||||
[account.mfa]
|
||||
title = "Έλεγχος ταυτότητας δύο παραγόντων"
|
||||
setupFailed = "Δεν ήταν δυνατή η έναρξη ρύθμισης ελέγχου ταυτότητας δύο παραγόντων. Προσπαθήστε ξανά."
|
||||
codeRequired = "Εισαγάγετε τον κωδικό ελέγχου ταυτότητας για να συνεχίσετε."
|
||||
enabled = "Ο έλεγχος ταυτότητας δύο παραγόντων ενεργοποιήθηκε."
|
||||
enableFailed = "Δεν ήταν δυνατή η ενεργοποίηση του ελέγχου ταυτότητας δύο παραγόντων. Ελέγξτε τον κωδικό και προσπαθήστε ξανά."
|
||||
disabled = "Ο έλεγχος ταυτότητας δύο παραγόντων απενεργοποιήθηκε."
|
||||
disableFailed = "Δεν ήταν δυνατή η απενεργοποίηση του ελέγχου ταυτότητας δύο παραγόντων. Ελέγξτε τον κωδικό και προσπαθήστε ξανά."
|
||||
description = "Προσθέστε ένα επιπλέον επίπεδο ασφάλειας στον λογαριασμό σας."
|
||||
enableButton = "Ενεργοποίηση ελέγχου ταυτότητας δύο παραγόντων"
|
||||
disableButton = "Απενεργοποίηση ελέγχου ταυτότητας δύο παραγόντων"
|
||||
setupTitle = "Ρύθμιση ελέγχου ταυτότητας δύο παραγόντων"
|
||||
setupDescription = "Σαρώστε τον κωδικό QR με την εφαρμογή ελέγχου ταυτότητας και, στη συνέχεια, εισαγάγετε τον 6ψήφιο κωδικό για επιβεβαίωση."
|
||||
manualKey = "Χειροκίνητο κλειδί ρύθμισης"
|
||||
secretWarning = "Κρατήστε αυτό το κλειδί ιδιωτικό. Όποιος έχει πρόσβαση μπορεί να δημιουργήσει έγκυρους κωδικούς ελέγχου ταυτότητας."
|
||||
codePlaceholder = "Εισαγάγετε 6ψήφιο κωδικό"
|
||||
confirmEnable = "Ενεργοποίηση"
|
||||
disableTitle = "Απενεργοποίηση ελέγχου ταυτότητας δύο παραγόντων"
|
||||
disableDescription = "Εισαγάγετε έναν έγκυρο κωδικό ελέγχου ταυτότητας για να απενεργοποιήσετε τον έλεγχο ταυτότητας δύο παραγόντων."
|
||||
codeLabel = "Κωδικός ελέγχου ταυτότητας"
|
||||
confirmDisable = "Απενεργοποίηση"
|
||||
ssoDescription = "Ο έλεγχος ταυτότητας δύο παραγόντων διαχειρίζεται από τον πάροχο ταυτότητάς σας για λογαριασμούς ενιαίας σύνδεσης."
|
||||
ssoManaged = "Ρυθμίστε το MFA μέσω του παρόχου ταυτότητάς σας."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "Ρυθμίσεις ελέγχου χρήστη"
|
||||
header = "Ρυθμίσεις ελέγχου διαχειριστή χρήστη"
|
||||
@@ -952,7 +925,6 @@ title = "Αυτόματο σπάσιμο με μέγεθος/σελ."
|
||||
desc = "Διαχωρισμός ενός PDF σε πολλαπλά έγγραφα βάσει μεγέθους, αριθμού σελίδων ή αριθμού εγγράφων"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Αλλαγή & αντιστροφή χρώματος"
|
||||
desc = "Αντικαταστήστε ή αντιστρέψτε χρώματα σε έγγραφα PDF"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "Αυτόματη σάρωση φακέλων"
|
||||
desc = "Σύνδεσμος προς τον οδηγό αυτοματοποιημένης σάρωσης φακέλων"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Οδηγός SSO"
|
||||
desc = "Σύνδεσμος προς τον οδηγό SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Ρύθμιση Air-gapped"
|
||||
desc = "Σύνδεσμος προς τον οδηγό ρύθμισης Air-gapped"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Προσθήκη κωδικού"
|
||||
desc = "Κρυπτογράφηση του εγγράφου PDF με κωδικό."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Αλλαγή δικαιωμάτων"
|
||||
desc = "Αλλαγή περιορισμών και δικαιωμάτων εγγράφου"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "Αυτοματοποίηση"
|
||||
desc = "Δημιουργήστε ροές πολλών βημάτων συνδέοντας ενέργειες PDF. Ιδανικό για επαναλαμβανόμενες εργασίες."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Επικάλυψη PDF πάνω σε άλλο PDF"
|
||||
title = "Επικάλυψη PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Επεξεργαστής κειμένου PDF"
|
||||
desc = "Επιθεωρήστε και επεξεργαστείτε εξαγωγές JSON του Stirling PDF με ομαδοποιημένη επεξεργασία κειμένου και αναδημιουργία PDF"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "Λήψη ενδιάμεσου αρχείου HTML αντί γι
|
||||
pdfaOptions = "Επιλογές PDF/A"
|
||||
outputFormat = "Μορφή εξόδου"
|
||||
pdfaNote = "Το PDF/A-1b είναι πιο συμβατό, το PDF/A-2b υποστηρίζει περισσότερες δυνατότητες."
|
||||
pdfaFormat = "Μορφή PDF/A"
|
||||
pdfaDigitalSignatureWarning = "Το PDF περιέχει ψηφιακή υπογραφή. Αυτή θα αφαιρεθεί στο επόμενο βήμα."
|
||||
pdfxDigitalSignatureWarning = "Το PDF περιέχει ψηφιακή υπογραφή. Αυτή θα αφαιρεθεί στο επόμενο βήμα."
|
||||
pdfxDescription = "Το PDF/X είναι ένα υποσύνολο PDF προτύπου ISO για αξιόπιστη εκτύπωση και ανταλλαγή γραφικών."
|
||||
fileFormat = "Μορφή αρχείου"
|
||||
wordDoc = "Έγγραφο Word"
|
||||
wordDocExt = "Έγγραφο Word (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "Προς το παρόν δεν λειτουργεί για πολλαπ
|
||||
outputFormat = "Μορφή εξόδου"
|
||||
pdfWithDigitalSignature = "Το PDF περιέχει ψηφιακή υπογραφή. Αυτή θα αφαιρεθεί στο επόμενο βήμα."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "εκτύπωση,πρότυπο,μετατροπή,παραγωγή,προεκτύπωση,αρχειοθέτηση"
|
||||
title = "PDF σε PDF/X"
|
||||
header = "PDF σε PDF/X"
|
||||
credit = "Αυτή η υπηρεσία χρησιμοποιεί Ghostscript για τη μετατροπή σε PDF/X"
|
||||
submit = "Μετατροπή"
|
||||
tip = "Προς το παρόν δεν λειτουργεί για πολλαπλές εισόδους ταυτόχρονα"
|
||||
outputFormat = "Μορφή εξόδου"
|
||||
pdfWithDigitalSignature = "Το PDF περιέχει ψηφιακή υπογραφή. Αυτή θα αφαιρεθεί στο επόμενο βήμα."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc,docx,odt,word,μετατροπή,μορφή,μετατροπή,γραφείο,microsoft,docfile"
|
||||
title = "PDF σε Word"
|
||||
@@ -3200,18 +3153,6 @@ other = "Άλλα"
|
||||
perPageInfo = "Πληροφορίες ανά σελίδα"
|
||||
tableOfContents = "Πίνακας περιεχομένων"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "Δεν εντοπίστηκε"
|
||||
passed = "Επιτυχής"
|
||||
failed = "Απέτυχε"
|
||||
compliant = "Συμβατό"
|
||||
nonCompliant = "Μη συμβατό"
|
||||
none = "Δεν εντοπίστηκαν πρότυπα"
|
||||
passedCount = "επιτυχίες"
|
||||
failedCount = "αποτυχίες"
|
||||
noVerification = "Δεν πραγματοποιήθηκε επαλήθευση"
|
||||
noVerificationDesc = "Η συμμόρφωση με τα πρότυπα PDF δεν επαληθεύτηκε για αυτό το έγγραφο."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "Συνημμένα"
|
||||
embeddedFiles = "Ενσωματωμένα αρχεία"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} περιορισμοί"
|
||||
permsMixed = "Ορισμένα δικαιώματα είναι περιορισμένα"
|
||||
hasCompliance = "Διαθέτει πρότυπα συμμόρφωσης"
|
||||
noCompliance = "Χωρίς πρότυπα συμμόρφωσης"
|
||||
compliancePassed = "Συμβατό με {{standards}}"
|
||||
complianceChecked = "Έγινε επαλήθευση προτύπων ({{failed}} απέτυχαν)"
|
||||
basic = "Βασικές πληροφορίες"
|
||||
documentInfo = "Πληροφορίες εγγράφου"
|
||||
securityTitle = "Κατάσταση ασφάλειας"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "Μέγεθος εικόνας"
|
||||
margin = "Περιθώριο"
|
||||
positionAndFormatting = "Θέση & μορφοποίηση"
|
||||
quickPosition = "Επιλέξτε μια θέση στη σελίδα για τοποθέτηση της σφραγίδας."
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "Παρουσιάστηκε σφάλμα κατά την προσθήκη σφραγίδας στο PDF."
|
||||
@@ -3594,14 +3508,6 @@ failed = "Παρουσιάστηκε σφάλμα κατά την προσθήκ
|
||||
[AddStampRequest.results]
|
||||
title = "Αποτελέσματα σφράγισης"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "αφαίρεση εικόνας,λειτουργίες σελίδας,backend,server side"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "Ο κωδικός άλλαξε με επιτυχία!
|
||||
credentialsUpdated = "Τα διαπιστευτήριά σας ενημερώθηκαν. Συνδεθείτε ξανά."
|
||||
defaultCredentials = "Προεπιλεγμένα διαπιστευτήρια σύνδεσης"
|
||||
changePasswordWarning = "Αλλάξτε τον κωδικό σας μετά την πρώτη σας σύνδεση"
|
||||
mfaRequired = "Απαιτείται κωδικός δύο παραγόντων"
|
||||
mfaCode = "Κωδικός ελέγχου ταυτότητας"
|
||||
enterMfaCode = "Εισαγάγετε 6ψήφιο κωδικό"
|
||||
mfaPromptTitle = "Έλεγχος ταυτότητας δύο παραγόντων"
|
||||
mfaPromptBody = "Εισαγάγετε τον κωδικό ελέγχου ταυτότητας από την εφαρμογή ελέγχου ταυτότητας για να συνεχίσετε."
|
||||
verifyingMfa = "Γίνεται επαλήθευση..."
|
||||
verifyMfa = "Επαλήθευση κωδικού"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "Επισκόπηση του Stirling PDF"
|
||||
@@ -4425,10 +4324,6 @@ title = "Απαιτείται λειτουργία σύνδεσης"
|
||||
message = "Πρέπει να είναι ενεργοποιημένη η λειτουργία σύνδεσης για να τροποποιήσετε τις ρυθμίσεις διαχειριστή. Ορίστε το SECURITY_ENABLELOGIN=true στο περιβάλλον σας ή το security.enableLogin: true στο settings.yml, και στη συνέχεια επανεκκινήστε τον διακομιστή."
|
||||
readOnly = "Οι παρακάτω ρυθμίσεις εμφανίζουν ενδεικτικές τιμές για αναφορά. Ενεργοποιήστε τη λειτουργία σύνδεσης για να δείτε και να επεξεργαστείτε την πραγματική διαμόρφωση."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "Απαιτείται άδεια Enterprise"
|
||||
message = "Απαιτείται άδεια Enterprise για πρόσβαση στο {{featureName}}. Βλέπετε δεδομένα επίδειξης για αναφορά."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "Απαιτείται επανεκκίνηση"
|
||||
message = "Οι ρυθμίσεις αποθηκεύτηκαν με επιτυχία. Απαιτείται επανεκκίνηση του διακομιστή για να τεθούν σε ισχύ οι αλλαγές."
|
||||
@@ -4568,13 +4463,6 @@ description = "Μέγιστος αριθμός αποτυχημένων προσ
|
||||
label = "Χρόνος επαναφοράς σύνδεσης (λεπτά)"
|
||||
description = "Χρόνος πριν μηδενιστούν οι αποτυχημένες προσπάθειες σύνδεσης"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Απενεργοποίηση προστασίας CSRF"
|
||||
description = "Απενεργοποίηση προστασίας Cross-Site Request Forgery (δεν συνιστάται)"
|
||||
@@ -4883,48 +4771,6 @@ description = "Εμφάνιση αναλυτικών μηνυμάτων σφάλ
|
||||
title = "Βάση δεδομένων"
|
||||
description = "Ρυθμίστε προσαρμοσμένες ρυθμίσεις σύνδεσης βάσης δεδομένων για επιχειρησιακές εγκαταστάσεις."
|
||||
configuration = "Διαμόρφωση βάσης δεδομένων"
|
||||
backupTitle = "Αντίγραφα ασφαλείας & Επαναφορά"
|
||||
backupDescription = "Διαχειριστείτε αντίγραφα ασφαλείας H2 απευθείας από την κονσόλα διαχειριστή."
|
||||
loadError = "Αποτυχία φόρτωσης αντιγράφων ασφαλείας βάσης δεδομένων"
|
||||
backupCreated = "Το αντίγραφο ασφαλείας δημιουργήθηκε με επιτυχία"
|
||||
backupFailed = "Αποτυχία δημιουργίας αντιγράφου ασφαλείας"
|
||||
deleteTitle = "Διαγραφή αντιγράφου ασφαλείας"
|
||||
deleteConfirm = "Να διαγραφεί αυτό το αντίγραφο ασφαλείας; Δεν είναι δυνατή η αναίρεση."
|
||||
deleteSuccess = "Το αντίγραφο ασφαλείας διαγράφηκε"
|
||||
deleteFailed = "Αποτυχία διαγραφής αντιγράφου ασφαλείας"
|
||||
deleteConfirmAction = "Διαγραφή αντιγράφου ασφαλείας"
|
||||
downloadFailed = "Αποτυχία λήψης αντιγράφου ασφαλείας"
|
||||
version = "Έκδοση H2"
|
||||
embedded = "Ενσωματωμένο H2"
|
||||
external = "Εξωτερική βάση δεδομένων"
|
||||
h2Only = "Τα αντίγραφα ασφαλείας είναι διαθέσιμα μόνο για την ενσωματωμένη βάση δεδομένων H2."
|
||||
h2Hint = "Ορίστε τον τύπο βάσης δεδομένων σε H2 και απενεργοποιήστε την προσαρμοσμένη βάση δεδομένων για να ενεργοποιήσετε τη δημιουργία αντιγράφων ασφαλείας και την επαναφορά."
|
||||
manageBackups = "Διαχείριση αντιγράφων ασφαλείας"
|
||||
refresh = "Ανανέωση"
|
||||
createBackup = "Δημιουργία αντιγράφου ασφαλείας"
|
||||
uploadTitle = "Μεταφόρτωση & εισαγωγή"
|
||||
chooseFile = "Επιλέξτε ένα αρχείο αντιγράφου ασφαλείας .sql"
|
||||
importFromUpload = "Εισαγωγή από μεταφόρτωση"
|
||||
confirmImportTitle = "Επιβεβαίωση εισαγωγής βάσης δεδομένων"
|
||||
overwriteWarning = "Προειδοποίηση: Αυτό θα αντικαταστήσει την τρέχουσα βάση δεδομένων."
|
||||
overwriteWarningBody = "Όλα τα υπάρχοντα δεδομένα θα αντικατασταθούν από το μεταφορτωμένο αντίγραφο ασφαλείας. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί."
|
||||
confirmCodeLabel = "Εισαγάγετε τον κωδικό επιβεβαίωσης για να συνεχίσετε"
|
||||
enterCode = "Εισαγάγετε τον κωδικό που εμφανίζεται παραπάνω"
|
||||
confirmImport = "Επιβεβαίωση εισαγωγής"
|
||||
codeMismatch = "Ο κωδικός επιβεβαίωσης δεν ταιριάζει"
|
||||
codeMismatchBody = "Εισαγάγετε τον κωδικό ακριβώς όπως εμφανίζεται για να συνεχίσετε."
|
||||
selectFile = "Επιλέξτε ένα αρχείο .sql για εισαγωγή"
|
||||
importSuccess = "Το αντίγραφο ασφαλείας εισήχθη με επιτυχία"
|
||||
importFailed = "Αποτυχία εισαγωγής αντιγράφου ασφαλείας"
|
||||
noBackups = "Δεν βρέθηκαν ακόμη αντίγραφα ασφαλείας."
|
||||
unavailable = "Η λίστα αντιγράφων ασφαλείας δεν είναι διαθέσιμη για την τρέχουσα διαμόρφωση βάσης δεδομένων."
|
||||
fileName = "Αρχείο"
|
||||
created = "Δημιουργήθηκε"
|
||||
size = "Μέγεθος"
|
||||
actions = "Ενέργειες"
|
||||
download = "Λήψη"
|
||||
import = "Εισαγωγή"
|
||||
delete = "Διαγραφή"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "Ενεργοποίηση προσαρμοσμένης βάσης δεδομένων"
|
||||
@@ -5002,26 +4848,6 @@ description = "Μέγιστο DPI για επεξεργασία εικόνας (
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "Κατάλογος Tessdata"
|
||||
description = "Διαδρομή προς τον κατάλογο tessdata για αρχεία γλωσσών OCR"
|
||||
downloadMissingTitle = "Δεν έχει επιλεγεί γλώσσα"
|
||||
downloadMissingBody = "Επιλέξτε τουλάχιστον μία γλώσσα για λήψη."
|
||||
downloadSuccessTitle = "Οι γλώσσες λήφθηκαν"
|
||||
downloadSuccessBody = "Οι επιλεγμένες γλώσσες tessdata αποθηκεύτηκαν."
|
||||
downloadErrorTitle = "Αποτυχία λήψης"
|
||||
loadingLanguages = "Φόρτωση εγκατεστημένων γλωσσών tessdata..."
|
||||
installedLanguages = "Εγκατεστημένες γλώσσες tessdata"
|
||||
noLanguages = "Δεν βρέθηκαν γλώσσες tessdata στον ρυθμισμένο κατάλογο."
|
||||
downloadLabel = "Λήψη επιπλέον γλωσσών tessdata"
|
||||
downloadPlaceholder = "Επιλέξτε γλώσσες"
|
||||
downloadNothingFound = "Δεν βρέθηκαν επιπλέον γλώσσες"
|
||||
permissionNotice = "Η διαδρομή tessdata δεν είναι εγγράψιμη. Οι λήψεις θα ανοίγουν στο πρόγραμμα περιήγησης. Αποθηκεύστε χειροκίνητα τα αρχεία .traineddata στον φάκελο tessdata."
|
||||
manualLinks = "Χειροκίνητες λήψεις: κάντε κλικ στους συνδέσμους και τοποθετήστε τα αρχεία στον φάκελο tessdata."
|
||||
downloadButton = "Λήψη επιλεγμένων γλωσσών"
|
||||
downloadInvalidTitle = "Μη έγκυρη επιλογή"
|
||||
downloadInvalidBody = "Ορισμένες επιλεγμένες γλώσσες δεν είναι διαθέσιμες για λήψη. Παρακαλώ ανανεώστε και επιλέξτε από τη λίστα."
|
||||
downloadErrorNetwork = "Η λήψη απέτυχε λόγω σφάλματος δικτύου. Ελέγξτε τη σύνδεσή σας και δοκιμάστε ξανά."
|
||||
downloadErrorServer = "Ο διακομιστής αντιμετώπισε σφάλμα κατά τη λήψη γλωσσών tessdata. Δοκιμάστε ξανά αργότερα."
|
||||
downloadErrorPermission = "Ο κατάλογος tessdata δεν είναι εγγράψιμος: {{message}}. Επιλέξτε έναν εγγράψιμο κατάλογο (π.χ. κάτω από τον φάκελο δεδομένων της εφαρμογής) ή προσαρμόστε τα δικαιώματα."
|
||||
downloadErrorGeneric = "Η λήψη απέτυχε: {{message}}. Δοκιμάστε ξανά αργότερα."
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "Απενεργοποίηση καθαρισμού HTML"
|
||||
@@ -5525,7 +5351,6 @@ desc = "Δημιουργήστε ροές εργασίας πολλών βημά
|
||||
invalidStep = "Μη έγκυρο βήμα"
|
||||
reviewTitle = "Αποτελέσματα αυτοματοποίησης"
|
||||
copyToSaved = "Αντιγραφή στα Αποθηκευμένα"
|
||||
exportForFolderScanning = "Εξαγωγή για σάρωση φακέλων"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "Επιλέξτε αρχεία για επεξεργασία με αυτήν την αυτοματοποίηση"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "Δημιουργία αυτοματοποίησης"
|
||||
editTitle = "Επεξεργασία αυτοματοποίησης"
|
||||
intro = "Οι αυτοματισμοί εκτελούν εργαλεία διαδοχικά. Για να ξεκινήσετε, προσθέστε εργαλεία με τη σειρά που θέλετε να εκτελεστούν."
|
||||
save = "Αποθήκευση αυτοματοποίησης"
|
||||
exportForFolderScanning = "Εξαγωγή για σάρωση φακέλων"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "Όνομα αυτοματοποίησης"
|
||||
@@ -5922,11 +5746,9 @@ username = "Όνομα χρήστη (Email)"
|
||||
usernamePlaceholder = "user@example.com"
|
||||
password = "Κωδικός πρόσβασης"
|
||||
passwordPlaceholder = "Εισαγάγετε κωδικό πρόσβασης"
|
||||
passwordRequired = "Απαιτείται κωδικός πρόσβασης"
|
||||
role = "Ρόλος"
|
||||
team = "Ομάδα (Προαιρετικό)"
|
||||
teamPlaceholder = "Επιλέξτε ομάδα"
|
||||
authType = "Τύπος ελέγχου ταυτότητας"
|
||||
forcePasswordChange = "Εξαναγκασμός αλλαγής κωδικού στην πρώτη σύνδεση"
|
||||
cancel = "Άκυρο"
|
||||
submit = "Προσθήκη μέλους"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "Απαιτούνται όνομα χρήστη και κωδ
|
||||
passwordTooShort = "Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον 6 χαρακτήρες"
|
||||
success = "Ο χρήστης δημιουργήθηκε με επιτυχία"
|
||||
error = "Αποτυχία δημιουργίας χρήστη"
|
||||
forceMFA = "Επιβολή ρύθμισης MFA στην επόμενη σύνδεση"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "Κωδικός πρόσβασης"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "Ο χρήστης θα πραγματοποιεί έλεγχο ταυτότητας μέσω παρόχου SSO"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "Επεξεργασία μέλους"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "{{count}} διαθέσιμη(ες) θέση(εις) χρήστ
|
||||
noSlotsAvailable = "Δεν υπάρχουν διαθέσιμες θέσεις"
|
||||
currentUsage = "Αυτή τη στιγμή χρησιμοποιείτε {{current}} από {{max}} άδειες χρήστη"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "Το MFA απενεργοποιήθηκε με επιτυχία για τον χρήστη"
|
||||
adminDisableError = "Αποτυχία απενεργοποίησης MFA για τον χρήστη"
|
||||
disableByAdmin = "Απενεργοποίηση MFA"
|
||||
|
||||
[workspace.teams]
|
||||
title = "Ομάδες"
|
||||
description = "Διαχείριση ομάδων και οργάνωση μελών του χώρου εργασίας"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "Endpoint"
|
||||
visits = "Επισκέψεις"
|
||||
percentage = "Ποσοστό"
|
||||
noData = "Δεν υπάρχουν διαθέσιμα δεδομένα"
|
||||
unknownEndpoint = "Άγνωστο endpoint"
|
||||
|
||||
[backendHealth]
|
||||
checking = "Έλεγχος κατάστασης backend..."
|
||||
|
||||
@@ -219,9 +219,6 @@ pagesAndStarting = "Pages & Starting Number"
|
||||
positionAndPages = "Position & Pages"
|
||||
preview = "Position Selection"
|
||||
previewDisclaimer = "Preview is approximate. Final output may vary due to PDF font metrics."
|
||||
zeroPad = "Zero‑pad Width (Bates Stamping)"
|
||||
zeroPadTooltip = "Zero‑pad (Bates Stamp) page numbers to this width (e.g., 3 ⇒ 001). Set 0 to disable."
|
||||
|
||||
|
||||
[addPageNumbers.selectText]
|
||||
1 = "Select PDF file:"
|
||||
@@ -309,7 +306,6 @@ selectOperation = "Select Operation"
|
||||
addOperationButton = "Add operation"
|
||||
pipelineHeader = "Pipeline:"
|
||||
saveButton = "Download"
|
||||
saveForFolderScanning = "Save for Folder Scanning"
|
||||
validateButton = "Validate"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -442,9 +438,7 @@ fullscreen = "Fullscreen"
|
||||
[settings.general.updates]
|
||||
title = "Software Updates"
|
||||
description = "Check for updates and view version information"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "Current Version"
|
||||
latestVersion = "Latest Version"
|
||||
checkForUpdates = "Check for Updates"
|
||||
viewDetails = "View Details"
|
||||
@@ -562,30 +556,6 @@ webBrowserSettings = "Web Browser Setting"
|
||||
syncToBrowser = "Sync Account -> Browser"
|
||||
syncToAccount = "Sync Account <- Browser"
|
||||
|
||||
[account.mfa]
|
||||
title = "Two-factor authentication"
|
||||
setupFailed = "Unable to start two-factor setup. Please try again."
|
||||
codeRequired = "Enter the authentication code to continue."
|
||||
enabled = "Two-factor authentication enabled."
|
||||
enableFailed = "Unable to enable two-factor authentication. Check the code and try again."
|
||||
disabled = "Two-factor authentication disabled."
|
||||
disableFailed = "Unable to disable two-factor authentication. Check the code and try again."
|
||||
description = "Add an extra layer of security to your account."
|
||||
enableButton = "Enable two-factor authentication"
|
||||
disableButton = "Disable two-factor authentication"
|
||||
setupTitle = "Set up two-factor authentication"
|
||||
setupDescription = "Scan the QR code with your authenticator app, then enter the 6-digit code to confirm."
|
||||
manualKey = "Manual setup key"
|
||||
secretWarning = "Keep this key private. Anyone with access can generate valid authentication codes."
|
||||
codePlaceholder = "Enter 6-digit code"
|
||||
confirmEnable = "Enable"
|
||||
disableTitle = "Disable two-factor authentication"
|
||||
disableDescription = "Enter a valid authentication code to disable two-factor authentication."
|
||||
codeLabel = "Authentication code"
|
||||
confirmDisable = "Disable"
|
||||
ssoDescription = "Two-factor authentication is managed by your identity provider for single sign-on accounts."
|
||||
ssoManaged = "Configure MFA through your identity provider."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "User Control Settings"
|
||||
header = "Admin User Control Settings"
|
||||
@@ -695,257 +665,257 @@ workbenchSlide = "Workspace panel"
|
||||
workspace = "Workspace"
|
||||
|
||||
[home.multiTool]
|
||||
tags = "multiple,tools,multi-tool,all-in-one,swiss army,page organizer,page editor,edit pages,manage pages,organize,reorganize"
|
||||
tags = "multiple,tools"
|
||||
title = "PDF Multi Tool"
|
||||
desc = "Merge, Rotate, Rearrange, Split, and Remove pages"
|
||||
|
||||
[home.merge]
|
||||
tags = "combine,join,unite,merge,merge PDFs,combine PDFs,join PDFs,concatenate,append,stitch,combine files,join files,merge documents"
|
||||
tags = "combine,join,unite"
|
||||
title = "Merge"
|
||||
desc = "Easily merge multiple PDFs into one."
|
||||
|
||||
[home.split]
|
||||
tags = "divide,separate,break,split,extract pages,separate pages,divide document,break apart,separate files,unbind,split by page,divide by chapter"
|
||||
tags = "divide,separate,break"
|
||||
title = "Split"
|
||||
desc = "Split PDFs into multiple documents"
|
||||
|
||||
[home.rotate]
|
||||
tags = "turn,flip,orient,rotate,orientation,landscape,portrait,90 degrees,180 degrees,clockwise,anticlockwise,counter-clockwise,fix orientation"
|
||||
tags = "turn,flip,orient"
|
||||
title = "Rotate"
|
||||
desc = "Easily rotate your PDFs."
|
||||
|
||||
[home.convert]
|
||||
tags = "transform,change,convert,PDF to Word,PDF to Excel,PDF to image,Word to PDF,Excel to PDF,PowerPoint to PDF,HTML to PDF,export,import,file conversion,format change,save as"
|
||||
tags = "transform,change"
|
||||
title = "Convert"
|
||||
desc = "Convert files between different formats"
|
||||
|
||||
[home.pdfOrganiser]
|
||||
tags = "organize,rearrange,reorder,organise,arrange pages,sort,move pages,delete pages,remove pages,page management,page organizer,page organiser,resequence"
|
||||
tags = "organize,rearrange,reorder"
|
||||
title = "Organise"
|
||||
desc = "Remove/Rearrange pages in any order"
|
||||
|
||||
[home.addImage]
|
||||
tags = "insert,embed,place,add image,insert image,place image,embed image,add photo,add picture,add logo,graphics,insert picture,place photo,PNG,JPG,JPEG"
|
||||
tags = "insert,embed,place"
|
||||
title = "Add image"
|
||||
desc = "Adds a image onto a set location on the PDF"
|
||||
|
||||
[home.addAttachments]
|
||||
tags = "embed,attach,include,attachments,attach files,embed files,include files,add files,file attachment,associated files,supplementary files"
|
||||
tags = "embed,attach,include"
|
||||
title = "Add Attachments"
|
||||
desc = "Add or remove embedded files (attachments) to/from a PDF"
|
||||
|
||||
[home.watermark]
|
||||
tags = "stamp,mark,overlay,watermark,branding,logo,confidential,draft,copyright,trademark,text overlay,image overlay,background text"
|
||||
tags = "stamp,mark,overlay"
|
||||
title = "Add Watermark"
|
||||
desc = "Add a custom watermark to your PDF document."
|
||||
|
||||
[home.removePassword]
|
||||
tags = "unlock,remove password,unlock PDF,decrypt,remove encryption,unprotect,open protected PDF,password removal,unlock protected,disable password,remove security,remove owner password"
|
||||
tags = "unlock"
|
||||
title = "Remove Password"
|
||||
desc = "Remove password protection from your PDF document."
|
||||
|
||||
[home.compress]
|
||||
tags = "shrink,reduce,optimize,compress,smaller,downsize,file size,reduce size,minimize,make smaller,decrease size,optimize size"
|
||||
tags = "shrink,reduce,optimize"
|
||||
title = "Compress"
|
||||
desc = "Compress PDFs to reduce their file size."
|
||||
|
||||
[home.unlockPDFForms]
|
||||
tags = "unlock,enable,edit,unlock forms,enable forms,editable forms,remove read only,make editable,unlock fields,enable editing,form fields,fillable,unprotect forms"
|
||||
tags = "unlock,enable,edit"
|
||||
title = "Unlock PDF Forms"
|
||||
desc = "Remove read-only property of form fields in a PDF document."
|
||||
|
||||
[home.changeMetadata]
|
||||
tags = "edit,modify,update,metadata,properties,document properties,author,title,subject,keywords,creator,producer,info,document info,file properties"
|
||||
tags = "edit,modify,update"
|
||||
title = "Change Metadata"
|
||||
desc = "Change/Remove/Add metadata from a PDF document"
|
||||
|
||||
[home.ocr]
|
||||
tags = "extract,scan,OCR,optical character recognition,text recognition,scan to text,image to text,scanned document,searchable PDF,make searchable,extract text,recognize text,read scanned"
|
||||
tags = "extract,scan"
|
||||
title = "OCR / Cleanup scans"
|
||||
desc = "Cleanup scans and detects text from images within a PDF and re-adds it as text."
|
||||
|
||||
[home.extractImages]
|
||||
tags = "pull,save,export,extract images,get images,save images,export images,extract photos,extract pictures,pull images,download images,rip images,extract graphics,save photos"
|
||||
tags = "pull,save,export"
|
||||
title = "Extract Images"
|
||||
desc = "Extracts all images from a PDF and saves them to zip"
|
||||
|
||||
[home.scannerImageSplit]
|
||||
tags = "detect,split,photos,auto detect,detect photos,split photos,separate photos,split scanned images,multiple photos,auto split,photo detection,image detection,scan separation"
|
||||
tags = "detect,split,photos"
|
||||
title = "Detect & Split Scanned Photos"
|
||||
desc = "Detect and split scanned photos into separate pages"
|
||||
|
||||
[home.sign]
|
||||
tags = "signature,autograph,e-sign,electronic signature,digital signature,sign document,approval,signoff,authorize,endorse,ink signature,handwriting"
|
||||
tags = "signature,autograph"
|
||||
title = "Sign"
|
||||
desc = "Adds signature to PDF by drawing, text or image"
|
||||
|
||||
[home.annotate]
|
||||
tags = "annotate,highlight,draw,markup,comment,notes,review,redline,feedback,markup tools,sticky notes,shapes,arrows,text box,freehand"
|
||||
tags = "annotate,highlight,draw"
|
||||
title = "Annotate"
|
||||
desc = "Highlight, draw, add notes and shapes in the viewer"
|
||||
|
||||
[home.flatten]
|
||||
tags = "simplify,remove,interactive,flatten,flatten form,remove form fields,make static,finalize form,lock form,disable editing,convert to image,non-editable"
|
||||
tags = "simplify,remove,interactive"
|
||||
title = "Flatten"
|
||||
desc = "Remove all interactive elements and forms from a PDF"
|
||||
|
||||
[home.certSign]
|
||||
tags = "authenticate,PEM,P12,official,encrypt,sign,certificate,PKCS12,JKS,server,manual,auto,digital certificate,certificate signature,PKI,cryptographic signature,trusted signature"
|
||||
tags = "authenticate,PEM,P12,official,encrypt,sign,certificate,PKCS12,JKS,server,manual,auto"
|
||||
title = "Sign with Certificate"
|
||||
desc = "Signs a PDF with a Certificate/Key (PEM/P12)"
|
||||
|
||||
[home.repair]
|
||||
tags = "fix,restore,repair,fix PDF,fix broken,fix corrupt,repair PDF,repair corrupt,broken PDF,corrupt PDF,damaged PDF,recover,fix errors,PDF won't open,can't open PDF,PDF errors,troubleshoot,restore PDF,rebuild,corrupted"
|
||||
tags = "fix,restore"
|
||||
title = "Repair"
|
||||
desc = "Tries to repair a corrupt/broken PDF"
|
||||
|
||||
[home.removeBlanks]
|
||||
tags = "delete,clean,empty,remove blank,delete blank pages,empty pages,white pages,remove empty,clean up,cleanup blank"
|
||||
tags = "delete,clean,empty"
|
||||
title = "Remove Blank pages"
|
||||
desc = "Detects and removes blank pages from a document"
|
||||
|
||||
[home.removeAnnotations]
|
||||
tags = "delete,clean,strip,remove annotations,remove comments,delete comments,remove markup,remove highlights,clean annotations,strip comments,remove notes,delete markup,clear comments"
|
||||
tags = "delete,clean,strip"
|
||||
title = "Remove Annotations"
|
||||
desc = "Removes all comments/annotations from a PDF"
|
||||
|
||||
[home.compare]
|
||||
tags = "difference,compare,diff,compare PDFs,compare documents,find differences,show differences,changes,what changed,track changes,revisions,version compare,side by side,contrast,delta"
|
||||
tags = "difference"
|
||||
title = "Compare"
|
||||
desc = "Compares and shows the differences between 2 PDF Documents"
|
||||
|
||||
[home.removeCertSign]
|
||||
tags = "remove,delete,unlock,remove certificate,remove signature,delete signature,unsigned,remove digital signature,strip signature,remove cert,unsign"
|
||||
tags = "remove,delete,unlock"
|
||||
title = "Remove Certificate Sign"
|
||||
desc = "Remove certificate signature from PDF"
|
||||
|
||||
[home.pageLayout]
|
||||
tags = "layout,arrange,combine,N-up,2-up,4-up,multiple per page,pages per sheet,layout pages,tile,grid layout,multi-page layout,combine on page,handout"
|
||||
tags = "layout,arrange,combine"
|
||||
title = "Multi-Page Layout"
|
||||
desc = "Merge multiple pages of a PDF document into a single page"
|
||||
|
||||
[home.bookletImposition]
|
||||
tags = "booklet,print,binding,imposition,booklet printing,saddle stitch,fold,pamphlet,brochure,print booklet,duplex,two-sided,signature,book layout,page imposition,print layout"
|
||||
tags = "booklet,print,binding"
|
||||
title = "Booklet Imposition"
|
||||
desc = "Create booklets with proper page ordering and multi-page layout for printing and binding"
|
||||
|
||||
[home.scalePages]
|
||||
tags = "resize,adjust,scale,page size,resize page,scale page,change size,adjust size,enlarge,shrink page,fit to page,A4,letter size"
|
||||
tags = "resize,adjust,scale"
|
||||
title = "Adjust page size/scale"
|
||||
desc = "Change the size/scale of a page and/or its contents."
|
||||
|
||||
[home.addPageNumbers]
|
||||
tags = "number,pagination,count,add page numbers,page numbering,page numbers,footer,header,number pages,sequential,pagination tool"
|
||||
tags = "number,pagination,count"
|
||||
title = "Add Page Numbers"
|
||||
desc = "Add Page numbers throughout a document in a set location"
|
||||
|
||||
[home.autoRename]
|
||||
tags = "auto-detect,header-based,organize,relabel,auto rename,automatic rename,smart rename,rename by content,filename,file naming,detect title"
|
||||
tags = "auto-detect,header-based,organize,relabel"
|
||||
title = "Auto Rename PDF File"
|
||||
desc = "Auto renames a PDF file based on its detected header"
|
||||
|
||||
[home.adjustContrast]
|
||||
tags = "contrast,brightness,saturation,adjust colors,color correction,enhance,lighten,darken,improve quality,color balance,hue,vibrance"
|
||||
tags = "contrast,brightness,saturation"
|
||||
title = "Adjust Colours/Contrast"
|
||||
desc = "Adjust Colors/Contrast, Saturation and Brightness of a PDF"
|
||||
|
||||
[home.crop]
|
||||
tags = "trim,cut,resize,crop,crop PDF,trim PDF,trim margins,remove margins,cut edges,trim borders,remove white space,crop pages,trim pages,reduce margins,set margins"
|
||||
tags = "trim,cut,resize"
|
||||
title = "Crop PDF"
|
||||
desc = "Crop a PDF to reduce its size (maintains text!)"
|
||||
|
||||
[home.autoSplitPDF]
|
||||
tags = "auto,split,QR,auto split,QR code,QR split,barcode,automatic split,divider page,separator page,scan divider,batch scanning"
|
||||
tags = "auto,split,QR"
|
||||
title = "Auto Split Pages"
|
||||
desc = "Auto Split Scanned PDF with physical scanned page splitter QR Code"
|
||||
|
||||
[home.sanitize]
|
||||
tags = "clean,purge,remove,sanitize,sanitise,remove scripts,remove javascript,remove metadata,strip metadata,security,clean document,remove hidden data,privacy"
|
||||
tags = "clean,purge,remove"
|
||||
title = "Sanitise"
|
||||
desc = "Remove potentially harmful elements from PDF files"
|
||||
|
||||
[home.getPdfInfo]
|
||||
tags = "info,metadata,details,PDF info,document info,properties,file info,get info,show info,view properties,document properties,statistics,page count,file details,inspect"
|
||||
tags = "info,metadata,details"
|
||||
title = "Get ALL Info on PDF"
|
||||
desc = "Grabs any and all information possible on PDFs"
|
||||
|
||||
[home.pdfToSinglePage]
|
||||
tags = "combine,merge,single,single page,one page,merge to single,combine all,stitch pages,concatenate vertical,long page,poster"
|
||||
tags = "combine,merge,single"
|
||||
title = "PDF to Single Large Page"
|
||||
desc = "Merges all PDF pages into one large single page"
|
||||
|
||||
[home.showJS]
|
||||
tags = "javascript,code,script,show javascript,show JS,find javascript,detect javascript,view javascript,embedded scripts,malware,security,inspect,debug"
|
||||
tags = "javascript,code,script"
|
||||
title = "Show Javascript"
|
||||
desc = "Searches and displays any JS injected into a PDF"
|
||||
|
||||
[home.redact]
|
||||
tags = "censor,blackout,hide,redact,redaction,black out,block out,remove sensitive,hide text,privacy,confidential,GDPR,PII,sensitive data,permanently remove,cover up,legal redaction"
|
||||
tags = "censor,blackout,hide"
|
||||
title = "Redact"
|
||||
desc = "Redacts (blacks out) a PDF based on selected text, drawn shapes and/or selected page(s)"
|
||||
|
||||
[home.splitBySections]
|
||||
tags = "split,sections,divide,split by sections,grid split,divide pages,split into sections,cut pages,divide grid,section split,horizontal split,vertical split"
|
||||
tags = "split,sections,divide"
|
||||
title = "Split PDF by Sections"
|
||||
desc = "Divide each page of a PDF into smaller horizontal and vertical sections"
|
||||
|
||||
[home.addStamp]
|
||||
tags = "stamp,mark,seal,approved,rejected,confidential,stamp tool,rubber stamp,date stamp,approval stamp,received,void,copy,original"
|
||||
tags = "stamp,mark,seal"
|
||||
title = "Add Stamp to PDF"
|
||||
desc = "Add text or add image stamps at set locations"
|
||||
|
||||
[home.removeImage]
|
||||
tags = "remove,delete,clean,remove image,delete image,strip images,remove pictures,delete photos,clean images,reduce size,remove graphics"
|
||||
tags = "remove,delete,clean"
|
||||
title = "Remove image"
|
||||
desc = "Remove image from PDF to reduce file size"
|
||||
|
||||
[home.splitByChapters]
|
||||
tags = "split,chapters,structure,split by chapters,split by bookmarks,bookmarks,outline,table of contents,TOC split,chapter split,divide by sections"
|
||||
tags = "split,chapters,structure"
|
||||
title = "Split PDF by Chapters"
|
||||
desc = "Split a PDF into multiple files based on its chapter structure."
|
||||
|
||||
[home.validateSignature]
|
||||
tags = "validate,verify,certificate,validate signature,verify signature,check signature,digital signature,certificate verification,signature validation,authentic,trust,signed,verify certificate"
|
||||
tags = "validate,verify,certificate"
|
||||
title = "Validate PDF Signature"
|
||||
desc = "Verify digital signatures and certificates in PDF documents"
|
||||
|
||||
[home.swagger]
|
||||
tags = "API,documentation,test,swagger,API docs,REST API,endpoints,developer,API reference,API testing,OpenAPI,integration,developer docs"
|
||||
tags = "API,documentation,test"
|
||||
title = "API Documentation"
|
||||
desc = "View API documentation and test endpoints"
|
||||
|
||||
[home.scannerEffect]
|
||||
tags = "scan,simulate,create,fake scan,look scanned,scanner effect,make look scanned,photocopy effect,simulate scanner,realistic scan"
|
||||
tags = "scan,simulate,create"
|
||||
title = "Scanner Effect"
|
||||
desc = "Create a PDF that looks like it was scanned"
|
||||
|
||||
[home.editTableOfContents]
|
||||
tags = "bookmarks,contents,edit,table of contents,TOC,outline,navigation,chapters,sections,add bookmarks,edit bookmarks,PDF outline"
|
||||
tags = "bookmarks,contents,edit"
|
||||
title = "Edit Table of Contents"
|
||||
desc = "Add or edit bookmarks and table of contents in PDF documents"
|
||||
|
||||
[home.manageCertificates]
|
||||
tags = "certificates,import,export,manage certificates,digital certificates,certificate management,PFX,P12,keystore,import certificate,export certificate,certificate store,PKI"
|
||||
tags = "certificates,import,export"
|
||||
title = "Manage Certificates"
|
||||
desc = "Import, export, or delete digital certificate files used for signing PDFs."
|
||||
|
||||
[home.read]
|
||||
tags = "view,open,display,read,viewer,PDF viewer,PDF reader,open PDF,view PDF,display PDF,preview,browse"
|
||||
tags = "view,open,display"
|
||||
title = "Read"
|
||||
desc = "View and annotate PDFs. Highlight text, draw, or insert comments for review and collaboration."
|
||||
|
||||
[home.reorganizePages]
|
||||
tags = "rearrange,reorder,organize,reorganize,move pages,page order,sort pages,arrange pages,shuffle,resequence"
|
||||
tags = "rearrange,reorder,organize"
|
||||
title = "Reorganize Pages"
|
||||
desc = "Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."
|
||||
|
||||
[home.extractPages]
|
||||
tags = "pull,select,copy,extract,extract pages,get pages,pull out,save pages,export pages,copy pages,select pages,specific pages"
|
||||
tags = "pull,select,copy"
|
||||
title = "Extract Pages"
|
||||
desc = "Extract specific pages from a PDF document"
|
||||
|
||||
[home.removePages]
|
||||
tags = "delete,extract,exclude,remove pages,delete pages,remove page,delete page,exclude pages,take out pages,discard pages,drop pages"
|
||||
tags = "delete,extract,exclude"
|
||||
title = "Remove Pages"
|
||||
desc = "Remove specific pages from a PDF document"
|
||||
|
||||
@@ -955,57 +925,50 @@ title = "Auto Split by Size/Count"
|
||||
desc = "Automatically split PDFs by file size or page count"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Replace & Invert Colour"
|
||||
desc = "Replace or invert colours in PDF documents"
|
||||
|
||||
[home.devApi]
|
||||
tags = "API,development,documentation,developer,REST,integration,endpoints,programmatic,automation,scripting"
|
||||
tags = "API,development,documentation"
|
||||
title = "API"
|
||||
desc = "Link to API documentation"
|
||||
|
||||
[home.devFolderScanning]
|
||||
tags = "automation,folder,scanning,watch folder,hot folder,automatic processing,batch,monitor folder,auto process,folder monitoring"
|
||||
tags = "automation,folder,scanning"
|
||||
title = "Automated Folder Scanning"
|
||||
desc = "Link to automated folder scanning guide"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO Guide"
|
||||
desc = "Link to SSO guide"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Air-gapped Setup"
|
||||
desc = "Link to air-gapped setup guide"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Add Password"
|
||||
desc = "Add password protection and restrictions to PDF files"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Change Permissions"
|
||||
desc = "Change document restrictions and permissions"
|
||||
|
||||
[home.automate]
|
||||
tags = "workflow,sequence,automation,automate,batch,batch processing,pipeline,chain,multi-step,recurring,scheduled,automatic,process multiple,bulk operations"
|
||||
tags = "workflow,sequence,automation"
|
||||
title = "Automate"
|
||||
desc = "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Overlay one PDF on top of another"
|
||||
title = "Overlay PDFs"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF Text Editor"
|
||||
desc = "Edit existing text and images inside PDFs"
|
||||
|
||||
[home.addText]
|
||||
tags = "text,annotation,label,add text,insert text,place text,text box,add label,add caption,type on PDF,write on PDF,add words,add note,text overlay,typewriter"
|
||||
tags = "text,annotation,label"
|
||||
title = "Add Text"
|
||||
desc = "Add custom text anywhere in your PDF"
|
||||
|
||||
@@ -1301,10 +1264,7 @@ downloadHtml = "Download HTML intermediate file instead of PDF"
|
||||
pdfaOptions = "PDF/A Options"
|
||||
outputFormat = "Output Format"
|
||||
pdfaNote = "PDF/A-1b is more compatible, PDF/A-2b supports more features, PDF/A-3b supports embedded files."
|
||||
pdfaFormat = "PDF/A Format"
|
||||
pdfaDigitalSignatureWarning = "The PDF contains a digital signature. This will be removed in the next step."
|
||||
pdfxDigitalSignatureWarning = "The PDF contains a digital signature. This will be removed in the next step."
|
||||
pdfxDescription = "PDF/X is an ISO standard PDF subset for reliable printing and graphics exchange."
|
||||
fileFormat = "File Format"
|
||||
wordDoc = "Word Document"
|
||||
wordDocExt = "Word Document (.docx)"
|
||||
@@ -2237,16 +2197,6 @@ tip = "Currently does not work for multiple inputs at once"
|
||||
outputFormat = "Output format"
|
||||
pdfWithDigitalSignature = "The PDF contains a digital signature. This will be removed in the next step."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "print,standard,conversion,production,prepress,archive"
|
||||
title = "PDF To PDF/X"
|
||||
header = "PDF To PDF/X"
|
||||
credit = "This service uses Ghostscript for PDF/X conversion"
|
||||
submit = "Convert"
|
||||
tip = "Currently does not work for multiple inputs at once"
|
||||
outputFormat = "Output format"
|
||||
pdfWithDigitalSignature = "The PDF contains a digital signature. This will be removed in the next step."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc,docx,odt,word,transformation,format,conversion,office,microsoft,docfile"
|
||||
title = "PDF to Word"
|
||||
@@ -2645,7 +2595,6 @@ label = "Original PDF"
|
||||
|
||||
[compare.edited]
|
||||
label = "Edited PDF"
|
||||
placeholder = "Select the edited PDF"
|
||||
|
||||
[compare.swap]
|
||||
confirmTitle = "Re-run comparison?"
|
||||
@@ -3204,18 +3153,6 @@ other = "Other"
|
||||
perPageInfo = "Per Page Info"
|
||||
tableOfContents = "Table of Contents"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "Not Detected"
|
||||
passed = "Passed"
|
||||
failed = "Failed"
|
||||
compliant = "Compliant"
|
||||
nonCompliant = "Non-Compliant"
|
||||
none = "No standards detected"
|
||||
passedCount = "passed"
|
||||
failedCount = "failed"
|
||||
noVerification = "No Verification Performed"
|
||||
noVerificationDesc = "PDF standards compliance was not verified for this document."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "Attachments"
|
||||
embeddedFiles = "Embedded Files"
|
||||
@@ -3247,8 +3184,6 @@ permsRestricted = "{{count}} restrictions"
|
||||
permsMixed = "Some permissions restricted"
|
||||
hasCompliance = "Has compliance standards"
|
||||
noCompliance = "No Compliance Standards"
|
||||
compliancePassed = "{{standards}} compliant"
|
||||
complianceChecked = "Standards verified ({{failed}} failed)"
|
||||
basic = "Basic Information"
|
||||
documentInfo = "Document Information"
|
||||
securityTitle = "Security Status"
|
||||
@@ -3566,44 +3501,6 @@ imageSize = "Image Size"
|
||||
margin = "Margin"
|
||||
positionAndFormatting = "Position & Formatting"
|
||||
quickPosition = "Select a position on the page to place the stamp."
|
||||
preview = "Preview:"
|
||||
useTemplate = "Use Template"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
clickToExpand = "Click to expand"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
dateTimeVars = "Date & Time"
|
||||
dateDesc = "Current date"
|
||||
timeDesc = "Current time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
customDateDesc = "Custom format"
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
pageVars = "Page Information"
|
||||
pageNumberDesc = "Current page number"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataVars = "Document Metadata"
|
||||
metadataDesc = "From PDF document properties"
|
||||
otherVars = "Other"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
examples = "Examples"
|
||||
multiLine = "multi-line"
|
||||
|
||||
[AddStampRequest.template]
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
dateHeader = "Date Header"
|
||||
europeanDate = "European Date"
|
||||
timestamp = "Timestamp"
|
||||
draftWatermark = "Draft Watermark"
|
||||
custom = "Custom"
|
||||
page-numbers = "Page Numbers"
|
||||
draft = "Draft Watermark"
|
||||
doc-info = "Document Info"
|
||||
legal-footer = "Legal Footer"
|
||||
european-date = "European Date (DD/MM/YYYY)"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "An error occurred while adding stamp to the PDF."
|
||||
@@ -3826,14 +3723,6 @@ passwordChangedSuccess = "Password changed successfully! Please sign in with you
|
||||
credentialsUpdated = "Your credentials have been updated. Please sign in again."
|
||||
defaultCredentials = "Default Login Credentials"
|
||||
changePasswordWarning = "Please change your password after logging in for the first time"
|
||||
mfaRequired = "Two-factor code required"
|
||||
mfaCode = "Authentication Code"
|
||||
enterMfaCode = "Enter 6-digit code"
|
||||
mfaPromptTitle = "Two-factor authentication"
|
||||
mfaPromptBody = "Enter the authentication code from your authenticator app to continue."
|
||||
verifyingMfa = "Verifying..."
|
||||
verifyMfa = "Verify code"
|
||||
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "Stirling PDF overview"
|
||||
@@ -4435,10 +4324,6 @@ title = "Login Mode Required"
|
||||
message = "Login mode must be enabled to modify admin settings. Please set SECURITY_ENABLELOGIN=true in your environment or security.enableLogin: true in settings.yml, then restart the server."
|
||||
readOnly = "The settings below show example values for reference. Enable login mode to view and edit actual configuration."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "Enterprise License Required"
|
||||
message = "An Enterprise license is required to access {{featureName}}. You are viewing demo data for reference."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "Restart Required"
|
||||
message = "Settings have been saved successfully. A server restart is required for the changes to take effect."
|
||||
@@ -4523,18 +4408,10 @@ description = "Configure custom file system paths for pipeline processing and ex
|
||||
[admin.settings.general.customPaths.pipeline]
|
||||
label = "Pipeline Directories"
|
||||
|
||||
[admin.settings.general.customPaths.pipeline.pipelineDir]
|
||||
label = "Pipeline Directory"
|
||||
description = "Base directory for pipeline resources (leave empty for default: /pipeline)"
|
||||
|
||||
[admin.settings.general.customPaths.pipeline.watchedFoldersDir]
|
||||
label = "Watched Folders Directory"
|
||||
description = "Directory where pipeline monitors for incoming PDFs (leave empty for default: /pipeline/watchedFolders)"
|
||||
|
||||
[admin.settings.general.customPaths.pipeline.watchedFoldersDirs]
|
||||
label = "Watched Folders Directories"
|
||||
description = "Directories where pipeline monitors for incoming PDFs (one per line or comma-separated; leave empty for default: /pipeline/watchedFolders)"
|
||||
|
||||
[admin.settings.general.customPaths.pipeline.finishedFoldersDir]
|
||||
label = "Finished Folders Directory"
|
||||
description = "Directory where processed PDFs are outputted (leave empty for default: /pipeline/finishedFolders)"
|
||||
@@ -4586,13 +4463,6 @@ description = "Maximum number of failed login attempts before account lockout"
|
||||
label = "Login Reset Time (minutes)"
|
||||
description = "Time before failed login attempts are reset"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Disable CSRF Protection"
|
||||
description = "Disable Cross-Site Request Forgery protection (not recommended)"
|
||||
@@ -5532,7 +5402,6 @@ desc = "Build multi-step workflows by chaining together PDF actions. Ideal for r
|
||||
invalidStep = "Invalid step"
|
||||
reviewTitle = "Automation Results"
|
||||
copyToSaved = "Copy to Saved"
|
||||
exportForFolderScanning = "Export for Folder Scanning"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "Select files to process with this automation"
|
||||
@@ -5554,7 +5423,6 @@ createTitle = "Create Automation"
|
||||
editTitle = "Edit Automation"
|
||||
intro = "Automations run tools sequentially. To get started, add tools in the order you want them to run."
|
||||
save = "Save Automation"
|
||||
exportForFolderScanning = "Export for Folder Scanning"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "Automation Name"
|
||||
@@ -5929,11 +5797,9 @@ username = "Username (Email)"
|
||||
usernamePlaceholder = "user@example.com"
|
||||
password = "Password"
|
||||
passwordPlaceholder = "Enter password"
|
||||
passwordRequired = "Password is required"
|
||||
role = "Role"
|
||||
team = "Team (Optional)"
|
||||
teamPlaceholder = "Select a team"
|
||||
authType = "Authentication Type"
|
||||
forcePasswordChange = "Force password change on first login"
|
||||
cancel = "Cancel"
|
||||
submit = "Add Member"
|
||||
@@ -5941,13 +5807,6 @@ usernameRequired = "Username and password are required"
|
||||
passwordTooShort = "Password must be at least 6 characters"
|
||||
success = "User created successfully"
|
||||
error = "Failed to create user"
|
||||
forceMFA = "Force MFA setup on next login"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "Password"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "User will authenticate via SSO provider"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "Edit Member"
|
||||
@@ -6051,11 +5910,6 @@ slotsAvailable = "{{count}} user slot(s) available"
|
||||
noSlotsAvailable = "No slots available"
|
||||
currentUsage = "Currently using {{current}} of {{max}} user licences"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "MFA disabled successfully for user"
|
||||
adminDisableError = "Failed to disable MFA for user"
|
||||
disableByAdmin = "Disable MFA"
|
||||
|
||||
[workspace.teams]
|
||||
title = "Teams"
|
||||
description = "Manage teams and organize workspace members"
|
||||
@@ -6498,7 +6352,6 @@ endpoint = "Endpoint"
|
||||
visits = "Visits"
|
||||
percentage = "Percentage"
|
||||
noData = "No data available"
|
||||
unknownEndpoint = "Unknown endpoint"
|
||||
|
||||
[backendHealth]
|
||||
checking = "Checking backend status..."
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "Seleccione la operación"
|
||||
addOperationButton = "Añadir operación"
|
||||
pipelineHeader = "Automatización:"
|
||||
saveButton = "Descargar"
|
||||
saveForFolderScanning = "Guardar para el escaneo de carpetas"
|
||||
validateButton = "Validar"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "Pantalla completa"
|
||||
[settings.general.updates]
|
||||
title = "Actualizaciones de software"
|
||||
description = "Compruebe actualizaciones y vea la información de versión"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "Versión actual"
|
||||
latestVersion = "Última versión"
|
||||
checkForUpdates = "Buscar actualizaciones"
|
||||
viewDetails = "Ver detalles"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "Configuración del navegador"
|
||||
syncToBrowser = "Sincronizar cuenta -> Navegador"
|
||||
syncToAccount = "Sincronizar cuenta <- Navegador"
|
||||
|
||||
[account.mfa]
|
||||
title = "Autenticación de dos factores"
|
||||
setupFailed = "No se pudo iniciar la configuración de la autenticación de dos factores. Vuelve a intentarlo."
|
||||
codeRequired = "Introduce el código de autenticación para continuar."
|
||||
enabled = "Autenticación de dos factores habilitada."
|
||||
enableFailed = "No se pudo habilitar la autenticación de dos factores. Comprueba el código e inténtalo de nuevo."
|
||||
disabled = "Autenticación de dos factores deshabilitada."
|
||||
disableFailed = "No se pudo deshabilitar la autenticación de dos factores. Comprueba el código e inténtalo de nuevo."
|
||||
description = "Añade una capa adicional de seguridad a tu cuenta."
|
||||
enableButton = "Habilitar la autenticación de dos factores"
|
||||
disableButton = "Deshabilitar la autenticación de dos factores"
|
||||
setupTitle = "Configurar la autenticación de dos factores"
|
||||
setupDescription = "Escanea el código QR con tu aplicación de autenticación y luego introduce el código de 6 dígitos para confirmar."
|
||||
manualKey = "Clave de configuración manual"
|
||||
secretWarning = "Mantén esta clave privada. Cualquiera con acceso puede generar códigos de autenticación válidos."
|
||||
codePlaceholder = "Introduce el código de 6 dígitos"
|
||||
confirmEnable = "Habilitar"
|
||||
disableTitle = "Deshabilitar la autenticación de dos factores"
|
||||
disableDescription = "Introduce un código de autenticación válido para deshabilitar la autenticación de dos factores."
|
||||
codeLabel = "Código de autenticación"
|
||||
confirmDisable = "Deshabilitar"
|
||||
ssoDescription = "La autenticación de dos factores es gestionada por tu proveedor de identidad para cuentas con inicio de sesión único."
|
||||
ssoManaged = "Configura la MFA a través de tu proveedor de identidad."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "Configuración de control de usuario"
|
||||
header = "Configuración de control de usuario administrador"
|
||||
@@ -952,7 +925,6 @@ title = "Auto dividir por tamaño/conteo"
|
||||
desc = "Divide un solo PDF en múltiples documentos según su tamaño, número de páginas, o número de documento"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Reemplazar e Invertir Color"
|
||||
desc = "Reemplace o invierta colores en documentos PDF"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "Escaneo Automatizado de Carpetas"
|
||||
desc = "Enlace a la guía de escaneo automatizado de carpetas"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Guía de SSO"
|
||||
desc = "Enlace a la guía de SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Configuración Aislada"
|
||||
desc = "Enlace a la guía de configuración aislada"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Proteger con contraseña"
|
||||
desc = "Cifrar documento PDF con contraseña"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Cambiar permisos"
|
||||
desc = "Modificar restricciones y permisos del documento"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "Automatizar"
|
||||
desc = "Crear flujos de trabajo de múltiples pasos encadenando acciones de PDF. Ideal para tareas recurrentes."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Superponer PDFs encima de otro PDF"
|
||||
title = "Superponer PDFs"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Editor de texto PDF"
|
||||
desc = "Revise y edite exportaciones JSON de Stirling PDF con edición de texto agrupada y regeneración de PDF"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "Descargar archivo intermedio HTML en lugar de PDF"
|
||||
pdfaOptions = "Opciones de PDF/A"
|
||||
outputFormat = "Formato de Salida"
|
||||
pdfaNote = "PDF/A-1b es más compatible, PDF/A-2b soporta más funciones."
|
||||
pdfaFormat = "Formato PDF/A"
|
||||
pdfaDigitalSignatureWarning = "El PDF contiene una firma digital. Ésta se eliminará en el siguiente paso."
|
||||
pdfxDigitalSignatureWarning = "El PDF contiene una firma digital. Se eliminará en el siguiente paso."
|
||||
pdfxDescription = "PDF/X es un subconjunto estándar ISO de PDF para impresión fiable e intercambio de gráficos."
|
||||
fileFormat = "Formato de Archivo"
|
||||
wordDoc = "Documento de Word"
|
||||
wordDocExt = "Documento de Word (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "Actualmente no funciona para múltiples entrada a la vez"
|
||||
outputFormat = "Formato de salida"
|
||||
pdfWithDigitalSignature = "El PDF contiene una firma digital. Ésta se eliminará en el siguiente paso."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "impresión,estándar,conversión,producción,preimpresión,archivo"
|
||||
title = "PDF a PDF/X"
|
||||
header = "PDF a PDF/X"
|
||||
credit = "Este servicio utiliza Ghostscript para la conversión a PDF/X"
|
||||
submit = "Convertir"
|
||||
tip = "Actualmente no funciona con varias entradas a la vez"
|
||||
outputFormat = "Formato de salida"
|
||||
pdfWithDigitalSignature = "El PDF contiene una firma digital. Se eliminará en el siguiente paso."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc,docx,odt,word,transformación,formato,conversión,office,microsoft,archivo del documento"
|
||||
title = "PDF a Word"
|
||||
@@ -3200,18 +3153,6 @@ other = "Otros"
|
||||
perPageInfo = "Información por página"
|
||||
tableOfContents = "Tabla de contenidos"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "No detectado"
|
||||
passed = "Aprobado"
|
||||
failed = "Fallido"
|
||||
compliant = "Conforme"
|
||||
nonCompliant = "No conforme"
|
||||
none = "No se detectaron estándares"
|
||||
passedCount = "aprobadas"
|
||||
failedCount = "fallidas"
|
||||
noVerification = "No se realizó verificación"
|
||||
noVerificationDesc = "No se verificó el cumplimiento de estándares PDF para este documento."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "Adjuntos"
|
||||
embeddedFiles = "Archivos incrustados"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} restricciones"
|
||||
permsMixed = "Algunos permisos restringidos"
|
||||
hasCompliance = "Cumple con estándares"
|
||||
noCompliance = "Sin estándares de conformidad"
|
||||
compliancePassed = "{{standards}} conforme"
|
||||
complianceChecked = "Estándares verificados ({{failed}} fallidos)"
|
||||
basic = "Información básica"
|
||||
documentInfo = "Información del documento"
|
||||
securityTitle = "Estado de seguridad"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "Tamaño de la imagen"
|
||||
margin = "Margen"
|
||||
positionAndFormatting = "Posición y formato"
|
||||
quickPosition = "Selecciona una posición en la página para colocar el sello."
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "Se produjo un error al añadir el sello al PDF."
|
||||
@@ -3594,14 +3508,6 @@ failed = "Se produjo un error al añadir el sello al PDF."
|
||||
[AddStampRequest.results]
|
||||
title = "Resultados del sello"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "Eliminar imagen,Operaciones de página,Back end,Backend"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "¡Contraseña cambiada correctamente! Inicia sesión c
|
||||
credentialsUpdated = "Tus credenciales se han actualizado. Vuelve a iniciar sesión."
|
||||
defaultCredentials = "Credenciales de inicio de sesión predeterminadas"
|
||||
changePasswordWarning = "Cambie su contraseña tras iniciar sesión por primera vez"
|
||||
mfaRequired = "Se requiere código de dos factores"
|
||||
mfaCode = "Código de autenticación"
|
||||
enterMfaCode = "Introduce el código de 6 dígitos"
|
||||
mfaPromptTitle = "Autenticación de dos factores"
|
||||
mfaPromptBody = "Introduce el código de autenticación de tu aplicación de autenticación para continuar."
|
||||
verifyingMfa = "Verificando..."
|
||||
verifyMfa = "Verificar código"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "Vista general de Stirling PDF"
|
||||
@@ -4425,10 +4324,6 @@ title = "Se requiere modo de inicio de sesión"
|
||||
message = "El modo de inicio de sesión debe estar habilitado para modificar la configuración de administración. Establece SECURITY_ENABLELOGIN=true en tu entorno o security.enableLogin: true en settings.yml y reinicia el servidor."
|
||||
readOnly = "La configuración siguiente muestra valores de ejemplo como referencia. Habilita el modo de inicio de sesión para ver y editar la configuración real."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "Se requiere una licencia Enterprise"
|
||||
message = "Se requiere una licencia Enterprise para acceder a {{featureName}}. Estás viendo datos de demostración como referencia."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "Se requiere reiniciar"
|
||||
message = "La configuración se ha guardado correctamente. Se requiere reiniciar el servidor para que los cambios surtan efecto."
|
||||
@@ -4568,13 +4463,6 @@ description = "Número máximo de intentos fallidos antes de bloquear la cuenta"
|
||||
label = "Tiempo de restablecimiento del inicio de sesión (minutos)"
|
||||
description = "Tiempo antes de que se restablezcan los intentos fallidos de inicio de sesión"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Desactivar la protección CSRF"
|
||||
description = "Desactivar la protección contra Cross-Site Request Forgery (no recomendado)"
|
||||
@@ -4883,48 +4771,6 @@ description = "Mostrar mensajes de error detallados para los usuarios."
|
||||
title = "Base de datos"
|
||||
description = "Configura ajustes de conexión de base de datos personalizados para despliegues empresariales."
|
||||
configuration = "Configuración de la base de datos"
|
||||
backupTitle = "Copias de seguridad y restauración"
|
||||
backupDescription = "Gestiona copias de seguridad de H2 directamente desde la consola de administración."
|
||||
loadError = "Error al cargar las copias de seguridad de la base de datos"
|
||||
backupCreated = "Copia de seguridad creada correctamente"
|
||||
backupFailed = "Error al crear la copia de seguridad"
|
||||
deleteTitle = "Eliminar copia de seguridad"
|
||||
deleteConfirm = "¿Eliminar esta copia de seguridad? Esta acción no se puede deshacer."
|
||||
deleteSuccess = "Copia de seguridad eliminada"
|
||||
deleteFailed = "Error al eliminar la copia de seguridad"
|
||||
deleteConfirmAction = "Eliminar copia de seguridad"
|
||||
downloadFailed = "Error al descargar la copia de seguridad"
|
||||
version = "Versión de H2"
|
||||
embedded = "H2 integrado"
|
||||
external = "Base de datos externa"
|
||||
h2Only = "Las copias de seguridad solo están disponibles para la base de datos H2 integrada."
|
||||
h2Hint = "Establece el tipo de base de datos en H2 y desactiva la base de datos personalizada para habilitar la copia de seguridad y la restauración."
|
||||
manageBackups = "Gestionar copias de seguridad"
|
||||
refresh = "Actualizar"
|
||||
createBackup = "Crear copia de seguridad"
|
||||
uploadTitle = "Subir e importar"
|
||||
chooseFile = "Selecciona un archivo de copia de seguridad .sql"
|
||||
importFromUpload = "Importar archivo subido"
|
||||
confirmImportTitle = "Confirmar importación de base de datos"
|
||||
overwriteWarning = "Advertencia: Esto sobrescribirá la base de datos actual."
|
||||
overwriteWarningBody = "Todos los datos existentes serán reemplazados por la copia de seguridad subida. Esta acción no se puede deshacer."
|
||||
confirmCodeLabel = "Introduce el código de confirmación para continuar"
|
||||
enterCode = "Introduce el código mostrado arriba"
|
||||
confirmImport = "Confirmar importación"
|
||||
codeMismatch = "El código de confirmación no coincide"
|
||||
codeMismatchBody = "Introduce el código exactamente como se muestra para continuar."
|
||||
selectFile = "Selecciona un archivo .sql para importar"
|
||||
importSuccess = "Copia de seguridad importada correctamente"
|
||||
importFailed = "Error al importar la copia de seguridad"
|
||||
noBackups = "Aún no se han encontrado copias de seguridad."
|
||||
unavailable = "La lista de copias de seguridad no está disponible para la configuración de base de datos actual."
|
||||
fileName = "Archivo"
|
||||
created = "Creado"
|
||||
size = "Tamaño"
|
||||
actions = "Acciones"
|
||||
download = "Descargar"
|
||||
import = "Importar"
|
||||
delete = "Eliminar"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "Habilitar base de datos personalizada"
|
||||
@@ -5002,26 +4848,6 @@ description = "DPI máximo para el procesamiento de imágenes (0 = ilimitado)"
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "Directorio Tessdata"
|
||||
description = "Ruta al directorio tessdata para archivos de idioma de OCR"
|
||||
downloadMissingTitle = "No se ha seleccionado ningún idioma"
|
||||
downloadMissingBody = "Selecciona al menos un idioma para descargar."
|
||||
downloadSuccessTitle = "Idiomas descargados"
|
||||
downloadSuccessBody = "Se han guardado los idiomas de tessdata seleccionados."
|
||||
downloadErrorTitle = "Descarga fallida"
|
||||
loadingLanguages = "Cargando idiomas de tessdata instalados..."
|
||||
installedLanguages = "Idiomas de tessdata instalados"
|
||||
noLanguages = "No se han encontrado idiomas de tessdata en el directorio configurado."
|
||||
downloadLabel = "Descargar idiomas de tessdata adicionales"
|
||||
downloadPlaceholder = "Selecciona idiomas"
|
||||
downloadNothingFound = "No se han encontrado idiomas adicionales"
|
||||
permissionNotice = "La ruta de tessdata no tiene permisos de escritura. Las descargas se abrirán en el navegador; guarda los archivos .traineddata manualmente en la carpeta tessdata."
|
||||
manualLinks = "Descargas manuales: haz clic en los enlaces y coloca los archivos en la carpeta tessdata."
|
||||
downloadButton = "Descargar idiomas seleccionados"
|
||||
downloadInvalidTitle = "Selección no válida"
|
||||
downloadInvalidBody = "Algunos de los idiomas seleccionados no están disponibles para descargar. Actualiza y elige de la lista."
|
||||
downloadErrorNetwork = "La descarga ha fallado debido a un error de red. Comprueba tu conexión e inténtalo de nuevo."
|
||||
downloadErrorServer = "El servidor encontró un error al descargar idiomas de tessdata. Inténtalo de nuevo más tarde."
|
||||
downloadErrorPermission = "El directorio tessdata no tiene permisos de escritura: {{message}}. Elige un directorio con permisos de escritura (p. ej., dentro de la carpeta de datos de la aplicación) o ajusta los permisos."
|
||||
downloadErrorGeneric = "Descarga fallida: {{message}}. Inténtalo de nuevo más tarde."
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "Desactivar la sanitización de HTML"
|
||||
@@ -5525,7 +5351,6 @@ desc = "Cree flujos de trabajo de varios pasos encadenando acciones de PDF. Idea
|
||||
invalidStep = "Paso inválido"
|
||||
reviewTitle = "Resultados de automatización"
|
||||
copyToSaved = "Copiar a guardados"
|
||||
exportForFolderScanning = "Exportar para el escaneo de carpetas"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "Seleccione archivos para procesar con esta automatización"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "Crear automatización"
|
||||
editTitle = "Editar automatización"
|
||||
intro = "Las automatizaciones ejecutan herramientas de forma secuencial. Para comenzar, añada herramientas en el orden en que desea que se ejecuten."
|
||||
save = "Guardar automatización"
|
||||
exportForFolderScanning = "Exportar para el escaneo de carpetas"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "Nombre de automatización"
|
||||
@@ -5922,11 +5746,9 @@ username = "Nombre de usuario (correo)"
|
||||
usernamePlaceholder = "user@example.com"
|
||||
password = "Contraseña"
|
||||
passwordPlaceholder = "Introduce la contraseña"
|
||||
passwordRequired = "La contraseña es obligatoria"
|
||||
role = "Rol"
|
||||
team = "Equipo (opcional)"
|
||||
teamPlaceholder = "Selecciona un equipo"
|
||||
authType = "Tipo de autenticación"
|
||||
forcePasswordChange = "Forzar el cambio de contraseña en el primer inicio de sesión"
|
||||
cancel = "Cancelar"
|
||||
submit = "Añadir miembro"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "Se requieren nombre de usuario y contraseña"
|
||||
passwordTooShort = "La contraseña debe tener al menos 6 caracteres"
|
||||
success = "Usuario creado correctamente"
|
||||
error = "No se pudo crear el usuario"
|
||||
forceMFA = "Forzar la configuración de MFA en el próximo inicio de sesión"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "Contraseña"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "El usuario se autenticará a través del proveedor de SSO"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "Editar miembro"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "{{count}} plaza(s) de usuario disponible(s)"
|
||||
noSlotsAvailable = "No hay plazas disponibles"
|
||||
currentUsage = "Actualmente usando {{current}} de {{max}} licencias de usuario"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "MFA deshabilitada correctamente para el usuario"
|
||||
adminDisableError = "No se pudo deshabilitar la MFA para el usuario"
|
||||
disableByAdmin = "Deshabilitar MFA"
|
||||
|
||||
[workspace.teams]
|
||||
title = "Equipos"
|
||||
description = "Gestiona equipos y organiza a los miembros del espacio de trabajo"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "Punto de conexión"
|
||||
visits = "Visitas"
|
||||
percentage = "Porcentaje"
|
||||
noData = "No hay datos disponibles"
|
||||
unknownEndpoint = "Endpoint desconocido"
|
||||
|
||||
[backendHealth]
|
||||
checking = "Comprobando el estado del backend..."
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "Aukeratu eragiketa"
|
||||
addOperationButton = "Gehitu eragiketa"
|
||||
pipelineHeader = "Pipeline:"
|
||||
saveButton = "Distira"
|
||||
saveForFolderScanning = "Gorde karpeta-eskaneatzerako"
|
||||
validateButton = "Balidatu"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "Pantaila osoa"
|
||||
[settings.general.updates]
|
||||
title = "Software eguneratzeak"
|
||||
description = "Egiaztatu eguneratzeak eta ikusi bertsio-informazioa"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "Uneko bertsioa"
|
||||
latestVersion = "Azken bertsioa"
|
||||
checkForUpdates = "Egiaztatu eguneratzeak"
|
||||
viewDetails = "Xehetasunak ikusi"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "Web nabigatzailearen ezarpenak"
|
||||
syncToBrowser = "Sync Kontua -> Nabigatzailea"
|
||||
syncToAccount = "Sync Kontua <- Nabigatzailea"
|
||||
|
||||
[account.mfa]
|
||||
title = "Bi faktoreko autentifikazioa"
|
||||
setupFailed = "Ezin izan da bi faktoreko autentifikazioaren konfigurazioa abiarazi. Saiatu berriro."
|
||||
codeRequired = "Jarraitzeko, sartu autentifikazio-kodea."
|
||||
enabled = "Bi faktoreko autentifikazioa gaituta."
|
||||
enableFailed = "Ezin izan da bi faktoreko autentifikazioa gaitu. Egiaztatu kodea eta saiatu berriro."
|
||||
disabled = "Bi faktoreko autentifikazioa desgaituta."
|
||||
disableFailed = "Ezin izan da bi faktoreko autentifikazioa desgaitu. Egiaztatu kodea eta saiatu berriro."
|
||||
description = "Gehitu segurtasun-geruza gehigarria zure kontuari."
|
||||
enableButton = "Gaitu bi faktoreko autentifikazioa"
|
||||
disableButton = "Desgaitu bi faktoreko autentifikazioa"
|
||||
setupTitle = "Konfiguratu bi faktoreko autentifikazioa"
|
||||
setupDescription = "Eskaneatu QR kodea zure autentifikazio-aplikazioarekin, eta ondoren sartu 6 digituko kodea berresteko."
|
||||
manualKey = "Eskuzko konfigurazio-gakoa"
|
||||
secretWarning = "Mantendu gako hau pribatua. Sarbidea duen edonork autentifikazio-kode baliodunak sor ditzake."
|
||||
codePlaceholder = "Sartu 6 digituko kodea"
|
||||
confirmEnable = "Gaitu"
|
||||
disableTitle = "Desgaitu bi faktoreko autentifikazioa"
|
||||
disableDescription = "Sartu baliozko autentifikazio-kode bat bi faktoreko autentifikazioa desgaitzeko."
|
||||
codeLabel = "Autentifikazio-kodea"
|
||||
confirmDisable = "Desgaitu"
|
||||
ssoDescription = "Bi faktoreko autentifikazioa zure identitate-hornitzaileak kudeatzen du single sign-on kontuentzat."
|
||||
ssoManaged = "Konfiguratu MFA zure identitate-hornitzailearen bidez."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "Erabiltzailearen Ezarpenen Kontrolak"
|
||||
header = "Admin Erabiltzailearen Ezarpenen Kontrolak"
|
||||
@@ -952,7 +925,6 @@ title = "Autom. zatitu tamaina/kop."
|
||||
desc = "Split a single PDF into multiple documents based on size, page count, or document count"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Ordeztu eta inbertitu kolorea"
|
||||
desc = "Ordeztu edo alderantzikatu koloreak PDF dokumentuetan"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "Karpeta eskaneatze autom."
|
||||
desc = "Esteka karpeta eskaneatze automatizatuaren gidara"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO gida"
|
||||
desc = "Esteka SSO gidara"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Sare isolatuko konfigurazioa"
|
||||
desc = "Esteka sare isolatutako konfigurazio gidara"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Gehitu pasahitza"
|
||||
desc = "Enkriptatu PDF dokumentua pasahitz batekin"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Aldatu baimenak"
|
||||
desc = "Aldatu dokumentuaren murrizketak eta baimenak"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "Automatizatu"
|
||||
desc = "Eraiki hainbat pausotako workflowak PDF ekintzak kateatuz. Egokia zeregin errepikakorretarako."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Overlays PDFs on-top of another PDF"
|
||||
title = "Gainjarri PDFak"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF testu-editorea"
|
||||
desc = "Berrikusi eta editatu Stirling PDF JSON esportazioak taldekatutako testu-edizioarekin eta PDF birsorkuntzarekin"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "Deskargatu HTML bitarteko fitxategia PDFaren ordez"
|
||||
pdfaOptions = "PDF/A aukerak"
|
||||
outputFormat = "Irteerako formatua"
|
||||
pdfaNote = "PDF/A-1b bateragarriagoa da; PDF/A-2b-k ezaugarri gehiago onartzen ditu."
|
||||
pdfaFormat = "PDF/A formatua"
|
||||
pdfaDigitalSignatureWarning = "PDFak sinadura digitala dauka. Hurrengo urratsean kenduko da."
|
||||
pdfxDigitalSignatureWarning = "PDFak sinadura digital bat dauka. Hurrengo urratsean kenduko da."
|
||||
pdfxDescription = "PDF/X inprimaketa eta grafikoen truke fidagarrirako ISO estandarreko PDF azpimultzoa da."
|
||||
fileFormat = "Fitxategi formatua"
|
||||
wordDoc = "Word dokumentua"
|
||||
wordDocExt = "Word dokumentua (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "Une honetan ez dabil aldi berean sarrera anitzekin"
|
||||
outputFormat = "Irteerako formatua"
|
||||
pdfWithDigitalSignature = "PDFak sinadura digitala dauka. Hurrengo urratsean kenduko da."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "inprimaketa,estandarra,bihurketa,produkzioa,aurre-inprimaketa,artxiboa"
|
||||
title = "PDFtik PDF/Xera"
|
||||
header = "PDFtik PDF/Xera"
|
||||
credit = "Zerbitzu honek Ghostscript erabiltzen du PDF/X bihurketarako"
|
||||
submit = "Bihurtu"
|
||||
tip = "Une honetan ez dabil hainbat sarrera aldi berean"
|
||||
outputFormat = "Irteera-formatua"
|
||||
pdfWithDigitalSignature = "PDFak sinadura digital bat dauka. Hurrengo urratsean kenduko da."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc,docx,odt,word,eraldaketa,formatua,bihurketa,office,microsoft,doc-fitxategia"
|
||||
title = "PDFa Word bihurtu"
|
||||
@@ -3200,18 +3153,6 @@ other = "Bestelakoak"
|
||||
perPageInfo = "Orrialdeko informazioa"
|
||||
tableOfContents = "Aurkibidea"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "Ez da detektatu"
|
||||
passed = "Gaindituta"
|
||||
failed = "Huts eginda"
|
||||
compliant = "Bateragarria"
|
||||
nonCompliant = "Ez da bateragarria"
|
||||
none = "Ez da estandarrik detektatu"
|
||||
passedCount = "gaindituta"
|
||||
failedCount = "huts eginda"
|
||||
noVerification = "Ez da egiaztapenik egin"
|
||||
noVerificationDesc = "Dokumentu honen PDF estandarren betetzea ez da egiaztatu."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "Eranskinak"
|
||||
embeddedFiles = "Txertatutako fitxategiak"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} murrizketa"
|
||||
permsMixed = "Zenbait baimen murriztuta"
|
||||
hasCompliance = "Betetze-estandarrak ditu"
|
||||
noCompliance = "Ez dago betetze-estandarrik"
|
||||
compliancePassed = "{{standards}} bateragarria"
|
||||
complianceChecked = "Estandarrak egiaztatuta ({{failed}} huts)"
|
||||
basic = "Oinarrizko informazioa"
|
||||
documentInfo = "Dokumentuaren informazioa"
|
||||
securityTitle = "Segurtasun-egoera"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "Irudiaren tamaina"
|
||||
margin = "Marjina"
|
||||
positionAndFormatting = "Kokapena eta formatua"
|
||||
quickPosition = "Hautatu orrian zigiloa kokatzeko posizio bat."
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "Errore bat gertatu da zigilua PDFari gehitzean."
|
||||
@@ -3594,14 +3508,6 @@ failed = "Errore bat gertatu da zigilua PDFari gehitzean."
|
||||
[AddStampRequest.results]
|
||||
title = "Zigiluaren emaitzak"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "Irudia kendu,Orrialde eragiketak,Back end,server side"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "Pasahitza ongi aldatu da! Hasi saioa pasahitz berriare
|
||||
credentialsUpdated = "Zure kredentzialak eguneratu dira. Hasi saioa berriro."
|
||||
defaultCredentials = "Saio-hasierako kredentzial lehenetsiak"
|
||||
changePasswordWarning = "Mesedez, aldatu pasahitza lehen aldiz saioa hasi ondoren"
|
||||
mfaRequired = "Bi faktoreko kodea behar da"
|
||||
mfaCode = "Autentifikazio-kodea"
|
||||
enterMfaCode = "Sartu 6 digituko kodea"
|
||||
mfaPromptTitle = "Bi faktoreko autentifikazioa"
|
||||
mfaPromptBody = "Sartu jarraitzeko zure autentifikazio-aplikazioak emandako autentifikazio-kodea."
|
||||
verifyingMfa = "Egiaztatzen..."
|
||||
verifyMfa = "Egiaztatu kodea"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "Stirling PDF ikuspegi orokorra"
|
||||
@@ -4425,10 +4324,6 @@ title = "Saio-hasiera modua beharrezkoa da"
|
||||
message = "Saio-hasiera modua gaitu behar da admin ezarpenak aldatzeko. Ezarri SECURITY_ENABLELOGIN=true zure ingurunean edo security.enableLogin: true settings.yml, eta berrabiarazi zerbitzaria."
|
||||
readOnly = "Azpiko ezarpenek adibide-balioak erakusten dituzte erreferentziarako. Gaitu saio-hasiera modua benetako konfigurazioa ikusi eta editatzeko."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "Enterprise lizentzia behar da"
|
||||
message = "{{featureName}} atzitzeko Enterprise lizentzia behar da. Erreferentzia gisa demo datuak ikusten ari zara."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "Berrabiarazi behar da"
|
||||
message = "Ezarpenak ongi gorde dira. Zerbitzaria berrabiaraztea beharrezkoa da aldaketek eragina izan dezaten."
|
||||
@@ -4568,13 +4463,6 @@ description = "Kontua blokeatu aurretik huts egindako saio-saiakeren gehienezko
|
||||
label = "Saio-hasieraren berrezartze denbora (minutuak)"
|
||||
description = "Huts egindako saio-saiakerak berrezarri aurreko denbora"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF babesa desgaitu"
|
||||
description = "Desgaitu Cross-Site Request Forgery babesa (ez da gomendatzen)"
|
||||
@@ -4883,48 +4771,6 @@ description = "Erakutsi errore-mezu zehatzak erabiltzaileentzat."
|
||||
title = "Datu-basea"
|
||||
description = "Konfiguratu datu-base konexio pertsonalizatuak enpresa-hedapenetarako."
|
||||
configuration = "Datu-basearen konfigurazioa"
|
||||
backupTitle = "Babeskopiak eta leheneratzea"
|
||||
backupDescription = "Kudeatu H2 babeskopiak administrazio-kontsolatik bertatik."
|
||||
loadError = "Huts egin du datu-basearen babeskopiak kargatzean"
|
||||
backupCreated = "Babeskopia arrakastaz sortu da"
|
||||
backupFailed = "Huts egin du babeskopia sortzean"
|
||||
deleteTitle = "Babeskopia ezabatu"
|
||||
deleteConfirm = "Ezabatu babeskopia hau? Hau ezin da desegin."
|
||||
deleteSuccess = "Babeskopia ezabatu da"
|
||||
deleteFailed = "Huts egin du babeskopia ezabatzean"
|
||||
deleteConfirmAction = "Babeskopia ezabatu"
|
||||
downloadFailed = "Huts egin du babeskopia deskargatzean"
|
||||
version = "H2 bertsioa"
|
||||
embedded = "Txertatutako H2"
|
||||
external = "Kanpoko DB"
|
||||
h2Only = "Babeskopiak H2 txertatutako datu-basearentzat bakarrik daude eskuragarri."
|
||||
h2Hint = "Ezarri datu-base mota H2 gisa eta desgaitu datu-base pertsonalizatua babeskopia eta leheneratzea gaitzeko."
|
||||
manageBackups = "Babeskopiak kudeatu"
|
||||
refresh = "Freskatu"
|
||||
createBackup = "Babeskopia sortu"
|
||||
uploadTitle = "Igo eta inportatu"
|
||||
chooseFile = "Aukeratu .sql babeskopia-fitxategi bat"
|
||||
importFromUpload = "Igotakoa inportatu"
|
||||
confirmImportTitle = "Datu-base inportazioa berretsi"
|
||||
overwriteWarning = "Abisua: honek uneko datu-basea gainidatziko du."
|
||||
overwriteWarningBody = "Existitzen den datu guztia igotako babeskopiak ordezkatuko du. Ekintza hau ezin da desegin."
|
||||
confirmCodeLabel = "Jarraitzeko, sartu berrespen-kodea"
|
||||
enterCode = "Sartu goian erakutsitako kodea"
|
||||
confirmImport = "Inportazioa berretsi"
|
||||
codeMismatch = "Berrespen-kodea ez dator bat"
|
||||
codeMismatchBody = "Mesedez, sartu kodea zehazki ageri den bezala jarraitzeko."
|
||||
selectFile = "Mesedez, hautatu inportatzeko .sql fitxategi bat"
|
||||
importSuccess = "Babeskopia arrakastaz inportatu da"
|
||||
importFailed = "Huts egin du babeskopia inportatzean"
|
||||
noBackups = "Oraindik ez da babeskopiarik aurkitu."
|
||||
unavailable = "Uneko datu-base konfigurazioarentzat babeskopien zerrenda ez dago erabilgarri."
|
||||
fileName = "Fitxategia"
|
||||
created = "Sortze-data"
|
||||
size = "Tamaina"
|
||||
actions = "Ekintzak"
|
||||
download = "Deskargatu"
|
||||
import = "Inportatu"
|
||||
delete = "Ezabatu"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "Datu-base pertsonalizatua gaitu"
|
||||
@@ -5002,26 +4848,6 @@ description = "Irudien prozesaketarako gehienezko DPI (0 = mugarik ez)"
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "Tessdata direktorioa"
|
||||
description = "OCR hizkuntza-fitxategietarako tessdata direktorioaren bide-izena"
|
||||
downloadMissingTitle = "Ez da hizkuntzarik hautatu"
|
||||
downloadMissingBody = "Hautatu gutxienez hizkuntza bat deskargatzeko."
|
||||
downloadSuccessTitle = "Hizkuntzak deskargatu dira"
|
||||
downloadSuccessBody = "Hautatutako tessdata hizkuntzak gorde dira."
|
||||
downloadErrorTitle = "Deskargak huts egin du"
|
||||
loadingLanguages = "Instalatutako tessdata hizkuntzak kargatzen..."
|
||||
installedLanguages = "Instalatutako tessdata hizkuntzak"
|
||||
noLanguages = "Ez da tessdata hizkuntzarik aurkitu konfiguratutako direktorioan."
|
||||
downloadLabel = "Deskargatu tessdata hizkuntza gehigarriak"
|
||||
downloadPlaceholder = "Hautatu hizkuntzak"
|
||||
downloadNothingFound = "Ez da hizkuntza gehigarririk aurkitu"
|
||||
permissionNotice = "tessdata bidea ez da idazgarria. Deskargak nabigatzailean irekiko dira; gorde .traineddata fitxategiak eskuz tessdata direktorioan."
|
||||
manualLinks = "Deskarga manualak: egin klik esteketan eta jarri fitxategiak tessdata direktorioan."
|
||||
downloadButton = "Hautatutako hizkuntzak deskargatu"
|
||||
downloadInvalidTitle = "Hautapen baliogabea"
|
||||
downloadInvalidBody = "Hautatutako hizkuntza batzuk ez daude deskargatzeko erabilgarri. Freskatu eta aukeratu zerrendatik."
|
||||
downloadErrorNetwork = "Sareko errore baten ondorioz deskargak huts egin du. Egiaztatu konexioa eta saiatu berriro."
|
||||
downloadErrorServer = "Zerbitzarian errorea gertatu da tessdata hizkuntzak deskargatzean. Saiatu berriro geroago."
|
||||
downloadErrorPermission = "tessdata direktorioa ez da idazgarria: {{message}}. Aukeratu idazgarri den direktorio bat (adib. aplikazioaren datu-karpetaren azpian) edo egokitu baimenak."
|
||||
downloadErrorGeneric = "Deskargak huts egin du: {{message}}. Saiatu berriro geroago."
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "HTML saneamendua desgaitu"
|
||||
@@ -5525,7 +5351,6 @@ desc = "Eraiki hainbat pausoko lan-fluxuak PDF ekintzak kateatuz. Egokia zeregin
|
||||
invalidStep = "Urrats baliogabea"
|
||||
reviewTitle = "Automatizazioaren emaitzak"
|
||||
copyToSaved = "Gordetakoetara kopiatu"
|
||||
exportForFolderScanning = "Karpeta-eskaneatzerako esportatu"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "Hautatu automatizazio honekin prozesatuko diren fitxategiak"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "Automatizazioa sortu"
|
||||
editTitle = "Automatizazioa editatu"
|
||||
intro = "Automatizazioek tresnak sekuentzialki exekutatzen dituzte. Hasteko, gehitu tresnak exekutatu nahi dituzun ordenan."
|
||||
save = "Automatizazioa gorde"
|
||||
exportForFolderScanning = "Karpeta-eskaneatzerako esportatu"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "Automatizazioaren izena"
|
||||
@@ -5922,11 +5746,9 @@ username = "Erabiltzaile-izena (Helbide elektronikoa)"
|
||||
usernamePlaceholder = "user@example.com"
|
||||
password = "Pasahitza"
|
||||
passwordPlaceholder = "Sartu pasahitza"
|
||||
passwordRequired = "Pasahitza beharrezkoa da"
|
||||
role = "Rola"
|
||||
team = "Taldea (aukerakoa)"
|
||||
teamPlaceholder = "Aukeratu talde bat"
|
||||
authType = "Autentifikazio mota"
|
||||
forcePasswordChange = "Behartu pasahitza aldatzera lehen saio-hasieran"
|
||||
cancel = "Utzi"
|
||||
submit = "Gehitu kidea"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "Erabiltzaile-izena eta pasahitza beharrezkoak dira"
|
||||
passwordTooShort = "Pasahitzak gutxienez 6 karaktere izan behar ditu"
|
||||
success = "Erabiltzailea ongi sortu da"
|
||||
error = "Ezin izan da erabiltzailea sortu"
|
||||
forceMFA = "Behartu MFA konfigurazioa hurrengo saio-hasieran"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "Pasahitza"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "Erabiltzailea SSO hornitzailearen bidez autentifikatuko da"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "Kidea editatu"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "{{count}} erabiltzaile-slot erabilgarri"
|
||||
noSlotsAvailable = "Ez dago slot erabilgarririk"
|
||||
currentUsage = "Une honetan {{current}}/{{max}} erabiltzaile-lizentzia erabiltzen"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "Erabiltzailearentzat MFA behar bezala desgaitu da"
|
||||
adminDisableError = "Erabiltzailearentzat MFA desgaitzea huts egin du"
|
||||
disableByAdmin = "Desgaitu MFA"
|
||||
|
||||
[workspace.teams]
|
||||
title = "Taldeak"
|
||||
description = "Kudeatu taldeak eta antolatu lan-espazioko kideak"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "Endpoint"
|
||||
visits = "Bisitak"
|
||||
percentage = "Ehunekoa"
|
||||
noData = "Ez dago daturik eskuragarri"
|
||||
unknownEndpoint = "Amaiera-puntu ezezaguna"
|
||||
|
||||
[backendHealth]
|
||||
checking = "Atzeko zerbitzuaren egoera egiaztatzen..."
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "عملیات را انتخاب کنید"
|
||||
addOperationButton = "اضافه کردن عملیات"
|
||||
pipelineHeader = "پایپلاین:"
|
||||
saveButton = "دانلود"
|
||||
saveForFolderScanning = "ذخیره برای اسکن پوشه"
|
||||
validateButton = "اعتبارسنجی"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "تمامصفحه"
|
||||
[settings.general.updates]
|
||||
title = "بهروزرسانی نرمافزار"
|
||||
description = "بررسی بهروزرسانی و مشاهده اطلاعات نسخه"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "نسخه فعلی"
|
||||
latestVersion = "آخرین نسخه"
|
||||
checkForUpdates = "بررسی بهروزرسانی"
|
||||
viewDetails = "مشاهده جزئیات"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "تنظیمات مرورگر وب"
|
||||
syncToBrowser = "همگامسازی حساب -> مرورگر"
|
||||
syncToAccount = "همگامسازی حساب <- مرورگر"
|
||||
|
||||
[account.mfa]
|
||||
title = "احراز هویت دومرحلهای"
|
||||
setupFailed = "امکان شروع راهاندازی احراز هویت دومرحلهای فراهم نشد. لطفاً دوباره تلاش کنید."
|
||||
codeRequired = "برای ادامه، کد احراز هویت را وارد کنید."
|
||||
enabled = "احراز هویت دومرحلهای فعال شد."
|
||||
enableFailed = "امکان فعالسازی احراز هویت دومرحلهای وجود ندارد. کد را بررسی کرده و دوباره تلاش کنید."
|
||||
disabled = "احراز هویت دومرحلهای غیرفعال شد."
|
||||
disableFailed = "امکان غیرفعالسازی احراز هویت دومرحلهای وجود ندارد. کد را بررسی کرده و دوباره تلاش کنید."
|
||||
description = "یک لایه امنیتی اضافی به حساب خود اضافه کنید."
|
||||
enableButton = "فعالسازی احراز هویت دومرحلهای"
|
||||
disableButton = "غیرفعالسازی احراز هویت دومرحلهای"
|
||||
setupTitle = "راهاندازی احراز هویت دومرحلهای"
|
||||
setupDescription = "کد QR را با برنامه احراز هویت خود اسکن کنید، سپس کد ۶ رقمی را برای تأیید وارد کنید."
|
||||
manualKey = "کلید راهاندازی دستی"
|
||||
secretWarning = "این کلید را محرمانه نگه دارید. هر کسی که به آن دسترسی داشته باشد میتواند کدهای احراز هویت معتبر تولید کند."
|
||||
codePlaceholder = "کد ۶ رقمی را وارد کنید"
|
||||
confirmEnable = "فعالسازی"
|
||||
disableTitle = "غیرفعال کردن احراز هویت دومرحلهای"
|
||||
disableDescription = "برای غیرفعال کردن احراز هویت دومرحلهای، یک کد احراز هویت معتبر وارد کنید."
|
||||
codeLabel = "کد احراز هویت"
|
||||
confirmDisable = "غیرفعالسازی"
|
||||
ssoDescription = "احراز هویت دومرحلهای برای حسابهای Single Sign-On توسط ارائهدهنده هویت شما مدیریت میشود."
|
||||
ssoManaged = "MFA را از طریق ارائهدهنده هویت خود پیکربندی کنید."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "تنظیمات کنترل کاربران"
|
||||
header = "تنظیمات کنترل کاربران توسط ادمین"
|
||||
@@ -952,7 +925,6 @@ title = "تقسیم خودکار بر اساس اندازه/تعداد"
|
||||
desc = "تقسیم یک PDF به چند سند بر اساس اندازه، تعداد صفحات، یا تعداد اسناد"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "جایگزینی و معکوس کردن رنگ"
|
||||
desc = "جایگزینی یا معکوس کردن رنگها در اسناد PDF"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "اسکن خودکار پوشه"
|
||||
desc = "پیوند به راهنمای اسکن خودکار پوشه"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "راهنمای SSO"
|
||||
desc = "پیوند به راهنمای SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "راهاندازی Air-gapped"
|
||||
desc = "پیوند به راهنمای راهاندازی Air-gapped"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "افزودن رمز عبور"
|
||||
desc = "رمزگذاری سند PDF شما با رمز عبور."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "تغییر مجوزها"
|
||||
desc = "تغییر محدودیتها و مجوزهای سند"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "اتوماسیون"
|
||||
desc = "ساخت گردشکارهای چندمرحلهای با زنجیره کردن اقدامات PDF. مناسب برای کارهای تکرارشونده."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "PDFها را بر روی PDF دیگری همپوشانی میکند"
|
||||
title = "همپوشانی PDFها"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "ویرایشگر متن PDF"
|
||||
desc = "بازبینی و ویرایش خروجیهای JSON Stirling PDF با ویرایش گروهی متن و بازتولید PDF"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "بهجای PDF فایل HTML میانی را دانلود ک
|
||||
pdfaOptions = "گزینههای PDF/A"
|
||||
outputFormat = "قالب خروجی"
|
||||
pdfaNote = "PDF/A-1b سازگارتر است، PDF/A-2b از قابلیتهای بیشتری پشتیبانی میکند."
|
||||
pdfaFormat = "فرمت PDF/A"
|
||||
pdfaDigitalSignatureWarning = "PDF حاوی یک امضای دیجیتال است. این در مرحله بعد حذف خواهد شد."
|
||||
pdfxDigitalSignatureWarning = "این PDF شامل امضای دیجیتال است. این مورد در مرحله بعد حذف خواهد شد."
|
||||
pdfxDescription = "PDF/X زیرمجموعه استاندارد ISO از PDF برای چاپ قابلاعتماد و تبادل گرافیک است."
|
||||
fileFormat = "قالب فایل"
|
||||
wordDoc = "سند Word"
|
||||
wordDocExt = "سند Word (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "در حال حاضر برای چندین ورودی به طور همزم
|
||||
outputFormat = "فرمت خروجی"
|
||||
pdfWithDigitalSignature = "PDF حاوی یک امضای دیجیتال است. این در مرحله بعد حذف خواهد شد."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "چاپ,استاندارد,تبدیل,تولید,پیشازچاپ,آرشیو"
|
||||
title = "تبدیل PDF به PDF/X"
|
||||
header = "تبدیل PDF به PDF/X"
|
||||
credit = "این سرویس برای تبدیل PDF/X از Ghostscript استفاده میکند"
|
||||
submit = "تبدیل"
|
||||
tip = "در حال حاضر برای چند ورودی همزمان کار نمیکند"
|
||||
outputFormat = "فرمت خروجی"
|
||||
pdfWithDigitalSignature = "این PDF شامل امضای دیجیتال است. این مورد در مرحله بعد حذف خواهد شد."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc،docx،odt،ورد،تبدیل،فرمت،تبدیل،دفتر،مایکروسافت،فایل ورد"
|
||||
title = "PDF به ورد"
|
||||
@@ -3200,18 +3153,6 @@ other = "سایر"
|
||||
perPageInfo = "اطلاعات هر صفحه"
|
||||
tableOfContents = "فهرست مطالب"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "شناسایی نشد"
|
||||
passed = "موفق"
|
||||
failed = "ناموفق"
|
||||
compliant = "مطابق"
|
||||
nonCompliant = "نامنطبق"
|
||||
none = "استانداردی شناسایی نشد"
|
||||
passedCount = "موفق"
|
||||
failedCount = "ناموفق"
|
||||
noVerification = "هیچ راستیآزمایی انجام نشد"
|
||||
noVerificationDesc = "انطباق با استانداردهای PDF برای این سند راستیآزمایی نشده است."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "پیوستها"
|
||||
embeddedFiles = "فایلهای جاسازیشده"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} محدودیت"
|
||||
permsMixed = "برخی مجوزها محدود شدهاند"
|
||||
hasCompliance = "دارای استانداردهای انطباق"
|
||||
noCompliance = "بدون استانداردهای انطباق"
|
||||
compliancePassed = "مطابق با {{standards}}"
|
||||
complianceChecked = "استانداردها راستیآزمایی شدند ({{failed}} ناموفق)"
|
||||
basic = "اطلاعات پایه"
|
||||
documentInfo = "اطلاعات سند"
|
||||
securityTitle = "وضعیت امنیتی"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "اندازه تصویر"
|
||||
margin = "حاشیه"
|
||||
positionAndFormatting = "موقعیت و قالببندی"
|
||||
quickPosition = "یک موقعیت روی صفحه برای قرار دادن مهر انتخاب کنید."
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "هنگام افزودن مهر به PDF خطایی رخ داد."
|
||||
@@ -3594,14 +3508,6 @@ failed = "هنگام افزودن مهر به PDF خطایی رخ داد."
|
||||
[AddStampRequest.results]
|
||||
title = "نتایج مهر"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "حذف تصویر، عملیات صفحه، سرور"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "گذرواژه با موفقیت تغییر کرد! ل
|
||||
credentialsUpdated = "اطلاعات ورود شما بهروزرسانی شد. لطفاً دوباره وارد شوید."
|
||||
defaultCredentials = "مشخصات ورود پیشفرض"
|
||||
changePasswordWarning = "لطفاً پس از اولین ورود، گذرواژه خود را تغییر دهید"
|
||||
mfaRequired = "کد احراز هویت دومرحلهای لازم است"
|
||||
mfaCode = "کد احراز هویت"
|
||||
enterMfaCode = "کد ۶ رقمی را وارد کنید"
|
||||
mfaPromptTitle = "احراز هویت دومرحلهای"
|
||||
mfaPromptBody = "برای ادامه، کد احراز هویت را از برنامه احراز هویت خود وارد کنید."
|
||||
verifyingMfa = "در حال تأیید..."
|
||||
verifyMfa = "تأیید کد"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "نمای کلی Stirling PDF"
|
||||
@@ -4425,10 +4324,6 @@ title = "نیاز به حالت ورود"
|
||||
message = "برای تغییر تنظیمات مدیر باید حالت ورود فعال باشد. لطفاً SECURITY_ENABLELOGIN=true را در محیط خود یا security.enableLogin: true را در settings.yml تنظیم کرده و سپس سرور را راهاندازی مجدد کنید."
|
||||
readOnly = "تنظیمات زیر صرفاً مقادیر نمونه را نشان میدهند. برای مشاهده و ویرایش پیکربندی واقعی، حالت ورود را فعال کنید."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "مجوز Enterprise لازم است"
|
||||
message = "برای دسترسی به {{featureName}} به یک مجوز Enterprise نیاز است. شما در حال مشاهده دادههای نمایشی برای مرجع هستید."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "نیاز به راهاندازی مجدد"
|
||||
message = "تنظیمات با موفقیت ذخیره شدند. برای اعمال تغییرات، راهاندازی مجدد سرور لازم است."
|
||||
@@ -4568,13 +4463,6 @@ description = "حداکثر تعداد تلاش ناموفق ورود قبل ا
|
||||
label = "زمان بازنشانی ورود (دقیقه)"
|
||||
description = "مدتی که پس از آن تلاشهای ناموفق ورود بازنشانی میشوند"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "غیرفعال کردن محافظت CSRF"
|
||||
description = "غیرفعال کردن محافظت Cross-Site Request Forgery (توصیه نمیشود)"
|
||||
@@ -4883,48 +4771,6 @@ description = "نمایش پیامهای خطای تفصیلی برای کا
|
||||
title = "پایگاه داده"
|
||||
description = "پیکربندی تنظیمات اتصال پایگاه داده سفارشی برای استقرارهای سازمانی."
|
||||
configuration = "پیکربندی پایگاه داده"
|
||||
backupTitle = "پشتیبانگیری و بازیابی"
|
||||
backupDescription = "مدیریت نسخههای پشتیبان H2 بهصورت مستقیم از کنسول مدیریتی."
|
||||
loadError = "بارگذاری نسخههای پشتیبان پایگاه داده ناموفق بود"
|
||||
backupCreated = "نسخهٔ پشتیبان با موفقیت ایجاد شد"
|
||||
backupFailed = "ایجاد نسخهٔ پشتیبان ناموفق بود"
|
||||
deleteTitle = "حذف نسخهٔ پشتیبان"
|
||||
deleteConfirm = "این نسخهٔ پشتیبان حذف شود؟ این کار قابل بازگشت نیست."
|
||||
deleteSuccess = "نسخهٔ پشتیبان حذف شد"
|
||||
deleteFailed = "حذف نسخهٔ پشتیبان ناموفق بود"
|
||||
deleteConfirmAction = "حذف نسخهٔ پشتیبان"
|
||||
downloadFailed = "دانلود نسخهٔ پشتیبان ناموفق بود"
|
||||
version = "نسخهٔ H2"
|
||||
embedded = "H2 تعبیهشده"
|
||||
external = "پایگاه دادهٔ خارجی"
|
||||
h2Only = "نسخههای پشتیبان فقط برای پایگاه دادهٔ H2 تعبیهشده در دسترس هستند."
|
||||
h2Hint = "نوع پایگاه داده را روی H2 تنظیم کرده و پایگاه دادهٔ سفارشی را غیرفعال کنید تا پشتیبانگیری و بازیابی فعال شود."
|
||||
manageBackups = "مدیریت نسخههای پشتیبان"
|
||||
refresh = "تازهسازی"
|
||||
createBackup = "ایجاد نسخهٔ پشتیبان"
|
||||
uploadTitle = "بارگذاری و درونریزی"
|
||||
chooseFile = "یک فایل پشتیبان .sql انتخاب کنید"
|
||||
importFromUpload = "درونریزی بارگذاریشده"
|
||||
confirmImportTitle = "تأیید درونریزی پایگاه داده"
|
||||
overwriteWarning = "هشدار: این کار پایگاه دادهٔ فعلی را بازنویسی میکند."
|
||||
overwriteWarningBody = "تمام دادههای موجود با نسخهٔ پشتیبان بارگذاریشده جایگزین خواهند شد. این عملیات غیرقابل بازگشت است."
|
||||
confirmCodeLabel = "برای ادامه، کد تأیید را وارد کنید"
|
||||
enterCode = "کد نمایشدادهشده در بالا را وارد کنید"
|
||||
confirmImport = "تأیید درونریزی"
|
||||
codeMismatch = "کد تأیید مطابقت ندارد"
|
||||
codeMismatchBody = "برای ادامه، کد را دقیقاً مطابق نمایش وارد کنید."
|
||||
selectFile = "لطفاً یک فایل .sql برای درونریزی انتخاب کنید"
|
||||
importSuccess = "نسخهٔ پشتیبان با موفقیت درونریزی شد"
|
||||
importFailed = "درونریزی نسخهٔ پشتیبان ناموفق بود"
|
||||
noBackups = "هنوز هیچ نسخهٔ پشتیبان یافت نشده است."
|
||||
unavailable = "فهرست نسخههای پشتیبان برای پیکربندی فعلی پایگاه داده در دسترس نیست."
|
||||
fileName = "فایل"
|
||||
created = "ایجاد شده"
|
||||
size = "اندازه"
|
||||
actions = "اقدامات"
|
||||
download = "دانلود"
|
||||
import = "درونریزی"
|
||||
delete = "حذف"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "فعالسازی پایگاه داده سفارشی"
|
||||
@@ -5002,26 +4848,6 @@ description = "حداکثر DPI برای پردازش تصویر (0 = نامحد
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "دایرکتوری Tessdata"
|
||||
description = "مسیر دایرکتوری tessdata برای فایلهای زبان OCR"
|
||||
downloadMissingTitle = "هیچ زبانی انتخاب نشده است"
|
||||
downloadMissingBody = "لطفاً دستکم یک زبان برای دانلود انتخاب کنید."
|
||||
downloadSuccessTitle = "زبانها دانلود شدند"
|
||||
downloadSuccessBody = "زبانهای انتخابشدهٔ tessdata ذخیره شدند."
|
||||
downloadErrorTitle = "دانلود ناموفق بود"
|
||||
loadingLanguages = "در حال بارگذاری زبانهای نصبشدهٔ tessdata..."
|
||||
installedLanguages = "زبانهای نصبشدهٔ tessdata"
|
||||
noLanguages = "هیچ زبان tessdata در پوشهٔ پیکربندیشده یافت نشد."
|
||||
downloadLabel = "دانلود زبانهای اضافی tessdata"
|
||||
downloadPlaceholder = "انتخاب زبانها"
|
||||
downloadNothingFound = "هیچ زبان اضافیای یافت نشد"
|
||||
permissionNotice = "مسیر tessdata قابل نوشتن نیست. دانلودها در مرورگر باز میشوند؛ لطفاً فایلهای .traineddata را بهصورت دستی در پوشهٔ tessdata ذخیره کنید."
|
||||
manualLinks = "دانلود دستی: روی پیوندها کلیک کنید و فایلها را در پوشهٔ tessdata قرار دهید."
|
||||
downloadButton = "دانلود زبانهای انتخابشده"
|
||||
downloadInvalidTitle = "انتخاب نامعتبر"
|
||||
downloadInvalidBody = "برخی از زبانهای انتخابشده برای دانلود در دسترس نیستند. لطفاً تازهسازی کنید و از فهرست انتخاب کنید."
|
||||
downloadErrorNetwork = "دانلود به دلیل خطای شبکه ناموفق بود. لطفاً اتصال خود را بررسی کرده و دوباره تلاش کنید."
|
||||
downloadErrorServer = "سرور هنگام دانلود زبانهای tessdata با خطا مواجه شد. لطفاً بعداً دوباره تلاش کنید."
|
||||
downloadErrorPermission = "پوشهٔ Tessdata قابل نوشتن نیست: {{message}}. لطفاً یک پوشهٔ قابل نوشتن انتخاب کنید (مثلاً در پوشهٔ دادههای برنامه) یا مجوزها را تنظیم کنید."
|
||||
downloadErrorGeneric = "دانلود ناموفق بود: {{message}}. لطفاً بعداً دوباره تلاش کنید."
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "غیرفعال کردن پاکسازی HTML"
|
||||
@@ -5525,7 +5351,6 @@ desc = "با زنجیرهکردن اقدامات PDF، گردشکار چن
|
||||
invalidStep = "گام نامعتبر"
|
||||
reviewTitle = "نتایج اتوماسیون"
|
||||
copyToSaved = "کپی به ذخیرهشدهها"
|
||||
exportForFolderScanning = "برونبری برای اسکن پوشه"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "فایلها را برای پردازش با این اتوماسیون انتخاب کنید"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "ایجاد اتوماسیون"
|
||||
editTitle = "ویرایش اتوماسیون"
|
||||
intro = "اتوماسیونها ابزارها را بهصورت ترتیبی اجرا میکنند. برای شروع، ابزارها را به ترتیبی که میخواهید اجرا شوند اضافه کنید."
|
||||
save = "ذخیره اتوماسیون"
|
||||
exportForFolderScanning = "برونبری برای اسکن پوشه"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "نام اتوماسیون"
|
||||
@@ -5922,11 +5746,9 @@ username = "نام کاربری (ایمیل)"
|
||||
usernamePlaceholder = "user@example.com"
|
||||
password = "رمز عبور"
|
||||
passwordPlaceholder = "رمز عبور را وارد کنید"
|
||||
passwordRequired = "رمز عبور الزامی است"
|
||||
role = "نقش"
|
||||
team = "تیم (اختیاری)"
|
||||
teamPlaceholder = "یک تیم انتخاب کنید"
|
||||
authType = "نوع احراز هویت"
|
||||
forcePasswordChange = "اجبار تغییر رمز عبور در اولین ورود"
|
||||
cancel = "انصراف"
|
||||
submit = "افزودن عضو"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "نام کاربری و رمز عبور الزامی است"
|
||||
passwordTooShort = "رمز عبور باید حداقل ۶ نویسه باشد"
|
||||
success = "کاربر با موفقیت ایجاد شد"
|
||||
error = "ایجاد کاربر ناموفق بود"
|
||||
forceMFA = "الزام راهاندازی MFA در ورود بعدی"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "رمز عبور"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "کاربر از طریق ارائهدهندهٔ SSO احراز هویت خواهد شد"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "ویرایش عضو"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "{{count}} جایگاه کاربر در دسترس است"
|
||||
noSlotsAvailable = "هیچ جایگاهی در دسترس نیست"
|
||||
currentUsage = "در حال استفاده از {{current}} از {{max}} مجوز کاربر"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "MFA برای کاربر با موفقیت غیرفعال شد"
|
||||
adminDisableError = "غیرفعال کردن MFA برای کاربر ناموفق بود"
|
||||
disableByAdmin = "غیرفعال کردن MFA"
|
||||
|
||||
[workspace.teams]
|
||||
title = "تیمها"
|
||||
description = "مدیریت تیمها و سازماندهی اعضای فضای کاری"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "اندپوینت"
|
||||
visits = "بازدیدها"
|
||||
percentage = "درصد"
|
||||
noData = "دادهای در دسترس نیست"
|
||||
unknownEndpoint = "نقطه پایانی ناشناخته"
|
||||
|
||||
[backendHealth]
|
||||
checking = "در حال بررسی وضعیت بکاند..."
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "Sélectionner une opération"
|
||||
addOperationButton = "Ajouter une opération"
|
||||
pipelineHeader = "Pipeline :"
|
||||
saveButton = "Télécharger"
|
||||
saveForFolderScanning = "Enregistrer pour l'analyse de dossier"
|
||||
validateButton = "Valider"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "Plein écran"
|
||||
[settings.general.updates]
|
||||
title = "Mises à jour logicielles"
|
||||
description = "Rechercher des mises à jour et voir les informations de version"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "Version actuelle"
|
||||
latestVersion = "Dernière version"
|
||||
checkForUpdates = "Rechercher des mises à jour"
|
||||
viewDetails = "Voir les détails"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "Paramètres du navigateur"
|
||||
syncToBrowser = "Synchroniser : Compte → Navigateur"
|
||||
syncToAccount = "Synchroniser : Compte ← Navigateur"
|
||||
|
||||
[account.mfa]
|
||||
title = "Authentification à deux facteurs"
|
||||
setupFailed = "Impossible de démarrer la configuration de l'authentification à deux facteurs. Veuillez réessayer."
|
||||
codeRequired = "Saisissez le code d'authentification pour continuer."
|
||||
enabled = "Authentification à deux facteurs activée."
|
||||
enableFailed = "Impossible d'activer l'authentification à deux facteurs. Vérifiez le code et réessayez."
|
||||
disabled = "Authentification à deux facteurs désactivée."
|
||||
disableFailed = "Impossible de désactiver l'authentification à deux facteurs. Vérifiez le code et réessayez."
|
||||
description = "Ajoutez une couche de sécurité supplémentaire à votre compte."
|
||||
enableButton = "Activer l'authentification à deux facteurs"
|
||||
disableButton = "Désactiver l'authentification à deux facteurs"
|
||||
setupTitle = "Configurer l'authentification à deux facteurs"
|
||||
setupDescription = "Scannez le code QR avec votre application d'authentification, puis saisissez le code à 6 chiffres pour confirmer."
|
||||
manualKey = "Clé de configuration manuelle"
|
||||
secretWarning = "Conservez cette clé privée. Toute personne y ayant accès peut générer des codes d'authentification valides."
|
||||
codePlaceholder = "Saisir le code à 6 chiffres"
|
||||
confirmEnable = "Activer"
|
||||
disableTitle = "Désactiver l'authentification à deux facteurs"
|
||||
disableDescription = "Saisissez un code d'authentification valide pour désactiver l'authentification à deux facteurs."
|
||||
codeLabel = "Code d'authentification"
|
||||
confirmDisable = "Désactiver"
|
||||
ssoDescription = "L'authentification à deux facteurs est gérée par votre fournisseur d'identité pour les comptes à authentification unique."
|
||||
ssoManaged = "Configurez la MFA via votre fournisseur d'identité."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "Administration des paramètres des utilisateurs"
|
||||
header = "Administration des paramètres des utilisateurs"
|
||||
@@ -952,7 +925,6 @@ title = "Scinder auto taille/pages"
|
||||
desc = "Séparer un PDF unique en plusieurs documents en fonction de la taille, du nombre de pages ou du nombre de documents."
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Remplacer & inverser couleurs"
|
||||
desc = "Remplacer ou inverser les couleurs dans les documents PDF"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "Scan auto de dossiers"
|
||||
desc = "Lien vers le guide d’analyse de dossier automatisée"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Guide SSO"
|
||||
desc = "Lien vers le guide SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Installation isolée"
|
||||
desc = "Lien vers le guide d’installation isolée (air-gapped)"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Ajouter un mot de passe"
|
||||
desc = "Chiffrez votre PDF avec un mot de passe."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Modifier les permissions"
|
||||
desc = "Modifier les restrictions et permissions du document"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "Automatiser"
|
||||
desc = "Créez des workflows multi-étapes en enchaînant des actions PDF. Idéal pour les tâches récurrentes."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Superposer un PDF sur un autre"
|
||||
title = "Superposer des PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Éditeur de texte PDF"
|
||||
desc = "Afficher et modifier les exports JSON de Stirling PDF avec édition de texte groupée et régénération du PDF"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "Télécharger le fichier HTML intermédiaire au lieu du PDF"
|
||||
pdfaOptions = "Options PDF/A"
|
||||
outputFormat = "Format de sortie"
|
||||
pdfaNote = "PDF/A-1b est plus compatible, PDF/A-2b prend en charge davantage de fonctionnalités."
|
||||
pdfaFormat = "Format PDF/A"
|
||||
pdfaDigitalSignatureWarning = "Le PDF contient une signature numérique. Elle sera supprimée dans l'étape suivante."
|
||||
pdfxDigitalSignatureWarning = "Le PDF contient une signature numérique. Elle sera supprimée à l'étape suivante."
|
||||
pdfxDescription = "PDF/X est un sous-ensemble PDF conforme à l'ISO pour une impression fiable et un échange graphique."
|
||||
fileFormat = "Format de fichier"
|
||||
wordDoc = "Document Word"
|
||||
wordDocExt = "Document Word (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "Ne fonctionne actuellement pas pour plusieurs entrées à la fois"
|
||||
outputFormat = "Format de sortie"
|
||||
pdfWithDigitalSignature = "Le PDF contient une signature numérique. Elle sera supprimée dans l'étape suivante."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "impression,standard,conversion,production,prépresse,archive"
|
||||
title = "PDF vers PDF/X"
|
||||
header = "PDF vers PDF/X"
|
||||
credit = "Ce service utilise Ghostscript pour la conversion PDF/X"
|
||||
submit = "Convertir"
|
||||
tip = "Ne fonctionne actuellement pas avec plusieurs entrées à la fois"
|
||||
outputFormat = "Format de sortie"
|
||||
pdfWithDigitalSignature = "Le PDF contient une signature numérique. Elle sera supprimée à l'étape suivante."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc, docx, odt, word, transformation, format, conversion, office, microsoft, docfile"
|
||||
title = "PDF en Word"
|
||||
@@ -3200,18 +3153,6 @@ other = "Autre"
|
||||
perPageInfo = "Informations par page"
|
||||
tableOfContents = "Table des matières"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "Non détecté"
|
||||
passed = "Réussi"
|
||||
failed = "Échec"
|
||||
compliant = "Conforme"
|
||||
nonCompliant = "Non conforme"
|
||||
none = "Aucune norme détectée"
|
||||
passedCount = "réussis"
|
||||
failedCount = "échoués"
|
||||
noVerification = "Aucune vérification effectuée"
|
||||
noVerificationDesc = "La conformité aux normes PDF n'a pas été vérifiée pour ce document."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "Pièces jointes"
|
||||
embeddedFiles = "Fichiers intégrés"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} restrictions"
|
||||
permsMixed = "Certaines autorisations sont restreintes"
|
||||
hasCompliance = "Respecte des normes de conformité"
|
||||
noCompliance = "Aucune norme de conformité"
|
||||
compliancePassed = "Conforme à {{standards}}"
|
||||
complianceChecked = "Normes vérifiées ({{failed}} échoués)"
|
||||
basic = "Informations de base"
|
||||
documentInfo = "Informations sur le document"
|
||||
securityTitle = "État de sécurité"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "Taille de l’image"
|
||||
margin = "Marge"
|
||||
positionAndFormatting = "Position et mise en forme"
|
||||
quickPosition = "Sélectionnez une position sur la page pour placer le tampon."
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "Une erreur s’est produite lors de l’ajout du tampon au PDF."
|
||||
@@ -3594,14 +3508,6 @@ failed = "Une erreur s’est produite lors de l’ajout du tampon au PDF."
|
||||
[AddStampRequest.results]
|
||||
title = "Résultats du tampon"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "Images,Remove Image,Page operations,Back end,server side"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "Mot de passe modifié avec succès ! Veuillez vous co
|
||||
credentialsUpdated = "Vos identifiants ont été mis à jour. Veuillez vous reconnecter."
|
||||
defaultCredentials = "Identifiants de connexion par défaut"
|
||||
changePasswordWarning = "Veuillez changer votre mot de passe après votre première connexion"
|
||||
mfaRequired = "Code à deux facteurs requis"
|
||||
mfaCode = "Code d'authentification"
|
||||
enterMfaCode = "Saisir le code à 6 chiffres"
|
||||
mfaPromptTitle = "Authentification à deux facteurs"
|
||||
mfaPromptBody = "Saisissez le code d'authentification provenant de votre application d'authentification pour continuer."
|
||||
verifyingMfa = "Vérification..."
|
||||
verifyMfa = "Vérifier le code"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "Aperçu de Stirling PDF"
|
||||
@@ -4425,10 +4324,6 @@ title = "Mode de connexion requis"
|
||||
message = "Le mode de connexion doit être activé pour modifier les paramètres d’administration. Veuillez définir SECURITY_ENABLELOGIN=true dans votre environnement ou security.enableLogin: true dans settings.yml, puis redémarrer le serveur."
|
||||
readOnly = "Les paramètres ci-dessous affichent des valeurs d’exemple à titre de référence. Activez le mode de connexion pour afficher et modifier la configuration réelle."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "Licence Entreprise requise"
|
||||
message = "Une licence Entreprise est requise pour accéder à {{featureName}}. Vous consultez des données de démonstration à titre de référence."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "Redémarrage requis"
|
||||
message = "Les paramètres ont été enregistrés avec succès. Un redémarrage du serveur est requis pour que les changements prennent effet."
|
||||
@@ -4568,13 +4463,6 @@ description = "Nombre maximal de tentatives de connexion échouées avant le ver
|
||||
label = "Délai de réinitialisation (minutes)"
|
||||
description = "Délai avant la réinitialisation du compteur de tentatives de connexion échouées"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Désactiver la protection CSRF"
|
||||
description = "Désactiver la protection contre la falsification de requête intersites (non recommandé)"
|
||||
@@ -4883,48 +4771,6 @@ description = "Afficher des messages d’erreur détaillés pour les utilisateur
|
||||
title = "Base de données"
|
||||
description = "Configurer des paramètres de connexion à une base de données personnalisée pour les déploiements d’entreprise."
|
||||
configuration = "Configuration de la base de données"
|
||||
backupTitle = "Sauvegardes & restauration"
|
||||
backupDescription = "Gérez les sauvegardes H2 directement depuis la console d'administration."
|
||||
loadError = "Échec du chargement des sauvegardes de la base de données"
|
||||
backupCreated = "Sauvegarde créée avec succès"
|
||||
backupFailed = "Échec de la création de la sauvegarde"
|
||||
deleteTitle = "Supprimer la sauvegarde"
|
||||
deleteConfirm = "Supprimer cette sauvegarde ? Cette action est irréversible."
|
||||
deleteSuccess = "Sauvegarde supprimée"
|
||||
deleteFailed = "Échec de la suppression de la sauvegarde"
|
||||
deleteConfirmAction = "Supprimer la sauvegarde"
|
||||
downloadFailed = "Échec du téléchargement de la sauvegarde"
|
||||
version = "Version H2"
|
||||
embedded = "H2 intégré"
|
||||
external = "Base de données externe"
|
||||
h2Only = "Les sauvegardes ne sont disponibles que pour la base de données H2 intégrée."
|
||||
h2Hint = "Définissez le type de base de données sur H2 et désactivez la base de données personnalisée pour activer la sauvegarde et la restauration."
|
||||
manageBackups = "Gérer les sauvegardes"
|
||||
refresh = "Actualiser"
|
||||
createBackup = "Créer une sauvegarde"
|
||||
uploadTitle = "Téléversement & import"
|
||||
chooseFile = "Choisir un fichier de sauvegarde .sql"
|
||||
importFromUpload = "Importer le téléversement"
|
||||
confirmImportTitle = "Confirmer l’importation de la base de données"
|
||||
overwriteWarning = "Avertissement : ceci écrasera la base de données actuelle."
|
||||
overwriteWarningBody = "Toutes les données existantes seront remplacées par la sauvegarde téléversée. Cette action est irréversible."
|
||||
confirmCodeLabel = "Saisissez le code de confirmation pour continuer"
|
||||
enterCode = "Saisissez le code affiché ci-dessus"
|
||||
confirmImport = "Confirmer l’importation"
|
||||
codeMismatch = "Le code de confirmation ne correspond pas"
|
||||
codeMismatchBody = "Veuillez saisir le code exactement tel qu’affiché pour continuer."
|
||||
selectFile = "Veuillez sélectionner un fichier .sql à importer"
|
||||
importSuccess = "Sauvegarde importée avec succès"
|
||||
importFailed = "Échec de l’importation de la sauvegarde"
|
||||
noBackups = "Aucune sauvegarde trouvée pour le moment."
|
||||
unavailable = "La liste des sauvegardes est indisponible pour la configuration actuelle de la base de données."
|
||||
fileName = "Fichier"
|
||||
created = "Créé"
|
||||
size = "Taille"
|
||||
actions = "Actions"
|
||||
download = "Télécharger"
|
||||
import = "Importer"
|
||||
delete = "Supprimer"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "Activer la base de données personnalisée"
|
||||
@@ -5002,26 +4848,6 @@ description = "DPI maximum pour le traitement d’images (0 = illimité)"
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "Répertoire tessdata"
|
||||
description = "Chemin vers le répertoire tessdata pour les fichiers de langue OCR"
|
||||
downloadMissingTitle = "Aucune langue sélectionnée"
|
||||
downloadMissingBody = "Veuillez sélectionner au moins une langue à télécharger."
|
||||
downloadSuccessTitle = "Langues téléchargées"
|
||||
downloadSuccessBody = "Les langues tessdata sélectionnées ont été enregistrées."
|
||||
downloadErrorTitle = "Échec du téléchargement"
|
||||
loadingLanguages = "Chargement des langues tessdata installées..."
|
||||
installedLanguages = "Langues tessdata installées"
|
||||
noLanguages = "Aucune langue tessdata trouvée dans le répertoire configuré."
|
||||
downloadLabel = "Télécharger des langues tessdata supplémentaires"
|
||||
downloadPlaceholder = "Sélectionner des langues"
|
||||
downloadNothingFound = "Aucune langue supplémentaire trouvée"
|
||||
permissionNotice = "Le chemin tessdata n’est pas accessible en écriture. Les téléchargements seront ouverts dans le navigateur ; veuillez enregistrer manuellement les fichiers .traineddata dans le dossier tessdata."
|
||||
manualLinks = "Téléchargements manuels : cliquez sur les liens et placez les fichiers dans le dossier tessdata."
|
||||
downloadButton = "Télécharger les langues sélectionnées"
|
||||
downloadInvalidTitle = "Sélection invalide"
|
||||
downloadInvalidBody = "Certaines des langues sélectionnées ne sont pas disponibles au téléchargement. Veuillez actualiser et choisir dans la liste."
|
||||
downloadErrorNetwork = "Échec du téléchargement en raison d’une erreur réseau. Veuillez vérifier votre connexion et réessayer."
|
||||
downloadErrorServer = "Le serveur a rencontré une erreur lors du téléchargement des langues tessdata. Veuillez réessayer plus tard."
|
||||
downloadErrorPermission = "Le répertoire tessdata n’est pas accessible en écriture : {{message}}. Veuillez choisir un répertoire accessible en écriture (par exemple dans le dossier de données de l’application) ou ajuster les autorisations."
|
||||
downloadErrorGeneric = "Échec du téléchargement : {{message}}. Veuillez réessayer plus tard."
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "Désactiver la sanitisation HTML"
|
||||
@@ -5525,7 +5351,6 @@ desc = "Créez des workflows multi-étapes en enchaînant des actions PDF. Idéa
|
||||
invalidStep = "Étape invalide"
|
||||
reviewTitle = "Résultats de l’automatisation"
|
||||
copyToSaved = "Copier vers Enregistré"
|
||||
exportForFolderScanning = "Exporter pour l'analyse de dossier"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "Sélectionnez des fichiers à traiter avec cette automatisation"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "Créer une automatisation"
|
||||
editTitle = "Modifier l’automatisation"
|
||||
intro = "Les automatisations exécutent les outils séquentiellement. Pour commencer, ajoutez les outils dans l’ordre d’exécution souhaité."
|
||||
save = "Enregistrer l’automatisation"
|
||||
exportForFolderScanning = "Exporter pour l'analyse de dossier"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "Nom de l’automatisation"
|
||||
@@ -5922,11 +5746,9 @@ username = "Nom d’utilisateur (e-mail)"
|
||||
usernamePlaceholder = "user@example.com"
|
||||
password = "Mot de passe"
|
||||
passwordPlaceholder = "Saisir le mot de passe"
|
||||
passwordRequired = "Le mot de passe est requis"
|
||||
role = "Rôle"
|
||||
team = "Équipe (facultatif)"
|
||||
teamPlaceholder = "Sélectionner une équipe"
|
||||
authType = "Type d’authentification"
|
||||
forcePasswordChange = "Forcer le changement de mot de passe à la première connexion"
|
||||
cancel = "Annuler"
|
||||
submit = "Ajouter le membre"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "Le nom d’utilisateur et le mot de passe sont obligatoires"
|
||||
passwordTooShort = "Le mot de passe doit comporter au moins 6 caractères"
|
||||
success = "Utilisateur créé avec succès"
|
||||
error = "Échec de la création de l’utilisateur"
|
||||
forceMFA = "Forcer la configuration MFA à la prochaine connexion"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "Mot de passe"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "L’utilisateur s’authentifiera via le fournisseur SSO"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "Modifier le membre"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "{{count}} place(s) utilisateur disponibles"
|
||||
noSlotsAvailable = "Aucune place disponible"
|
||||
currentUsage = "Utilise actuellement {{current}} sur {{max}} licences utilisateur"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "MFA désactivée avec succès pour l'utilisateur"
|
||||
adminDisableError = "Échec de la désactivation de la MFA pour l'utilisateur"
|
||||
disableByAdmin = "Désactiver la MFA"
|
||||
|
||||
[workspace.teams]
|
||||
title = "Équipes"
|
||||
description = "Gérez les équipes et organisez les membres de l’espace de travail"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "Endpoint"
|
||||
visits = "Visites"
|
||||
percentage = "Pourcentage"
|
||||
noData = "Aucune donnée disponible"
|
||||
unknownEndpoint = "Point de terminaison inconnu"
|
||||
|
||||
[backendHealth]
|
||||
checking = "Vérification de l’état du backend..."
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "Roghnaigh Oibríocht"
|
||||
addOperationButton = "Cuir oibríocht leis"
|
||||
pipelineHeader = "Píblíne:"
|
||||
saveButton = "Íosluchtaigh"
|
||||
saveForFolderScanning = "Sábháil le haghaidh Scanadh Fillteáin"
|
||||
validateButton = "Bailíochtaigh"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "Lánscáileán"
|
||||
[settings.general.updates]
|
||||
title = "Nuashonruithe Bogearraí"
|
||||
description = "Seiceáil le haghaidh nuashonruithe agus féach faisnéis leagain"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "Leagan Reatha"
|
||||
latestVersion = "Leagan is Déanaí"
|
||||
checkForUpdates = "Seiceáil le haghaidh Nuashonruithe"
|
||||
viewDetails = "Féach Sonraí"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "Socrú Brabhsálaí Gréasáin"
|
||||
syncToBrowser = "Cuntas Sync -> Brabhsálaí"
|
||||
syncToAccount = "Cuntas Sioncronaigh <- Brabhsálaí"
|
||||
|
||||
[account.mfa]
|
||||
title = "Fíordheimhniú dhá fhachtóir"
|
||||
setupFailed = "Níorbh fhéidir socrú dhá fhachtóir a thosú. Bain triail eile as, le do thoil."
|
||||
codeRequired = "Cuir isteach an cód fíordheimhnithe chun leanúint ar aghaidh."
|
||||
enabled = "Cumasaíodh fíordheimhniú dhá fhachtóir."
|
||||
enableFailed = "Níorbh fhéidir fíordheimhniú dhá fhachtóir a chumasú. Seiceáil an cód agus bain triail eile as."
|
||||
disabled = "Díchumasaíodh fíordheimhniú dhá fhachtóir."
|
||||
disableFailed = "Níorbh fhéidir fíordheimhniú dhá fhachtóir a dhíchumasú. Seiceáil an cód agus bain triail eile as."
|
||||
description = "Cuir ciseal breise slándála le do chuntas."
|
||||
enableButton = "Cumasaigh fíordheimhniú dhá fhachtóir"
|
||||
disableButton = "Díchumasaigh fíordheimhniú dhá fhachtóir"
|
||||
setupTitle = "Socraigh fíordheimhniú dhá fhachtóir"
|
||||
setupDescription = "Scan an cód QR le d’aip fíordheimhnitheora, ansin cuir isteach an cód 6 dhigit chun deimhniú."
|
||||
manualKey = "Eochair shocraithe láimhe"
|
||||
secretWarning = "Coinnigh an eochair seo príobháideach. Féadfaidh aon duine a bhfuil rochtain aige/aici cóid fíordheimhnithe bailí a ghiniúint."
|
||||
codePlaceholder = "Cuir isteach cód 6 dhigit"
|
||||
confirmEnable = "Cumasaigh"
|
||||
disableTitle = "Díchumasaigh fíordheimhniú dhá fhachtóir"
|
||||
disableDescription = "Cuir isteach cód fíordheimhnithe bailí chun fíordheimhniú dhá fhachtóir a dhíchumasú."
|
||||
codeLabel = "Cód fíordheimhnithe"
|
||||
confirmDisable = "Díchumasaigh"
|
||||
ssoDescription = "Is ag do sholáthraí aitheantais a bhainistítear fíordheimhniú dhá fhachtóir do chuntais le síniú isteach aonair."
|
||||
ssoManaged = "Cumraigh MFA trí do sholáthraí aitheantais."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "Socruithe Rialaithe Úsáideora"
|
||||
header = "Socruithe Rialaithe Úsáideoir Riaracháin"
|
||||
@@ -952,7 +925,6 @@ title = "Scoilt auto: méid/líon"
|
||||
desc = "Scoilt PDF amháin i ndoiciméid iolracha bunaithe ar mhéid, líon na leathanach, nó comhaireamh doiciméad"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Ionadaigh & Inbhéartaigh Dath"
|
||||
desc = "Ionadaigh nó inbhéartaigh dathanna i gcáipéisí PDF"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "Scanadh Fillteán Uathoibríoch"
|
||||
desc = "Nasc le treoir scantha fillteán uathoibrithe"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Treoir SSO"
|
||||
desc = "Nasc le treoir SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Socrú Air-gapped"
|
||||
desc = "Nasc le treoir socraithe Air-gapped"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Cuir Pasfhocal leis"
|
||||
desc = "Criptigh do dhoiciméad PDF le focal faire."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Athrú Ceadanna"
|
||||
desc = "Athraigh srianta agus ceadanna cáipéise"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "Uathoibrigh"
|
||||
desc = "Tóg sreafaí oibre ilchéime trí ghníomhartha PDF a nascadh le chéile. Foirfe do thascanna athfhillteacha."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Forleagain PDF ar bharr PDF eile"
|
||||
title = "Forleagan PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Eagarthóir Téacs PDF"
|
||||
desc = "Cuir téacs agus íomhánna atá ann cheana in eagar laistigh de PDFanna"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "Íoslódáil comhad idirmheánach HTML in ionad PDF"
|
||||
pdfaOptions = "Roghanna PDF/A"
|
||||
outputFormat = "Formáid Aschuir"
|
||||
pdfaNote = "Tá PDF/A-1b níos comhoiriúnaí, tacaíonn PDF/A-2b le níos mó gnéithe."
|
||||
pdfaFormat = "Formáid PDF/A"
|
||||
pdfaDigitalSignatureWarning = "Tá síniú digiteach ar an PDF. Bainfear é seo sa chéad chéim eile."
|
||||
pdfxDigitalSignatureWarning = "Tá síniú digiteach sa PDF. Bainfear é sa chéad chéim eile."
|
||||
pdfxDescription = "Is fo-thacar PDF de chaighdeán ISO é PDF/X le haghaidh priontála agus malartaithe grafaice iontaofa."
|
||||
fileFormat = "Formáid Comhaid"
|
||||
wordDoc = "Cáipéis Word"
|
||||
wordDocExt = "Cáipéis Word (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "Faoi láthair ní oibríonn sé le haghaidh ionchuir iolracha ag an am c
|
||||
outputFormat = "Formáid aschuir"
|
||||
pdfWithDigitalSignature = "Tá síniú digiteach ar an PDF. Bainfear é seo sa chéad chéim eile."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "priontáil,caighdeán,tiontú,táirgeadh,réamhphriontáil,cartlann"
|
||||
title = "PDF go PDF/X"
|
||||
header = "PDF go PDF/X"
|
||||
credit = "Úsáideann an tseirbhís seo Ghostscript le haghaidh tiontaithe PDF/X"
|
||||
submit = "Tiontaigh"
|
||||
tip = "Ní oibríonn sé faoi láthair le haghaidh ionchuir iolracha ag an am céanna"
|
||||
outputFormat = "Formáid aschuir"
|
||||
pdfWithDigitalSignature = "Tá síniú digiteach sa PDF. Bainfear é sa chéad chéim eile."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc, docx, odt, focal, claochlú, formáid, comhshó, oifig, microsoft, docfile"
|
||||
title = "PDF a thiontú go word"
|
||||
@@ -3200,18 +3153,6 @@ other = "Eile"
|
||||
perPageInfo = "Eolas in aghaidh an leathanaigh"
|
||||
tableOfContents = "Clár Ábhair"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "Níor braitheadh"
|
||||
passed = "D'éirigh leis"
|
||||
failed = "Theip air"
|
||||
compliant = "Comhlíontach"
|
||||
nonCompliant = "Neamhchomhlíontach"
|
||||
none = "Níor braitheadh caighdeáin ar bith"
|
||||
passedCount = "rite"
|
||||
failedCount = "teipthe"
|
||||
noVerification = "Ní dhearnadh aon fhíorú"
|
||||
noVerificationDesc = "Ní dhearnadh fíorú ar chomhlíonadh caighdeáin PDF don cháipéis seo."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "Iatáin"
|
||||
embeddedFiles = "Comhaid Leabaithe"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} srianta"
|
||||
permsMixed = "Tá roinnt ceadanna srianta"
|
||||
hasCompliance = "Tá caighdeáin chomhlíonta ann"
|
||||
noCompliance = "Gan chaighdeáin chomhlíonta"
|
||||
compliancePassed = "I gcomhréir le {{standards}}"
|
||||
complianceChecked = "Caighdeáin fíoraithe ({{failed}} teipthe)"
|
||||
basic = "Buneolas"
|
||||
documentInfo = "Eolas faoin Doiciméad"
|
||||
securityTitle = "Stádas Slándála"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "Méid Íomhá"
|
||||
margin = "Imeall"
|
||||
positionAndFormatting = "Suíomh & Formáidiú"
|
||||
quickPosition = "Roghnaigh suíomh ar an leathanach le stampa a chur."
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "Tharla earráid agus stampa á chur leis an PDF."
|
||||
@@ -3594,14 +3508,6 @@ failed = "Tharla earráid agus stampa á chur leis an PDF."
|
||||
[AddStampRequest.results]
|
||||
title = "Torthaí Stampa"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "Bain Íomhá, Oibríochtaí Leathanaigh, Cúl, taobh an fhreastalaí"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "Athraíodh an focal faire go rathúil! Logáil isteach
|
||||
credentialsUpdated = "Nuashonraíodh do dhintiúir. Logáil isteach arís, le do thoil."
|
||||
defaultCredentials = "Dintiúir logála isteach réamhshocraithe"
|
||||
changePasswordWarning = "Athraigh do fhocal faire tar éis duit logáil isteach don chéad uair"
|
||||
mfaRequired = "Cód dhá fhachtóir de dhíth"
|
||||
mfaCode = "Cód Fíordheimhnithe"
|
||||
enterMfaCode = "Cuir isteach cód 6 dhigit"
|
||||
mfaPromptTitle = "Fíordheimhniú dhá fhachtóir"
|
||||
mfaPromptBody = "Cuir isteach an cód fíordheimhnithe ó d’aip fíordheimhnitheora chun leanúint ar aghaidh."
|
||||
verifyingMfa = "Á fhíorú..."
|
||||
verifyMfa = "Fíoraigh an cód"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "Forbhreathnú Stirling PDF"
|
||||
@@ -4425,10 +4324,6 @@ title = "Mód Logála Isteach de Dhíth"
|
||||
message = "Ní mór mód logála isteach a bheith cumasaithe chun socruithe riarthóra a athrú. Socraigh SECURITY_ENABLELOGIN=true i do thimpeallacht nó security.enableLogin: true i settings.yml, agus atosaigh an freastalaí ansin."
|
||||
readOnly = "Taispeánann na socruithe thíos luachanna samplacha le tagairt. Cumasaigh mód logála isteach chun an chumraíocht iarbhír a fheiceáil agus a chur in eagar."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "Ceadúnas Fiontraíochta de dhíth"
|
||||
message = "Tá ceadúnas Fiontraíochta de dhíth chun rochtain a fháil ar {{featureName}}. Tá tú ag féachaint ar shonraí taispeána mar thagairt."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "Athtosú de Dhíth"
|
||||
message = "Sábháladh na socruithe go rathúil. Tá athtosú freastalaí de dhíth chun na hathruithe a chur i bhfeidhm."
|
||||
@@ -4568,13 +4463,6 @@ description = "Uasmhéid iarrachtaí logála isteach teipthe sula gcuirtear an c
|
||||
label = "Am Athshocraithe Logála Isteach (nóiméid)"
|
||||
description = "Am sula n‑athshocraítear iarrachtaí logála isteach teipthe"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Díchumasaigh Cosaint CSRF"
|
||||
description = "Díchumasaigh cosaint Cross-Site Request Forgery (ní mholtar)"
|
||||
@@ -4883,48 +4771,6 @@ description = "Taispeáin teachtaireachtaí earráide mionsonraithe d’úsáide
|
||||
title = "Bunachar Sonraí"
|
||||
description = "Cumraigh socruithe ceangail bunachair shonraí saincheaptha le haghaidh imscaradh fiontraíochta."
|
||||
configuration = "Cumraíocht Bunachair Sonraí"
|
||||
backupTitle = "Cúltacaí agus Athchóiriú"
|
||||
backupDescription = "Bainistigh cúltacaí H2 go díreach ón gconsól riaracháin."
|
||||
loadError = "Theip ar luchtú cúltacaí an bhunachair sonraí"
|
||||
backupCreated = "Cruthaíodh cúltaca go rathúil"
|
||||
backupFailed = "Theip ar chruthú cúltaca"
|
||||
deleteTitle = "Scrios cúltaca"
|
||||
deleteConfirm = "Scrios an cúltaca seo? Ní féidir é seo a chur ar ceal."
|
||||
deleteSuccess = "Scriosadh cúltaca"
|
||||
deleteFailed = "Theip ar scriosadh cúltaca"
|
||||
deleteConfirmAction = "Scrios an cúltaca"
|
||||
downloadFailed = "Theip ar íoslódáil cúltaca"
|
||||
version = "Leagan H2"
|
||||
embedded = "H2 leabaithe"
|
||||
external = "Bunachar Sonraí Seachtrach"
|
||||
h2Only = "Níl cúltacaí ar fáil ach don bhunachar sonraí H2 leabaithe."
|
||||
h2Hint = "Socraigh cineál an bhunachair sonraí mar H2 agus díchumasaigh an bunachar sonraí saincheaptha chun cúltaca agus athchóiriú a chumasú."
|
||||
manageBackups = "Bainistigh cúltacaí"
|
||||
refresh = "Athnuaigh"
|
||||
createBackup = "Cruthaigh cúltaca"
|
||||
uploadTitle = "Uaslódáil agus iompórtáil"
|
||||
chooseFile = "Roghnaigh comhad cúltaca .sql"
|
||||
importFromUpload = "Iompórtáil ón uaslódáil"
|
||||
confirmImportTitle = "Deimhnigh iompórtáil an bhunachair sonraí"
|
||||
overwriteWarning = "Rabhadh: Forscríobhfaidh sé seo an bunachar sonraí reatha."
|
||||
overwriteWarningBody = "Cuirfear na sonraí go léir atá ann in ionad na sonraí reatha leis an gcúltaca uaslódáilte. Ní féidir an gníomh seo a chur ar ceal."
|
||||
confirmCodeLabel = "Iontráil an cód dearbhaithe chun dul ar aghaidh"
|
||||
enterCode = "Iontráil an cód a thaispeántar thuas"
|
||||
confirmImport = "Deimhnigh an iompórtáil"
|
||||
codeMismatch = "Ní mheaitseálann an cód dearbhaithe"
|
||||
codeMismatchBody = "Iontráil an cód go díreach mar a thaispeántar é chun dul ar aghaidh."
|
||||
selectFile = "Roghnaigh comhad .sql le hiompórtáil"
|
||||
importSuccess = "Iompórtáladh cúltaca go rathúil"
|
||||
importFailed = "Theip ar iompórtáil cúltaca"
|
||||
noBackups = "Níor aimsíodh aon chúltacaí go fóill."
|
||||
unavailable = "Níl liosta na gcúltacaí ar fáil don chumraíocht bhunachair sonraí reatha."
|
||||
fileName = "Comhad"
|
||||
created = "Cruthaithe"
|
||||
size = "Méid"
|
||||
actions = "Gníomhartha"
|
||||
download = "Íoslódáil"
|
||||
import = "Iompórtáil"
|
||||
delete = "Scrios"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "Cumasaigh Bunachar Sonraí Saincheaptha"
|
||||
@@ -5002,26 +4848,6 @@ description = "DPI uasta le haghaidh próiseála íomhá (0 = gan teorainn)"
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "Comhadlann tessdata"
|
||||
description = "Conair chuig an gcomhadlann tessdata do chomhaid teanga OCR"
|
||||
downloadMissingTitle = "Níor roghnaíodh aon teanga"
|
||||
downloadMissingBody = "Roghnaigh teanga amháin ar a laghad le híoslódáil."
|
||||
downloadSuccessTitle = "Íoslódáladh na teangacha"
|
||||
downloadSuccessBody = "Sábháladh na teangacha tessdata roghnaithe."
|
||||
downloadErrorTitle = "Theip ar an íoslódáil"
|
||||
loadingLanguages = "Teangacha tessdata suiteáilte á lódáil..."
|
||||
installedLanguages = "Teangacha tessdata suiteáilte"
|
||||
noLanguages = "Níor aimsíodh teangacha tessdata sa chomhadlann chumraithe."
|
||||
downloadLabel = "Íoslódáil teangacha tessdata breise"
|
||||
downloadPlaceholder = "Roghnaigh teangacha"
|
||||
downloadNothingFound = "Níor aimsíodh teangacha breise"
|
||||
permissionNotice = "Níl cosán an tessdata inscríofa. Osclófar íoslódálacha sa bhrabhsálaí; sábháil na comhaid .traineddata de láimh sa chomhadlann tessdata le do thoil."
|
||||
manualLinks = "Íoslódálacha láimhe: cliceáil ar na naisc agus cuir na comhaid sa chomhadlann tessdata."
|
||||
downloadButton = "Íoslódáil na teangacha roghnaithe"
|
||||
downloadInvalidTitle = "Rogha neamhbhailí"
|
||||
downloadInvalidBody = "Níl roinnt de na teangacha roghnaithe ar fáil le híoslódáil. Athnuaigh agus roghnaigh ón liosta le do thoil."
|
||||
downloadErrorNetwork = "Theip ar an íoslódáil mar gheall ar earráid líonra. Seiceáil do cheangal agus bain triail eile as."
|
||||
downloadErrorServer = "Tharla earráid ar an bhfreastalaí agus teangacha tessdata á n-íoslódáil. Bain triail eile as níos déanaí."
|
||||
downloadErrorPermission = "Níl an chomhadlann tessdata inscríofa: {{message}}. Roghnaigh comhadlann inscríofa (m.sh. faoin bhfillteán sonraí feidhmchláir) nó coigeartaigh na ceadanna."
|
||||
downloadErrorGeneric = "Theip ar an íoslódáil: {{message}}. Bain triail eile as níos déanaí."
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "Díchumasaigh Sláintiú HTML"
|
||||
@@ -5525,7 +5351,6 @@ desc = "Tóg sreafaí oibre ilchéime trí ghníomhartha PDF a shlabhrú le ché
|
||||
invalidStep = "Céim neamhbhailí"
|
||||
reviewTitle = "Torthaí an Uathoibrithe"
|
||||
copyToSaved = "Cóipeáil chuig Sábháilte"
|
||||
exportForFolderScanning = "Easpórtáil le haghaidh Scanadh Fillteáin"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "Roghnaigh comhaid le próiseáil leis an uathoibriú seo"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "Cruthaigh Uathoibriú"
|
||||
editTitle = "Cuir Uathoibriú in Eagar"
|
||||
intro = "Ritheann uathoibrithe uirlisí as a chéile. Chun tosú, cuir uirlisí leis san ord ina bhfuil tú ag iarraidh iad a rith."
|
||||
save = "Sábháil an tUathoibriú"
|
||||
exportForFolderScanning = "Easpórtáil le haghaidh Scanadh Fillteáin"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "Ainm an Uathoibrithe"
|
||||
@@ -5922,11 +5746,9 @@ username = "Ainm Úsáideora (R-phost)"
|
||||
usernamePlaceholder = "úsáideoir@sampla.com"
|
||||
password = "Focal Faire"
|
||||
passwordPlaceholder = "Cuir isteach focal faire"
|
||||
passwordRequired = "Tá focal faire riachtanach"
|
||||
role = "Ról"
|
||||
team = "Foireann (Roghnach)"
|
||||
teamPlaceholder = "Roghnaigh foireann"
|
||||
authType = "Cineál Fíordheimhnithe"
|
||||
forcePasswordChange = "Cuir iallach ar athrú focail faire ag an gcéad logáil isteach"
|
||||
cancel = "Cealaigh"
|
||||
submit = "Cuir Ball Leis"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "Tá ainm úsáideora agus focal faire ag teastáil"
|
||||
passwordTooShort = "Caithfidh an focal faire a bheith ar a laghad 6 charachtar"
|
||||
success = "Cruthaíodh an t-úsáideoir go rathúil"
|
||||
error = "Theip ar úsáideoir a chruthú"
|
||||
forceMFA = "Cuir iallach ar shocrú MFA ag an gcéad logáil isteach eile"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "Focal faire"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "Déanfaidh an t-úsáideoir fíordheimhniú trí sholáthraí SSO"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "Cuir Ball in Eagar"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "{{count}} áit(í) úsáideora ar fáil"
|
||||
noSlotsAvailable = "Níl aon áiteanna ar fáil"
|
||||
currentUsage = "Faoi láthair tá {{current}} as {{max}} ceadúnas úsáideora in úsáid"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "Díchumasaíodh MFA don úsáideoir go rathúil"
|
||||
adminDisableError = "Níorbh fhéidir MFA a dhíchumasú don úsáideoir"
|
||||
disableByAdmin = "Díchumasaigh MFA"
|
||||
|
||||
[workspace.teams]
|
||||
title = "Foirne"
|
||||
description = "Bainistigh foirne agus eagraigh baill an spáis oibre"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "Críochphointe"
|
||||
visits = "Cuairteanna"
|
||||
percentage = "Céatadán"
|
||||
noData = "Níl aon sonraí ar fáil"
|
||||
unknownEndpoint = "Críochphointe anaithnid"
|
||||
|
||||
[backendHealth]
|
||||
checking = "Stádas an chúlchórais á sheiceáil..."
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "ऑपरेशन चुनें"
|
||||
addOperationButton = "ऑपरेशन जोड़ें"
|
||||
pipelineHeader = "पाइपलाइन:"
|
||||
saveButton = "डाउनलोड करें"
|
||||
saveForFolderScanning = "फ़ोल्डर स्कैनिंग के लिए सहेजें"
|
||||
validateButton = "मान्य करें"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "फुलस्क्रीन"
|
||||
[settings.general.updates]
|
||||
title = "सॉफ़्टवेयर अपडेट्स"
|
||||
description = "अपडेट्स जाँचें और वर्ज़न जानकारी देखें"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "वर्तमान संस्करण"
|
||||
latestVersion = "नवीनतम संस्करण"
|
||||
checkForUpdates = "अपडेट्स जाँचें"
|
||||
viewDetails = "विवरण देखें"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "वेब ब्राउज़र सेटिंग"
|
||||
syncToBrowser = "सिंक खाता -> ब्राउज़र"
|
||||
syncToAccount = "सिंक खाता <- ब्राउज़र"
|
||||
|
||||
[account.mfa]
|
||||
title = "दो-कारक प्रमाणीकरण"
|
||||
setupFailed = "दो-कारक सेटअप प्रारंभ करने में असमर्थ। कृपया पुनः प्रयास करें।"
|
||||
codeRequired = "जारी रखने के लिए प्रमाणीकरण कोड दर्ज करें।"
|
||||
enabled = "दो-कारक प्रमाणीकरण सक्षम किया गया।"
|
||||
enableFailed = "दो-कारक प्रमाणीकरण सक्षम करने में असमर्थ। कोड जाँचें और पुनः प्रयास करें।"
|
||||
disabled = "दो-कारक प्रमाणीकरण अक्षम किया गया।"
|
||||
disableFailed = "दो-कारक प्रमाणीकरण अक्षम करने में असमर्थ। कोड जाँचें और पुनः प्रयास करें।"
|
||||
description = "अपने खाते में सुरक्षा की अतिरिक्त परत जोड़ें।"
|
||||
enableButton = "दो-कारक प्रमाणीकरण सक्षम करें"
|
||||
disableButton = "दो-कारक प्रमाणीकरण अक्षम करें"
|
||||
setupTitle = "दो-कारक प्रमाणीकरण सेट करें"
|
||||
setupDescription = "अपने ऑथेंटिकेटर ऐप से QR कोड स्कैन करें, फिर पुष्टि के लिए 6-अंकों का कोड दर्ज करें।"
|
||||
manualKey = "मैनुअल सेटअप कुंजी"
|
||||
secretWarning = "इस कुंजी को निजी रखें। जिसके पास भी पहुँच होगी, वह वैध प्रमाणीकरण कोड जनरेट कर सकता है।"
|
||||
codePlaceholder = "6-अंकों का कोड दर्ज करें"
|
||||
confirmEnable = "सक्षम करें"
|
||||
disableTitle = "दो-कारक प्रमाणीकरण अक्षम करें"
|
||||
disableDescription = "दो-कारक प्रमाणीकरण अक्षम करने के लिए वैध प्रमाणीकरण कोड दर्ज करें।"
|
||||
codeLabel = "प्रमाणीकरण कोड"
|
||||
confirmDisable = "अक्षम करें"
|
||||
ssoDescription = "SSO खातों के लिए दो-कारक प्रमाणीकरण आपका पहचान प्रदाता प्रबंधित करता है।"
|
||||
ssoManaged = "अपने पहचान प्रदाता के माध्यम से MFA कॉन्फ़िगर करें।"
|
||||
|
||||
[adminUserSettings]
|
||||
title = "उपयोगकर्ता नियंत्रण सेटिंग्स"
|
||||
header = "व्यवस्थापक उपयोगकर्ता नियंत्रण सेटिंग्स"
|
||||
@@ -952,7 +925,6 @@ title = "आकार/गिनती से ऑटो बाँटें"
|
||||
desc = "एक PDF को आकार, पृष्ठ संख्या, या दस्तावेज़ संख्या के आधार पर कई दस्तावेज़ों में विभाजित करें"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "रंग बदलें/उलटें"
|
||||
desc = "PDF दस्तावेज़ों में रंगों को प्रतिस्थापित या उलटें"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "स्वचालित फ़ोल्डर स्कैनिंग
|
||||
desc = "स्वचालित फ़ोल्डर स्कैनिंग गाइड के लिए लिंक"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO गाइड"
|
||||
desc = "SSO गाइड के लिए लिंक"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "एयर-गैप्ड सेटअप"
|
||||
desc = "एयर-गैप्ड सेटअप गाइड के लिए लिंक"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "पासवर्ड जोड़ें"
|
||||
desc = "पासवर्ड के साथ अपने PDF दस्तावेज को एन्क्रिप्ट करें।"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "अनुमतियां बदलें"
|
||||
desc = "दस्तावेज़ प्रतिबंध और अनुमतियाँ बदलें"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "स्वचालित करें"
|
||||
desc = "PDF क्रियाओं को जोड़कर बहु-चरणीय वर्कफ़्लो बनाएँ। बार-बार होने वाले कार्यों के लिए उपयुक्त।"
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "PDF को दूसरी PDF के ऊपर ओवरले करें"
|
||||
title = "PDF ओवरले करें"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF टेक्स्ट एडिटर"
|
||||
desc = "ग्रुप्ड टेक्स्ट एडिटिंग और PDF पुनर्जनन के साथ Stirling PDF JSON एक्सपोर्ट की समीक्षा व संपादन करें"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "PDF के बजाय HTML इंटरमीडिएट फ
|
||||
pdfaOptions = "PDF/A विकल्प"
|
||||
outputFormat = "आउटपुट फ़ॉर्मेट"
|
||||
pdfaNote = "PDF/A-1b अधिक संगत है, PDF/A-2b अधिक सुविधाएँ समर्थित करता है।"
|
||||
pdfaFormat = "PDF/A फ़ॉर्मेट"
|
||||
pdfaDigitalSignatureWarning = "PDF में एक डिजिटल हस्ताक्षर है। यह अगले चरण में हटा दिया जाएगा।"
|
||||
pdfxDigitalSignatureWarning = "PDF में एक डिजिटल हस्ताक्षर है। यह अगले चरण में हटा दिया जाएगा।"
|
||||
pdfxDescription = "PDF/X विश्वसनीय प्रिंटिंग और ग्राफ़िक्स विनिमय के लिए एक ISO मानक PDF उपसमुच्चय है।"
|
||||
fileFormat = "फ़ाइल फ़ॉर्मेट"
|
||||
wordDoc = "Word दस्तावेज़"
|
||||
wordDocExt = "Word दस्तावेज़ (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "वर्तमान में एक बार में कई इन
|
||||
outputFormat = "आउटपुट प्रारूप"
|
||||
pdfWithDigitalSignature = "PDF में एक डिजिटल हस्ताक्षर है। यह अगले चरण में हटा दिया जाएगा।"
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "print,standard,conversion,production,prepress,archive"
|
||||
title = "PDF से PDF/X"
|
||||
header = "PDF से PDF/X"
|
||||
credit = "यह सेवा PDF/X रूपांतरण के लिए Ghostscript का उपयोग करती है"
|
||||
submit = "परिवर्तित करें"
|
||||
tip = "फिलहाल एक साथ कई इनपुट पर काम नहीं करता।"
|
||||
outputFormat = "आउटपुट फ़ॉर्मेट"
|
||||
pdfWithDigitalSignature = "PDF में एक डिजिटल हस्ताक्षर है। यह अगले चरण में हटा दिया जाएगा।"
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc,docx,odt,word,रूपांतरण,प्रारूप,रूपांतरण,कार्यालय,माइक्रोसॉफ्ट,docfile"
|
||||
title = "PDF से Word"
|
||||
@@ -3200,18 +3153,6 @@ other = "अन्य"
|
||||
perPageInfo = "प्रति पेज जानकारी"
|
||||
tableOfContents = "विषय सूची"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "पता नहीं चला"
|
||||
passed = "उत्तीर्ण"
|
||||
failed = "असफल"
|
||||
compliant = "अनुरूप"
|
||||
nonCompliant = "अननुरूप"
|
||||
none = "कोई मानक नहीं मिला"
|
||||
passedCount = "उत्तीर्ण"
|
||||
failedCount = "असफल"
|
||||
noVerification = "कोई सत्यापन नहीं किया गया"
|
||||
noVerificationDesc = "इस दस्तावेज़ के लिए PDF मानकों का अनुपालन सत्यापित नहीं किया गया।"
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "संलग्नक"
|
||||
embeddedFiles = "एम्बेडेड फ़ाइलें"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} प्रतिबंध"
|
||||
permsMixed = "कुछ अनुमतियाँ प्रतिबंधित"
|
||||
hasCompliance = "अनुपालन मानक मौजूद हैं"
|
||||
noCompliance = "कोई अनुपालन मानक नहीं"
|
||||
compliancePassed = "{{standards}} अनुरूप"
|
||||
complianceChecked = "मानक सत्यापित ({{failed}} असफल)"
|
||||
basic = "मूल जानकारी"
|
||||
documentInfo = "दस्तावेज़ जानकारी"
|
||||
securityTitle = "सुरक्षा स्थिति"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "छवि आकार"
|
||||
margin = "हाशिया"
|
||||
positionAndFormatting = "स्थिति और फ़ॉर्मैटिंग"
|
||||
quickPosition = "मुहर रखने के लिए पृष्ठ पर एक स्थान चुनें।"
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "PDF में मुहर जोड़ते समय त्रुटि हुई।"
|
||||
@@ -3594,14 +3508,6 @@ failed = "PDF में मुहर जोड़ते समय त्रु
|
||||
[AddStampRequest.results]
|
||||
title = "मुहर के परिणाम"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "छवि हटाएं,पृष्ठ कार्य,बैक एंड,सर्वर साइड"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "पासवर्ड सफलतापूर्व
|
||||
credentialsUpdated = "आपके क्रेडेंशियल अपडेट कर दिए गए हैं। कृपया फिर से साइन इन करें।"
|
||||
defaultCredentials = "डिफ़ॉल्ट लॉगिन क्रेडेंशियल्स"
|
||||
changePasswordWarning = "पहली बार लॉगिन करने के बाद कृपया अपना पासवर्ड बदलें"
|
||||
mfaRequired = "दो-कारक कोड आवश्यक"
|
||||
mfaCode = "प्रमाणीकरण कोड"
|
||||
enterMfaCode = "6-अंकों का कोड दर्ज करें"
|
||||
mfaPromptTitle = "दो-कारक प्रमाणीकरण"
|
||||
mfaPromptBody = "जारी रखने के लिए अपने ऑथेंटिकेटर ऐप का प्रमाणीकरण कोड दर्ज करें।"
|
||||
verifyingMfa = "सत्यापन हो रहा है..."
|
||||
verifyMfa = "कोड सत्यापित करें"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "Stirling PDF ओवरव्यू"
|
||||
@@ -4425,10 +4324,6 @@ title = "लॉगिन मोड आवश्यक"
|
||||
message = "एडमिन सेटिंग्स बदलने के लिए लॉगिन मोड सक्षम होना चाहिए। कृपया अपने एनवायरनमेंट में SECURITY_ENABLELOGIN=true या settings.yml में security.enableLogin: true सेट करें, फिर सर्वर रीस्टार्ट करें।"
|
||||
readOnly = "नीचे दी गई सेटिंग्स संदर्भ हेतु उदाहरण मान दिखाती हैं। वास्तविक कॉन्फ़िगरेशन देखने और संपादित करने के लिए लॉगिन मोड सक्षम करें।"
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "एंटरप्राइज़ लाइसेंस आवश्यक"
|
||||
message = "{{featureName}} तक पहुँच के लिए एंटरप्राइज़ लाइसेंस आवश्यक है। आप संदर्भ के लिए डेमो डेटा देख रहे हैं।"
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "रीस्टार्ट आवश्यक"
|
||||
message = "सेटिंग्स सफलतापूर्वक सहेजी गई हैं। परिवर्तनों के प्रभावी होने के लिए सर्वर रीस्टार्ट आवश्यक है।"
|
||||
@@ -4568,13 +4463,6 @@ description = "खाते को लॉक करने से पहले
|
||||
label = "लॉगिन रीसेट समय (मिनट)"
|
||||
description = "वह समय जिसके बाद असफल लॉगिन प्रयास रीसेट हो जाते हैं"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF सुरक्षा निष्क्रिय करें"
|
||||
description = "Cross-Site Request Forgery सुरक्षा निष्क्रिय करें (अनुशंसित नहीं)"
|
||||
@@ -4883,48 +4771,6 @@ description = "उपयोगकर्ताओं के लिए विस
|
||||
title = "डेटाबेस"
|
||||
description = "एंटरप्राइज़ डिप्लॉयमेंट के लिए कस्टम डेटाबेस कनेक्शन सेटिंग्स कॉन्फ़िगर करें।"
|
||||
configuration = "डेटाबेस कॉन्फ़िगरेशन"
|
||||
backupTitle = "बैकअप और रिस्टोर"
|
||||
backupDescription = "एडमिन कंसोल से सीधे H2 बैकअप प्रबंधित करें।"
|
||||
loadError = "डेटाबेस बैकअप लोड करने में विफल"
|
||||
backupCreated = "बैकअप सफलतापूर्वक बनाया गया"
|
||||
backupFailed = "बैकअप बनाने में विफल"
|
||||
deleteTitle = "बैकअप हटाएं"
|
||||
deleteConfirm = "क्या इस बैकअप को हटाना चाहते हैं? इसे वापस नहीं किया जा सकता।"
|
||||
deleteSuccess = "बैकअप हटाया गया"
|
||||
deleteFailed = "बैकअप हटाने में विफल"
|
||||
deleteConfirmAction = "बैकअप हटाएं"
|
||||
downloadFailed = "बैकअप डाउनलोड करने में विफल"
|
||||
version = "H2 संस्करण"
|
||||
embedded = "एम्बेडेड H2"
|
||||
external = "बाहरी डेटाबेस"
|
||||
h2Only = "बैकअप केवल एम्बेडेड H2 डेटाबेस के लिए उपलब्ध हैं।"
|
||||
h2Hint = "बैकअप और रिस्टोर सक्षम करने के लिए डेटाबेस प्रकार को H2 पर सेट करें और कस्टम डेटाबेस को अक्षम करें।"
|
||||
manageBackups = "बैकअप प्रबंधित करें"
|
||||
refresh = "रिफ्रेश"
|
||||
createBackup = "बैकअप बनाएं"
|
||||
uploadTitle = "अपलोड और इम्पोर्ट"
|
||||
chooseFile = ".sql बैकअप फ़ाइल चुनें"
|
||||
importFromUpload = "अपलोड इम्पोर्ट करें"
|
||||
confirmImportTitle = "डेटाबेस इम्पोर्ट की पुष्टि करें"
|
||||
overwriteWarning = "चेतावनी: यह वर्तमान डेटाबेस को ओवरराइट कर देगा।"
|
||||
overwriteWarningBody = "सारा मौजूदा डेटा अपलोड किए गए बैकअप से बदल दिया जाएगा। यह क्रिया वापस नहीं की जा सकती।"
|
||||
confirmCodeLabel = "आगे बढ़ने के लिए कन्फर्मेशन कोड दर्ज करें"
|
||||
enterCode = "ऊपर दिखाया गया कोड दर्ज करें"
|
||||
confirmImport = "इम्पोर्ट की पुष्टि करें"
|
||||
codeMismatch = "कन्फर्मेशन कोड मेल नहीं खाता"
|
||||
codeMismatchBody = "कृपया आगे बढ़ने के लिए कोड ठीक उसी प्रकार दर्ज करें जैसा दिखाया गया है।"
|
||||
selectFile = "इम्पोर्ट करने के लिए .sql फ़ाइल चुनें"
|
||||
importSuccess = "बैकअप सफलतापूर्वक इम्पोर्ट किया गया"
|
||||
importFailed = "बैकअप इम्पोर्ट करने में विफल"
|
||||
noBackups = "अभी तक कोई बैकअप नहीं मिला।"
|
||||
unavailable = "वर्तमान डेटाबेस कॉन्फ़िगरेशन के लिए बैकअप सूची उपलब्ध नहीं है।"
|
||||
fileName = "फ़ाइल"
|
||||
created = "बनाया गया"
|
||||
size = "आकार"
|
||||
actions = "क्रियाएं"
|
||||
download = "डाउनलोड"
|
||||
import = "इम्पोर्ट"
|
||||
delete = "हटाएं"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "कस्टम डेटाबेस सक्रिय करें"
|
||||
@@ -5002,26 +4848,6 @@ description = "इमेज प्रोसेसिंग के लिए अ
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "tessdata डायरेक्टरी"
|
||||
description = "OCR भाषा फ़ाइलों के लिए tessdata डायरेक्टरी का पाथ"
|
||||
downloadMissingTitle = "कोई भाषा चयनित नहीं"
|
||||
downloadMissingBody = "कृपया डाउनलोड के लिए कम से कम एक भाषा चुनें।"
|
||||
downloadSuccessTitle = "भाषाएं डाउनलोड की गईं"
|
||||
downloadSuccessBody = "चयनित tessdata भाषाएं सहेज ली गई हैं।"
|
||||
downloadErrorTitle = "डाउनलोड विफल"
|
||||
loadingLanguages = "इंस्टॉल की गई tessdata भाषाएं लोड की जा रही हैं..."
|
||||
installedLanguages = "इंस्टॉल की गई tessdata भाषाएं"
|
||||
noLanguages = "कॉन्फ़िगर की गई डायरेक्टरी में कोई tessdata भाषा नहीं मिली।"
|
||||
downloadLabel = "अतिरिक्त tessdata भाषाएं डाउनलोड करें"
|
||||
downloadPlaceholder = "भाषाएं चुनें"
|
||||
downloadNothingFound = "कोई अतिरिक्त भाषाएं नहीं मिलीं"
|
||||
permissionNotice = "tessdata पाथ पर लिखना संभव नहीं है। डाउनलोड ब्राउज़र में खुलेंगे; कृपया .traineddata फ़ाइलें मैन्युअल रूप से tessdata फ़ोल्डर में सहेजें।"
|
||||
manualLinks = "मैन्युअल डाउनलोड: लिंक पर क्लिक करें और फ़ाइलें tessdata फ़ोल्डर में रखें।"
|
||||
downloadButton = "चयनित भाषाएं डाउनलोड करें"
|
||||
downloadInvalidTitle = "अमान्य चयन"
|
||||
downloadInvalidBody = "कुछ चयनित भाषाएं डाउनलोड के लिए उपलब्ध नहीं हैं। कृपया रिफ्रेश करें और सूची में से चुनें।"
|
||||
downloadErrorNetwork = "नेटवर्क त्रुटि के कारण डाउनलोड विफल हुआ। कृपया अपना कनेक्शन जांचें और पुनः प्रयास करें।"
|
||||
downloadErrorServer = "tessdata भाषाएं डाउनलोड करते समय सर्वर में त्रुटि हुई। कृपया बाद में पुनः प्रयास करें।"
|
||||
downloadErrorPermission = "tessdata डायरेक्टरी पर लिखना संभव नहीं है: {{message}}। कृपया किसी लिखने योग्य डायरेक्टरी का चयन करें (जैसे एप्लिकेशन डेटा फ़ोल्डर के अंदर) या अनुमतियां समायोजित करें।"
|
||||
downloadErrorGeneric = "डाउनलोड विफल: {{message}}। कृपया बाद में पुनः प्रयास करें।"
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "HTML सैनिटाइज़ेशन निष्क्रिय करें"
|
||||
@@ -5525,7 +5351,6 @@ desc = "PDF कार्रवाइयों को जोड़कर बह
|
||||
invalidStep = "अमान्य चरण"
|
||||
reviewTitle = "स्वचालन परिणाम"
|
||||
copyToSaved = "सहेजे गए में कॉपी करें"
|
||||
exportForFolderScanning = "फ़ोल्डर स्कैनिंग के लिए निर्यात करें"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "इस स्वचालन के साथ प्रोसेस करने के लिए फ़ाइलें चुनें"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "स्वचालन बनाएँ"
|
||||
editTitle = "स्वचालन संपादित करें"
|
||||
intro = "स्वचालन टूल्स को क्रमवार चलाते हैं। शुरू करने के लिए, टूल्स उसी क्रम में जोड़ें जिसमें आप उन्हें चलाना चाहते हैं।"
|
||||
save = "स्वचालन सहेजें"
|
||||
exportForFolderScanning = "फ़ोल्डर स्कैनिंग के लिए निर्यात करें"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "स्वचालन नाम"
|
||||
@@ -5922,11 +5746,9 @@ username = "यूज़रनेम (ईमेल)"
|
||||
usernamePlaceholder = "user@example.com"
|
||||
password = "पासवर्ड"
|
||||
passwordPlaceholder = "पासवर्ड दर्ज करें"
|
||||
passwordRequired = "पासवर्ड आवश्यक है"
|
||||
role = "भूमिका"
|
||||
team = "टीम (वैकल्पिक)"
|
||||
teamPlaceholder = "एक टीम चुनें"
|
||||
authType = "प्रमाणीकरण प्रकार"
|
||||
forcePasswordChange = "पहली लॉगिन पर पासवर्ड बदलना अनिवार्य करें"
|
||||
cancel = "रद्द करें"
|
||||
submit = "सदस्य जोड़ें"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "यूज़रनेम और पासवर्ड आव
|
||||
passwordTooShort = "पासवर्ड कम से कम 6 अक्षर का होना चाहिए"
|
||||
success = "उपयोगकर्ता सफलतापूर्वक बनाया गया"
|
||||
error = "उपयोगकर्ता बनाने में विफल"
|
||||
forceMFA = "अगले लॉगिन पर MFA सेटअप अनिवार्य करें"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "पासवर्ड"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "उपयोगकर्ता SSO प्रदाता के माध्यम से प्रमाणीकरण करेगा"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "सदस्य संपादित करें"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "{{count}} उपयोगकर्ता स्लॉट उ
|
||||
noSlotsAvailable = "कोई स्लॉट उपलब्ध नहीं"
|
||||
currentUsage = "वर्तमान में {{max}} में से {{current}} उपयोगकर्ता लाइसेंस उपयोग में"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "उपयोगकर्ता के लिए MFA सफलतापूर्वक अक्षम किया गया"
|
||||
adminDisableError = "उपयोगकर्ता के लिए MFA अक्षम करने में विफल"
|
||||
disableByAdmin = "MFA अक्षम करें"
|
||||
|
||||
[workspace.teams]
|
||||
title = "टीम्स"
|
||||
description = "टीम्स प्रबंधित करें और वर्कस्पेस सदस्यों को व्यवस्थित करें"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "एंडपॉइंट"
|
||||
visits = "विज़िट"
|
||||
percentage = "प्रतिशत"
|
||||
noData = "कोई डेटा उपलब्ध नहीं"
|
||||
unknownEndpoint = "अज्ञात एंडपॉइंट"
|
||||
|
||||
[backendHealth]
|
||||
checking = "बैकएंड स्थिति की जाँच की जा रही है..."
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "Odaberite Operaciju"
|
||||
addOperationButton = "Dodajte operaciju"
|
||||
pipelineHeader = "Cjevovod:"
|
||||
saveButton = "Preuzmi datoteku"
|
||||
saveForFolderScanning = "Spremi za skeniranje mape"
|
||||
validateButton = "Potvrdi"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "Cijeli zaslon"
|
||||
[settings.general.updates]
|
||||
title = "Ažuriranja softvera"
|
||||
description = "Provjerite ažuriranja i pogledajte informacije o verziji"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "Trenutačna verzija"
|
||||
latestVersion = "Najnovija verzija"
|
||||
checkForUpdates = "Provjeri ažuriranja"
|
||||
viewDetails = "Prikaži detalje"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "Postavka web-preglednika"
|
||||
syncToBrowser = "Sinkronizacija Račun -> Preglednik"
|
||||
syncToAccount = "Sinkronizacija Račun <- Preglednik"
|
||||
|
||||
[account.mfa]
|
||||
title = "Dvofaktorska autentikacija"
|
||||
setupFailed = "Nije moguće započeti postavljanje dvofaktorske autentikacije. Pokušajte ponovno."
|
||||
codeRequired = "Unesite autentikacijski kod za nastavak."
|
||||
enabled = "Dvofaktorska autentikacija je omogućena."
|
||||
enableFailed = "Nije moguće omogućiti dvofaktorsku autentikaciju. Provjerite kod i pokušajte ponovno."
|
||||
disabled = "Dvofaktorska autentikacija je onemogućena."
|
||||
disableFailed = "Nije moguće onemogućiti dvofaktorsku autentikaciju. Provjerite kod i pokušajte ponovno."
|
||||
description = "Dodajte dodatni sloj sigurnosti svom računu."
|
||||
enableButton = "Omogući dvofaktorsku autentikaciju"
|
||||
disableButton = "Onemogući dvofaktorsku autentikaciju"
|
||||
setupTitle = "Postavite dvofaktorsku autentikaciju"
|
||||
setupDescription = "Skenirajte QR kod aplikacijom za autentikaciju, zatim unesite 6-znamenkasti kod za potvrdu."
|
||||
manualKey = "Ključ za ručno postavljanje"
|
||||
secretWarning = "Držite ovaj ključ privatnim. Svatko s pristupom može generirati valjane autentikacijske kodove."
|
||||
codePlaceholder = "Unesite 6-znamenkasti kod"
|
||||
confirmEnable = "Omogući"
|
||||
disableTitle = "Onemogućite dvofaktorsku autentikaciju"
|
||||
disableDescription = "Unesite valjani autentikacijski kod da biste onemogućili dvofaktorsku autentikaciju."
|
||||
codeLabel = "Autentikacijski kod"
|
||||
confirmDisable = "Onemogući"
|
||||
ssoDescription = "Za račune s jedinstvenom prijavom dvofaktorskom autentikacijom upravlja vaš davatelj identiteta."
|
||||
ssoManaged = "Konfigurirajte MFA putem svojeg davatelja identiteta."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "Postavka kontrole korisnika"
|
||||
header = "Postavka kontrole korisnika za administratora"
|
||||
@@ -952,7 +925,6 @@ title = "Auto dijeli po veličini/broju"
|
||||
desc = "Podijelite jedan PDF na više dokumenata na temelju veličine, broja stranica ili broja dokumenata"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Zamijeni i invertiraj boju"
|
||||
desc = "Zamijenite ili invertirajte boje u PDF dokumentima"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "Auto skeniranje mapa"
|
||||
desc = "Poveznica na vodič za automatizirano skeniranje mapa"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO vodič"
|
||||
desc = "Poveznica na SSO vodič"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Air-gapped postavljanje"
|
||||
desc = "Poveznica na vodič za air-gapped postavljanje"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Dodaj lozinku"
|
||||
desc = "Šifrirajte svoj PDF dokument lozinkom.."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Promjena dopuštenja"
|
||||
desc = "Promijenite ograničenja dokumenta i dopuštenja"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "Automatiziraj"
|
||||
desc = "Izgradite višekoračne tijekove rada povezivanjem PDF radnji. Idealno za ponavljajuće zadatke."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Preklapa PDF-ove na drugi PDF"
|
||||
title = "Preklapanje PDF-ova"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF uređivač teksta"
|
||||
desc = "Pregledajte i uredite Stirling PDF JSON izvoze s grupnim uređivanjem teksta i ponovnim generiranjem PDF-a"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "Preuzmi HTML međudatoteku umjesto PDF-a"
|
||||
pdfaOptions = "PDF/A opcije"
|
||||
outputFormat = "Izlazni format"
|
||||
pdfaNote = "PDF/A-1b je kompatibilniji, PDF/A-2b podržava više značajki."
|
||||
pdfaFormat = "PDF/A format"
|
||||
pdfaDigitalSignatureWarning = "PDF sadrži digitalni potpis. U sledećem koraku će biti uklonjen."
|
||||
pdfxDigitalSignatureWarning = "PDF sadrži digitalni potpis. Bit će uklonjen u sljedećem koraku."
|
||||
pdfxDescription = "PDF/X je ISO standardni podskup PDF-a za pouzdani tisak i razmjenu grafike."
|
||||
fileFormat = "Format datoteke"
|
||||
wordDoc = "Word dokument"
|
||||
wordDocExt = "Word dokument (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "Trenutno ne radi za više unosa odjednom"
|
||||
outputFormat = "Izlazni format"
|
||||
pdfWithDigitalSignature = "PDF sadrži digitalni potpis. U sledećem koraku će biti uklonjen."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "tisak,standard,pretvorba,produkcija,priprema za tisak,arhiva"
|
||||
title = "PDF u PDF/X"
|
||||
header = "PDF u PDF/X"
|
||||
credit = "Ova usluga koristi Ghostscript za pretvorbu u PDF/X"
|
||||
submit = "Pretvori"
|
||||
tip = "Trenutačno ne radi s više ulaza odjednom"
|
||||
outputFormat = "Izlazni format"
|
||||
pdfWithDigitalSignature = "PDF sadrži digitalni potpis. Bit će uklonjen u sljedećem koraku."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc,docx,odt,word,transformacija,format,konverzija,office,microsoft,docfile"
|
||||
title = "PDF u Word"
|
||||
@@ -3200,18 +3153,6 @@ other = "Ostalo"
|
||||
perPageInfo = "Informacije po stranici"
|
||||
tableOfContents = "Sadržaj"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "Nije otkriveno"
|
||||
passed = "Prošlo"
|
||||
failed = "Nije prošlo"
|
||||
compliant = "Sukladno"
|
||||
nonCompliant = "Nesukladno"
|
||||
none = "Nisu otkriveni standardi"
|
||||
passedCount = "prošlo"
|
||||
failedCount = "nije prošlo"
|
||||
noVerification = "Provjera nije izvršena"
|
||||
noVerificationDesc = "Usklađenost s PDF standardima nije provjerena za ovaj dokument."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "Privici"
|
||||
embeddedFiles = "Ugrađene datoteke"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} ograničenja"
|
||||
permsMixed = "Neke dozvole su ograničene"
|
||||
hasCompliance = "Ima standarde sukladnosti"
|
||||
noCompliance = "Nema standarda sukladnosti"
|
||||
compliancePassed = "U skladu s {{standards}}"
|
||||
complianceChecked = "Standardi provjereni ({{failed}} nije prošlo)"
|
||||
basic = "Osnovne informacije"
|
||||
documentInfo = "Informacije o dokumentu"
|
||||
securityTitle = "Status sigurnosti"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "Veličina slike"
|
||||
margin = "Margina"
|
||||
positionAndFormatting = "Položaj i oblikovanje"
|
||||
quickPosition = "Odaberite položaj na stranici za postavljanje pečata."
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "Došlo je do pogreške pri dodavanju pečata u PDF."
|
||||
@@ -3594,14 +3508,6 @@ failed = "Došlo je do pogreške pri dodavanju pečata u PDF."
|
||||
[AddStampRequest.results]
|
||||
title = "Rezultati pečata"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "Ukloni sliku, Rad sa stranicama, Back end, server strana"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "Lozinka je uspješno promijenjena! Prijavite se novom
|
||||
credentialsUpdated = "Vaše vjerodajnice su ažurirane. Prijavite se ponovno."
|
||||
defaultCredentials = "Zadane vjerodajnice za prijavu"
|
||||
changePasswordWarning = "Promijenite lozinku nakon prve prijave"
|
||||
mfaRequired = "Potreban je dvofaktorski kod"
|
||||
mfaCode = "Autentikacijski kod"
|
||||
enterMfaCode = "Unesite 6-znamenkasti kod"
|
||||
mfaPromptTitle = "Dvofaktorska autentikacija"
|
||||
mfaPromptBody = "Unesite autentikacijski kod iz svoje aplikacije za autentikaciju za nastavak."
|
||||
verifyingMfa = "Provjera..."
|
||||
verifyMfa = "Provjeri kod"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "Pregled Stirling PDF-a"
|
||||
@@ -4425,10 +4324,6 @@ title = "Potreban je način prijave"
|
||||
message = "Način prijave mora biti omogućen za izmjenu administratorskih postavki. Postavite SECURITY_ENABLELOGIN=true u svom okruženju ili security.enableLogin: true u settings.yml, zatim ponovno pokrenite poslužitelj."
|
||||
readOnly = "Donje postavke prikazuju primjerne vrijednosti za referencu. Omogućite način prijave za pregled i uređivanje stvarne konfiguracije."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "Potrebna je Enterprise licenca"
|
||||
message = "Za pristup {{featureName}} potrebna je Enterprise licenca. Prikazuju se ogledni podaci radi reference."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "Potrebno ponovno pokretanje"
|
||||
message = "Postavke su uspješno spremljene. Potrebno je ponovno pokretanje poslužitelja da bi promjene stupile na snagu."
|
||||
@@ -4568,13 +4463,6 @@ description = "Maksimalan broj neuspjelih pokušaja prijave prije zaključavanja
|
||||
label = "Vrijeme resetiranja prijave (minute)"
|
||||
description = "Vrijeme prije resetiranja neuspjelih pokušaja prijave"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Onemogući zaštitu od CSRF-a"
|
||||
description = "Onemogući zaštitu od Cross-Site Request Forgery (ne preporučuje se)"
|
||||
@@ -4883,48 +4771,6 @@ description = "Prikaži detaljne poruke o pogreškama za korisnike."
|
||||
title = "Baza podataka"
|
||||
description = "Konfigurirajte prilagođene postavke povezivanja baze podataka za enterprise implementacije."
|
||||
configuration = "Konfiguracija baze podataka"
|
||||
backupTitle = "Sigurnosne kopije i vraćanje"
|
||||
backupDescription = "Upravljajte H2 sigurnosnim kopijama izravno iz administratorske konzole."
|
||||
loadError = "Učitavanje sigurnosnih kopija baze podataka nije uspjelo"
|
||||
backupCreated = "Sigurnosna kopija je uspješno izrađena"
|
||||
backupFailed = "Izrada sigurnosne kopije nije uspjela"
|
||||
deleteTitle = "Izbriši sigurnosnu kopiju"
|
||||
deleteConfirm = "Izbrisati ovu sigurnosnu kopiju? Ovo se ne može poništiti."
|
||||
deleteSuccess = "Sigurnosna kopija je izbrisana"
|
||||
deleteFailed = "Brisanje sigurnosne kopije nije uspjelo"
|
||||
deleteConfirmAction = "Izbriši sigurnosnu kopiju"
|
||||
downloadFailed = "Preuzimanje sigurnosne kopije nije uspjelo"
|
||||
version = "H2 verzija"
|
||||
embedded = "Ugrađeni H2"
|
||||
external = "Vanjska baza podataka"
|
||||
h2Only = "Sigurnosne kopije dostupne su samo za ugrađenu H2 bazu podataka."
|
||||
h2Hint = "Postavite vrstu baze podataka na H2 i onemogućite prilagođenu bazu podataka kako biste omogućili izradu i vraćanje sigurnosnih kopija."
|
||||
manageBackups = "Upravljaj sigurnosnim kopijama"
|
||||
refresh = "Osvježi"
|
||||
createBackup = "Izradi sigurnosnu kopiju"
|
||||
uploadTitle = "Prijenos i uvoz"
|
||||
chooseFile = "Odaberite .sql datoteku sigurnosne kopije"
|
||||
importFromUpload = "Uvezi preneseno"
|
||||
confirmImportTitle = "Potvrdite uvoz baze podataka"
|
||||
overwriteWarning = "Upozorenje: Ovo će prebrisati trenutačnu bazu podataka."
|
||||
overwriteWarningBody = "Svi postojeći podaci bit će zamijenjeni prenesenom sigurnosnom kopijom. Ova se radnja ne može poništiti."
|
||||
confirmCodeLabel = "Unesite potvrdni kod za nastavak"
|
||||
enterCode = "Unesite kod prikazan iznad"
|
||||
confirmImport = "Potvrdi uvoz"
|
||||
codeMismatch = "Potvrdni kod se ne podudara"
|
||||
codeMismatchBody = "Za nastavak unesite kod točno onako kako je prikazan."
|
||||
selectFile = "Odaberite .sql datoteku za uvoz"
|
||||
importSuccess = "Sigurnosna kopija je uspješno uvezena"
|
||||
importFailed = "Uvoz sigurnosne kopije nije uspio"
|
||||
noBackups = "Još nema sigurnosnih kopija."
|
||||
unavailable = "Popis sigurnosnih kopija nije dostupan za trenutačnu konfiguraciju baze podataka."
|
||||
fileName = "Datoteka"
|
||||
created = "Stvoreno"
|
||||
size = "Veličina"
|
||||
actions = "Radnje"
|
||||
download = "Preuzmi"
|
||||
import = "Uvezi"
|
||||
delete = "Izbriši"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "Omogući prilagođenu bazu podataka"
|
||||
@@ -5002,26 +4848,6 @@ description = "Maksimalni DPI za obradu slika (0 = neograničeno)"
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "Direktorij tessdata"
|
||||
description = "Put do direktorija tessdata za OCR jezične datoteke"
|
||||
downloadMissingTitle = "Nije odabran nijedan jezik"
|
||||
downloadMissingBody = "Odaberite barem jedan jezik za preuzimanje."
|
||||
downloadSuccessTitle = "Jezici su preuzeti"
|
||||
downloadSuccessBody = "Odabrani tessdata jezici su spremljeni."
|
||||
downloadErrorTitle = "Preuzimanje nije uspjelo"
|
||||
loadingLanguages = "Učitavanje instaliranih tessdata jezika..."
|
||||
installedLanguages = "Instalirani tessdata jezici"
|
||||
noLanguages = "U konfiguriranom direktoriju nisu pronađeni tessdata jezici."
|
||||
downloadLabel = "Preuzmi dodatne tessdata jezike"
|
||||
downloadPlaceholder = "Odaberite jezike"
|
||||
downloadNothingFound = "Nisu pronađeni dodatni jezici"
|
||||
permissionNotice = "Putanja do tessdata nije zapisiva. Preuzimanja će se otvoriti u pregledniku; spremite .traineddata datoteke ručno u mapu tessdata."
|
||||
manualLinks = "Ručno preuzimanje: kliknite poveznice i stavite datoteke u mapu tessdata."
|
||||
downloadButton = "Preuzmi odabrane jezike"
|
||||
downloadInvalidTitle = "Neispravan odabir"
|
||||
downloadInvalidBody = "Neki odabrani jezici nisu dostupni za preuzimanje. Osvježite i odaberite s popisa."
|
||||
downloadErrorNetwork = "Preuzimanje nije uspjelo zbog mrežne pogreške. Provjerite vezu i pokušajte ponovno."
|
||||
downloadErrorServer = "Poslužitelj je naišao na pogrešku tijekom preuzimanja tessdata jezika. Pokušajte ponovno kasnije."
|
||||
downloadErrorPermission = "Direktorij tessdata nije zapisiv: {{message}}. Odaberite direktorij s mogućnošću upisa (npr. unutar mape podataka aplikacije) ili prilagodite dopuštenja."
|
||||
downloadErrorGeneric = "Preuzimanje nije uspjelo: {{message}}. Pokušajte ponovno kasnije."
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "Onemogući sanitizaciju HTML-a"
|
||||
@@ -5525,7 +5351,6 @@ desc = "Izgradite višekoračne tijekove rada povezivanjem PDF radnji. Idealno z
|
||||
invalidStep = "Nevažeći korak"
|
||||
reviewTitle = "Rezultati automatizacije"
|
||||
copyToSaved = "Kopiraj u spremljeno"
|
||||
exportForFolderScanning = "Izvoz za skeniranje mape"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "Odaberite datoteke za obradu ovom automatizacijom"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "Stvori automatizaciju"
|
||||
editTitle = "Uredi automatizaciju"
|
||||
intro = "Automatizacije pokreću alate sekvencijalno. Za početak dodajte alate redoslijedom kojim želite da se izvršavaju."
|
||||
save = "Spremi automatizaciju"
|
||||
exportForFolderScanning = "Izvoz za skeniranje mape"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "Naziv automatizacije"
|
||||
@@ -5922,11 +5746,9 @@ username = "Korisničko ime (e-pošta)"
|
||||
usernamePlaceholder = "user@example.com"
|
||||
password = "Lozinka"
|
||||
passwordPlaceholder = "Unesite lozinku"
|
||||
passwordRequired = "Lozinka je obavezna"
|
||||
role = "Uloga"
|
||||
team = "Tim (neobavezno)"
|
||||
teamPlaceholder = "Odaberite tim"
|
||||
authType = "Vrsta autentikacije"
|
||||
forcePasswordChange = "Prisili promjenu lozinke pri prvoj prijavi"
|
||||
cancel = "Odustani"
|
||||
submit = "Dodaj člana"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "Potrebni su korisničko ime i lozinka"
|
||||
passwordTooShort = "Lozinka mora imati najmanje 6 znakova"
|
||||
success = "Korisnik je uspješno stvoren"
|
||||
error = "Stvaranje korisnika nije uspjelo"
|
||||
forceMFA = "Nametni postavljanje MFA pri sljedećoj prijavi"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "Lozinka"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "Korisnik će se autentificirati putem SSO pružatelja"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "Uredi člana"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "{{count}} slobodnih korisničkih mjesta"
|
||||
noSlotsAvailable = "Nema dostupnih mjesta"
|
||||
currentUsage = "Trenutno se koristi {{current}} od {{max}} korisničkih licenci"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "MFA je uspješno onemogućena za korisnika"
|
||||
adminDisableError = "Onemogućavanje MFA za korisnika nije uspjelo"
|
||||
disableByAdmin = "Onemogući MFA"
|
||||
|
||||
[workspace.teams]
|
||||
title = "Timovi"
|
||||
description = "Upravljajte timovima i organizirajte članove radnog prostora"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "Krajnja točka"
|
||||
visits = "Posjete"
|
||||
percentage = "Postotak"
|
||||
noData = "Podaci nisu dostupni"
|
||||
unknownEndpoint = "Nepoznata krajnja točka"
|
||||
|
||||
[backendHealth]
|
||||
checking = "Provjera statusa pozadinskog sustava..."
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "Művelet kiválasztása"
|
||||
addOperationButton = "Művelet hozzáadása"
|
||||
pipelineHeader = "Folyamat:"
|
||||
saveButton = "Letöltés"
|
||||
saveForFolderScanning = "Mentés mappavizsgálathoz"
|
||||
validateButton = "Ellenőrzés"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "Teljes képernyő"
|
||||
[settings.general.updates]
|
||||
title = "Szoftverfrissítések"
|
||||
description = "Frissítések keresése és verzióinformációk megtekintése"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "Jelenlegi verzió"
|
||||
latestVersion = "Legújabb verzió"
|
||||
checkForUpdates = "Frissítések keresése"
|
||||
viewDetails = "Részletek megtekintése"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "Böngészőbeállítások"
|
||||
syncToBrowser = "Szinkronizálás: Fiók -> Böngésző"
|
||||
syncToAccount = "Szinkronizálás: Böngésző -> Fiók"
|
||||
|
||||
[account.mfa]
|
||||
title = "Kétfaktoros hitelesítés"
|
||||
setupFailed = "Nem sikerült elindítani a kétfaktoros beállítást. Próbálja újra."
|
||||
codeRequired = "A folytatáshoz adja meg a hitelesítési kódot."
|
||||
enabled = "Kétfaktoros hitelesítés engedélyezve."
|
||||
enableFailed = "Nem sikerült engedélyezni a kétfaktoros hitelesítést. Ellenőrizze a kódot, majd próbálja újra."
|
||||
disabled = "Kétfaktoros hitelesítés letiltva."
|
||||
disableFailed = "Nem sikerült letiltani a kétfaktoros hitelesítést. Ellenőrizze a kódot, majd próbálja újra."
|
||||
description = "Adjon hozzá egy további biztonsági réteget a fiókjához."
|
||||
enableButton = "Kétfaktoros hitelesítés engedélyezése"
|
||||
disableButton = "Kétfaktoros hitelesítés letiltása"
|
||||
setupTitle = "Kétfaktoros hitelesítés beállítása"
|
||||
setupDescription = "Olvassa be a QR-kódot a hitelesítő alkalmazással, majd a megerősítéshez adja meg a 6 számjegyű kódot."
|
||||
manualKey = "Kézi beállítási kulcs"
|
||||
secretWarning = "Tartsa ezt a kulcsot titokban. Aki hozzáfér, érvényes hitelesítési kódokat tud generálni."
|
||||
codePlaceholder = "Adja meg a 6 számjegyű kódot"
|
||||
confirmEnable = "Engedélyezés"
|
||||
disableTitle = "Kétfaktoros hitelesítés letiltása"
|
||||
disableDescription = "Adjon meg egy érvényes hitelesítési kódot a kétfaktoros hitelesítés letiltásához."
|
||||
codeLabel = "Hitelesítési kód"
|
||||
confirmDisable = "Letiltás"
|
||||
ssoDescription = "Az egyszeri bejelentkezéses fiókoknál a kétfaktoros hitelesítést az identitásszolgáltató kezeli."
|
||||
ssoManaged = "Konfigurálja az MFA-t az identitásszolgáltatón keresztül."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "Felhasználókezelés"
|
||||
header = "Rendszergazdai felhasználókezelés"
|
||||
@@ -952,7 +925,6 @@ title = "Auto felosztás méret/darab"
|
||||
desc = "Egyetlen PDF felosztása több dokumentumra méret, oldalszám vagy dokumentumszám alapján"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Színek cseréje és invertálása"
|
||||
desc = "Színek cseréje vagy invertálása PDF dokumentumokban"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "Automatikus mappaszkennelés"
|
||||
desc = "Hivatkozás az automatikus mappaszkennelés útmutatójára"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "SSO útmutató"
|
||||
desc = "Hivatkozás az SSO útmutatóra"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Air-gapped beállítás"
|
||||
desc = "Hivatkozás az elszigetelt környezet beállítási útmutatójára"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Jelszó hozzáadása"
|
||||
desc = "PDF dokumentum jelszavas védelme"
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Jogosultságok módosítása"
|
||||
desc = "Dokumentumkorlátozások és jogosultságok módosítása"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "Automatizálás"
|
||||
desc = "Többlépéses munkafolyamatok összeállítása PDF műveletek összefűzésével. Ideális ismétlődő feladatokhoz."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "PDF-ek egymásra helyezése egy másik PDF-en"
|
||||
title = "PDF-ek egymásra helyezése"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "PDF szövegszerkesztő"
|
||||
desc = "Nézze át és szerkessze a Stirling PDF JSON exportokat csoportosított szövegszerkesztéssel és PDF-újragenerálással"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "HTML köztes fájl letöltése PDF helyett"
|
||||
pdfaOptions = "PDF/A beállítások"
|
||||
outputFormat = "Kimeneti formátum"
|
||||
pdfaNote = "A PDF/A-1b kompatibilisebb, a PDF/A-2b több funkciót támogat."
|
||||
pdfaFormat = "PDF/A formátum"
|
||||
pdfaDigitalSignatureWarning = "A PDF digitális aláírást tartalmaz. Ez a következő lépésben eltávolításra kerül."
|
||||
pdfxDigitalSignatureWarning = "A PDF digitális aláírást tartalmaz. Ez a következő lépésben eltávolításra kerül."
|
||||
pdfxDescription = "A PDF/X egy ISO szabványos PDF-részhalmaz megbízható nyomtatáshoz és grafikai cseréhez."
|
||||
fileFormat = "Fájlformátum"
|
||||
wordDoc = "Word-dokumentum"
|
||||
wordDocExt = "Word-dokumentum (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "Jelenleg nem támogatja a több fájl egyidejű feldolgozását"
|
||||
outputFormat = "Kimeneti formátum"
|
||||
pdfWithDigitalSignature = "A PDF digitális aláírást tartalmaz. Ez a következő lépésben eltávolításra kerül."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "nyomtatás,szabvány,átalakítás,gyártás,előnyomdai,archiválás"
|
||||
title = "PDF PDF/X-re"
|
||||
header = "PDF PDF/X-re"
|
||||
credit = "Ez a szolgáltatás a Ghostscriptet használja a PDF/X átalakításhoz"
|
||||
submit = "Átalakítás"
|
||||
tip = "Jelenleg nem működik több bemenettel egyszerre"
|
||||
outputFormat = "Kimeneti formátum"
|
||||
pdfWithDigitalSignature = "A PDF digitális aláírást tartalmaz. Ez a következő lépésben eltávolításra kerül."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc,docx,odt,word,átalakítás,formátum,konvertálás,iroda,microsoft,docfile"
|
||||
title = "PDF konvertálása Word formátumba"
|
||||
@@ -3200,18 +3153,6 @@ other = "Egyéb"
|
||||
perPageInfo = "Oldalankénti információk"
|
||||
tableOfContents = "Tartalomjegyzék"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "Nem észlelve"
|
||||
passed = "Megfelelt"
|
||||
failed = "Nem felelt meg"
|
||||
compliant = "Megfelelő"
|
||||
nonCompliant = "Nem megfelelő"
|
||||
none = "Nem észleltek szabványt"
|
||||
passedCount = "megfelelt"
|
||||
failedCount = "nem felelt meg"
|
||||
noVerification = "Nem történt ellenőrzés"
|
||||
noVerificationDesc = "Ennél a dokumentumnál nem ellenőrizték a PDF-szabványoknak való megfelelést."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "Mellékletek"
|
||||
embeddedFiles = "Beágyazott fájlok"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} korlátozás"
|
||||
permsMixed = "Néhány jogosultság korlátozott"
|
||||
hasCompliance = "Megfelelőségi szabványokkal rendelkezik"
|
||||
noCompliance = "Nincsenek megfelelőségi szabványok"
|
||||
compliancePassed = "{{standards}}-kompatibilis"
|
||||
complianceChecked = "Szabványok ellenőrizve ({{failed}} sikertelen)"
|
||||
basic = "Alapinformációk"
|
||||
documentInfo = "Dokumentuminformációk"
|
||||
securityTitle = "Biztonsági állapot"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "Képméret"
|
||||
margin = "Margó"
|
||||
positionAndFormatting = "Pozíció és formázás"
|
||||
quickPosition = "Válasszon pozíciót az oldalon a bélyeg elhelyezéséhez."
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "Hiba történt a bélyeg hozzáadása közben a PDF-hez."
|
||||
@@ -3594,14 +3508,6 @@ failed = "Hiba történt a bélyeg hozzáadása közben a PDF-hez."
|
||||
[AddStampRequest.results]
|
||||
title = "Bélyegzés eredményei"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "Képek eltávolítása,Oldalműveletek,Backend,szerver oldali"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "A jelszó sikeresen megváltozott! Jelentkezzen be az
|
||||
credentialsUpdated = "Az adatait frissítettük. Kérjük, jelentkezzen be újra."
|
||||
defaultCredentials = "Alapértelmezett bejelentkezési adatok"
|
||||
changePasswordWarning = "Kérjük, az első bejelentkezés után változtassa meg a jelszavát"
|
||||
mfaRequired = "Kétfaktoros kód szükséges"
|
||||
mfaCode = "Hitelesítési kód"
|
||||
enterMfaCode = "Adja meg a 6 számjegyű kódot"
|
||||
mfaPromptTitle = "Kétfaktoros hitelesítés"
|
||||
mfaPromptBody = "A folytatáshoz adja meg a hitelesítő alkalmazás által generált kódot."
|
||||
verifyingMfa = "Ellenőrzés..."
|
||||
verifyMfa = "Kód ellenőrzése"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "Stirling PDF áttekintés"
|
||||
@@ -4425,10 +4324,6 @@ title = "Bejelentkezési mód szükséges"
|
||||
message = "Az admin beállítások módosításához be kell kapcsolni a bejelentkezési módot. Állítsa be a SECURITY_ENABLELOGIN=true értéket a környezetben, vagy a security.enableLogin: true beállítást a settings.yml fájlban, majd indítsa újra a szervert."
|
||||
readOnly = "Az alábbi beállítások csak példaként láthatók. Engedélyezze a bejelentkezési módot a tényleges konfiguráció megtekintéséhez és szerkesztéséhez."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "Vállalati licenc szükséges"
|
||||
message = "{{featureName}} eléréséhez vállalati licenc szükséges. Jelenleg referenciaként demóadatokat lát."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "Újraindítás szükséges"
|
||||
message = "A beállítások sikeresen mentésre kerültek. A változtatások életbe lépéséhez a szervert újra kell indítani."
|
||||
@@ -4568,13 +4463,6 @@ description = "Sikertelen bejelentkezési kísérletek maximális száma fiókz
|
||||
label = "Visszaállítás ideje (perc)"
|
||||
description = "Idő, ami után a sikertelen bejelentkezési kísérletek számlálója lenullázódik"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "CSRF védelem letiltása"
|
||||
description = "Cross-Site Request Forgery védelem letiltása (nem ajánlott)"
|
||||
@@ -4883,48 +4771,6 @@ description = "Részletes hibaüzenetek megjelenítése felhasználók számára
|
||||
title = "Adatbázis"
|
||||
description = "Egyéni adatbázis-kapcsolati beállítások konfigurálása vállalati telepítésekhez."
|
||||
configuration = "Adatbázis-konfiguráció"
|
||||
backupTitle = "Biztonsági mentések és visszaállítás"
|
||||
backupDescription = "H2 biztonsági mentések kezelése közvetlenül az admin konzolról."
|
||||
loadError = "Nem sikerült betölteni az adatbázis biztonsági mentéseit"
|
||||
backupCreated = "A biztonsági mentés sikeresen létrejött"
|
||||
backupFailed = "Nem sikerült létrehozni a biztonsági mentést"
|
||||
deleteTitle = "Biztonsági mentés törlése"
|
||||
deleteConfirm = "Törli ezt a biztonsági mentést? Ez nem vonható vissza."
|
||||
deleteSuccess = "Biztonsági mentés törölve"
|
||||
deleteFailed = "Nem sikerült törölni a biztonsági mentést"
|
||||
deleteConfirmAction = "Biztonsági mentés törlése"
|
||||
downloadFailed = "Nem sikerült letölteni a biztonsági mentést"
|
||||
version = "H2 verzió"
|
||||
embedded = "Beágyazott H2"
|
||||
external = "Külső adatbázis"
|
||||
h2Only = "A biztonsági mentések csak a beágyazott H2 adatbázishoz érhetők el."
|
||||
h2Hint = "Állítsa az adatbázistípust H2-re, és tiltsa le az egyéni adatbázist a mentés és visszaállítás engedélyezéséhez."
|
||||
manageBackups = "Biztonsági mentések kezelése"
|
||||
refresh = "Frissítés"
|
||||
createBackup = "Biztonsági mentés készítése"
|
||||
uploadTitle = "Feltöltés és importálás"
|
||||
chooseFile = "Válasszon egy .sql biztonsági mentési fájlt"
|
||||
importFromUpload = "Feltöltés importálása"
|
||||
confirmImportTitle = "Adatbázis-import megerősítése"
|
||||
overwriteWarning = "Figyelem: Ez felülírja a jelenlegi adatbázist."
|
||||
overwriteWarningBody = "Minden meglévő adatot a feltöltött biztonsági mentés felülír. Ez a művelet nem vonható vissza."
|
||||
confirmCodeLabel = "A folytatáshoz adja meg a megerősítő kódot"
|
||||
enterCode = "Adja meg a fent látható kódot"
|
||||
confirmImport = "Importálás megerősítése"
|
||||
codeMismatch = "A megerősítő kód nem egyezik"
|
||||
codeMismatchBody = "A folytatáshoz pontosan a megjelenített kódot adja meg."
|
||||
selectFile = "Válasszon ki egy .sql fájlt az importáláshoz"
|
||||
importSuccess = "A biztonsági mentés sikeresen importálva"
|
||||
importFailed = "Nem sikerült importálni a biztonsági mentést"
|
||||
noBackups = "Még nem található biztonsági mentés."
|
||||
unavailable = "A mentéslista nem érhető el a jelenlegi adatbázis-konfigurációhoz."
|
||||
fileName = "Fájl"
|
||||
created = "Létrehozva"
|
||||
size = "Méret"
|
||||
actions = "Műveletek"
|
||||
download = "Letöltés"
|
||||
import = "Importálás"
|
||||
delete = "Törlés"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "Egyéni adatbázis engedélyezése"
|
||||
@@ -5002,26 +4848,6 @@ description = "Maximális DPI a képfeldolgozáshoz (0 = korlátlan)"
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "Tessdata könyvtár"
|
||||
description = "Elérési út a tessdata könyvtárhoz az OCR nyelvi fájlokhoz"
|
||||
downloadMissingTitle = "Nincs kiválasztott nyelv"
|
||||
downloadMissingBody = "Válasszon ki legalább egy letöltendő nyelvet."
|
||||
downloadSuccessTitle = "Nyelvek letöltve"
|
||||
downloadSuccessBody = "A kiválasztott tessdata nyelvek mentésre kerültek."
|
||||
downloadErrorTitle = "Sikertelen letöltés"
|
||||
loadingLanguages = "Telepített tessdata nyelvek betöltése..."
|
||||
installedLanguages = "Telepített tessdata nyelvek"
|
||||
noLanguages = "Nem találhatók tessdata nyelvek a beállított könyvtárban."
|
||||
downloadLabel = "További tessdata nyelvek letöltése"
|
||||
downloadPlaceholder = "Válasszon nyelveket"
|
||||
downloadNothingFound = "Nem találhatók további nyelvek"
|
||||
permissionNotice = "A tessdata elérési út nem írható. A letöltések a böngészőben nyílnak meg; mentse a .traineddata fájlokat kézzel a tessdata mappába."
|
||||
manualLinks = "Kézi letöltések: kattintson a hivatkozásokra, és helyezze a fájlokat a tessdata mappába."
|
||||
downloadButton = "Kiválasztott nyelvek letöltése"
|
||||
downloadInvalidTitle = "Érvénytelen kiválasztás"
|
||||
downloadInvalidBody = "Néhány kiválasztott nyelv nem tölthető le. Frissítsen, és válasszon a listából."
|
||||
downloadErrorNetwork = "A letöltés hálózati hiba miatt sikertelen. Ellenőrizze a kapcsolatot, majd próbálja újra."
|
||||
downloadErrorServer = "A szerveren hiba történt a tessdata nyelvek letöltése közben. Próbálja meg később újra."
|
||||
downloadErrorPermission = "A tessdata könyvtár nem írható: {{message}}. Válasszon írható könyvtárat (pl. az alkalmazásadatok mappája alatt), vagy módosítsa a jogosultságokat."
|
||||
downloadErrorGeneric = "Sikertelen letöltés: {{message}}. Próbálja meg később újra."
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "HTML tisztítás letiltása"
|
||||
@@ -5525,7 +5351,6 @@ desc = "Többlépéses munkafolyamatok építése PDF-műveletek láncolásával
|
||||
invalidStep = "Érvénytelen lépés"
|
||||
reviewTitle = "Automatizálás eredményei"
|
||||
copyToSaved = "Másolás a mentettek közé"
|
||||
exportForFolderScanning = "Exportálás mappavizsgálathoz"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "Válassza ki az automatizálással feldolgozandó fájlokat"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "Automatizálás létrehozása"
|
||||
editTitle = "Automatizálás szerkesztése"
|
||||
intro = "Az automatizálások egymás után futtatják az eszközöket. Kezdésként adja hozzá az eszközöket a kívánt sorrendben."
|
||||
save = "Automatizálás mentése"
|
||||
exportForFolderScanning = "Exportálás mappavizsgálathoz"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "Automatizálás neve"
|
||||
@@ -5922,11 +5746,9 @@ username = "Felhasználónév (e-mail)"
|
||||
usernamePlaceholder = "user@example.com"
|
||||
password = "Jelszó"
|
||||
passwordPlaceholder = "Adja meg a jelszót"
|
||||
passwordRequired = "Jelszó szükséges"
|
||||
role = "Szerepkör"
|
||||
team = "Csapat (opcionális)"
|
||||
teamPlaceholder = "Válasszon csapatot"
|
||||
authType = "Hitelesítési típus"
|
||||
forcePasswordChange = "Jelszócsere kényszerítése az első bejelentkezéskor"
|
||||
cancel = "Mégse"
|
||||
submit = "Tag hozzáadása"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "Felhasználónév és jelszó megadása kötelező"
|
||||
passwordTooShort = "A jelszónak legalább 6 karakterből kell állnia"
|
||||
success = "Felhasználó sikeresen létrehozva"
|
||||
error = "Nem sikerült létrehozni a felhasználót"
|
||||
forceMFA = "MFA beállításának kényszerítése a következő bejelentkezéskor"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "Jelszó"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "A felhasználó SSO szolgáltatón keresztül azonosítja magát"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "Tag szerkesztése"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "{{count}} felhasználói hely elérhető"
|
||||
noSlotsAvailable = "Nincs elérhető hely"
|
||||
currentUsage = "Jelenleg {{current}} / {{max}} felhasználói licenc használatban"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "A felhasználónál az MFA sikeresen letiltva"
|
||||
adminDisableError = "Nem sikerült letiltani az MFA-t a felhasználónál"
|
||||
disableByAdmin = "MFA letiltása"
|
||||
|
||||
[workspace.teams]
|
||||
title = "Csapatok"
|
||||
description = "Csapatok kezelése és a munkaterület tagjainak rendszerezése"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "Végpont"
|
||||
visits = "Látogatások"
|
||||
percentage = "Százalék"
|
||||
noData = "Nem áll rendelkezésre adat"
|
||||
unknownEndpoint = "Ismeretlen végpont"
|
||||
|
||||
[backendHealth]
|
||||
checking = "Backend állapotának ellenőrzése..."
|
||||
|
||||
@@ -306,7 +306,6 @@ selectOperation = "Pilih Operasi"
|
||||
addOperationButton = "Tambah operasi"
|
||||
pipelineHeader = "Pipeline:"
|
||||
saveButton = "Unduh"
|
||||
saveForFolderScanning = "Simpan untuk Pemindaian Folder"
|
||||
validateButton = "Validasi"
|
||||
|
||||
[enterpriseEdition]
|
||||
@@ -439,9 +438,7 @@ fullscreen = "Layar penuh"
|
||||
[settings.general.updates]
|
||||
title = "Pembaruan Software"
|
||||
description = "Periksa pembaruan dan lihat informasi versi"
|
||||
currentBackendVersion = "Current Backend Version"
|
||||
currentFrontendVersion = "Current Frontend Version"
|
||||
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
|
||||
currentVersion = "Versi Saat Ini"
|
||||
latestVersion = "Versi Terbaru"
|
||||
checkForUpdates = "Periksa Pembaruan"
|
||||
viewDetails = "Lihat Detail"
|
||||
@@ -559,30 +556,6 @@ webBrowserSettings = "Pengaturan Peramban Web"
|
||||
syncToBrowser = "Sinkronisasi Akun -> Browser"
|
||||
syncToAccount = "Sinkronisasi Akun <- Browser"
|
||||
|
||||
[account.mfa]
|
||||
title = "Autentikasi dua faktor"
|
||||
setupFailed = "Tidak dapat memulai penyiapan autentikasi dua faktor. Coba lagi."
|
||||
codeRequired = "Masukkan kode autentikasi untuk melanjutkan."
|
||||
enabled = "Autentikasi dua faktor diaktifkan."
|
||||
enableFailed = "Tidak dapat mengaktifkan autentikasi dua faktor. Periksa kode dan coba lagi."
|
||||
disabled = "Autentikasi dua faktor dinonaktifkan."
|
||||
disableFailed = "Tidak dapat menonaktifkan autentikasi dua faktor. Periksa kode dan coba lagi."
|
||||
description = "Tambahkan lapisan keamanan ekstra ke akun Anda."
|
||||
enableButton = "Aktifkan autentikasi dua faktor"
|
||||
disableButton = "Nonaktifkan autentikasi dua faktor"
|
||||
setupTitle = "Siapkan autentikasi dua faktor"
|
||||
setupDescription = "Pindai kode QR dengan aplikasi autentikator Anda, lalu masukkan kode 6 digit untuk mengonfirmasi."
|
||||
manualKey = "Kunci penyiapan manual"
|
||||
secretWarning = "Jaga kerahasiaan kunci ini. Siapa pun yang memiliki akses dapat menghasilkan kode autentikasi yang valid."
|
||||
codePlaceholder = "Masukkan kode 6 digit"
|
||||
confirmEnable = "Aktifkan"
|
||||
disableTitle = "Nonaktifkan autentikasi dua faktor"
|
||||
disableDescription = "Masukkan kode autentikasi yang valid untuk menonaktifkan autentikasi dua faktor."
|
||||
codeLabel = "Kode autentikasi"
|
||||
confirmDisable = "Nonaktifkan"
|
||||
ssoDescription = "Autentikasi dua faktor dikelola oleh penyedia identitas Anda untuk akun single sign-on."
|
||||
ssoManaged = "Konfigurasikan MFA melalui penyedia identitas Anda."
|
||||
|
||||
[adminUserSettings]
|
||||
title = "Pengaturan Kontrol Pengguna"
|
||||
header = "Pengaturan Kontrol Admin"
|
||||
@@ -952,7 +925,6 @@ title = "Pisah otomatis ukuran/jumlah"
|
||||
desc = "Membagi satu PDF menjadi beberapa dokumen berdasarkan ukuran, jumlah halaman, atau jumlah dokumen"
|
||||
|
||||
[home.replaceColor]
|
||||
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change"
|
||||
title = "Ganti & Balik Warna"
|
||||
desc = "Ganti atau balik warna dalam dokumen PDF"
|
||||
|
||||
@@ -967,22 +939,18 @@ title = "Pemindaian Folder Otomatis"
|
||||
desc = "Tautan ke panduan pemindaian folder otomatis"
|
||||
|
||||
[home.devSsoGuide]
|
||||
tags = "SSO,single sign-on,authentication,SAML,OAuth,OIDC,login,enterprise,identity provider,IdP"
|
||||
title = "Panduan SSO"
|
||||
desc = "Tautan ke panduan SSO"
|
||||
|
||||
[home.devAirgapped]
|
||||
tags = "air-gapped,offline,isolated,no internet,disconnected,secure,network isolation,standalone"
|
||||
title = "Penyiapan Air-gapped"
|
||||
desc = "Tautan ke panduan penyiapan air-gapped"
|
||||
|
||||
[home.addPassword]
|
||||
tags = "encrypt,password,lock,secure,protect,security,encryption,safeguard,confidential,private,restrict access"
|
||||
title = "Tambahkan Kata Sandi"
|
||||
desc = "Enkripsi dokumen PDF Anda dengan kata sandi."
|
||||
|
||||
[home.changePermissions]
|
||||
tags = "permissions,restrictions,rights,access control,allow,deny,printing,copying,editing,modify permissions,security settings,user rights"
|
||||
title = "Ganti Perizinan"
|
||||
desc = "Ubah pembatasan dan izin dokumen"
|
||||
|
||||
@@ -992,12 +960,10 @@ title = "Otomasi"
|
||||
desc = "Bangun alur kerja multi-langkah dengan merangkai tindakan PDF. Ideal untuk tugas berulang."
|
||||
|
||||
[home.overlay-pdfs]
|
||||
tags = "overlay,combine,merge,layer,superimpose,overlay PDF,layer PDFs,combine PDFs,stack,overlay pages,background,foreground,composite"
|
||||
desc = "Menumpuk PDF di atas PDF lain"
|
||||
title = "Tumpuk PDF"
|
||||
|
||||
[home.pdfTextEditor]
|
||||
tags = "edit text,modify text,change text,edit content,update text,rewrite,correct,amend,redline,revise,text editor,content editor"
|
||||
title = "Editor Teks PDF"
|
||||
desc = "Tinjau dan edit ekspor Stirling PDF JSON dengan pengeditan teks terkelompok dan pembuatan ulang PDF"
|
||||
|
||||
@@ -1298,10 +1264,7 @@ downloadHtml = "Unduh file perantara HTML alih-alih PDF"
|
||||
pdfaOptions = "Opsi PDF/A"
|
||||
outputFormat = "Format Keluaran"
|
||||
pdfaNote = "PDF/A-1b lebih kompatibel, PDF/A-2b mendukung lebih banyak fitur."
|
||||
pdfaFormat = "Format PDF/A"
|
||||
pdfaDigitalSignatureWarning = "PDF ini mengandung tanda tangan digital. Ini akan dihapus pada langkah berikutnya."
|
||||
pdfxDigitalSignatureWarning = "PDF berisi tanda tangan digital. Ini akan dihapus pada langkah berikutnya."
|
||||
pdfxDescription = "PDF/X adalah subset standar ISO dari PDF untuk pencetakan yang andal dan pertukaran grafis."
|
||||
fileFormat = "Format File"
|
||||
wordDoc = "Dokumen Word"
|
||||
wordDocExt = "Dokumen Word (.docx)"
|
||||
@@ -2234,16 +2197,6 @@ tip = "Saat ini tidak dapat digunakan untuk beberapa input sekaligus"
|
||||
outputFormat = "Format keluaran"
|
||||
pdfWithDigitalSignature = "PDF ini mengandung tanda tangan digital. Ini akan dihapus pada langkah berikutnya."
|
||||
|
||||
[pdfToPDFX]
|
||||
tags = "cetak,standar,konversi,produksi,pracetak,arsip"
|
||||
title = "PDF ke PDF/X"
|
||||
header = "PDF ke PDF/X"
|
||||
credit = "Layanan ini menggunakan Ghostscript untuk konversi PDF/X"
|
||||
submit = "Konversi"
|
||||
tip = "Saat ini tidak berfungsi untuk beberapa input sekaligus"
|
||||
outputFormat = "Format keluaran"
|
||||
pdfWithDigitalSignature = "PDF berisi tanda tangan digital. Ini akan dihapus pada langkah berikutnya."
|
||||
|
||||
[PDFToWord]
|
||||
tags = "doc, docx, odt, kata, transformasi, format, konversi, kantor, microsoft, docfile"
|
||||
title = "PDF ke Word"
|
||||
@@ -3200,18 +3153,6 @@ other = "Lainnya"
|
||||
perPageInfo = "Info per Halaman"
|
||||
tableOfContents = "Daftar Isi"
|
||||
|
||||
[getPdfInfo.compliance]
|
||||
notDetected = "Tidak Terdeteksi"
|
||||
passed = "Lulus"
|
||||
failed = "Gagal"
|
||||
compliant = "Patuh"
|
||||
nonCompliant = "Tidak Patuh"
|
||||
none = "Tidak ada standar yang terdeteksi"
|
||||
passedCount = "lulus"
|
||||
failedCount = "gagal"
|
||||
noVerification = "Tidak Ada Verifikasi Dilakukan"
|
||||
noVerificationDesc = "Kepatuhan terhadap standar PDF tidak diverifikasi untuk dokumen ini."
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "Lampiran"
|
||||
embeddedFiles = "File Tertanam"
|
||||
@@ -3243,8 +3184,6 @@ permsRestricted = "{{count}} pembatasan"
|
||||
permsMixed = "Beberapa izin dibatasi"
|
||||
hasCompliance = "Memiliki standar kepatuhan"
|
||||
noCompliance = "Tidak ada standar kepatuhan"
|
||||
compliancePassed = "Sesuai {{standards}}"
|
||||
complianceChecked = "Standar diverifikasi ({{failed}} gagal)"
|
||||
basic = "Informasi Dasar"
|
||||
documentInfo = "Informasi Dokumen"
|
||||
securityTitle = "Status Keamanan"
|
||||
@@ -3562,31 +3501,6 @@ imageSize = "Ukuran Gambar"
|
||||
margin = "Margin"
|
||||
positionAndFormatting = "Posisi & Pemformatan"
|
||||
quickPosition = "Pilih posisi pada halaman untuk menempatkan stempel."
|
||||
clickToExpand = "Click to expand"
|
||||
customDateDesc = "Custom format"
|
||||
dateDesc = "Current date"
|
||||
dateTimeVars = "Date & Time"
|
||||
datetimeDesc = "Date and time combined"
|
||||
dynamicVariables = "Dynamic Variables"
|
||||
examples = "Examples"
|
||||
fileVars = "File Information"
|
||||
filenameDesc = "Filename without extension"
|
||||
filenameFullDesc = "Filename with extension"
|
||||
metadataDesc = "From PDF document properties"
|
||||
metadataVars = "Document Metadata"
|
||||
multiLine = "multi-line"
|
||||
otherVars = "Other"
|
||||
pageNumberDesc = "Current page number"
|
||||
pageVars = "Page Information"
|
||||
preview = "Preview:"
|
||||
selectTemplate = "Select a template..."
|
||||
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
|
||||
timeDesc = "Current time"
|
||||
totalPagesDesc = "Total number of pages"
|
||||
useTemplate = "Use Template"
|
||||
uuidDesc = "Short unique identifier (8 chars)"
|
||||
variablesHelp = "Click on any variable to insert it into your stamp text. Use @@ for literal @."
|
||||
yearMonthDayDesc = "Individual date parts"
|
||||
|
||||
[AddStampRequest.error]
|
||||
failed = "Terjadi kesalahan saat menambahkan stempel ke PDF."
|
||||
@@ -3594,14 +3508,6 @@ failed = "Terjadi kesalahan saat menambahkan stempel ke PDF."
|
||||
[AddStampRequest.results]
|
||||
title = "Hasil Stempel"
|
||||
|
||||
[AddStampRequest.template]
|
||||
custom = "Custom"
|
||||
dateHeader = "Date Header"
|
||||
draftWatermark = "Draft Watermark"
|
||||
europeanDate = "European Date"
|
||||
pageNumberFooter = "Page Number Footer"
|
||||
timestamp = "Timestamp"
|
||||
|
||||
[removeImagePdf]
|
||||
tags = "Hapus Gambar,Operasi Halaman,Backend,server side"
|
||||
|
||||
@@ -3817,13 +3723,6 @@ passwordChangedSuccess = "Kata sandi berhasil diubah! Silakan masuk dengan kata
|
||||
credentialsUpdated = "Kredensial Anda telah diperbarui. Silakan masuk lagi."
|
||||
defaultCredentials = "Kredensial Login Bawaan"
|
||||
changePasswordWarning = "Harap ganti kata sandi Anda setelah login untuk pertama kalinya"
|
||||
mfaRequired = "Kode autentikasi dua faktor diperlukan"
|
||||
mfaCode = "Kode Autentikasi"
|
||||
enterMfaCode = "Masukkan kode 6 digit"
|
||||
mfaPromptTitle = "Autentikasi dua faktor"
|
||||
mfaPromptBody = "Masukkan kode autentikasi dari aplikasi autentikator Anda untuk melanjutkan."
|
||||
verifyingMfa = "Memverifikasi..."
|
||||
verifyMfa = "Verifikasi kode"
|
||||
|
||||
[login.slides.overview]
|
||||
alt = "Sekilas Stirling PDF"
|
||||
@@ -4425,10 +4324,6 @@ title = "Mode Login Diperlukan"
|
||||
message = "Mode login harus diaktifkan untuk mengubah pengaturan admin. Harap set SECURITY_ENABLELOGIN=true di lingkungan Anda atau security.enableLogin: true di settings.yml, lalu mulai ulang server."
|
||||
readOnly = "Pengaturan di bawah ini menampilkan nilai contoh sebagai referensi. Aktifkan mode login untuk melihat dan mengedit konfigurasi sebenarnya."
|
||||
|
||||
[admin.settings.enterpriseRequired]
|
||||
title = "Lisensi Enterprise Diperlukan"
|
||||
message = "Lisensi Enterprise diperlukan untuk mengakses {{featureName}}. Anda sedang melihat data demo sebagai referensi."
|
||||
|
||||
[admin.settings.restart]
|
||||
title = "Perlu Mulai Ulang"
|
||||
message = "Pengaturan telah berhasil disimpan. Diperlukan mulai ulang server agar perubahan berlaku."
|
||||
@@ -4568,13 +4463,6 @@ description = "Jumlah maksimum kegagalan login sebelum akun terkunci"
|
||||
label = "Waktu Reset Login (menit)"
|
||||
description = "Waktu sebelum upaya login yang gagal direset"
|
||||
|
||||
[admin.settings.security.xFrameOptions]
|
||||
label = "X-Frame-Options"
|
||||
description = "Controls whether the application can be embedded in iframes"
|
||||
deny = "Deny (Prevents all framing)"
|
||||
sameorigin = "Same Origin (Allow framing from same domain)"
|
||||
disabled = "Disabled (No X-Frame-Options header)"
|
||||
|
||||
[admin.settings.security.csrfDisabled]
|
||||
label = "Nonaktifkan Perlindungan CSRF"
|
||||
description = "Nonaktifkan perlindungan Cross-Site Request Forgery (tidak disarankan)"
|
||||
@@ -4883,48 +4771,6 @@ description = "Tampilkan pesan kesalahan terperinci untuk pengguna."
|
||||
title = "Database"
|
||||
description = "Konfigurasikan pengaturan koneksi database kustom untuk penerapan enterprise."
|
||||
configuration = "Konfigurasi Database"
|
||||
backupTitle = "Cadangan & Pemulihan"
|
||||
backupDescription = "Kelola cadangan H2 langsung dari konsol admin."
|
||||
loadError = "Gagal memuat cadangan basis data"
|
||||
backupCreated = "Cadangan berhasil dibuat"
|
||||
backupFailed = "Gagal membuat cadangan"
|
||||
deleteTitle = "Hapus cadangan"
|
||||
deleteConfirm = "Hapus cadangan ini? Tindakan ini tidak dapat dibatalkan."
|
||||
deleteSuccess = "Cadangan dihapus"
|
||||
deleteFailed = "Gagal menghapus cadangan"
|
||||
deleteConfirmAction = "Hapus cadangan"
|
||||
downloadFailed = "Gagal mengunduh cadangan"
|
||||
version = "Versi H2"
|
||||
embedded = "H2 Tertanam"
|
||||
external = "DB Eksternal"
|
||||
h2Only = "Cadangan hanya tersedia untuk basis data H2 tertanam."
|
||||
h2Hint = "Setel tipe basis data ke H2 dan nonaktifkan basis data kustom untuk mengaktifkan pencadangan dan pemulihan."
|
||||
manageBackups = "Kelola cadangan"
|
||||
refresh = "Segarkan"
|
||||
createBackup = "Buat cadangan"
|
||||
uploadTitle = "Unggah & impor"
|
||||
chooseFile = "Pilih berkas cadangan .sql"
|
||||
importFromUpload = "Impor unggahan"
|
||||
confirmImportTitle = "Konfirmasi impor basis data"
|
||||
overwriteWarning = "Peringatan: Ini akan menimpa basis data saat ini."
|
||||
overwriteWarningBody = "Semua data yang ada akan diganti oleh cadangan yang diunggah. Tindakan ini tidak dapat dibatalkan."
|
||||
confirmCodeLabel = "Masukkan kode konfirmasi untuk melanjutkan"
|
||||
enterCode = "Masukkan kode yang ditampilkan di atas"
|
||||
confirmImport = "Konfirmasi impor"
|
||||
codeMismatch = "Kode konfirmasi tidak cocok"
|
||||
codeMismatchBody = "Masukkan kode persis seperti yang ditampilkan untuk melanjutkan."
|
||||
selectFile = "Silakan pilih berkas .sql untuk diimpor"
|
||||
importSuccess = "Cadangan berhasil diimpor"
|
||||
importFailed = "Gagal mengimpor cadangan"
|
||||
noBackups = "Belum ada cadangan."
|
||||
unavailable = "Daftar cadangan tidak tersedia untuk konfigurasi basis data saat ini."
|
||||
fileName = "Berkas"
|
||||
created = "Dibuat"
|
||||
size = "Ukuran"
|
||||
actions = "Tindakan"
|
||||
download = "Unduh"
|
||||
import = "Impor"
|
||||
delete = "Hapus"
|
||||
|
||||
[admin.settings.database.enableCustom]
|
||||
label = "Aktifkan Database Kustom"
|
||||
@@ -5002,26 +4848,6 @@ description = "DPI maksimum untuk pemrosesan gambar (0 = tanpa batas)"
|
||||
[admin.settings.advanced.tessdataDir]
|
||||
label = "Direktori Tessdata"
|
||||
description = "Path ke direktori tessdata untuk file bahasa OCR"
|
||||
downloadMissingTitle = "Tidak ada bahasa yang dipilih"
|
||||
downloadMissingBody = "Pilih setidaknya satu bahasa untuk diunduh."
|
||||
downloadSuccessTitle = "Bahasa berhasil diunduh"
|
||||
downloadSuccessBody = "Bahasa tessdata yang dipilih telah disimpan."
|
||||
downloadErrorTitle = "Unduhan Gagal"
|
||||
loadingLanguages = "Memuat bahasa tessdata yang terpasang..."
|
||||
installedLanguages = "Bahasa tessdata yang terpasang"
|
||||
noLanguages = "Tidak ditemukan bahasa tessdata di direktori yang dikonfigurasi."
|
||||
downloadLabel = "Unduh bahasa tessdata tambahan"
|
||||
downloadPlaceholder = "Pilih bahasa"
|
||||
downloadNothingFound = "Tidak ditemukan bahasa tambahan"
|
||||
permissionNotice = "Jalur tessdata tidak dapat ditulisi. Unduhan akan dibuka di peramban; silakan simpan berkas .traineddata secara manual ke dalam folder tessdata."
|
||||
manualLinks = "Unduhan manual: klik tautan dan tempatkan berkas ke folder tessdata."
|
||||
downloadButton = "Unduh bahasa yang dipilih"
|
||||
downloadInvalidTitle = "Pilihan tidak valid"
|
||||
downloadInvalidBody = "Beberapa bahasa yang dipilih tidak tersedia untuk diunduh. Silakan segarkan dan pilih dari daftar."
|
||||
downloadErrorNetwork = "Unduhan gagal karena kesalahan jaringan. Periksa koneksi Anda dan coba lagi."
|
||||
downloadErrorServer = "Server mengalami kesalahan saat mengunduh bahasa tessdata. Cobalah lagi nanti."
|
||||
downloadErrorPermission = "Direktori tessdata tidak dapat ditulisi: {{message}}. Pilih direktori yang dapat ditulisi (mis. di bawah folder data aplikasi) atau sesuaikan izin."
|
||||
downloadErrorGeneric = "Unduhan gagal: {{message}}. Silakan coba lagi nanti."
|
||||
|
||||
[admin.settings.advanced.disableSanitize]
|
||||
label = "Nonaktifkan Sanitasi HTML"
|
||||
@@ -5525,7 +5351,6 @@ desc = "Bangun alur kerja multi-langkah dengan merangkaikan aksi PDF. Ideal untu
|
||||
invalidStep = "Langkah tidak valid"
|
||||
reviewTitle = "Hasil Otomatisasi"
|
||||
copyToSaved = "Salin ke Tersimpan"
|
||||
exportForFolderScanning = "Ekspor untuk Pemindaian Folder"
|
||||
|
||||
[automate.files]
|
||||
placeholder = "Pilih file untuk diproses dengan otomatisasi ini"
|
||||
@@ -5547,7 +5372,6 @@ createTitle = "Buat Otomatisasi"
|
||||
editTitle = "Edit Otomatisasi"
|
||||
intro = "Otomatisasi menjalankan alat secara berurutan. Untuk memulai, tambahkan alat sesuai urutan yang Anda inginkan."
|
||||
save = "Simpan Otomatisasi"
|
||||
exportForFolderScanning = "Ekspor untuk Pemindaian Folder"
|
||||
|
||||
[automate.creation.name]
|
||||
label = "Nama Otomatisasi"
|
||||
@@ -5922,11 +5746,9 @@ username = "Nama Pengguna (Email)"
|
||||
usernamePlaceholder = "user@example.com"
|
||||
password = "Kata Sandi"
|
||||
passwordPlaceholder = "Masukkan kata sandi"
|
||||
passwordRequired = "Kata sandi diperlukan"
|
||||
role = "Peran"
|
||||
team = "Tim (Opsional)"
|
||||
teamPlaceholder = "Pilih tim"
|
||||
authType = "Jenis autentikasi"
|
||||
forcePasswordChange = "Wajibkan ganti kata sandi saat login pertama"
|
||||
cancel = "Batal"
|
||||
submit = "Tambah Anggota"
|
||||
@@ -5934,13 +5756,6 @@ usernameRequired = "Nama pengguna dan kata sandi wajib diisi"
|
||||
passwordTooShort = "Kata sandi harus minimal 6 karakter"
|
||||
success = "Pengguna berhasil dibuat"
|
||||
error = "Gagal membuat pengguna"
|
||||
forceMFA = "Wajibkan penyiapan MFA pada login berikutnya"
|
||||
|
||||
[workspace.people.authType]
|
||||
password = "Kata sandi"
|
||||
oauth = "OAuth2"
|
||||
saml = "SAML2"
|
||||
ssoDescription = "Pengguna akan diautentikasi melalui penyedia SSO"
|
||||
|
||||
[workspace.people.editMember]
|
||||
title = "Edit Anggota"
|
||||
@@ -6044,11 +5859,6 @@ slotsAvailable = "{{count}} slot pengguna tersedia"
|
||||
noSlotsAvailable = "Tidak ada slot tersedia"
|
||||
currentUsage = "Saat ini menggunakan {{current}} dari {{max}} lisensi pengguna"
|
||||
|
||||
[workspace.people.mfa]
|
||||
adminDisableSuccess = "MFA berhasil dinonaktifkan untuk pengguna"
|
||||
adminDisableError = "Gagal menonaktifkan MFA untuk pengguna"
|
||||
disableByAdmin = "Nonaktifkan MFA"
|
||||
|
||||
[workspace.teams]
|
||||
title = "Tim"
|
||||
description = "Kelola tim dan atur anggota ruang kerja"
|
||||
@@ -6491,7 +6301,6 @@ endpoint = "Endpoint"
|
||||
visits = "Kunjungan"
|
||||
percentage = "Persentase"
|
||||
noData = "Tidak ada data"
|
||||
unknownEndpoint = "Endpoint tidak dikenal"
|
||||
|
||||
[backendHealth]
|
||||
checking = "Memeriksa status backend..."
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user