diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..54b49fd80 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,74 @@ +# Node modules and build artifacts +node_modules +frontend/node_modules +frontend/dist +frontend/build +frontend/.vite +frontend/.tauri + +# Gradle build artifacts +.gradle +build +bin +target +out + +# Git +.git +.gitignore + +# IDE +.vscode +.idea +*.iml +*.iws +*.ipr + +# Logs +*.log +logs + +# Environment files +.env +.env.* +!.env.example + +# OS files +.DS_Store +Thumbs.db + +# Java compiled files +*.class +*.jar +*.war +*.ear + +# Test reports +test-results +coverage + +# Docker +docker-compose.override.yml +.dockerignore + +# Temporary files +tmp +temp +*.tmp +*.swp +*~ + +# Runtime database and config files (locked by running app) +app/core/configs/** +stirling/** +stirling-pdf-DB*.mv.db +stirling-pdf-DB*.trace.db + +# Documentation +*.md +!README.md +docs + +# CI/CD +.github +.gitlab-ci.yml diff --git a/.github/workflows/tauri-build.yml b/.github/workflows/tauri-build.yml index d56c462d0..4c833bc16 100644 --- a/.github/workflows/tauri-build.yml +++ b/.github/workflows/tauri-build.yml @@ -17,18 +17,11 @@ on: branches: [main, V2] paths: - 'frontend/src-tauri/**' - - 'frontend/src/**' - - 'frontend/package.json' - - 'frontend/package-lock.json' + - 'frontend/src/desktop/**' + - 'frontend/tsconfig.desktop.json' - '.github/workflows/tauri-build.yml' push: branches: [main, V2] - paths: - - 'frontend/src-tauri/**' - - 'frontend/src/**' - - 'frontend/package.json' - - 'frontend/package-lock.json' - - '.github/workflows/tauri-build.yml' permissions: contents: read @@ -47,21 +40,19 @@ jobs: "windows") echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"}]}' >> $GITHUB_OUTPUT ;; - # "macos") - # echo 'matrix={"include":[{"platform":"macos-latest","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-13","args":"--target x86_64-apple-darwin","name":"macos-x86_64"}]}' >> $GITHUB_OUTPUT - # ;; + "macos") + echo 'matrix={"include":[{"platform":"macos-15","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-15-intel","args":"--target x86_64-apple-darwin","name":"macos-x86_64"}]}' >> $GITHUB_OUTPUT + ;; "linux") echo 'matrix={"include":[{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT ;; *) - echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"},{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT - # Disabled Mac builds: {"platform":"macos-latest","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-13","args":"--target x86_64-apple-darwin","name":"macos-x86_64"} + echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"},{"platform":"macos-15","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-15-intel","args":"--target x86_64-apple-darwin","name":"macos-x86_64"},{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT ;; esac else # For PR/push events, build all platforms - echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"},{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT - # Disabled Mac builds: {"platform":"macos-latest","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-13","args":"--target x86_64-apple-darwin","name":"macos-x86_64"} + echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"},{"platform":"macos-15","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-15-intel","args":"--target x86_64-apple-darwin","name":"macos-x86_64"},{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT fi build: @@ -96,7 +87,7 @@ jobs: uses: dtolnay/rust-toolchain@stable with: toolchain: stable - targets: ${{ (matrix.platform == 'macos-latest' || matrix.platform == 'macos-13') && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} + targets: ${{ (matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel') && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} @@ -183,80 +174,96 @@ jobs: working-directory: ./frontend run: npm install - # Disabled Mac builds - Import Apple Developer Certificate - # - name: Import Apple Developer Certificate - # if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-13' - # env: - # APPLE_ID: ${{ secrets.APPLE_ID }} - # APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} - # APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} - # APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - # KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} - # run: | - # echo "Importing Apple Developer Certificate..." - # echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12 - # security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain - # security default-keychain -s build.keychain - # security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain - # security set-keychain-settings -t 3600 -u build.keychain - # security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign - # security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain - # security find-identity -v -p codesigning build.keychain - # - name: Verify Certificate - # if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-13' - # run: | - # echo "Verifying Apple Developer Certificate..." - # CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application") - # echo "Certificate Info: $CERT_INFO" - # CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') - # echo "Certificate ID: $CERT_ID" - # echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV - # echo "Certificate imported." + - name: Import Apple Developer Certificate + if: matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel' + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + run: | + echo "Importing Apple Developer Certificate..." + echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12 + # Create temporary keychain + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + KEYCHAIN_PASSWORD=$(openssl rand -base64 32) + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + # Import certificate + security import certificate.p12 -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + # Clean up + rm certificate.p12 - # - name: Check DMG creation dependencies (macOS only) - # if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-13' - # run: | - # echo "🔍 Checking DMG creation dependencies on ${{ matrix.platform }}..." - # echo "hdiutil version: $(hdiutil --version || echo 'NOT FOUND')" - # echo "create-dmg availability: $(which create-dmg || echo 'NOT FOUND')" - # echo "Available disk space: $(df -h /tmp | tail -1)" - # echo "macOS version: $(sw_vers -productVersion)" - # echo "Available tools:" - # ls -la /usr/bin/hd* || echo "No hd* tools found" + - name: Verify Certificate + if: matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel' + run: | + echo "Verifying Apple Developer Certificate..." + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + CERT_INFO=$(security find-identity -v -p codesigning $KEYCHAIN_PATH | grep "Developer ID Application") + echo "Certificate Info: $CERT_INFO" + CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') + echo "Certificate ID: $CERT_ID" + echo "APPLE_SIGNING_IDENTITY=$CERT_ID" >> $GITHUB_ENV + echo "Certificate imported successfully." - - name: Build Tauri app + - name: Check DMG creation dependencies (macOS only) + if: matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel' + run: | + echo "🔍 Checking DMG creation dependencies on ${{ matrix.platform }}..." + echo "hdiutil version: $(hdiutil --version || echo 'NOT FOUND')" + echo "create-dmg availability: $(which create-dmg || echo 'NOT FOUND')" + echo "Available disk space: $(df -h /tmp | tail -1)" + echo "macOS version: $(sw_vers -productVersion)" + echo "Available tools:" + ls -la /usr/bin/hd* || echo "No hd* tools found" + + - name: Build Tauri app uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }} + APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }} APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} APPLE_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.APPIMAGETOOL_SIGN_PASSPHRASE }} SIGN: 1 - CI: true + CI: true with: projectPath: ./frontend tauriScript: npx tauri args: ${{ matrix.args }} - + + - name: Verify notarization (macOS only) + if: matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel' + run: | + echo "🔍 Verifying notarization status..." + cd ./frontend/src-tauri/target + DMG_FILE=$(find . -name "*.dmg" | head -1) + if [ -n "$DMG_FILE" ]; then + echo "Found DMG: $DMG_FILE" + echo "Checking notarization ticket..." + spctl -a -vvv -t install "$DMG_FILE" || echo "⚠️ Notarization check failed or not yet complete" + stapler validate "$DMG_FILE" || echo "⚠️ No notarization ticket attached" + else + echo "⚠️ No DMG file found to verify" + fi + - name: Rename artifacts shell: bash run: | mkdir -p ./dist cd ./frontend/src-tauri/target - + # Find and rename artifacts based on platform if [ "${{ matrix.platform }}" = "windows-latest" ]; then find . -name "*.exe" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.exe" \; find . -name "*.msi" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.msi" \; - # Disabled Mac builds - # elif [ "${{ matrix.platform }}" = "macos-latest" ] || [ "${{ matrix.platform }}" = "macos-13" ]; then - # find . -name "*.dmg" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.dmg" \; - # find . -name "*.app" -exec cp -r {} "../../../dist/Stirling-PDF-${{ matrix.name }}.app" \; + elif [ "${{ matrix.platform }}" = "macos-15" ] || [ "${{ matrix.platform }}" = "macos-15-intel" ]; then + find . -name "*.dmg" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.dmg" \; + find . -name "*.app" -exec cp -r {} "../../../dist/Stirling-PDF-${{ matrix.name }}.app" \; else find . -name "*.deb" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.deb" \; find . -name "*.AppImage" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.AppImage" \; @@ -273,7 +280,7 @@ jobs: shell: bash run: | cd ./frontend/src-tauri/target - + # Check for expected artifacts based on platform if [ "${{ matrix.platform }}" = "windows-latest" ]; then echo "Checking for Windows artifacts..." @@ -282,14 +289,13 @@ jobs: echo "❌ No Windows executable found" exit 1 fi - # Disabled Mac builds - # elif [ "${{ matrix.platform }}" = "macos-latest" ] || [ "${{ matrix.platform }}" = "macos-13" ]; then - # echo "Checking for macOS artifacts..." - # find . -name "*.dmg" -o -name "*.app" | head -5 - # if [ $(find . -name "*.dmg" -o -name "*.app" | wc -l) -eq 0 ]; then - # echo "❌ No macOS artifacts found" - # exit 1 - # fi + elif [ "${{ matrix.platform }}" = "macos-15" ] || [ "${{ matrix.platform }}" = "macos-15-intel" ]; then + echo "Checking for macOS artifacts..." + find . -name "*.dmg" -o -name "*.app" | head -5 + if [ $(find . -name "*.dmg" -o -name "*.app" | wc -l) -eq 0 ]; then + echo "❌ No macOS artifacts found" + exit 1 + fi else echo "Checking for Linux artifacts..." find . -name "*.deb" -o -name "*.AppImage" | head -5 @@ -298,7 +304,7 @@ jobs: exit 1 fi fi - + echo "✅ Build artifacts found for ${{ matrix.name }}" - name: Test artifact sizes @@ -331,4 +337,4 @@ jobs: echo "❌ Some Tauri builds failed." echo "Please check the logs and fix any issues." exit 1 - fi \ No newline at end of file + fi diff --git a/README.md b/README.md index f8058d906..211301b99 100644 --- a/README.md +++ b/README.md @@ -115,46 +115,46 @@ Stirling-PDF currently supports 40 languages! | Language | Progress | | -------------------------------------------- | -------------------------------------- | -| Arabic (العربية) (ar_AR) | ![83%](https://geps.dev/progress/83) | -| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![32%](https://geps.dev/progress/32) | -| Basque (Euskara) (eu_ES) | ![18%](https://geps.dev/progress/18) | -| Bulgarian (Български) (bg_BG) | ![35%](https://geps.dev/progress/35) | -| Catalan (Català) (ca_CA) | ![34%](https://geps.dev/progress/34) | -| Croatian (Hrvatski) (hr_HR) | ![31%](https://geps.dev/progress/31) | -| Czech (Česky) (cs_CZ) | ![34%](https://geps.dev/progress/34) | -| Danish (Dansk) (da_DK) | ![30%](https://geps.dev/progress/30) | -| Dutch (Nederlands) (nl_NL) | ![30%](https://geps.dev/progress/30) | +| Arabic (العربية) (ar_AR) | ![64%](https://geps.dev/progress/64) | +| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![24%](https://geps.dev/progress/24) | +| Basque (Euskara) (eu_ES) | ![14%](https://geps.dev/progress/14) | +| Bulgarian (Български) (bg_BG) | ![26%](https://geps.dev/progress/26) | +| Catalan (Català) (ca_CA) | ![26%](https://geps.dev/progress/26) | +| Croatian (Hrvatski) (hr_HR) | ![24%](https://geps.dev/progress/24) | +| Czech (Česky) (cs_CZ) | ![26%](https://geps.dev/progress/26) | +| Danish (Dansk) (da_DK) | ![23%](https://geps.dev/progress/23) | +| Dutch (Nederlands) (nl_NL) | ![23%](https://geps.dev/progress/23) | | 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) | ![82%](https://geps.dev/progress/82) | -| German (Deutsch) (de_DE) | ![84%](https://geps.dev/progress/84) | -| Greek (Ελληνικά) (el_GR) | ![34%](https://geps.dev/progress/34) | -| Hindi (हिंदी) (hi_IN) | ![34%](https://geps.dev/progress/34) | -| Hungarian (Magyar) (hu_HU) | ![38%](https://geps.dev/progress/38) | -| Indonesian (Bahasa Indonesia) (id_ID) | ![31%](https://geps.dev/progress/31) | -| Irish (Gaeilge) (ga_IE) | ![34%](https://geps.dev/progress/34) | -| Italian (Italiano) (it_IT) | ![84%](https://geps.dev/progress/84) | -| Japanese (日本語) (ja_JP) | ![62%](https://geps.dev/progress/62) | -| Korean (한국어) (ko_KR) | ![34%](https://geps.dev/progress/34) | -| Norwegian (Norsk) (no_NB) | ![32%](https://geps.dev/progress/32) | -| Persian (فارسی) (fa_IR) | ![34%](https://geps.dev/progress/34) | -| Polish (Polski) (pl_PL) | ![36%](https://geps.dev/progress/36) | -| Portuguese (Português) (pt_PT) | ![34%](https://geps.dev/progress/34) | -| Portuguese Brazilian (Português) (pt_BR) | ![83%](https://geps.dev/progress/83) | -| Romanian (Română) (ro_RO) | ![28%](https://geps.dev/progress/28) | -| Russian (Русский) (ru_RU) | ![83%](https://geps.dev/progress/83) | -| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![37%](https://geps.dev/progress/37) | -| Simplified Chinese (简体中文) (zh_CN) | ![85%](https://geps.dev/progress/85) | -| Slovakian (Slovensky) (sk_SK) | ![26%](https://geps.dev/progress/26) | -| Slovenian (Slovenščina) (sl_SI) | ![36%](https://geps.dev/progress/36) | -| Spanish (Español) (es_ES) | ![84%](https://geps.dev/progress/84) | -| Swedish (Svenska) (sv_SE) | ![33%](https://geps.dev/progress/33) | -| Thai (ไทย) (th_TH) | ![31%](https://geps.dev/progress/31) | +| French (Français) (fr_FR) | ![63%](https://geps.dev/progress/63) | +| German (Deutsch) (de_DE) | ![64%](https://geps.dev/progress/64) | +| Greek (Ελληνικά) (el_GR) | ![26%](https://geps.dev/progress/26) | +| Hindi (हिंदी) (hi_IN) | ![26%](https://geps.dev/progress/26) | +| Hungarian (Magyar) (hu_HU) | ![29%](https://geps.dev/progress/29) | +| Indonesian (Bahasa Indonesia) (id_ID) | ![24%](https://geps.dev/progress/24) | +| Irish (Gaeilge) (ga_IE) | ![26%](https://geps.dev/progress/26) | +| Italian (Italiano) (it_IT) | ![64%](https://geps.dev/progress/64) | +| Japanese (日本語) (ja_JP) | ![47%](https://geps.dev/progress/47) | +| Korean (한국어) (ko_KR) | ![26%](https://geps.dev/progress/26) | +| Norwegian (Norsk) (no_NB) | ![24%](https://geps.dev/progress/24) | +| Persian (فارسی) (fa_IR) | ![26%](https://geps.dev/progress/26) | +| Polish (Polski) (pl_PL) | ![27%](https://geps.dev/progress/27) | +| Portuguese (Português) (pt_PT) | ![26%](https://geps.dev/progress/26) | +| Portuguese Brazilian (Português) (pt_BR) | ![64%](https://geps.dev/progress/64) | +| Romanian (Română) (ro_RO) | ![22%](https://geps.dev/progress/22) | +| Russian (Русский) (ru_RU) | ![63%](https://geps.dev/progress/63) | +| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![28%](https://geps.dev/progress/28) | +| Simplified Chinese (简体中文) (zh_CN) | ![65%](https://geps.dev/progress/65) | +| Slovakian (Slovensky) (sk_SK) | ![19%](https://geps.dev/progress/19) | +| Slovenian (Slovenščina) (sl_SI) | ![27%](https://geps.dev/progress/27) | +| Spanish (Español) (es_ES) | ![64%](https://geps.dev/progress/64) | +| Swedish (Svenska) (sv_SE) | ![25%](https://geps.dev/progress/25) | +| Thai (ไทย) (th_TH) | ![23%](https://geps.dev/progress/23) | | Tibetan (བོད་ཡིག་) (bo_CN) | ![65%](https://geps.dev/progress/65) | -| Traditional Chinese (繁體中文) (zh_TW) | ![38%](https://geps.dev/progress/38) | -| Turkish (Türkçe) (tr_TR) | ![37%](https://geps.dev/progress/37) | -| Ukrainian (Українська) (uk_UA) | ![36%](https://geps.dev/progress/36) | -| Vietnamese (Tiếng Việt) (vi_VN) | ![28%](https://geps.dev/progress/28) | +| Traditional Chinese (繁體中文) (zh_TW) | ![29%](https://geps.dev/progress/29) | +| Turkish (Türkçe) (tr_TR) | ![28%](https://geps.dev/progress/28) | +| Ukrainian (Українська) (uk_UA) | ![28%](https://geps.dev/progress/28) | +| Vietnamese (Tiếng Việt) (vi_VN) | ![21%](https://geps.dev/progress/21) | | Malayalam (മലയാളം) (ml_IN) | ![73%](https://geps.dev/progress/73) | ## Stirling PDF Enterprise diff --git a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java index 4b5a202c0..6a6ee8453 100644 --- a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java +++ b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java @@ -505,10 +505,19 @@ public class ApplicationProperties { public static class Ui { private String appNameNavbar; private List languages; + private String logoStyle = "classic"; // Options: "classic" (default) or "modern" public String getAppNameNavbar() { return appNameNavbar != null && !appNameNavbar.trim().isEmpty() ? appNameNavbar : null; } + + public String getLogoStyle() { + // Validate and return either "modern" or "classic" + if ("modern".equalsIgnoreCase(logoStyle)) { + return "modern"; + } + return "classic"; // default + } } @Data diff --git a/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java b/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java index 23d369bf3..a0d7f3610 100644 --- a/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java +++ b/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java @@ -75,6 +75,9 @@ public class RequestUriUtils { || trimmedUri.startsWith("/api/v1/auth/login") || trimmedUri.startsWith("/api/v1/auth/refresh") || trimmedUri.startsWith("/api/v1/auth/logout") + || trimmedUri.startsWith( + "/api/v1/proprietary/ui-data/login") // Login page config (SSO providers + + // enableLogin) || trimmedUri.startsWith("/v1/api-docs") || trimmedUri.startsWith("/api/v1/invite/validate") || trimmedUri.startsWith("/api/v1/invite/accept") diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java index 02b5233b8..ffbec5a7d 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java @@ -10,6 +10,8 @@ import org.springframework.web.bind.annotation.RequestParam; import io.swagger.v3.oas.annotations.Hidden; +import lombok.extern.slf4j.Slf4j; + import stirling.software.SPDF.config.EndpointConfiguration; import stirling.software.SPDF.config.InitialSetup; import stirling.software.common.annotations.api.ConfigApi; @@ -20,6 +22,7 @@ import stirling.software.common.service.UserServiceInterface; @ConfigApi @Hidden +@Slf4j public class ConfigController { private final ApplicationProperties applicationProperties; @@ -59,9 +62,15 @@ public class ConfigController { // Extract values from ApplicationProperties configData.put("appNameNavbar", applicationProperties.getUi().getAppNameNavbar()); configData.put("languages", applicationProperties.getUi().getLanguages()); + configData.put("logoStyle", applicationProperties.getUi().getLogoStyle()); // Security settings - configData.put("enableLogin", applicationProperties.getSecurity().getEnableLogin()); + // enableLogin requires both the config flag AND proprietary features to be loaded + // If userService is null, proprietary module isn't loaded + // (DISABLE_ADDITIONAL_FEATURES=true or DOCKER_ENABLE_SECURITY=false) + boolean enableLogin = + applicationProperties.getSecurity().getEnableLogin() && userService != null; + configData.put("enableLogin", enableLogin); // Mail settings - check both SMTP enabled AND invites enabled boolean smtpEnabled = applicationProperties.getMail().isEnabled(); diff --git a/app/core/src/main/resources/settings.yml.template b/app/core/src/main/resources/settings.yml.template index b676fd3b8..5ea28f71e 100644 --- a/app/core/src/main/resources/settings.yml.template +++ b/app/core/src/main/resources/settings.yml.template @@ -11,7 +11,7 @@ ############################################################################################################# security: - enableLogin: false # set to 'true' to enable login + enableLogin: true # set to 'true' to enable login csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production) loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1 loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts @@ -193,6 +193,7 @@ stirling: ui: appNameNavbar: '' # name displayed on the navigation bar + logoStyle: classic # Options: 'classic' (default - classic S icon) or 'modern' (minimalist logo) languages: [] # If empty, all languages are enabled. To display only German and Polish ["de_DE", "pl_PL"]. British English is always enabled. endpoints: diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/ProprietaryUIDataController.java b/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/ProprietaryUIDataController.java index ed9106f2f..a5e0b8a0f 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/ProprietaryUIDataController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/ProprietaryUIDataController.java @@ -116,6 +116,10 @@ public class ProprietaryUIDataController { LoginData data = new LoginData(); Map providerList = new HashMap<>(); Security securityProps = applicationProperties.getSecurity(); + + // Add enableLogin flag so frontend doesn't need to call /app-config + data.setEnableLogin(securityProps.getEnableLogin()); + OAUTH2 oauth = securityProps.getOauth2(); if (oauth != null && oauth.getEnabled()) { @@ -448,6 +452,7 @@ public class ProprietaryUIDataController { @Data public static class LoginData { + private Boolean enableLogin; private Map providerList; private String loginMethod; private boolean altLogin; diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java index cdf7bdd0d..9d1dbc96f 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java @@ -223,7 +223,8 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { || trimmedUri.startsWith("/saml2") || trimmedUri.startsWith("/api/v1/auth/login") || trimmedUri.startsWith("/api/v1/auth/refresh") - || trimmedUri.startsWith("/api/v1/auth/logout"); + || trimmedUri.startsWith("/api/v1/auth/logout") + || trimmedUri.startsWith("/api/v1/proprietary/ui-data/login"); } private enum UserLoginType { diff --git a/build.gradle b/build.gradle index 9acb8c9f0..2451aa45c 100644 --- a/build.gradle +++ b/build.gradle @@ -57,7 +57,7 @@ repositories { allprojects { group = 'stirling.software' - version = '1.4.0' + version = '2.0.0' configurations.configureEach { exclude group: 'commons-logging', module: 'commons-logging' diff --git a/docker/unified/nginx.conf b/docker/unified/nginx.conf index 77ee17f89..1e47b8619 100644 --- a/docker/unified/nginx.conf +++ b/docker/unified/nginx.conf @@ -1,3 +1,6 @@ +# Run nginx as non-root user +pid /tmp/nginx.pid; + events { worker_connections 1024; } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ad27a37f7..498218e70 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,6 +10,7 @@ "license": "SEE LICENSE IN https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/proprietary/LICENSE", "dependencies": { "@atlaskit/pragmatic-drag-and-drop": "^1.7.7", + "@dnd-kit/core": "^6.3.1", "@embedpdf/core": "^1.4.1", "@embedpdf/engines": "^1.4.1", "@embedpdf/plugin-annotation": "^1.4.1", @@ -506,6 +507,63 @@ "node": ">=18" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/accessibility/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/@embedpdf/core": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.4.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 2d488c93d..9d021a728 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,6 +6,7 @@ "proxy": "http://localhost:8080", "dependencies": { "@atlaskit/pragmatic-drag-and-drop": "^1.7.7", + "@dnd-kit/core": "^6.3.1", "@embedpdf/core": "^1.4.1", "@embedpdf/engines": "^1.4.1", "@embedpdf/plugin-annotation": "^1.4.1", diff --git a/frontend/public/branding/old/favicon.ico b/frontend/public/branding/old/favicon.ico new file mode 100644 index 000000000..8ad57cac7 Binary files /dev/null and b/frontend/public/branding/old/favicon.ico differ diff --git a/frontend/public/branding/old/favicon.png b/frontend/public/branding/old/favicon.png new file mode 100644 index 000000000..5edc6eae2 Binary files /dev/null and b/frontend/public/branding/old/favicon.png differ diff --git a/frontend/public/branding/old/favicon.svg b/frontend/public/branding/old/favicon.svg new file mode 100644 index 000000000..0fef4393a --- /dev/null +++ b/frontend/public/branding/old/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index c85de2119..a0f7a2666 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -35,9 +35,16 @@ "discardChanges": "Discard & Leave", "applyAndContinue": "Save & Leave", "exportAndContinue": "Export & Continue", + "zipWarning": { + "title": "Large ZIP File", + "message": "This ZIP contains {{count}} files. Extract anyway?", + "cancel": "Cancel", + "confirm": "Extract" + }, "language": { "direction": "ltr" }, + "cancel": "Cancel", "addPageNumbers": { "fontSize": "Font Size", "fontName": "Font Name", @@ -98,6 +105,8 @@ "unpin": "Unpin File (replace after tool run)", "undoOperationTooltip": "Click to undo the last operation and restore the original files", "undo": "Undo", + "back": "Back", + "nothingToUndo": "Nothing to undo", "moreOptions": "More Options", "editYourNewFiles": "Edit your new file(s)", "close": "Close", @@ -473,7 +482,14 @@ "globalPopularity": "Global Popularity", "sortBy": "Sort by:", "mobile": { - "brandAlt": "Stirling PDF logo" + "brandAlt": "Stirling PDF logo", + "openFiles": "Open files", + "swipeHint": "Swipe left or right to switch views", + "tools": "Tools", + "toolsSlide": "Tool selection panel", + "viewSwitcher": "Switch workspace view", + "workbenchSlide": "Workspace panel", + "workspace": "Workspace" }, "multiTool": { "tags": "multiple,tools", @@ -765,21 +781,6 @@ "title": "Automate", "desc": "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks." }, - "pdfTextEditor": { - "tags": "edit,text,modify", - "title": "PDF Text Editor", - "desc": "Edit text content in PDF documents" - }, - "mobile": { - "brandAlt": "Stirling PDF logo", - "openFiles": "Open files", - "swipeHint": "Swipe left or right to switch views", - "tools": "Tools", - "toolsSlide": "Tool selection panel", - "viewSwitcher": "Switch workspace view", - "workbenchSlide": "Workspace panel", - "workspace": "Workspace" - }, "overlay-pdfs": { "desc": "Overlay one PDF on top of another", "title": "Overlay PDFs" @@ -822,15 +823,19 @@ "merge": { "tags": "merge,Page operations,Back end,server side", "title": "Merge", - "removeDigitalSignature": "Remove digital signature in the merged file?", - "generateTableOfContents": "Generate table of contents in the merged file?", - "removeDigitalSignature.tooltip": { - "title": "Remove Digital Signature", - "description": "Digital signatures will be invalidated when merging files. Check this to remove them from the final merged PDF." + "removeDigitalSignature": { + "label": "Remove digital signature in the merged file?", + "tooltip": { + "title": "Remove Digital Signature", + "description": "Digital signatures will be invalidated when merging files. Check this to remove them from the final merged PDF." + } }, - "generateTableOfContents.tooltip": { - "title": "Generate Table of Contents", - "description": "Automatically creates a clickable table of contents in the merged PDF based on the original file names and page numbers." + "generateTableOfContents": { + "label": "Generate table of contents in the merged file?", + "tooltip": { + "title": "Generate Table of Contents", + "description": "Automatically creates a clickable table of contents in the merged PDF based on the original file names and page numbers." + } }, "submit": "Merge", "sortBy": { @@ -1006,7 +1011,8 @@ "title": "Choose Your Split Method" } }, - "selectMethod": "Select a split method" + "selectMethod": "Select a split method", + "resultsTitle": "Split Results" }, "rotate": { "title": "Rotate PDF", @@ -1100,7 +1106,11 @@ "markdown": "Markdown", "textRtf": "Text/RTF", "grayscale": "Greyscale", - "errorConversion": "An error occurred while converting the file." + "errorConversion": "An error occurred while converting the file.", + "cbzOptions": "CBZ to PDF Options", + "optimizeForEbook": "Optimize PDF for ebook readers (uses Ghostscript)", + "cbzOutputOptions": "PDF to CBZ Options", + "cbzDpi": "DPI for image rendering" }, "imageToPdf": { "tags": "conversion,img,jpg,picture,photo" @@ -2040,8 +2050,10 @@ "options": { "stepTitle": "Flatten Options", "title": "Flatten Options", - "flattenOnlyForms": "Flatten only forms", - "flattenOnlyForms.desc": "Only flatten form fields, leaving other interactive elements intact", + "flattenOnlyForms": { + "label": "Flatten only forms", + "desc": "Only flatten form fields, leaving other interactive elements intact" + }, "note": "Flattening removes interactive elements from the PDF, making them non-editable." }, "results": { @@ -2159,15 +2171,99 @@ "tags": "differentiate,contrast,changes,analysis", "title": "Compare", "header": "Compare PDFs", - "highlightColor": { - "1": "Highlight Colour 1:", - "2": "Highlight Colour 2:" + "clearSelected": "Clear selected", + "clear": { + "confirmTitle": "Clear selected PDFs?", + "confirmBody": "This will close the current comparison and take you back to Active Files.", + "confirm": "Clear and return" }, - "document": { - "1": "Document 1", - "2": "Document 2" + "review": { + "title": "Comparison Result", + "actionsHint": "Review the comparison, switch document roles, or export the summary.", + "switchOrder": "Switch order", + "exportSummary": "Export summary" }, - "submit": "Compare", + "base": { + "label": "Original document", + "placeholder": "Select the original PDF" + }, + "comparison": { + "label": "Edited document", + "placeholder": "Select the edited PDF" + }, + "addFilesHint": "Add PDFs in the Files step to enable selection.", + "noFiles": "No PDFs available yet", + "pages": "Pages", + "selection": { + "originalEditedTitle": "Select Original and Edited PDFs" + }, + "original": { "label": "Original PDF" }, + "edited": { "label": "Edited PDF" }, + "swap": { + "confirmTitle": "Re-run comparison?", + "confirmBody": "This will rerun the tool. Are you sure you want to swap the order of Original and Edited?", + "confirm": "Swap and Re-run" + }, + "cta": "Compare", + "loading": "Comparing...", + + "summary": { + "baseHeading": "Original document", + "comparisonHeading": "Edited document", + "pageLabel": "Page" + }, + "rendering": { + "pageNotReadyTitle": "Page not rendered yet", + "pageNotReadyBody": "Some pages are still rendering. Navigation will snap once they are ready.", + "rendering": "rendering", + "inProgress": "At least one of these PDFs are very large, scrolling won't be smooth until the rendering is complete", + "pagesRendered": "pages rendered", + "complete": "Page rendering complete" + }, + "dropdown": { + "deletionsLabel": "Deletions", + "additionsLabel": "Additions", + "deletions": "Deletions ({{count}})", + "additions": "Additions ({{count}})", + "searchPlaceholder": "Search changes...", + "noResults": "No changes found" + }, + "actions": { + "stackVertically": "Stack vertically", + "placeSideBySide": "Place side by side", + "zoomOut": "Zoom out", + "zoomIn": "Zoom in", + "resetView": "Reset view", + "unlinkScrollPan": "Unlink scroll and pan", + "linkScrollPan": "Link scroll and pan", + "unlinkScroll": "Unlink scroll", + "linkScroll": "Link scroll" + }, + "toasts": { + "unlinkedTitle": "Independent scroll & pan enabled", + "unlinkedBody": "Tip: Arrow Up/Down scroll both panes; panning only moves the active pane." + }, + "error": { + "selectRequired": "Select a original and edited document.", + "filesMissing": "Unable to locate the selected files. Please re-select them.", + "generic": "Unable to compare these files." + }, + "status": { + "extracting": "Extracting text...", + "processing": "Analysing differences...", + "complete": "Comparison ready" + }, + "longJob": { + "title": "Large comparison in progress", + "body": "These PDFs together exceed 2,000 pages. Processing can take several minutes." + }, + "slowOperation": { + "title": "Still working…", + "body": "This comparison is taking longer than usual. You can let it continue or cancel it.", + "cancel": "Cancel comparison" + }, + + "newLine": "new-line", "complex": { "message": "One or both of the provided documents are large files, accuracy of comparison may be reduced" }, @@ -2180,6 +2276,16 @@ "text": { "message": "One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison." } + }, + "too": { + "dissimilar": { + "message": "These documents appear highly dissimilar. Comparison was stopped to save time." + } + }, + "earlyDissimilarity": { + "title": "These PDFs look highly different", + "body": "We're seeing very few similarities so far. You can stop the comparison if these aren't related documents.", + "stopButton": "Stop comparison" } }, "certSign": { @@ -2660,7 +2766,14 @@ "title": "Show Javascript", "header": "Show Javascript", "downloadJS": "Download Javascript", - "submit": "Show" + "submit": "Show", + "results": "Result", + "processing": "Extracting JavaScript...", + "done": "JavaScript extracted", + "singleFileWarning": "This tool only supports one file at a time. Please select a single file.", + "view": { + "title": "Extracted JavaScript" + } }, "redact": { "tags": "Redact,Hide,black out,black,marker,hidden,auto redact,manual redact", @@ -3064,6 +3177,7 @@ "rememberme": "Remember me", "invalid": "Invalid username or password.", "locked": "Your account has been locked.", + "sessionExpired": "Your session has expired. Please sign in again.", "signinTitle": "Please sign in", "ssoSignIn": "Login via Single Sign-on", "oAuth2AutoCreateDisabled": "OAUTH2 Auto-Create User Disabled", @@ -3132,13 +3246,14 @@ "passwordsDoNotMatch": "Passwords do not match", "passwordTooShort": "Password must be at least 6 characters long", "invalidEmail": "Please enter a valid email address", + "checkEmailConfirmation": "Check your email for a confirmation link to complete your registration.", + "accountCreatedSuccessfully": "Account created successfully! You can now sign in.", + "unexpectedError": "Unexpected error: {{message}}", + "useEmailInstead": "Use Email Instead", "nameRequired": "Name is required", "emailRequired": "Email is required", "passwordRequired": "Password is required", - "confirmPasswordRequired": "Confirm password is required", - "checkEmailConfirmation": "Check your email for a confirmation link to complete your registration.", - "accountCreatedSuccessfully": "Account created successfully! You can now sign in.", - "unexpectedError": "Unexpected error: {{message}}" + "confirmPasswordRequired": "Please confirm your password" }, "pdfToSinglePage": { "title": "PDF To Single Page", @@ -3492,7 +3607,8 @@ "toggleAnnotations": "Toggle Annotations Visibility", "annotationMode": "Toggle Annotation Mode", "draw": "Draw", - "save": "Save" + "save": "Save", + "saveChanges": "Save Changes" }, "search": { "title": "Search PDF", @@ -3539,8 +3655,16 @@ "help": "Help", "account": "Account", "config": "Config", + "settings": "Settings", "adminSettings": "Admin Settings", - "allTools": "All Tools" + "allTools": "Tools", + "reader": "Reader", + "helpMenu": { + "toolsTour": "Tools Tour", + "toolsTourDesc": "Learn what the tools can do", + "adminTour": "Admin Tour", + "adminTourDesc": "Explore admin settings & features" + } }, "admin": { "error": "Error", @@ -3560,6 +3684,12 @@ "saveSuccess": "Settings saved successfully", "save": "Save Changes", "restartRequired": "Restart Required", + "loginRequired": "Login mode must be enabled to modify admin settings", + "loginDisabled": { + "title": "Login Mode Required", + "message": "Login mode must be enabled to modify admin settings. Please set SECURITY_ENABLELOGIN=true in your environment or security.enableLogin: true in settings.yml, then restart the server.", + "readOnly": "The settings below show example values for reference. Enable login mode to view and edit actual configuration." + }, "restart": { "title": "Restart Required", "message": "Settings have been saved successfully. A server restart is required for the changes to take effect.", @@ -3575,45 +3705,93 @@ "description": "Configure system-wide application settings including branding and default behaviour.", "ui": "User Interface", "system": "System", - "appName": "Application Name", - "appName.description": "The name displayed in the browser tab and home page", - "appNameNavbar": "Navbar Brand", - "appNameNavbar.description": "The name displayed in the navigation bar", - "homeDescription": "Home Description", - "homeDescription.description": "The description text shown on the home page", - "defaultLocale": "Default Locale", - "defaultLocale.description": "The default language for new users (e.g., en_US, es_ES)", - "fileUploadLimit": "File Upload Limit", - "fileUploadLimit.description": "Maximum file upload size (e.g., 100MB, 1GB)", - "showUpdate": "Show Update Notifications", - "showUpdate.description": "Display notifications when a new version is available", - "showUpdateOnlyAdmin": "Show Updates to Admins Only", - "showUpdateOnlyAdmin.description": "Restrict update notifications to admin users only", - "customHTMLFiles": "Custom HTML Files", - "customHTMLFiles.description": "Allow serving custom HTML files from the customFiles directory", - "languages": "Available Languages", - "languages.description": "Languages that users can select from (leave empty to enable all languages)", - "customMetadata": "Custom Metadata", - "customMetadata.autoUpdate": "Auto Update Metadata", - "customMetadata.autoUpdate.description": "Automatically update PDF metadata on all processed documents", - "customMetadata.author": "Default Author", - "customMetadata.author.description": "Default author for PDF metadata (e.g., username)", - "customMetadata.creator": "Default Creator", - "customMetadata.creator.description": "Default creator for PDF metadata", - "customMetadata.producer": "Default Producer", - "customMetadata.producer.description": "Default producer for PDF metadata", - "customPaths": "Custom Paths", - "customPaths.description": "Configure custom file system paths for pipeline processing and external tools", - "customPaths.pipeline": "Pipeline Directories", - "customPaths.pipeline.watchedFoldersDir": "Watched Folders Directory", - "customPaths.pipeline.watchedFoldersDir.description": "Directory where pipeline monitors for incoming PDFs (leave empty for default: /pipeline/watchedFolders)", - "customPaths.pipeline.finishedFoldersDir": "Finished Folders Directory", - "customPaths.pipeline.finishedFoldersDir.description": "Directory where processed PDFs are outputted (leave empty for default: /pipeline/finishedFolders)", - "customPaths.operations": "External Tool Paths", - "customPaths.operations.weasyprint": "WeasyPrint Executable", - "customPaths.operations.weasyprint.description": "Path to WeasyPrint executable for HTML to PDF conversion (leave empty for default: /opt/venv/bin/weasyprint)", - "customPaths.operations.unoconvert": "Unoconvert Executable", - "customPaths.operations.unoconvert.description": "Path to LibreOffice unoconvert for document conversions (leave empty for default: /opt/venv/bin/unoconvert)" + "appName": { + "label": "Application Name", + "description": "The name displayed in the browser tab and home page" + }, + "appNameNavbar": { + "label": "Navbar Brand", + "description": "The name displayed in the navigation bar" + }, + "homeDescription": { + "label": "Home Description", + "description": "The description text shown on the home page" + }, + "defaultLocale": { + "label": "Default Locale", + "description": "The default language for new users (e.g., en_US, es_ES)" + }, + "fileUploadLimit": { + "label": "File Upload Limit", + "description": "Maximum file upload size (e.g., 100MB, 1GB)" + }, + "showUpdate": { + "label": "Show Update Notifications", + "description": "Display notifications when a new version is available" + }, + "showUpdateOnlyAdmin": { + "label": "Show Updates to Admins Only", + "description": "Restrict update notifications to admin users only" + }, + "customHTMLFiles": { + "label": "Custom HTML Files", + "description": "Allow serving custom HTML files from the customFiles directory" + }, + "languages": { + "label": "Available Languages", + "description": "Languages that users can select from (leave empty to enable all languages)" + }, + "customMetadata": { + "label": "Custom Metadata", + "autoUpdate": { + "label": "Auto Update Metadata", + "description": "Automatically update PDF metadata on all processed documents" + }, + "author": { + "label": "Default Author", + "description": "Default author for PDF metadata (e.g., username)" + }, + "creator": { + "label": "Default Creator", + "description": "Default creator for PDF metadata" + }, + "producer": { + "label": "Default Producer", + "description": "Default producer for PDF metadata" + } + }, + "logoStyle": { + "label": "Logo Style", + "description": "Choose between the modern minimalist logo or the classic S icon", + "classic": "Classic", + "modern": "Modern" + }, + "customPaths": { + "label": "Custom Paths", + "description": "Configure custom file system paths for pipeline processing and external tools", + "pipeline": { + "label": "Pipeline Directories", + "watchedFoldersDir": { + "label": "Watched Folders Directory", + "description": "Directory where pipeline monitors for incoming PDFs (leave empty for default: /pipeline/watchedFolders)" + }, + "finishedFoldersDir": { + "label": "Finished Folders Directory", + "description": "Directory where processed PDFs are outputted (leave empty for default: /pipeline/finishedFolders)" + } + }, + "operations": { + "label": "External Tool Paths", + "weasyprint": { + "label": "WeasyPrint Executable", + "description": "Path to WeasyPrint executable for HTML to PDF conversion (leave empty for default: /opt/venv/bin/weasyprint)" + }, + "unoconvert": { + "label": "Unoconvert Executable", + "description": "Path to LibreOffice unoconvert for document conversions (leave empty for default: /opt/venv/bin/unoconvert)" + } + } + } }, "security": { "title": "Security", @@ -3623,68 +3801,124 @@ "message": "OAuth2 and SAML2 authentication providers have been moved to the Connections menu for easier management." }, "authentication": "Authentication", - "enableLogin": "Enable Login", - "enableLogin.description": "Require users to log in before accessing the application", - "loginMethod": "Login Method", - "loginMethod.description": "The authentication method to use for user login", - "loginMethod.all": "All Methods", - "loginMethod.normal": "Username/Password Only", - "loginMethod.oauth2": "OAuth2 Only", - "loginMethod.saml2": "SAML2 Only", - "loginAttemptCount": "Login Attempt Limit", - "loginAttemptCount.description": "Maximum number of failed login attempts before account lockout", - "loginResetTimeMinutes": "Login Reset Time (minutes)", - "loginResetTimeMinutes.description": "Time before failed login attempts are reset", - "csrfDisabled": "Disable CSRF Protection", - "csrfDisabled.description": "Disable Cross-Site Request Forgery protection (not recommended)", - "initialLogin": "Initial Login", - "initialLogin.username": "Initial Username", - "initialLogin.username.description": "The username for the initial admin account", - "initialLogin.password": "Initial Password", - "initialLogin.password.description": "The password for the initial admin account", - "jwt": "JWT Configuration", - "jwt.secureCookie": "Secure Cookie", - "jwt.secureCookie.description": "Require HTTPS for JWT cookies (recommended for production)", - "jwt.keyRetentionDays": "Key Retention Days", - "jwt.keyRetentionDays.description": "Number of days to retain old JWT keys for verification", - "jwt.persistence": "Enable Key Persistence", - "jwt.persistence.description": "Store JWT keys persistently to survive server restarts", - "jwt.enableKeyRotation": "Enable Key Rotation", - "jwt.enableKeyRotation.description": "Automatically rotate JWT signing keys periodically", - "jwt.enableKeyCleanup": "Enable Key Cleanup", - "jwt.enableKeyCleanup.description": "Automatically remove expired JWT keys", - "audit": "Audit Logging", - "audit.enabled": "Enable Audit Logging", - "audit.enabled.description": "Track user actions and system events for compliance and security monitoring", - "audit.level": "Audit Level", - "audit.level.description": "0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE", - "audit.retentionDays": "Audit Retention (days)", - "audit.retentionDays.description": "Number of days to retain audit logs", - "htmlUrlSecurity": "HTML URL Security", - "htmlUrlSecurity.description": "Configure URL access restrictions for HTML processing to prevent SSRF attacks", - "htmlUrlSecurity.enabled": "Enable URL Security", - "htmlUrlSecurity.enabled.description": "Enable URL security restrictions for HTML to PDF conversions", - "htmlUrlSecurity.level": "Security Level", - "htmlUrlSecurity.level.description": "MAX: whitelist only, MEDIUM: block internal networks, OFF: no restrictions", - "htmlUrlSecurity.level.max": "Maximum (Whitelist Only)", - "htmlUrlSecurity.level.medium": "Medium (Block Internal)", - "htmlUrlSecurity.level.off": "Off (No Restrictions)", - "htmlUrlSecurity.advanced": "Advanced Settings", - "htmlUrlSecurity.allowedDomains": "Allowed Domains (Whitelist)", - "htmlUrlSecurity.allowedDomains.description": "One domain per line (e.g., cdn.example.com). Only these domains allowed when level is MAX", - "htmlUrlSecurity.blockedDomains": "Blocked Domains (Blacklist)", - "htmlUrlSecurity.blockedDomains.description": "One domain per line (e.g., malicious.com). Additional domains to block", - "htmlUrlSecurity.internalTlds": "Internal TLDs", - "htmlUrlSecurity.internalTlds.description": "One TLD per line (e.g., .local, .internal). Block domains with these TLD patterns", - "htmlUrlSecurity.networkBlocking": "Network Blocking", - "htmlUrlSecurity.blockPrivateNetworks": "Block Private Networks", - "htmlUrlSecurity.blockPrivateNetworks.description": "Block RFC 1918 private networks (10.x.x.x, 192.168.x.x, 172.16-31.x.x)", - "htmlUrlSecurity.blockLocalhost": "Block Localhost", - "htmlUrlSecurity.blockLocalhost.description": "Block localhost and loopback addresses (127.x.x.x, ::1)", - "htmlUrlSecurity.blockLinkLocal": "Block Link-Local Addresses", - "htmlUrlSecurity.blockLinkLocal.description": "Block link-local addresses (169.254.x.x, fe80::/10)", - "htmlUrlSecurity.blockCloudMetadata": "Block Cloud Metadata Endpoints", - "htmlUrlSecurity.blockCloudMetadata.description": "Block cloud provider metadata endpoints (169.254.169.254)" + "enableLogin": { + "label": "Enable Login", + "description": "Require users to log in before accessing the application" + }, + "loginMethod": { + "label": "Login Method", + "description": "The authentication method to use for user login", + "all": "All Methods", + "normal": "Username/Password Only", + "oauth2": "OAuth2 Only", + "saml2": "SAML2 Only" + }, + "loginAttemptCount": { + "label": "Login Attempt Limit", + "description": "Maximum number of failed login attempts before account lockout" + }, + "loginResetTimeMinutes": { + "label": "Login Reset Time (minutes)", + "description": "Time before failed login attempts are reset" + }, + "csrfDisabled": { + "label": "Disable CSRF Protection", + "description": "Disable Cross-Site Request Forgery protection (not recommended)" + }, + "initialLogin": { + "label": "Initial Login", + "username": { + "label": "Initial Username", + "description": "The username for the initial admin account" + }, + "password": { + "label": "Initial Password", + "description": "The password for the initial admin account" + } + }, + "jwt": { + "label": "JWT Configuration", + "secureCookie": { + "label": "Secure Cookie", + "description": "Require HTTPS for JWT cookies (recommended for production)" + }, + "keyRetentionDays": { + "label": "Key Retention Days", + "description": "Number of days to retain old JWT keys for verification" + }, + "persistence": { + "label": "Enable Key Persistence", + "description": "Store JWT keys persistently to survive server restarts" + }, + "enableKeyRotation": { + "label": "Enable Key Rotation", + "description": "Automatically rotate JWT signing keys periodically" + }, + "enableKeyCleanup": { + "label": "Enable Key Cleanup", + "description": "Automatically remove expired JWT keys" + } + }, + "audit": { + "label": "Audit Logging", + "enabled": { + "label": "Enable Audit Logging", + "description": "Track user actions and system events for compliance and security monitoring" + }, + "level": { + "label": "Audit Level", + "description": "0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE" + }, + "retentionDays": { + "label": "Audit Retention (days)", + "description": "Number of days to retain audit logs" + } + }, + "htmlUrlSecurity": { + "label": "HTML URL Security", + "description": "Configure URL access restrictions for HTML processing to prevent SSRF attacks", + "enabled": { + "label": "Enable URL Security", + "description": "Enable URL security restrictions for HTML to PDF conversions" + }, + "level": { + "label": "Security Level", + "description": "MAX: whitelist only, MEDIUM: block internal networks, OFF: no restrictions", + "max": "Maximum (Whitelist Only)", + "medium": "Medium (Block Internal)", + "off": "Off (No Restrictions)" + }, + "advanced": "Advanced Settings", + "allowedDomains": { + "label": "Allowed Domains (Whitelist)", + "description": "One domain per line (e.g., cdn.example.com). Only these domains allowed when level is MAX" + }, + "blockedDomains": { + "label": "Blocked Domains (Blacklist)", + "description": "One domain per line (e.g., malicious.com). Additional domains to block" + }, + "internalTlds": { + "label": "Internal TLDs", + "description": "One TLD per line (e.g., .local, .internal). Block domains with these TLD patterns" + }, + "networkBlocking": "Network Blocking", + "blockPrivateNetworks": { + "label": "Block Private Networks", + "description": "Block RFC 1918 private networks (10.x.x.x, 192.168.x.x, 172.16-31.x.x)" + }, + "blockLocalhost": { + "label": "Block Localhost", + "description": "Block localhost and loopback addresses (127.x.x.x, ::1)" + }, + "blockLinkLocal": { + "label": "Block Link-Local Addresses", + "description": "Block link-local addresses (169.254.x.x, fe80::/10)" + }, + "blockCloudMetadata": { + "label": "Block Cloud Metadata Endpoints", + "description": "Block cloud provider metadata endpoints (169.254.169.254)" + } + } }, "connections": { "title": "Connections", @@ -3695,146 +3929,254 @@ "disconnect": "Disconnect", "disconnected": "Provider disconnected successfully", "disconnectError": "Failed to disconnect provider", - "ssoAutoLogin": "SSO Auto Login", - "ssoAutoLogin.enable": "Enable SSO Auto Login", - "ssoAutoLogin.description": "Automatically redirect to SSO login when authentication is required", - "oauth2": "OAuth2", - "oauth2.enabled": "Enable OAuth2", - "oauth2.enabled.description": "Allow users to authenticate using OAuth2 providers", - "oauth2.provider": "Provider", - "oauth2.provider.description": "The OAuth2 provider to use for authentication", - "oauth2.issuer": "Issuer URL", - "oauth2.issuer.description": "The OAuth2 provider issuer URL", - "oauth2.clientId": "Client ID", - "oauth2.clientId.description": "The OAuth2 client ID from your provider", - "oauth2.clientSecret": "Client Secret", - "oauth2.clientSecret.description": "The OAuth2 client secret from your provider", - "oauth2.useAsUsername": "Use as Username", - "oauth2.useAsUsername.description": "The OAuth2 claim to use as the username (e.g., email, sub)", - "oauth2.autoCreateUser": "Auto Create Users", - "oauth2.autoCreateUser.description": "Automatically create user accounts on first OAuth2 login", - "oauth2.blockRegistration": "Block Registration", - "oauth2.blockRegistration.description": "Prevent new user registration via OAuth2", - "oauth2.scopes": "OAuth2 Scopes", - "oauth2.scopes.description": "Comma-separated list of OAuth2 scopes to request (e.g., openid, profile, email)", - "saml2": "SAML2", - "saml2.enabled": "Enable SAML2", - "saml2.enabled.description": "Allow users to authenticate using SAML2 providers", - "saml2.provider": "Provider", - "saml2.provider.description": "The SAML2 provider name", - "saml2.registrationId": "Registration ID", - "saml2.registrationId.description": "The SAML2 registration identifier", - "saml2.autoCreateUser": "Auto Create Users", - "saml2.autoCreateUser.description": "Automatically create user accounts on first SAML2 login", - "saml2.blockRegistration": "Block Registration", - "saml2.blockRegistration.description": "Prevent new user registration via SAML2" + "ssoAutoLogin": { + "label": "SSO Auto Login", + "enable": "Enable SSO Auto Login", + "description": "Automatically redirect to SSO login when authentication is required" + }, + "oauth2": { + "label": "OAuth2", + "enabled": { + "label": "Enable OAuth2", + "description": "Allow users to authenticate using OAuth2 providers" + }, + "provider": { + "label": "Provider", + "description": "The OAuth2 provider to use for authentication" + }, + "issuer": { + "label": "Issuer URL", + "description": "The OAuth2 provider issuer URL" + }, + "clientId": { + "label": "Client ID", + "description": "The OAuth2 client ID from your provider" + }, + "clientSecret": { + "label": "Client Secret", + "description": "The OAuth2 client secret from your provider" + }, + "useAsUsername": { + "label": "Use as Username", + "description": "The OAuth2 claim to use as the username (e.g., email, sub)" + }, + "autoCreateUser": { + "label": "Auto Create Users", + "description": "Automatically create user accounts on first OAuth2 login" + }, + "blockRegistration": { + "label": "Block Registration", + "description": "Prevent new user registration via OAuth2" + }, + "scopes": { + "label": "OAuth2 Scopes", + "description": "Comma-separated list of OAuth2 scopes to request (e.g., openid, profile, email)" + } + }, + "saml2": { + "label": "SAML2", + "enabled": { + "label": "Enable SAML2", + "description": "Allow users to authenticate using SAML2 providers" + }, + "provider": { + "label": "Provider", + "description": "The SAML2 provider name" + }, + "registrationId": { + "label": "Registration ID", + "description": "The SAML2 registration identifier" + }, + "autoCreateUser": { + "label": "Auto Create Users", + "description": "Automatically create user accounts on first SAML2 login" + }, + "blockRegistration": { + "label": "Block Registration", + "description": "Prevent new user registration via SAML2" + } + } }, "database": { "title": "Database", "description": "Configure custom database connection settings for enterprise deployments.", "configuration": "Database Configuration", - "enableCustom": "Enable Custom Database", - "enableCustom.description": "Use your own custom database configuration instead of the default embedded database", - "customUrl": "Custom Database URL", - "customUrl.description": "Full JDBC connection string (e.g., jdbc:postgresql://localhost:5432/postgres). If provided, individual connection settings below are not used.", - "type": "Database Type", - "type.description": "Type of database (not used if custom URL is provided)", - "hostName": "Host Name", - "hostName.description": "Database server hostname (not used if custom URL is provided)", - "port": "Port", - "port.description": "Database server port (not used if custom URL is provided)", - "name": "Database Name", - "name.description": "Name of the database (not used if custom URL is provided)", - "username": "Username", - "username.description": "Database authentication username", - "password": "Password", - "password.description": "Database authentication password" + "enableCustom": { + "label": "Enable Custom Database", + "description": "Use your own custom database configuration instead of the default embedded database" + }, + "customUrl": { + "label": "Custom Database URL", + "description": "Full JDBC connection string (e.g., jdbc:postgresql://localhost:5432/postgres). If provided, individual connection settings below are not used." + }, + "type": { + "label": "Database Type", + "description": "Type of database (not used if custom URL is provided)" + }, + "hostName": { + "label": "Host Name", + "description": "Database server hostname (not used if custom URL is provided)" + }, + "port": { + "label": "Port", + "description": "Database server port (not used if custom URL is provided)" + }, + "name": { + "label": "Database Name", + "description": "Name of the database (not used if custom URL is provided)" + }, + "username": { + "label": "Username", + "description": "Database authentication username" + }, + "password": { + "label": "Password", + "description": "Database authentication password" + } }, "privacy": { "title": "Privacy", "description": "Configure privacy and data collection settings.", "analytics": "Analytics & Tracking", - "enableAnalytics": "Enable Analytics", - "enableAnalytics.description": "Collect anonymous usage analytics to help improve the application", - "metricsEnabled": "Enable Metrics", - "metricsEnabled.description": "Enable collection of performance and usage metrics. Provides API endpoint for admins to access metrics data", + "enableAnalytics": { + "label": "Enable Analytics", + "description": "Collect anonymous usage analytics to help improve the application" + }, + "metricsEnabled": { + "label": "Enable Metrics", + "description": "Enable collection of performance and usage metrics. Provides API endpoint for admins to access metrics data" + }, "searchEngine": "Search Engine Visibility", - "googleVisibility": "Google Visibility", - "googleVisibility.description": "Allow search engines to index this application" + "googleVisibility": { + "label": "Google Visibility", + "description": "Allow search engines to index this application" + } }, "advanced": { "title": "Advanced", "description": "Configure advanced features and experimental functionality.", "features": "Feature Flags", "processing": "Processing", - "endpoints": "Endpoints", - "endpoints.manage": "Manage API Endpoints", - "endpoints.description": "Endpoint management is configured via YAML. See documentation for details on enabling/disabling specific endpoints.", - "enableAlphaFunctionality": "Enable Alpha Features", - "enableAlphaFunctionality.description": "Enable experimental and alpha-stage features (may be unstable)", - "enableUrlToPDF": "Enable URL to PDF", - "enableUrlToPDF.description": "Allow conversion of web pages to PDF documents", - "maxDPI": "Maximum DPI", - "maxDPI.description": "Maximum DPI for image processing (0 = unlimited)", - "tessdataDir": "Tessdata Directory", - "tessdataDir.description": "Path to the tessdata directory for OCR language files", - "disableSanitize": "Disable HTML Sanitization", - "disableSanitize.description": "WARNING: Security risk - disabling HTML sanitization can lead to XSS vulnerabilities", - "tempFileManagement": "Temp File Management", - "tempFileManagement.description": "Configure temporary file storage and cleanup behavior", - "tempFileManagement.baseTmpDir": "Base Temp Directory", - "tempFileManagement.baseTmpDir.description": "Base directory for temporary files (leave empty for default: java.io.tmpdir/stirling-pdf)", - "tempFileManagement.libreofficeDir": "LibreOffice Temp Directory", - "tempFileManagement.libreofficeDir.description": "Directory for LibreOffice temp files (leave empty for default: baseTmpDir/libreoffice)", - "tempFileManagement.systemTempDir": "System Temp Directory", - "tempFileManagement.systemTempDir.description": "System temp directory to clean (only used if cleanupSystemTemp is enabled)", - "tempFileManagement.prefix": "Temp File Prefix", - "tempFileManagement.prefix.description": "Prefix for temp file names", - "tempFileManagement.maxAgeHours": "Max Age (hours)", - "tempFileManagement.maxAgeHours.description": "Maximum age in hours before temp files are cleaned up", - "tempFileManagement.cleanupIntervalMinutes": "Cleanup Interval (minutes)", - "tempFileManagement.cleanupIntervalMinutes.description": "How often to run cleanup (in minutes)", - "tempFileManagement.startupCleanup": "Startup Cleanup", - "tempFileManagement.startupCleanup.description": "Clean up old temp files on application startup", - "tempFileManagement.cleanupSystemTemp": "Cleanup System Temp", - "tempFileManagement.cleanupSystemTemp.description": "Whether to clean broader system temp directory (use with caution)", - "processExecutor": "Process Executor Limits", - "processExecutor.description": "Configure session limits and timeouts for each process executor", - "processExecutor.sessionLimit": "Session Limit", - "processExecutor.sessionLimit.description": "Maximum concurrent instances", - "processExecutor.timeout": "Timeout (minutes)", - "processExecutor.timeout.description": "Maximum execution time", - "processExecutor.libreOffice": "LibreOffice", - "processExecutor.pdfToHtml": "PDF to HTML", - "processExecutor.qpdf": "QPDF", - "processExecutor.tesseract": "Tesseract OCR", - "processExecutor.pythonOpenCv": "Python OpenCV", - "processExecutor.weasyPrint": "WeasyPrint", - "processExecutor.installApp": "Install App", - "processExecutor.calibre": "Calibre", - "processExecutor.ghostscript": "Ghostscript", - "processExecutor.ocrMyPdf": "OCRmyPDF" + "endpoints": { + "label": "Endpoints", + "manage": "Manage API Endpoints", + "description": "Endpoint management is configured via YAML. See documentation for details on enabling/disabling specific endpoints." + }, + "enableAlphaFunctionality": { + "label": "Enable Alpha Features", + "description": "Enable experimental and alpha-stage features (may be unstable)" + }, + "enableUrlToPDF": { + "label": "Enable URL to PDF", + "description": "Allow conversion of web pages to PDF documents" + }, + "maxDPI": { + "label": "Maximum DPI", + "description": "Maximum DPI for image processing (0 = unlimited)" + }, + "tessdataDir": { + "label": "Tessdata Directory", + "description": "Path to the tessdata directory for OCR language files" + }, + "disableSanitize": { + "label": "Disable HTML Sanitization", + "description": "WARNING: Security risk - disabling HTML sanitization can lead to XSS vulnerabilities" + }, + "tempFileManagement": { + "label": "Temp File Management", + "description": "Configure temporary file storage and cleanup behavior", + "baseTmpDir": { + "label": "Base Temp Directory", + "description": "Base directory for temporary files (leave empty for default: java.io.tmpdir/stirling-pdf)" + }, + "libreofficeDir": { + "label": "LibreOffice Temp Directory", + "description": "Directory for LibreOffice temp files (leave empty for default: baseTmpDir/libreoffice)" + }, + "systemTempDir": { + "label": "System Temp Directory", + "description": "System temp directory to clean (only used if cleanupSystemTemp is enabled)" + }, + "prefix": { + "label": "Temp File Prefix", + "description": "Prefix for temp file names" + }, + "maxAgeHours": { + "label": "Max Age (hours)", + "description": "Maximum age in hours before temp files are cleaned up" + }, + "cleanupIntervalMinutes": { + "label": "Cleanup Interval (minutes)", + "description": "How often to run cleanup (in minutes)" + }, + "startupCleanup": { + "label": "Startup Cleanup", + "description": "Clean up old temp files on application startup" + }, + "cleanupSystemTemp": { + "label": "Cleanup System Temp", + "description": "Whether to clean broader system temp directory (use with caution)" + } + }, + "processExecutor": { + "label": "Process Executor Limits", + "description": "Configure session limits and timeouts for each process executor", + "sessionLimit": { + "label": "Session Limit", + "description": "Maximum concurrent instances" + }, + "timeout": { + "label": "Timeout (minutes)", + "description": "Maximum execution time" + }, + "libreOffice": "LibreOffice", + "pdfToHtml": "PDF to HTML", + "qpdf": "QPDF", + "tesseract": "Tesseract OCR", + "pythonOpenCv": "Python OpenCV", + "weasyPrint": "WeasyPrint", + "installApp": "Install App", + "calibre": "Calibre", + "ghostscript": "Ghostscript", + "ocrMyPdf": "OCRmyPDF" + } }, "mail": { "title": "Mail Server", "description": "Configure SMTP settings for sending email notifications.", "smtp": "SMTP Configuration", - "enabled": "Enable Mail", - "enabled.description": "Enable email notifications and SMTP functionality", - "host": "SMTP Host", - "host.description": "The hostname or IP address of your SMTP server", - "port": "SMTP Port", - "port.description": "The port number for SMTP connection (typically 25, 465, or 587)", - "username": "SMTP Username", - "username.description": "Username for SMTP authentication", - "password": "SMTP Password", - "password.description": "Password for SMTP authentication", - "from": "From Address", - "from.description": "The email address to use as the sender", - "enableInvites": "Enable Email Invites", - "enableInvites.description": "Allow admins to invite users via email with auto-generated passwords", - "frontendUrl": "Frontend URL", - "frontendUrl.description": "Base URL for frontend (e.g. https://pdf.example.com). Used for generating invite links in emails. Leave empty to use backend URL." + "enabled": { + "label": "Enable Mail", + "description": "Enable email notifications and SMTP functionality" + }, + "host": { + "label": "SMTP Host", + "description": "The hostname or IP address of your SMTP server" + }, + "port": { + "label": "SMTP Port", + "description": "The port number for SMTP connection (typically 25, 465, or 587)" + }, + "username": { + "label": "SMTP Username", + "description": "Username for SMTP authentication" + }, + "password": { + "label": "SMTP Password", + "description": "Password for SMTP authentication" + }, + "from": { + "label": "From Address", + "description": "The email address to use as the sender" + }, + "enableInvites": { + "label": "Enable Email Invites", + "description": "Allow admins to invite users via email with auto-generated passwords" + }, + "frontendUrl": { + "label": "Frontend URL", + "description": "Base URL for frontend (e.g. https://pdf.example.com). Used for generating invite links in emails. Leave empty to use backend URL." + } }, "legal": { "title": "Legal Documents", @@ -3843,25 +4185,39 @@ "title": "Legal Responsibility Warning", "message": "By customizing these legal documents, you assume full responsibility for ensuring compliance with all applicable laws and regulations, including but not limited to GDPR and other EU data protection requirements. Only modify these settings if: (1) you are operating a personal/private instance, (2) you are outside EU jurisdiction and understand your local legal obligations, or (3) you have obtained proper legal counsel and accept sole responsibility for all user data and legal compliance. Stirling-PDF and its developers assume no liability for your legal obligations." }, - "termsAndConditions": "Terms and Conditions", - "termsAndConditions.description": "URL or filename to terms and conditions", - "privacyPolicy": "Privacy Policy", - "privacyPolicy.description": "URL or filename to privacy policy", - "accessibilityStatement": "Accessibility Statement", - "accessibilityStatement.description": "URL or filename to accessibility statement", - "cookiePolicy": "Cookie Policy", - "cookiePolicy.description": "URL or filename to cookie policy", - "impressum": "Impressum", - "impressum.description": "URL or filename to impressum (required in some jurisdictions)" + "termsAndConditions": { + "label": "Terms and Conditions", + "description": "URL or filename to terms and conditions" + }, + "privacyPolicy": { + "label": "Privacy Policy", + "description": "URL or filename to privacy policy" + }, + "accessibilityStatement": { + "label": "Accessibility Statement", + "description": "URL or filename to accessibility statement" + }, + "cookiePolicy": { + "label": "Cookie Policy", + "description": "URL or filename to cookie policy" + }, + "impressum": { + "label": "Impressum", + "description": "URL or filename to impressum (required in some jurisdictions)" + } }, "premium": { "title": "Premium & Enterprise", "description": "Configure your premium or enterprise license key.", "license": "License Configuration", - "key": "License Key", - "key.description": "Enter your premium or enterprise license key", - "enabled": "Enable Premium Features", - "enabled.description": "Enable license key checks for pro/enterprise features", + "key": { + "label": "License Key", + "description": "Enter your premium or enterprise license key" + }, + "enabled": { + "label": "Enable Premium Features", + "description": "Enable license key checks for pro/enterprise features" + }, "movedFeatures": { "title": "Premium Features Distributed", "message": "Premium and Enterprise features are now organized in their respective sections:" @@ -3870,25 +4226,39 @@ "features": { "title": "Features", "description": "Configure optional features and functionality.", - "serverCertificate": "Server Certificate", - "serverCertificate.description": "Configure server-side certificate generation for \"Sign with Stirling-PDF\" functionality", - "serverCertificate.enabled": "Enable Server Certificate", - "serverCertificate.enabled.description": "Enable server-side certificate for \"Sign with Stirling-PDF\" option", - "serverCertificate.organizationName": "Organization Name", - "serverCertificate.organizationName.description": "Organization name for generated certificates", - "serverCertificate.validity": "Certificate Validity (days)", - "serverCertificate.validity.description": "Number of days the certificate will be valid", - "serverCertificate.regenerateOnStartup": "Regenerate on Startup", - "serverCertificate.regenerateOnStartup.description": "Generate new certificate on each application startup" + "serverCertificate": { + "label": "Server Certificate", + "description": "Configure server-side certificate generation for \"Sign with Stirling-PDF\" functionality", + "enabled": { + "label": "Enable Server Certificate", + "description": "Enable server-side certificate for \"Sign with Stirling-PDF\" option" + }, + "organizationName": { + "label": "Organization Name", + "description": "Organization name for generated certificates" + }, + "validity": { + "label": "Certificate Validity (days)", + "description": "Number of days the certificate will be valid" + }, + "regenerateOnStartup": { + "label": "Regenerate on Startup", + "description": "Generate new certificate on each application startup" + } + } }, "endpoints": { "title": "API Endpoints", "description": "Control which API endpoints and endpoint groups are available.", "management": "Endpoint Management", - "toRemove": "Disabled Endpoints", - "toRemove.description": "Select individual endpoints to disable", - "groupsToRemove": "Disabled Endpoint Groups", - "groupsToRemove.description": "Select endpoint groups to disable", + "toRemove": { + "label": "Disabled Endpoints", + "description": "Select individual endpoints to disable" + }, + "groupsToRemove": { + "label": "Disabled Endpoint Groups", + "description": "Select endpoint groups to disable" + }, "note": "Note: Disabling endpoints restricts API access but does not remove UI components. Restart required for changes to take effect." } } @@ -3999,8 +4369,10 @@ "desc": "Remove potentially harmful elements from PDF files.", "submit": "Sanitise PDF", "completed": "Sanitisation completed successfully", - "error.generic": "Sanitisation failed", - "error.failed": "An error occurred while sanitising the PDF.", + "error": { + "generic": "Sanitisation failed", + "failed": "An error occurred while sanitising the PDF." + }, "filenamePrefix": "sanitised", "sanitizationResults": "Sanitisation Results", "steps": { @@ -4014,18 +4386,30 @@ "options": { "title": "Sanitisation Options", "note": "Select the elements you want to remove from the PDF. At least one option must be selected.", - "removeJavaScript": "Remove JavaScript", - "removeJavaScript.desc": "Remove JavaScript actions and scripts from the PDF", - "removeEmbeddedFiles": "Remove Embedded Files", - "removeEmbeddedFiles.desc": "Remove any files embedded within the PDF", - "removeXMPMetadata": "Remove XMP Metadata", - "removeXMPMetadata.desc": "Remove XMP metadata from the PDF", - "removeMetadata": "Remove Document Metadata", - "removeMetadata.desc": "Remove document information metadata (title, author, etc.)", - "removeLinks": "Remove Links", - "removeLinks.desc": "Remove external links and launch actions from the PDF", - "removeFonts": "Remove Fonts", - "removeFonts.desc": "Remove embedded fonts from the PDF" + "removeJavaScript": { + "label": "Remove JavaScript", + "desc": "Remove JavaScript actions and scripts from the PDF" + }, + "removeEmbeddedFiles": { + "label": "Remove Embedded Files", + "desc": "Remove any files embedded within the PDF" + }, + "removeXMPMetadata": { + "label": "Remove XMP Metadata", + "desc": "Remove XMP metadata from the PDF" + }, + "removeMetadata": { + "label": "Remove Document Metadata", + "desc": "Remove document information metadata (title, author, etc.)" + }, + "removeLinks": { + "label": "Remove Links", + "desc": "Remove external links and launch actions from the PDF" + }, + "removeFonts": { + "label": "Remove Fonts", + "desc": "Remove embedded fonts from the PDF" + } } }, "addPassword": { @@ -4239,6 +4623,12 @@ } }, "common": { + "previous": "Previous", + "next": "Next", + "collapse": "Collapse", + "expand": "Expand", + "collapsed": "collapsed", + "lines": "lines", "copy": "Copy", "copied": "Copied!", "refresh": "Refresh", @@ -4275,6 +4665,12 @@ } }, "apiKeys": { + "intro": "Use your API key to programmatically access Stirling PDF's processing capabilities.", + "docsTitle": "API Documentation", + "docsDescription": "Learn more about integrating with Stirling PDF:", + "docsLink": "API Documentation", + "schemaLink": "API Schema Reference", + "usage": "Include this key in the X-API-KEY header with all API requests.", "description": "Your API key for accessing Stirling's suite of PDF tools. Copy it to your project or refresh to generate a new one.", "publicKeyAriaLabel": "Public API key", "copyKeyAriaLabel": "Copy API key", @@ -4394,34 +4790,6 @@ "undoStorageError": "Undo completed but some files could not be saved to storage", "undoSuccess": "Operation undone successfully", "unsupported": "Unsupported", - "signup": { - "title": "Create an account", - "subtitle": "Join Stirling PDF to get started", - "name": "Name", - "email": "Email", - "password": "Password", - "confirmPassword": "Confirm password", - "enterName": "Enter your name", - "enterEmail": "Enter your email", - "enterPassword": "Enter your password", - "confirmPasswordPlaceholder": "Confirm password", - "or": "or", - "creatingAccount": "Creating Account...", - "signUp": "Sign Up", - "alreadyHaveAccount": "Already have an account? Sign in", - "pleaseFillAllFields": "Please fill in all fields", - "passwordsDoNotMatch": "Passwords do not match", - "passwordTooShort": "Password must be at least 6 characters long", - "invalidEmail": "Please enter a valid email address", - "checkEmailConfirmation": "Check your email for a confirmation link to complete your registration.", - "accountCreatedSuccessfully": "Account created successfully! You can now sign in.", - "unexpectedError": "Unexpected error: {{message}}", - "useEmailInstead": "Use Email Instead", - "nameRequired": "Name is required", - "emailRequired": "Email is required", - "passwordRequired": "Password is required", - "confirmPasswordRequired": "Please confirm your password" - }, "onboarding": { "welcomeModal": { "title": "Welcome to Stirling PDF!", @@ -4431,7 +4799,7 @@ "maybeLater": "Maybe Later", "dontShowAgain": "Don't Show Again" }, - "allTools": "This is the All Tools panel, where you can browse and select from all available PDF tools.", + "allTools": "This is the Tools panel, where you can browse and select from all available PDF tools.", "selectCropTool": "Let's select the Crop tool to demonstrate how to use one of the tools.", "toolInterface": "This is the Crop tool interface. As you can see, there's not much there because we haven't added any PDF files to work with yet.", "filesButton": "The Files button on the Quick Access bar allows you to upload PDFs to use the tools on.", @@ -4466,162 +4834,6 @@ "adminTools": "Finally, we have advanced administration tools like Auditing to track system activity and Usage Analytics to monitor how your users interact with the platform.", "wrapUp": "That's the admin tour! You've seen the enterprise features that make Stirling PDF a powerful, customisable solution for organisations. Access this tour anytime from the Help menu." }, - "pdfTextEditor": { - "viewLabel": "PDF Text Editor", - "title": "PDF Editor", - "badges": { - "unsaved": "Unsaved changes", - "modified": "Edited", - "earlyAccess": "Early Access - Pre-Alpha" - }, - "controls": { - "title": "Document Controls" - }, - "actions": { - "load": "Load File", - "reset": "Reset Changes", - "downloadJson": "Download JSON", - "generatePdf": "Generate PDF" - }, - "currentFile": "Current file: {{name}}", - "pageSummary": "Page {{number}} of {{total}}", - "groupList": "Detected Text Groups", - "fontSizeValue": "{{size}}pt", - "noTextOnPage": "No editable text was detected on this page.", - "emptyGroup": "[Empty Group]", - "imageLabel": "Placed image", - "pagePreviewAlt": "Page preview", - "empty": { - "title": "No document loaded", - "subtitle": "Load a PDF or JSON file to begin editing text content." - }, - "converting": "Converting PDF to editable format...", - "conversionFailed": "Failed to convert PDF. Please try again.", - "errors": { - "invalidJson": "Unable to read the JSON file. Ensure it was generated by the PDF to JSON tool.", - "pdfConversion": "Unable to convert the edited JSON back into a PDF." - }, - "options": { - "title": "Viewing options", - "autoScaleText": { - "title": "Auto-scale text to fit boxes", - "description": "Automatically scales text horizontally to fit within its original bounding box when font rendering differs from PDF." - }, - "groupingMode": { - "title": "Text Grouping Mode", - "autoDescription": "Automatically detects page type and groups text appropriately.", - "paragraphDescription": "Groups aligned lines into multi-line paragraph text boxes.", - "singleLineDescription": "Keeps each PDF text line as a separate text box." - }, - "forceSingleElement": { - "title": "Lock edited text to a single PDF element", - "description": "When enabled, the editor exports each edited text box as one PDF text element to avoid overlapping glyphs or mixed fonts." - } - }, - "pageType": { - "paragraph": "Paragraph page", - "sparse": "Sparse text" - }, - "groupingMode": { - "auto": "Auto", - "paragraph": "Paragraph", - "singleLine": "Single Line" - }, - "modeChange": { - "title": "Confirm Mode Change", - "warning": "Changing the text grouping mode will reset all unsaved changes. Are you sure you want to continue?", - "cancel": "Cancel", - "confirm": "Reset and Change Mode" - }, - "welcomeBanner": { - "title": "Welcome to PDF Text Editor (Early Access)", - "experimental": "This is an experimental feature in active development. Expect some instability and issues during use.", - "howItWorks": "This tool converts your PDF to an editable format where you can modify text content and reposition images. Changes are saved back as a new PDF.", - "bestFor": "Works Best With:", - "bestFor1": "Simple PDFs containing primarily text and images", - "bestFor2": "Documents with standard paragraph formatting", - "bestFor3": "Letters, essays, reports, and basic documents", - "notIdealFor": "Not Ideal For:", - "notIdealFor1": "PDFs with special formatting like bullet points, tables, or multi-column layouts", - "notIdealFor2": "Magazines, brochures, or heavily designed documents", - "notIdealFor3": "Instruction manuals with complex layouts", - "limitations": "Current Limitations:", - "limitation1": "Font rendering may differ slightly from the original PDF", - "limitation2": "Complex graphics, form fields, and annotations are preserved but not editable", - "limitation3": "Large files may take time to convert and process", - "knownIssues": "Known Issues (Being Fixed):", - "issue1": "Text colour is not currently preserved (will be added soon)", - "issue2": "Paragraph mode has more alignment and spacing issues - Single Line mode recommended", - "issue3": "The preview display differs from the exported PDF - exported PDFs are closer to the original", - "issue4": "Rotated text alignment may need manual adjustment", - "issue5": "Transparency and layering effects may vary from original", - "feedback": "This is an early access feature. Please report any issues you encounter to help us improve!", - "gotIt": "Got it", - "dontShowAgain": "Don't show again" - }, - "disclaimer": { - "heading": "Preview limitations", - "textFocus": "This workspace focuses on editing text and repositioning embedded images. Complex page artwork, form widgets, and layered graphics are preserved for export but are not fully editable here.", - "previewVariance": "Some visuals (such as table borders, shapes, or annotation appearances) may not display exactly in the preview. The exported PDF keeps the original drawing commands whenever possible.", - "alpha": "This alpha viewer is still evolving—certain fonts, colours, transparency effects, and layout details may shift slightly. Please double-check the generated PDF before sharing." - }, - "stages": { - "uploading": "Uploading", - "initializing": "Initializing", - "loading": "Loading", - "normalizing": "Normalizing", - "parsing": "Parsing", - "processing": "Processing", - "fonts": "Fonts", - "text": "Text Extraction", - "images": "Images", - "annotations": "Annotations", - "metadata": "Metadata", - "serializing": "Finalizing", - "complete": "Complete" - }, - "fontAnalysis": { - "currentPageFonts": "Fonts on this page", - "allFonts": "All fonts", - "perfectTitle": "All Fonts Available", - "perfectMessage": "All fonts are fully available. You can edit and add text without issues in exported PDFs.", - "warningTitle": "Font Limitations", - "warningMessage": "Some fonts have limitations. Existing text will export correctly, but adding new text may cause issues.", - "infoTitle": "Font Information", - "infoMessage": "Font reproduction information available.", - "summary": "Font Summary", - "font": "font", - "fonts": "fonts", - "perfect": "perfect", - "subset": "subset", - "fallback": "fallback", - "missing": "missing", - "fontDetails": "Font Details", - "details": "Font Details", - "embedded": "Embedded", - "type": "Type", - "encoding": "Encoding", - "webFormat": "Web Format", - "standard14": "Standard PDF Font", - "warnings": "Warnings", - "suggestions": "Notes" - }, - "manual": { - "mergeTooltip": "Merge selected boxes into a single paragraph", - "merge": "Merge selection", - "ungroupTooltip": "Split paragraph back into separate lines", - "ungroup": "Ungroup selection", - "widthMenu": "Width options", - "expandWidth": "Expand to page edge", - "resetWidth": "Reset width", - "resizeHandle": "Adjust text width" - }, - "options": { - "manualGrouping": { - "descriptionInline": "Tip: Hold Ctrl (Cmd) or Shift to multi-select text boxes. A floating toolbar will appear above the selection so you can merge, ungroup, or adjust widths." - } - } - }, "workspace": { "title": "Workspace", "people": { @@ -4630,8 +4842,10 @@ "loading": "Loading people...", "searchMembers": "Search members...", "addMembers": "Add Members", - "inviteMembers": "Invite Members", - "inviteMembers.subtitle": "Type or paste in emails below, separated by commas. Your workspace will be billed by members.", + "inviteMembers": { + "label": "Invite Members", + "subtitle": "Type or paste in emails below, separated by commas. Your workspace will be billed by members." + }, "user": "User", "role": "Role", "team": "Team", @@ -4645,7 +4859,8 @@ "admin": "Admin", "roleDescriptions": { "admin": "Can manage settings and invite members, with full administrative access.", - "member": "Can view and edit shared files, but cannot manage workspace settings or members." + "member": "Can view and edit shared files, but cannot manage workspace settings or members.", + "user": "User" }, "editRole": "Edit Role", "enable": "Enable", @@ -4726,6 +4941,7 @@ "copied": "Link copied to clipboard", "success": "Invite link generated successfully", "successWithEmail": "Invite link generated and sent via email", + "emailSent": "Invite link generated and sent via email", "emailFailed": "Invite link generated, but email failed", "emailFailedDetails": "Error: {0}. Please share the invite link manually.", "error": "Failed to generate invite link", @@ -5066,6 +5282,8 @@ "backendHealth": { "checking": "Checking backend status...", "online": "Backend Online", - "offline": "Backend Offline" + "offline": "Backend Offline", + "starting": "Backend starting up...", + "wait": "Please wait for the backend to finish launching and try again." } } diff --git a/frontend/public/locales/en-US/translation.json b/frontend/public/locales/en-US/translation.json index 10e923c90..76f89ff4a 100644 --- a/frontend/public/locales/en-US/translation.json +++ b/frontend/public/locales/en-US/translation.json @@ -3967,7 +3967,7 @@ "account": "Account", "config": "Config", "adminSettings": "Admin Settings", - "allTools": "All Tools" + "allTools": "Tools" }, "admin": { "error": "Error", diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json index d845a2dc9..a6dffc881 100644 --- a/frontend/src-tauri/tauri.conf.json +++ b/frontend/src-tauri/tauri.conf.json @@ -50,6 +50,12 @@ "deb": { "desktopTemplate": "stirling-pdf.desktop" } + }, + "macOS": { + "minimumSystemVersion": "10.15", + "signingIdentity": null, + "entitlements": null, + "providerShortName": null } }, "plugins": { diff --git a/frontend/src/core/components/AppProviders.tsx b/frontend/src/core/components/AppProviders.tsx index 3bf96f29f..34d0e9e06 100644 --- a/frontend/src/core/components/AppProviders.tsx +++ b/frontend/src/core/components/AppProviders.tsx @@ -15,6 +15,7 @@ import { SignatureProvider } from "@app/contexts/SignatureContext"; import { OnboardingProvider } from "@app/contexts/OnboardingContext"; import { TourOrchestrationProvider } from "@app/contexts/TourOrchestrationContext"; import { AdminTourOrchestrationProvider } from "@app/contexts/AdminTourOrchestrationContext"; +import { PageEditorProvider } from "@app/contexts/PageEditorContext"; import ErrorBoundary from "@app/components/shared/ErrorBoundary"; import { useScarfTracking } from "@app/hooks/useScarfTracking"; import { useAppInitialization } from "@app/hooks/useAppInitialization"; @@ -64,15 +65,17 @@ export function AppProviders({ children, appConfigRetryOptions, appConfigProvide - - - - - {children} - - - - + + + + + + {children} + + + + + diff --git a/frontend/src/core/components/layout/Workbench.module.css b/frontend/src/core/components/layout/Workbench.module.css index 602110e4b..73d2e402f 100644 --- a/frontend/src/core/components/layout/Workbench.module.css +++ b/frontend/src/core/components/layout/Workbench.module.css @@ -1,21 +1,21 @@ -.workbench-scrollable { +.workbenchScrollable { overflow-y: auto !important; overflow-x: hidden !important; } -.workbench-scrollable::-webkit-scrollbar { +.workbenchScrollable::-webkit-scrollbar { width: 0.375rem; } -.workbench-scrollable::-webkit-scrollbar-track { +.workbenchScrollable::-webkit-scrollbar-track { background: transparent; } -.workbench-scrollable::-webkit-scrollbar-thumb { +.workbenchScrollable::-webkit-scrollbar-thumb { background-color: var(--mantine-color-gray-4); border-radius: 0.1875rem; } -.workbench-scrollable::-webkit-scrollbar-thumb:hover { +.workbenchScrollable::-webkit-scrollbar-thumb:hover { background-color: var(--mantine-color-gray-5); } diff --git a/frontend/src/core/components/layout/Workbench.tsx b/frontend/src/core/components/layout/Workbench.tsx index 4b3d1ebc8..f6477c67a 100644 --- a/frontend/src/core/components/layout/Workbench.tsx +++ b/frontend/src/core/components/layout/Workbench.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { Box } from '@mantine/core'; import { useRainbowThemeContext } from '@app/components/shared/RainbowThemeProvider'; import { useToolWorkflow } from '@app/contexts/ToolWorkflowContext'; @@ -187,18 +186,16 @@ export default function Workbench() { {/* Main content area */} 0 ? '3.5rem' : '0'), - overflow: currentView === 'viewer' || !isBaseWorkbench(currentView) ? 'hidden' : undefined, }} > {renderMainContent()}