Properly sync US and GB translation files (#6635)

add en-US changes to SaaS, previously merged into main. So this is
effectively a main -> SaaS PR also. It seems to be all additive.

Also take the 230 ish missing translations from en-GB over to en-US
using a script, and also make and english spellings American when adding
them to the en-US file, and fix any existing American spellings in the
en-GB file.
This commit is contained in:
EthanHealy01
2026-06-12 11:18:25 +01:00
committed by GitHub
parent d363a1e957
commit eb2527fc7f
4 changed files with 1014 additions and 260 deletions
@@ -162,7 +162,7 @@ logOut = "Log out"
signedInAs = "Signed in as: {{email}}" signedInAs = "Signed in as: {{email}}"
[add-page-numbers] [add-page-numbers]
tags = "paginate,label,organize,index" tags = "paginate,label,organise,index"
[addAttachments] [addAttachments]
tags = "embed,attach,include,attachments,attach files,embed files,include files,add files,file attachment,associated files,supplementary files" tags = "embed,attach,include,attachments,attach files,embed files,include files,add files,file attachment,associated files,supplementary files"
@@ -271,7 +271,7 @@ saved = "Saved"
text = "Text" text = "Text"
[addPageNumbers] [addPageNumbers]
customize = "Customize Appearance" customize = "Customise Appearance"
customNumberDesc = "e.g., \"Page {n}\" or leave blank for just numbers" customNumberDesc = "e.g., \"Page {n}\" or leave blank for just numbers"
fontName = "Font Name" fontName = "Font Name"
fontSize = "Font Size" fontSize = "Font Size"
@@ -386,7 +386,7 @@ stampText = "Stamp Text"
stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines." stampTextDescription = "Use dynamic variables below. Use @@ for literal @. Use \\n for new lines."
stampType = "Stamp Type" stampType = "Stamp Type"
submit = "Submit" submit = "Submit"
tags = "Stamp, Add image, center image, Watermark, PDF, Embed, Customize,Customise" tags = "Stamp, Add image, centre image, Watermark, PDF, Embed, Customise,Customise"
timeDesc = "Current time" timeDesc = "Current time"
title = "Stamp PDF" title = "Stamp PDF"
totalPagesDesc = "Total number of pages" totalPagesDesc = "Total number of pages"
@@ -487,10 +487,10 @@ saved = "Saved"
text = "Text" text = "Text"
[adjust-contrast] [adjust-contrast]
tags = "color-correction,tune,modify,enhance,colour-correction" tags = "colour-correction,tune,modify,enhance,colour-correction"
[adjustContrast] [adjustContrast]
adjustColors = "Adjust Colors" adjustColors = "Adjust Colours"
basic = "Basic Adjustments" basic = "Basic Adjustments"
blue = "Blue" blue = "Blue"
brightness = "Brightness:" brightness = "Brightness:"
@@ -500,11 +500,11 @@ green = "Green"
noPreview = "Select a PDF to preview" noPreview = "Select a PDF to preview"
red = "Red" red = "Red"
saturation = "Saturation:" saturation = "Saturation:"
tags = "contrast,brightness,saturation,adjust colors,color correction,enhance,lighten,darken,improve quality,color balance,hue,vibrance" tags = "contrast,brightness,saturation,adjust colours,colour correction,enhance,lighten,darken,improve quality,colour balance,hue,vibrance"
title = "Adjust Colors/Contrast" title = "Adjust Colours/Contrast"
[adjustContrast.error] [adjustContrast.error]
failed = "Failed to adjust colors/contrast" failed = "Failed to adjust colours/contrast"
[adjustContrast.results] [adjustContrast.results]
title = "Adjusted PDF" title = "Adjusted PDF"
@@ -578,8 +578,8 @@ processing = "Processing"
title = "Advanced" title = "Advanced"
[admin.settings.advanced.disableSanitize] [admin.settings.advanced.disableSanitize]
description = "WARNING: Security risk - disabling HTML sanitization can lead to XSS vulnerabilities" description = "WARNING: Security risk - disabling HTML sanitisation can lead to XSS vulnerabilities"
label = "Disable HTML Sanitization" label = "Disable HTML Sanitisation"
[admin.settings.advanced.enableAlphaFunctionality] [admin.settings.advanced.enableAlphaFunctionality]
description = "Enable experimental and alpha-stage features (may be unstable)" description = "Enable experimental and alpha-stage features (may be unstable)"
@@ -621,7 +621,7 @@ description = "Maximum execution time"
label = "Timeout (minutes)" label = "Timeout (minutes)"
[admin.settings.advanced.tempFileManagement] [admin.settings.advanced.tempFileManagement]
description = "Configure temporary file storage and cleanup behavior" description = "Configure temporary file storage and cleanup behaviour"
label = "Temp File Management" label = "Temp File Management"
[admin.settings.advanced.tempFileManagement.baseTmpDir] [admin.settings.advanced.tempFileManagement.baseTmpDir]
@@ -703,7 +703,7 @@ mobileScannerImageResolutionDesc = "Resolution of uploaded images. \"Reduced\" s
mobileScannerPageFormat = "Page Format" mobileScannerPageFormat = "Page Format"
mobileScannerPageFormatDesc = "PDF page size for converted images. \"Keep\" uses original image dimensions." mobileScannerPageFormatDesc = "PDF page size for converted images. \"Keep\" uses original image dimensions."
mobileScannerStretchToFit = "Stretch to Fit" mobileScannerStretchToFit = "Stretch to Fit"
mobileScannerStretchToFitDesc = "Stretch images to fill the entire page. If disabled, images are centered with preserved aspect ratio." mobileScannerStretchToFitDesc = "Stretch images to fill the entire page. If disabled, images are centred with preserved aspect ratio."
pageFormatA4 = "A4 (210×297mm)" pageFormatA4 = "A4 (210×297mm)"
pageFormatKeep = "Keep (Original Dimensions)" pageFormatKeep = "Keep (Original Dimensions)"
pageFormatLetter = "Letter (8.5×11in)" pageFormatLetter = "Letter (8.5×11in)"
@@ -724,7 +724,7 @@ mobileScannerImageResolutionDesc = "Resolution of uploaded images. \"Reduced\" s
mobileScannerPageFormat = "Page Format" mobileScannerPageFormat = "Page Format"
mobileScannerPageFormatDesc = "PDF page size for converted images. \"Keep\" uses original image dimensions." mobileScannerPageFormatDesc = "PDF page size for converted images. \"Keep\" uses original image dimensions."
mobileScannerStretchToFit = "Stretch to Fit" mobileScannerStretchToFit = "Stretch to Fit"
mobileScannerStretchToFitDesc = "Stretch images to fill the entire page. If disabled, images are centered with preserved aspect ratio." mobileScannerStretchToFitDesc = "Stretch images to fill the entire page. If disabled, images are centred with preserved aspect ratio."
note = "Note: Requires Frontend URL to be configured. " note = "Note: Requires Frontend URL to be configured. "
pageFormatA4 = "A4 (210×297mm)" pageFormatA4 = "A4 (210×297mm)"
pageFormatKeep = "Keep (Original Dimensions)" pageFormatKeep = "Keep (Original Dimensions)"
@@ -903,8 +903,8 @@ label = "Disabled Endpoints"
placeholder = "Select endpoints to disable" placeholder = "Select endpoints to disable"
[admin.settings.enterpriseRequired] [admin.settings.enterpriseRequired]
message = "An Enterprise license is required to access {{featureName}}. You are viewing demo data for reference." message = "An Enterprise licence is required to access {{featureName}}. You are viewing demo data for reference."
title = "Enterprise License Required" title = "Enterprise Licence Required"
[admin.settings.features] [admin.settings.features]
description = "Configure optional features and functionality." description = "Configure optional features and functionality."
@@ -919,8 +919,8 @@ description = "Enable server-side certificate for \"Sign with Stirling-PDF\" opt
label = "Enable Server Certificate" label = "Enable Server Certificate"
[admin.settings.features.serverCertificate.organizationName] [admin.settings.features.serverCertificate.organizationName]
description = "Organization name for generated certificates" description = "Organisation name for generated certificates"
label = "Organization Name" label = "Organisation Name"
[admin.settings.features.serverCertificate.regenerateOnStartup] [admin.settings.features.serverCertificate.regenerateOnStartup]
description = "Generate new certificate on each application startup" description = "Generate new certificate on each application startup"
@@ -1063,7 +1063,7 @@ description = "URL or filename to cookie policy"
label = "Cookie Policy" label = "Cookie Policy"
[admin.settings.legal.disclaimer] [admin.settings.legal.disclaimer]
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." message = "By customising 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."
title = "Legal Responsibility Warning" title = "Legal Responsibility Warning"
[admin.settings.legal.impressum] [admin.settings.legal.impressum]
@@ -1161,7 +1161,7 @@ tipApiKey = "Tip: API-key mode needs no external IdP - simplest for self-host. T
title = "Connect an MCP client" title = "Connect an MCP client"
[admin.settings.mcp.issuerUri] [admin.settings.mcp.issuerUri]
description = "Your OAuth2 authorization server (must publish /.well-known/openid-configuration). Required when enabled." description = "Your OAuth2 authorisation server (must publish /.well-known/openid-configuration). Required when enabled."
label = "OAuth Issuer URL" label = "OAuth Issuer URL"
[admin.settings.mcp.jwksUri] [admin.settings.mcp.jwksUri]
@@ -1194,55 +1194,55 @@ message = "Plans data is not available at the moment."
title = "No data available" title = "No data available"
[admin.settings.premium] [admin.settings.premium]
description = "Configure your premium or enterprise license key." description = "Configure your premium or enterprise licence key."
license = "License Configuration" license = "Licence Configuration"
noInput = "Please provide a license key or file" noInput = "Please provide a licence key or file"
title = "Premium & Enterprise" title = "Premium & Enterprise"
[admin.settings.premium.currentLicense] [admin.settings.premium.currentLicense]
file = "Source: License file ({{path}})" file = "Source: Licence file ({{path}})"
key = "Source: License key" key = "Source: Licence key"
noInput = "Please provide a license key or upload a certificate file" noInput = "Please provide a licence key or upload a certificate file"
success = "Success" success = "Success"
title = "Active License" title = "Active Licence"
type = "Type: {{type}}" type = "Type: {{type}}"
[admin.settings.premium.enabled] [admin.settings.premium.enabled]
description = "Enable license key checks for pro/enterprise features" description = "Enable licence key checks for pro/enterprise features"
label = "Enable Premium Features" label = "Enable Premium Features"
[admin.settings.premium.file] [admin.settings.premium.file]
choose = "Choose License File" choose = "Choose Licence File"
description = "Upload your .lic or .cert license file from offline purchases" description = "Upload your .lic or .cert licence file from offline purchases"
label = "License Certificate File" label = "Licence Certificate File"
selected = "Selected: {{filename}} ({{size}})" selected = "Selected: {{filename}} ({{size}})"
successMessage = "License file uploaded and activated successfully. No restart required." successMessage = "Licence file uploaded and activated successfully. No restart required."
[admin.settings.premium.inputMethod] [admin.settings.premium.inputMethod]
file = "Certificate File" file = "Certificate File"
text = "License Key" text = "Licence Key"
[admin.settings.premium.key] [admin.settings.premium.key]
description = "Enter your premium or enterprise license key. Premium features will be automatically enabled when a key is provided." description = "Enter your premium or enterprise licence key. Premium features will be automatically enabled when a key is provided."
label = "License Key" label = "Licence Key"
success = "License Key Saved" success = "Licence Key Saved"
successMessage = "Your license key has been activated successfully. No restart required." successMessage = "Your licence key has been activated successfully. No restart required."
[admin.settings.premium.key.overwriteWarning] [admin.settings.premium.key.overwriteWarning]
line1 = "Overwriting your current license key cannot be undone." line1 = "Overwriting your current licence key cannot be undone."
line2 = "Your previous license will be permanently lost unless you have backed it up elsewhere." line2 = "Your previous licence will be permanently lost unless you have backed it up elsewhere."
line3 = "Important: Keep license keys private and secure. Never share them publicly." line3 = "Important: Keep licence keys private and secure. Never share them publicly."
title = "⚠️ Warning: Existing License Detected" title = "⚠️ Warning: Existing Licence Detected"
[admin.settings.premium.licenseKey] [admin.settings.premium.licenseKey]
info = "If you have a license key or certificate file from a direct purchase, you can enter it here to activate premium or enterprise features." info = "If you have a licence key or certificate file from a direct purchase, you can enter it here to activate premium or enterprise features."
toggle = "Got a license key or certificate file?" toggle = "Got a licence key or certificate file?"
[admin.settings.premium.movedFeatures] [admin.settings.premium.movedFeatures]
auditLogging = "<0>Audit Logging</0> (ENTERPRISE) - Security" auditLogging = "<0>Audit Logging</0> (ENTERPRISE) - Security"
customMetadata = "<0>Custom Metadata</0> (PRO) - General" customMetadata = "<0>Custom Metadata</0> (PRO) - General"
databaseConfiguration = "<0>Database Configuration</0> (ENTERPRISE) - Database" databaseConfiguration = "<0>Database Configuration</0> (ENTERPRISE) - Database"
message = "Premium and Enterprise features are now organized in their respective sections:" message = "Premium and Enterprise features are now organised in their respective sections:"
title = "Premium Features Distributed" title = "Premium Features Distributed"
[admin.settings.privacy] [admin.settings.privacy]
@@ -1469,7 +1469,7 @@ description = "Allow users to create multi-participant document signing sessions
label = "Enable Group Signing (Alpha)" label = "Enable Group Signing (Alpha)"
[admin.settings.telegram] [admin.settings.telegram]
description = "Configure Telegram bot connectivity, access controls, and feedback behavior." description = "Configure Telegram bot connectivity, access controls, and feedback behaviour."
title = "Telegram Bot" title = "Telegram Bot"
[admin.settings.telegram.accessControl] [admin.settings.telegram.accessControl]
@@ -1682,8 +1682,8 @@ configureAudit = "Configure Audit Logging"
configureAuditMessage = "Adjust audit logging level, retention period, and other settings in the Security & Authentication section." configureAuditMessage = "Adjust audit logging level, retention period, and other settings in the Security & Authentication section."
disabled = "Audit logging is disabled" disabled = "Audit logging is disabled"
disabledMessage = "Enable audit logging in your application configuration to track system events." disabledMessage = "Enable audit logging in your application configuration to track system events."
enterpriseRequired = "Enterprise License Required" enterpriseRequired = "Enterprise Licence Required"
enterpriseRequiredMessage = "The audit logging system is an enterprise feature. Please upgrade to an enterprise license to access audit logs and analytics." enterpriseRequiredMessage = "The audit logging system is an enterprise feature. Please upgrade to an enterprise licence to access audit logs and analytics."
goToSettings = "Go to Audit Settings" goToSettings = "Go to Audit Settings"
notAvailable = "Audit system not available" notAvailable = "Audit system not available"
notAvailableMessage = "The audit system is not configured or not available." notAvailableMessage = "The audit system is not configured or not available."
@@ -1813,7 +1813,7 @@ user = "User"
[auto-rename] [auto-rename]
description = "Automatically finds the title from your PDF content and uses it as the filename." description = "Automatically finds the title from your PDF content and uses it as the filename."
submit = "Auto Rename" submit = "Auto Rename"
tags = "auto-detect,header-based,organize,relabel" tags = "auto-detect,header-based,organise,relabel"
title = "Auto Rename" title = "Auto Rename"
[auto-rename.error] [auto-rename.error]
@@ -1935,8 +1935,8 @@ unnamed = "Unnamed Automation"
[automation.suggested] [automation.suggested]
emailPreparation = "Email Preparation" emailPreparation = "Email Preparation"
emailPreparationDesc = "Optimises PDFs for email distribution by compressing files, splitting large documents into 20MB chunks for email compatibility, and removing metadata for privacy." emailPreparationDesc = "Optimises PDFs for email distribution by compressing files, splitting large documents into 20MB chunks for email compatibility, and removing metadata for privacy."
prePublishSanitization = "Pre-publish Sanitization" prePublishSanitization = "Pre-publish Sanitisation"
prePublishSanitizationDesc = "Sanitization workflow that removes all hidden metadata, JavaScript, embedded files, annotations, and flattens forms to prevent data leakage before publishing PDFs online." prePublishSanitizationDesc = "Sanitisation workflow that removes all hidden metadata, JavaScript, embedded files, annotations, and flattens forms to prevent data leakage before publishing PDFs online."
processImages = "Process Images" processImages = "Process Images"
processImagesDesc = "Converts multiple image files into a single PDF document, then applies OCR technology to extract searchable text from the images." processImagesDesc = "Converts multiple image files into a single PDF document, then applies OCR technology to extract searchable text from the images."
securePdfIngestion = "Secure PDF Ingestion" securePdfIngestion = "Secure PDF Ingestion"
@@ -1946,15 +1946,15 @@ secureWorkflowDesc = "Secures PDF documents by removing potentially malicious co
[autoRename] [autoRename]
description = "This tool will automatically rename PDF files based on their content. It analyzes the document to find the most suitable title from the text." description = "This tool will automatically rename PDF files based on their content. It analyzes the document to find the most suitable title from the text."
tags = "auto-detect,header-based,organize,relabel,auto rename,automatic rename,smart rename,rename by content,filename,file naming,detect title" tags = "auto-detect,header-based,organise,relabel,auto rename,automatic rename,smart rename,rename by content,filename,file naming,detect title"
[autoSizeSplitPDF] [autoSizeSplitPDF]
tags = "pdf,split,document,organization" tags = "pdf,split,document,organisation"
[autoSplitPDF] [autoSplitPDF]
dividerDownload2 = "Download 'Auto Splitter Divider (with instructions).pdf'" dividerDownload2 = "Download 'Auto Splitter Divider (with instructions).pdf'"
duplexMode = "Duplex Mode (Front and back scanning)" duplexMode = "Duplex Mode (Front and back scanning)"
tags = "QR-based,separate,scan-segment,organize" tags = "QR-based,separate,scan-segment,organise"
title = "Auto Split PDF" title = "Auto Split PDF"
[backendHealth] [backendHealth]
@@ -2155,11 +2155,11 @@ header = "Certificate Types"
[certificateChoice.tooltip.organization] [certificateChoice.tooltip.organization]
bullet1 = "Managed by system administrators" bullet1 = "Managed by system administrators"
bullet2 = "Shared across authorized users" bullet2 = "Shared across authorised users"
bullet3 = "Represents company identity, not individual" bullet3 = "Represents company identity, not individual"
bullet4 = "Best for: Official documents, team signatures" bullet4 = "Best for: Official documents, team signatures"
description = "A shared certificate provided by your organization. Used for company-wide signing authority." description = "A shared certificate provided by your organisation. Used for company-wide signing authority."
title = "Organization Certificate" title = "Organisation Certificate"
[certificateChoice.tooltip.personal] [certificateChoice.tooltip.personal]
bullet1 = "Generated automatically on first use" bullet1 = "Generated automatically on first use"
@@ -2186,7 +2186,7 @@ choosePfxFile = "Choose PFX File"
choosePrivateKey = "Choose Private Key File" choosePrivateKey = "Choose Private Key File"
declined = "Declined" declined = "Declined"
fetchFailed = "Failed to load signing data" fetchFailed = "Failed to load signing data"
finalized = "Finalized" finalized = "Finalised"
location = "Location" location = "Location"
logoTitle = "Logo" logoTitle = "Logo"
name = "Name" name = "Name"
@@ -2196,7 +2196,7 @@ pageNumber = "Page Number"
password = "Certificate Password" password = "Certificate Password"
passwordOptional = "Leave empty if no password" passwordOptional = "Leave empty if no password"
pending = "Pending" pending = "Pending"
readyToFinalize = "Ready to finalize" readyToFinalize = "Ready to finalise"
reason = "Reason" reason = "Reason"
serverCertMessage = "Using server certificate - no files or password required" serverCertMessage = "Using server certificate - no files or password required"
showLogo = "Show Logo" showLogo = "Show Logo"
@@ -2264,8 +2264,8 @@ reasonHelp = "Pre-set a signing reason for these participants (optional, they ca
reasonPlaceholder = "e.g. Approval, Review..." reasonPlaceholder = "e.g. Approval, Review..."
[certSign.collab.finalize] [certSign.collab.finalize]
button = "Finalize and Load Signed PDF" button = "Finalise and Load Signed PDF"
early = "Finalize with Current Signatures" early = "Finalise with Current Signatures"
[certSign.collab.participant] [certSign.collab.participant]
certInvalid = "✗ {{error}}" certInvalid = "✗ {{error}}"
@@ -2288,7 +2288,7 @@ deleted = "Session deleted"
deleteError = "Failed to delete session" deleteError = "Failed to delete session"
deleteSession = "Delete Session" deleteSession = "Delete Session"
dueDate = "Due Date" dueDate = "Due Date"
finalizeError = "Failed to finalize session" finalizeError = "Failed to finalise session"
loadPdfError = "Failed to load signed PDF" loadPdfError = "Failed to load signed PDF"
loadSignedPdf = "Load Signed PDF into Active Files" loadSignedPdf = "Load Signed PDF into Active Files"
messageLabel = "Message" messageLabel = "Message"
@@ -2304,7 +2304,7 @@ workbenchTitle = "Session Management"
[certSign.collab.sessionList] [certSign.collab.sessionList]
active = "Active" active = "Active"
finalized = "Finalized" finalized = "Finalised"
[certSign.collab.signatureSettings] [certSign.collab.signatureSettings]
description = "Configure how signatures will appear for all participants" description = "Configure how signatures will appear for all participants"
@@ -2336,7 +2336,7 @@ noCertificate = "Please select a certificate file"
noSignatures = "Please place at least one signature on the PDF" noSignatures = "Please place at least one signature on the PDF"
p12File = "P12/PFX Certificate File" p12File = "P12/PFX Certificate File"
password = "Certificate Password" password = "Certificate Password"
penColor = "Pen Color" penColor = "Pen Colour"
penSize = "Pen Size: {{size}}px" penSize = "Pen Size: {{size}}px"
reason = "Reason (Optional)" reason = "Reason (Optional)"
reasonPlaceholder = "Why are you signing?" reasonPlaceholder = "Why are you signing?"
@@ -2351,15 +2351,15 @@ signatureTextPlaceholder = "Enter your name..."
signatureTypeLabel = "Signature Type" signatureTypeLabel = "Signature Type"
signButton = "Sign Document" signButton = "Sign Document"
signingTitle = "Signing" signingTitle = "Signing"
textColor = "Text Color" textColor = "Text Colour"
typeSignature = "Type your name to create a signature" typeSignature = "Type your name to create a signature"
uploadCert = "Custom Certificate" uploadCert = "Custom Certificate"
uploadCertDesc = "Use your own P12/PFX certificate" uploadCertDesc = "Use your own P12/PFX certificate"
uploadSignature = "Upload your signature image" uploadSignature = "Upload your signature image"
usePersonalCert = "Personal Certificate" usePersonalCert = "Personal Certificate"
usePersonalCertDesc = "Auto-generated for your account" usePersonalCertDesc = "Auto-generated for your account"
useServerCert = "Organization Certificate" useServerCert = "Organisation Certificate"
useServerCertDesc = "Shared organization certificate" useServerCertDesc = "Shared organisation certificate"
workbenchTitle = "Sign Request" workbenchTitle = "Sign Request"
[certSign.collab.signRequest.canvas] [certSign.collab.signRequest.canvas]
@@ -2427,7 +2427,7 @@ visible = "Visible"
yourSignatures = "Your Signatures ({{count}})" yourSignatures = "Your Signatures ({{count}})"
[certSign.collab.signRequest.text] [certSign.collab.signRequest.text]
colorLabel = "Color" colorLabel = "Colour"
fontLabel = "Font" fontLabel = "Font"
fontSizeLabel = "Size" fontSizeLabel = "Size"
fontSizePlaceholder = "16" fontSizePlaceholder = "16"
@@ -2453,7 +2453,7 @@ panelPeople = "People"
[certSign.sessions] [certSign.sessions]
deleted = "Session deleted" deleted = "Session deleted"
fetchFailed = "Failed to load session details" fetchFailed = "Failed to load session details"
finalized = "Session finalized" finalized = "Session finalised"
loaded = "Signed PDF loaded" loaded = "Signed PDF loaded"
pdfNotReady = "PDF Not Ready" pdfNotReady = "PDF Not Ready"
pdfNotReadyDesc = "The signed PDF is being generated. Please try again in a moment." pdfNotReadyDesc = "The signed PDF is being generated. Please try again in a moment."
@@ -2902,7 +2902,7 @@ message = "These documents appear highly dissimilar. Comparison was stopped to s
[compress] [compress]
desc = "Compress PDFs to reduce their file size." desc = "Compress PDFs to reduce their file size."
submit = "Compress" submit = "Compress"
tags = "shrink,reduce,optimize,compress,smaller,downsize,file size,reduce size,minimize,make smaller,decrease size,optimize size" tags = "shrink,reduce,optimise,compress,smaller,downsize,file size,reduce size,minimise,make smaller,decrease size,optimise size"
title = "Compress" title = "Compress"
[compress.compressionLevel] [compress.compressionLevel]
@@ -2914,7 +2914,7 @@ range7to9 = "Higher values reduce file size significantly but may reduce image c
failed = "An error occurred while compressing the PDF." failed = "An error occurred while compressing the PDF."
[compress.grayscale] [compress.grayscale]
label = "Apply Grayscale for Compression" label = "Apply Greyscale for Compression"
[compress.linearize] [compress.linearize]
label = "Linearize PDF for fast web viewing" label = "Linearize PDF for fast web viewing"
@@ -2944,7 +2944,7 @@ title = "Description"
[compress.tooltip.grayscale] [compress.tooltip.grayscale]
text = "Select this option to convert all images to black and white, which can significantly reduce file size especially for scanned PDFs or image-heavy documents." text = "Select this option to convert all images to black and white, which can significantly reduce file size especially for scanned PDFs or image-heavy documents."
title = "Grayscale" title = "Greyscale"
[compress.tooltip.header] [compress.tooltip.header]
title = "Compress Settings Overview" title = "Compress Settings Overview"
@@ -2981,7 +2981,7 @@ signedInAs = "Signed in as"
title = "Account Settings" title = "Account Settings"
[config.account.profilePicture] [config.account.profilePicture]
description = "Upload an image to personalize your account." description = "Upload an image to personalise your account."
help = "PNG, JPG, or WebP up to 2MB." help = "PNG, JPG, or WebP up to 2MB."
remove = "Remove" remove = "Remove"
sizeError = "Please select an image smaller than 2MB." sizeError = "Please select an image smaller than 2MB."
@@ -3135,7 +3135,7 @@ includeAttachments = "Include email attachments"
maintainAspectRatio = "Maintain Aspect Ratio" maintainAspectRatio = "Maintain Aspect Ratio"
maxAttachmentSize = "Maximum attachment size (MB)" maxAttachmentSize = "Maximum attachment size (MB)"
multiple = "Multiple" multiple = "Multiple"
optimizeForEbook = "Optimize PDF for ebook readers (uses Ghostscript)" optimizeForEbook = "Optimise PDF for ebook readers (uses Ghostscript)"
output = "Output" output = "Output"
outputFormat = "Output Format" outputFormat = "Output Format"
pdfaDigitalSignatureWarning = "The PDF contains a digital signature. This will be removed in the next step." pdfaDigitalSignatureWarning = "The PDF contains a digital signature. This will be removed in the next step."
@@ -3167,18 +3167,18 @@ includePageNumbers = "Include page numbers"
includePageNumbersDesc = "Add page numbers to the generated PDF" includePageNumbersDesc = "Add page numbers to the generated PDF"
includeTableOfContents = "Include table of contents" includeTableOfContents = "Include table of contents"
includeTableOfContentsDesc = "Add a generated table of contents to the resulting PDF" includeTableOfContentsDesc = "Add a generated table of contents to the resulting PDF"
optimizeForEbookPdf = "Optimize for ebook readers" optimizeForEbookPdf = "Optimise for ebook readers"
optimizeForEbookPdfDesc = "Optimize the PDF for eBook reading (smaller file size, better rendering on eInk devices)" optimizeForEbookPdfDesc = "Optimise the PDF for eBook reading (smaller file size, better rendering on eInk devices)"
[convert.epubOptions] [convert.epubOptions]
detectChapters = "Detect chapters" detectChapters = "Detect chapters"
detectChaptersDesc = "Detect headings that look like chapters and insert EPUB page breaks" detectChaptersDesc = "Detect headings that look like chapters and insert EPUB page breaks"
kindleEink = "Kindle e-Ink (text optimized)" kindleEink = "Kindle e-Ink (text optimised)"
outputFormat = "Output format" outputFormat = "Output format"
outputFormatDesc = "Choose the output format for the ebook" outputFormatDesc = "Choose the output format for the ebook"
tabletPhone = "Tablet/Phone (with images)" tabletPhone = "Tablet/Phone (with images)"
targetDevice = "Target device" targetDevice = "Target device"
targetDeviceDesc = "Choose an output profile optimized for the reader device" targetDeviceDesc = "Choose an output profile optimised for the reader device"
[cookieBanner.popUp] [cookieBanner.popUp]
acceptAllBtn = "Okay" acceptAllBtn = "Okay"
@@ -3197,7 +3197,7 @@ closeIconLabel = "Close modal"
savePreferencesBtn = "Save preferences" savePreferencesBtn = "Save preferences"
serviceCounterLabel = "Service|Services" serviceCounterLabel = "Service|Services"
subtitle = "Cookie Usage" subtitle = "Cookie Usage"
title = "Consent Preferences Center" title = "Consent Preferences Centre"
[cookieBanner.preferencesModal.analytics] [cookieBanner.preferencesModal.analytics]
description = "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured-Stirling PDF cannot and will never track the content of the documents you work with." description = "These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured-Stirling PDF cannot and will never track the content of the documents you work with."
@@ -3281,7 +3281,7 @@ teamSubscription = "Team"
titleExhausted = "You've used your free credits" titleExhausted = "You've used your free credits"
titleExhaustedPro = "You have run out of credits" titleExhaustedPro = "You have run out of credits"
unlimitedApiAccess = "Unlimited API access" unlimitedApiAccess = "Unlimited API access"
unlimitedMonthlyCredits = "Site License" unlimitedMonthlyCredits = "Site Licence"
unlimitedSeats = "Unlimited seats" unlimitedSeats = "Unlimited seats"
[crop] [crop]
@@ -3929,7 +3929,7 @@ welcomeTitle = "Welcome!"
[flatten] [flatten]
submit = "Flatten" submit = "Flatten"
tags = "simplify,remove,interactive,flatten,flatten form,remove form fields,make static,finalize form,lock form,disable editing,convert to image,non-editable" tags = "simplify,remove,interactive,flatten,flatten form,remove form fields,make static,finalise form,lock form,disable editing,convert to image,non-editable"
title = "Flatten" title = "Flatten"
[flatten.error] [flatten.error]
@@ -4133,13 +4133,13 @@ header = "About Group Signing"
[groupSigning.tooltip.finalization] [groupSigning.tooltip.finalization]
bullet1 = "All signatures are applied in the participant order you specified" bullet1 = "All signatures are applied in the participant order you specified"
bullet2 = "You can finalize with partial signatures if needed" bullet2 = "You can finalise with partial signatures if needed"
bullet3 = "Once finalized, the session cannot be modified" bullet3 = "Once finalised, the session cannot be modified"
description = "Once all participants have signed (or you choose to finalize early), you can generate the final signed PDF." description = "Once all participants have signed (or you choose to finalise early), you can generate the final signed PDF."
title = "Finalization Process" title = "Finalisation Process"
[groupSigning.tooltip.roles] [groupSigning.tooltip.roles]
bullet1 = "Owner (you): Creates session, configures signature defaults, finalizes document" bullet1 = "Owner (you): Creates session, configures signature defaults, finalises document"
bullet2 = "Participants: Create their signature, choose certificate, place on PDF" bullet2 = "Participants: Create their signature, choose certificate, place on PDF"
bullet3 = "Participants cannot modify signature visibility, reason, or location settings" bullet3 = "Participants cannot modify signature visibility, reason, or location settings"
description = "You control the signature appearance settings for all participants." description = "You control the signature appearance settings for all participants."
@@ -4189,8 +4189,8 @@ tags = "text,annotation,label,add text,insert text,place text,text box,add label
title = "Add Text" title = "Add Text"
[home.adjustContrast] [home.adjustContrast]
desc = "Adjust Colors/Contrast, Saturation and Brightness of a PDF" desc = "Adjust Colours/Contrast, Saturation and Brightness of a PDF"
tags = "contrast,brightness,saturation,adjust colors,color correction,enhance,lighten,darken,improve quality,color balance,hue,vibrance" tags = "contrast,brightness,saturation,adjust colours,colour correction,enhance,lighten,darken,improve quality,colour balance,hue,vibrance"
title = "Adjust Colours/Contrast" title = "Adjust Colours/Contrast"
[home.annotate] [home.annotate]
@@ -4205,7 +4205,7 @@ title = "Automate"
[home.autoRename] [home.autoRename]
desc = "Auto renames a PDF file based on its detected header" desc = "Auto renames a PDF file based on its detected header"
tags = "auto-detect,header-based,organize,relabel,auto rename,automatic rename,smart rename,rename by content,filename,file naming,detect title" tags = "auto-detect,header-based,organise,relabel,auto rename,automatic rename,smart rename,rename by content,filename,file naming,detect title"
title = "Auto Rename PDF File" title = "Auto Rename PDF File"
[home.autoSizeSplitPDF] [home.autoSizeSplitPDF]
@@ -4243,7 +4243,7 @@ title = "Compare"
[home.compress] [home.compress]
desc = "Compress PDFs to reduce their file size." desc = "Compress PDFs to reduce their file size."
tags = "shrink,reduce,optimize,compress,smaller,downsize,file size,reduce size,minimize,make smaller,decrease size,optimize size" tags = "shrink,reduce,optimise,compress,smaller,downsize,file size,reduce size,minimise,make smaller,decrease size,optimise size"
title = "Compress" title = "Compress"
[home.convert] [home.convert]
@@ -4293,7 +4293,7 @@ title = "Extract Pages"
[home.flatten] [home.flatten]
desc = "Remove all interactive elements and forms from a PDF" desc = "Remove all interactive elements and forms from a PDF"
tags = "simplify,remove,interactive,flatten,flatten form,remove form fields,make static,finalize form,lock form,disable editing,convert to image,non-editable" tags = "simplify,remove,interactive,flatten,flatten form,remove form fields,make static,finalise form,lock form,disable editing,convert to image,non-editable"
title = "Flatten" title = "Flatten"
[home.formFill] [home.formFill]
@@ -4326,12 +4326,12 @@ workspace = "Workspace"
[home.multiTool] [home.multiTool]
desc = "Merge, Rotate, Rearrange, Split, and Remove pages" desc = "Merge, Rotate, Rearrange, Split, and Remove pages"
tags = "multiple,tools,multi-tool,all-in-one,swiss army,page organizer,page editor,edit pages,manage pages,organize,reorganize" tags = "multiple,tools,multi-tool,all-in-one,swiss army,page organiser,page editor,edit pages,manage pages,organise,reorganize"
title = "PDF Multi Tool" title = "PDF Multi Tool"
[home.ocr] [home.ocr]
desc = "Cleanup scans and detects text from images within a PDF and re-adds it as text." desc = "Cleanup scans and detects text from images within a PDF and re-adds it as text."
tags = "extract,scan,OCR,optical character recognition,text recognition,scan to text,image to text,scanned document,searchable PDF,make searchable,extract text,recognize text,read scanned" tags = "extract,scan,OCR,optical character recognition,text recognition,scan to text,image to text,scanned document,searchable PDF,make searchable,extract text,recognise text,read scanned"
title = "OCR / Cleanup scans" title = "OCR / Cleanup scans"
[home.overlay-pdfs] [home.overlay-pdfs]
@@ -4350,7 +4350,7 @@ tags = "AI,agent,comment,annotate,sticky note,review,feedback,notes"
title = "Add AI comments" title = "Add AI comments"
[home.pdfOrganiser] [home.pdfOrganiser]
tags = "organize,rearrange,reorder,organise,arrange pages,sort,move pages,delete pages,remove pages,page management,page organizer,page organiser,resequence" tags = "organise,rearrange,reorder,organise,arrange pages,sort,move pages,delete pages,remove pages,page management,page organiser,page organiser,resequence"
title = "Organise" title = "Organise"
[home.pdfTextEditor] [home.pdfTextEditor]
@@ -4405,7 +4405,7 @@ title = "Remove Password"
[home.reorganizePages] [home.reorganizePages]
desc = "Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control." desc = "Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."
tags = "rearrange,reorder,organize,reorganize,move pages,page order,sort pages,arrange pages,shuffle,resequence" tags = "rearrange,reorder,organise,reorganize,move pages,page order,sort pages,arrange pages,shuffle,resequence"
title = "Reorganize Pages" title = "Reorganize Pages"
[home.repair] [home.repair]
@@ -4415,7 +4415,7 @@ title = "Repair"
[home.replaceColor] [home.replaceColor]
desc = "Replace or invert colours in PDF documents" desc = "Replace or invert colours in PDF documents"
tags = "replace color,invert color,color replacement,swap colors,change color,invert,negative,color swap,find and replace color,convert color,color change" tags = "replace colour,invert colour,colour replacement,swap colours,change colour,invert,negative,colour swap,find and replace colour,convert colour,colour change"
title = "Replace & Invert Colour" title = "Replace & Invert Colour"
[home.rotate] [home.rotate]
@@ -4425,7 +4425,7 @@ title = "Rotate"
[home.sanitize] [home.sanitize]
desc = "Remove potentially harmful elements from PDF files" desc = "Remove potentially harmful elements from PDF files"
tags = "clean,purge,remove,sanitize,sanitise,remove scripts,remove javascript,remove metadata,strip metadata,security,clean document,remove hidden data,privacy" tags = "clean,purge,remove,sanitise,sanitise,remove scripts,remove javascript,remove metadata,strip metadata,security,clean document,remove hidden data,privacy"
title = "Sanitise" title = "Sanitise"
[home.scalePages] [home.scalePages]
@@ -4450,7 +4450,7 @@ title = "Show Javascript"
[home.sign] [home.sign]
desc = "Adds signature to PDF by drawing, text or image" desc = "Adds signature to PDF by drawing, text or image"
tags = "signature,autograph,e-sign,electronic signature,digital signature,sign document,approval,signoff,authorize,endorse,ink signature,handwriting" tags = "signature,autograph,e-sign,electronic signature,digital signature,sign document,approval,signoff,authorise,endorse,ink signature,handwriting"
title = "Sign" title = "Sign"
[home.split] [home.split]
@@ -4747,16 +4747,16 @@ approving = "Allowing..."
decisionFailed = "Could not submit your decision. Please try again." decisionFailed = "Could not submit your decision. Please try again."
deny = "Deny" deny = "Deny"
denying = "Denying..." denying = "Denying..."
loadFailed = "This authorization request is invalid or has expired. Close this tab and try connecting again from the app." loadFailed = "This authorisation request is invalid or has expired. Close this tab and try connecting again from the app."
loading = "Loading authorization request..." loading = "Loading authorisation request..."
missingId = "Missing authorization request. Start the connection from your app and try again." missingId = "Missing authorisation request. Start the connection from your app and try again."
redirecting = "Returning you to the app..." redirecting = "Returning you to the app..."
requesting = "{{app}} wants to access your Stirling PDF account" requesting = "{{app}} wants to access your Stirling PDF account"
scopesIntro = "This will allow {{app}} to:" scopesIntro = "This will allow {{app}} to:"
signedInAs = "Signed in as {{name}}" signedInAs = "Signed in as {{name}}"
signInButton = "Sign in to continue" signInButton = "Sign in to continue"
signInPrompt = "Sign in to your Stirling PDF account to continue connecting the app." signInPrompt = "Sign in to your Stirling PDF account to continue connecting the app."
title = "Authorize access" title = "Authorise access"
unknownApp = "A third-party application" unknownApp = "A third-party application"
[oauthConsent.access] [oauthConsent.access]
@@ -4894,7 +4894,7 @@ qrCodeLoading = "Generating your QR code…"
adminBodyLoginDisabled = "Once you enable login mode, you can manage users, configure settings, and monitor server health. The first <strong>{{freeTierLimit}}</strong> people on your server get to use Stirling free of charge." adminBodyLoginDisabled = "Once you enable login mode, you can manage users, configure settings, and monitor server health. The first <strong>{{freeTierLimit}}</strong> people on your server get to use Stirling free of charge."
adminBodyLoginEnabled = "As an admin, you can manage users, configure settings, and monitor server health. The first <strong>{{freeTierLimit}}</strong> people on your server get to use Stirling free of charge." adminBodyLoginEnabled = "As an admin, you can manage users, configure settings, and monitor server health. The first <strong>{{freeTierLimit}}</strong> people on your server get to use Stirling free of charge."
adminTitle = "Admin Overview" adminTitle = "Admin Overview"
userBody = "Invite teammates, assign roles, and keep your documents organized in one secure workspace. Enable login mode whenever you're ready to grow beyond solo use." userBody = "Invite teammates, assign roles, and keep your documents organised in one secure workspace. Enable login mode whenever you're ready to grow beyond solo use."
userTitle = "Plan Overview" userTitle = "Plan Overview"
[onboarding.saas.freeEditor] [onboarding.saas.freeEditor]
@@ -4921,9 +4921,9 @@ roleUser = "User"
[onboarding.serverLicense] [onboarding.serverLicense]
freeBody = "Our <strong>Open-Core</strong> licensing permits up to <strong>{{freeTierLimit}}</strong> users for free per server. To scale uninterrupted, we recommend the Stirling Server plan - <strong>unlimited seats</strong> and <strong>SSO support</strong> for $99/server/mo." freeBody = "Our <strong>Open-Core</strong> licensing permits up to <strong>{{freeTierLimit}}</strong> users for free per server. To scale uninterrupted, we recommend the Stirling Server plan - <strong>unlimited seats</strong> and <strong>SSO support</strong> for $99/server/mo."
freeTitle = "Server License" freeTitle = "Server Licence"
overLimitBody = "Our licensing permits up to <strong>{{freeTierLimit}}</strong> users for free per server. You have <strong>{{overLimitUserCopy}}</strong> Stirling users. To continue uninterrupted, upgrade to the Stirling Server plan - <strong>unlimited seats</strong>, PDF text editing, and full admin control for $99/server/mo." overLimitBody = "Our licensing permits up to <strong>{{freeTierLimit}}</strong> users for free per server. You have <strong>{{overLimitUserCopy}}</strong> Stirling users. To continue uninterrupted, upgrade to the Stirling Server plan - <strong>unlimited seats</strong>, PDF text editing, and full admin control for $99/server/mo."
overLimitTitle = "Server License Needed" overLimitTitle = "Server Licence Needed"
seePlans = "See Plans →" seePlans = "See Plans →"
upgrade = "Upgrade now →" upgrade = "Upgrade now →"
@@ -5040,7 +5040,7 @@ right = "Right Margin"
rows = "Rows" rows = "Rows"
rowsPlaceholder = "Enter rows" rowsPlaceholder = "Enter rows"
submit = "Submit" submit = "Submit"
tags = "merge,composite,single-view,organize" tags = "merge,composite,single-view,organise"
title = "Multi Page Layout" title = "Multi Page Layout"
top = "Top Margin" top = "Top Margin"
@@ -5398,34 +5398,34 @@ checkoutInstructions = "Complete your purchase in the browser window that just o
checkoutOpened = "Checkout Opened in Browser" checkoutOpened = "Checkout Opened in Browser"
closeLater = "I'll Do This Later" closeLater = "I'll Do This Later"
error = "Payment Error" error = "Payment Error"
generatingLicense = "Generating your license key..." generatingLicense = "Generating your licence key..."
licenseActivated = "License activated! Your license key has been saved. A confirmation email has been sent to your registered email address." licenseActivated = "Licence activated! Your licence key has been saved. A confirmation email has been sent to your registered email address."
licenseDelayed = "Payment successful! Your license is being generated. You will receive an email with your license key shortly. If you don't receive it within 10 minutes, please contact support." licenseDelayed = "Payment successful! Your licence is being generated. You will receive an email with your licence key shortly. If you don't receive it within 10 minutes, please contact support."
licenseDelayedMessage = "Your license key is being generated. Please check your email shortly or contact support." licenseDelayedMessage = "Your licence key is being generated. Please check your email shortly or contact support."
licenseInstructions = "This has been added to your installation. You will receive a copy in your email as well." licenseInstructions = "This has been added to your installation. You will receive a copy in your email as well."
licenseKey = "Your License Key" licenseKey = "Your Licence Key"
licensePollingError = "Payment successful but we couldn't retrieve your license key automatically. Please check your email or contact support with your payment confirmation." licensePollingError = "Payment successful but we couldn't retrieve your licence key automatically. Please check your email or contact support with your payment confirmation."
licenseRetrievalError = "Payment successful but license retrieval failed. You will receive your license key via email. Please contact support if you don't receive it within 10 minutes." licenseRetrievalError = "Payment successful but licence retrieval failed. You will receive your licence key via email. Please contact support if you don't receive it within 10 minutes."
licenseSaveError = "Failed to save license key. Please contact support with your license key to complete activation." licenseSaveError = "Failed to save licence key. Please contact support with your licence key to complete activation."
monthly = "Monthly" monthly = "Monthly"
paymentCanceled = "Payment was canceled. No charges were made." paymentCanceled = "Payment was cancelled. No charges were made."
perMonth = "/month" perMonth = "/month"
preparing = "Preparing your checkout..." preparing = "Preparing your checkout..."
redirecting = "Redirecting to secure checkout..." redirecting = "Redirecting to secure checkout..."
refreshBilling = "I've Completed Payment - Refresh Billing" refreshBilling = "I've Completed Payment - Refresh Billing"
success = "Payment Successful!" success = "Payment Successful!"
successMessage = "Your subscription has been activated successfully. You will receive a confirmation email shortly." successMessage = "Your subscription has been activated successfully. You will receive a confirmation email shortly."
syncError = "Payment successful but license sync failed. Your license will be updated shortly. Please contact support if issues persist." syncError = "Payment successful but licence sync failed. Your licence will be updated shortly. Please contact support if issues persist."
syncingLicense = "Syncing your upgraded license..." syncingLicense = "Syncing your upgraded licence..."
upgradeComplete = "Upgrade Complete" upgradeComplete = "Upgrade Complete"
upgradeCompleteMessage = "Your subscription has been upgraded successfully. Your existing license key has been updated." upgradeCompleteMessage = "Your subscription has been upgraded successfully. Your existing licence key has been updated."
upgradeSuccess = "Payment successful! Your subscription has been upgraded. The license has been updated on your server. You will receive a confirmation email shortly." upgradeSuccess = "Payment successful! Your subscription has been upgraded. The licence has been updated on your server. You will receive a confirmation email shortly."
upgradeTitle = "Upgrade to {{planName}}" upgradeTitle = "Upgrade to {{planName}}"
yearly = "Yearly" yearly = "Yearly"
[payment.emailStage] [payment.emailStage]
continue = "Continue" continue = "Continue"
description = "We'll use this to send your license key and receipts." description = "We'll use this to send your licence key and receipts."
emailLabel = "Email Address" emailLabel = "Email Address"
emailPlaceholder = "[email protected]" emailPlaceholder = "[email protected]"
modalTitle = "Get Started - {{planName}}" modalTitle = "Get Started - {{planName}}"
@@ -5489,7 +5489,7 @@ REMOVE_FIRST = "Remove the first page from the document."
REMOVE_FIRST_AND_LAST = "Remove both the first and last pages from the document." REMOVE_FIRST_AND_LAST = "Remove both the first and last pages from the document."
REMOVE_LAST = "Remove the last page from the document." REMOVE_LAST = "Remove the last page from the document."
REVERSE_ORDER = "Flip the document so the last page becomes first and so on." REVERSE_ORDER = "Flip the document so the last page becomes first and so on."
SIDE_STITCH_BOOKLET_SORT = "Arrange pages for sidestitch booklet printing (optimized for binding on the side)." SIDE_STITCH_BOOKLET_SORT = "Arrange pages for sidestitch booklet printing (optimised for binding on the side)."
[pdfTextEditor] [pdfTextEditor]
conversionFailed = "Failed to convert PDF. Please try again." conversionFailed = "Failed to convert PDF. Please try again."
@@ -5737,7 +5737,7 @@ highlight3 = "Latest features"
name = "Enterprise" name = "Enterprise"
requiresServer = "Requires Server" requiresServer = "Requires Server"
requiresServerMessage = "Please upgrade to the Server plan first before upgrading to Enterprise." requiresServerMessage = "Please upgrade to the Server plan first before upgrading to Enterprise."
siteLicense = "Site License" siteLicense = "Site Licence"
[plan.feature] [plan.feature]
api = "API Access" api = "API Access"
@@ -5820,9 +5820,9 @@ message = "You've made the most of this month's cap. That's a load of automation
title = "You're on a Roll!" title = "You're on a Roll!"
[plan.static] [plan.static]
activateLicense = "Activate Your License" activateLicense = "Activate Your Licence"
contactToUpgrade = "Contact us to upgrade or customize your plan" contactToUpgrade = "Contact us to upgrade or customise your plan"
getLicense = "Get Server License" getLicense = "Get Server Licence"
monthlyBilling = "Monthly Billing" monthlyBilling = "Monthly Billing"
selectPeriod = "Select Billing Period" selectPeriod = "Select Billing Period"
upgradeToEnterprise = "Upgrade to Enterprise" upgradeToEnterprise = "Upgrade to Enterprise"
@@ -5834,14 +5834,14 @@ message = "You will need to verify your email address in the Stripe billing port
title = "Email Verification Required" title = "Email Verification Required"
[plan.static.licenseActivation] [plan.static.licenseActivation]
activate = "Activate License" activate = "Activate Licence"
checkoutOpened = "Checkout Opened in New Tab" checkoutOpened = "Checkout Opened in New Tab"
doLater = "I'll do this later" doLater = "I'll do this later"
enterKey = "Enter your license key below to activate your plan:" enterKey = "Enter your licence key below to activate your plan:"
instructions = "Complete your purchase in the Stripe tab. Once your payment is complete, you will receive an email with your license key." instructions = "Complete your purchase in the Stripe tab. Once your payment is complete, you will receive an email with your licence key."
keyDescription = "Paste the license key from your email" keyDescription = "Paste the licence key from your email"
success = "License Activated!" success = "Licence Activated!"
successMessage = "Your license has been successfully activated. You can now close this window." successMessage = "Your licence has been successfully activated. You can now close this window."
[plan.team] [plan.team]
name = "Team" name = "Team"
@@ -5869,7 +5869,7 @@ security = "Security"
[policies.detail] [policies.detail]
editSettings = "Edit Settings" editSettings = "Edit Settings"
enforces = "Enforces" enforces = "Enforces"
managedByOrg = "Managed by your organization. Contact a team leader to change this policy." managedByOrg = "Managed by your organisation. Contact a team leader to change this policy."
noActivityDescription = "Documents will appear here once this policy runs." noActivityDescription = "Documents will appear here once this policy runs."
noActivityTitle = "No activity yet" noActivityTitle = "No activity yet"
onEveryUpload = "On every upload" onEveryUpload = "On every upload"
@@ -5932,7 +5932,7 @@ filenameSuffix = "Suffix"
filenameTextAria = "Filename text" filenameTextAria = "Filename text"
filenameTextPlaceholder = "Text to add (optional)" filenameTextPlaceholder = "Text to add (optional)"
lockedDescription = "Contact a team leader to change this policy." lockedDescription = "Contact a team leader to change this policy."
lockedTitle = "Managed by your organization" lockedTitle = "Managed by your organisation"
maxRetriesLabel = "Max retries" maxRetriesLabel = "Max retries"
noToolsError = "Add at least one configured tool to the workflow first." noToolsError = "Add at least one configured tool to the workflow first."
outputAsLabel = "Output as" outputAsLabel = "Output as"
@@ -6504,7 +6504,7 @@ description = "Removing password protection requires the password that was used
[reorganizePages] [reorganizePages]
submit = "Reorganize Pages" submit = "Reorganize Pages"
tags = "rearrange,reorder,organize,reorganize,move pages,page order,sort pages,arrange pages,shuffle,resequence" tags = "rearrange,reorder,organise,reorganize,move pages,page order,sort pages,arrange pages,shuffle,resequence"
[reorganizePages.error] [reorganizePages.error]
failed = "Failed to reorganize pages" failed = "Failed to reorganize pages"
@@ -6529,13 +6529,13 @@ title = "Repair Results"
[replace-color] [replace-color]
submit = "Replace" submit = "Replace"
title = "Replace-Invert-Color" title = "Replace-Invert-Colour"
[replace-color.selectText] [replace-color.selectText]
1 = "Replace or invert colour options" 1 = "Replace or invert colour options"
10 = "Choose text Color" 10 = "Choose text Colour"
11 = "Choose background Color" 11 = "Choose background Colour"
5 = "High contrast color options" 5 = "High contrast colour options"
6 = "White text on black background" 6 = "White text on black background"
7 = "Black text on white background" 7 = "Black text on white background"
8 = "Yellow text on black background" 8 = "Yellow text on black background"
@@ -6558,7 +6558,7 @@ highContrast = "High contrast"
invertAll = "Invert all colours" invertAll = "Invert all colours"
[replaceColor.tooltip.cmyk] [replaceColor.tooltip.cmyk]
text = "Convert the PDF from RGB colour space to CMYK colour space, optimized for professional printing. This process converts colours to the Cyan, Magenta, Yellow, Black model used by printers." text = "Convert the PDF from RGB colour space to CMYK colour space, optimised for professional printing. This process converts colours to the Cyan, Magenta, Yellow, Black model used by printers."
title = "Convert to CMYK" title = "Convert to CMYK"
[replaceColor.tooltip.custom] [replaceColor.tooltip.custom]
@@ -6614,7 +6614,7 @@ title = "Rotate Settings Overview"
desc = "Remove potentially harmful elements from PDF files." desc = "Remove potentially harmful elements from PDF files."
sanitizationResults = "Sanitisation Results" sanitizationResults = "Sanitisation Results"
submit = "Sanitise PDF" submit = "Sanitise PDF"
tags = "clean,purge,remove,sanitize,sanitise,remove scripts,remove javascript,remove metadata,strip metadata,security,clean document,remove hidden data,privacy" tags = "clean,purge,remove,sanitise,sanitise,remove scripts,remove javascript,remove metadata,strip metadata,security,clean document,remove hidden data,privacy"
title = "Sanitise" title = "Sanitise"
[sanitize.error] [sanitize.error]
@@ -6678,7 +6678,7 @@ tags = "scan,simulate,create,fake scan,look scanned,scanner effect,make look sca
[scannerImageSplit] [scannerImageSplit]
submit = "Extract Image Scans" submit = "Extract Image Scans"
tags = "separate,auto-detect,scans,multi-photo,organize" tags = "separate,auto-detect,scans,multi-photo,organise"
title = "Extracted Images" title = "Extracted Images"
[scannerImageSplit.error] [scannerImageSplit.error]
@@ -6729,18 +6729,18 @@ header = "Managing Signing Sessions"
[sessionManagement.tooltip.addParticipants] [sessionManagement.tooltip.addParticipants]
bullet1 = "New participants added to the end of signing order" bullet1 = "New participants added to the end of signing order"
bullet2 = "Cannot add participants after session is finalized" bullet2 = "Cannot add participants after session is finalised"
bullet3 = "Each participant receives a notification when it's their turn" bullet3 = "Each participant receives a notification when it's their turn"
description = "You can add more participants to an active session at any time before finalization." description = "You can add more participants to an active session at any time before finalisation."
title = "Adding Participants" title = "Adding Participants"
[sessionManagement.tooltip.finalization] [sessionManagement.tooltip.finalization]
bullet1 = "<b>Full finalization</b>: All participants have signed" bullet1 = "<b>Full finalisation</b>: All participants have signed"
bullet2 = "<b>Partial finalization</b>: Some participants haven't signed yet" bullet2 = "<b>Partial finalisation</b>: Some participants haven't signed yet"
bullet3 = "Unsigned participants will be excluded from the final document" bullet3 = "Unsigned participants will be excluded from the final document"
bullet4 = "Once finalized, you can load the signed PDF into active files" bullet4 = "Once finalised, you can load the signed PDF into active files"
description = "Finalization combines all signatures into a single signed PDF. This action cannot be undone." description = "Finalisation combines all signatures into a single signed PDF. This action cannot be undone."
title = "Session Finalization" title = "Session Finalisation"
[sessionManagement.tooltip.participantRemoval] [sessionManagement.tooltip.participantRemoval]
bullet1 = "Cannot remove participants who have already signed" bullet1 = "Cannot remove participants who have already signed"
@@ -6850,12 +6850,12 @@ modeDisabled = "Don't check for updates"
modePrompt = "Ask me before installing updates" modePrompt = "Ask me before installing updates"
serverNeedsUpdate = "Server needs to be updated by administrator" serverNeedsUpdate = "Server needs to be updated by administrator"
title = "Software Updates" title = "Software Updates"
updateBehavior = "Update behavior" updateBehavior = "Update behaviour"
updateBehaviorDescription = "Choose whether to prompt before installing updates, install them automatically, or skip update checks entirely." updateBehaviorDescription = "Choose whether to prompt before installing updates, install them automatically, or skip update checks entirely."
updateBehaviorError = "Could not change update behavior" updateBehaviorError = "Could not change update behaviour"
updateBehaviorErrorLocked = "This setting is locked by your administrator." updateBehaviorErrorLocked = "This setting is locked by your administrator."
updateBehaviorLockedDescription = "Your administrator has configured how Stirling-PDF handles updates on this machine. Contact them to change this." updateBehaviorLockedDescription = "Your administrator has configured how Stirling-PDF handles updates on this machine. Contact them to change this."
updateBehaviorSaved = "Update behavior saved." updateBehaviorSaved = "Update behaviour saved."
versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version." versionMismatch = "Warning: A mismatch has been detected between the client version and the AppConfig version. Using different versions can lead to compatibility issues, errors, and security risks. Please ensure that server and client are using the same version."
viewDetails = "View Details" viewDetails = "View Details"
@@ -6889,7 +6889,7 @@ capturing = "Press keys… (Esc to cancel)"
change = "Change shortcut" change = "Change shortcut"
customBadge = "Custom" customBadge = "Custom"
defaultLabel = "Default: {{shortcut}}" defaultLabel = "Default: {{shortcut}}"
description = "Customize keyboard shortcuts for quick tool access. Click \"Change shortcut\" and press a new key combination. Press Esc to cancel." description = "Customise keyboard shortcuts for quick tool access. Click \"Change shortcut\" and press a new key combination. Press Esc to cancel."
errorConflict = "Shortcut already used by {{tool}}." errorConflict = "Shortcut already used by {{tool}}."
none = "Not assigned" none = "Not assigned"
noShortcut = "No shortcut set" noShortcut = "No shortcut set"
@@ -6946,7 +6946,7 @@ retry = "Retry"
[settings.planBilling.status] [settings.planBilling.status]
active = "Active" active = "Active"
canceled = "Canceled" canceled = "Cancelled"
pastDue = "Past Due" pastDue = "Past Due"
trial = "Trial" trial = "Trial"
@@ -7025,7 +7025,7 @@ title = "Team"
[settings.tooltips] [settings.tooltips]
enableLoginFirst = "Enable login mode first" enableLoginFirst = "Enable login mode first"
requiresEnterprise = "Requires Enterprise license" requiresEnterprise = "Requires Enterprise licence"
[settings.workspace] [settings.workspace]
people = "People" people = "People"
@@ -7119,7 +7119,7 @@ redo = "Redo"
save = "Save Signature" save = "Save Signature"
sharedSigs = "Shared Signatures" sharedSigs = "Shared Signatures"
submit = "Sign Document" submit = "Sign Document"
tags = "signature,autograph,e-sign,electronic signature,digital signature,sign document,approval,signoff,authorize,endorse,ink signature,handwriting" tags = "signature,autograph,e-sign,electronic signature,digital signature,sign document,approval,signoff,authorise,endorse,ink signature,handwriting"
title = "Sign" title = "Sign"
undo = "Undo" undo = "Undo"
updateAndPlace = "Update and Place" updateAndPlace = "Update and Place"
@@ -7362,7 +7362,7 @@ bullet4 = "You can change methods at any time before processing"
title = "Choose Your Split Method" title = "Choose Your Split Method"
[split.methodSelection.tooltip.header] [split.methodSelection.tooltip.header]
text = "Choose how you want to split your PDF document. Each method is optimized for different use cases and document types." text = "Choose how you want to split your PDF document. Each method is optimised for different use cases and document types."
title = "Split Method Selection" title = "Split Method Selection"
[split.poster] [split.poster]
@@ -7460,7 +7460,7 @@ placeholder = "e.g. 5, 10"
[split-by-sections] [split-by-sections]
merge = "Merge Into One PDF" merge = "Merge Into One PDF"
tags = "Section Split, Divide, Customize,Customise" tags = "Section Split, Divide, Customise,Customise"
title = "Split PDF by Sections" title = "Split PDF by Sections"
[split-by-sections.customPages] [split-by-sections.customPages]
@@ -7498,7 +7498,7 @@ includeMetadata = "Include Metadata"
title = "Split PDF by Chapters" title = "Split PDF by Chapters"
[splitPdfByChapters] [splitPdfByChapters]
tags = "split,chapters,bookmarks,organize" tags = "split,chapters,bookmarks,organise"
[storageShare] [storageShare]
accessDenied = "You do not have access to this shared file. Ask the owner to share it with you." accessDenied = "You do not have access to this shared file. Ask the owner to share it with you."
@@ -7653,7 +7653,7 @@ roleColumn = "Role"
title = "Team Members" title = "Team Members"
[textAlign] [textAlign]
center = "Center" center = "Centre"
left = "Left" left = "Left"
right = "Right" right = "Right"
@@ -7823,7 +7823,7 @@ urgent = "Urgent"
[upgradeBanner] [upgradeBanner]
attentionBody = "Your admin needs to sign in to see more info. Please contact them immediately." attentionBody = "Your admin needs to sign in to see more info. Please contact them immediately."
attentionBodyAdmin = "Review the license requirements to keep this server compliant." attentionBodyAdmin = "Review the licence requirements to keep this server compliant."
attentionTitle = "This server needs admin attention" attentionTitle = "This server needs admin attention"
message = "Get the most out of Stirling PDF with unlimited users and advanced features" message = "Get the most out of Stirling PDF with unlimited users and advanced features"
seeInfo = "See info" seeInfo = "See info"
@@ -8125,7 +8125,7 @@ autoScanHelp = "New PDF files in this folder are processed automatically every 1
changeFolder = "Change" changeFolder = "Change"
chooseFolder = "Choose" chooseFolder = "Choose"
clearFolder = "Clear" clearFolder = "Clear"
color = "Accent color" color = "Accent colour"
createFolder = "Create Folder" createFolder = "Create Folder"
createTitle = "New Watched Folder" createTitle = "New Watched Folder"
editTitle = "Edit Watched Folder" editTitle = "Edit Watched Folder"
@@ -8372,7 +8372,7 @@ text = "Text"
header = "Signature Creation Methods" header = "Signature Creation Methods"
[wetSignature.tooltip.draw] [wetSignature.tooltip.draw]
bullet1 = "Customize pen color and thickness" bullet1 = "Customise pen colour and thickness"
bullet2 = "Clear and redraw until satisfied" bullet2 = "Clear and redraw until satisfied"
bullet3 = "Works on touch devices (tablets, phones)" bullet3 = "Works on touch devices (tablets, phones)"
description = "Create a handwritten signature using your mouse or touchscreen. Best for personal, authentic signatures." description = "Create a handwritten signature using your mouse or touchscreen. Best for personal, authentic signatures."
@@ -8380,8 +8380,8 @@ title = "Draw Signature"
[wetSignature.tooltip.type] [wetSignature.tooltip.type]
bullet1 = "Choose from multiple fonts" bullet1 = "Choose from multiple fonts"
bullet2 = "Customize text size and color" bullet2 = "Customise text size and colour"
bullet3 = "Perfect for standardized signatures" bullet3 = "Perfect for standardised signatures"
description = "Generate a signature from typed text. Fast and consistent, suitable for business documents." description = "Generate a signature from typed text. Fast and consistent, suitable for business documents."
title = "Type Signature" title = "Type Signature"
@@ -8592,7 +8592,7 @@ username = "Username"
[workspace.people.license] [workspace.people.license]
currentUsage = "Currently using {{current}} of {{max}} user licences" currentUsage = "Currently using {{current}} of {{max}} user licences"
fromLicense = "from license" fromLicense = "from licence"
grandfathered = "Grandfathered" grandfathered = "Grandfathered"
grandfatheredShort = "{{count}} grandfathered" grandfatheredShort = "{{count}} grandfathered"
noSlotsAvailable = "No slots available" noSlotsAvailable = "No slots available"
@@ -8623,7 +8623,7 @@ confirmDelete = "Are you sure you want to delete this team? This team must be em
confirmRemove = "Remove user from this team?" confirmRemove = "Remove user from this team?"
createNewTeam = "Create New Team" createNewTeam = "Create New Team"
deleteTeamLabel = "Delete Team" deleteTeamLabel = "Delete Team"
description = "Manage teams and organize workspace members" description = "Manage teams and organise workspace members"
loadError = "Failed to load team details" loadError = "Failed to load team details"
loading = "Loading teams..." loading = "Loading teams..."
loadingDetails = "Loading team details..." loadingDetails = "Loading team details..."
@@ -50,12 +50,9 @@ help = "Help"
imgPrompt = "Select Image(s)" imgPrompt = "Select Image(s)"
incorrectPasswordMessage = "Current password is incorrect." incorrectPasswordMessage = "Current password is incorrect."
info = "Info" info = "Info"
insufficientCredits = "Insufficient credits. Required: {{requiredCredits}}, Available: {{currentBalance}}, Shortfall: {{shortfall}}"
invalidUndoData = "Cannot undo: invalid operation data" invalidUndoData = "Cannot undo: invalid operation data"
keepWorking = "Keep Working" keepWorking = "Keep Working"
loading = "Loading..." loading = "Loading..."
loadingCredits = "Checking credits..."
loadingProStatus = "Checking subscription status..."
logOut = "Log out" logOut = "Log out"
marginTooltip = "Distance between the page number and the edge of the page." marginTooltip = "Distance between the page number and the edge of the page."
moreOptions = "More Options" moreOptions = "More Options"
@@ -66,7 +63,6 @@ noFileSelected = "No file loaded. Please upload one."
noFilesToUndo = "Cannot undo: no files were processed in the last operation" noFilesToUndo = "Cannot undo: no files were processed in the last operation"
noOperationToUndo = "No operation to undo" noOperationToUndo = "No operation to undo"
nothingToUndo = "Nothing to undo" nothingToUndo = "Nothing to undo"
noticeTopUpOrPlan = "Not enough credits, please top up or upgrade to a plan"
noValidFiles = "No valid files to process" noValidFiles = "No valid files to process"
oops = "Oops!" oops = "Oops!"
openInNewWindow = "Open in new window" openInNewWindow = "Open in new window"
@@ -1134,6 +1130,10 @@ apikeyNote = "Clients send a Stirling API key in the X-API-KEY header (or Author
description = "Expose Stirling's PDF tools and AI agents to MCP clients over an OAuth-protected endpoint." description = "Expose Stirling's PDF tools and AI agents to MCP clients over an OAuth-protected endpoint."
title = "MCP Server" title = "MCP Server"
[admin.settings.mcp.acceptedAudiences]
description = "Extra token audience values accepted besides the Resource ID (comma or space separated). Leave blank for strict RFC 8707. Needed for IdPs that cannot mint resource audiences - e.g. Supabase's OAuth server always issues aud=authenticated."
label = "Additional accepted audiences (optional)"
[admin.settings.mcp.allowedOps] [admin.settings.mcp.allowedOps]
description = "If set, ONLY these operation ids are exposed (an allow-list; comma or space separated). Leave blank to expose all enabled tools except any blocked below." description = "If set, ONLY these operation ids are exposed (an allow-list; comma or space separated). Leave blank to expose all enabled tools except any blocked below."
label = "Allowed tools" label = "Allowed tools"
@@ -1575,30 +1575,7 @@ admin = "Admin"
title = "User Control Settings" title = "User Control Settings"
[agents] [agents]
auto_redaction_description = "Redact PII automatically"
auto_redaction_name = "Auto Redaction"
back_to_tools = "Back to tools"
coming_soon = "Coming soon"
compliance_description = "Audit documents for compliance"
compliance_name = "Compliance Check"
data_extraction_description = "Extract tables & structured data"
data_extraction_name = "Data Extraction"
doc_summary_description = "Summarize long documents"
doc_summary_name = "Summarizer"
form_filler_description = "Fill PDF forms intelligently"
form_filler_name = "Form Filler"
pdf_to_markdown_description = "Convert PDFs to clean Markdown"
pdf_to_markdown_name = "PDF to Markdown"
section_title = "Agents"
show_less = "Show less"
start_chat = "Start chatting"
stirling_description = "Your general-purpose PDF assistant"
stirling_full_name = "Stirling General Agent"
stirling_long_description = "General purpose PDF assistant that can run tools, create PDFs and extract insights from your documents."
stirling_name = "Stirling" stirling_name = "Stirling"
stirling_running = "Running..."
stirling_tooltip = "Stirling agent"
view_all = "View all agents"
[analytics] [analytics]
learnMore = "Learn more about our analytics" learnMore = "Learn more about our analytics"
@@ -2678,9 +2655,6 @@ title = "Change Permissions"
[changePermissions.tooltip.warning] [changePermissions.tooltip.warning]
text = "To make these permissions unchangeable, use the Add Password tool to set an owner password." text = "To make these permissions unchangeable, use the Add Password tool to set an owner password."
[chat]
resize = "Resize chat panel"
[chat.actions] [chat.actions]
copy = "Copy message" copy = "Copy message"
@@ -2695,6 +2669,7 @@ clearChat = "Clear chat"
[chat.input] [chat.input]
placeholder = "What do you want to do?" placeholder = "What do you want to do?"
send = "Send message" send = "Send message"
disclaimer = "AI can make mistakes. Be sure to verify the output before sharing."
[chat.progress] [chat.progress]
analyzing = "Analyzing your request..." analyzing = "Analyzing your request..."
@@ -2729,8 +2704,6 @@ mergeMany = "Merge these {{count}} documents into 1"
moreFiles = "+{{count}} more" moreFiles = "+{{count}} more"
openFromComputer = "Open from computer" openFromComputer = "Open from computer"
removeFile = "Remove {{name}}" removeFile = "Remove {{name}}"
rotateMany = "Rotate these documents"
rotateOne = "Rotate this document"
splitOne = "Split this document" splitOne = "Split this document"
[chat.responses] [chat.responses]
@@ -2767,7 +2740,6 @@ copy = "Copy"
done = "Done" done = "Done"
error = "Error" error = "Error"
expand = "Expand" expand = "Expand"
learnMore = "Learn more"
loading = "Loading..." loading = "Loading..."
next = "Next" next = "Next"
operation = "this operation" operation = "this operation"
@@ -2966,6 +2938,7 @@ tags = "squish,small,tiny"
[config] [config]
plan = "Plan" plan = "Plan"
team = "Team"
[config.account.overview] [config.account.overview]
confirmDelete = "Delete My Account" confirmDelete = "Delete My Account"
@@ -3082,14 +3055,6 @@ addTo = "Add to"
hint = "Pick your client, paste the snippet into the file shown, then restart it. You'll sign in with your Stirling account on first use - no keys to copy." hint = "Pick your client, paste the snippet into the file shown, then restart it. You'll sign in with your Stirling account on first use - no keys to copy."
title = "Connect your AI assistant" title = "Connect your AI assistant"
[config.mcp.tools]
ai = "AI"
convert = "Convert"
misc = "Misc"
pages = "Pages"
security = "Security"
title = "What your assistant can do"
[config.overview] [config.overview]
description = "Current application settings and configuration details." description = "Current application settings and configuration details."
error = "Error" error = "Error"
@@ -4750,6 +4715,34 @@ title = "Authentication Failed"
message = "You can close this window and return to Stirling PDF." message = "You can close this window and return to Stirling PDF."
title = "Authentication Successful" title = "Authentication Successful"
[oauthConsent]
approve = "Allow access"
approving = "Allowing..."
decisionFailed = "Could not submit your decision. Please try again."
deny = "Deny"
denying = "Denying..."
loadFailed = "This authorization request is invalid or has expired. Close this tab and try connecting again from the app."
loading = "Loading authorization request..."
missingId = "Missing authorization request. Start the connection from your app and try again."
redirecting = "Returning you to the app..."
requesting = "{{app}} wants to access your Stirling PDF account"
scopesIntro = "This will allow {{app}} to:"
signedInAs = "Signed in as {{name}}"
signInButton = "Sign in to continue"
signInPrompt = "Sign in to your Stirling PDF account to continue connecting the app."
title = "Authorize access"
unknownApp = "A third-party application"
[oauthConsent.access]
actAsYou = "Act as you - everything {{app}} does runs under your account and counts towards your usage"
tools = "Use your Stirling PDF tools on your behalf - convert, edit, sign, secure and process your documents"
[oauthConsent.scope]
email = "See your email address"
openid = "Confirm your identity"
phone = "See your phone number"
profile = "See your basic profile information"
[ocr] [ocr]
desc = "Cleanup scans and detects text from images within a PDF and re-adds it as text." desc = "Cleanup scans and detects text from images within a PDF and re-adds it as text."
tags = "recognition,text,image,scan,read,identify,detection,editable" tags = "recognition,text,image,scan,read,identify,detection,editable"
@@ -4867,15 +4860,6 @@ body = "Stirling works best as a desktop app. You can use it offline, access doc
title = "Download" title = "Download"
titleWithOs = "Download for {{osLabel}}" titleWithOs = "Download for {{osLabel}}"
[onboarding.freeTrial]
afterTrialWithoutPayment = "After your trial ends, you'll continue with our free tier. Add a payment method to keep Pro access."
afterTrialWithPayment = "Your Pro subscription will start automatically when the trial ends."
body = "You have full access to Stirling PDF Pro features during your trial. Enjoy unlimited conversions, larger file sizes, and priority processing."
daysRemaining = "{{days}} days remaining"
daysRemainingSingular = "{{days}} day remaining"
title = "Your 30-Day Pro Trial"
trialEnds = "Trial ends {{date}}"
[onboarding.mfa] [onboarding.mfa]
authenticationCode = "Authentication code" authenticationCode = "Authentication code"
qrCodeLoading = "Generating your QR code…" qrCodeLoading = "Generating your QR code…"
@@ -4887,6 +4871,22 @@ adminTitle = "Admin Overview"
userBody = "Invite teammates, assign roles, and keep your documents organized in one secure workspace. Enable login mode whenever you're ready to grow beyond solo use." userBody = "Invite teammates, assign roles, and keep your documents organized in one secure workspace. Enable login mode whenever you're ready to grow beyond solo use."
userTitle = "Plan Overview" userTitle = "Plan Overview"
[onboarding.saas.freeEditor]
freeLine = "And the best part? The editor is now <free>completely free</free>."
premium = "We've added a whole host of new features, including <strong>Policies</strong> and <strong>Agent Chat</strong>."
title = "Welcome to Stirling"
[onboarding.saas.team]
addButton = "Add"
createBody = "Work on documents together — teammates share files, automations and your plan. Add the first member by email to create your team."
createTitle = "Create your team"
inviteBody = "Everyone on your team shares files, automations and your plan. Add teammates by email — they'll get an invite right away."
inviteTitle = "Invite members to your team"
[onboarding.saas.usage]
body = "Automations, AI and API requests draw from your one-time free allowance. Here's where you stand - manual editing never counts against it."
title = "Your free Processor allowance"
[onboarding.securityCheck] [onboarding.securityCheck]
message = "The application has undergone significant changes recently. Your server admin's attention may be required. Please confirm your role to continue." message = "The application has undergone significant changes recently. Your server admin's attention may be required. Please confirm your role to continue."
roleAdmin = "Admin" roleAdmin = "Admin"
@@ -5157,6 +5157,209 @@ title = "Page Ranges"
bullet1 = "<strong>all</strong> → selects all pages" bullet1 = "<strong>all</strong> → selects all pages"
title = "Special Keywords" title = "Special Keywords"
[payg.activity]
docs = "docs"
empty = "No billable activity yet this period."
subtitle = "Only AI and automation draw from your budget. Everyday tools are free and aren't listed here."
title = "Recent billable activity"
[payg.cap]
amount = "Cap amount"
custom = "Custom"
docsEstimate = "≈ {{docs}} processed PDFs / month"
docsRate = "at {{rate}} / PDF"
noCapDesc = "Usage is billed without an upper limit. You can re-enable a cap at any time."
noCapLabel = "No cap"
noneShort = "No cap"
perMonth = "/ month"
save = "Update cap"
subtitle = "Set your maximum spend. We convert this to PDFs using the current price tier."
title = "Monthly spending cap"
[payg.checkout]
connecting = "Connecting to Stripe…"
errorTitle = "Stripe error"
[payg.checkout.error]
startFailed = "Couldn't start checkout session"
[payg.checkout.mock]
backend = "Backend is in mock mode; no real Stripe session was created."
continue = "Continue with mock subscription"
noKey = "VITE_STRIPE_PUBLISHABLE_KEY is unset. Real iframe mounts here once configured."
title = "Stripe Embedded Checkout (mock mode)"
[payg.confirm]
body = "Your team can now run automation, AI, and API operations beyond your 500 free PDFs."
capValue = "{{symbol}}{{amount}} / month"
noCap = "No cap"
note = "You can change your cap, cancel, or open the Stripe customer portal any time from this page."
summaryLabel = "Monthly ceiling"
title = "Welcome to the Processor plan"
[payg.docHelp]
billable = "Only automation runs, AI tools, and API calls count. Manual tools in the editor are always free."
chains = "Running the same file through several steps of one automation counts it once, not once per step."
perFile = "Each file you process counts as one PDF. Very long or very large files can count as more than one."
refunds = "If a job fails on its first step, the PDF is credited back automatically."
toggle = "What counts as a PDF?"
[payg.error]
body = "We couldn't reach the billing service. Refresh the page to try again."
title = "Couldn't load your plan"
[payg.free.cta]
benefit1Body = "chain tools, schedule runs, batch process"
benefit1Title = "Automation pipelines"
benefit2Body = "summarize, classify, redact, AI-OCR"
benefit2Title = "AI tools"
benefit3Body = "call any Stirling endpoint programmatically"
benefit3Title = "API access"
button = "Turn on Processor →"
reassurance = "No minimum · Set a $0 cap to test · Cancel anytime"
subtitle = "Keep going past your 500 free PDFs with automation, AI, and the API. Set a monthly ceiling, so you stay in control."
title = "Turn on the Processor plan"
[payg.free.editor]
eyebrow = "Editor plan · Always free"
[payg.free.header]
freeBody = "View, edit, merge, split, sign, watermark, compress, convert and manual OCR — as much as you want, no matter where you trigger it."
freeTitle = "Unlimited PDF editing"
[payg.free.hero]
capSuffix = "/ {{limit}} free PDFs"
metaCategories = "Automation · AI · API requests"
neverResets = "One-time, never resets"
[payg.free.member]
ownerOnly = "Only your team owner can turn on Processor. Manual tools stay free for you to use as much as you like."
[payg.free.proc]
eyebrow = "Processor plan · metered"
[payg.free.state]
approachingLimit = "Approaching limit"
limitReached = "Limit reached"
plentyLeft = "Plenty left"
[payg.gates]
ai = "AI tools (AI Create, suggestions, AI-OCR)"
automation = "Automations & pipelines"
client = "Browser-only tools (viewer, page editor, file management)"
offsite = "Server tools (compress, OCR, convert, watermark…)"
pauses = "pauses at cap"
title = "What happens when the cap is reached"
[payg.header]
eyebrow = "Processor plan · {{start}} {{end}}"
freeBody = "View, edit, merge, split, sign, watermark, compress, convert and manual OCR — as much as you want, no matter where you trigger it."
freeLabel = "Always free"
freeTitle = "Unlimited PDF editing"
meterBody = "{{limit}} free PDFs to start, then billed per PDF up to your cap."
meterLabel = "Metered"
meterTitle = "Automation · AI · API"
[payg.member]
askLeader = "Only your team owner can change the cap."
[payg.members]
docs = "PDFs"
subtitle = "Billable PDFs each teammate has processed this period."
title = "Team member usage"
[payg.role]
leader = "Team owner"
member = "Member"
[payg.signupRequired]
body = "Stirling PDF gives every signed-up account 500 free operations, enough to keep most workflows humming without paying a cent. You're currently using Stirling as a guest, which doesn't include billable tools like AI, automations, or hosted processing."
cancel = "Not now"
cta = "Sign up free"
subtext = "Creating an account is free and takes a few seconds. No credit card required."
title = "Sign up to use {{category}}"
[payg.signupRequired.category]
ai = "AI features"
api = "this tool"
automation = "automations"
default = "this feature"
[payg.spendCapMeter]
capSuffix = "/ {{amount}} cap"
metaCategories = "Automation · AI · API spend"
resets = "Resets each billing period"
[payg.state]
degraded = "Cap reached"
full = "Healthy"
warned = "Approaching cap"
[payg.stripe]
open = "Open billing portal"
subtitle = "Receipts, invoices, payment method, billing currency."
title = "Manage billing in Stripe"
[payg.stripe.toast.unavailable]
body = "Billing portal isn't available right now. Try again in a moment."
title = "Billing portal unavailable"
[payg.upgrade]
backAria = "Back"
closeAria = "Close"
[payg.upgrade.button]
cancel = "Cancel"
continue = "Continue →"
finish = "Finish"
[payg.upgrade.cap]
help = "We'll never charge above this. Set $0 if you want to keep everything free while testing."
title = "Set your monthly spend ceiling"
usdNote = "Estimated in USD. You can adjust your cap any time after subscribing, in your own currency."
[payg.upgrade.checkout]
capValue = "{{symbol}}{{amount}} / month"
edit = "Edit"
help = "Stripe handles your card details. Stirling never sees them."
loading = "Loading checkout…"
noCap = "No cap"
title = "Add your payment method"
[payg.upgrade.help]
aiBody = "summarize, classify, redact, AI-OCR"
aiTitle = "AI tools"
apiBody = "programmatic access to any Stirling endpoint"
apiTitle = "API calls"
automationBody = "chained tools or scheduled runs that don't need clicks"
automationTitle = "Automation pipelines"
footnote = "Manual tools (viewing, editing, merging, splitting, watermarking, compressing, manual OCR) are always free, even past 500. The distinction is the type of work, not where you click."
title = "What we count toward billing"
[payg.upgrade.promise]
body = "You only pay for automation pipelines, AI tools, and API calls, the work that goes beyond a single click. Edit, merge, split, sign, compress as much as you want, no charge."
highlight = "Manual tools stay free, always."
[payg.upgrade.steps]
cap = "Set monthly ceiling"
payment = "Add payment method"
[payg.upgrade.title]
confirm = "You're subscribed"
default = "Upgrade to Processor plan"
[payg.usage]
breakdown = "AI {{ai}} • Automation {{automation}} • API {{api}}"
capLine = "{{cap}}/mo cap"
estBill = "≈ {{amount}} so far this period"
firstFree = "First {{free}} free"
noCap = "No monthly cap"
ofLimitProcessed = "/ {{limit}} PDFs processed"
processed = "PDFs processed"
resetsIn = "Resets in {{days}} days"
resetsTomorrow = "Resets tomorrow"
thisPeriod = "This billing period"
[payment] [payment]
autoClose = "This window will close automatically..." autoClose = "This window will close automatically..."
canCloseWindow = "You can now close this window." canCloseWindow = "You can now close this window."
@@ -5480,28 +5683,17 @@ manage = "Manage"
perMonth = "/month" perMonth = "/month"
perSeat = "/seat" perSeat = "/seat"
popular = "Popular" popular = "Popular"
purchase = "Purchase"
selectCredits = "Select Credit Amount"
selectPlan = "Select Plan" selectPlan = "Select Plan"
showComparison = "Compare All Features" showComparison = "Compare All Features"
totalCost = "Total Cost"
upgrade = "Upgrade" upgrade = "Upgrade"
withServer = "+ Server Plan" withServer = "+ Server Plan"
[plan.activePlan]
subtitle = "Your current subscription details"
title = "Active Plan"
[plan.api] [plan.api]
large = "5,000 Credits" large = "5,000 Credits"
medium = "1,000 Credits" medium = "1,000 Credits"
small = "500 Credits" small = "500 Credits"
xsmall = "100 Credits" xsmall = "100 Credits"
[plan.apiPackages]
subtitle = "Purchase API credits for your applications"
title = "API Credit Packages"
[plan.availablePlans] [plan.availablePlans]
loadError = "Unable to load plan pricing. Using default values." loadError = "Unable to load plan pricing. Using default values."
subtitle = "Choose the plan that fits your needs" subtitle = "Choose the plan that fits your needs"
@@ -5553,6 +5745,12 @@ highlight3 = "Community support"
included = "Included" included = "Included"
name = "Free" name = "Free"
[plan.freeLimit]
cta = "View Processor Plan"
dismiss = "Maybe Later"
message = "That's your whole free allowance for automation, AI and the API. Seriously impressive! Keep the momentum going for just pennies a day."
title = "Woah, {{total}} PDFs Processed!"
[plan.highlights] [plan.highlights]
advancedIntegrations = "Advanced integrations" advancedIntegrations = "Advanced integrations"
allBasicFeatures = "All basic features" allBasicFeatures = "All basic features"
@@ -5584,6 +5782,12 @@ highlight2 = "Advanced PDF tools"
highlight3 = "No watermarks" highlight3 = "No watermarks"
name = "Pro" name = "Pro"
[plan.spendCap]
cta = "View Spending Limit"
dismiss = "Not Now"
message = "You've made the most of this month's cap. That's a load of automation, AI and API work! Bump it up whenever you like to keep going."
title = "You're on a Roll!"
[plan.static] [plan.static]
activateLicense = "Activate Your License" activateLicense = "Activate Your License"
contactToUpgrade = "Contact us to upgrade or customize your plan" contactToUpgrade = "Contact us to upgrade or customize your plan"
@@ -5612,18 +5816,115 @@ successMessage = "Your license has been successfully activated. You can now clos
name = "Team" name = "Team"
[plan.trial] [plan.trial]
badge = "Trial"
continueWithFree = "Continue with Free" continueWithFree = "Continue with Free"
daysRemaining = "Your trial ends in {{days}} days"
endDate = "Expires: {{date}}"
expired = "Your Trial Has Ended" expired = "Your Trial Has Ended"
expiredMessage = "Your 30-day Pro trial has expired. Subscribe to Pro to continue accessing premium features, or continue with our free tier." expiredMessage = "Your 30-day Pro trial has expired. Subscribe to Pro to continue accessing premium features, or continue with our free tier."
freeTierLimitations = "Free tier includes basic PDF tools with usage limits." freeTierLimitations = "Free tier includes basic PDF tools with usage limits."
message = ""
subscribe = "Subscribe to Pro" subscribe = "Subscribe to Pro"
subscribeToPro = "Subscribe to Pro" subscribeToPro = "Subscribe to Pro"
subscriptionScheduled = "Subscription scheduled - starts {{date}}"
title = "Free Trial Active" [policies]
deleteConfirmBody = "This removes the policy and its workflow. Documents already processed are not affected."
deleteConfirmTitle = "Delete {{label}} policy?"
[policies.catalog]
compliance = "Compliance"
ingestion = "Ingestion"
retention = "Retention"
routing = "Routing"
security = "Security"
[policies.detail]
editSettings = "Edit Settings"
enforces = "Enforces"
managedByOrg = "Managed by your organization. Contact a team leader to change this policy."
noActivityDescription = "Documents will appear here once this policy runs."
noActivityTitle = "No activity yet"
onEveryUpload = "On every upload"
originalsNote = "Originals stay untouched • Enforced version saved alongside"
pause = "Pause"
recentActivity = "Recent Activity"
resume = "Resume"
retry = "Retry"
showLess = "Show less"
showMore = "Show more"
statActive = "Active"
statDataProcessed = "Data processed"
statDocsEnforced = "Docs enforced"
statusActive = "Active"
statusPaused = "Paused"
[policies.fields]
selectedCount = "{{count}} selected"
[policies.pii]
fieldLabel = "PII to redact"
placeholder = "Select PII types"
[policies.sidebar]
activeCount = "{{count}} active"
infoAriaLabel = "What is a policy?"
infoTooltip = "A policy is a fixed set of tools that runs automatically whenever it's triggered — for example when a new document arrives — enforcing rules like redacting PII with no manual steps."
loading = "Loading…"
railAriaLabel = "{{label}} policy — {{status}}"
railSuffixActive = " (Active)"
railSuffixPaused = " (Paused)"
setUp = "Set up"
title = "Policies"
upgradeToEnterprise = "Upgrade to enterprise"
[policies.status]
active = "Active"
paused = "Paused"
setup = "Set up"
[policies.toolConfig]
enableAriaLabel = "Enable {{tool}}"
infoAriaLabel = "What does {{tool}} do?"
[policies.wizard]
allDocTypesDescription = "Enable the Classification policy to filter by document type."
allDocTypesTitle = "All document types"
back = "Back"
builderDesc = "Build the sequence of tools this policy runs on each document."
clear = "Clear"
continue = "Continue"
docTypesLabel = "Document types"
edit = "Edit"
editTitle = "Edit {{label}} Policy"
enablePolicy = "Enable Policy"
filenameAutoNumber = "Auto-number"
filenamePositionAria = "Filename position"
filenamePrefix = "Prefix"
filenameSuffix = "Suffix"
filenameTextAria = "Filename text"
filenameTextPlaceholder = "Text to add (optional)"
lockedDescription = "Contact a team leader to change this policy."
lockedTitle = "Managed by your organization"
maxRetriesLabel = "Max retries"
noToolsError = "Add at least one configured tool to the workflow first."
outputAsLabel = "Output as"
outputFilenameSubhead = "Output filename"
outputModeAria = "Output mode"
outputNewFile = "New file"
outputNewVersion = "New version"
outputRetriesLabel = "Output & retries"
outputSubhead = "Output"
retryDelayAria = "Retry delay minutes"
retryDelayLabel = "Retry delay (min)"
runOnExport = "Export"
runOnLabel = "Run on"
runOnSubhead = "Run on"
runOnUpload = "Upload"
saveChanges = "Save Changes"
saveError = "Couldn't save the policy. Please try again."
setupClassification = "Set up Classification"
setupTitle = "Set up {{label}} Policy"
sourcesDesc = "Choose where this policy runs and which document types it applies to."
sourcesLabel = "Sources"
stepOf = "Step {{step}} of {{total}}"
toolChainDesc = "Configure the tools this policy runs on each document."
typesSelected = "{{count}} types selected"
[printFile] [printFile]
title = "Print File" title = "Print File"
@@ -6568,6 +6869,18 @@ title = "Keyboard Shortcuts"
mac = "Include ⌘ (Command), ⌥ (Option), or another modifier in your shortcut." mac = "Include ⌘ (Command), ⌥ (Option), or another modifier in your shortcut."
windows = "Include Ctrl, Alt, or another modifier in your shortcut." windows = "Include Ctrl, Alt, or another modifier in your shortcut."
[settings.legal]
label = "Legal"
title = "Legal"
[settings.legal.cookiePreferences]
description = "Review or change your cookie consent choices."
manage = "Manage"
[settings.legal.documents]
description = "Policies and legal information for this service."
title = "Legal Documents"
[settings.licensingAnalytics] [settings.licensingAnalytics]
audit = "Audit" audit = "Audit"
plan = "Plan" plan = "Plan"
@@ -7256,7 +7569,6 @@ updateButton = "Update on Server"
uploadButton = "Upload to Server" uploadButton = "Upload to Server"
[survey] [survey]
nav = "Survey"
title = "Stirling-PDF Survey" title = "Stirling-PDF Survey"
[swagger] [swagger]
@@ -7289,28 +7601,6 @@ removeError = "Failed to remove member"
renameError = "Failed to rename team" renameError = "Failed to rename team"
renameSuccess = "Team renamed successfully" renameSuccess = "Team renamed successfully"
[team.features]
badge = "Team Features"
subtitle = "Collaborate with your team"
title = "Team Collaboration"
viewPlans = "View Plans"
[team.features.billing]
description = "Manage billing and subscriptions"
title = "Billing Management"
[team.features.credits]
description = "Shared credit pool for all members"
title = "Shared Credits"
[team.features.dashboard]
description = "View team activity and usage"
title = "Team Dashboard"
[team.features.invite]
description = "Add members to your team"
title = "Invite Members"
[team.invitationBanner] [team.invitationBanner]
acceptButton = "Accept" acceptButton = "Accept"
message = "has invited you to join" message = "has invited you to join"
@@ -7332,11 +7622,6 @@ remove = "Remove"
roleColumn = "Role" roleColumn = "Role"
title = "Team Members" title = "Team Members"
[team.upgrade]
button = "Upgrade to Team"
description = "Unlock team collaboration features"
title = "Upgrade to Team Plan"
[textAlign] [textAlign]
center = "Center" center = "Center"
left = "Left" left = "Left"
@@ -30,7 +30,9 @@ export async function savePolicy(
/** List all stored policies. */ /** List all stored policies. */
export async function listPolicies(): Promise<BackendPolicy[]> { export async function listPolicies(): Promise<BackendPolicy[]> {
const res = await apiClient.get<BackendPolicy[]>("/api/v1/policies"); const res = await apiClient.get<BackendPolicy[]>("/api/v1/policies", {
suppressErrorToast: true,
});
return res.data; return res.data;
} }
+467
View File
@@ -0,0 +1,467 @@
#!/usr/bin/env python3
"""Sync en-US translations from en-GB and normalise spelling variants.
Two jobs, one pass:
1. Copy every key that exists in en-GB but is missing from en-US into en-US,
converting British spellings to American on the way in. Keys that only exist
in en-US (e.g. SaaS-only strings) are left untouched.
2. Fix any American spellings that have leaked into the en-GB *values* by
converting them back to British.
Rules that keep this safe:
* Only the VALUE (the quoted right-hand side) is ever rewritten. TOML keys and
section headers are never touched -- they are code identifiers.
* Matching is whole-word and case-preserving, driven by an explicit word map.
We never match on substrings/stems, so "parameters", "entire", "literal",
"programmatically", "checkbox" etc. are never mangled.
* Software-ambiguous words whose "British" form would be wrong in a UI
(program, dialog, disk, check/cheque, story, catalog as a CS term...) are
deliberately left out of the map. See AMBIGUOUS_NOTES.
Usage:
python3 scripts/sync_en_us_spelling.py # apply changes
python3 scripts/sync_en_us_spelling.py --dry-run # report only
"""
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
LOCALES = Path(__file__).resolve().parent.parent / "frontend/editor/public/locales"
EN_US = LOCALES / "en-US/translation.toml"
EN_GB = LOCALES / "en-GB/translation.toml"
# --------------------------------------------------------------------------- #
# Spelling map: British (uk) -> American (us), as full whole words.
#
# Inflected forms are generated for the regular families so the map stays
# readable. Irregular / one-off pairs are listed explicitly at the bottom.
# --------------------------------------------------------------------------- #
# Words intentionally NOT mapped because the "British" form is wrong or
# ambiguous in a software UI context (documented, not applied):
AMBIGUOUS_NOTES = {
"program/programme": "program is correct in British English for software.",
"dialog/dialogue": "dialog is standard UI terminology (dialog box) in all locales.",
"disk/disc": "disk is standard for storage in all locales.",
"check/cheque": "check (verb/checkbox) is identical in both; only the money sense differs.",
"analog/analogue": "rare in this UI and collides with technical usage.",
"story/storey": "story (narrative/UI) is identical; only the building-floor sense differs.",
"meter/metre": "meter (a device/metered) is valid in British English too.",
}
uk_to_us: dict[str, str] = {}
def add(uk: str, us: str) -> None:
"""Register a British->American whole-word pair (lower-cased key)."""
uk_to_us[uk.lower()] = us.lower()
# --- -our / -or family (colour -> color) ---------------------------------- #
# us = uk with the single 'our' turned into 'or'.
_OUR = [
"colour", "colours", "coloured", "colouring", "colourful", "colourless",
"colourise", "colourised", "colouriser",
"favour", "favours", "favoured", "favouring", "favourite", "favourites",
"favourable", "favourably", "favouritism",
"behaviour", "behaviours", "behavioural",
"neighbour", "neighbours", "neighbouring",
"labour", "labours", "laboured", "labouring",
"honour", "honours", "honoured", "honouring", "honourable",
"humour", "humours", "humoured",
"flavour", "flavours", "flavoured", "flavouring",
"harbour", "harbours",
"rumour", "rumours", "rumoured",
"vapour", "vapours",
"odour", "odours",
"valour", "splendour", "armour", "armoured",
"saviour", "saviours",
"endeavour", "endeavours", "endeavoured",
"parlour", "savour", "savoured", "savouring", "savoury",
"candour", "demeanour", "rigour", "vigour",
]
for w in _OUR:
add(w, w.replace("our", "or"))
# --- -re / -er family (centre -> center) ---------------------------------- #
_RE = {
"centre": "center", "centres": "centers", "centred": "centered",
"centring": "centering",
"metre": "meter", "metres": "meters", # length unit; meter device excluded above
"litre": "liter", "litres": "liters",
"fibre": "fiber", "fibres": "fibers",
# NB: calibre is omitted -- in this codebase it is the proper noun
# Calibre (the e-book conversion tool), not the British caliber.
"sombre": "somber", "spectre": "specter", "lustre": "luster",
"theatre": "theater", "theatres": "theaters",
"metre": "meter",
"centimetre": "centimeter", "centimetres": "centimeters",
"millimetre": "millimeter", "millimetres": "millimeters",
"kilometre": "kilometer", "kilometres": "kilometers",
"manoeuvre": "maneuver", "manoeuvres": "maneuvers",
}
for uk, us in _RE.items():
add(uk, us)
# --- -ise / -isation family (organise -> organize) ------------------------ #
# Generate the regular inflections from a list of stems (the part before 'se').
_ISE_STEMS = [
"organi", "recogni", "customi", "optimi", "authori", "reali", "finali",
"initiali", "normali", "synchroni", "summari", "prioriti", "personali",
"capitali", "categori", "standardi", "visuali", "apologi", "utili",
"locali", "digiti", "moderni", "centrali", "minimi", "maximi", "emphasi",
"characteri", "stabili", "generali", "specifi? ".strip(),
"sterili", "neutrali", "saniti", "tokeni", "serie? ".strip(),
"alphabeti", "synthesi", "memori", "itemi", "colouri",
]
_ISE_STEMS = [s for s in _ISE_STEMS if s and not s.endswith("?")]
_ISE_SUFFIXES = ["se", "ses", "sed", "sing", "ser", "sers", "sation", "sations"]
for stem in _ISE_STEMS:
for suf in _ISE_SUFFIXES:
add(stem + suf, stem + suf.replace("s", "z", 1))
# -yse -> -yze verbs. Deliberately excludes the 3rd-person analyses/analyzes
# because it collides with the noun analyses (identical in both locales), and
# analysis/analyses which are spelled the same on both sides.
for uk, us in {
"analyse": "analyze", "analysed": "analyzed", "analysing": "analyzing",
"analyser": "analyzer",
"paralyse": "paralyze", "paralysed": "paralyzed", "paralysing": "paralyzing",
"catalyse": "catalyze", "catalysed": "catalyzed",
}.items():
add(uk, us)
# --- doubled-l family (cancelled -> canceled) ----------------------------- #
_LL = {
"cancelled": "canceled", "cancelling": "canceling",
"labelled": "labeled", "labelling": "labeling",
"modelled": "modeled", "modelling": "modeling",
"signalled": "signaled", "signalling": "signaling",
"travelled": "traveled", "travelling": "traveling", "traveller": "traveler",
"travellers": "travelers",
"fuelled": "fueled", "fuelling": "fueling",
"marvellous": "marvelous", "counsellor": "counselor",
"jewellery": "jewelry",
}
for uk, us in _LL.items():
add(uk, us)
# single-l where US doubles it (enrol -> enroll, fulfil -> fulfill)
_L_TO_LL = {
"enrol": "enroll", "enrols": "enrolls", "enrolment": "enrollment",
"enrolments": "enrollments",
"fulfil": "fulfill", "fulfils": "fulfills", "fulfilment": "fulfillment",
"fulfilments": "fulfillments",
"instalment": "installment", "instalments": "installments",
"skilful": "skillful", "wilful": "willful",
}
for uk, us in _L_TO_LL.items():
add(uk, us)
# --- -ce / -se nouns (licence -> license, defence -> defense) ------------- #
# Only the unambiguous noun forms; verb/adjective forms licensed/licensing
# are identical in both and are left alone.
_CE = {
"licence": "license", "licences": "licenses",
"defence": "defense", "defences": "defenses",
"offence": "offense", "offences": "offenses",
"pretence": "pretense",
}
for uk, us in _CE.items():
add(uk, us)
# --- -ogue (catalogue -> catalog) ----------------------------------------- #
# Note: dialogue/dialog and analogue/analog are excluded (UI terminology).
_OGUE = {
"catalogue": "catalog", "catalogues": "catalogs",
"catalogued": "cataloged", "cataloguing": "cataloging",
}
for uk, us in _OGUE.items():
add(uk, us)
# --- miscellaneous irregulars --------------------------------------------- #
_MISC = {
"grey": "gray", "greys": "grays", "greyed": "grayed", "greying": "graying",
"greyscale": "grayscale",
"mould": "mold", "moulds": "molds",
"sceptical": "skeptical", "sceptic": "skeptic", "scepticism": "skepticism",
"aluminium": "aluminum",
"artefact": "artifact", "artefacts": "artifacts",
"speciality": "specialty", "specialities": "specialties",
"judgement": "judgment", "judgements": "judgments",
"acknowledgement": "acknowledgment", "acknowledgements": "acknowledgments",
"kerb": "curb", "kerbs": "curbs",
"tyre": "tire", "tyres": "tires",
"sulphur": "sulfur",
"enquiry": "inquiry", "enquiries": "inquiries",
"ageing": "aging",
"cancelled": "canceled", # belt & braces (also in _LL)
# NB: while/whilst, toward/towards, among/amongst are deliberately omitted.
# They are lexical/register choices, not spelling variants -- and "while",
# "toward", "among" are all valid British English. Converting them would be
# intrusive and out of scope for a spelling pass.
}
for uk, us in _MISC.items():
add(uk, us)
# Reverse map for fixing American spellings inside en-GB.
us_to_uk: dict[str, str] = {}
for uk, us in uk_to_us.items():
# Don't overwrite a genuine British form that happens to equal another US form.
us_to_uk.setdefault(us, uk)
# --------------------------------------------------------------------------- #
# Case-preserving whole-word replacement.
# --------------------------------------------------------------------------- #
def _match_case(template: str, replacement: str) -> str:
if template.isupper():
return replacement.upper()
if template[:1].isupper():
return replacement[:1].upper() + replacement[1:]
return replacement
# Literal technical tokens that must never be respelled, even though they look
# like convertible words. These are protocol/identifier strings, not prose.
PROTECTED = re.compile(
r"Authorization(?=\s*:)" # the HTTP "Authorization:" header
r"|Authorization\s+header",
re.IGNORECASE,
)
_SENTINEL = "\x00{}\x00"
def make_converter(mapping: dict[str, str]):
if not mapping:
return lambda text: (text, [])
# Longest-first so multi-word/longer forms win; \b ensures whole words.
pattern = re.compile(
r"\b(" + "|".join(re.escape(w) for w in sorted(mapping, key=len, reverse=True)) + r")\b",
re.IGNORECASE,
)
def convert(text: str) -> tuple[str, list[tuple[str, str]]]:
# Mask protected literals so they pass through untouched.
protected: list[str] = []
def mask(m: re.Match[str]) -> str:
protected.append(m.group(0))
return _SENTINEL.format(len(protected) - 1)
masked = PROTECTED.sub(mask, text)
changes: list[tuple[str, str]] = []
def repl(m: re.Match[str]) -> str:
src = m.group(0)
dst = _match_case(src, mapping[src.lower()])
if dst != src:
changes.append((src, dst))
return dst
converted = pattern.sub(repl, masked)
for i, original in enumerate(protected):
converted = converted.replace(_SENTINEL.format(i), original)
return converted, changes
return convert
uk_to_us_convert = make_converter(uk_to_us)
us_to_uk_convert = make_converter(us_to_uk)
# --------------------------------------------------------------------------- #
# TOML line model (value-only edits; structure preserved verbatim).
# --------------------------------------------------------------------------- #
SECTION_RE = re.compile(r"^\[(.+)\]$")
KV_RE = re.compile(r'^([A-Za-z0-9_.-]+)\s*=\s*"(.*)"$')
def fq(section: str, key: str) -> str:
return f"{section}.{key}" if section else key
def parse_keys(path: Path) -> tuple[dict[str, str], list[tuple[str, str, str]]]:
"""Return {fq_key: value} and an ordered list of (section, key, value)."""
keys: dict[str, str] = {}
ordered: list[tuple[str, str, str]] = []
section = ""
for raw in path.read_text(encoding="utf-8").splitlines():
s = raw.strip()
if not s or s.startswith("#"):
continue
sec = SECTION_RE.match(s)
if sec:
section = sec.group(1)
continue
kv = KV_RE.match(s)
if kv:
k, v = kv.group(1), kv.group(2)
keys[fq(section, k)] = v
ordered.append((section, k, v))
return keys, ordered
def fix_en_gb(dry_run: bool) -> int:
"""Rewrite American spellings in en-GB values to British. Returns # changed."""
out_lines: list[str] = []
section = ""
changed = 0
report: list[str] = []
for raw in EN_GB.read_text(encoding="utf-8").splitlines():
s = raw.strip()
sec = SECTION_RE.match(s)
if sec:
section = sec.group(1)
out_lines.append(raw)
continue
kv = KV_RE.match(s)
if not kv:
out_lines.append(raw)
continue
key, value = kv.group(1), kv.group(2)
new_value, edits = us_to_uk_convert(value)
if edits:
changed += 1
for src, dst in edits:
report.append(f" [en-GB] {fq(section, key)}: {src} -> {dst}")
indent = raw[: len(raw) - len(raw.lstrip())]
out_lines.append(f'{indent}{key} = "{new_value}"')
else:
out_lines.append(raw)
if report:
print(f"en-GB: {changed} value(s) Americanised -> British:")
print("\n".join(report))
else:
print("en-GB: no American spellings found in values.")
if not dry_run and changed:
EN_GB.write_text("\n".join(out_lines) + "\n", encoding="utf-8")
return changed
def parse_structured(path: Path) -> tuple[list[tuple[str, str]], list[str], dict[str, list[tuple[str, str]]]]:
"""Return (top_level_kvs, section_order, {section: kvs}) preserving file order."""
top: list[tuple[str, str]] = []
order: list[str] = []
sections: dict[str, list[tuple[str, str]]] = {}
section = ""
for raw in path.read_text(encoding="utf-8").splitlines():
s = raw.strip()
if not s or s.startswith("#"):
continue
sec = SECTION_RE.match(s)
if sec:
section = sec.group(1)
if section not in sections:
order.append(section)
sections[section] = []
continue
kv = KV_RE.match(s)
if kv:
(top if section == "" else sections[section]).append((kv.group(1), kv.group(2)))
return top, order, sections
def _insert_ci(items: list, new, key_lower):
"""Insert `new` into `items` at the case-insensitive sorted position."""
nk = key_lower(new)
for i, existing in enumerate(items):
if key_lower(existing) > nk:
items.insert(i, new)
return
items.append(new)
def sync_en_us(dry_run: bool) -> int:
"""Regenerate en-US to mirror en-GB's structure, British->American.
en-GB is the source of truth for which keys exist and in what order. For
keys present in both, en-US keeps its own (US) wording; en-GB-only keys are
added in their en-GB position; en-US-only keys/sections are slotted into the
correct case-insensitive sorted position so the file stays ordered. Every
value is run through the British->American converter.
"""
us_keys, _ = parse_keys(EN_US)
gb_keys, _ = parse_keys(EN_GB)
gb_top, gb_order, gb_sections = parse_structured(EN_GB)
us_top, us_order, us_sections = parse_structured(EN_US)
added = [k for k in gb_keys if k not in us_keys]
us_only = [k for k in us_keys if k not in gb_keys]
def pick(section: str, key: str, gb_value: str) -> str:
"""Prefer en-US's own wording for shared keys; convert UK->US either way."""
value = us_keys.get(fq(section, key), gb_value)
return uk_to_us_convert(value)[0]
# --- top-level keys: mirror en-GB, slot en-US-only keys in sorted order ---
out_top: list[tuple[str, str]] = [(k, pick("", k, v)) for k, v in gb_top]
gb_top_keys = {k for k, _ in gb_top}
for k, v in us_top:
if k not in gb_top_keys:
_insert_ci(out_top, (k, uk_to_us_convert(v)[0]), lambda kv: kv[0].lower())
# --- sections: mirror en-GB order/keys, merge en-US-only keys & sections ---
out_sections: list[tuple[str, list[tuple[str, str]]]] = []
for name in gb_order:
gb_kvs = gb_sections[name]
gb_section_keys = {k for k, _ in gb_kvs}
merged = [(k, pick(name, k, v)) for k, v in gb_kvs]
# en-US-only keys that belong to this (shared) section
for k, v in us_sections.get(name, []):
if k not in gb_section_keys:
_insert_ci(merged, (k, uk_to_us_convert(v)[0]), lambda kv: kv[0].lower())
out_sections.append((name, merged))
# en-US-only sections (absent from en-GB): insert by ci header order
gb_section_names = set(gb_order)
for name in us_order:
if name not in gb_section_names:
kvs = [(k, uk_to_us_convert(v)[0]) for k, v in us_sections[name]]
_insert_ci(out_sections, (name, kvs), lambda s: s[0].lower())
# --- emit (top-level block, then one blank line before each section) ---
lines: list[str] = [f'{k} = "{v}"' for k, v in out_top]
for name, kvs in out_sections:
lines.append("")
lines.append(f"[{name}]")
lines.extend(f'{k} = "{v}"' for k, v in kvs)
print(f"en-US: +{len(added)} key(s) from en-GB, "
f"{len(us_only)} en-US-only key(s) preserved (British->American applied).")
for k in added:
print(f" [en-US] + {k}")
if not dry_run:
EN_US.write_text("\n".join(lines) + "\n", encoding="utf-8")
return len(added)
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--dry-run", action="store_true", help="report changes without writing")
args = ap.parse_args()
if not EN_US.exists() or not EN_GB.exists():
print(f"error: expected files under {LOCALES}", file=sys.stderr)
return 1
print(f"Spelling map: {len(uk_to_us)} British->American word forms.\n")
added = sync_en_us(args.dry_run)
print()
fixed = fix_en_gb(args.dry_run)
print()
mode = "DRY RUN (no files written)" if args.dry_run else "written"
print(f"Done [{mode}]: +{added} en-US key(s), {fixed} en-GB value(s) corrected.")
return 0
if __name__ == "__main__":
raise SystemExit(main())