Fix SAML login "something went wrong" when language list = 1 (#5750)

# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
This commit is contained in:
Anthony Stirling
2026-02-18 08:49:08 +00:00
committed by GitHub
parent 757a666f5e
commit 8d5b3eb36b
13 changed files with 89 additions and 51 deletions

View File

@@ -67,7 +67,7 @@ springBoot {
allprojects {
group = 'stirling.software'
version = '2.5.0'
version = '2.5.1'
configurations.configureEach {
exclude group: 'commons-logging', module: 'commons-logging'

View File

@@ -1,7 +1,7 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Stirling-PDF",
"version": "2.5.0",
"version": "2.5.1",
"identifier": "stirling.pdf.dev",
"build": {
"frontendDist": "../dist",

View File

@@ -52,12 +52,15 @@ const LanguageItem: React.FC<LanguageItemProps> = ({
}) => {
const { t } = useTranslation();
const labelText = option.label;
const comingSoonText = t('comingSoon', 'Coming soon');
const label = disabled ? (
<Tooltip content={t('comingSoon', 'Coming soon')} position="left" arrow>
<p>{option.label}</p>
<Tooltip content={comingSoonText} position="left" arrow>
<p>{labelText}</p>
</Tooltip>
) : (
<p>{option.label}</p>
<p>{labelText}</p>
);
return (
@@ -157,12 +160,27 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({
compact = false,
tooltip
}) => {
const { i18n } = useTranslation();
const { i18n, ready } = useTranslation();
const [opened, setOpened] = useState(false);
const [animationTriggered, setAnimationTriggered] = useState(false);
const [pendingLanguage, setPendingLanguage] = useState<string | null>(null);
const [rippleEffect, setRippleEffect] = useState<RippleEffect | null>(null);
// Trigger animation when dropdown opens
useEffect(() => {
if (opened) {
setAnimationTriggered(false);
// Small delay to ensure DOM is ready
setTimeout(() => setAnimationTriggered(true), 20);
}
}, [opened]);
// Don't render until i18n is ready to prevent race condition
// during SAML auth where components render before i18n initializes
if (!ready || !i18n.language) {
return null;
}
// Get the filtered list of supported languages from i18n
// This respects server config (ui.languages) applied by AppConfigLoader
const allowedLanguages = (i18n.options.supportedLngs as string[] || [])
@@ -176,12 +194,6 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({
label: name,
}));
// Hide the language selector if there's only one language option
// (no point showing a selector when there's nothing to select)
if (languageOptions.length <= 1) {
return null;
}
// Calculate dropdown width and grid columns based on number of languages
// 2-4: 300px/2 cols, 5-9: 400px/3 cols, 10+: 600px/4 cols
const dropdownWidth = languageOptions.length <= 4 ? 300
@@ -225,16 +237,14 @@ const LanguageSelector: React.FC<LanguageSelectorProps> = ({
};
const currentLanguage = supportedLanguages[i18n.language as keyof typeof supportedLanguages] ||
supportedLanguages['en-GB'];
supportedLanguages['en-GB'] ||
'English'; // Fallback if supportedLanguages lookup fails
// Trigger animation when dropdown opens
useEffect(() => {
if (opened) {
setAnimationTriggered(false);
// Small delay to ensure DOM is ready
setTimeout(() => setAnimationTriggered(true), 20);
}
}, [opened]);
// Hide the language selector if there's only one language option
// (no point showing a selector when there's nothing to select)
if (languageOptions.length <= 1) {
return null;
}
return (
<>

View File

@@ -24,9 +24,8 @@ const ToolChain: React.FC<ToolChainProps> = ({
size = 'xs',
color = 'var(--mantine-color-blue-7)'
}) => {
if (!toolChain || toolChain.length === 0) return null;
const { t } = useTranslation();
if (!toolChain || toolChain.length === 0) return null;
const toolIds = toolChain.map(tool => tool.toolId);

View File

@@ -74,6 +74,11 @@ const CompareDocumentPane = ({
}
}, [zoom]);
const renderedPageNumbers = useMemo(
() => new Set(pages.map((p) => p.pageNumber)),
[pages]
);
return (
<div className="compare-pane">
<div className="compare-header">
@@ -88,7 +93,7 @@ const CompareDocumentPane = ({
placeholder={dropdownPlaceholder ?? null}
className={pane === 'comparison' ? 'compare-changes-select--comparison' : undefined}
onNavigate={onNavigateChange}
renderedPageNumbers={useMemo(() => new Set(pages.map(p => p.pageNumber)), [pages])}
renderedPageNumbers={renderedPageNumbers}
/>
)}
</Group>

View File

@@ -43,6 +43,16 @@ interface EditTableOfContentsWorkbenchViewProps {
const EditTableOfContentsWorkbenchView = ({ data }: EditTableOfContentsWorkbenchViewProps) => {
const { t } = useTranslation();
const terminology = useFileActionTerminology();
const files = data?.files ?? [];
const thumbnails = data?.thumbnails ?? [];
const previewFiles = useMemo(
() =>
files.map((file, index) => ({
file,
thumbnail: thumbnails[index],
})),
[files, thumbnails]
);
if (!data) {
return (
@@ -63,8 +73,6 @@ const EditTableOfContentsWorkbenchView = ({ data }: EditTableOfContentsWorkbench
bookmarks,
selectedFileName,
disabled,
files,
thumbnails,
downloadUrl,
downloadFilename,
errorMessage,
@@ -78,15 +86,6 @@ const EditTableOfContentsWorkbenchView = ({ data }: EditTableOfContentsWorkbench
onFileClick,
} = data;
const previewFiles = useMemo(
() =>
files?.map((file, index) => ({
file,
thumbnail: thumbnails[index],
})) ?? [],
[files, thumbnails]
);
const showResults = Boolean(
previewFiles.length > 0 || downloadUrl || errorMessage
);

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import FileStatusIndicator from '@app/components/tools/shared/FileStatusIndicator';
import { StirlingFile } from '@app/types/fileContext';
import i18n from '@app/i18n';
export interface FilesToolStepProps {
selectedFiles: StirlingFile[];
@@ -14,9 +14,7 @@ export function createFilesToolStep(
createStep: (title: string, props: any, children?: React.ReactNode) => React.ReactElement,
props: FilesToolStepProps
): React.ReactElement {
const { t } = useTranslation();
return createStep(t("files.title", "Files"), {
return createStep(i18n.t("files.title", "Files"), {
isVisible: true,
isCollapsed: props.isCollapsed,
onCollapsedClick: props.onCollapsedClick

View File

@@ -12,6 +12,7 @@ import { useFileActionIcons } from "@app/hooks/useFileActionIcons";
import { saveOperationResults } from "@app/services/operationResultsSaveService";
import { useFileActions, useFileState } from "@app/contexts/FileContext";
import { FileId } from "@app/types/fileContext";
import i18n from "@app/i18n";
export interface ReviewToolStepProps<TParams = unknown> {
isVisible: boolean;
@@ -151,10 +152,8 @@ export function createReviewToolStep<TParams = unknown>(
) => React.ReactElement,
props: ReviewToolStepProps<TParams>
): React.ReactElement {
const { t } = useTranslation();
return createStep(
t("review", "Review"),
i18n.t("review", "Review"),
{
isVisible: props.isVisible,
isCollapsed: props.isCollapsed,

View File

@@ -80,8 +80,6 @@ const ToolStep = ({
alwaysShowTooltip = false,
tooltip
}: ToolStepProps) => {
if (!isVisible) return null;
const parent = useContext(ToolStepContext);
// Auto-detect if we should show numbers based on sibling count or force option
@@ -91,6 +89,8 @@ const ToolStep = ({
return parent ? parent.visibleStepCount >= 3 : false; // Auto-detect
}, [showNumber, parent]);
if (!isVisible) return null;
const stepNumber = _stepNumber;
return (

View File

@@ -38,7 +38,7 @@ const FREE_LICENSE_INFO: LicenseInfo = {
const BASE_NO_LOGIN_CONFIG: AppConfig = {
enableAnalytics: true,
appVersion: '2.5.0',
appVersion: '2.5.1',
serverCertificateEnabled: false,
enableAlphaFunctionality: false,
serverPort: 8080,

View File

@@ -48,7 +48,7 @@ const FREE_LICENSE_INFO: LicenseInfo = {
const BASE_NO_LOGIN_CONFIG: AppConfig = {
enableAnalytics: true,
appVersion: '2.5.0',
appVersion: '2.5.1',
serverCertificateEnabled: false,
enableAlphaFunctionality: false,
enableDesktopInstallSlide: true,

View File

@@ -74,7 +74,7 @@ services:
DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "true"
SECURITY_LOGINMETHOD: "${SECURITY_LOGINMETHOD:-all}"
SYSTEM_DEFAULTLOCALE: en-US
SYSTEM_DEFAULTLOCALE: "${SYSTEM_DEFAULTLOCALE:-en-US}"
SYSTEM_BACKENDURL: "http://localhost:8080"
# Enterprise License (required for SAML)

View File

@@ -13,24 +13,48 @@ echo -e "${BLUE}╚════════════════════
echo ""
AUTO_LOGIN=false
DEFAULT_LANGUAGE="en-US"
COMPOSE_UP_ARGS=(-d --build)
for arg in "$@"; do
case "$arg" in
while [[ $# -gt 0 ]]; do
case "$1" in
--auto)
AUTO_LOGIN=true
shift
;;
--nobuild)
COMPOSE_UP_ARGS=(-d)
shift
;;
--language)
if [[ -z "${2:-}" ]]; then
echo -e "${RED}Missing value for --language${NC}"
exit 1
fi
DEFAULT_LANGUAGE="$2"
shift 2
;;
--language=*)
DEFAULT_LANGUAGE="${1#*=}"
shift
;;
-l)
if [[ -z "${2:-}" ]]; then
echo -e "${RED}Missing value for -l${NC}"
exit 1
fi
DEFAULT_LANGUAGE="$2"
shift 2
;;
-h|--help)
echo "Usage: $0 [--auto] [--nobuild]"
echo "Usage: $0 [--auto] [--nobuild] [--language <locale>]"
echo ""
echo " --auto Enable SSO auto-login and force SAML-only login method"
echo " --nobuild Skip building images (use existing images)"
echo " --language Set system default locale (e.g. de-DE, sv-SE)"
exit 0
;;
*)
echo -e "${RED}Unknown option: $arg${NC}"
echo -e "${RED}Unknown option: $1${NC}"
exit 1
;;
esac
@@ -65,6 +89,10 @@ if [ "$AUTO_LOGIN" = true ]; then
echo ""
fi
export SYSTEM_DEFAULTLOCALE="$DEFAULT_LANGUAGE"
echo -e "${GREEN}✓ Default locale set to: ${SYSTEM_DEFAULTLOCALE}${NC}"
echo ""
echo -e "${YELLOW}▶ Starting Keycloak (SAML) containers...${NC}"
docker-compose -f docker-compose-keycloak-saml.yml up "${COMPOSE_UP_ARGS[@]}" keycloak-saml-db keycloak-saml