diff --git a/README.md b/README.md index a979da3b683..e9f2d8166bb 100644 --- a/README.md +++ b/README.md @@ -172,27 +172,27 @@ Stirling PDF currently supports 38! | Language | Progress | | ------------------------------------------- | -------------------------------------- | -| Arabic (العربية) (ar_AR) | ![94%](https://geps.dev/progress/94) | -| Basque (Euskara) (eu_ES) | ![57%](https://geps.dev/progress/57) | -| Bulgarian (Български) (bg_BG) | ![99%](https://geps.dev/progress/99) | +| Arabic (العربية) (ar_AR) | ![93%](https://geps.dev/progress/93) | +| Basque (Euskara) (eu_ES) | ![56%](https://geps.dev/progress/56) | +| Bulgarian (Български) (bg_BG) | ![98%](https://geps.dev/progress/98) | | Catalan (Català) (ca_CA) | ![44%](https://geps.dev/progress/44) | -| Croatian (Hrvatski) (hr_HR) | ![87%](https://geps.dev/progress/87) | -| Czech (Česky) (cs_CZ) | ![83%](https://geps.dev/progress/83) | +| Croatian (Hrvatski) (hr_HR) | ![86%](https://geps.dev/progress/86) | +| Czech (Česky) (cs_CZ) | ![82%](https://geps.dev/progress/82) | | Danish (Dansk) (da_DK) | ![91%](https://geps.dev/progress/91) | -| Dutch (Nederlands) (nl_NL) | ![88%](https://geps.dev/progress/88) | +| Dutch (Nederlands) (nl_NL) | ![87%](https://geps.dev/progress/87) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) | -| French (Français) (fr_FR) | ![88%](https://geps.dev/progress/88) | -| German (Deutsch) (de_DE) | ![94%](https://geps.dev/progress/94) | +| French (Français) (fr_FR) | ![94%](https://geps.dev/progress/94) | +| German (Deutsch) (de_DE) | ![98%](https://geps.dev/progress/98) | | Greek (Ελληνικά) (el_GR) | ![75%](https://geps.dev/progress/75) | | Hindi (हिंदी) (hi_IN) | ![72%](https://geps.dev/progress/72) | | Hungarian (Magyar) (hu_HU) | ![69%](https://geps.dev/progress/69) | -| Indonesia (Bahasa Indonesia) (id_ID) | ![70%](https://geps.dev/progress/70) | +| Indonesia (Bahasa Indonesia) (id_ID) | ![69%](https://geps.dev/progress/69) | | Irish (Gaeilge) (ga_IE) | ![90%](https://geps.dev/progress/90) | -| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) | +| Italian (Italiano) (it_IT) | ![98%](https://geps.dev/progress/98) | | Japanese (日本語) (ja_JP) | ![87%](https://geps.dev/progress/87) | | Korean (한국어) (ko_KR) | ![77%](https://geps.dev/progress/77) | -| Norwegian (Norsk) (no_NB) | ![90%](https://geps.dev/progress/90) | +| Norwegian (Norsk) (no_NB) | ![89%](https://geps.dev/progress/89) | | Polish (Polski) (pl_PL) | ![99%](https://geps.dev/progress/99) | | Portuguese (Português) (pt_PT) | ![72%](https://geps.dev/progress/72) | | Portuguese Brazilian (Português) (pt_BR) | ![99%](https://geps.dev/progress/99) | @@ -201,13 +201,13 @@ Stirling PDF currently supports 38! | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![72%](https://geps.dev/progress/72) | | Simplified Chinese (简体中文) (zh_CN) | ![93%](https://geps.dev/progress/93) | | Slovakian (Slovensky) (sk_SK) | ![84%](https://geps.dev/progress/84) | -| Spanish (Español) (es_ES) | ![93%](https://geps.dev/progress/93) | -| Swedish (Svenska) (sv_SE) | ![92%](https://geps.dev/progress/92) | -| Thai (ไทย) (th_TH) | ![91%](https://geps.dev/progress/91) | +| Spanish (Español) (es_ES) | ![98%](https://geps.dev/progress/98) | +| Swedish (Svenska) (sv_SE) | ![94%](https://geps.dev/progress/94) | +| Thai (ไทย) (th_TH) | ![90%](https://geps.dev/progress/90) | | Traditional Chinese (繁體中文) (zh_TW) | ![99%](https://geps.dev/progress/99) | | Turkish (Türkçe) (tr_TR) | ![94%](https://geps.dev/progress/94) | | Ukrainian (Українська) (uk_UA) | ![82%](https://geps.dev/progress/82) | -| Vietnamese (Tiếng Việt) (vi_VN) | ![91%](https://geps.dev/progress/91) | +| Vietnamese (Tiếng Việt) (vi_VN) | ![90%](https://geps.dev/progress/90) | ## Contributing (creating issues, translations, fixing bugs, etc.) diff --git a/build.gradle b/build.gradle index f620cdc18cd..e9ceaa90c7a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id "java" - id "org.springframework.boot" version "3.3.4" + id "org.springframework.boot" version "3.3.5" id "io.spring.dependency-management" version "1.1.6" id "org.springdoc.openapi-gradle-plugin" version "1.8.0" id "io.swagger.swaggerhub" version "1.3.2" @@ -13,7 +13,7 @@ plugins { import com.github.jk1.license.render.* ext { - springBootVersion = "3.3.4" + springBootVersion = "3.3.5" pdfboxVersion = "3.0.3" logbackVersion = "1.5.7" imageioVersion = "3.12.0" @@ -181,6 +181,9 @@ dependencies { runtimeOnly "com.twelvemonkeys.imageio:imageio-webp:$imageioVersion" // runtimeOnly "com.twelvemonkeys.imageio:imageio-xwd:$imageioVersion" + // Image metadata extractor + implementation "com.drewnoakes:metadata-extractor:2.19.0" + implementation "commons-io:commons-io:2.17.0" implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0" //general PDF diff --git a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java index e0ae56a1c10..9c0b3bf4347 100644 --- a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java @@ -5,6 +5,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +43,7 @@ public void enableEndpoint(String endpoint) { public void disableEndpoint(String endpoint) { if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) { - logger.info("Disabling {}", endpoint); + logger.debug("Disabling {}", endpoint); endpointStatuses.put(endpoint, false); } } @@ -76,6 +77,23 @@ public void disableGroup(String group) { } } + public void logDisabledEndpointsSummary() { + List disabledList = + endpointStatuses.entrySet().stream() + .filter(entry -> !entry.getValue()) // only get disabled endpoints (value + // is false) + .map(Map.Entry::getKey) + .sorted() + .collect(Collectors.toList()); + + if (!disabledList.isEmpty()) { + logger.info( + "Total disabled endpoints: {}. Disabled endpoints: {}", + disabledList.size(), + String.join(", ", disabledList)); + } + } + public void init() { // Adding endpoints to "PageOps" group addEndpointToGroup("PageOps", "remove-pages"); @@ -163,14 +181,12 @@ public void init() { // python addEndpointToGroup("Python", "extract-image-scans"); - addEndpointToGroup("Python", REMOVE_BLANKS); addEndpointToGroup("Python", "html-to-pdf"); addEndpointToGroup("Python", "url-to-pdf"); addEndpointToGroup("Python", "pdf-to-img"); // openCV addEndpointToGroup("OpenCV", "extract-image-scans"); - addEndpointToGroup("OpenCV", REMOVE_BLANKS); // LibreOffice addEndpointToGroup("LibreOffice", "repair"); @@ -230,6 +246,17 @@ public void init() { addEndpointToGroup("Javascript", "sign"); addEndpointToGroup("Javascript", "compare"); addEndpointToGroup("Javascript", "adjust-contrast"); + + // Ghostscript dependent endpoints + addEndpointToGroup("Ghostscript", "compress-pdf"); + addEndpointToGroup("Ghostscript", "pdf-to-pdfa"); + + // Weasyprint dependent endpoints + addEndpointToGroup("Weasyprint", "html-to-pdf"); + addEndpointToGroup("Weasyprint", "url-to-pdf"); + + // Pdftohtml dependent endpoints + addEndpointToGroup("Pdftohtml", "pdf-to-html"); } private void processEnvironmentConfigs() { @@ -251,5 +278,9 @@ private void processEnvironmentConfigs() { } } + public Set getEndpointsForGroup(String group) { + return endpointGroups.getOrDefault(group, new HashSet<>()); + } + private static final String REMOVE_BLANKS = "remove-blanks"; } diff --git a/src/main/java/stirling/software/SPDF/config/ExternalAppDepConfig.java b/src/main/java/stirling/software/SPDF/config/ExternalAppDepConfig.java new file mode 100644 index 00000000000..c939bd60220 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/ExternalAppDepConfig.java @@ -0,0 +1,146 @@ +package stirling.software.SPDF.config; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; + +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; + +@Configuration +@Slf4j +public class ExternalAppDepConfig { + @Autowired private EndpointConfiguration endpointConfiguration; + + private boolean isCommandAvailable(String command) { + try { + ProcessBuilder processBuilder = new ProcessBuilder(); + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + processBuilder.command("where", command); + } else { + processBuilder.command("which", command); + } + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + return exitCode == 0; + } catch (Exception e) { + log.debug("Error checking for command {}: {}", command, e.getMessage()); + return false; + } + } + + private final Map> commandToGroupMapping = + new HashMap<>() { + { + put("gs", List.of("Ghostscript")); + put("soffice", List.of("LibreOffice")); + put("ocrmypdf", List.of("OCRmyPDF")); + put("weasyprint", List.of("Weasyprint")); + put("pdftohtml", List.of("Pdftohtml")); + } + }; + + private List getAffectedFeatures(String group) { + return endpointConfiguration.getEndpointsForGroup(group).stream() + .map(endpoint -> formatEndpointAsFeature(endpoint)) + .collect(Collectors.toList()); + } + + private String formatEndpointAsFeature(String endpoint) { + // First replace common terms + String feature = endpoint.replace("-", " ").replace("pdf", "PDF").replace("img", "image"); + + // Split into words and capitalize each word + return Arrays.stream(feature.split("\\s+")) + .map(word -> capitalizeWord(word)) + .collect(Collectors.joining(" ")); + } + + private String capitalizeWord(String word) { + if (word.isEmpty()) { + return word; + } + if ("pdf".equalsIgnoreCase(word)) { + return "PDF"; + } + return word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase(); + } + + private void checkDependencyAndDisableGroup(String command) { + boolean isAvailable = isCommandAvailable(command); + if (!isAvailable) { + List affectedGroups = commandToGroupMapping.get(command); + + if (affectedGroups != null) { + for (String group : affectedGroups) { + List affectedFeatures = getAffectedFeatures(group); + endpointConfiguration.disableGroup(group); + log.warn( + "Missing dependency: {} - Disabling group: {} (Affected features: {})", + command, + group, + affectedFeatures != null && !affectedFeatures.isEmpty() + ? String.join(", ", affectedFeatures) + : "unknown"); + } + } + } + } + + @PostConstruct + public void checkDependencies() { + + // Check core dependencies + checkDependencyAndDisableGroup("gs"); + checkDependencyAndDisableGroup("soffice"); + checkDependencyAndDisableGroup("ocrmypdf"); + checkDependencyAndDisableGroup("weasyprint"); + checkDependencyAndDisableGroup("pdftohtml"); + + // Special handling for Python/OpenCV dependencies + boolean pythonAvailable = isCommandAvailable("python3") || isCommandAvailable("python"); + if (!pythonAvailable) { + List pythonFeatures = getAffectedFeatures("Python"); + List openCVFeatures = getAffectedFeatures("OpenCV"); + + endpointConfiguration.disableGroup("Python"); + endpointConfiguration.disableGroup("OpenCV"); + log.warn( + "Missing dependency: Python - Disabling Python features: {} and OpenCV features: {}", + String.join(", ", pythonFeatures), + String.join(", ", openCVFeatures)); + } else { + // If Python is available, check for OpenCV + try { + ProcessBuilder processBuilder = new ProcessBuilder(); + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + processBuilder.command("python", "-c", "import cv2"); + } else { + processBuilder.command("python3", "-c", "import cv2"); + } + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + if (exitCode != 0) { + List openCVFeatures = getAffectedFeatures("OpenCV"); + endpointConfiguration.disableGroup("OpenCV"); + log.warn( + "OpenCV not available in Python - Disabling OpenCV features: {}", + String.join(", ", openCVFeatures)); + } + } catch (Exception e) { + List openCVFeatures = getAffectedFeatures("OpenCV"); + endpointConfiguration.disableGroup("OpenCV"); + log.warn( + "Error checking OpenCV: {} - Disabling OpenCV features: {}", + e.getMessage(), + String.join(", ", openCVFeatures)); + } + } + endpointConfiguration.logDisabledEndpointsSummary(); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml2/CertificateUtils.java b/src/main/java/stirling/software/SPDF/config/security/saml2/CertificateUtils.java index 6a01ce0aaa9..4f0d2488ad4 100644 --- a/src/main/java/stirling/software/SPDF/config/security/saml2/CertificateUtils.java +++ b/src/main/java/stirling/software/SPDF/config/security/saml2/CertificateUtils.java @@ -1,48 +1,42 @@ package stirling.software.SPDF.config.security.saml2; import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Base64; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; import org.springframework.core.io.Resource; -import org.springframework.util.FileCopyUtils; public class CertificateUtils { public static X509Certificate readCertificate(Resource certificateResource) throws Exception { - String certificateString = - new String( - FileCopyUtils.copyToByteArray(certificateResource.getInputStream()), - StandardCharsets.UTF_8); - String certContent = - certificateString - .replace("-----BEGIN CERTIFICATE-----", "") - .replace("-----END CERTIFICATE-----", "") - .replaceAll("\\R", "") - .replaceAll("\\s+", ""); - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - byte[] decodedCert = Base64.getDecoder().decode(certContent); - return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(decodedCert)); + try (PemReader pemReader = + new PemReader( + new InputStreamReader( + certificateResource.getInputStream(), StandardCharsets.UTF_8))) { + PemObject pemObject = pemReader.readPemObject(); + byte[] decodedCert = pemObject.getContent(); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(decodedCert)); + } } public static RSAPrivateKey readPrivateKey(Resource privateKeyResource) throws Exception { - String privateKeyString = - new String( - FileCopyUtils.copyToByteArray(privateKeyResource.getInputStream()), - StandardCharsets.UTF_8); - String privateKeyContent = - privateKeyString - .replace("-----BEGIN PRIVATE KEY-----", "") - .replace("-----END PRIVATE KEY-----", "") - .replaceAll("\\R", "") - .replaceAll("\\s+", ""); - KeyFactory kf = KeyFactory.getInstance("RSA"); - byte[] decodedKey = Base64.getDecoder().decode(privateKeyContent); - return (RSAPrivateKey) kf.generatePrivate(new PKCS8EncodedKeySpec(decodedKey)); + try (PemReader pemReader = + new PemReader( + new InputStreamReader( + privateKeyResource.getInputStream(), StandardCharsets.UTF_8))) { + PemObject pemObject = pemReader.readPemObject(); + byte[] decodedKey = pemObject.getContent(); + return (RSAPrivateKey) + KeyFactory.getInstance("RSA") + .generatePrivate(new PKCS8EncodedKeySpec(decodedKey)); + } } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java index c37fcc9c6e3..b36f8c90772 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java @@ -1,8 +1,11 @@ package stirling.software.SPDF.controller.api.security; +import java.awt.Color; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.security.KeyStore; @@ -14,12 +17,38 @@ import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.Calendar; +import java.util.List; import org.apache.pdfbox.examples.signature.CreateSignatureBase; import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.PDResources; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.common.PDStream; +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName; +import org.apache.pdfbox.pdmodel.graphics.blend.BlendMode; +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions; +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDField; +import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField; +import org.apache.pdfbox.util.Matrix; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x500.RDN; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x500.style.IETFUtils; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMDecryptorProvider; import org.bouncycastle.openssl.PEMEncryptedKeyPair; @@ -35,6 +64,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; @@ -62,13 +92,103 @@ public class CertSignController { } class CreateSignature extends CreateSignatureBase { + File imageFile; + public CreateSignature(KeyStore keystore, char[] pin) throws KeyStoreException, - UnrecoverableKeyException, - NoSuchAlgorithmException, - IOException, - CertificateException { + UnrecoverableKeyException, + NoSuchAlgorithmException, + IOException, + CertificateException { super(keystore, pin); + ClassPathResource resource = new ClassPathResource("static/images/signature.png"); + imageFile = resource.getFile(); + } + + public InputStream createVisibleSignature(PDDocument srcDoc, PDSignature signature, Integer pageNumber, + Boolean showImage) throws IOException { + // modified from org.apache.pdfbox.examples.signature.CreateVisibleSignature2 + try (PDDocument doc = new PDDocument()) { + PDPage page = new PDPage(srcDoc.getPage(pageNumber).getMediaBox()); + doc.addPage(page); + PDAcroForm acroForm = new PDAcroForm(doc); + doc.getDocumentCatalog().setAcroForm(acroForm); + PDSignatureField signatureField = new PDSignatureField(acroForm); + PDAnnotationWidget widget = signatureField.getWidgets().get(0); + List acroFormFields = acroForm.getFields(); + acroForm.setSignaturesExist(true); + acroForm.setAppendOnly(true); + acroForm.getCOSObject().setDirect(true); + acroFormFields.add(signatureField); + + PDRectangle rect = new PDRectangle(0, 0, 200, 50); + + widget.setRectangle(rect); + + // from PDVisualSigBuilder.createHolderForm() + PDStream stream = new PDStream(doc); + PDFormXObject form = new PDFormXObject(stream); + PDResources res = new PDResources(); + form.setResources(res); + form.setFormType(1); + PDRectangle bbox = new PDRectangle(rect.getWidth(), rect.getHeight()); + float height = bbox.getHeight(); + form.setBBox(bbox); + PDFont font = new PDType1Font(FontName.TIMES_BOLD); + + // from PDVisualSigBuilder.createAppearanceDictionary() + PDAppearanceDictionary appearance = new PDAppearanceDictionary(); + appearance.getCOSObject().setDirect(true); + PDAppearanceStream appearanceStream = new PDAppearanceStream(form.getCOSObject()); + appearance.setNormalAppearance(appearanceStream); + widget.setAppearance(appearance); + + try (PDPageContentStream cs = new PDPageContentStream(doc, appearanceStream)) { + if (showImage) { + cs.saveGraphicsState(); + PDExtendedGraphicsState extState = new PDExtendedGraphicsState(); + extState.setBlendMode(BlendMode.MULTIPLY); + extState.setNonStrokingAlphaConstant(0.5f); + cs.setGraphicsStateParameters(extState); + cs.transform(Matrix.getScaleInstance(0.08f, 0.08f)); + PDImageXObject img = PDImageXObject.createFromFileByExtension(imageFile, + doc); + cs.drawImage(img, 100, 0); + cs.restoreGraphicsState(); + } + + // show text + float fontSize = 10; + float leading = fontSize * 1.5f; + cs.beginText(); + cs.setFont(font, fontSize); + cs.setNonStrokingColor(Color.black); + cs.newLineAtOffset(fontSize, height - leading); + cs.setLeading(leading); + + X509Certificate cert = (X509Certificate) getCertificateChain()[0]; + + // https://stackoverflow.com/questions/2914521/ + X500Name x500Name = new X500Name(cert.getSubjectX500Principal().getName()); + RDN cn = x500Name.getRDNs(BCStyle.CN)[0]; + String name = IETFUtils.valueToString(cn.getFirst().getValue()); + + String date = signature.getSignDate().getTime().toString(); + String reason = signature.getReason(); + + cs.showText("Signed by " + name); + cs.newLine(); + cs.showText(date); + cs.newLine(); + cs.showText(reason); + + cs.endText(); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + doc.save(baos); + return new ByteArrayInputStream(baos.toByteArray()); + } } } @@ -80,10 +200,7 @@ public CertSignController(CustomPDDocumentFactory pdfDocumentFactory) { } @PostMapping(consumes = "multipart/form-data", value = "/cert-sign") - @Operation( - summary = "Sign PDF with a Digital Certificate", - description = - "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO") + @Operation(summary = "Sign PDF with a Digital Certificate", description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO") public ResponseEntity signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request) throws Exception { MultipartFile pdf = request.getFileInput(); @@ -97,7 +214,7 @@ public ResponseEntity signPDFWithCert(@ModelAttribute SignPDFWithCertReq String reason = request.getReason(); String location = request.getLocation(); String name = request.getName(); - Integer pageNumber = request.getPageNumber(); + Integer pageNumber = request.getPageNumber() - 1; if (certType == null) { throw new IllegalArgumentException("Cert type must be provided"); @@ -112,7 +229,7 @@ public ResponseEntity signPDFWithCert(@ModelAttribute SignPDFWithCertReq PrivateKey privateKey = getPrivateKeyFromPEM(privateKeyFile.getBytes(), password); Certificate cert = (Certificate) getCertificateFromPEM(certFile.getBytes()); ks.setKeyEntry( - "alias", privateKey, password.toCharArray(), new Certificate[] {cert}); + "alias", privateKey, password.toCharArray(), new Certificate[] { cert }); break; case "PKCS12": ks = KeyStore.getInstance("PKCS12"); @@ -126,11 +243,10 @@ public ResponseEntity signPDFWithCert(@ModelAttribute SignPDFWithCertReq throw new IllegalArgumentException("Invalid cert type: " + certType); } - // TODO: page number - CreateSignature createSignature = new CreateSignature(ks, password.toCharArray()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - sign(pdfDocumentFactory, pdf.getBytes(), baos, createSignature, name, location, reason); + sign(pdfDocumentFactory, pdf.getBytes(), baos, createSignature, showSignature, pageNumber, name, location, + reason); return WebResponseUtils.boasToWebResponse( baos, Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst("[.][^.]+$", "") @@ -142,6 +258,8 @@ private static void sign( byte[] input, OutputStream output, CreateSignature instance, + Boolean showSignature, + Integer pageNumber, String name, String location, String reason) { @@ -154,7 +272,17 @@ private static void sign( signature.setReason(reason); signature.setSignDate(Calendar.getInstance()); - doc.addSignature(signature, instance); + if (showSignature) { + SignatureOptions signatureOptions = new SignatureOptions(); + signatureOptions + .setVisualSignature(instance.createVisibleSignature(doc, signature, pageNumber, true)); + signatureOptions.setPage(pageNumber); + + doc.addSignature(signature, instance, signatureOptions); + + } else { + doc.addSignature(signature, instance); + } doc.saveIncremental(output); } catch (Exception e) { logger.error("exception", e); @@ -163,22 +291,19 @@ private static void sign( private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password) throws IOException, OperatorCreationException, PKCSException { - try (PEMParser pemParser = - new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) { + try (PEMParser pemParser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) { Object pemObject = pemParser.readObject(); JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); PrivateKeyInfo pkInfo; if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) { - InputDecryptorProvider decProv = - new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password.toCharArray()); + InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder() + .build(password.toCharArray()); pkInfo = ((PKCS8EncryptedPrivateKeyInfo) pemObject).decryptPrivateKeyInfo(decProv); } else if (pemObject instanceof PEMEncryptedKeyPair) { - PEMDecryptorProvider decProv = - new JcePEMDecryptorProviderBuilder().build(password.toCharArray()); - pkInfo = - ((PEMEncryptedKeyPair) pemObject) - .decryptKeyPair(decProv) - .getPrivateKeyInfo(); + PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray()); + pkInfo = ((PEMEncryptedKeyPair) pemObject) + .decryptKeyPair(decProv) + .getPrivateKeyInfo(); } else { pkInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo(); } diff --git a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java index 8dcb2a9a5fb..b46290201d3 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java @@ -89,10 +89,9 @@ public String login(HttpServletRequest request, Model model, Authentication auth } SAML2 saml2 = securityProps.getSaml2(); - if (saml2 != null) { - if (saml2.getEnabled()) { - providerList.put("/saml2/authenticate/" + saml2.getRegistrationId(), "SAML 2"); - } + if (securityProps.isSaml2Activ() + && applicationProperties.getSystem().getEnableAlphaFunctionality()) { + providerList.put("/saml2/authenticate/" + saml2.getRegistrationId(), "SAML 2"); } // Remove any null keys/values from the providerList providerList @@ -101,7 +100,8 @@ public String login(HttpServletRequest request, Model model, Authentication auth model.addAttribute("providerlist", providerList); model.addAttribute("loginMethod", securityProps.getLoginMethod()); - model.addAttribute("altLogin", securityProps.isAltLogin()); + boolean altLogin = providerList.size() > 0 ? securityProps.isAltLogin() : false; + model.addAttribute("altLogin", altLogin); model.addAttribute("currentPage", "login"); @@ -164,6 +164,17 @@ public String login(HttpServletRequest request, Model model, Authentication auth case "userIsDisabled": erroroauth = "login.userIsDisabled"; break; + case "invalid_destination": + erroroauth = "login.invalid_destination"; + break; + // Valid InResponseTo was not available from the validation context, unable to + // evaluate + case "invalid_in_response_to": + erroroauth = "login.invalid_in_response_to"; + break; + case "not_authentication_provider_found": + erroroauth = "login.not_authentication_provider_found"; + break; default: break; } diff --git a/src/main/java/stirling/software/SPDF/utils/ImageProcessingUtils.java b/src/main/java/stirling/software/SPDF/utils/ImageProcessingUtils.java index 655e344c3aa..ba305fcc56e 100644 --- a/src/main/java/stirling/software/SPDF/utils/ImageProcessingUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/ImageProcessingUtils.java @@ -1,30 +1,46 @@ package stirling.software.SPDF.utils; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferInt; +import java.io.IOException; +import java.io.InputStream; import java.nio.ByteBuffer; +import javax.imageio.ImageIO; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.multipart.MultipartFile; + +import com.drew.imaging.ImageMetadataReader; +import com.drew.imaging.ImageProcessingException; +import com.drew.metadata.Metadata; +import com.drew.metadata.MetadataException; +import com.drew.metadata.exif.ExifSubIFDDirectory; + public class ImageProcessingUtils { + private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class); + static BufferedImage convertColorType(BufferedImage sourceImage, String colorType) { BufferedImage convertedImage; switch (colorType) { case "greyscale": - convertedImage = - new BufferedImage( - sourceImage.getWidth(), - sourceImage.getHeight(), - BufferedImage.TYPE_BYTE_GRAY); + convertedImage = new BufferedImage( + sourceImage.getWidth(), + sourceImage.getHeight(), + BufferedImage.TYPE_BYTE_GRAY); convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null); break; case "blackwhite": - convertedImage = - new BufferedImage( - sourceImage.getWidth(), - sourceImage.getHeight(), - BufferedImage.TYPE_BYTE_BINARY); + convertedImage = new BufferedImage( + sourceImage.getWidth(), + sourceImage.getHeight(), + BufferedImage.TYPE_BYTE_BINARY); convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null); break; default: // full color @@ -59,4 +75,49 @@ public static byte[] getImageData(BufferedImage image) { return data; } } + + public static double extractImageOrientation(InputStream is) throws IOException { + try { + Metadata metadata = ImageMetadataReader.readMetadata(is); + ExifSubIFDDirectory directory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class); + if (directory == null) { + return 0; + } + int orientationTag = directory.getInt(ExifSubIFDDirectory.TAG_ORIENTATION); + switch (orientationTag) { + case 1: + return 0; + case 6: + return 90; + case 3: + return 180; + case 8: + return 270; + default: + logger.warn("Unknown orientation tag: {}", orientationTag); + return 0; + } + } catch (ImageProcessingException | MetadataException e) { + return 0; + } + } + + public static BufferedImage applyOrientation(BufferedImage image, double orientation) { + if (orientation == 0) { + return image; + } + AffineTransform transform = AffineTransform.getRotateInstance( + Math.toRadians(orientation), + image.getWidth() / 2.0, + image.getHeight() / 2.0); + AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR); + return op.filter(image, null); + } + + public static BufferedImage loadImageWithExifOrientation(MultipartFile file) + throws IOException { + BufferedImage image = ImageIO.read(file.getInputStream()); + double orientation = extractImageOrientation(file.getInputStream()); + return applyOrientation(image, orientation); + } } diff --git a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java index 3416dee6feb..8ccd29429b4 100644 --- a/src/main/java/stirling/software/SPDF/utils/PdfUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/PdfUtils.java @@ -194,7 +194,8 @@ public boolean pageSize(PDDocument pdfDocument, String expectedPageSize) throws pdfDocument.close(); - // Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842" for A4 + // Assumes the expectedPageSize is in the format "widthxheight", e.g. "595x842" + // for A4 String[] dimensions = expectedPageSize.split("x"); float expectedPageWidth = Float.parseFloat(dimensions[0]); float expectedPageHeight = Float.parseFloat(dimensions[1]); @@ -407,7 +408,7 @@ public static byte[] imageToPdf( addImageToDocument(doc, pdImage, fitOption, autoRotate); } } else { - BufferedImage image = ImageIO.read(file.getInputStream()); + BufferedImage image = ImageProcessingUtils.loadImageWithExifOrientation(file); BufferedImage convertedImage = ImageProcessingUtils.convertColorType(image, colorType); // Use JPEGFactory if it's JPEG since JPEG is lossy diff --git a/src/main/java/stirling/software/SPDF/utils/misc/CustomColorReplaceStrategy.java b/src/main/java/stirling/software/SPDF/utils/misc/CustomColorReplaceStrategy.java index 3c5753f027f..de548881e27 100644 --- a/src/main/java/stirling/software/SPDF/utils/misc/CustomColorReplaceStrategy.java +++ b/src/main/java/stirling/software/SPDF/utils/misc/CustomColorReplaceStrategy.java @@ -5,6 +5,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.List; import java.util.Set; @@ -53,7 +54,7 @@ public InputStreamResource replace() throws IOException { } // Create a temporary file, with the original filename from the multipart file - File file = File.createTempFile("temp", getFileInput().getOriginalFilename()); + File file = Files.createTempFile("temp", getFileInput().getOriginalFilename()).toFile(); // Transfer the content of the multipart file to the file getFileInput().transferTo(file); diff --git a/src/main/java/stirling/software/SPDF/utils/misc/InvertFullColorStrategy.java b/src/main/java/stirling/software/SPDF/utils/misc/InvertFullColorStrategy.java index 12cd5204fbf..e32c1375d4e 100644 --- a/src/main/java/stirling/software/SPDF/utils/misc/InvertFullColorStrategy.java +++ b/src/main/java/stirling/software/SPDF/utils/misc/InvertFullColorStrategy.java @@ -6,6 +6,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import javax.imageio.ImageIO; @@ -30,7 +31,7 @@ public InvertFullColorStrategy(MultipartFile file, ReplaceAndInvert replaceAndIn public InputStreamResource replace() throws IOException { // Create a temporary file, with the original filename from the multipart file - File file = File.createTempFile("temp", getFileInput().getOriginalFilename()); + File file = Files.createTempFile("temp", getFileInput().getOriginalFilename()).toFile(); // Transfer the content of the multipart file to the file getFileInput().transferTo(file); diff --git a/src/main/resources/messages_ar_AR.properties b/src/main/resources/messages_ar_AR.properties index 275557428d3..d77feac6d0b 100644 --- a/src/main/resources/messages_ar_AR.properties +++ b/src/main/resources/messages_ar_AR.properties @@ -79,6 +79,7 @@ info=معلومات pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=لون التظليل 2: compare.document.1=المستند 1 compare.document.2=المستند 2 compare.submit=مقارنة +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=الكتب والكوميكس إلى PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_bg_BG.properties b/src/main/resources/messages_bg_BG.properties index c1f7a10d329..094b322aa29 100644 --- a/src/main/resources/messages_bg_BG.properties +++ b/src/main/resources/messages_bg_BG.properties @@ -79,6 +79,7 @@ info=Информация pro=Pro page=Страница pages=Страници +loading=Loading... legal.privacy=Политика за поверителност legal.terms=Правила и условия @@ -782,6 +783,9 @@ compare.highlightColor.2=Цвят на маркирането 2: compare.document.1=Документ 1 compare.document.2=Документ 2 compare.submit=Сравнявай +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Книги и комикси в PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Ниво на отметка: Изберете нивот splitByChapters.desc.3=Включване на метаданни: Ако е отметнато, метаданните на оригиналния PDF ще бъдат включени във всеки разделен PDF. splitByChapters.desc.4=Разрешаване на дубликати: Ако е отметнато, позволява множество отметки на една и съща страница за създаване на отделни PDF файлове. splitByChapters.submit=Разделяне на PDF - - diff --git a/src/main/resources/messages_ca_CA.properties b/src/main/resources/messages_ca_CA.properties index 71d30fa730c..cb6be070bff 100644 --- a/src/main/resources/messages_ca_CA.properties +++ b/src/main/resources/messages_ca_CA.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=Document 1 compare.document.2=Document 2 compare.submit=Comparar +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Books and Comics to PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_cs_CZ.properties b/src/main/resources/messages_cs_CZ.properties index b266097f803..d67562f8938 100644 --- a/src/main/resources/messages_cs_CZ.properties +++ b/src/main/resources/messages_cs_CZ.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=Dokument 1 compare.document.2=Dokument 2 compare.submit=Porovnat +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Knihy a komiksy do PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_da_DK.properties b/src/main/resources/messages_da_DK.properties index 22348c345ef..5521cbd3930 100644 --- a/src/main/resources/messages_da_DK.properties +++ b/src/main/resources/messages_da_DK.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Fremhævningsfarve 2: compare.document.1=Dokument 1 compare.document.2=Dokument 2 compare.submit=Sammenlign +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Bøger og Tegneserier til PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_de_DE.properties b/src/main/resources/messages_de_DE.properties index 8467edd067a..2737b736470 100644 --- a/src/main/resources/messages_de_DE.properties +++ b/src/main/resources/messages_de_DE.properties @@ -79,6 +79,7 @@ info=Informationen pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Datenschutz legal.terms=AGB @@ -114,21 +115,21 @@ pipelineOptions.validateButton=Validieren ######################## # ENTERPRISE EDITION # ######################## -enterpriseEdition.button=Upgrade to Pro -enterpriseEdition.warning=This feature is only available to Pro users. -enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. -enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro +enterpriseEdition.button=Auf Pro-Version umsteigen +enterpriseEdition.warning=Diese Funktion ist nur für Pro-Nutzer verfügbar. +enterpriseEdition.yamlAdvert=Stirling-PDF Pro unterstützt YAML Konfigurationsdateien, SSO und weitere Funktionen. +enterpriseEdition.ssoAdvert=Suchen Sie weitere Funktionen in der Benutzerverwaltung? Steigen Sie auf die Pro-Version um ################# # Analytics # ################# -analytics.title=Do you want make Stirling PDF better? -analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. -analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. -analytics.enable=Enable analytics -analytics.disable=Disable analytics -analytics.settings=You can change the settings for analytics in the config/settings.yml file +analytics.title=Möchten Sie Stirling-PDF verbessern? +analytics.paragraph1=Stirling-PDF verfügt über Opt-in-Analytics, die uns helfen, das Produkt zu verbessern. Wir zeichnen keine persönlichen Informationen oder Dateiinhalte auf. +analytics.paragraph2=Bitte erwägen Sie die Analytics zu aktivieren, um Stirling-PDF beim Wachsen zu helfen und um unsere User besser zu verstehen. +analytics.enable=Analytics aktivieren +analytics.disable=Analytics deaktivieren +analytics.settings=Sie können die Einstellungen für die Analytics in der config/settings.yml Datei bearbeiten ############# # NAVBAR # @@ -392,7 +393,7 @@ certSign.tags=authentifizieren,pem,p12,offiziell,verschlüsseln home.removeCertSign.title=Zertifikatsignatur entfernen home.removeCertSign.desc=Zertifikatsignatur aus PDF entfernen -removeCertSign.tags=authentifizieren,PEM,P12,offiziell,entschlüsseln,decrypt +removeCertSign.tags=authentifizieren,PEM,P12,offiziell,entschlüsseln home.pageLayout.title=Mehrseitiges Layout home.pageLayout.desc=Mehrere Seiten eines PDF zu einer Seite zusammenführen @@ -503,28 +504,28 @@ home.removeImagePdf.desc=Bild aus PDF entfernen, um die Dateigröße zu verringe removeImagePdf.tags=bild entfernen,seitenoperationen,back end,server side -home.splitPdfByChapters.title=Split PDF by Chapters -home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. -splitPdfByChapters.tags=split,chapters,bookmarks,organize +home.splitPdfByChapters.title=PDF-Datei nach Kapiteln aufteilen +home.splitPdfByChapters.desc=Aufteilung einer PDF-Datei in mehrere Dateien auf Basis der Kapitelstruktur. +splitPdfByChapters.tags=aufteilen,kapitel,lesezeichen,organisieren #replace-invert-color -replace-color.title=Replace-Invert-Color -replace-color.header=Replace-Invert Color PDF -home.replaceColorPdf.title=Replace and Invert Color -home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size -replaceColorPdf.tags=Replace Color,Page operations,Back end,server side -replace-color.selectText.1=Replace or Invert color Options -replace-color.selectText.2=Default(Default high contrast colors) -replace-color.selectText.3=Custom(Customized colors) -replace-color.selectText.4=Full-Invert(Invert all colors) -replace-color.selectText.5=High contrast color options -replace-color.selectText.6=white text on black background -replace-color.selectText.7=Black text on white background -replace-color.selectText.8=Yellow text on black background -replace-color.selectText.9=Green text on black background -replace-color.selectText.10=Choose text Color -replace-color.selectText.11=Choose background Color -replace-color.submit=Replace +replace-color.title=Farbe Ersetzen-Invertieren +replace-color.header=Farb-PDF Ersetzen-Invertieren +home.replaceColorPdf.title=Farbe ersetzen und invertieren +home.replaceColorPdf.desc=Ersetzen Sie die Farbe des Texts und Hintergrund der PDF-Datei und invertieren Sie die komplette Farbe der PDF-Datei, um die Dateigröße zu reduzieren +replaceColorPdf.tags=Farbe ersetzen,Seiteneinstellungen,Backend,Serverseite +replace-color.selectText.1=Ersetzen oder Invertieren von Farboptionen +replace-color.selectText.2=Standard(Standardfarben mit hohem Kontrast) +replace-color.selectText.3=Benutzerdefiniert(Benutzerdefinierte Farben) +replace-color.selectText.4=Vollinvertierung(Invertierung aller Farben) +replace-color.selectText.5=Farboptionen mit hohem Kontrast +replace-color.selectText.6=Weißer Text auf schwarzem Hintergrund +replace-color.selectText.7=Schwarzer Text auf weißem Hintergrund +replace-color.selectText.8=Gelber Text auf schwarzem Hintergrund +replace-color.selectText.9=Grüner Text auf schwarzem Hintergrund +replace-color.selectText.10=Textfarbe auswählen +replace-color.selectText.11=Hintergrundfarbe auswählen +replace-color.submit=Ersetzen @@ -551,9 +552,9 @@ login.oauth2AccessDenied=Zugriff abgelehnt login.oauth2InvalidTokenResponse=Ungültige Token-Antwort login.oauth2InvalidIdToken=Ungültiges ID-Token login.userIsDisabled=Benutzer ist deaktiviert, die Anmeldung ist mit diesem Benutzernamen derzeit gesperrt. Bitte wenden Sie sich an den Administrator. -login.alreadyLoggedIn=You are already logged in to -login.alreadyLoggedIn2=devices. Please log out of the devices and try again. -login.toManySessions=You have too many active sessions +login.alreadyLoggedIn=Sie sind bereits an +login.alreadyLoggedIn2=Geräten angemeldet. Bitte melden Sie sich dort ab und versuchen es dann erneut. +login.toManySessions=Sie haben zu viele aktive Sitzungen #auto-redact autoRedact.title=Automatisch zensieren/schwärzen @@ -703,7 +704,7 @@ autoSplitPDF.header=PDF automatisch teilen autoSplitPDF.description=Drucken Sie, fügen Sie ein, scannen Sie, laden Sie hoch und lassen Sie uns Ihre Dokumente automatisch trennen. Kein manuelles Sortieren erforderlich. autoSplitPDF.selectText.1=Drucken Sie einige Trennblätter aus (schwarz/weiß ist ausreichend). autoSplitPDF.selectText.2=Scannen Sie alle Dokumente auf einmal, indem Sie das Trennblatt zwischen die Dokumente einlegen. -autoSplitPDF.selectText.3=Laden Sie die einzelne große gescannte PDF-Datei hoch und überlassen Sie Stirling PDF den Rest. +autoSplitPDF.selectText.3=Laden Sie die einzelne große gescannte PDF-Datei hoch und überlassen Sie Stirling-PDF den Rest. autoSplitPDF.selectText.4=Trennseiten werden automatisch erkannt und entfernt, so dass ein sauberes Enddokument garantiert ist. autoSplitPDF.formPrompt=PDF mit Stirling-PDF Seitentrennern hochladen: autoSplitPDF.duplexMode=Duplex-Modus (Scannen von Vorder- und Rückseite) @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight-Farbe 2: compare.document.1=Dokument 1 compare.document.2=Dokument 2 compare.submit=Vergleichen +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Bücher und Comics zu PDF @@ -1210,15 +1214,13 @@ removeImage.removeImage=Bild entfernen removeImage.submit=Bild entfernen -splitByChapters.title=Split PDF by Chapters -splitByChapters.header=Split PDF by Chapters -splitByChapters.bookmarkLevel=Bookmark Level -splitByChapters.includeMetadata=Include Metadata -splitByChapters.allowDuplicates=Allow Duplicates -splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. -splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). -splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. -splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. -splitByChapters.submit=Split PDF - - +splitByChapters.title=PDF nach Kapiteln aufteilen +splitByChapters.header=PDF nach Kapiteln aufteilen +splitByChapters.bookmarkLevel=Lesezeichenebene +splitByChapters.includeMetadata=Metadaten einschließen +splitByChapters.allowDuplicates=Duplikate erlauben +splitByChapters.desc.1=Dieses Werkzeug teilt eine PDF-Datei auf der Grundlage ihrer Kapitelstruktur in mehrere PDF-Dateien auf. +splitByChapters.desc.2=Lesezeichenebene: Wählen Sie die Ebene der Lesezeichen, die für die Aufteilung verwendet werden soll (0 für die erste Ebene, 1 für die zweite Ebene usw.). +splitByChapters.desc.3=Metadaten einschließen: Wenn diese Option aktiviert ist, werden die Metadaten der ursprünglichen PDF-Datei in jede aufgeteilte PDF-Datei übernommen. +splitByChapters.desc.4=Duplikate erlauben: Wenn diese Option aktiviert ist, können mehrere Lesezeichen auf derselben Seite separate PDF Dateien erstellen. +splitByChapters.submit=PDF teilen diff --git a/src/main/resources/messages_el_GR.properties b/src/main/resources/messages_el_GR.properties index a973715e866..fa91125714a 100644 --- a/src/main/resources/messages_el_GR.properties +++ b/src/main/resources/messages_el_GR.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=Έγγραφο 1 compare.document.2=Έγγραφο 2 compare.submit=Σύγκριση +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Books και Comics σε PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 12eb074fe2f..f0430f310c4 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=Document 1 compare.document.2=Document 2 compare.submit=Compare +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Books and Comics to PDF @@ -1225,5 +1229,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_en_US.properties b/src/main/resources/messages_en_US.properties index eadf3b031c5..9c67fab7b1d 100644 --- a/src/main/resources/messages_en_US.properties +++ b/src/main/resources/messages_en_US.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=Document 1 compare.document.2=Document 2 compare.submit=Compare +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Books and Comics to PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties index 624ef96b241..c34d62344b9 100644 --- a/src/main/resources/messages_es_ES.properties +++ b/src/main/resources/messages_es_ES.properties @@ -3,8 +3,8 @@ ########### # the direction that the language is written (ltr = left to right, rtl = right to left) language.direction=ltr -addPageNumbers.fontSize=Font Size -addPageNumbers.fontName=Font Name +addPageNumbers.fontSize=Tamaño de Letra +addPageNumbers.fontName=Nombre de Letra pdfPrompt=Seleccionar PDF(s) multiPdfPrompt=Seleccionar PDFs (2+) multiPdfDropPrompt=Seleccione (o arrastre y suelte) todos los PDFs que quiera @@ -50,7 +50,7 @@ WorkInProgess=Tarea en progreso, puede no funcionar o ralentizarse; ¡por favor, poweredBy=Desarrollado por yes=Sí no=No -changedCredsMessage=Se cambiaron las credenciales! +changedCredsMessage=¡Se cambiaron las credenciales! notAuthenticatedMessage=Usuario no autentificado. userNotFoundMessage=Usuario no encontrado. incorrectPasswordMessage=La contraseña actual no es correcta. @@ -77,14 +77,15 @@ color=Color sponsor=Patrocinador info=Info pro=Pro -page=Page -pages=Pages +page=Página +pages=Páginas +loading=Loading... -legal.privacy=Privacy Policy -legal.terms=Terms and Conditions -legal.accessibility=Accessibility -legal.cookie=Cookie Policy -legal.impressum=Impressum +legal.privacy=Política de Privacidad +legal.terms=Términos y Condiciones +legal.accessibility=Accesibilidad +legal.cookie=Política de Cookies +legal.impressum=Impresión ############### # Pipeline # @@ -114,21 +115,21 @@ pipelineOptions.validateButton=Validar ######################## # ENTERPRISE EDITION # ######################## -enterpriseEdition.button=Upgrade to Pro -enterpriseEdition.warning=This feature is only available to Pro users. -enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. -enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro +enterpriseEdition.button=Actualiza a Pro +enterpriseEdition.warning=Esta característica está únicamente disponible para usuarios Pro. +enterpriseEdition.yamlAdvert=Stirling PDF Pro soporta configuración de ficheros YAML y otras características SSO. +enterpriseEdition.ssoAdvert=¿Busca más funciones de administración de usuarios? Consulte Stirling PDF Pro ################# # Analytics # ################# -analytics.title=Do you want make Stirling PDF better? -analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. -analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. -analytics.enable=Enable analytics -analytics.disable=Disable analytics -analytics.settings=You can change the settings for analytics in the config/settings.yml file +analytics.title=¿Quieres mejorar Stirling PDF? +analytics.paragraph1=Stirling PDF ha optado por analíticas para ayudarnos a mejorar el producto. No rastreamos ninguna información personal ni contenido de archivos. +analytics.paragraph2=Considere habilitar analíticas para ayudar a Stirling-PDF a crecer y permitirnos comprender mejor a nuestros usuarios. +analytics.enable=Habilitar analíticas +analytics.disable=Deshabilitar analíticas +analytics.settings=Puede cambiar la configuración de analíticas en el archivo config/settings.yml ############# # NAVBAR # @@ -243,7 +244,7 @@ database.fileNotFound=Archivo no encontrado database.fileNullOrEmpty=El archivo no debe ser nulo o vacío. database.failedImportFile=Archivo de importación fallido -session.expired=Your session has expired. Please refresh the page and try again. +session.expired=Tu sesión ha caducado. Actualice la página e inténtelo de nuevo. ############# # HOME-PAGE # @@ -392,7 +393,7 @@ certSign.tags=autentificar,PEM,P12,oficial,encriptar home.removeCertSign.title=Quitar signo de certificado home.removeCertSign.desc=Eliminar firma de certificado de PDF -removeCertSign.tags=authenticate,PEM,P12,official,decrypt +removeCertSign.tags=autenticar,PEM,P12,oficial,desencriptar home.pageLayout.title=Diseño de varias páginas home.pageLayout.desc=Unir varias páginas de un documento PDF en una sola página @@ -503,28 +504,28 @@ home.removeImagePdf.desc=Eliminar imagen del PDF> para reducir el tamaño de arc removeImagePdf.tags=Eliminar imagen,Operaciones de página,Back end,lado del servidor -home.splitPdfByChapters.title=Split PDF by Chapters -home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. -splitPdfByChapters.tags=split,chapters,bookmarks,organize +home.splitPdfByChapters.title=Dividir PDF por capítulos +home.splitPdfByChapters.desc=Divida un PDF en varios archivos según su estructura de capítulos. +splitPdfByChapters.tags=dividir,capítulos,marcadores,organizar #replace-invert-color -replace-color.title=Replace-Invert-Color -replace-color.header=Replace-Invert Color PDF -home.replaceColorPdf.title=Replace and Invert Color -home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size -replaceColorPdf.tags=Replace Color,Page operations,Back end,server side -replace-color.selectText.1=Replace or Invert color Options -replace-color.selectText.2=Default(Default high contrast colors) -replace-color.selectText.3=Custom(Customized colors) -replace-color.selectText.4=Full-Invert(Invert all colors) -replace-color.selectText.5=High contrast color options -replace-color.selectText.6=white text on black background -replace-color.selectText.7=Black text on white background -replace-color.selectText.8=Yellow text on black background -replace-color.selectText.9=Green text on black background -replace-color.selectText.10=Choose text Color -replace-color.selectText.11=Choose background Color -replace-color.submit=Replace +replace-color.title=Reemplazar-Invertir-Color +replace-color.header=Reemplazar-Invertir Color en PDF +home.replaceColorPdf.title=Reemplazar e Invertir Color +home.replaceColorPdf.desc=Reemplaza el color del texto y el fondo en el PDF e invierte el color completo del PDF para reducir el tamaño del archivo +replaceColorPdf.tags=Reemplazar Color,Operaciones de Página,Back end,Lado del servidor +replace-color.selectText.1=Opciones para Reemplazar o Invertir color +replace-color.selectText.2=Predeterminado (Colores de alto contraste predeterminados) +replace-color.selectText.3=Personalizado (Colores personalizados) +replace-color.selectText.4=Invertir Completo (Invertir todos los colores) +replace-color.selectText.5=Opciones de color de alto contraste +replace-color.selectText.6=Texto blanco sobre fondo negro +replace-color.selectText.7=Texto negro sobre fondo blanco +replace-color.selectText.8=Texto amarillo sobre fondo negro +replace-color.selectText.9=Texto verde sobre fondo negro +replace-color.selectText.10=Elegir Color de Texto +replace-color.selectText.11=Elegir Color de Fondo +replace-color.submit=Reemplazar @@ -551,9 +552,9 @@ login.oauth2AccessDenied=Acceso denegado login.oauth2InvalidTokenResponse=Respuesta de token no válida login.oauth2InvalidIdToken=Token de identificación no válido login.userIsDisabled=El usuario está desactivado, actualmente el acceso está bloqueado para ese nombre de usuario. Por favor, póngase en contacto con el administrador. -login.alreadyLoggedIn=You are already logged in to -login.alreadyLoggedIn2=devices. Please log out of the devices and try again. -login.toManySessions=You have too many active sessions +login.alreadyLoggedIn=Ya has iniciado sesión en +login.alreadyLoggedIn2=dispositivos. Cierra sesión en los dispositivos y vuelve a intentarlo. +login.toManySessions=Tienes demasiadas sesiones activas #auto-redact autoRedact.title=Auto Redactar @@ -782,6 +783,9 @@ compare.highlightColor.2=Color resaltado 2: compare.document.1=Documento 1 compare.document.2=Documento 2 compare.submit=Comparar +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Libros y Cómics a PDF @@ -887,7 +891,7 @@ addImage.title=Añadir imagen addImage.header=Añadir imagen de PDF addImage.everyPage=¿Todas las páginas? addImage.upload=Añadir imagen -addImage.submit=Añadir imagen +addImage.submit=Enviar imagen #merge @@ -1210,15 +1214,13 @@ removeImage.removeImage=Eliminar imagen removeImage.submit=Eliminar imagen -splitByChapters.title=Split PDF by Chapters -splitByChapters.header=Split PDF by Chapters -splitByChapters.bookmarkLevel=Bookmark Level -splitByChapters.includeMetadata=Include Metadata -splitByChapters.allowDuplicates=Allow Duplicates -splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. -splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). -splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. -splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. -splitByChapters.submit=Split PDF - - +splitByChapters.title=Dividir PDF por Capítulos +splitByChapters.header=Dividir PDF por Capítulos +splitByChapters.bookmarkLevel=Nivel de Marcador +splitByChapters.includeMetadata=Incluir Metadatos +splitByChapters.allowDuplicates=Permitir Duplicados +splitByChapters.desc.1=Esta herramienta divide un archivo PDF en múltiples archivos PDF según su estructura de capítulos. +splitByChapters.desc.2=Nivel de Marcador: Elige el nivel de marcadores para dividir (0 para el nivel superior, 1 para el segundo nivel, etc.). +splitByChapters.desc.3=Incluir Metadatos: Si está seleccionado, los metadatos del PDF original se incluirán en cada PDF dividido. +splitByChapters.desc.4=Permitir Duplicados: Si está seleccionado, permite que múltiples marcadores en la misma página creen archivos PDF separados. +splitByChapters.submit=Dividir PDF diff --git a/src/main/resources/messages_eu_ES.properties b/src/main/resources/messages_eu_ES.properties index 99a6a153149..f50f254bff4 100644 --- a/src/main/resources/messages_eu_ES.properties +++ b/src/main/resources/messages_eu_ES.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=1. dokumentua compare.document.2=2. dokumentua compare.submit=Konparatu +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Books and Comics to PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties index aca42d2b21b..0bf6d87ca23 100644 --- a/src/main/resources/messages_fr_FR.properties +++ b/src/main/resources/messages_fr_FR.properties @@ -3,14 +3,14 @@ ########### # the direction that the language is written (ltr = left to right, rtl = right to left) language.direction=ltr -addPageNumbers.fontSize=Font Size -addPageNumbers.fontName=Font Name +addPageNumbers.fontSize=Taille de Police +addPageNumbers.fontName=Nom de la Police pdfPrompt=Sélectionnez le(s) PDF multiPdfPrompt=Sélectionnez les PDF multiPdfDropPrompt=Sélectionnez (ou glissez-déposez) tous les PDF dont vous avez besoin imgPrompt=Choisir une image genericSubmit=Envoyer -processTimeWarning=Attention, ce processus peut prendre jusqu’à une minute en fonction de la taille du fichier. +processTimeWarning=Attention, ce processus peut prendre jusqu'à une minute en fonction de la taille du fichier. pageOrderPrompt=Ordre des pages (entrez une liste de numéros de page séparés par des virgules ou des fonctions telles que 2n+1) : pageSelectionPrompt=Sélection des pages (entrez une liste de numéros de page séparés par des virgules ou des fonctions telles que 2n+1) : goToPage=Aller @@ -23,7 +23,7 @@ close=Fermer filesSelected=fichiers sélectionnés noFavourites=Aucun favori ajouté downloadComplete=Téléchargement terminé -bored=Marre d’attendre ? +bored=Marre d'attendre ? alphabet=Alphabet downloadPdf=Télécharger le PDF text=Texte @@ -34,9 +34,9 @@ sizes.small=Petit sizes.medium=Moyen sizes.large=Grand sizes.x-large=Très grand -error.pdfPassword=Le document PDF est protégé par un mot de passe qui n’a pas été fourni ou était incorrect +error.pdfPassword=Le document PDF est protégé par un mot de passe qui n'a pas été fourni ou était incorrect delete=Supprimer -username=Nom d’utilisateur +username=Nom d'utilisateur password=Mot de passe welcome=Bienvenue property=Propriété @@ -54,12 +54,12 @@ changedCredsMessage=Les identifiants ont été mis à jour ! notAuthenticatedMessage=Utilisateur non authentifié. userNotFoundMessage=Utilisateur non trouvé. incorrectPasswordMessage=Le mot de passe actuel est incorrect. -usernameExistsMessage=Le nouveau nom d’utilisateur existe déjà. -invalidUsernameMessage=Nom d’utilisateur invalide, le nom d’utilisateur ne peut contenir que des lettres, des chiffres et les caractères spéciaux suivants @._+- ou doit être une adresse e-mail valide. +usernameExistsMessage=Le nouveau nom d'utilisateur existe déjà. +invalidUsernameMessage=Nom d'utilisateur invalide, le nom d'utilisateur ne peut contenir que des lettres, des chiffres et les caractères spéciaux suivants @._+- ou doit être une adresse e-mail valide. invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end. confirmPasswordErrorMessage=New Password and Confirm New Password must match. -deleteCurrentUserMessage=Impossible de supprimer l’utilisateur actuellement connecté. -deleteUsernameExistsMessage=Le nom d’utilisateur n’existe pas et ne peut pas être supprimé. +deleteCurrentUserMessage=Impossible de supprimer l'utilisateur actuellement connecté. +deleteUsernameExistsMessage=Le nom d'utilisateur n'existe pas et ne peut pas être supprimé. downgradeCurrentUserMessage=Impossible de rétrograder le rôle de l'utilisateur actuel. disabledCurrentUserMessage=The current user cannot be disabled downgradeCurrentUserLongMessage=Impossible de rétrograder le rôle de l'utilisateur actuel. Par conséquent, l'utilisateur actuel ne sera pas affiché. @@ -68,7 +68,7 @@ userAlreadyExistsWebMessage=L'utilisateur existe déjà en tant qu'utilisateur W error=Erreur oops=Oups ! help=Aide -goHomepage=Aller à la page d’accueil +goHomepage=Aller à la page d'accueil joinDiscord=Rejoignez notre serveur Discord seeDockerHub=Consulter le Docker Hub visitGithub=Visiter le dépôt Github @@ -79,12 +79,13 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... -legal.privacy=Privacy Policy -legal.terms=Terms and Conditions -legal.accessibility=Accessibility -legal.cookie=Cookie Policy -legal.impressum=Impressum +legal.privacy=Politique de Confidentialité +legal.terms=Conditions Générales +legal.accessibility=Accessibilité +legal.cookie=Politique des Cookies +legal.impressum=Mentions Légales ############### # Pipeline # @@ -114,21 +115,21 @@ pipelineOptions.validateButton=Valider ######################## # ENTERPRISE EDITION # ######################## -enterpriseEdition.button=Upgrade to Pro -enterpriseEdition.warning=This feature is only available to Pro users. -enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. -enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro +enterpriseEdition.button=Passer à Pro +enterpriseEdition.warning=Cette fonctionnalité est uniquement disponible pour les utilisateurs Pro. +enterpriseEdition.yamlAdvert=Stirling PDF Pro prend en charge les fichiers de configuration YAML et d'autres fonctionnalités SSO. +enterpriseEdition.ssoAdvert=Vous cherchez plus de fonctionnalités de gestion des utilisateurs ? Découvrez Stirling PDF Pro ################# # Analytics # ################# -analytics.title=Do you want make Stirling PDF better? -analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. -analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. -analytics.enable=Enable analytics -analytics.disable=Disable analytics -analytics.settings=You can change the settings for analytics in the config/settings.yml file +analytics.title=Souhaitez-vous améliorer Stirling PDF ? +analytics.paragraph1=Stirling PDF utilise des analyses volontaires pour nous aider à améliorer le produit. Nous ne suivons aucune information personnelle ni le contenu des fichiers. +analytics.paragraph2=Veuillez envisager d'activer les analyses pour aider Stirling-PDF à se développer et pour nous permettre de mieux comprendre nos utilisateurs. +analytics.enable=Activer les analyses +analytics.disable=Désactiver les analyses +analytics.settings=Vous pouvez modifier les paramètres des analyses dans le fichier config/settings.yml ############# # NAVBAR # @@ -153,8 +154,8 @@ navbar.sections.popular=Populaire settings.title=Paramètres settings.update=Mise à jour disponible settings.updateAvailable={0} est la version actuellement installée. Une nouvelle version ({1}) est disponible. -settings.appVersion=Version de l’application : -settings.downloadOption.title=Choisissez l’option de téléchargement (pour les téléchargements à fichier unique non ZIP) : +settings.appVersion=Version de l'application : +settings.downloadOption.title=Choisissez l'option de téléchargement (pour les téléchargements à fichier unique non ZIP) : settings.downloadOption.1=Ouvrir dans la même fenêtre settings.downloadOption.2=Ouvrir dans une nouvelle fenêtre settings.downloadOption.3=Télécharger le fichier @@ -168,7 +169,7 @@ settings.cacheInputs.help=Permet de stocker les entrées précédemment utilisé changeCreds.title=Modifiez vos identifiants changeCreds.header=Mettez à jour vos identifiants de connexion changeCreds.changePassword=Vous utilisez les identifiants de connexion par défaut. Veuillez saisir un nouveau mot de passe -changeCreds.newUsername=Nouveau nom d’utilisateur +changeCreds.newUsername=Nouveau nom d'utilisateur changeCreds.oldPassword=Mot de passe actuel changeCreds.newPassword=Nouveau mot de passe changeCreds.confirmNewPassword=Confirmer le nouveau mot de passe @@ -178,10 +179,10 @@ changeCreds.submit=Soumettre les modifications account.title=Paramètres du compte account.accountSettings=Paramètres du compte -account.adminSettings=Paramètres d’administration – Voir et ajouter des utilisateurs +account.adminSettings=Paramètres d'administration – Voir et ajouter des utilisateurs account.userControlSettings=Contrôle des paramètres des utilisateurs -account.changeUsername=Modifier le nom d’utilisateur -account.newUsername=Nouveau nom d’utilisateur +account.changeUsername=Modifier le nom d'utilisateur +account.newUsername=Nouveau nom d'utilisateur account.password=Mot de passe de confirmation account.oldPassword=Ancien mot de passe account.newPassword=Nouveau mot de passe @@ -210,11 +211,11 @@ adminUserSettings.roles=Rôles adminUserSettings.role=Rôle adminUserSettings.actions=Actions adminUserSettings.apiUser=Utilisateur API limité -adminUserSettings.extraApiUser=Utilisateur limité supplémentaire de l’API +adminUserSettings.extraApiUser=Utilisateur limité supplémentaire de l'API adminUserSettings.webOnlyUser=Utilisateur Web uniquement adminUserSettings.demoUser=Demo User (Paramètres par défaut) adminUserSettings.internalApiUser=Utilisateur de l'API interne -adminUserSettings.forceChange=Forcer l’utilisateur à changer son nom d’utilisateur/mot de passe lors de la connexion +adminUserSettings.forceChange=Forcer l'utilisateur à changer son nom d'utilisateur/mot de passe lors de la connexion adminUserSettings.submit=Ajouter adminUserSettings.changeUserRole=Changer le rôle de l'utilisateur adminUserSettings.authenticated=Authentifié @@ -282,7 +283,7 @@ home.pdfToImage.desc=Convertissez un PDF en image (PNG, JPEG, GIF). pdfToImage.tags=conversion,img,jpg,image,photo home.pdfOrganiser.title=Organiser -home.pdfOrganiser.desc=Supprimez ou réorganisez les pages dans n’importe quel ordre. +home.pdfOrganiser.desc=Supprimez ou réorganisez les pages dans n'importe quel ordre. pdfOrganiser.tags=organiser,recto-verso,duplex,even,odd,sort,move @@ -292,7 +293,7 @@ addImage.tags=img,jpg,image,photo home.watermark.title=Ajouter un filigrane home.watermark.desc=Ajoutez un filigrane personnalisé à votre PDF. -watermark.tags=texte,filigrane,label,propriété,droit d’auteur,marque déposée,img,jpg,image,photo,copyright,trademark +watermark.tags=texte,filigrane,label,propriété,droit d'auteur,marque déposée,img,jpg,image,photo,copyright,trademark home.permissions.title=Modifier les permissions home.permissions.desc=Modifiez les permissions de votre PDF. @@ -321,16 +322,16 @@ home.changeMetadata.desc=Modifiez, supprimez ou ajoutez des métadonnées à un changeMetadata.tags=métadonnées,titre,auteur,date,création,heure,éditeur,statistiques,title,author,date,creation,time,publisher,producer,stats,metadata home.fileToPDF.title=Fichier en PDF -home.fileToPDF.desc=Convertissez presque n’importe quel fichier en PDF (DOCX, PNG, XLS, PPT, TXT, etc.). +home.fileToPDF.desc=Convertissez presque n'importe quel fichier en PDF (DOCX, PNG, XLS, PPT, TXT, etc.). fileToPDF.tags=convertion,transformation,format,document,image,slide,texte,conversion,office,docs,word,excel,powerpoint home.ocr.title=OCR / Nettoyage des numérisations -home.ocr.desc=Utilisez l’OCR pour analyser et détecter le texte des images d’un PDF et le rajouter en tant que tel. +home.ocr.desc=Utilisez l'OCR pour analyser et détecter le texte des images d'un PDF et le rajouter en tant que tel. ocr.tags=ocr,reconnaissance,texte,image,numérisation,scan,read,identify,detection,editable home.extractImages.title=Extraire les images -home.extractImages.desc=Extrayez toutes les images d’un PDF et enregistrez-les dans un ZIP. +home.extractImages.desc=Extrayez toutes les images d'un PDF et enregistrez-les dans un ZIP. extractImages.tags=image,photo,save,archive,zip,capture,grab home.pdfToPDFA.title=PDF en PDF/A @@ -359,7 +360,7 @@ home.PDFToXML.desc=Convertissez un PDF au format XML. PDFToXML.tags=xml,extraction de données,contenu structuré,interopérabilité,data-extraction,structured-content,interop,transformation,convert home.ScannerImageSplit.title=Diviser les photos numérisées -home.ScannerImageSplit.desc=Divisez plusieurs photos à partir d’une photo ou d’un PDF. +home.ScannerImageSplit.desc=Divisez plusieurs photos à partir d'une photo ou d'un PDF. ScannerImageSplit.tags=diviser,détecter automatiquement,numériser,separate,auto-detect,scans,multi-photo,organize home.sign.title=Signer @@ -367,7 +368,7 @@ home.sign.desc=Ajoutez une signature au PDF avec un dessin, du texte ou une imag sign.tags=signer,authorize,initials,drawn-signature,text-sign,image-signature home.flatten.title=Rendre inerte -home.flatten.desc=Supprimez tous les éléments et formulaires interactifs d’un PDF. +home.flatten.desc=Supprimez tous les éléments et formulaires interactifs d'un PDF. flatten.tags=inerte,static,deactivate,non-interactive,streamline home.repair.title=Réparer @@ -375,11 +376,11 @@ home.repair.desc=Essayez de réparer un PDF corrompu ou cassé. repair.tags=réparer,restaurer,corriger,récupérer,fix,restore,correction,recover home.removeBlanks.title=Supprimer les pages vierges -home.removeBlanks.desc=Détectez et supprimez les pages vierges d’un PDF. +home.removeBlanks.desc=Détectez et supprimez les pages vierges d'un PDF. removeBlanks.tags=pages vierges,supprimer,nettoyer,cleanup,streamline,non-content,organize home.removeAnnotations.title=Supprimer les annotations -home.removeAnnotations.desc=Supprimer tous les commentaires/annotations d’un PDF. +home.removeAnnotations.desc=Supprimer tous les commentaires/annotations d'un PDF. removeAnnotations.tags=commentaires,supprimer,annotations,highlight,notes,markup,remove home.compare.title=Comparer @@ -395,11 +396,11 @@ home.removeCertSign.desc=Supprimez la signature par certificat d'un PDF removeCertSign.tags=signer,chiffrer,certificat,authenticate,PEM,P12,official,decrypt home.pageLayout.title=Fusionner des pages -home.pageLayout.desc=Fusionnez plusieurs pages d’un PDF en une seule. +home.pageLayout.desc=Fusionnez plusieurs pages d'un PDF en une seule. pageLayout.tags=fusionner,merge,composite,single-view,organize -home.scalePages.title=Ajuster l’échelle ou la taille -home.scalePages.desc=Modifiez la taille ou l’échelle d’une page et/ou de son contenu. +home.scalePages.title=Ajuster l'échelle ou la taille +home.scalePages.desc=Modifiez la taille ou l'échelle d'une page et/ou de son contenu. scalePages.tags=ajuster,redimensionner,resize,modify,dimension,adapt home.pipeline.title=Pipeline @@ -415,7 +416,7 @@ home.auto-rename.desc=Renommez automatiquement un fichier PDF en fonction de son auto-rename.tags=renommer,détection automatique,réétiqueter,auto-detect,header-based,organize,relabel home.adjust-contrast.title=Ajuster les couleurs -home.adjust-contrast.desc=Ajustez le contraste, la saturation et la luminosité d’un PDF. +home.adjust-contrast.desc=Ajustez le contraste, la saturation et la luminosité d'un PDF. adjust-contrast.tags=ajuster,couleurs,amélioration,color-correction,tune,modify,enhance home.crop.title=Redimensionner @@ -431,16 +432,16 @@ home.sanitizePdf.desc=Supprimez les scripts et autres éléments des PDF. sanitizePdf.tags=assainir,sécurisé,clean,secure,safe,remove-threats home.URLToPDF.title=URL en PDF -home.URLToPDF.desc=Convertissez n’importe quelle URL http(s) en PDF. +home.URLToPDF.desc=Convertissez n'importe quelle URL http(s) en PDF. URLToPDF.tags=pdf,contenu Web,save-page,web-to-doc,archive home.HTMLToPDF.title=HTML en PDF -home.HTMLToPDF.desc=Convertissez n’importe quel fichier HTML ou ZIP en PDF. +home.HTMLToPDF.desc=Convertissez n'importe quel fichier HTML ou ZIP en PDF. HTMLToPDF.tags=html,markup,contenu Web,transformation,convert home.MarkdownToPDF.title=Markdown en PDF -home.MarkdownToPDF.desc=Convertissez n’importe quel fichier Markdown en PDF. +home.MarkdownToPDF.desc=Convertissez n'importe quel fichier Markdown en PDF. MarkdownToPDF.tags=markdown,markup,contenu Web,transformation,convert @@ -464,11 +465,11 @@ home.showJS.desc=Recherche et affiche tout JavaScript injecté dans un PDF. showJS.tags=JS home.autoRedact.title=Caviarder automatiquement -home.autoRedact.desc=Caviardez automatiquement les informations sensibles d’un PDF. +home.autoRedact.desc=Caviardez automatiquement les informations sensibles d'un PDF. autoRedact.tags=caviarder,redact,auto home.tableExtraxt.title=PDF en CSV -home.tableExtraxt.desc=Extrait les tableaux d’un PDF et les transforme en CSV. +home.tableExtraxt.desc=Extrait les tableaux d'un PDF et les transforme en CSV. tableExtraxt.tags=CSV,Table Extraction,extract,convert @@ -478,15 +479,15 @@ autoSizeSplitPDF.tags=pdf,split,document,organization home.overlay-pdfs.title=Incrustation de PDF -home.overlay-pdfs.desc=Incrustation d’un PDF sur un autre PDF. +home.overlay-pdfs.desc=Incrustation d'un PDF sur un autre PDF. overlay-pdfs.tags=Overlay,incrustation home.split-by-sections.title=Séparer un PDF en sections -home.split-by-sections.desc=Diviser chaque page d’un PDF en sections horizontales/verticales plus petites. +home.split-by-sections.desc=Diviser chaque page d'un PDF en sections horizontales/verticales plus petites. split-by-sections.tags=Sections,Diviser,Section Split, Divide, Customize home.AddStampRequest.title=Ajouter un tampon sur un PDF -home.AddStampRequest.desc=Ajouter un texte ou l’image d’un tampon à un emplacement défini. +home.AddStampRequest.desc=Ajouter un texte ou l'image d'un tampon à un emplacement défini. AddStampRequest.tags=Tampon,Ajouter,Stamp,Add image,center image,Watermark,PDF,Embed,Customize @@ -508,23 +509,23 @@ home.splitPdfByChapters.desc=Séparez un PDF en fichiers multiples en fonction d splitPdfByChapters.tags=séparer,chapitres,split,chapters,bookmarks,organize #replace-invert-color -replace-color.title=Replace-Invert-Color -replace-color.header=Replace-Invert Color PDF -home.replaceColorPdf.title=Replace and Invert Color -home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size -replaceColorPdf.tags=Replace Color,Page operations,Back end,server side -replace-color.selectText.1=Replace or Invert color Options -replace-color.selectText.2=Default(Default high contrast colors) -replace-color.selectText.3=Custom(Customized colors) -replace-color.selectText.4=Full-Invert(Invert all colors) -replace-color.selectText.5=High contrast color options -replace-color.selectText.6=white text on black background -replace-color.selectText.7=Black text on white background -replace-color.selectText.8=Yellow text on black background -replace-color.selectText.9=Green text on black background -replace-color.selectText.10=Choose text Color -replace-color.selectText.11=Choose background Color -replace-color.submit=Replace +replace-color.title=Remplacer-Inverser-Couleur +replace-color.header=Remplacer-Inverser Couleur PDF +home.replaceColorPdf.title=Remplacer et Inverser Couleur +home.replaceColorPdf.desc=Remplacer la couleur pour le texte et l'arrière-plan dans le PDF et inverser la couleur complète du PDF pour réduire la taille du fichier +replaceColorPdf.tags=Remplacer Couleur,Opérations de Page,Back-end,Côté serveur +replace-color.selectText.1=Options de Remplacement ou d'Inversion de Couleur +replace-color.selectText.2=Par défaut (Couleurs à fort contraste par défaut) +replace-color.selectText.3=Personnalisé (Couleurs personnalisées) +replace-color.selectText.4=Inversion complète (Inverser toutes les couleurs) +replace-color.selectText.5=Options de couleur à fort contraste +replace-color.selectText.6=Texte blanc sur fond noir +replace-color.selectText.7=Texte noir sur fond blanc +replace-color.selectText.8=Texte jaune sur fond noir +replace-color.selectText.9=Texte vert sur fond noir +replace-color.selectText.10=Choisir la couleur du texte +replace-color.selectText.11=Choisir la couleur de l'arrière-plan +replace-color.submit=Remplacer @@ -538,7 +539,7 @@ login.title=Connexion login.header=Connexion login.signin=Connexion login.rememberme=Se souvenir de moi -login.invalid=Nom d’utilisateur ou mot de passe invalide. +login.invalid=Nom d'utilisateur ou mot de passe invalide. login.locked=Votre compte a été verrouillé. login.signinTitle=Veuillez vous connecter login.ssoSignIn=Se connecter via l'authentification unique @@ -550,10 +551,10 @@ login.oauth2invalidRequest=Requête invalide login.oauth2AccessDenied=Accès refusé login.oauth2InvalidTokenResponse=Réponse contenant le jeton est invalide login.oauth2InvalidIdToken=Jeton d'identification invalide -login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. -login.alreadyLoggedIn=You are already logged in to -login.alreadyLoggedIn2=devices. Please log out of the devices and try again. -login.toManySessions=You have too many active sessions +login.userIsDisabled=L'utilisateur est désactivé, la connexion est actuellement bloquée avec ce nom d'utilisateur. Veuillez contacter l'administrateur. +login.alreadyLoggedIn=Vous êtes déjà connecté sur +login.alreadyLoggedIn2=appareils. Veuillez vous déconnecter des appareils et réessayer. +login.toManySessions=Vous avez trop de sessions actives. #auto-redact autoRedact.title=Caviarder automatiquement @@ -617,15 +618,15 @@ HTMLToPDF.header=HTML en PDF HTMLToPDF.help=Accepte les fichiers HTML et les ZIP contenant du HTML, du CSS, des images, etc. (requis). HTMLToPDF.submit=Convertir HTMLToPDF.credit=Utilise WeasyPrint. -HTMLToPDF.zoom=Niveau de zoom pour l’affichage du site web. +HTMLToPDF.zoom=Niveau de zoom pour l'affichage du site web. HTMLToPDF.pageWidth=Largeur de la page en centimètres. (Vide par défaut) HTMLToPDF.pageHeight=Hauteur de la page en centimètres. (Vide par défaut) HTMLToPDF.marginTop=Marge supérieure de la page en millimètres. (Vide par défaut) HTMLToPDF.marginBottom=Marge inférieure de la page en millimètres. (Vide par défaut) HTMLToPDF.marginLeft=Marge gauche de la page en millimètres. (Vide par défaut) HTMLToPDF.marginRight=Marge droite de la page en millimètres. (Vide par défaut) -HTMLToPDF.printBackground=Restituer l’image de fond des sites web. -HTMLToPDF.defaultHeader=Activer l’entête par défaut (Nom et numéro de page) +HTMLToPDF.printBackground=Restituer l'image de fond des sites web. +HTMLToPDF.defaultHeader=Activer l'entête par défaut (Nom et numéro de page) HTMLToPDF.cssMediaType=Modifier le type de média CSS de la page. HTMLToPDF.none=Aucun HTMLToPDF.print=Imprimer @@ -703,7 +704,7 @@ autoSplitPDF.header=Séparer automatiquement les pages autoSplitPDF.description=Imprimez, insérez, numérisez, téléchargez et laissez-nous séparer automatiquement vos documents. Aucun travail de tri manuel nécessaire. autoSplitPDF.selectText.1=Imprimez des feuilles de séparation ci-dessous (le mode noir et blanc convient). autoSplitPDF.selectText.2=Numérisez tous vos documents en une seule fois en insérant les feuilles intercalaires entre eux. -autoSplitPDF.selectText.3=Téléchargez le fichier PDF numérisé et laissez Stirling PDF s’occuper du reste. +autoSplitPDF.selectText.3=Téléchargez le fichier PDF numérisé et laissez Stirling PDF s'occuper du reste. autoSplitPDF.selectText.4=Les feuilles de séparation sont automatiquement détectées et supprimées, garantissant un document final soigné. autoSplitPDF.formPrompt=PDF contenant des feuilles de séparation de Stirling PDF : autoSplitPDF.duplexMode=Mode recto-verso @@ -725,11 +726,11 @@ pageLayout.submit=Fusionner #scalePages -scalePages.title=Ajuster la taille ou l’échelle -scalePages.header=Ajuster la taille ou l’échelle -scalePages.pageSize=Taille d’une page du document +scalePages.title=Ajuster la taille ou l'échelle +scalePages.header=Ajuster la taille ou l'échelle +scalePages.pageSize=Taille d'une page du document scalePages.keepPageSize=Original Size -scalePages.scaleFactor=Niveau de zoom (recadrage) d’une page +scalePages.scaleFactor=Niveau de zoom (recadrage) d'une page scalePages.submit=Ajuster @@ -737,10 +738,10 @@ scalePages.submit=Ajuster certSign.title=Signer avec un certificat certSign.header=Signer avec un certificat (Travail en cours) certSign.selectPDF=PDF à signer -certSign.jksNote=Note: Si votre type de certificat n’est pas listé ci-dessous, merci de le convertir en fichier Java Keystore (.jks) en utilisant l’outil en ligne de commande keytool. Puis choisissez l’option Fichier .jks ci-dessous. +certSign.jksNote=Note: Si votre type de certificat n'est pas listé ci-dessous, merci de le convertir en fichier Java Keystore (.jks) en utilisant l'outil en ligne de commande keytool. Puis choisissez l'option Fichier .jks ci-dessous. certSign.selectKey=Fichier de clé privée (format PKCS#8, peut être .pem ou .der) certSign.selectCert=Fichier de certificat (format X.509, peut être .pem ou .der) -certSign.selectP12=Fichier keystore de clés PKCS#12 (.p12 ou .pfx) (facultatif, s’il n’est fourni, il doit contenir votre clé privée et votre certificat) +certSign.selectP12=Fichier keystore de clés PKCS#12 (.p12 ou .pfx) (facultatif, s'il n'est fourni, il doit contenir votre clé privée et votre certificat) certSign.selectJKS=Sélectionner votre fichier Java Keystore File (.jks or .keystore): certSign.certType=Type de certificat certSign.password=Mot de passe keystore ou clé privée le cas échéant @@ -752,10 +753,10 @@ certSign.submit=Signer #removeCertSign -removeCertSign.title=Remove Certificate Signature -removeCertSign.header=Remove the digital certificate from the PDF -removeCertSign.selectPDF=Select a PDF file: -removeCertSign.submit=Remove Signature +removeCertSign.title=Supprimer la Signature de Certificat +removeCertSign.header=Supprimer le certificat numérique du PDF +removeCertSign.selectPDF=Sélectionnez un fichier PDF : +removeCertSign.submit=Supprimer la Signature #removeBlanks @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=Document 1 compare.document.2=Document 2 compare.submit=Comparer +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Livres et BD vers PDF @@ -821,9 +825,9 @@ flatten.submit=Rendre inerte #ScannerImageSplit ScannerImageSplit.selectText.1=Seuil de rotation -ScannerImageSplit.selectText.2=Définit l’angle absolu minimum requis pour la rotation de l’image (par défaut : 10). +ScannerImageSplit.selectText.2=Définit l'angle absolu minimum requis pour la rotation de l'image (par défaut : 10). ScannerImageSplit.selectText.3=Tolérance -ScannerImageSplit.selectText.4=Détermine la plage de variation de couleur autour de la couleur d’arrière-plan estimée (par défaut : 20). +ScannerImageSplit.selectText.4=Détermine la plage de variation de couleur autour de la couleur d'arrière-plan estimée (par défaut : 20). ScannerImageSplit.selectText.5=Surface minimale ScannerImageSplit.selectText.6=Définit la surface minimale pour une photo (par défaut : 8 000). ScannerImageSplit.selectText.7=Surface de contour minimale @@ -839,24 +843,24 @@ ocr.header=OCR (Reconnaissance optique de caractères) / Nettoyage des numérisa ocr.selectText.1=Langues à détecter dans le PDF (celles listées sont celles actuellement détectées) ocr.selectText.2=Produire un fichier texte contenant le texte détecté à côté du PDF ocr.selectText.3=Corriger les pages qui ont été numérisées à un angle oblique en les remettant en place -ocr.selectText.4=Nettoyer la page afin qu’il soit moins probable que l’OCR trouve du texte dans le bruit de fond, sans modifier la sortie -ocr.selectText.5=Nettoyer la page afin qu’il soit moins probable que l’OCR trouve du texte dans le bruit de fond, en modifiant la sortie -ocr.selectText.6=Ignorer les pages contenant du texte interactif, n’analyser que les pages qui sont des images -ocr.selectText.7=Forcer l’OCR, analyser chaque page et supprimer tous les éléments de texte d’origine +ocr.selectText.4=Nettoyer la page afin qu'il soit moins probable que l'OCR trouve du texte dans le bruit de fond, sans modifier la sortie +ocr.selectText.5=Nettoyer la page afin qu'il soit moins probable que l'OCR trouve du texte dans le bruit de fond, en modifiant la sortie +ocr.selectText.6=Ignorer les pages contenant du texte interactif, n'analyser que les pages qui sont des images +ocr.selectText.7=Forcer l'OCR, analyser chaque page et supprimer tous les éléments de texte d'origine ocr.selectText.8=Normal (génère une erreur si le PDF contient du texte) ocr.selectText.9=Paramètres additionnels ocr.selectText.10=Mode OCR -ocr.selectText.11=Supprimer les images après l’OCR (Supprime TOUTES les images, utile uniquement si elles font partie de l’étape de conversion) +ocr.selectText.11=Supprimer les images après l'OCR (Supprime TOUTES les images, utile uniquement si elles font partie de l'étape de conversion) ocr.selectText.12=Type de rendu (avancé) -ocr.help=Veuillez lire cette documentation pour savoir comment utiliser l’OCR pour d’autres langues ou une utilisation hors Docker : -ocr.credit=Ce service utilise OCRmyPDF et Tesseract pour l’OCR. +ocr.help=Veuillez lire cette documentation pour savoir comment utiliser l'OCR pour d'autres langues ou une utilisation hors Docker : +ocr.credit=Ce service utilise OCRmyPDF et Tesseract pour l'OCR. ocr.submit=Traiter #extractImages extractImages.title=Extraire les images extractImages.header=Extraire les images -extractImages.selectText=Format d’image dans lequel convertir les images extraites +extractImages.selectText=Format d'image dans lequel convertir les images extraites extractImages.allowDuplicates=Save duplicate images extractImages.submit=Extraire @@ -872,10 +876,10 @@ fileToPDF.submit=Convertir #compress compress.title=Compresser un PDF -compress.header=Compresser un PDF (lorsque c’est possible!) -compress.credit=Ce service utilise Ghostscript pour la compression et l’optimisation des PDF. +compress.header=Compresser un PDF (lorsque c'est possible!) +compress.credit=Ce service utilise Ghostscript pour la compression et l'optimisation des PDF. compress.selectText.1=Mode manuel – de 1 à 4 -compress.selectText.2=Niveau d’optimisation +compress.selectText.2=Niveau d'optimisation compress.selectText.3=4 (terrible pour les images textuelles) compress.selectText.4=Mode automatique – ajuste automatiquement la qualité pour obtenir le PDF à la taille exacte compress.selectText.5=Taille PDF attendue (par exemple, 25 MB, 10,8 MB, 25 KB) @@ -960,9 +964,9 @@ split.submit=Diviser imageToPDF.title=Image en PDF imageToPDF.header=Image en PDF imageToPDF.submit=Convertir -imageToPDF.selectLabel=Options d’ajustement de l’image +imageToPDF.selectLabel=Options d'ajustement de l'image imageToPDF.fillPage=Remplir la page -imageToPDF.fitDocumentToImage=Ajuster la page à l’image +imageToPDF.fitDocumentToImage=Ajuster la page à l'image imageToPDF.maintainAspectRatio=Maintenir les proportions imageToPDF.selectText.2=Rotation automatique du PDF imageToPDF.selectText.3=Logique multi-fichiers (uniquement activée si vous travaillez avec plusieurs images) @@ -973,11 +977,11 @@ imageToPDF.selectText.5=Convertir en PDF séparés #pdfToImage pdfToImage.title=Image en PDF pdfToImage.header=Image en PDF -pdfToImage.selectText=Format d’image +pdfToImage.selectText=Format d'image pdfToImage.singleOrMultiple=Type de résultat pdfToImage.single=Une seule grande image pdfToImage.multi=Plusieurs images -pdfToImage.colorType=Type d’impression +pdfToImage.colorType=Type d'impression pdfToImage.color=Couleur pdfToImage.grey=Niveaux de gris pdfToImage.blackwhite=Noir et blanc (peut engendrer une perte de données !) @@ -989,21 +993,21 @@ pdfToImage.info=Python is not installed. Required for WebP conversion. addPassword.title=Ajouter un mot de passe addPassword.header=Ajouter un mot de passe addPassword.selectText.1=PDF à chiffrer -addPassword.selectText.2=Mot de passe de l’utilisateur +addPassword.selectText.2=Mot de passe de l'utilisateur addPassword.selectText.3=Longueur de la clé de chiffrement addPassword.selectText.4=Les valeurs plus élevées sont plus fortes, mais les valeurs plus faibles ont une meilleure compatibilité. addPassword.selectText.5=Autorisations à définir (utilisation recommandée avec le mot de passe du propriétaire) -addPassword.selectText.6=Empêcher l’assemblage du document -addPassword.selectText.7=Empêcher l’extraction de contenu -addPassword.selectText.8=Empêcher l’extraction pour l’accessibilité +addPassword.selectText.6=Empêcher l'assemblage du document +addPassword.selectText.7=Empêcher l'extraction de contenu +addPassword.selectText.8=Empêcher l'extraction pour l'accessibilité addPassword.selectText.9=Empêcher de remplir les formulaires addPassword.selectText.10=Empêcher la modification addPassword.selectText.11=Empêcher la modification des annotations -addPassword.selectText.12=Empêcher l’impression -addPassword.selectText.13=Empêcher l’impression des différents formats +addPassword.selectText.12=Empêcher l'impression +addPassword.selectText.13=Empêcher l'impression des différents formats addPassword.selectText.14=Mot de passe du propriétaire -addPassword.selectText.15=Restreint ce qui peut être fait avec le document une fois qu’il est ouvert (non pris en charge par tous les lecteurs). -addPassword.selectText.16=Restreint l’ouverture du document lui-même. +addPassword.selectText.15=Restreint ce qui peut être fait avec le document une fois qu'il est ouvert (non pris en charge par tous les lecteurs). +addPassword.selectText.16=Restreint l'ouverture du document lui-même. addPassword.submit=Chiffrer @@ -1031,14 +1035,14 @@ permissions.header=Modifier les permissions permissions.warning=Attention, pour que ces permissions soient immuables il est recommandé de les paramétrer avec un mot de passe via la page Ajouter un mot de passe. permissions.selectText.1=Sélectionnez le PDF permissions.selectText.2=Permissions à définir -permissions.selectText.3=Empêcher l’assemblage du document -permissions.selectText.4=Empêcher l’extraction de contenu -permissions.selectText.5=Empêcher l’extraction pour l’accessibilité +permissions.selectText.3=Empêcher l'assemblage du document +permissions.selectText.4=Empêcher l'extraction de contenu +permissions.selectText.5=Empêcher l'extraction pour l'accessibilité permissions.selectText.6=Empêcher de remplir les formulaires permissions.selectText.7=Empêcher la modification permissions.selectText.8=Empêcher la modification des annotations -permissions.selectText.9=Empêcher l’impression -permissions.selectText.10=Empêcher l’impression des différents formats +permissions.selectText.9=Empêcher l'impression +permissions.selectText.10=Empêcher l'impression des différents formats permissions.submit=Modifier @@ -1063,7 +1067,7 @@ changeMetadata.keywords=Mots clés changeMetadata.modDate=Date de modification (yyyy/MM/dd HH:mm:ss) changeMetadata.producer=Producteur changeMetadata.subject=Sujet -changeMetadata.trapped=Recouvrement (technique d’impression) +changeMetadata.trapped=Recouvrement (technique d'impression) changeMetadata.selectText.4=Autres métadonnées changeMetadata.selectText.5=Ajouter une entrée de métadonnées personnalisée changeMetadata.submit=Modifier @@ -1138,13 +1142,13 @@ split-by-size-or-count.submit=Séparer overlay-pdfs.header=Incrustation de PDF overlay-pdfs.baseFile.label=Sélectionner le fichier PDF de base overlay-pdfs.overlayFiles.label=Sélectionner les fichiers PDF à superposer -overlay-pdfs.mode.label=Sélectionner le mode d’incrustation +overlay-pdfs.mode.label=Sélectionner le mode d'incrustation overlay-pdfs.mode.sequential=Superposition séquentielle overlay-pdfs.mode.interleaved=Superposition entrelacée overlay-pdfs.mode.fixedRepeat=Superposition à répétition fixe overlay-pdfs.counts.label=Nombre de superpositions (pour le mode de répétition fixe) overlay-pdfs.counts.placeholder=Compteurs (séparés par des virgules, exemple : 2,3,1) -overlay-pdfs.position.label=Définir la position de l’incrustation +overlay-pdfs.position.label=Définir la position de l'incrustation overlay-pdfs.position.foreground=Premier plan overlay-pdfs.position.background=Arrière-plan overlay-pdfs.submit=Soumettre @@ -1178,47 +1182,45 @@ licenses.version=Version licenses.license=Licence #survey -survey.nav=Survey -survey.title=Stirling-PDF Survey -survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! -survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: -survey.changes2=With these changes we are getting paid business support and funding -survey.please=Please consider taking our survey! -survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) -survey.button=Take Survey -survey.dontShowAgain=Don't show again +survey.nav=Enquête +survey.title=Enquête Stirling-PDF +survey.description=Stirling-PDF n'a pas de suivi, donc nous voulons entendre nos utilisateurs pour améliorer Stirling-PDF ! +survey.changes=Stirling-PDF a changé depuis la dernière enquête ! Pour en savoir plus, veuillez consulter notre article de blog ici : +survey.changes2=Avec ces changements, nous obtenons un soutien commercial rémunéré et un financement +survey.please=Veuillez envisager de répondre à notre enquête ! +survey.disabled=(La fenêtre contextuelle de l'enquête sera désactivée dans les mises à jour suivantes mais sera disponible en bas de page) +survey.button=Répondre à l'enquête +survey.dontShowAgain=Ne plus afficher #error error.sorry=Désolé pour ce problème ! -error.needHelp=Besoin d’aide / Vous avez trouvé un problème ? -error.contactTip=Si vous avez encore des problèmes, n’hésitez pas à nous contacter pour obtenir de l’aide. Vous pouvez soumettre un ticket sur notre page GitHub ou nous contacter via Discord : -error.404.head=404 - Page non trouvée | oups on s’est foiré ! +error.needHelp=Besoin d'aide / Vous avez trouvé un problème ? +error.contactTip=Si vous avez encore des problèmes, n'hésitez pas à nous contacter pour obtenir de l'aide. Vous pouvez soumettre un ticket sur notre page GitHub ou nous contacter via Discord : +error.404.head=404 - Page non trouvée | oups on s'est foiré ! error.404.1=Nous ne parvenons pas à trouver la page que vous recherchez. -error.404.2=Quelque chose n’a pas fonctionné +error.404.2=Quelque chose n'a pas fonctionné error.github=Créer un ticket sur GitHub error.showStack=Afficher la Stack Trace error.copyStack=Copier la Stack Trace error.githubSubmit=GitHub - Créer un ticket -error.discordSubmit=Discord - Poster un message de demande d’assistance +error.discordSubmit=Discord - Poster un message de demande d'assistance #remove-image -removeImage.title=Remove image -removeImage.header=Remove image -removeImage.removeImage=Remove image -removeImage.submit=Remove image - - -splitByChapters.title=Split PDF by Chapters -splitByChapters.header=Split PDF by Chapters -splitByChapters.bookmarkLevel=Bookmark Level -splitByChapters.includeMetadata=Include Metadata -splitByChapters.allowDuplicates=Allow Duplicates -splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. -splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). -splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. -splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. -splitByChapters.submit=Split PDF - - +removeImage.title=Supprimer l'image +removeImage.header=Supprimer l'image +removeImage.removeImage=Supprimer l'image +removeImage.submit=Supprimer l'image + + +splitByChapters.title=Diviser un PDF par Chapitres +splitByChapters.header=Diviser un PDF par Chapitres +splitByChapters.bookmarkLevel=Niveau de Signet +splitByChapters.includeMetadata=Inclure les Métadonnées +splitByChapters.allowDuplicates=Autoriser les Doublons +splitByChapters.desc.1=Cet outil divise un fichier PDF en plusieurs PDF en fonction de sa structure de chapitres. +splitByChapters.desc.2=Niveau de Signet : Choisissez le niveau de signets à utiliser pour la division (0 pour le niveau supérieur, 1 pour le deuxième niveau, etc...). +splitByChapters.desc.3=Inclure les Métadonnées : Si coché, les métadonnées du PDF original seront incluses dans chaque PDF divisé. +splitByChapters.desc.4=Autoriser les Doublons : Si coché, permet à plusieurs signets sur la même page de créer des PDF séparés. +splitByChapters.submit=Diviser le PDF diff --git a/src/main/resources/messages_ga_IE.properties b/src/main/resources/messages_ga_IE.properties index c5389eb40c3..a254ca74465 100644 --- a/src/main/resources/messages_ga_IE.properties +++ b/src/main/resources/messages_ga_IE.properties @@ -79,6 +79,7 @@ info=Eolas pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Dath Aibhsithe 2: compare.document.1=Doiciméad 1 compare.document.2=Doiciméad 2 compare.submit=Déan comparáid idir +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Leabhair agus comics a PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_hi_IN.properties b/src/main/resources/messages_hi_IN.properties index 93983531843..f5029b1da6b 100644 --- a/src/main/resources/messages_hi_IN.properties +++ b/src/main/resources/messages_hi_IN.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=पृष्ठ pages=पृष्ठों +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=दस्तावेज़ 1 compare.document.2=दस्तावेज़ 2 compare.submit=तुलना करें +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Books and Comics to PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_hr_HR.properties b/src/main/resources/messages_hr_HR.properties index aa0cd65cccd..2317ca160c6 100644 --- a/src/main/resources/messages_hr_HR.properties +++ b/src/main/resources/messages_hr_HR.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=Dokument 1 compare.document.2=Dokument 2 compare.submit=Uporedi +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Knjige i stripovi u PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_hu_HU.properties b/src/main/resources/messages_hu_HU.properties index c53e52e1d83..c776b58cd19 100644 --- a/src/main/resources/messages_hu_HU.properties +++ b/src/main/resources/messages_hu_HU.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=Dokumentum 1 compare.document.2=Dokumentum 2 compare.submit=Összehasonlítás +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Books and Comics to PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_id_ID.properties b/src/main/resources/messages_id_ID.properties index afdea33e69b..c337c8b2833 100644 --- a/src/main/resources/messages_id_ID.properties +++ b/src/main/resources/messages_id_ID.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=Dokumen 1 compare.document.2=Dokumen 2 compare.submit=Bandingkan +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Books and Comics to PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_it_IT.properties b/src/main/resources/messages_it_IT.properties index bc3de60ba05..67322c42843 100644 --- a/src/main/resources/messages_it_IT.properties +++ b/src/main/resources/messages_it_IT.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Pagina pages=Pagine +loading=Loading... legal.privacy=Informativa sulla privacy legal.terms=Termini e Condizioni @@ -145,7 +146,7 @@ navbar.sections.convertFrom=Converti da PDF navbar.sections.security=Firma & Sicurezza navbar.sections.advance=Avanzate navbar.sections.edit=Visualizza & Modifica -navbar.sections.popular=Populare +navbar.sections.popular=Popolare ############# # SETTINGS # @@ -782,6 +783,9 @@ compare.highlightColor.2=Evidenzia colore 2: compare.document.1=Documento 1 compare.document.2=Documento 2 compare.submit=Compara +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Libri e fumetti in PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Livello segnalibro: seleziona il livello dei segnalibri d splitByChapters.desc.3=Includi metadati: se selezionato, i metadati del PDF originale verranno inclusi in ogni PDF diviso. splitByChapters.desc.4=Consenti duplicati: se selezionata, consente più segnalibri sulla stessa pagina per creare PDF separati. splitByChapters.submit=Dividi PDF - - diff --git a/src/main/resources/messages_ja_JP.properties b/src/main/resources/messages_ja_JP.properties index 6e7aac2c03f..09ff1b21989 100644 --- a/src/main/resources/messages_ja_JP.properties +++ b/src/main/resources/messages_ja_JP.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=プライバシーポリシー legal.terms=利用規約 @@ -782,6 +783,9 @@ compare.highlightColor.2=ハイライトカラー 2: compare.document.1=ドキュメント 1 compare.document.2=ドキュメント 2 compare.submit=比較 +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=書籍やコミックをPDFに変換 @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_ko_KR.properties b/src/main/resources/messages_ko_KR.properties index 832f7affb6e..054c1163edb 100644 --- a/src/main/resources/messages_ko_KR.properties +++ b/src/main/resources/messages_ko_KR.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=문서 1 compare.document.2=문서 2 compare.submit=비교 +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=책과 만화를 PDF로 @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_nl_NL.properties b/src/main/resources/messages_nl_NL.properties index ed8096c3ba9..054e2d189ab 100644 --- a/src/main/resources/messages_nl_NL.properties +++ b/src/main/resources/messages_nl_NL.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=Document 1 compare.document.2=Document 2 compare.submit=Vergelijken +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Boeken en strips naar PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_no_NB.properties b/src/main/resources/messages_no_NB.properties index 207ab27dc20..99e7c9b811a 100644 --- a/src/main/resources/messages_no_NB.properties +++ b/src/main/resources/messages_no_NB.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Uthevingsfarge 2: compare.document.1=Dokument 1 compare.document.2=Dokument 2 compare.submit=Sammenlign +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Bøker og Tegneserier til PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_pl_PL.properties b/src/main/resources/messages_pl_PL.properties index 241ae94c963..311cccc6016 100755 --- a/src/main/resources/messages_pl_PL.properties +++ b/src/main/resources/messages_pl_PL.properties @@ -79,6 +79,7 @@ info=informacje pro=Pro page=Strona pages=Strony +loading=Loading... legal.privacy=Polityka Prywatności legal.terms=Zasady i Postanowienia @@ -782,6 +783,9 @@ compare.highlightColor.2=Kolor Podświetlenia 2: compare.document.1=Dokument 1 compare.document.2=Dokument 2 compare.submit=Porównaj +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=eBooki do PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Poziom Zakładek: Wybierz poziom zakładek, który ma zos splitByChapters.desc.3=Dołącz Metadane: Jeśli opcja ta jest zaznaczona, metadane oryginalnego pliku PDF zostaną uwzględnione w każdym rozdzielonych plików PDF. splitByChapters.desc.4=Zezwól na Duplikaty: Jeśli ta opcja jest zaznaczona, pozwala na tworzenie oddzielnych plików PDF przez wiele zakładek na tej samej stronie. splitByChapters.submit=Podziel PDF - - diff --git a/src/main/resources/messages_pt_BR.properties b/src/main/resources/messages_pt_BR.properties index acdce05cf9b..f982c8d5ff0 100644 --- a/src/main/resources/messages_pt_BR.properties +++ b/src/main/resources/messages_pt_BR.properties @@ -79,6 +79,7 @@ info=Informações pro=Pro page=Página pages=Páginas +loading=Loading... legal.privacy=Política de Privacidade legal.terms=Termos e Condições @@ -782,6 +783,9 @@ compare.highlightColor.2=Cor de destaque 2: compare.document.1=Documento 1 compare.document.2=Documento 2 compare.submit=Comparar +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Livros e Quadrinhos para PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Nível de Marcador: Escolha o nível de marcador a ser us splitByChapters.desc.3=Incluir Metadados: Se marcado, os metadados do PDF original serão incluidos em cada divisão do PDF. splitByChapters.desc.4=Permitir Cópias: Se marcado, habilita vários marcadores na mesma página para criar PDFs separados. splitByChapters.submit=Dividir PDF - - diff --git a/src/main/resources/messages_pt_PT.properties b/src/main/resources/messages_pt_PT.properties index 94b2e53569d..d7e76e0bf3b 100644 --- a/src/main/resources/messages_pt_PT.properties +++ b/src/main/resources/messages_pt_PT.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=Documento 1 compare.document.2=Documento 2 compare.submit=Comparar +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Books and Comics to PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_ro_RO.properties b/src/main/resources/messages_ro_RO.properties index d7dbffaa705..1a4cb185c4d 100644 --- a/src/main/resources/messages_ro_RO.properties +++ b/src/main/resources/messages_ro_RO.properties @@ -79,6 +79,7 @@ info=Informații pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Culoare Evidențiere 2: compare.document.1=Documentul 1 compare.document.2=Documentul 2 compare.submit=Compară +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Cărți și Benzi Desenate în PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_ru_RU.properties b/src/main/resources/messages_ru_RU.properties index d72c6feae0e..19583530918 100644 --- a/src/main/resources/messages_ru_RU.properties +++ b/src/main/resources/messages_ru_RU.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=Документ 1 compare.document.2=Документ 2 compare.submit=Сравнить +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Книги и комиксы в PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_sk_SK.properties b/src/main/resources/messages_sk_SK.properties index 6e1518a3509..86467ce1ac2 100644 --- a/src/main/resources/messages_sk_SK.properties +++ b/src/main/resources/messages_sk_SK.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=Dokument 1 compare.document.2=Dokument 2 compare.submit=Porovnať +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Knihy a komiksy do PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_sr_LATN_RS.properties b/src/main/resources/messages_sr_LATN_RS.properties index d1e68c51dcf..d302f2c6c7f 100644 --- a/src/main/resources/messages_sr_LATN_RS.properties +++ b/src/main/resources/messages_sr_LATN_RS.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=Dokument 1 compare.document.2=Dokument 2 compare.submit=Uporedi +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Books and Comics to PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_sv_SE.properties b/src/main/resources/messages_sv_SE.properties index d9d8c0728e0..692ba377ab4 100644 --- a/src/main/resources/messages_sv_SE.properties +++ b/src/main/resources/messages_sv_SE.properties @@ -79,6 +79,7 @@ info=Info pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -114,16 +115,16 @@ pipelineOptions.validateButton=Validera ######################## # ENTERPRISE EDITION # ######################## -enterpriseEdition.button=Upgrade to Pro -enterpriseEdition.warning=This feature is only available to Pro users. -enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. -enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro +enterpriseEdition.button=Uppgradera till Pro +enterpriseEdition.warning=Den här funktionen är endast tillgänglig för Pro-användare. +enterpriseEdition.yamlAdvert=Stirling PDF Pro stöder YAML-konfigurationsfiler och andra SSO funktioner. +enterpriseEdition.ssoAdvert=Söker du fler funktioner för användarhantering? Spana in Stirling PDF Pro. ################# # Analytics # ################# -analytics.title=Do you want make Stirling PDF better? +analytics.title=Vill du göra Stirling PDF bättre? analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. analytics.enable=Enable analytics @@ -145,7 +146,7 @@ navbar.sections.convertFrom=Konvertera från PDF navbar.sections.security=Signera & Säkerhet navbar.sections.advance=Avancerat navbar.sections.edit=Visa & Redigera -navbar.sections.popular=Popular +navbar.sections.popular=Populära ############# # SETTINGS # @@ -243,7 +244,7 @@ database.fileNotFound=Filen hittades inte database.fileNullOrEmpty=Filen får inte vara null eller tom database.failedImportFile=Misslyckades med att importera fil -session.expired=Your session has expired. Please refresh the page and try again. +session.expired=Din session har löpt ut. Uppdatera sidan och försök igen. ############# # HOME-PAGE # @@ -503,9 +504,9 @@ home.removeImagePdf.desc=Ta bort bild från PDF för att minska filstorlek removeImagePdf.tags=Ta bort bild,Sidoperationer,Backend,serversida -home.splitPdfByChapters.title=Split PDF by Chapters -home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. -splitPdfByChapters.tags=split,chapters,bookmarks,organize +home.splitPdfByChapters.title=Dela upp PDF efter kapitel +home.splitPdfByChapters.desc=Dela upp en PDF till flera filer baserat på dess kapitelstruktur. +splitPdfByChapters.tags=dela,kapitel,bokmärken,organisera #replace-invert-color replace-color.title=Replace-Invert-Color @@ -551,9 +552,9 @@ login.oauth2AccessDenied=Åtkomst nekad login.oauth2InvalidTokenResponse=Ogiltigt token-svar login.oauth2InvalidIdToken=Ogiltigt Id-token login.userIsDisabled=Användaren är inaktiverad, inloggning är för närvarande blockerad med detta användarnamn. Kontakta administratören. -login.alreadyLoggedIn=You are already logged in to -login.alreadyLoggedIn2=devices. Please log out of the devices and try again. -login.toManySessions=You have too many active sessions +login.alreadyLoggedIn=Du är redan inloggad på +login.alreadyLoggedIn2=enheter. Logga ut från enheterna och försök igen. +login.toManySessions=Du har för många aktiva sessioner #auto-redact autoRedact.title=Auto-redigera @@ -728,7 +729,7 @@ pageLayout.submit=Skicka scalePages.title=Justera sidskala scalePages.header=Justera sidskala scalePages.pageSize=Storlek på en sida i dokumentet. -scalePages.keepPageSize=Original Size +scalePages.keepPageSize=Originalstorlek scalePages.scaleFactor=Zoomnivå (beskärning) för en sida. scalePages.submit=Skicka @@ -782,6 +783,9 @@ compare.highlightColor.2=Markeringsfärg 2: compare.document.1=Dokument 1 compare.document.2=Dokument 2 compare.submit=Jämför +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Böcker och serier till PDF @@ -1181,7 +1185,7 @@ licenses.license=Licens survey.nav=Undersökning survey.title=Stirling-PDF-undersökning survey.description=Stirling-PDF har ingen spårning så vi vill höra från våra användare för att förbättra Stirling-PDF! -survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes=Stirling-PDF har ändrats sedan den senaste undersökningen. Lär dig mer på vår blogg här: survey.changes2=With these changes we are getting paid business support and funding survey.please=Vänligen överväg att delta i vår undersökning! survey.disabled=(Undersökningspopup kommer att inaktiveras i kommande uppdateringar men finns tillgänglig längst ner på sidan) @@ -1210,15 +1214,13 @@ removeImage.removeImage=Ta bort bild removeImage.submit=Ta bort bild -splitByChapters.title=Split PDF by Chapters -splitByChapters.header=Split PDF by Chapters -splitByChapters.bookmarkLevel=Bookmark Level -splitByChapters.includeMetadata=Include Metadata -splitByChapters.allowDuplicates=Allow Duplicates -splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.title=Dela upp PDF efter kapitel +splitByChapters.header=Dela upp PDF efter kapitel +splitByChapters.bookmarkLevel=Bokmärkesnivå +splitByChapters.includeMetadata=Inkludera Metadata +splitByChapters.allowDuplicates=Tillåt Dubletter +splitByChapters.desc.1=Detta verktyg delar upp en PDF till flera PDFer baserat på dess kapitelstruktur. splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. -splitByChapters.submit=Split PDF - - +splitByChapters.submit=Dela upp PDF diff --git a/src/main/resources/messages_th_TH.properties b/src/main/resources/messages_th_TH.properties index 8df2f7fab1d..272db88574c 100644 --- a/src/main/resources/messages_th_TH.properties +++ b/src/main/resources/messages_th_TH.properties @@ -79,6 +79,7 @@ info=ข้อมูล pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=สีเน้น 2: compare.document.1=เอกสาร 1 compare.document.2=เอกสาร 2 compare.submit=เปรียบเทียบ +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=หนังสือและการ์ตูนเป็น PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_tr_TR.properties b/src/main/resources/messages_tr_TR.properties index ec95707c59d..906082cebe8 100644 --- a/src/main/resources/messages_tr_TR.properties +++ b/src/main/resources/messages_tr_TR.properties @@ -79,6 +79,7 @@ info=Bilgi pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Gizlilik Politikası legal.terms=Şartlar ve koşullar @@ -782,6 +783,9 @@ compare.highlightColor.2=Vurgu Rengi 2: compare.document.1=Belge 1 compare.document.2=Belge 2 compare.submit=Karşılaştır +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Kitapları ve Çizgi Romanları PDF'e Dönüştürme @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_uk_UA.properties b/src/main/resources/messages_uk_UA.properties index 2a933bb78f4..0c0ed2c2cd2 100644 --- a/src/main/resources/messages_uk_UA.properties +++ b/src/main/resources/messages_uk_UA.properties @@ -79,6 +79,7 @@ info=Інформація pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Highlight Color 2: compare.document.1=Документ 1 compare.document.2=Документ 2 compare.submit=Порівняти +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Книги та комікси в PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_vi_VN.properties b/src/main/resources/messages_vi_VN.properties index cb5de6aa9c5..59d9848c85e 100644 --- a/src/main/resources/messages_vi_VN.properties +++ b/src/main/resources/messages_vi_VN.properties @@ -79,6 +79,7 @@ info=Thông tin pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=Màu đánh dấu 2: compare.document.1=Tài liệu 1 compare.document.2=Tài liệu 2 compare.submit=So sánh +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=Sách và truyện tranh sang PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties index 5a60398df79..69992626e8e 100644 --- a/src/main/resources/messages_zh_CN.properties +++ b/src/main/resources/messages_zh_CN.properties @@ -79,6 +79,7 @@ info=信息 pro=Pro page=Page pages=Pages +loading=Loading... legal.privacy=Privacy Policy legal.terms=Terms and Conditions @@ -782,6 +783,9 @@ compare.highlightColor.2=高亮颜色 2: compare.document.1=文档 1 compare.document.2=文档 2 compare.submit=比较 +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=电子书和漫画转换成PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. splitByChapters.submit=Split PDF - - diff --git a/src/main/resources/messages_zh_TW.properties b/src/main/resources/messages_zh_TW.properties index cf23acfbbef..3c5ad03a8e2 100644 --- a/src/main/resources/messages_zh_TW.properties +++ b/src/main/resources/messages_zh_TW.properties @@ -79,6 +79,7 @@ info=資訊 pro=專業版 page=頁面 pages=頁面 +loading=Loading... legal.privacy=隱私權政策 legal.terms=使用條款 @@ -782,6 +783,9 @@ compare.highlightColor.2=標示顏色 2: compare.document.1=文件 1 compare.document.2=文件 2 compare.submit=比較 +compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced +compare.large.file.message=One or Both of the provided documents are too large to process +compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison. #BookToPDF BookToPDF.title=電子書和漫畫轉 PDF @@ -1220,5 +1224,3 @@ splitByChapters.desc.2=書籤層級:選擇用於分割的書籤層級(0 表 splitByChapters.desc.3=包含中繼資料:如果勾選,原始 PDF 的中繼資料將包含在每個分割後的 PDF 中。 splitByChapters.desc.4=允許重複:如果勾選,允許同一頁面上的多個書籤建立獨立的 PDF。 splitByChapters.submit=分割 PDF - - diff --git a/src/main/resources/static/3rdPartyLicenses.json b/src/main/resources/static/3rdPartyLicenses.json index 4c60ddd3ce6..fc3cb16fb23 100644 --- a/src/main/resources/static/3rdPartyLicenses.json +++ b/src/main/resources/static/3rdPartyLicenses.json @@ -3,17 +3,24 @@ { "moduleName": "ch.qos.logback:logback-classic", "moduleUrl": "http://www.qos.ch", - "moduleVersion": "1.5.8", + "moduleVersion": "1.5.11", "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.8", + "moduleVersion": "1.5.11", "moduleLicense": "GNU Lesser General Public License", "moduleLicenseUrl": "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" }, + { + "moduleName": "com.adobe.xmp:xmpcore", + "moduleUrl": "https://www.adobe.com/devnet/xmp/library/eula-xmp-library-java.html", + "moduleVersion": "6.1.11", + "moduleLicense": "The BSD 3-Clause License (BSD3)", + "moduleLicenseUrl": "https://opensource.org/licenses/BSD-3-Clause" + }, { "moduleName": "com.bucket4j:bucket4j_jdk17-core", "moduleUrl": "http://github.com/bucket4j/bucket4j/bucket4j_jdk17-core", @@ -28,6 +35,13 @@ "moduleLicense": "MIT", "moduleLicenseUrl": "https://opensource.org/licenses/MIT" }, + { + "moduleName": "com.drewnoakes:metadata-extractor", + "moduleUrl": "https://drewnoakes.com/code/exif/", + "moduleVersion": "2.19.0", + "moduleLicense": "The Apache Software License, Version 2.0", + "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + }, { "moduleName": "com.fasterxml.jackson.core:jackson-annotations", "moduleUrl": "https://github.com/FasterXML/jackson", @@ -400,7 +414,7 @@ { "moduleName": "io.micrometer:micrometer-commons", "moduleUrl": "https://github.com/micrometer-metrics/micrometer", - "moduleVersion": "1.13.4", + "moduleVersion": "1.13.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, @@ -414,14 +428,14 @@ { "moduleName": "io.micrometer:micrometer-jakarta9", "moduleUrl": "https://github.com/micrometer-metrics/micrometer", - "moduleVersion": "1.13.4", + "moduleVersion": "1.13.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, { "moduleName": "io.micrometer:micrometer-observation", "moduleUrl": "https://github.com/micrometer-metrics/micrometer", - "moduleVersion": "1.13.4", + "moduleVersion": "1.13.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, @@ -681,7 +695,7 @@ { "moduleName": "org.apache.tomcat.embed:tomcat-embed-el", "moduleUrl": "https://tomcat.apache.org/", - "moduleVersion": "10.1.30", + "moduleVersion": "10.1.31", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, @@ -776,182 +790,182 @@ { "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-client", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-common", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-servlet", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-annotations", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-plus", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlet", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlets", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-webapp", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-client", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-common", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-server", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-api", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-common", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-alpn-client", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-client", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-ee", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-http", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-io", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-plus", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-security", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-server", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-session", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-util", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-xml", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.13", + "moduleVersion": "12.0.14", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, @@ -1139,175 +1153,175 @@ { "moduleName": "org.springframework.boot:spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-actuator", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-actuator-autoconfigure", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-autoconfigure", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-devtools", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-actuator", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-aop", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-data-jpa", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-jdbc", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-jetty", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-json", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-logging", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-oauth2-client", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-security", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-thymeleaf", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-web", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.data:spring-data-commons", "moduleUrl": "https://spring.io/projects/spring-data", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.data:spring-data-jpa", "moduleUrl": "https://projects.spring.io/spring-data-jpa", - "moduleVersion": "3.3.4", + "moduleVersion": "3.3.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.security:spring-security-config", "moduleUrl": "https://spring.io/projects/spring-security", - "moduleVersion": "6.3.3", + "moduleVersion": "6.3.4", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.security:spring-security-core", "moduleUrl": "https://spring.io/projects/spring-security", - "moduleVersion": "6.3.3", + "moduleVersion": "6.3.4", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.security:spring-security-crypto", "moduleUrl": "https://spring.io/projects/spring-security", - "moduleVersion": "6.3.3", + "moduleVersion": "6.3.4", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.security:spring-security-oauth2-client", "moduleUrl": "https://spring.io/projects/spring-security", - "moduleVersion": "6.3.3", + "moduleVersion": "6.3.4", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.security:spring-security-oauth2-core", "moduleUrl": "https://spring.io/projects/spring-security", - "moduleVersion": "6.3.3", + "moduleVersion": "6.3.4", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.security:spring-security-oauth2-jose", "moduleUrl": "https://spring.io/projects/spring-security", - "moduleVersion": "6.3.3", + "moduleVersion": "6.3.4", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, @@ -1321,84 +1335,84 @@ { "moduleName": "org.springframework.security:spring-security-web", "moduleUrl": "https://spring.io/projects/spring-security", - "moduleVersion": "6.3.3", + "moduleVersion": "6.3.4", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-aop", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.1.13", + "moduleVersion": "6.1.14", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-aspects", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.1.13", + "moduleVersion": "6.1.14", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-beans", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.1.13", + "moduleVersion": "6.1.14", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-context", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.1.13", + "moduleVersion": "6.1.14", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-core", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.1.13", + "moduleVersion": "6.1.14", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-expression", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.1.13", + "moduleVersion": "6.1.14", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-jcl", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.1.13", + "moduleVersion": "6.1.14", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-jdbc", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.1.13", + "moduleVersion": "6.1.14", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-orm", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.1.13", + "moduleVersion": "6.1.14", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-tx", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.1.13", + "moduleVersion": "6.1.14", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-web", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.1.13", + "moduleVersion": "6.1.14", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, diff --git a/src/main/resources/static/images/signature.png b/src/main/resources/static/images/signature.png new file mode 100644 index 00000000000..1adfcedc3c2 Binary files /dev/null and b/src/main/resources/static/images/signature.png differ diff --git a/src/main/resources/static/js/compare/diff.js b/src/main/resources/static/js/compare/diff.js new file mode 100644 index 00000000000..2c2c33344ec --- /dev/null +++ b/src/main/resources/static/js/compare/diff.js @@ -0,0 +1,2106 @@ +/*! + + diff v7.0.0 + +BSD 3-Clause License + +Copyright (c) 2009-2015, Kevin Decker +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +@license +*/ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Diff = {})); +})(this, (function (exports) { 'use strict'; + + function Diff() {} + Diff.prototype = { + diff: function diff(oldString, newString) { + var _options$timeout; + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + var callback = options.callback; + if (typeof options === 'function') { + callback = options; + options = {}; + } + var self = this; + function done(value) { + value = self.postProcess(value, options); + if (callback) { + setTimeout(function () { + callback(value); + }, 0); + return true; + } else { + return value; + } + } + + // Allow subclasses to massage the input prior to running + oldString = this.castInput(oldString, options); + newString = this.castInput(newString, options); + oldString = this.removeEmpty(this.tokenize(oldString, options)); + newString = this.removeEmpty(this.tokenize(newString, options)); + var newLen = newString.length, + oldLen = oldString.length; + var editLength = 1; + var maxEditLength = newLen + oldLen; + if (options.maxEditLength != null) { + maxEditLength = Math.min(maxEditLength, options.maxEditLength); + } + var maxExecutionTime = (_options$timeout = options.timeout) !== null && _options$timeout !== void 0 ? _options$timeout : Infinity; + var abortAfterTimestamp = Date.now() + maxExecutionTime; + var bestPath = [{ + oldPos: -1, + lastComponent: undefined + }]; + + // Seed editLength = 0, i.e. the content starts with the same values + var newPos = this.extractCommon(bestPath[0], newString, oldString, 0, options); + if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) { + // Identity per the equality and tokenizer + return done(buildValues(self, bestPath[0].lastComponent, newString, oldString, self.useLongestToken)); + } + + // Once we hit the right edge of the edit graph on some diagonal k, we can + // definitely reach the end of the edit graph in no more than k edits, so + // there's no point in considering any moves to diagonal k+1 any more (from + // which we're guaranteed to need at least k+1 more edits). + // Similarly, once we've reached the bottom of the edit graph, there's no + // point considering moves to lower diagonals. + // We record this fact by setting minDiagonalToConsider and + // maxDiagonalToConsider to some finite value once we've hit the edge of + // the edit graph. + // This optimization is not faithful to the original algorithm presented in + // Myers's paper, which instead pointlessly extends D-paths off the end of + // the edit graph - see page 7 of Myers's paper which notes this point + // explicitly and illustrates it with a diagram. This has major performance + // implications for some common scenarios. For instance, to compute a diff + // where the new text simply appends d characters on the end of the + // original text of length n, the true Myers algorithm will take O(n+d^2) + // time while this optimization needs only O(n+d) time. + var minDiagonalToConsider = -Infinity, + maxDiagonalToConsider = Infinity; + + // Main worker method. checks all permutations of a given edit length for acceptance. + function execEditLength() { + for (var diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) { + var basePath = void 0; + var removePath = bestPath[diagonalPath - 1], + addPath = bestPath[diagonalPath + 1]; + if (removePath) { + // No one else is going to attempt to use this value, clear it + bestPath[diagonalPath - 1] = undefined; + } + var canAdd = false; + if (addPath) { + // what newPos will be after we do an insertion: + var addPathNewPos = addPath.oldPos - diagonalPath; + canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen; + } + var canRemove = removePath && removePath.oldPos + 1 < oldLen; + if (!canAdd && !canRemove) { + // If this path is a terminal then prune + bestPath[diagonalPath] = undefined; + continue; + } + + // Select the diagonal that we want to branch from. We select the prior + // path whose position in the old string is the farthest from the origin + // and does not pass the bounds of the diff graph + if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) { + basePath = self.addToPath(addPath, true, false, 0, options); + } else { + basePath = self.addToPath(removePath, false, true, 1, options); + } + newPos = self.extractCommon(basePath, newString, oldString, diagonalPath, options); + if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) { + // If we have hit the end of both strings, then we are done + return done(buildValues(self, basePath.lastComponent, newString, oldString, self.useLongestToken)); + } else { + bestPath[diagonalPath] = basePath; + if (basePath.oldPos + 1 >= oldLen) { + maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1); + } + if (newPos + 1 >= newLen) { + minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1); + } + } + } + editLength++; + } + + // Performs the length of edit iteration. Is a bit fugly as this has to support the + // sync and async mode which is never fun. Loops over execEditLength until a value + // is produced, or until the edit length exceeds options.maxEditLength (if given), + // in which case it will return undefined. + if (callback) { + (function exec() { + setTimeout(function () { + if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) { + return callback(); + } + if (!execEditLength()) { + exec(); + } + }, 0); + })(); + } else { + while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) { + var ret = execEditLength(); + if (ret) { + return ret; + } + } + } + }, + addToPath: function addToPath(path, added, removed, oldPosInc, options) { + var last = path.lastComponent; + if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) { + return { + oldPos: path.oldPos + oldPosInc, + lastComponent: { + count: last.count + 1, + added: added, + removed: removed, + previousComponent: last.previousComponent + } + }; + } else { + return { + oldPos: path.oldPos + oldPosInc, + lastComponent: { + count: 1, + added: added, + removed: removed, + previousComponent: last + } + }; + } + }, + extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath, options) { + var newLen = newString.length, + oldLen = oldString.length, + oldPos = basePath.oldPos, + newPos = oldPos - diagonalPath, + commonCount = 0; + while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldString[oldPos + 1], newString[newPos + 1], options)) { + newPos++; + oldPos++; + commonCount++; + if (options.oneChangePerToken) { + basePath.lastComponent = { + count: 1, + previousComponent: basePath.lastComponent, + added: false, + removed: false + }; + } + } + if (commonCount && !options.oneChangePerToken) { + basePath.lastComponent = { + count: commonCount, + previousComponent: basePath.lastComponent, + added: false, + removed: false + }; + } + basePath.oldPos = oldPos; + return newPos; + }, + equals: function equals(left, right, options) { + if (options.comparator) { + return options.comparator(left, right); + } else { + return left === right || options.ignoreCase && left.toLowerCase() === right.toLowerCase(); + } + }, + removeEmpty: function removeEmpty(array) { + var ret = []; + for (var i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + return ret; + }, + castInput: function castInput(value) { + return value; + }, + tokenize: function tokenize(value) { + return Array.from(value); + }, + join: function join(chars) { + return chars.join(''); + }, + postProcess: function postProcess(changeObjects) { + return changeObjects; + } + }; + function buildValues(diff, lastComponent, newString, oldString, useLongestToken) { + // First we convert our linked list of components in reverse order to an + // array in the right order: + var components = []; + var nextComponent; + while (lastComponent) { + components.push(lastComponent); + nextComponent = lastComponent.previousComponent; + delete lastComponent.previousComponent; + lastComponent = nextComponent; + } + components.reverse(); + var componentPos = 0, + componentLen = components.length, + newPos = 0, + oldPos = 0; + for (; componentPos < componentLen; componentPos++) { + var component = components[componentPos]; + if (!component.removed) { + if (!component.added && useLongestToken) { + var value = newString.slice(newPos, newPos + component.count); + value = value.map(function (value, i) { + var oldValue = oldString[oldPos + i]; + return oldValue.length > value.length ? oldValue : value; + }); + component.value = diff.join(value); + } else { + component.value = diff.join(newString.slice(newPos, newPos + component.count)); + } + newPos += component.count; + + // Common case + if (!component.added) { + oldPos += component.count; + } + } else { + component.value = diff.join(oldString.slice(oldPos, oldPos + component.count)); + oldPos += component.count; + } + } + return components; + } + + var characterDiff = new Diff(); + function diffChars(oldStr, newStr, options) { + return characterDiff.diff(oldStr, newStr, options); + } + + function longestCommonPrefix(str1, str2) { + var i; + for (i = 0; i < str1.length && i < str2.length; i++) { + if (str1[i] != str2[i]) { + return str1.slice(0, i); + } + } + return str1.slice(0, i); + } + function longestCommonSuffix(str1, str2) { + var i; + + // Unlike longestCommonPrefix, we need a special case to handle all scenarios + // where we return the empty string since str1.slice(-0) will return the + // entire string. + if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) { + return ''; + } + for (i = 0; i < str1.length && i < str2.length; i++) { + if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) { + return str1.slice(-i); + } + } + return str1.slice(-i); + } + function replacePrefix(string, oldPrefix, newPrefix) { + if (string.slice(0, oldPrefix.length) != oldPrefix) { + throw Error("string ".concat(JSON.stringify(string), " doesn't start with prefix ").concat(JSON.stringify(oldPrefix), "; this is a bug")); + } + return newPrefix + string.slice(oldPrefix.length); + } + function replaceSuffix(string, oldSuffix, newSuffix) { + if (!oldSuffix) { + return string + newSuffix; + } + if (string.slice(-oldSuffix.length) != oldSuffix) { + throw Error("string ".concat(JSON.stringify(string), " doesn't end with suffix ").concat(JSON.stringify(oldSuffix), "; this is a bug")); + } + return string.slice(0, -oldSuffix.length) + newSuffix; + } + function removePrefix(string, oldPrefix) { + return replacePrefix(string, oldPrefix, ''); + } + function removeSuffix(string, oldSuffix) { + return replaceSuffix(string, oldSuffix, ''); + } + function maximumOverlap(string1, string2) { + return string2.slice(0, overlapCount(string1, string2)); + } + + // Nicked from https://stackoverflow.com/a/60422853/1709587 + function overlapCount(a, b) { + // Deal with cases where the strings differ in length + var startA = 0; + if (a.length > b.length) { + startA = a.length - b.length; + } + var endB = b.length; + if (a.length < b.length) { + endB = a.length; + } + // Create a back-reference for each index + // that should be followed in case of a mismatch. + // We only need B to make these references: + var map = Array(endB); + var k = 0; // Index that lags behind j + map[0] = 0; + for (var j = 1; j < endB; j++) { + if (b[j] == b[k]) { + map[j] = map[k]; // skip over the same character (optional optimisation) + } else { + map[j] = k; + } + while (k > 0 && b[j] != b[k]) { + k = map[k]; + } + if (b[j] == b[k]) { + k++; + } + } + // Phase 2: use these references while iterating over A + k = 0; + for (var i = startA; i < a.length; i++) { + while (k > 0 && a[i] != b[k]) { + k = map[k]; + } + if (a[i] == b[k]) { + k++; + } + } + return k; + } + + /** + * Returns true if the string consistently uses Windows line endings. + */ + function hasOnlyWinLineEndings(string) { + return string.includes('\r\n') && !string.startsWith('\n') && !string.match(/[^\r]\n/); + } + + /** + * Returns true if the string consistently uses Unix line endings. + */ + function hasOnlyUnixLineEndings(string) { + return !string.includes('\r\n') && string.includes('\n'); + } + + // Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode + // + // Ranges and exceptions: + // Latin-1 Supplement, 0080–00FF + // - U+00D7 × Multiplication sign + // - U+00F7 ÷ Division sign + // Latin Extended-A, 0100–017F + // Latin Extended-B, 0180–024F + // IPA Extensions, 0250–02AF + // Spacing Modifier Letters, 02B0–02FF + // - U+02C7 ˇ ˇ Caron + // - U+02D8 ˘ ˘ Breve + // - U+02D9 ˙ ˙ Dot Above + // - U+02DA ˚ ˚ Ring Above + // - U+02DB ˛ ˛ Ogonek + // - U+02DC ˜ ˜ Small Tilde + // - U+02DD ˝ ˝ Double Acute Accent + // Latin Extended Additional, 1E00–1EFF + var extendedWordChars = "a-zA-Z0-9_\\u{C0}-\\u{FF}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}"; + + // Each token is one of the following: + // - A punctuation mark plus the surrounding whitespace + // - A word plus the surrounding whitespace + // - Pure whitespace (but only in the special case where this the entire text + // is just whitespace) + // + // We have to include surrounding whitespace in the tokens because the two + // alternative approaches produce horribly broken results: + // * If we just discard the whitespace, we can't fully reproduce the original + // text from the sequence of tokens and any attempt to render the diff will + // get the whitespace wrong. + // * If we have separate tokens for whitespace, then in a typical text every + // second token will be a single space character. But this often results in + // the optimal diff between two texts being a perverse one that preserves + // the spaces between words but deletes and reinserts actual common words. + // See https://github.com/kpdecker/jsdiff/issues/160#issuecomment-1866099640 + // for an example. + // + // Keeping the surrounding whitespace of course has implications for .equals + // and .join, not just .tokenize. + + // This regex does NOT fully implement the tokenization rules described above. + // Instead, it gives runs of whitespace their own "token". The tokenize method + // then handles stitching whitespace tokens onto adjacent word or punctuation + // tokens. + var tokenizeIncludingWhitespace = new RegExp("[".concat(extendedWordChars, "]+|\\s+|[^").concat(extendedWordChars, "]"), 'ug'); + var wordDiff = new Diff(); + wordDiff.equals = function (left, right, options) { + if (options.ignoreCase) { + left = left.toLowerCase(); + right = right.toLowerCase(); + } + return left.trim() === right.trim(); + }; + wordDiff.tokenize = function (value) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var parts; + if (options.intlSegmenter) { + if (options.intlSegmenter.resolvedOptions().granularity != 'word') { + throw new Error('The segmenter passed must have a granularity of "word"'); + } + parts = Array.from(options.intlSegmenter.segment(value), function (segment) { + return segment.segment; + }); + } else { + parts = value.match(tokenizeIncludingWhitespace) || []; + } + var tokens = []; + var prevPart = null; + parts.forEach(function (part) { + if (/\s/.test(part)) { + if (prevPart == null) { + tokens.push(part); + } else { + tokens.push(tokens.pop() + part); + } + } else if (/\s/.test(prevPart)) { + if (tokens[tokens.length - 1] == prevPart) { + tokens.push(tokens.pop() + part); + } else { + tokens.push(prevPart + part); + } + } else { + tokens.push(part); + } + prevPart = part; + }); + return tokens; + }; + wordDiff.join = function (tokens) { + // Tokens being joined here will always have appeared consecutively in the + // same text, so we can simply strip off the leading whitespace from all the + // tokens except the first (and except any whitespace-only tokens - but such + // a token will always be the first and only token anyway) and then join them + // and the whitespace around words and punctuation will end up correct. + return tokens.map(function (token, i) { + if (i == 0) { + return token; + } else { + return token.replace(/^\s+/, ''); + } + }).join(''); + }; + wordDiff.postProcess = function (changes, options) { + if (!changes || options.oneChangePerToken) { + return changes; + } + var lastKeep = null; + // Change objects representing any insertion or deletion since the last + // "keep" change object. There can be at most one of each. + var insertion = null; + var deletion = null; + changes.forEach(function (change) { + if (change.added) { + insertion = change; + } else if (change.removed) { + deletion = change; + } else { + if (insertion || deletion) { + // May be false at start of text + dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, change); + } + lastKeep = change; + insertion = null; + deletion = null; + } + }); + if (insertion || deletion) { + dedupeWhitespaceInChangeObjects(lastKeep, deletion, insertion, null); + } + return changes; + }; + function diffWords(oldStr, newStr, options) { + // This option has never been documented and never will be (it's clearer to + // just call `diffWordsWithSpace` directly if you need that behavior), but + // has existed in jsdiff for a long time, so we retain support for it here + // for the sake of backwards compatibility. + if ((options === null || options === void 0 ? void 0 : options.ignoreWhitespace) != null && !options.ignoreWhitespace) { + return diffWordsWithSpace(oldStr, newStr, options); + } + return wordDiff.diff(oldStr, newStr, options); + } + function dedupeWhitespaceInChangeObjects(startKeep, deletion, insertion, endKeep) { + // Before returning, we tidy up the leading and trailing whitespace of the + // change objects to eliminate cases where trailing whitespace in one object + // is repeated as leading whitespace in the next. + // Below are examples of the outcomes we want here to explain the code. + // I=insert, K=keep, D=delete + // 1. diffing 'foo bar baz' vs 'foo baz' + // Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz' + // After cleanup, we want: K:'foo ' D:'bar ' K:'baz' + // + // 2. Diffing 'foo bar baz' vs 'foo qux baz' + // Prior to cleanup, we have K:'foo ' D:' bar ' I:' qux ' K:' baz' + // After cleanup, we want K:'foo ' D:'bar' I:'qux' K:' baz' + // + // 3. Diffing 'foo\nbar baz' vs 'foo baz' + // Prior to cleanup, we have K:'foo ' D:'\nbar ' K:' baz' + // After cleanup, we want K'foo' D:'\nbar' K:' baz' + // + // 4. Diffing 'foo baz' vs 'foo\nbar baz' + // Prior to cleanup, we have K:'foo\n' I:'\nbar ' K:' baz' + // After cleanup, we ideally want K'foo' I:'\nbar' K:' baz' + // but don't actually manage this currently (the pre-cleanup change + // objects don't contain enough information to make it possible). + // + // 5. Diffing 'foo bar baz' vs 'foo baz' + // Prior to cleanup, we have K:'foo ' D:' bar ' K:' baz' + // After cleanup, we want K:'foo ' D:' bar ' K:'baz' + // + // Our handling is unavoidably imperfect in the case where there's a single + // indel between keeps and the whitespace has changed. For instance, consider + // diffing 'foo\tbar\nbaz' vs 'foo baz'. Unless we create an extra change + // object to represent the insertion of the space character (which isn't even + // a token), we have no way to avoid losing information about the texts' + // original whitespace in the result we return. Still, we do our best to + // output something that will look sensible if we e.g. print it with + // insertions in green and deletions in red. + + // Between two "keep" change objects (or before the first or after the last + // change object), we can have either: + // * A "delete" followed by an "insert" + // * Just an "insert" + // * Just a "delete" + // We handle the three cases separately. + if (deletion && insertion) { + var oldWsPrefix = deletion.value.match(/^\s*/)[0]; + var oldWsSuffix = deletion.value.match(/\s*$/)[0]; + var newWsPrefix = insertion.value.match(/^\s*/)[0]; + var newWsSuffix = insertion.value.match(/\s*$/)[0]; + if (startKeep) { + var commonWsPrefix = longestCommonPrefix(oldWsPrefix, newWsPrefix); + startKeep.value = replaceSuffix(startKeep.value, newWsPrefix, commonWsPrefix); + deletion.value = removePrefix(deletion.value, commonWsPrefix); + insertion.value = removePrefix(insertion.value, commonWsPrefix); + } + if (endKeep) { + var commonWsSuffix = longestCommonSuffix(oldWsSuffix, newWsSuffix); + endKeep.value = replacePrefix(endKeep.value, newWsSuffix, commonWsSuffix); + deletion.value = removeSuffix(deletion.value, commonWsSuffix); + insertion.value = removeSuffix(insertion.value, commonWsSuffix); + } + } else if (insertion) { + // The whitespaces all reflect what was in the new text rather than + // the old, so we essentially have no information about whitespace + // insertion or deletion. We just want to dedupe the whitespace. + // We do that by having each change object keep its trailing + // whitespace and deleting duplicate leading whitespace where + // present. + if (startKeep) { + insertion.value = insertion.value.replace(/^\s*/, ''); + } + if (endKeep) { + endKeep.value = endKeep.value.replace(/^\s*/, ''); + } + // otherwise we've got a deletion and no insertion + } else if (startKeep && endKeep) { + var newWsFull = endKeep.value.match(/^\s*/)[0], + delWsStart = deletion.value.match(/^\s*/)[0], + delWsEnd = deletion.value.match(/\s*$/)[0]; + + // Any whitespace that comes straight after startKeep in both the old and + // new texts, assign to startKeep and remove from the deletion. + var newWsStart = longestCommonPrefix(newWsFull, delWsStart); + deletion.value = removePrefix(deletion.value, newWsStart); + + // Any whitespace that comes straight before endKeep in both the old and + // new texts, and hasn't already been assigned to startKeep, assign to + // endKeep and remove from the deletion. + var newWsEnd = longestCommonSuffix(removePrefix(newWsFull, newWsStart), delWsEnd); + deletion.value = removeSuffix(deletion.value, newWsEnd); + endKeep.value = replacePrefix(endKeep.value, newWsFull, newWsEnd); + + // If there's any whitespace from the new text that HASN'T already been + // assigned, assign it to the start: + startKeep.value = replaceSuffix(startKeep.value, newWsFull, newWsFull.slice(0, newWsFull.length - newWsEnd.length)); + } else if (endKeep) { + // We are at the start of the text. Preserve all the whitespace on + // endKeep, and just remove whitespace from the end of deletion to the + // extent that it overlaps with the start of endKeep. + var endKeepWsPrefix = endKeep.value.match(/^\s*/)[0]; + var deletionWsSuffix = deletion.value.match(/\s*$/)[0]; + var overlap = maximumOverlap(deletionWsSuffix, endKeepWsPrefix); + deletion.value = removeSuffix(deletion.value, overlap); + } else if (startKeep) { + // We are at the END of the text. Preserve all the whitespace on + // startKeep, and just remove whitespace from the start of deletion to + // the extent that it overlaps with the end of startKeep. + var startKeepWsSuffix = startKeep.value.match(/\s*$/)[0]; + var deletionWsPrefix = deletion.value.match(/^\s*/)[0]; + var _overlap = maximumOverlap(startKeepWsSuffix, deletionWsPrefix); + deletion.value = removePrefix(deletion.value, _overlap); + } + } + var wordWithSpaceDiff = new Diff(); + wordWithSpaceDiff.tokenize = function (value) { + // Slightly different to the tokenizeIncludingWhitespace regex used above in + // that this one treats each individual newline as a distinct tokens, rather + // than merging them into other surrounding whitespace. This was requested + // in https://github.com/kpdecker/jsdiff/issues/180 & + // https://github.com/kpdecker/jsdiff/issues/211 + var regex = new RegExp("(\\r?\\n)|[".concat(extendedWordChars, "]+|[^\\S\\n\\r]+|[^").concat(extendedWordChars, "]"), 'ug'); + return value.match(regex) || []; + }; + function diffWordsWithSpace(oldStr, newStr, options) { + return wordWithSpaceDiff.diff(oldStr, newStr, options); + } + + function generateOptions(options, defaults) { + if (typeof options === 'function') { + defaults.callback = options; + } else if (options) { + for (var name in options) { + /* istanbul ignore else */ + if (options.hasOwnProperty(name)) { + defaults[name] = options[name]; + } + } + } + return defaults; + } + + var lineDiff = new Diff(); + lineDiff.tokenize = function (value, options) { + if (options.stripTrailingCr) { + // remove one \r before \n to match GNU diff's --strip-trailing-cr behavior + value = value.replace(/\r\n/g, '\n'); + } + var retLines = [], + linesAndNewlines = value.split(/(\n|\r\n)/); + + // Ignore the final empty token that occurs if the string ends with a new line + if (!linesAndNewlines[linesAndNewlines.length - 1]) { + linesAndNewlines.pop(); + } + + // Merge the content and line separators into single tokens + for (var i = 0; i < linesAndNewlines.length; i++) { + var line = linesAndNewlines[i]; + if (i % 2 && !options.newlineIsToken) { + retLines[retLines.length - 1] += line; + } else { + retLines.push(line); + } + } + return retLines; + }; + lineDiff.equals = function (left, right, options) { + // If we're ignoring whitespace, we need to normalise lines by stripping + // whitespace before checking equality. (This has an annoying interaction + // with newlineIsToken that requires special handling: if newlines get their + // own token, then we DON'T want to trim the *newline* tokens down to empty + // strings, since this would cause us to treat whitespace-only line content + // as equal to a separator between lines, which would be weird and + // inconsistent with the documented behavior of the options.) + if (options.ignoreWhitespace) { + if (!options.newlineIsToken || !left.includes('\n')) { + left = left.trim(); + } + if (!options.newlineIsToken || !right.includes('\n')) { + right = right.trim(); + } + } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) { + if (left.endsWith('\n')) { + left = left.slice(0, -1); + } + if (right.endsWith('\n')) { + right = right.slice(0, -1); + } + } + return Diff.prototype.equals.call(this, left, right, options); + }; + function diffLines(oldStr, newStr, callback) { + return lineDiff.diff(oldStr, newStr, callback); + } + + // Kept for backwards compatibility. This is a rather arbitrary wrapper method + // that just calls `diffLines` with `ignoreWhitespace: true`. It's confusing to + // have two ways to do exactly the same thing in the API, so we no longer + // document this one (library users should explicitly use `diffLines` with + // `ignoreWhitespace: true` instead) but we keep it around to maintain + // compatibility with code that used old versions. + function diffTrimmedLines(oldStr, newStr, callback) { + var options = generateOptions(callback, { + ignoreWhitespace: true + }); + return lineDiff.diff(oldStr, newStr, options); + } + + var sentenceDiff = new Diff(); + sentenceDiff.tokenize = function (value) { + return value.split(/(\S.+?[.!?])(?=\s+|$)/); + }; + function diffSentences(oldStr, newStr, callback) { + return sentenceDiff.diff(oldStr, newStr, callback); + } + + var cssDiff = new Diff(); + cssDiff.tokenize = function (value) { + return value.split(/([{}:;,]|\s+)/); + }; + function diffCss(oldStr, newStr, callback) { + return cssDiff.diff(oldStr, newStr, callback); + } + + function ownKeys(e, r) { + var t = Object.keys(e); + if (Object.getOwnPropertySymbols) { + var o = Object.getOwnPropertySymbols(e); + r && (o = o.filter(function (r) { + return Object.getOwnPropertyDescriptor(e, r).enumerable; + })), t.push.apply(t, o); + } + return t; + } + function _objectSpread2(e) { + for (var r = 1; r < arguments.length; r++) { + var t = null != arguments[r] ? arguments[r] : {}; + r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { + _defineProperty(e, r, t[r]); + }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { + Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); + }); + } + return e; + } + function _toPrimitive(t, r) { + if ("object" != typeof t || !t) return t; + var e = t[Symbol.toPrimitive]; + if (void 0 !== e) { + var i = e.call(t, r || "default"); + if ("object" != typeof i) return i; + throw new TypeError("@@toPrimitive must return a primitive value."); + } + return ("string" === r ? String : Number)(t); + } + function _toPropertyKey(t) { + var i = _toPrimitive(t, "string"); + return "symbol" == typeof i ? i : i + ""; + } + function _typeof(o) { + "@babel/helpers - typeof"; + + return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { + return typeof o; + } : function (o) { + return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; + }, _typeof(o); + } + function _defineProperty(obj, key, value) { + key = _toPropertyKey(key); + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + return obj; + } + function _toConsumableArray(arr) { + return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); + } + function _arrayWithoutHoles(arr) { + if (Array.isArray(arr)) return _arrayLikeToArray(arr); + } + function _iterableToArray(iter) { + if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); + } + function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(o); + if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); + } + function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; + return arr2; + } + function _nonIterableSpread() { + throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + + var jsonDiff = new Diff(); + // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a + // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: + jsonDiff.useLongestToken = true; + jsonDiff.tokenize = lineDiff.tokenize; + jsonDiff.castInput = function (value, options) { + var undefinedReplacement = options.undefinedReplacement, + _options$stringifyRep = options.stringifyReplacer, + stringifyReplacer = _options$stringifyRep === void 0 ? function (k, v) { + return typeof v === 'undefined' ? undefinedReplacement : v; + } : _options$stringifyRep; + return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' '); + }; + jsonDiff.equals = function (left, right, options) { + return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options); + }; + function diffJson(oldObj, newObj, options) { + return jsonDiff.diff(oldObj, newObj, options); + } + + // This function handles the presence of circular references by bailing out when encountering an + // object that is already on the "stack" of items being processed. Accepts an optional replacer + function canonicalize(obj, stack, replacementStack, replacer, key) { + stack = stack || []; + replacementStack = replacementStack || []; + if (replacer) { + obj = replacer(key, obj); + } + var i; + for (i = 0; i < stack.length; i += 1) { + if (stack[i] === obj) { + return replacementStack[i]; + } + } + var canonicalizedObj; + if ('[object Array]' === Object.prototype.toString.call(obj)) { + stack.push(obj); + canonicalizedObj = new Array(obj.length); + replacementStack.push(canonicalizedObj); + for (i = 0; i < obj.length; i += 1) { + canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key); + } + stack.pop(); + replacementStack.pop(); + return canonicalizedObj; + } + if (obj && obj.toJSON) { + obj = obj.toJSON(); + } + if (_typeof(obj) === 'object' && obj !== null) { + stack.push(obj); + canonicalizedObj = {}; + replacementStack.push(canonicalizedObj); + var sortedKeys = [], + _key; + for (_key in obj) { + /* istanbul ignore else */ + if (Object.prototype.hasOwnProperty.call(obj, _key)) { + sortedKeys.push(_key); + } + } + sortedKeys.sort(); + for (i = 0; i < sortedKeys.length; i += 1) { + _key = sortedKeys[i]; + canonicalizedObj[_key] = canonicalize(obj[_key], stack, replacementStack, replacer, _key); + } + stack.pop(); + replacementStack.pop(); + } else { + canonicalizedObj = obj; + } + return canonicalizedObj; + } + + var arrayDiff = new Diff(); + arrayDiff.tokenize = function (value) { + return value.slice(); + }; + arrayDiff.join = arrayDiff.removeEmpty = function (value) { + return value; + }; + function diffArrays(oldArr, newArr, callback) { + return arrayDiff.diff(oldArr, newArr, callback); + } + + function unixToWin(patch) { + if (Array.isArray(patch)) { + return patch.map(unixToWin); + } + return _objectSpread2(_objectSpread2({}, patch), {}, { + hunks: patch.hunks.map(function (hunk) { + return _objectSpread2(_objectSpread2({}, hunk), {}, { + lines: hunk.lines.map(function (line, i) { + var _hunk$lines; + return line.startsWith('\\') || line.endsWith('\r') || (_hunk$lines = hunk.lines[i + 1]) !== null && _hunk$lines !== void 0 && _hunk$lines.startsWith('\\') ? line : line + '\r'; + }) + }); + }) + }); + } + function winToUnix(patch) { + if (Array.isArray(patch)) { + return patch.map(winToUnix); + } + return _objectSpread2(_objectSpread2({}, patch), {}, { + hunks: patch.hunks.map(function (hunk) { + return _objectSpread2(_objectSpread2({}, hunk), {}, { + lines: hunk.lines.map(function (line) { + return line.endsWith('\r') ? line.substring(0, line.length - 1) : line; + }) + }); + }) + }); + } + + /** + * Returns true if the patch consistently uses Unix line endings (or only involves one line and has + * no line endings). + */ + function isUnix(patch) { + if (!Array.isArray(patch)) { + patch = [patch]; + } + return !patch.some(function (index) { + return index.hunks.some(function (hunk) { + return hunk.lines.some(function (line) { + return !line.startsWith('\\') && line.endsWith('\r'); + }); + }); + }); + } + + /** + * Returns true if the patch uses Windows line endings and only Windows line endings. + */ + function isWin(patch) { + if (!Array.isArray(patch)) { + patch = [patch]; + } + return patch.some(function (index) { + return index.hunks.some(function (hunk) { + return hunk.lines.some(function (line) { + return line.endsWith('\r'); + }); + }); + }) && patch.every(function (index) { + return index.hunks.every(function (hunk) { + return hunk.lines.every(function (line, i) { + var _hunk$lines2; + return line.startsWith('\\') || line.endsWith('\r') || ((_hunk$lines2 = hunk.lines[i + 1]) === null || _hunk$lines2 === void 0 ? void 0 : _hunk$lines2.startsWith('\\')); + }); + }); + }); + } + + function parsePatch(uniDiff) { + var diffstr = uniDiff.split(/\n/), + list = [], + i = 0; + function parseIndex() { + var index = {}; + list.push(index); + + // Parse diff metadata + while (i < diffstr.length) { + var line = diffstr[i]; + + // File header found, end parsing diff metadata + if (/^(\-\-\-|\+\+\+|@@)\s/.test(line)) { + break; + } + + // Diff index + var header = /^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/.exec(line); + if (header) { + index.index = header[1]; + } + i++; + } + + // Parse file headers if they are defined. Unified diff requires them, but + // there's no technical issues to have an isolated hunk without file header + parseFileHeader(index); + parseFileHeader(index); + + // Parse hunks + index.hunks = []; + while (i < diffstr.length) { + var _line = diffstr[i]; + if (/^(Index:\s|diff\s|\-\-\-\s|\+\+\+\s|===================================================================)/.test(_line)) { + break; + } else if (/^@@/.test(_line)) { + index.hunks.push(parseHunk()); + } else if (_line) { + throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(_line)); + } else { + i++; + } + } + } + + // Parses the --- and +++ headers, if none are found, no lines + // are consumed. + function parseFileHeader(index) { + var fileHeader = /^(---|\+\+\+)\s+(.*)\r?$/.exec(diffstr[i]); + if (fileHeader) { + var keyPrefix = fileHeader[1] === '---' ? 'old' : 'new'; + var data = fileHeader[2].split('\t', 2); + var fileName = data[0].replace(/\\\\/g, '\\'); + if (/^".*"$/.test(fileName)) { + fileName = fileName.substr(1, fileName.length - 2); + } + index[keyPrefix + 'FileName'] = fileName; + index[keyPrefix + 'Header'] = (data[1] || '').trim(); + i++; + } + } + + // Parses a hunk + // This assumes that we are at the start of a hunk. + function parseHunk() { + var chunkHeaderIndex = i, + chunkHeaderLine = diffstr[i++], + chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/); + var hunk = { + oldStart: +chunkHeader[1], + oldLines: typeof chunkHeader[2] === 'undefined' ? 1 : +chunkHeader[2], + newStart: +chunkHeader[3], + newLines: typeof chunkHeader[4] === 'undefined' ? 1 : +chunkHeader[4], + lines: [] + }; + + // Unified Diff Format quirk: If the chunk size is 0, + // the first number is one lower than one would expect. + // https://www.artima.com/weblogs/viewpost.jsp?thread=164293 + if (hunk.oldLines === 0) { + hunk.oldStart += 1; + } + if (hunk.newLines === 0) { + hunk.newStart += 1; + } + var addCount = 0, + removeCount = 0; + for (; i < diffstr.length && (removeCount < hunk.oldLines || addCount < hunk.newLines || (_diffstr$i = diffstr[i]) !== null && _diffstr$i !== void 0 && _diffstr$i.startsWith('\\')); i++) { + var _diffstr$i; + var operation = diffstr[i].length == 0 && i != diffstr.length - 1 ? ' ' : diffstr[i][0]; + if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') { + hunk.lines.push(diffstr[i]); + if (operation === '+') { + addCount++; + } else if (operation === '-') { + removeCount++; + } else if (operation === ' ') { + addCount++; + removeCount++; + } + } else { + throw new Error("Hunk at line ".concat(chunkHeaderIndex + 1, " contained invalid line ").concat(diffstr[i])); + } + } + + // Handle the empty block count case + if (!addCount && hunk.newLines === 1) { + hunk.newLines = 0; + } + if (!removeCount && hunk.oldLines === 1) { + hunk.oldLines = 0; + } + + // Perform sanity checking + if (addCount !== hunk.newLines) { + throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1)); + } + if (removeCount !== hunk.oldLines) { + throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1)); + } + return hunk; + } + while (i < diffstr.length) { + parseIndex(); + } + return list; + } + + // Iterator that traverses in the range of [min, max], stepping + // by distance from a given start position. I.e. for [0, 4], with + // start of 2, this will iterate 2, 3, 1, 4, 0. + function distanceIterator (start, minLine, maxLine) { + var wantForward = true, + backwardExhausted = false, + forwardExhausted = false, + localOffset = 1; + return function iterator() { + if (wantForward && !forwardExhausted) { + if (backwardExhausted) { + localOffset++; + } else { + wantForward = false; + } + + // Check if trying to fit beyond text length, and if not, check it fits + // after offset location (or desired location on first iteration) + if (start + localOffset <= maxLine) { + return start + localOffset; + } + forwardExhausted = true; + } + if (!backwardExhausted) { + if (!forwardExhausted) { + wantForward = true; + } + + // Check if trying to fit before text beginning, and if not, check it fits + // before offset location + if (minLine <= start - localOffset) { + return start - localOffset++; + } + backwardExhausted = true; + return iterator(); + } + + // We tried to fit hunk before text beginning and beyond text length, then + // hunk can't fit on the text. Return undefined + }; + } + + function applyPatch(source, uniDiff) { + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + if (typeof uniDiff === 'string') { + uniDiff = parsePatch(uniDiff); + } + if (Array.isArray(uniDiff)) { + if (uniDiff.length > 1) { + throw new Error('applyPatch only works with a single input.'); + } + uniDiff = uniDiff[0]; + } + if (options.autoConvertLineEndings || options.autoConvertLineEndings == null) { + if (hasOnlyWinLineEndings(source) && isUnix(uniDiff)) { + uniDiff = unixToWin(uniDiff); + } else if (hasOnlyUnixLineEndings(source) && isWin(uniDiff)) { + uniDiff = winToUnix(uniDiff); + } + } + + // Apply the diff to the input + var lines = source.split('\n'), + hunks = uniDiff.hunks, + compareLine = options.compareLine || function (lineNumber, line, operation, patchContent) { + return line === patchContent; + }, + fuzzFactor = options.fuzzFactor || 0, + minLine = 0; + if (fuzzFactor < 0 || !Number.isInteger(fuzzFactor)) { + throw new Error('fuzzFactor must be a non-negative integer'); + } + + // Special case for empty patch. + if (!hunks.length) { + return source; + } + + // Before anything else, handle EOFNL insertion/removal. If the patch tells us to make a change + // to the EOFNL that is redundant/impossible - i.e. to remove a newline that's not there, or add a + // newline that already exists - then we either return false and fail to apply the patch (if + // fuzzFactor is 0) or simply ignore the problem and do nothing (if fuzzFactor is >0). + // If we do need to remove/add a newline at EOF, this will always be in the final hunk: + var prevLine = '', + removeEOFNL = false, + addEOFNL = false; + for (var i = 0; i < hunks[hunks.length - 1].lines.length; i++) { + var line = hunks[hunks.length - 1].lines[i]; + if (line[0] == '\\') { + if (prevLine[0] == '+') { + removeEOFNL = true; + } else if (prevLine[0] == '-') { + addEOFNL = true; + } + } + prevLine = line; + } + if (removeEOFNL) { + if (addEOFNL) { + // This means the final line gets changed but doesn't have a trailing newline in either the + // original or patched version. In that case, we do nothing if fuzzFactor > 0, and if + // fuzzFactor is 0, we simply validate that the source file has no trailing newline. + if (!fuzzFactor && lines[lines.length - 1] == '') { + return false; + } + } else if (lines[lines.length - 1] == '') { + lines.pop(); + } else if (!fuzzFactor) { + return false; + } + } else if (addEOFNL) { + if (lines[lines.length - 1] != '') { + lines.push(''); + } else if (!fuzzFactor) { + return false; + } + } + + /** + * Checks if the hunk can be made to fit at the provided location with at most `maxErrors` + * insertions, substitutions, or deletions, while ensuring also that: + * - lines deleted in the hunk match exactly, and + * - wherever an insertion operation or block of insertion operations appears in the hunk, the + * immediately preceding and following lines of context match exactly + * + * `toPos` should be set such that lines[toPos] is meant to match hunkLines[0]. + * + * If the hunk can be applied, returns an object with properties `oldLineLastI` and + * `replacementLines`. Otherwise, returns null. + */ + function applyHunk(hunkLines, toPos, maxErrors) { + var hunkLinesI = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; + var lastContextLineMatched = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; + var patchedLines = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : []; + var patchedLinesLength = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : 0; + var nConsecutiveOldContextLines = 0; + var nextContextLineMustMatch = false; + for (; hunkLinesI < hunkLines.length; hunkLinesI++) { + var hunkLine = hunkLines[hunkLinesI], + operation = hunkLine.length > 0 ? hunkLine[0] : ' ', + content = hunkLine.length > 0 ? hunkLine.substr(1) : hunkLine; + if (operation === '-') { + if (compareLine(toPos + 1, lines[toPos], operation, content)) { + toPos++; + nConsecutiveOldContextLines = 0; + } else { + if (!maxErrors || lines[toPos] == null) { + return null; + } + patchedLines[patchedLinesLength] = lines[toPos]; + return applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI, false, patchedLines, patchedLinesLength + 1); + } + } + if (operation === '+') { + if (!lastContextLineMatched) { + return null; + } + patchedLines[patchedLinesLength] = content; + patchedLinesLength++; + nConsecutiveOldContextLines = 0; + nextContextLineMustMatch = true; + } + if (operation === ' ') { + nConsecutiveOldContextLines++; + patchedLines[patchedLinesLength] = lines[toPos]; + if (compareLine(toPos + 1, lines[toPos], operation, content)) { + patchedLinesLength++; + lastContextLineMatched = true; + nextContextLineMustMatch = false; + toPos++; + } else { + if (nextContextLineMustMatch || !maxErrors) { + return null; + } + + // Consider 3 possibilities in sequence: + // 1. lines contains a *substitution* not included in the patch context, or + // 2. lines contains an *insertion* not included in the patch context, or + // 3. lines contains a *deletion* not included in the patch context + // The first two options are of course only possible if the line from lines is non-null - + // i.e. only option 3 is possible if we've overrun the end of the old file. + return lines[toPos] && (applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI + 1, false, patchedLines, patchedLinesLength + 1) || applyHunk(hunkLines, toPos + 1, maxErrors - 1, hunkLinesI, false, patchedLines, patchedLinesLength + 1)) || applyHunk(hunkLines, toPos, maxErrors - 1, hunkLinesI + 1, false, patchedLines, patchedLinesLength); + } + } + } + + // Before returning, trim any unmodified context lines off the end of patchedLines and reduce + // toPos (and thus oldLineLastI) accordingly. This allows later hunks to be applied to a region + // that starts in this hunk's trailing context. + patchedLinesLength -= nConsecutiveOldContextLines; + toPos -= nConsecutiveOldContextLines; + patchedLines.length = patchedLinesLength; + return { + patchedLines: patchedLines, + oldLineLastI: toPos - 1 + }; + } + var resultLines = []; + + // Search best fit offsets for each hunk based on the previous ones + var prevHunkOffset = 0; + for (var _i = 0; _i < hunks.length; _i++) { + var hunk = hunks[_i]; + var hunkResult = void 0; + var maxLine = lines.length - hunk.oldLines + fuzzFactor; + var toPos = void 0; + for (var maxErrors = 0; maxErrors <= fuzzFactor; maxErrors++) { + toPos = hunk.oldStart + prevHunkOffset - 1; + var iterator = distanceIterator(toPos, minLine, maxLine); + for (; toPos !== undefined; toPos = iterator()) { + hunkResult = applyHunk(hunk.lines, toPos, maxErrors); + if (hunkResult) { + break; + } + } + if (hunkResult) { + break; + } + } + if (!hunkResult) { + return false; + } + + // Copy everything from the end of where we applied the last hunk to the start of this hunk + for (var _i2 = minLine; _i2 < toPos; _i2++) { + resultLines.push(lines[_i2]); + } + + // Add the lines produced by applying the hunk: + for (var _i3 = 0; _i3 < hunkResult.patchedLines.length; _i3++) { + var _line = hunkResult.patchedLines[_i3]; + resultLines.push(_line); + } + + // Set lower text limit to end of the current hunk, so next ones don't try + // to fit over already patched text + minLine = hunkResult.oldLineLastI + 1; + + // Note the offset between where the patch said the hunk should've applied and where we + // applied it, so we can adjust future hunks accordingly: + prevHunkOffset = toPos + 1 - hunk.oldStart; + } + + // Copy over the rest of the lines from the old text + for (var _i4 = minLine; _i4 < lines.length; _i4++) { + resultLines.push(lines[_i4]); + } + return resultLines.join('\n'); + } + + // Wrapper that supports multiple file patches via callbacks. + function applyPatches(uniDiff, options) { + if (typeof uniDiff === 'string') { + uniDiff = parsePatch(uniDiff); + } + var currentIndex = 0; + function processIndex() { + var index = uniDiff[currentIndex++]; + if (!index) { + return options.complete(); + } + options.loadFile(index, function (err, data) { + if (err) { + return options.complete(err); + } + var updatedContent = applyPatch(data, index, options); + options.patched(index, updatedContent, function (err) { + if (err) { + return options.complete(err); + } + processIndex(); + }); + }); + } + processIndex(); + } + + function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { + if (!options) { + options = {}; + } + if (typeof options === 'function') { + options = { + callback: options + }; + } + if (typeof options.context === 'undefined') { + options.context = 4; + } + if (options.newlineIsToken) { + throw new Error('newlineIsToken may not be used with patch-generation functions, only with diffing functions'); + } + if (!options.callback) { + return diffLinesResultToPatch(diffLines(oldStr, newStr, options)); + } else { + var _options = options, + _callback = _options.callback; + diffLines(oldStr, newStr, _objectSpread2(_objectSpread2({}, options), {}, { + callback: function callback(diff) { + var patch = diffLinesResultToPatch(diff); + _callback(patch); + } + })); + } + function diffLinesResultToPatch(diff) { + // STEP 1: Build up the patch with no "\ No newline at end of file" lines and with the arrays + // of lines containing trailing newline characters. We'll tidy up later... + + if (!diff) { + return; + } + diff.push({ + value: '', + lines: [] + }); // Append an empty value to make cleanup easier + + function contextLines(lines) { + return lines.map(function (entry) { + return ' ' + entry; + }); + } + var hunks = []; + var oldRangeStart = 0, + newRangeStart = 0, + curRange = [], + oldLine = 1, + newLine = 1; + var _loop = function _loop() { + var current = diff[i], + lines = current.lines || splitLines(current.value); + current.lines = lines; + if (current.added || current.removed) { + var _curRange; + // If we have previous context, start with that + if (!oldRangeStart) { + var prev = diff[i - 1]; + oldRangeStart = oldLine; + newRangeStart = newLine; + if (prev) { + curRange = options.context > 0 ? contextLines(prev.lines.slice(-options.context)) : []; + oldRangeStart -= curRange.length; + newRangeStart -= curRange.length; + } + } + + // Output our changes + (_curRange = curRange).push.apply(_curRange, _toConsumableArray(lines.map(function (entry) { + return (current.added ? '+' : '-') + entry; + }))); + + // Track the updated file position + if (current.added) { + newLine += lines.length; + } else { + oldLine += lines.length; + } + } else { + // Identical context lines. Track line changes + if (oldRangeStart) { + // Close out any changes that have been output (or join overlapping) + if (lines.length <= options.context * 2 && i < diff.length - 2) { + var _curRange2; + // Overlapping + (_curRange2 = curRange).push.apply(_curRange2, _toConsumableArray(contextLines(lines))); + } else { + var _curRange3; + // end the range and output + var contextSize = Math.min(lines.length, options.context); + (_curRange3 = curRange).push.apply(_curRange3, _toConsumableArray(contextLines(lines.slice(0, contextSize)))); + var _hunk = { + oldStart: oldRangeStart, + oldLines: oldLine - oldRangeStart + contextSize, + newStart: newRangeStart, + newLines: newLine - newRangeStart + contextSize, + lines: curRange + }; + hunks.push(_hunk); + oldRangeStart = 0; + newRangeStart = 0; + curRange = []; + } + } + oldLine += lines.length; + newLine += lines.length; + } + }; + for (var i = 0; i < diff.length; i++) { + _loop(); + } + + // Step 2: eliminate the trailing `\n` from each line of each hunk, and, where needed, add + // "\ No newline at end of file". + for (var _i = 0, _hunks = hunks; _i < _hunks.length; _i++) { + var hunk = _hunks[_i]; + for (var _i2 = 0; _i2 < hunk.lines.length; _i2++) { + if (hunk.lines[_i2].endsWith('\n')) { + hunk.lines[_i2] = hunk.lines[_i2].slice(0, -1); + } else { + hunk.lines.splice(_i2 + 1, 0, '\\ No newline at end of file'); + _i2++; // Skip the line we just added, then continue iterating + } + } + } + return { + oldFileName: oldFileName, + newFileName: newFileName, + oldHeader: oldHeader, + newHeader: newHeader, + hunks: hunks + }; + } + } + function formatPatch(diff) { + if (Array.isArray(diff)) { + return diff.map(formatPatch).join('\n'); + } + var ret = []; + if (diff.oldFileName == diff.newFileName) { + ret.push('Index: ' + diff.oldFileName); + } + ret.push('==================================================================='); + ret.push('--- ' + diff.oldFileName + (typeof diff.oldHeader === 'undefined' ? '' : '\t' + diff.oldHeader)); + ret.push('+++ ' + diff.newFileName + (typeof diff.newHeader === 'undefined' ? '' : '\t' + diff.newHeader)); + for (var i = 0; i < diff.hunks.length; i++) { + var hunk = diff.hunks[i]; + // Unified Diff Format quirk: If the chunk size is 0, + // the first number is one lower than one would expect. + // https://www.artima.com/weblogs/viewpost.jsp?thread=164293 + if (hunk.oldLines === 0) { + hunk.oldStart -= 1; + } + if (hunk.newLines === 0) { + hunk.newStart -= 1; + } + ret.push('@@ -' + hunk.oldStart + ',' + hunk.oldLines + ' +' + hunk.newStart + ',' + hunk.newLines + ' @@'); + ret.push.apply(ret, hunk.lines); + } + return ret.join('\n') + '\n'; + } + function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { + var _options2; + if (typeof options === 'function') { + options = { + callback: options + }; + } + if (!((_options2 = options) !== null && _options2 !== void 0 && _options2.callback)) { + var patchObj = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options); + if (!patchObj) { + return; + } + return formatPatch(patchObj); + } else { + var _options3 = options, + _callback2 = _options3.callback; + structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, _objectSpread2(_objectSpread2({}, options), {}, { + callback: function callback(patchObj) { + if (!patchObj) { + _callback2(); + } else { + _callback2(formatPatch(patchObj)); + } + } + })); + } + } + function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) { + return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options); + } + + /** + * Split `text` into an array of lines, including the trailing newline character (where present) + */ + function splitLines(text) { + var hasTrailingNl = text.endsWith('\n'); + var result = text.split('\n').map(function (line) { + return line + '\n'; + }); + if (hasTrailingNl) { + result.pop(); + } else { + result.push(result.pop().slice(0, -1)); + } + return result; + } + + function arrayEqual(a, b) { + if (a.length !== b.length) { + return false; + } + return arrayStartsWith(a, b); + } + function arrayStartsWith(array, start) { + if (start.length > array.length) { + return false; + } + for (var i = 0; i < start.length; i++) { + if (start[i] !== array[i]) { + return false; + } + } + return true; + } + + function calcLineCount(hunk) { + var _calcOldNewLineCount = calcOldNewLineCount(hunk.lines), + oldLines = _calcOldNewLineCount.oldLines, + newLines = _calcOldNewLineCount.newLines; + if (oldLines !== undefined) { + hunk.oldLines = oldLines; + } else { + delete hunk.oldLines; + } + if (newLines !== undefined) { + hunk.newLines = newLines; + } else { + delete hunk.newLines; + } + } + function merge(mine, theirs, base) { + mine = loadPatch(mine, base); + theirs = loadPatch(theirs, base); + var ret = {}; + + // For index we just let it pass through as it doesn't have any necessary meaning. + // Leaving sanity checks on this to the API consumer that may know more about the + // meaning in their own context. + if (mine.index || theirs.index) { + ret.index = mine.index || theirs.index; + } + if (mine.newFileName || theirs.newFileName) { + if (!fileNameChanged(mine)) { + // No header or no change in ours, use theirs (and ours if theirs does not exist) + ret.oldFileName = theirs.oldFileName || mine.oldFileName; + ret.newFileName = theirs.newFileName || mine.newFileName; + ret.oldHeader = theirs.oldHeader || mine.oldHeader; + ret.newHeader = theirs.newHeader || mine.newHeader; + } else if (!fileNameChanged(theirs)) { + // No header or no change in theirs, use ours + ret.oldFileName = mine.oldFileName; + ret.newFileName = mine.newFileName; + ret.oldHeader = mine.oldHeader; + ret.newHeader = mine.newHeader; + } else { + // Both changed... figure it out + ret.oldFileName = selectField(ret, mine.oldFileName, theirs.oldFileName); + ret.newFileName = selectField(ret, mine.newFileName, theirs.newFileName); + ret.oldHeader = selectField(ret, mine.oldHeader, theirs.oldHeader); + ret.newHeader = selectField(ret, mine.newHeader, theirs.newHeader); + } + } + ret.hunks = []; + var mineIndex = 0, + theirsIndex = 0, + mineOffset = 0, + theirsOffset = 0; + while (mineIndex < mine.hunks.length || theirsIndex < theirs.hunks.length) { + var mineCurrent = mine.hunks[mineIndex] || { + oldStart: Infinity + }, + theirsCurrent = theirs.hunks[theirsIndex] || { + oldStart: Infinity + }; + if (hunkBefore(mineCurrent, theirsCurrent)) { + // This patch does not overlap with any of the others, yay. + ret.hunks.push(cloneHunk(mineCurrent, mineOffset)); + mineIndex++; + theirsOffset += mineCurrent.newLines - mineCurrent.oldLines; + } else if (hunkBefore(theirsCurrent, mineCurrent)) { + // This patch does not overlap with any of the others, yay. + ret.hunks.push(cloneHunk(theirsCurrent, theirsOffset)); + theirsIndex++; + mineOffset += theirsCurrent.newLines - theirsCurrent.oldLines; + } else { + // Overlap, merge as best we can + var mergedHunk = { + oldStart: Math.min(mineCurrent.oldStart, theirsCurrent.oldStart), + oldLines: 0, + newStart: Math.min(mineCurrent.newStart + mineOffset, theirsCurrent.oldStart + theirsOffset), + newLines: 0, + lines: [] + }; + mergeLines(mergedHunk, mineCurrent.oldStart, mineCurrent.lines, theirsCurrent.oldStart, theirsCurrent.lines); + theirsIndex++; + mineIndex++; + ret.hunks.push(mergedHunk); + } + } + return ret; + } + function loadPatch(param, base) { + if (typeof param === 'string') { + if (/^@@/m.test(param) || /^Index:/m.test(param)) { + return parsePatch(param)[0]; + } + if (!base) { + throw new Error('Must provide a base reference or pass in a patch'); + } + return structuredPatch(undefined, undefined, base, param); + } + return param; + } + function fileNameChanged(patch) { + return patch.newFileName && patch.newFileName !== patch.oldFileName; + } + function selectField(index, mine, theirs) { + if (mine === theirs) { + return mine; + } else { + index.conflict = true; + return { + mine: mine, + theirs: theirs + }; + } + } + function hunkBefore(test, check) { + return test.oldStart < check.oldStart && test.oldStart + test.oldLines < check.oldStart; + } + function cloneHunk(hunk, offset) { + return { + oldStart: hunk.oldStart, + oldLines: hunk.oldLines, + newStart: hunk.newStart + offset, + newLines: hunk.newLines, + lines: hunk.lines + }; + } + function mergeLines(hunk, mineOffset, mineLines, theirOffset, theirLines) { + // This will generally result in a conflicted hunk, but there are cases where the context + // is the only overlap where we can successfully merge the content here. + var mine = { + offset: mineOffset, + lines: mineLines, + index: 0 + }, + their = { + offset: theirOffset, + lines: theirLines, + index: 0 + }; + + // Handle any leading content + insertLeading(hunk, mine, their); + insertLeading(hunk, their, mine); + + // Now in the overlap content. Scan through and select the best changes from each. + while (mine.index < mine.lines.length && their.index < their.lines.length) { + var mineCurrent = mine.lines[mine.index], + theirCurrent = their.lines[their.index]; + if ((mineCurrent[0] === '-' || mineCurrent[0] === '+') && (theirCurrent[0] === '-' || theirCurrent[0] === '+')) { + // Both modified ... + mutualChange(hunk, mine, their); + } else if (mineCurrent[0] === '+' && theirCurrent[0] === ' ') { + var _hunk$lines; + // Mine inserted + (_hunk$lines = hunk.lines).push.apply(_hunk$lines, _toConsumableArray(collectChange(mine))); + } else if (theirCurrent[0] === '+' && mineCurrent[0] === ' ') { + var _hunk$lines2; + // Theirs inserted + (_hunk$lines2 = hunk.lines).push.apply(_hunk$lines2, _toConsumableArray(collectChange(their))); + } else if (mineCurrent[0] === '-' && theirCurrent[0] === ' ') { + // Mine removed or edited + removal(hunk, mine, their); + } else if (theirCurrent[0] === '-' && mineCurrent[0] === ' ') { + // Their removed or edited + removal(hunk, their, mine, true); + } else if (mineCurrent === theirCurrent) { + // Context identity + hunk.lines.push(mineCurrent); + mine.index++; + their.index++; + } else { + // Context mismatch + conflict(hunk, collectChange(mine), collectChange(their)); + } + } + + // Now push anything that may be remaining + insertTrailing(hunk, mine); + insertTrailing(hunk, their); + calcLineCount(hunk); + } + function mutualChange(hunk, mine, their) { + var myChanges = collectChange(mine), + theirChanges = collectChange(their); + if (allRemoves(myChanges) && allRemoves(theirChanges)) { + // Special case for remove changes that are supersets of one another + if (arrayStartsWith(myChanges, theirChanges) && skipRemoveSuperset(their, myChanges, myChanges.length - theirChanges.length)) { + var _hunk$lines3; + (_hunk$lines3 = hunk.lines).push.apply(_hunk$lines3, _toConsumableArray(myChanges)); + return; + } else if (arrayStartsWith(theirChanges, myChanges) && skipRemoveSuperset(mine, theirChanges, theirChanges.length - myChanges.length)) { + var _hunk$lines4; + (_hunk$lines4 = hunk.lines).push.apply(_hunk$lines4, _toConsumableArray(theirChanges)); + return; + } + } else if (arrayEqual(myChanges, theirChanges)) { + var _hunk$lines5; + (_hunk$lines5 = hunk.lines).push.apply(_hunk$lines5, _toConsumableArray(myChanges)); + return; + } + conflict(hunk, myChanges, theirChanges); + } + function removal(hunk, mine, their, swap) { + var myChanges = collectChange(mine), + theirChanges = collectContext(their, myChanges); + if (theirChanges.merged) { + var _hunk$lines6; + (_hunk$lines6 = hunk.lines).push.apply(_hunk$lines6, _toConsumableArray(theirChanges.merged)); + } else { + conflict(hunk, swap ? theirChanges : myChanges, swap ? myChanges : theirChanges); + } + } + function conflict(hunk, mine, their) { + hunk.conflict = true; + hunk.lines.push({ + conflict: true, + mine: mine, + theirs: their + }); + } + function insertLeading(hunk, insert, their) { + while (insert.offset < their.offset && insert.index < insert.lines.length) { + var line = insert.lines[insert.index++]; + hunk.lines.push(line); + insert.offset++; + } + } + function insertTrailing(hunk, insert) { + while (insert.index < insert.lines.length) { + var line = insert.lines[insert.index++]; + hunk.lines.push(line); + } + } + function collectChange(state) { + var ret = [], + operation = state.lines[state.index][0]; + while (state.index < state.lines.length) { + var line = state.lines[state.index]; + + // Group additions that are immediately after subtractions and treat them as one "atomic" modify change. + if (operation === '-' && line[0] === '+') { + operation = '+'; + } + if (operation === line[0]) { + ret.push(line); + state.index++; + } else { + break; + } + } + return ret; + } + function collectContext(state, matchChanges) { + var changes = [], + merged = [], + matchIndex = 0, + contextChanges = false, + conflicted = false; + while (matchIndex < matchChanges.length && state.index < state.lines.length) { + var change = state.lines[state.index], + match = matchChanges[matchIndex]; + + // Once we've hit our add, then we are done + if (match[0] === '+') { + break; + } + contextChanges = contextChanges || change[0] !== ' '; + merged.push(match); + matchIndex++; + + // Consume any additions in the other block as a conflict to attempt + // to pull in the remaining context after this + if (change[0] === '+') { + conflicted = true; + while (change[0] === '+') { + changes.push(change); + change = state.lines[++state.index]; + } + } + if (match.substr(1) === change.substr(1)) { + changes.push(change); + state.index++; + } else { + conflicted = true; + } + } + if ((matchChanges[matchIndex] || '')[0] === '+' && contextChanges) { + conflicted = true; + } + if (conflicted) { + return changes; + } + while (matchIndex < matchChanges.length) { + merged.push(matchChanges[matchIndex++]); + } + return { + merged: merged, + changes: changes + }; + } + function allRemoves(changes) { + return changes.reduce(function (prev, change) { + return prev && change[0] === '-'; + }, true); + } + function skipRemoveSuperset(state, removeChanges, delta) { + for (var i = 0; i < delta; i++) { + var changeContent = removeChanges[removeChanges.length - delta + i].substr(1); + if (state.lines[state.index + i] !== ' ' + changeContent) { + return false; + } + } + state.index += delta; + return true; + } + function calcOldNewLineCount(lines) { + var oldLines = 0; + var newLines = 0; + lines.forEach(function (line) { + if (typeof line !== 'string') { + var myCount = calcOldNewLineCount(line.mine); + var theirCount = calcOldNewLineCount(line.theirs); + if (oldLines !== undefined) { + if (myCount.oldLines === theirCount.oldLines) { + oldLines += myCount.oldLines; + } else { + oldLines = undefined; + } + } + if (newLines !== undefined) { + if (myCount.newLines === theirCount.newLines) { + newLines += myCount.newLines; + } else { + newLines = undefined; + } + } + } else { + if (newLines !== undefined && (line[0] === '+' || line[0] === ' ')) { + newLines++; + } + if (oldLines !== undefined && (line[0] === '-' || line[0] === ' ')) { + oldLines++; + } + } + }); + return { + oldLines: oldLines, + newLines: newLines + }; + } + + function reversePatch(structuredPatch) { + if (Array.isArray(structuredPatch)) { + return structuredPatch.map(reversePatch).reverse(); + } + return _objectSpread2(_objectSpread2({}, structuredPatch), {}, { + oldFileName: structuredPatch.newFileName, + oldHeader: structuredPatch.newHeader, + newFileName: structuredPatch.oldFileName, + newHeader: structuredPatch.oldHeader, + hunks: structuredPatch.hunks.map(function (hunk) { + return { + oldLines: hunk.newLines, + oldStart: hunk.newStart, + newLines: hunk.oldLines, + newStart: hunk.oldStart, + lines: hunk.lines.map(function (l) { + if (l.startsWith('-')) { + return "+".concat(l.slice(1)); + } + if (l.startsWith('+')) { + return "-".concat(l.slice(1)); + } + return l; + }) + }; + }) + }); + } + + // See: http://code.google.com/p/google-diff-match-patch/wiki/API + function convertChangesToDMP(changes) { + var ret = [], + change, + operation; + for (var i = 0; i < changes.length; i++) { + change = changes[i]; + if (change.added) { + operation = 1; + } else if (change.removed) { + operation = -1; + } else { + operation = 0; + } + ret.push([operation, change.value]); + } + return ret; + } + + function convertChangesToXML(changes) { + var ret = []; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + if (change.added) { + ret.push(''); + } else if (change.removed) { + ret.push(''); + } + ret.push(escapeHTML(change.value)); + if (change.added) { + ret.push(''); + } else if (change.removed) { + ret.push(''); + } + } + return ret.join(''); + } + function escapeHTML(s) { + var n = s; + n = n.replace(/&/g, '&'); + n = n.replace(//g, '>'); + n = n.replace(/"/g, '"'); + return n; + } + + exports.Diff = Diff; + exports.applyPatch = applyPatch; + exports.applyPatches = applyPatches; + exports.canonicalize = canonicalize; + exports.convertChangesToDMP = convertChangesToDMP; + exports.convertChangesToXML = convertChangesToXML; + exports.createPatch = createPatch; + exports.createTwoFilesPatch = createTwoFilesPatch; + exports.diffArrays = diffArrays; + exports.diffChars = diffChars; + exports.diffCss = diffCss; + exports.diffJson = diffJson; + exports.diffLines = diffLines; + exports.diffSentences = diffSentences; + exports.diffTrimmedLines = diffTrimmedLines; + exports.diffWords = diffWords; + exports.diffWordsWithSpace = diffWordsWithSpace; + exports.formatPatch = formatPatch; + exports.merge = merge; + exports.parsePatch = parsePatch; + exports.reversePatch = reversePatch; + exports.structuredPatch = structuredPatch; + +})); diff --git a/src/main/resources/static/js/compare/pdfWorker.js b/src/main/resources/static/js/compare/pdfWorker.js new file mode 100644 index 00000000000..78952bd759d --- /dev/null +++ b/src/main/resources/static/js/compare/pdfWorker.js @@ -0,0 +1,145 @@ +importScripts('./diff.js'); + +self.onmessage = async function (e) { + const { text1, text2, color1, color2 } = e.data; + console.log('Received text for comparison:', { text1, text2 }); + + const startTime = performance.now(); + + if (text1.trim() === "" || text2.trim() === "") { + self.postMessage({ status: 'error', message: 'One or both of the texts are empty.' }); + return; + } + + const words1 = text1.split(' '); + const words2 = text2.split(' '); + const MAX_WORD_COUNT = 150000; + const COMPLEX_WORD_COUNT = 50000; + const BATCH_SIZE = 5000; // Define a suitable batch size for processing + const OVERLAP_SIZE = 200; // Number of words to overlap - bigger increases accuracy but affects performance + + const isComplex = words1.length > COMPLEX_WORD_COUNT || words2.length > COMPLEX_WORD_COUNT; + const isTooLarge = words1.length > MAX_WORD_COUNT || words2.length > MAX_WORD_COUNT; + + let complexMessage = 'One or both of the provided documents are large files, accuracy of comparison may be reduced'; + let tooLargeMessage = 'One or Both of the provided documents are too large to process'; + + // Listen for messages from the main thread + self.addEventListener('message', (event) => { + if (event.data.type === 'SET_TOO_LARGE_MESSAGE') { + tooLargeMessage = event.data.message; + } + if (event.data.type === 'SET_COMPLEX_MESSAGE') { + complexMessage = event.data.message; + } + }); + + if (isTooLarge) { + self.postMessage({ + status: 'warning', + message: tooLargeMessage, + }); + return; + } else { + + if (isComplex) { + self.postMessage({ + status: 'warning', + message: complexMessage, + }); + } + // Perform diff operation depending on document size + const differences = isComplex + ? await staggeredBatchDiff(words1, words2, color1, color2, BATCH_SIZE, OVERLAP_SIZE) + : diff(words1, words2, color1, color2); + + console.log(`Diff operation took ${performance.now() - startTime} milliseconds`); + self.postMessage({ status: 'success', differences }); + } +}; + +//Splits text into smaller batches to run through diff checking algorithms. overlaps the batches to help ensure +async function staggeredBatchDiff(words1, words2, color1, color2, batchSize, overlapSize) { + const differences = []; + const totalWords1 = words1.length; + const totalWords2 = words2.length; + + let previousEnd1 = 0; // Track where the last batch ended in words1 + let previousEnd2 = 0; // Track where the last batch ended in words2 + + // Function to determine if differences are large, differences that are too large indicate potential error in batching + const isLargeDifference = (differences) => { + return differences.length > 50; + }; + + while (previousEnd1 < totalWords1 || previousEnd2 < totalWords2) { + // Define the next chunk boundaries + const start1 = previousEnd1; + const end1 = Math.min(start1 + batchSize, totalWords1); + + const start2 = previousEnd2; + const end2 = Math.min(start2 + batchSize, totalWords2); + + //If difference is too high decrease batch size for more granular check + const dynamicBatchSize = isLargeDifference(differences) ? batchSize / 2 : batchSize; + + // Adjust the size of the current chunk using dynamic batch size + const batchWords1 = words1.slice(start1, end1 + dynamicBatchSize); + const batchWords2 = words2.slice(start2, end2 + dynamicBatchSize); + + // Include overlap from the previous chunk + const overlapWords1 = previousEnd1 > 0 ? words1.slice(Math.max(0, previousEnd1 - overlapSize), previousEnd1) : []; + const overlapWords2 = previousEnd2 > 0 ? words2.slice(Math.max(0, previousEnd2 - overlapSize), previousEnd2) : []; + + // Combine overlaps and current batches for comparison + const combinedWords1 = overlapWords1.concat(batchWords1); + const combinedWords2 = overlapWords2.concat(batchWords2); + + // Perform the diff on the combined words + const batchDifferences = diff(combinedWords1, combinedWords2, color1, color2); + differences.push(...batchDifferences); + + // Update the previous end indices based on the results of this batch + previousEnd1 = end1; + previousEnd2 = end2; + } + + return differences; +} + + +// Standard diff function for small text comparisons +function diff(words1, words2, color1, color2) { + console.log(`Starting diff between ${words1.length} words and ${words2.length} words`); + const matrix = Array.from({ length: words1.length + 1 }, () => Array(words2.length + 1).fill(0)); + + for (let i = 1; i <= words1.length; i++) { + for (let j = 1; j <= words2.length; j++) { + matrix[i][j] = words1[i - 1] === words2[j - 1] + ? matrix[i - 1][j - 1] + 1 + : Math.max(matrix[i][j - 1], matrix[i - 1][j]); + } + } + return backtrack(matrix, words1, words2, color1, color2); +} + +// Backtrack function to find differences +function backtrack(matrix, words1, words2, color1, color2) { + let i = words1.length, j = words2.length; + const differences = []; + + while (i > 0 || j > 0) { + if (i > 0 && j > 0 && words1[i - 1] === words2[j - 1]) { + differences.unshift(['black', words1[i - 1]]); + i--; j--; + } else if (j > 0 && (i === 0 || matrix[i][j] === matrix[i][j - 1])) { + differences.unshift([color2, words2[j - 1]]); + j--; + } else { + differences.unshift([color1, words1[i - 1]]); + i--; + } + } + + return differences; +} diff --git a/src/main/resources/templates/addUsers.html b/src/main/resources/templates/addUsers.html index e17e165adbd..ba020c4f649 100644 --- a/src/main/resources/templates/addUsers.html +++ b/src/main/resources/templates/addUsers.html @@ -283,25 +283,5 @@