mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-27 06:39:24 +00:00
remove stuff
This commit is contained in:
parent
ce417469d4
commit
4c2c40dc4f
@ -5,8 +5,8 @@ import java.io.IOException;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for handling exceptions with consistent English error messages. Frontend will
|
* Utility class for handling exceptions with internationalized error messages. Provides consistent
|
||||||
* handle translation to user's language.
|
* error handling and user-friendly messages across the application.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ExceptionUtils {
|
public class ExceptionUtils {
|
||||||
@ -32,12 +32,15 @@ public class ExceptionUtils {
|
|||||||
String message;
|
String message;
|
||||||
if (context != null && !context.isEmpty()) {
|
if (context != null && !context.isEmpty()) {
|
||||||
message =
|
message =
|
||||||
String.format(
|
I18nUtils.getMessage(
|
||||||
"Error %s: PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation.",
|
"error.pdfCorruptedDuring",
|
||||||
|
"Error {0}: PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation.",
|
||||||
context);
|
context);
|
||||||
} else {
|
} else {
|
||||||
message =
|
message =
|
||||||
"PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation.";
|
I18nUtils.getMessage(
|
||||||
|
"error.pdfCorrupted",
|
||||||
|
"PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation.");
|
||||||
}
|
}
|
||||||
return new IOException(message, cause);
|
return new IOException(message, cause);
|
||||||
}
|
}
|
||||||
@ -50,7 +53,9 @@ public class ExceptionUtils {
|
|||||||
*/
|
*/
|
||||||
public static IOException createMultiplePdfCorruptedException(Exception cause) {
|
public static IOException createMultiplePdfCorruptedException(Exception cause) {
|
||||||
String message =
|
String message =
|
||||||
"One or more PDF files appear to be corrupted or damaged. Please try using the 'Repair PDF' feature on each file first before attempting to merge them.";
|
I18nUtils.getMessage(
|
||||||
|
"error.pdfCorruptedMultiple",
|
||||||
|
"One or more PDF files appear to be corrupted or damaged. Please try using the 'Repair PDF' feature on each file first before attempting to merge them.");
|
||||||
return new IOException(message, cause);
|
return new IOException(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +67,10 @@ public class ExceptionUtils {
|
|||||||
*/
|
*/
|
||||||
public static IOException createPdfEncryptionException(Exception cause) {
|
public static IOException createPdfEncryptionException(Exception cause) {
|
||||||
String message =
|
String message =
|
||||||
"The PDF appears to have corrupted encryption data. This can happen when the PDF was created with incompatible encryption methods. Please try using the 'Repair PDF' feature first, or contact the document creator for a new copy.";
|
I18nUtils.getMessage(
|
||||||
|
"error.pdfEncryption",
|
||||||
|
"The PDF appears to have corrupted encryption data. This can happen when the PDF was created with incompatible encryption methods. Please try using the 'Repair PDF' feature first, or contact the document creator for a new copy.",
|
||||||
|
cause.getMessage());
|
||||||
return new IOException(message, cause);
|
return new IOException(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +82,9 @@ public class ExceptionUtils {
|
|||||||
*/
|
*/
|
||||||
public static IOException createPdfPasswordException(Exception cause) {
|
public static IOException createPdfPasswordException(Exception cause) {
|
||||||
String message =
|
String message =
|
||||||
"The PDF Document is passworded and either the password was not provided or was incorrect";
|
I18nUtils.getMessage(
|
||||||
|
"error.pdfPassword",
|
||||||
|
"The PDF Document is passworded and either the password was not provided or was incorrect");
|
||||||
return new IOException(message, cause);
|
return new IOException(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,74 +97,57 @@ public class ExceptionUtils {
|
|||||||
*/
|
*/
|
||||||
public static IOException createFileProcessingException(String operation, Exception cause) {
|
public static IOException createFileProcessingException(String operation, Exception cause) {
|
||||||
String message =
|
String message =
|
||||||
String.format(
|
I18nUtils.getMessage(
|
||||||
"An error occurred while processing the file during %s operation: %s",
|
"error.fileProcessing",
|
||||||
operation, cause.getMessage());
|
"An error occurred while processing the file during {0} operation: {1}",
|
||||||
|
operation,
|
||||||
|
cause.getMessage());
|
||||||
return new IOException(message, cause);
|
return new IOException(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a generic IOException with readable English message.
|
* Create a generic IOException with internationalized message.
|
||||||
*
|
*
|
||||||
* @param messageKey the i18n message key for frontend translation
|
* @param messageKey the i18n message key
|
||||||
* @param defaultMessage the English message template
|
* @param defaultMessage the default message if i18n is not available
|
||||||
* @param cause the original exception
|
* @param cause the original exception
|
||||||
* @param args arguments for message formatting
|
* @param args optional arguments for the message
|
||||||
* @return IOException with readable English message
|
* @return IOException with user-friendly message
|
||||||
*/
|
*/
|
||||||
public static IOException createIOException(
|
public static IOException createIOException(
|
||||||
String messageKey, String defaultMessage, Exception cause, Object... args) {
|
String messageKey, String defaultMessage, Exception cause, Object... args) {
|
||||||
String message = String.format(defaultMessage, args);
|
String message = I18nUtils.getMessage(messageKey, defaultMessage, args);
|
||||||
return new IOException(message, cause);
|
return new IOException(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a generic RuntimeException with readable English message.
|
* Create a generic RuntimeException with internationalized message.
|
||||||
*
|
*
|
||||||
* @param messageKey the i18n message key for frontend translation
|
* @param messageKey the i18n message key
|
||||||
* @param defaultMessage the English message template
|
* @param defaultMessage the default message if i18n is not available
|
||||||
* @param cause the original exception
|
* @param cause the original exception
|
||||||
* @param args arguments for message formatting
|
* @param args optional arguments for the message
|
||||||
* @return RuntimeException with readable English message
|
* @return RuntimeException with user-friendly message
|
||||||
*/
|
*/
|
||||||
public static RuntimeException createRuntimeException(
|
public static RuntimeException createRuntimeException(
|
||||||
String messageKey, String defaultMessage, Exception cause, Object... args) {
|
String messageKey, String defaultMessage, Exception cause, Object... args) {
|
||||||
String message = String.format(defaultMessage, args);
|
String message = I18nUtils.getMessage(messageKey, defaultMessage, args);
|
||||||
if (messageKey != null) {
|
|
||||||
return new TranslatableException(message, messageKey, args);
|
|
||||||
}
|
|
||||||
return new RuntimeException(message, cause);
|
return new RuntimeException(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an IllegalArgumentException with readable English message.
|
* Create an IllegalArgumentException with internationalized message.
|
||||||
*
|
*
|
||||||
* @param messageKey the i18n message key for frontend translation
|
* @param messageKey the i18n message key
|
||||||
* @param defaultMessage the English message template
|
* @param defaultMessage the default message if i18n is not available
|
||||||
* @param args arguments for message formatting
|
* @param args optional arguments for the message
|
||||||
* @return IllegalArgumentException with readable English message
|
* @return IllegalArgumentException with user-friendly message
|
||||||
*/
|
*/
|
||||||
public static IllegalArgumentException createIllegalArgumentException(
|
public static IllegalArgumentException createIllegalArgumentException(
|
||||||
String messageKey, String defaultMessage, Object... args) {
|
String messageKey, String defaultMessage, Object... args) {
|
||||||
String message = String.format(defaultMessage, args);
|
String message = I18nUtils.getMessage(messageKey, defaultMessage, args);
|
||||||
return new TranslatableException(message, messageKey, args);
|
System.out.println("######## Test " + message);
|
||||||
}
|
return new IllegalArgumentException(message);
|
||||||
|
|
||||||
/** Create common validation exceptions with translation support. */
|
|
||||||
public static IllegalArgumentException createInvalidArgumentException(String argumentName) {
|
|
||||||
return createIllegalArgumentException(
|
|
||||||
"error.invalidArgument", "Invalid argument: {0}", argumentName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IllegalArgumentException createInvalidArgumentException(
|
|
||||||
String argumentName, String value) {
|
|
||||||
return createIllegalArgumentException(
|
|
||||||
"error.invalidFormat", "Invalid {0} format: {1}", argumentName, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IllegalArgumentException createNullArgumentException(String argumentName) {
|
|
||||||
return createIllegalArgumentException(
|
|
||||||
"error.argumentRequired", "{0} must not be null", argumentName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create file validation exceptions. */
|
/** Create file validation exceptions. */
|
||||||
@ -319,7 +312,7 @@ public class ExceptionUtils {
|
|||||||
public static void logException(String operation, Exception e) {
|
public static void logException(String operation, Exception e) {
|
||||||
if (e instanceof IOException && PdfErrorUtils.isCorruptedPdfError((IOException) e)) {
|
if (e instanceof IOException && PdfErrorUtils.isCorruptedPdfError((IOException) e)) {
|
||||||
log.warn("PDF corruption detected during {}: {}", operation, e.getMessage());
|
log.warn("PDF corruption detected during {}: {}", operation, e.getMessage());
|
||||||
} else if (e instanceof IOException io && (isEncryptionError(io) || isPasswordError(io))) {
|
} else if (isEncryptionError((IOException) e) || isPasswordError((IOException) e)) {
|
||||||
log.info("PDF security issue during {}: {}", operation, e.getMessage());
|
log.info("PDF security issue during {}: {}", operation, e.getMessage());
|
||||||
} else {
|
} else {
|
||||||
log.error("Unexpected error during {}", operation, e);
|
log.error("Unexpected error during {}", operation, e);
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package stirling.software.common.util;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception that carries translation information for frontend internationalization. The
|
|
||||||
* GlobalExceptionHandler extracts this info to create structured error responses.
|
|
||||||
*/
|
|
||||||
public class TranslatableException extends IllegalArgumentException {
|
|
||||||
|
|
||||||
private final String translationKey;
|
|
||||||
private final Object[] translationArgs;
|
|
||||||
|
|
||||||
public TranslatableException(String message, String translationKey, Object... translationArgs) {
|
|
||||||
super(message);
|
|
||||||
this.translationKey = translationKey;
|
|
||||||
this.translationArgs = translationArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTranslationKey() {
|
|
||||||
return translationKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object[] getTranslationArgs() {
|
|
||||||
return translationArgs;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,196 +0,0 @@
|
|||||||
# Exception Handling Guide
|
|
||||||
|
|
||||||
This guide shows how to use the centralized exception handling utilities for consistent error messages with frontend translation support.
|
|
||||||
|
|
||||||
## Architecture Overview
|
|
||||||
|
|
||||||
The system uses a **backend-frontend translation split**:
|
|
||||||
- **Backend**: Creates structured JSON error responses with translation keys and English fallbacks
|
|
||||||
- **Frontend**: Translates error messages to user's language using JavaScript
|
|
||||||
|
|
||||||
## New Utilities
|
|
||||||
|
|
||||||
### 1. ExceptionUtils
|
|
||||||
Creates `TranslatableException` instances with structured translation data for frontend.
|
|
||||||
Backend uses hardcoded English strings for readability.
|
|
||||||
|
|
||||||
### 2. GlobalExceptionHandler
|
|
||||||
Converts exceptions to structured JSON responses with translation information.
|
|
||||||
|
|
||||||
### 3. MessageFormatter.js
|
|
||||||
Frontend utility for translating error messages with placeholder replacement.
|
|
||||||
|
|
||||||
### 4. GlobalModelAdvice
|
|
||||||
Loads error translations using Spring's MessageSource and adds them to all templates.
|
|
||||||
|
|
||||||
## Usage Examples
|
|
||||||
|
|
||||||
### Basic PDF Exception Handling
|
|
||||||
|
|
||||||
**Before:**
|
|
||||||
```java
|
|
||||||
try {
|
|
||||||
// PDF operation
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (PdfErrorUtils.isCorruptedPdfError(e)) {
|
|
||||||
throw new IOException("PDF file is corrupted...", e);
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**After:**
|
|
||||||
```java
|
|
||||||
try {
|
|
||||||
// PDF operation
|
|
||||||
} catch (IOException e) {
|
|
||||||
ExceptionUtils.logException("operation name", e);
|
|
||||||
throw ExceptionUtils.handlePdfException(e);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Creating Specific Exception Types
|
|
||||||
|
|
||||||
```java
|
|
||||||
// PDF corruption
|
|
||||||
throw ExceptionUtils.createPdfCorruptedException(originalException);
|
|
||||||
|
|
||||||
// PDF corruption with context
|
|
||||||
throw ExceptionUtils.createPdfCorruptedException("during merge", originalException);
|
|
||||||
|
|
||||||
// Multiple PDF corruption (for merge operations)
|
|
||||||
throw ExceptionUtils.createMultiplePdfCorruptedException(originalException);
|
|
||||||
|
|
||||||
// PDF encryption issues
|
|
||||||
throw ExceptionUtils.createPdfEncryptionException(originalException);
|
|
||||||
|
|
||||||
// File processing errors
|
|
||||||
throw ExceptionUtils.createFileProcessingException("merge", originalException);
|
|
||||||
|
|
||||||
// Generic exceptions with i18n
|
|
||||||
throw ExceptionUtils.createIOException("error.customKey", "Default message", originalException, arg1, arg2);
|
|
||||||
```
|
|
||||||
|
|
||||||
### JSON Error Response Format
|
|
||||||
|
|
||||||
The system returns structured JSON error responses with translation support:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "Bad Request",
|
|
||||||
"message": "DPI value 500 exceeds maximum safe limit of 300. High DPI values can cause memory issues and crashes. Please use a lower DPI value.",
|
|
||||||
"trace": "java.lang.IllegalArgumentException: ...",
|
|
||||||
"translationKey": "error.dpiExceedsLimit",
|
|
||||||
"translationArgs": ["500", "300"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Key Features:**
|
|
||||||
- `message`: Readable English hardcoded in backend for API consumers
|
|
||||||
- `translationKey`: Frontend translation key
|
|
||||||
- `translationArgs`: Arguments for placeholder replacement
|
|
||||||
- Backend sends English, frontend translates to user's language
|
|
||||||
|
|
||||||
### Frontend Translation with MessageFormatter
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Frontend translates using Spring's loaded translation data
|
|
||||||
const displayMessage = window.MessageFormatter.translate(
|
|
||||||
json.translationKey,
|
|
||||||
json.translationArgs,
|
|
||||||
json.message // English fallback
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Controller Pattern
|
|
||||||
|
|
||||||
```java
|
|
||||||
@RestController
|
|
||||||
public class MyController {
|
|
||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
|
||||||
|
|
||||||
@PostMapping("/process")
|
|
||||||
public ResponseEntity<byte[]> processFile(@ModelAttribute FileRequest request) throws IOException {
|
|
||||||
try {
|
|
||||||
PDDocument document = pdfDocumentFactory.load(request.getFileInput());
|
|
||||||
|
|
||||||
// Process document...
|
|
||||||
|
|
||||||
return WebResponseUtils.pdfDocToWebResponse(document, "output.pdf");
|
|
||||||
} catch (IOException e) {
|
|
||||||
ExceptionUtils.logException("file processing", e);
|
|
||||||
throw ExceptionUtils.handlePdfException(e, "during processing");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Message Keys
|
|
||||||
|
|
||||||
When creating new exception messages, add the corresponding i18n keys to `messages_en_GB.properties` only. The translation scripts will automatically propagate them to other language files during merge.
|
|
||||||
|
|
||||||
### Key Categories Available:
|
|
||||||
|
|
||||||
**Core PDF Operations:**
|
|
||||||
- `error.pdfCorrupted` - General PDF corruption
|
|
||||||
- `error.pdfCorruptedDuring` - Corruption with context (takes operation parameter)
|
|
||||||
- `error.pdfEncryption` - Encryption/decryption issues
|
|
||||||
- `error.pdfPassword` - Password-related errors
|
|
||||||
|
|
||||||
**File Processing:**
|
|
||||||
- `error.fileProcessing` - Generic file operation errors (takes operation and error message)
|
|
||||||
- `error.commandFailed` - External tool failures (takes tool name)
|
|
||||||
|
|
||||||
**Validation:**
|
|
||||||
- `error.invalidArgument` - Invalid parameters (takes argument description)
|
|
||||||
- `error.invalidFormat` - Invalid file formats (takes format type)
|
|
||||||
- `error.optionsNotSpecified` - Missing required options (takes option type)
|
|
||||||
|
|
||||||
**System Requirements:**
|
|
||||||
- `error.toolNotInstalled` - Missing tools (takes tool name)
|
|
||||||
- `error.toolRequired` - Tool requirements (takes tool and operation)
|
|
||||||
|
|
||||||
### Creating New Keys:
|
|
||||||
When adding new error scenarios, follow the naming pattern:
|
|
||||||
- `error.[category].[specific]` (e.g., `error.ocr.languageRequired`)
|
|
||||||
- Keep parameter placeholders simple and translatable
|
|
||||||
- Avoid putting full sentences in `{0}` parameters
|
|
||||||
|
|
||||||
### Parameter Best Practices:
|
|
||||||
|
|
||||||
**✅ Good Examples:**
|
|
||||||
```java
|
|
||||||
// Simple identifiers or values
|
|
||||||
ExceptionUtils.createIllegalArgumentException("error.invalidArgument", "Invalid argument: {0}", "angle");
|
|
||||||
ExceptionUtils.createRuntimeException("error.commandFailed", "{0} command failed", null, "Tesseract");
|
|
||||||
ExceptionUtils.createIllegalArgumentException("error.invalidFormat", "Invalid {0} format", "PDF");
|
|
||||||
```
|
|
||||||
|
|
||||||
**❌ Bad Examples:**
|
|
||||||
```java
|
|
||||||
// Full sentences that can't be translated
|
|
||||||
ExceptionUtils.createIllegalArgumentException("error.invalidArgument", "Invalid argument: {0}", "angle must be multiple of 90");
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution for Complex Messages:**
|
|
||||||
Create specific i18n keys instead:
|
|
||||||
```java
|
|
||||||
// Instead of complex parameters, create specific keys
|
|
||||||
ExceptionUtils.createIllegalArgumentException("error.angleNotMultipleOf90", "Angle must be a multiple of 90");
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing Error Messages
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Test
|
|
||||||
public void testErrorMessageLocalization() {
|
|
||||||
// Test with different locales
|
|
||||||
LocaleContextHolder.setLocale(Locale.FRENCH);
|
|
||||||
|
|
||||||
IOException exception = ExceptionUtils.createPdfCorruptedException(new RuntimeException("test"));
|
|
||||||
|
|
||||||
// Verify message is in French
|
|
||||||
assertThat(exception.getMessage()).contains("PDF");
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,94 +0,0 @@
|
|||||||
package stirling.software.SPDF.config;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Global exception handler that creates structured error responses with translation information for
|
|
||||||
* frontend internationalization support.
|
|
||||||
*/
|
|
||||||
@ControllerAdvice
|
|
||||||
@Slf4j
|
|
||||||
public class GlobalExceptionHandler {
|
|
||||||
|
|
||||||
public static class ErrorResponse {
|
|
||||||
public String error;
|
|
||||||
public String message;
|
|
||||||
public String trace;
|
|
||||||
public String translationKey;
|
|
||||||
public List<String> translationArgs;
|
|
||||||
|
|
||||||
public ErrorResponse(
|
|
||||||
String error,
|
|
||||||
String message,
|
|
||||||
String trace,
|
|
||||||
String translationKey,
|
|
||||||
List<String> translationArgs) {
|
|
||||||
this.error = error;
|
|
||||||
this.message = message;
|
|
||||||
this.trace = trace;
|
|
||||||
this.translationKey = translationKey;
|
|
||||||
this.translationArgs = translationArgs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(stirling.software.common.util.TranslatableException.class)
|
|
||||||
public ResponseEntity<ErrorResponse> handleTranslatableException(
|
|
||||||
stirling.software.common.util.TranslatableException e) {
|
|
||||||
List<String> translationArgs = null;
|
|
||||||
if (e.getTranslationArgs() != null) {
|
|
||||||
translationArgs = Arrays.stream(e.getTranslationArgs()).map(String::valueOf).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorResponse errorResponse =
|
|
||||||
new ErrorResponse(
|
|
||||||
"Bad Request",
|
|
||||||
e.getMessage(),
|
|
||||||
getStackTrace(e),
|
|
||||||
e.getTranslationKey(),
|
|
||||||
translationArgs);
|
|
||||||
|
|
||||||
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(IllegalArgumentException.class)
|
|
||||||
public ResponseEntity<ErrorResponse> handleIllegalArgumentException(
|
|
||||||
IllegalArgumentException e) {
|
|
||||||
ErrorResponse errorResponse =
|
|
||||||
new ErrorResponse("Bad Request", e.getMessage(), getStackTrace(e), null, null);
|
|
||||||
|
|
||||||
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(RuntimeException.class)
|
|
||||||
public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException e) {
|
|
||||||
ErrorResponse errorResponse =
|
|
||||||
new ErrorResponse(
|
|
||||||
"Internal Server Error", e.getMessage(), getStackTrace(e), null, null);
|
|
||||||
|
|
||||||
return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(Exception.class)
|
|
||||||
public ResponseEntity<ErrorResponse> handleGenericException(Exception e) {
|
|
||||||
ErrorResponse errorResponse =
|
|
||||||
new ErrorResponse(
|
|
||||||
"Internal Server Error", e.getMessage(), getStackTrace(e), null, null);
|
|
||||||
|
|
||||||
return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getStackTrace(Exception e) {
|
|
||||||
if (e.getCause() != null) {
|
|
||||||
return e.getCause().toString();
|
|
||||||
}
|
|
||||||
return e.getClass().getSimpleName() + ": " + e.getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import org.springframework.context.MessageSource;
|
|
||||||
import org.springframework.context.i18n.LocaleContextHolder;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* API endpoint for on-demand error message translation.
|
|
||||||
* Provides translations for error messages when needed instead of pre-loading all translations.
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api")
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Slf4j
|
|
||||||
public class TranslationController {
|
|
||||||
|
|
||||||
private final MessageSource messageSource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get translated error message for user's locale.
|
|
||||||
*
|
|
||||||
* @param key the translation key (e.g. "error.dpiExceedsLimit")
|
|
||||||
* @param args comma-separated arguments for message formatting
|
|
||||||
* @return translated message in user's locale
|
|
||||||
*/
|
|
||||||
@GetMapping("/translate")
|
|
||||||
public ResponseEntity<String> translate(
|
|
||||||
@RequestParam String key,
|
|
||||||
@RequestParam(required = false) String args) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
Object[] messageArgs = null;
|
|
||||||
if (args != null && !args.trim().isEmpty()) {
|
|
||||||
messageArgs = Arrays.stream(args.split(","))
|
|
||||||
.map(String::trim)
|
|
||||||
.toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
String translatedMessage = messageSource.getMessage(
|
|
||||||
key,
|
|
||||||
messageArgs,
|
|
||||||
LocaleContextHolder.getLocale()
|
|
||||||
);
|
|
||||||
|
|
||||||
return ResponseEntity.ok(translatedMessage);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug("Translation failed for key '{}': {}", key, e.getMessage());
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -330,9 +330,7 @@
|
|||||||
|
|
||||||
async function handleJsonResponse(response) {
|
async function handleJsonResponse(response) {
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
|
const errorMessage = JSON.stringify(json, null, 2);
|
||||||
// Check for password-related errors first
|
|
||||||
const errorMessage = json.message || '';
|
|
||||||
if (
|
if (
|
||||||
errorMessage.toLowerCase().includes('the password is incorrect') ||
|
errorMessage.toLowerCase().includes('the password is incorrect') ||
|
||||||
errorMessage.toLowerCase().includes('Password is not provided') ||
|
errorMessage.toLowerCase().includes('Password is not provided') ||
|
||||||
@ -342,24 +340,9 @@
|
|||||||
firstErrorOccurred = true;
|
firstErrorOccurred = true;
|
||||||
alert(pdfPasswordPrompt);
|
alert(pdfPasswordPrompt);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle structured error response with async translation
|
|
||||||
let displayMessage = 'Loading...'; // Brief loading state
|
|
||||||
|
|
||||||
if (json.translationKey && window.MessageFormatter) {
|
|
||||||
// Async translation with timeout and English fallback
|
|
||||||
displayMessage = await window.MessageFormatter.translateAsync(
|
|
||||||
json.translationKey,
|
|
||||||
json.translationArgs,
|
|
||||||
json.message // English fallback
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
displayMessage = json.message; // Direct English message
|
showErrorBanner(json.error + ':' + json.message, json.trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
showErrorBanner((json.error || 'Error') + ': ' + displayMessage, json.trace || '');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleResponse(blob, filename, considerViewOptions = false, isZip = false) {
|
async function handleResponse(blob, filename, considerViewOptions = false, isZip = false) {
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
/**
|
|
||||||
* Async translation utility with timeout and English fallback.
|
|
||||||
* Fetches translations on-demand from API with brief loading state.
|
|
||||||
*/
|
|
||||||
window.MessageFormatter = (function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Translate error message with async API call and fallback to English.
|
|
||||||
* Shows brief loading, attempts translation, falls back to English on timeout/error.
|
|
||||||
*
|
|
||||||
* @param {string} translationKey - The translation key
|
|
||||||
* @param {Array} translationArgs - Arguments for message formatting
|
|
||||||
* @param {string} fallbackMessage - English fallback message
|
|
||||||
* @param {number} timeout - Timeout in milliseconds (default: 500ms)
|
|
||||||
* @returns {Promise<string>} - Translated message or English fallback
|
|
||||||
*/
|
|
||||||
async function translateAsync(translationKey, translationArgs, fallbackMessage, timeout = 500) {
|
|
||||||
if (!translationKey) {
|
|
||||||
return fallbackMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const params = new URLSearchParams({ key: translationKey });
|
|
||||||
if (translationArgs && translationArgs.length > 0) {
|
|
||||||
params.append('args', translationArgs.join(','));
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(`${window.stirlingPDF.translationApiUrl}?${params}`, {
|
|
||||||
signal: controller.signal
|
|
||||||
});
|
|
||||||
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Translation API returned ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await response.text();
|
|
||||||
} catch (error) {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
console.debug('Translation failed, using English fallback:', error.message);
|
|
||||||
return fallbackMessage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { translateAsync };
|
|
||||||
})();
|
|
@ -86,7 +86,6 @@
|
|||||||
<script th:src="@{'/js/tab-container.js'}"></script>
|
<script th:src="@{'/js/tab-container.js'}"></script>
|
||||||
<script th:src="@{'/js/darkmode.js'}"></script>
|
<script th:src="@{'/js/darkmode.js'}"></script>
|
||||||
<script th:src="@{'/js/csrf.js'}"></script>
|
<script th:src="@{'/js/csrf.js'}"></script>
|
||||||
<script th:src="@{'/js/messageFormatter.js'}"></script>
|
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
|
|
||||||
function UpdatePosthogConsent(){
|
function UpdatePosthogConsent(){
|
||||||
@ -251,8 +250,8 @@
|
|||||||
window.stirlingPDF.uploadLimit = /*[[${@uploadLimitService.getUploadLimit()}]]*/ 0;
|
window.stirlingPDF.uploadLimit = /*[[${@uploadLimitService.getUploadLimit()}]]*/ 0;
|
||||||
window.stirlingPDF.uploadLimitExceededSingular = /*[[#{uploadLimitExceededSingular}]]*/ 'is too large. Maximum allowed size is';
|
window.stirlingPDF.uploadLimitExceededSingular = /*[[#{uploadLimitExceededSingular}]]*/ 'is too large. Maximum allowed size is';
|
||||||
window.stirlingPDF.uploadLimitExceededPlural = /*[[#{uploadLimitExceededPlural}]]*/ 'are too large. Maximum allowed size is';
|
window.stirlingPDF.uploadLimitExceededPlural = /*[[#{uploadLimitExceededPlural}]]*/ 'are too large. Maximum allowed size is';
|
||||||
// Translation API base URL for async error message translation
|
window.stirlingPDF.pdfCorruptedMessage = /*[[#{error.pdfInvalid}]]*/ 'The PDF file "{0}" appears to be corrupted or has an invalid structure. Please try using the \'Repair PDF\' feature to fix the file before proceeding.';
|
||||||
window.stirlingPDF.translationApiUrl = '/api/translate';
|
window.stirlingPDF.tryRepairMessage = /*[[#{error.tryRepair}]]*/ 'Try using the Repair PDF feature to fix corrupted files.';
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user