From 9fd8fd89ed26de1147e4ed21a585390658b0cfe4 Mon Sep 17 00:00:00 2001 From: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:18:23 +0000 Subject: [PATCH 1/4] add enum SERVER to list of valid licenses (#5172) --- frontend/src/core/hooks/useServerExperience.ts | 2 +- frontend/src/proprietary/contexts/ServerExperienceContext.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/core/hooks/useServerExperience.ts b/frontend/src/core/hooks/useServerExperience.ts index 8dd60069a7..28f62c1c57 100644 --- a/frontend/src/core/hooks/useServerExperience.ts +++ b/frontend/src/core/hooks/useServerExperience.ts @@ -65,7 +65,7 @@ export function useServerExperience(): ServerExperienceValue { const loginEnabled = config?.enableLogin !== false; const configIsAdmin = Boolean(config?.isAdmin); const effectiveIsAdmin = configIsAdmin || (!loginEnabled && selfReportedAdmin); - const hasPaidLicense = config?.license === 'PRO' || config?.license === 'ENTERPRISE'; + const hasPaidLicense = config?.license === 'SERVER' || config?.license === 'PRO' || config?.license === 'ENTERPRISE'; const setSelfReportedAdmin = useCallback((value: boolean) => { setSelfReportedAdminState(value); diff --git a/frontend/src/proprietary/contexts/ServerExperienceContext.tsx b/frontend/src/proprietary/contexts/ServerExperienceContext.tsx index 17ac572fc3..92ce8148a4 100644 --- a/frontend/src/proprietary/contexts/ServerExperienceContext.tsx +++ b/frontend/src/proprietary/contexts/ServerExperienceContext.tsx @@ -249,7 +249,7 @@ export function ServerExperienceProvider({ children }: { children: ReactNode }) }, [fetchUserCounts]); const hasPaidLicense = useMemo(() => { - return config?.license === 'PRO' || config?.license === 'ENTERPRISE'; + return config?.license === 'SERVER' || config?.license === 'PRO' || config?.license === 'ENTERPRISE'; }, [config?.license]); const licenseKeyValid = useMemo(() => { From 82dbcfbb9b78a01dee5a2754391a5444172b8875 Mon Sep 17 00:00:00 2001 From: Dario Ghunney Ware Date: Fri, 5 Dec 2025 23:19:41 +0000 Subject: [PATCH 2/4] SSO login fix (#5167) Fixes bug where SSO login with custom providers caused an `InvalidClientRegistrationIdException: Invalid Client Registration with Id: oidc` errors. Root Cause: - Backend: Redirect URI was hardcoded to `/login/oauth2/code/oidc` regardless of provider registration ID - Frontend: Unknown providers were mapped back to 'oidc' instead of using actual provider ID Closes #5141 --------- Co-authored-by: Anthony Stirling <77850077+frooodle@users.noreply.github.com> Co-authored-by: Keon Chen <66115421+keonchennl@users.noreply.github.com> --- ...tomOAuth2AuthenticationSuccessHandler.java | 8 + .../security/oauth2/OAuth2Configuration.java | 24 +- ...stomSaml2AuthenticationSuccessHandler.java | 6 + .../service/UserLicenseSettingsService.java | 71 ++++- .../oauth2/OAuth2ConfigurationTest.java | 162 ++++++++++ .../UserLicenseSettingsServiceTest.java | 218 +++++++++++++ frontend/src/proprietary/auth/oauthTypes.ts | 24 ++ .../src/proprietary/auth/springAuthClient.ts | 8 +- .../src/proprietary/routes/Login.test.tsx | 178 +++++++++-- frontend/src/proprietary/routes/Login.tsx | 21 +- .../routes/login/OAuthButtons.test.tsx | 291 ++++++++++++++++++ .../proprietary/routes/login/OAuthButtons.tsx | 5 +- 12 files changed, 961 insertions(+), 55 deletions(-) create mode 100644 app/proprietary/src/test/java/stirling/software/proprietary/security/oauth2/OAuth2ConfigurationTest.java create mode 100644 frontend/src/proprietary/auth/oauthTypes.ts create mode 100644 frontend/src/proprietary/routes/login/OAuthButtons.test.tsx diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java index e1e6703945..793c6b62fa 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java @@ -27,6 +27,7 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.exception.UnsupportedProviderException; @@ -39,6 +40,7 @@ import stirling.software.proprietary.security.service.JwtServiceInterface; import stirling.software.proprietary.security.service.LoginAttemptService; import stirling.software.proprietary.security.service.UserService; +@Slf4j @RequiredArgsConstructor public class CustomOAuth2AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { @@ -77,12 +79,18 @@ public class CustomOAuth2AuthenticationSuccessHandler if (user != null && !licenseSettingsService.isOAuthEligible(user)) { // User is not grandfathered and no paid license - block OAuth login + log.warn( + "OAuth login blocked for existing user '{}' - not eligible (not grandfathered and no paid license)", + username); response.sendRedirect( request.getContextPath() + "/logout?oAuth2RequiresLicense=true"); return; } } else if (!licenseSettingsService.isOAuthEligible(null)) { // No existing user and no paid license -> block auto creation + log.warn( + "OAuth login blocked for new user '{}' - not eligible (no paid license for auto-creation)", + username); response.sendRedirect(request.getContextPath() + "/logout?oAuth2RequiresLicense=true"); return; } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/OAuth2Configuration.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/OAuth2Configuration.java index a053c1ead2..2d5f94620a 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/OAuth2Configuration.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/OAuth2Configuration.java @@ -67,10 +67,15 @@ public class OAuth2Configuration { keycloakClientRegistration().ifPresent(registrations::add); if (registrations.isEmpty()) { - log.error("No OAuth2 provider registered"); + log.error("No OAuth2 provider registered - check your OAuth2 configuration"); throw new NoProviderFoundException("At least one OAuth2 provider must be configured."); } + log.info( + "OAuth2 ClientRegistrationRepository created with {} provider(s): {}", + registrations.size(), + registrations.stream().map(ClientRegistration::getRegistrationId).toList()); + return new InMemoryClientRegistrationRepository(registrations); } @@ -165,7 +170,6 @@ public class OAuth2Configuration { githubClient.getUseAsUsername()); boolean isValid = validateProvider(github); - log.info("Initialised GitHub OAuth2 provider"); return isValid ? Optional.of( @@ -208,7 +212,19 @@ public class OAuth2Configuration { null, null); - return !isStringEmpty(oidcProvider.getIssuer()) || validateProvider(oidcProvider) + boolean isValid = + !isStringEmpty(oidcProvider.getIssuer()) || validateProvider(oidcProvider); + if (isValid) { + log.info( + "Initialised OIDC OAuth2 provider: registrationId='{}', issuer='{}', redirectUri='{}'", + name, + oauth.getIssuer(), + REDIRECT_URI_PATH + name); + } else { + log.warn("OIDC OAuth2 provider validation failed - provider will not be registered"); + } + + return isValid ? Optional.of( ClientRegistrations.fromIssuerLocation(oauth.getIssuer()) .registrationId(name) @@ -217,7 +233,7 @@ public class OAuth2Configuration { .scope(oidcProvider.getScopes()) .userNameAttributeName(oidcProvider.getUseAsUsername().getName()) .clientName(clientName) - .redirectUri(REDIRECT_URI_PATH + "oidc") + .redirectUri(REDIRECT_URI_PATH + name) .authorizationGrantType(AUTHORIZATION_CODE) .build()) : Optional.empty(); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticationSuccessHandler.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticationSuccessHandler.java index b342fdcb46..e8bce579a0 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticationSuccessHandler.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticationSuccessHandler.java @@ -74,12 +74,18 @@ public class CustomSaml2AuthenticationSuccessHandler if (user != null && !licenseSettingsService.isSamlEligible(user)) { // User is not grandfathered and no ENTERPRISE license - block SAML login + log.warn( + "SAML2 login blocked for existing user '{}' - not eligible (not grandfathered and no ENTERPRISE license)", + username); response.sendRedirect( request.getContextPath() + "/logout?saml2RequiresLicense=true"); return; } } else if (!licenseSettingsService.isSamlEligible(null)) { // No existing user and no ENTERPRISE license -> block auto creation + log.warn( + "SAML2 login blocked for new user '{}' - not eligible (no ENTERPRISE license for auto-creation)", + username); response.sendRedirect( request.getContextPath() + "/logout?saml2RequiresLicense=true"); return; diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/UserLicenseSettingsService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/UserLicenseSettingsService.java index d3bade89c0..aa794e6997 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/UserLicenseSettingsService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/UserLicenseSettingsService.java @@ -21,6 +21,7 @@ import stirling.software.common.model.ApplicationProperties; import stirling.software.proprietary.model.UserLicenseSettings; import stirling.software.proprietary.security.configuration.ee.KeygenLicenseVerifier.License; import stirling.software.proprietary.security.configuration.ee.LicenseKeyChecker; +import stirling.software.proprietary.security.model.User; import stirling.software.proprietary.security.repository.UserLicenseSettingsRepository; import stirling.software.proprietary.security.service.UserService; @@ -331,28 +332,45 @@ public class UserLicenseSettingsService { } /** - * Checks if a user is eligible to use OAuth authentication. + * Checks if a user is eligible to use OAuth/SAML authentication. * *

A user is eligible if: * *

* * @param user The user to check - * @return true if the user can use OAuth + * @return true if the user can use OAuth/SAML */ - public boolean isOAuthEligible(stirling.software.proprietary.security.model.User user) { + public boolean isOAuthEligible(User user) { + String username = (user != null) ? user.getUsername() : ""; + log.info("OAuth eligibility check for user: {}", username); + // Grandfathered users always have OAuth access if (user != null && user.isOauthGrandfathered()) { log.debug("User {} is grandfathered for OAuth", user.getUsername()); return true; } + // todo: remove + if (user != null) { + log.info( + "User {} is NOT grandfathered (isOauthGrandfathered={})", + username, + user.isOauthGrandfathered()); + } else { + log.info("New user attempting OAuth login - checking license requirement"); + } + // Users can use OAuth with SERVER or ENTERPRISE license boolean hasPaid = hasPaidLicense(); - log.debug("OAuth eligibility check: hasPaidLicense={}", hasPaid); + log.info( + "OAuth eligibility result: hasPaidLicense={}, user={}, eligible={}", + hasPaid, + username, + hasPaid); return hasPaid; } @@ -369,16 +387,32 @@ public class UserLicenseSettingsService { * @param user The user to check * @return true if the user can use SAML */ - public boolean isSamlEligible(stirling.software.proprietary.security.model.User user) { + public boolean isSamlEligible(User user) { + String username = (user != null) ? user.getUsername() : ""; + log.info("SAML2 eligibility check for user: {}", username); + // Grandfathered users always have SAML access if (user != null && user.isOauthGrandfathered()) { - log.debug("User {} is grandfathered for SAML", user.getUsername()); + log.info("User {} is grandfathered for SAML2 - ELIGIBLE", username); return true; } + if (user != null) { + log.info( + "User {} is NOT grandfathered (isOauthGrandfathered={})", + username, + user.isOauthGrandfathered()); + } else { + log.info("New user attempting SAML2 login - checking license requirement"); + } + // Users can use SAML only with ENTERPRISE license boolean hasEnterprise = hasEnterpriseLicense(); - log.debug("SAML eligibility check: hasEnterpriseLicense={}", hasEnterprise); + log.info( + "SAML2 eligibility result: hasEnterpriseLicense={}, user={}, eligible={}", + hasEnterprise, + username, + hasEnterprise); return hasEnterprise; } @@ -521,12 +555,17 @@ public class UserLicenseSettingsService { if (checker == null) { return false; } + License license = checker.getPremiumLicenseEnabledResult(); - return license == License.SERVER || license == License.ENTERPRISE; + boolean hasPaid = (license == License.SERVER || license == License.ENTERPRISE); + log.info("License check result: type={}, requiresPaid=true, hasPaid={}", license, hasPaid); + + return hasPaid; } /** - * Checks if the system has an ENTERPRISE license. Used for enterprise-only features like SAML. + * Checks if the system has an ENTERPRISE license. Used for enterprise-only features like SSO + * (OAuth/SAML). * * @return true if ENTERPRISE license is active */ @@ -535,7 +574,19 @@ public class UserLicenseSettingsService { if (checker == null) { return false; } + License license = checker.getPremiumLicenseEnabledResult(); + log.info( + "License check result: type={}, requiresEnterprise=true, hasEnterprise={}", + license, + (license == License.ENTERPRISE)); + + if (license != License.ENTERPRISE) { + log.warn( + "SAML2 requires ENTERPRISE license but found: {}. SAML2 login will be blocked.", + license); + } + return license == License.ENTERPRISE; } } diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/security/oauth2/OAuth2ConfigurationTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/security/oauth2/OAuth2ConfigurationTest.java new file mode 100644 index 0000000000..750696b770 --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/security/oauth2/OAuth2ConfigurationTest.java @@ -0,0 +1,162 @@ +package stirling.software.proprietary.security.oauth2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for OAuth2Configuration redirect URI logic. + * + *

These tests validate the critical fix for GitHub issue #5141: The redirect URI path segment + * MUST match the registration ID. Previously, the redirect URI was hardcoded to 'oidc', causing + * InvalidClientRegistrationIdException when custom provider names were used. + * + *

Note: These are conceptual tests documenting the expected behavior. Full integration testing + * with actual OIDC discovery would require: 1. Mock HTTP server for OIDC discovery endpoints 2. + * Valid OIDC configuration responses 3. Network mocking infrastructure + */ +class OAuth2ConfigurationTest { + + /** + * Tests the redirect URI pattern for OIDC provider configurations. + * + *

Critical behavior (GitHub issue #5141 fix): The redirect URI path segment MUST match the + * registration ID. For example: - Provider name: "authentik" → Redirect URI: + * "/login/oauth2/code/authentik" - Provider name: "mycompany" → Redirect URI: + * "/login/oauth2/code/mycompany" - Provider name: "oidc" → Redirect URI: + * "/login/oauth2/code/oidc" + * + *

Previously, the redirect URI was hardcoded to 'oidc', causing Spring Security to look for + * a registration with ID 'oidc' when the provider redirected back. This caused + * InvalidClientRegistrationIdException when custom provider names were used. + */ + @Test + void testRedirectUriPattern_usesProviderNameNotHardcodedOidc() { + // Verify the redirect URI pattern constant + String redirectUriBase = "{baseUrl}/login/oauth2/code/"; + + // Test cases: provider name → expected redirect URI + String[][] testCases = { + {"authentik", redirectUriBase + "authentik"}, + {"mycompany", redirectUriBase + "mycompany"}, + {"oidc", redirectUriBase + "oidc"}, + {"okta", redirectUriBase + "okta"}, + {"auth0", redirectUriBase + "auth0"} + }; + + for (String[] testCase : testCases) { + String providerName = testCase[0]; + String expectedRedirectUri = testCase[1]; + + // The fix ensures: .redirectUri(REDIRECT_URI_PATH + name) + // instead of: .redirectUri(REDIRECT_URI_PATH + "oidc") + String actualRedirectUri = redirectUriBase + providerName; + + assertEquals( + expectedRedirectUri, + actualRedirectUri, + String.format( + "Redirect URI for provider '%s' must use provider name, not hardcoded 'oidc'", + providerName)); + } + } + + /** + * Documents the critical fix for OAuth2 redirect URI mismatch. + * + *

This test validates the logic that was changed in OAuth2Configuration.java line 220: + * + *

+     * // BEFORE (bug):
+     * .redirectUri(REDIRECT_URI_PATH + "oidc")  // Always "oidc"
+     *
+     * // AFTER (fix):
+     * .redirectUri(REDIRECT_URI_PATH + name)  // Dynamic provider name
+     * 
+ */ + @Test + void testCriticalFix_redirectUriMatchesRegistrationId() { + // The redirect URI path segment extraction by Spring Security + String callbackUrl = "http://localhost:8080/login/oauth2/code/authentik?code=abc123"; + + // Spring extracts the path segment between "code/" and "?" + String extractedRegistrationId = extractRegistrationIdFromCallback(callbackUrl); + + // The extracted ID MUST match an actual registration ID + assertEquals("authentik", extractedRegistrationId); + + // If we had used hardcoded "oidc", the callback would be: + String buggyCallbackUrl = "http://localhost:8080/login/oauth2/code/oidc?code=abc123"; + String buggyExtractedId = extractRegistrationIdFromCallback(buggyCallbackUrl); + + // This would look for registration with ID "oidc" but we registered "authentik" + assertEquals("oidc", buggyExtractedId); + + // The mismatch: registrationId="authentik", but Spring looks for "oidc" + // Result: InvalidClientRegistrationIdException + assertNotNull(buggyExtractedId, "This demonstrates the bug that was fixed"); + } + + /** Helper method simulating Spring's extraction of registration ID from callback URL */ + private String extractRegistrationIdFromCallback(String callbackUrl) { + // Simplified version of what Spring Security does + // Actual: OAuth2AuthorizationRequestRedirectFilter extracts from path + String path = callbackUrl.split("\\?")[0]; + String[] parts = path.split("/"); + return parts[parts.length - 1]; // Last path segment + } + + /** + * Validates the frontend-backend flow for custom provider names. + * + *

Complete flow: 1. Backend: Provider configured as "authentik" in settings.yml 2. Backend: + * ClientRegistration created with registrationId="authentik" 3. Backend: Redirect URI set to + * "{baseUrl}/login/oauth2/code/authentik" 4. Backend: Login endpoint returns providerList with + * "/oauth2/authorization/authentik" 5. Frontend: Extracts "authentik" from path and uses it for + * OAuth login 6. Frontend: Redirects to "/oauth2/authorization/authentik" 7. Backend: Spring + * Security redirects to provider with redirect_uri containing "authentik" 8. Provider: + * Redirects back to "/login/oauth2/code/authentik?code=..." 9. Backend: Spring Security + * extracts "authentik" from callback URL 10. Backend: Looks up ClientRegistration with ID + * "authentik" ✅ SUCCESS + * + *

If redirect URI was hardcoded to "oidc" (the bug): Step 7: Provider redirects to + * "/login/oauth2/code/oidc?code=..." Step 9: Spring Security looks for registration ID "oidc" + * Step 10: FAIL - No registration found with ID "oidc" (we registered "authentik") Result: + * InvalidClientRegistrationIdException + */ + @Test + void testEndToEndFlow_registrationIdConsistency() { + String providerName = "authentik"; + + // Step 2: Registration ID + String registrationId = providerName; + assertEquals("authentik", registrationId); + + // Step 3: Redirect URI (MUST use same name) + String redirectUri = "{baseUrl}/login/oauth2/code/" + providerName; + assertEquals("{baseUrl}/login/oauth2/code/authentik", redirectUri); + + // Step 4: Provider list endpoint + String authorizationPath = "/oauth2/authorization/" + providerName; + assertEquals("/oauth2/authorization/authentik", authorizationPath); + + // Step 5: Frontend extracts provider ID + String frontendProviderId = + authorizationPath.substring(authorizationPath.lastIndexOf('/') + 1); + assertEquals("authentik", frontendProviderId); + + // Step 6-8: OAuth flow (external) + + // Step 9: Callback URL from provider + String callbackUrl = + "http://localhost:8080/login/oauth2/code/" + providerName + "?code=abc123"; + String extractedId = extractRegistrationIdFromCallback(callbackUrl); + + // Step 10: Registration lookup + assertEquals( + registrationId, + extractedId, + "Registration ID from callback MUST match original registration ID"); + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/service/UserLicenseSettingsServiceTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/service/UserLicenseSettingsServiceTest.java index 139146d707..7f9445ad7c 100644 --- a/app/proprietary/src/test/java/stirling/software/proprietary/service/UserLicenseSettingsServiceTest.java +++ b/app/proprietary/src/test/java/stirling/software/proprietary/service/UserLicenseSettingsServiceTest.java @@ -267,4 +267,222 @@ class UserLicenseSettingsServiceTest { verify(userService, times(1)).grandfatherAllOAuthUsers(); verify(userService, times(1)).grandfatherPendingSsoUsersWithoutSession(); } + + // ===== OAuth Eligibility Tests ===== + + @Test + void isOAuthEligible_grandfatheredUser_returnsTrue() { + // Grandfathered user should be eligible regardless of license + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("grandfathered-user"); + user.setOauthGrandfathered(true); + + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.NORMAL); + + boolean result = service.isOAuthEligible(user); + + assertEquals(true, result, "Grandfathered user should be eligible for OAuth"); + } + + @Test + void isOAuthEligible_nonGrandfatheredUserWithServerLicense_returnsTrue() { + // Non-grandfathered user with SERVER license should be eligible + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("test-user"); + user.setOauthGrandfathered(false); + + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.SERVER); + + boolean result = service.isOAuthEligible(user); + + assertEquals(true, result, "Non-grandfathered user with SERVER license should be eligible"); + } + + @Test + void isOAuthEligible_nonGrandfatheredUserWithEnterpriseLicense_returnsTrue() { + // Non-grandfathered user with ENTERPRISE license should be eligible + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("test-user"); + user.setOauthGrandfathered(false); + + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.ENTERPRISE); + + boolean result = service.isOAuthEligible(user); + + assertEquals( + true, result, "Non-grandfathered user with ENTERPRISE license should be eligible"); + } + + @Test + void isOAuthEligible_nonGrandfatheredUserWithNoLicense_returnsFalse() { + // Non-grandfathered user without license should NOT be eligible + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("test-user"); + user.setOauthGrandfathered(false); + + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.NORMAL); + + boolean result = service.isOAuthEligible(user); + + assertEquals( + false, + result, + "Non-grandfathered user without paid license should NOT be eligible"); + } + + @Test + void isOAuthEligible_newUserWithServerLicense_returnsTrue() { + // New user (null) with SERVER license should be eligible for auto-creation + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.SERVER); + + boolean result = service.isOAuthEligible(null); + + assertEquals( + true, result, "New user with SERVER license should be eligible for auto-creation"); + } + + @Test + void isOAuthEligible_newUserWithNoLicense_returnsFalse() { + // New user (null) without license should NOT be eligible + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.NORMAL); + + boolean result = service.isOAuthEligible(null); + + assertEquals( + false, + result, + "New user without paid license should NOT be eligible for auto-creation"); + } + + @Test + void isOAuthEligible_licenseCheckerUnavailable_returnsFalse() { + // If LicenseKeyChecker is unavailable, OAuth should be blocked + when(licenseKeyCheckerProvider.getIfAvailable()).thenReturn(null); + + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("test-user"); + user.setOauthGrandfathered(false); + + boolean result = service.isOAuthEligible(user); + + assertEquals( + false, result, "OAuth should be blocked when LicenseKeyChecker is unavailable"); + } + + // ===== SAML Eligibility Tests ===== + + @Test + void isSamlEligible_grandfatheredUser_returnsTrue() { + // Grandfathered user should be eligible for SAML regardless of license + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("grandfathered-user"); + user.setOauthGrandfathered(true); + + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.NORMAL); + + boolean result = service.isSamlEligible(user); + + assertEquals(true, result, "Grandfathered user should be eligible for SAML"); + } + + @Test + void isSamlEligible_nonGrandfatheredUserWithEnterpriseLicense_returnsTrue() { + // Non-grandfathered user with ENTERPRISE license should be eligible + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("test-user"); + user.setOauthGrandfathered(false); + + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.ENTERPRISE); + + boolean result = service.isSamlEligible(user); + + assertEquals( + true, + result, + "Non-grandfathered user with ENTERPRISE license should be eligible for SAML"); + } + + @Test + void isSamlEligible_nonGrandfatheredUserWithServerLicense_returnsFalse() { + // Non-grandfathered user with SERVER license should NOT be eligible for SAML + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("test-user"); + user.setOauthGrandfathered(false); + + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.SERVER); + + boolean result = service.isSamlEligible(user); + + assertEquals( + false, + result, + "Non-grandfathered user with SERVER license should NOT be eligible for SAML"); + } + + @Test + void isSamlEligible_nonGrandfatheredUserWithNoLicense_returnsFalse() { + // Non-grandfathered user without license should NOT be eligible + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("test-user"); + user.setOauthGrandfathered(false); + + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.NORMAL); + + boolean result = service.isSamlEligible(user); + + assertEquals( + false, + result, + "Non-grandfathered user without ENTERPRISE license should NOT be eligible for SAML"); + } + + @Test + void isSamlEligible_newUserWithEnterpriseLicense_returnsTrue() { + // New user (null) with ENTERPRISE license should be eligible for auto-creation + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.ENTERPRISE); + + boolean result = service.isSamlEligible(null); + + assertEquals( + true, + result, + "New user with ENTERPRISE license should be eligible for SAML auto-creation"); + } + + @Test + void isSamlEligible_newUserWithServerLicense_returnsFalse() { + // New user (null) with SERVER license should NOT be eligible for SAML + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.SERVER); + + boolean result = service.isSamlEligible(null); + + assertEquals( + false, + result, + "New user with SERVER license should NOT be eligible for SAML (requires ENTERPRISE)"); + } + + @Test + void isSamlEligible_licenseCheckerUnavailable_returnsFalse() { + // If LicenseKeyChecker is unavailable, SAML should be blocked + when(licenseKeyCheckerProvider.getIfAvailable()).thenReturn(null); + + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("test-user"); + user.setOauthGrandfathered(false); + + boolean result = service.isSamlEligible(user); + + assertEquals(false, result, "SAML should be blocked when LicenseKeyChecker is unavailable"); + } } diff --git a/frontend/src/proprietary/auth/oauthTypes.ts b/frontend/src/proprietary/auth/oauthTypes.ts new file mode 100644 index 0000000000..2d38f1b3e5 --- /dev/null +++ b/frontend/src/proprietary/auth/oauthTypes.ts @@ -0,0 +1,24 @@ +/** + * Known OAuth providers with dedicated UI support. + * Custom providers are also supported - the backend determines availability. + */ +export const KNOWN_OAUTH_PROVIDERS = [ + 'github', + 'google', + 'apple', + 'azure', + 'keycloak', + 'cloudron', + 'authentik', + 'oidc', +] as const; + +export type KnownOAuthProvider = typeof KNOWN_OAUTH_PROVIDERS[number]; + +/** + * OAuth provider ID - can be any known provider or custom string. + * The backend configuration determines which providers are available. + * + * @example 'github' | 'google' | 'mycompany' | 'authentik' + */ +export type OAuthProvider = KnownOAuthProvider | (string & {}); diff --git a/frontend/src/proprietary/auth/springAuthClient.ts b/frontend/src/proprietary/auth/springAuthClient.ts index 2f1aa36cb5..646b711823 100644 --- a/frontend/src/proprietary/auth/springAuthClient.ts +++ b/frontend/src/proprietary/auth/springAuthClient.ts @@ -10,6 +10,7 @@ import apiClient from '@app/services/apiClient'; import { AxiosError } from 'axios'; import { BASE_PATH } from '@app/constants/app'; +import { type OAuthProvider } from '@app/auth/oauthTypes'; // Helper to extract error message from axios error function getErrorMessage(error: unknown, fallback: string): string { @@ -248,11 +249,14 @@ class SpringAuthClient { } /** - * Sign in with OAuth provider (GitHub, Google, etc.) + * Sign in with OAuth provider (GitHub, Google, Authentik, etc.) * This redirects to the Spring OAuth2 authorization endpoint + * + * @param params.provider - OAuth provider ID (e.g., 'github', 'google', 'authentik', 'mycompany') + * Can be any known provider or custom string - the backend determines available providers */ async signInWithOAuth(params: { - provider: 'github' | 'google' | 'apple' | 'azure' | 'keycloak' | 'oidc'; + provider: OAuthProvider; options?: { redirectTo?: string; queryParams?: Record }; }): Promise<{ error: AuthError | null }> { try { diff --git a/frontend/src/proprietary/routes/Login.test.tsx b/frontend/src/proprietary/routes/Login.test.tsx index 62679f22ac..996176c01c 100644 --- a/frontend/src/proprietary/routes/Login.test.tsx +++ b/frontend/src/proprietary/routes/Login.test.tsx @@ -7,6 +7,7 @@ import Login from '@app/routes/Login'; import { useAuth } from '@app/auth/UseSession'; import { springAuth } from '@app/auth/springAuthClient'; import { PreferencesProvider } from '@app/contexts/PreferencesContext'; +import apiClient from '@app/services/apiClient'; // Mock i18n to return fallback text vi.mock('react-i18next', () => ({ @@ -36,8 +37,13 @@ vi.mock('@app/hooks/useDocumentMeta', () => ({ useDocumentMeta: vi.fn(), })); -// Mock fetch for provider list -global.fetch = vi.fn(); +// Mock apiClient for provider list +vi.mock('@app/services/apiClient', () => ({ + default: { + get: vi.fn(), + post: vi.fn(), + }, +})); const mockNavigate = vi.fn(); const mockBackendProbeState = { @@ -89,14 +95,13 @@ describe('Login', () => { refreshSession: vi.fn(), }); - // Mock fetch for login UI data - vi.mocked(fetch).mockResolvedValue({ - ok: true, - json: async () => ({ + // Mock apiClient for login UI data + vi.mocked(apiClient.get).mockResolvedValue({ + data: { enableLogin: true, providerList: {}, - }), - } as Response); + }, + }); }); it('should render login form', async () => { @@ -239,6 +244,136 @@ describe('Login', () => { }); }); + it('should use actual provider ID for OAuth login (authentik)', async () => { + const user = userEvent.setup(); + + // Mock provider list with authentik + vi.mocked(apiClient.get).mockResolvedValue({ + data: { + enableLogin: true, + providerList: { + '/oauth2/authorization/authentik': 'Authentik', + }, + }, + }); + + vi.mocked(springAuth.signInWithOAuth).mockResolvedValueOnce({ + error: null, + }); + + render( + + + + + + ); + + // Wait for OAuth button to appear + await waitFor(() => { + const button = screen.queryByText('Authentik'); + expect(button).toBeTruthy(); + }, { timeout: 3000 }); + + const oauthButton = screen.getByText('Authentik'); + await user.click(oauthButton); + + await waitFor(() => { + // Should use 'authentik' directly, NOT map to 'oidc' + expect(springAuth.signInWithOAuth).toHaveBeenCalledWith({ + provider: 'authentik', + options: { redirectTo: '/auth/callback' } + }); + }); + }); + + it('should use actual provider ID for OAuth login (custom provider)', async () => { + const user = userEvent.setup(); + + // Mock provider list with custom provider 'mycompany' + vi.mocked(apiClient.get).mockResolvedValue({ + data: { + enableLogin: true, + providerList: { + '/oauth2/authorization/mycompany': 'My Company SSO', + }, + }, + }); + + vi.mocked(springAuth.signInWithOAuth).mockResolvedValueOnce({ + error: null, + }); + + render( + + + + + + ); + + // Wait for OAuth button to appear (will show 'Mycompany' as label) + await waitFor(() => { + const button = screen.queryByText('Mycompany'); + expect(button).toBeTruthy(); + }, { timeout: 3000 }); + + const oauthButton = screen.getByText('Mycompany'); + await user.click(oauthButton); + + await waitFor(() => { + // Should use 'mycompany' directly - this is the critical fix + // Previously it would map unknown providers to 'oidc' + expect(springAuth.signInWithOAuth).toHaveBeenCalledWith({ + provider: 'mycompany', + options: { redirectTo: '/auth/callback' } + }); + }); + }); + + it('should use oidc provider ID when explicitly configured', async () => { + const user = userEvent.setup(); + + // Mock provider list with 'oidc' + vi.mocked(apiClient.get).mockResolvedValue({ + data: { + enableLogin: true, + providerList: { + '/oauth2/authorization/oidc': 'OIDC', + }, + }, + }); + + vi.mocked(springAuth.signInWithOAuth).mockResolvedValueOnce({ + error: null, + }); + + render( + + + + + + ); + + // Wait for OAuth button to appear + await waitFor(() => { + const button = screen.queryByText('OIDC'); + expect(button).toBeTruthy(); + }, { timeout: 3000 }); + + const oauthButton = screen.getByText('OIDC'); + await user.click(oauthButton); + + await waitFor(() => { + // Should use 'oidc' when explicitly configured + expect(springAuth.signInWithOAuth).toHaveBeenCalledWith({ + provider: 'oidc', + options: { redirectTo: '/auth/callback' } + }); + }); + }); + it('should show error on failed login', async () => { const user = userEvent.setup(); const errorMessage = 'Invalid credentials'; @@ -359,13 +494,12 @@ describe('Login', () => { it('should redirect to home when login disabled', async () => { mockBackendProbeState.loginDisabled = true; mockProbe.mockResolvedValueOnce({ status: 'up', loginDisabled: true, loading: false }); - vi.mocked(fetch).mockResolvedValueOnce({ - ok: true, - json: async () => ({ + vi.mocked(apiClient.get).mockResolvedValueOnce({ + data: { enableLogin: false, providerList: {}, - }), - } as Response); + }, + }); render( @@ -381,15 +515,14 @@ describe('Login', () => { }); it('should handle OAuth provider click', async () => { - vi.mocked(fetch).mockResolvedValueOnce({ - ok: true, - json: async () => ({ + vi.mocked(apiClient.get).mockResolvedValueOnce({ + data: { enableLogin: true, providerList: { '/oauth2/authorization/github': 'GitHub', }, - }), - } as Response); + }, + }); vi.mocked(springAuth.signInWithOAuth).mockResolvedValueOnce({ error: null, @@ -416,13 +549,12 @@ describe('Login', () => { }); it('should show email form by default when no SSO providers', async () => { - vi.mocked(fetch).mockResolvedValueOnce({ - ok: true, - json: async () => ({ + vi.mocked(apiClient.get).mockResolvedValueOnce({ + data: { enableLogin: true, providerList: {}, // No providers - }), - } as Response); + }, + }); render( diff --git a/frontend/src/proprietary/routes/Login.tsx b/frontend/src/proprietary/routes/Login.tsx index 80a12e1d09..cf6004e505 100644 --- a/frontend/src/proprietary/routes/Login.tsx +++ b/frontend/src/proprietary/routes/Login.tsx @@ -10,6 +10,7 @@ import AuthLayout from '@app/routes/authShared/AuthLayout'; import { useBackendProbe } from '@app/hooks/useBackendProbe'; import apiClient from '@app/services/apiClient'; import { BASE_PATH } from '@app/constants/app'; +import { type OAuthProvider } from '@app/auth/oauthTypes'; // Import login components import LoginHeader from '@app/routes/login/LoginHeader'; @@ -31,7 +32,7 @@ export default function Login() { const [showEmailForm, setShowEmailForm] = useState(false); const [email, setEmail] = useState(() => searchParams.get('email') ?? ''); const [password, setPassword] = useState(''); - const [enabledProviders, setEnabledProviders] = useState([]); + const [enabledProviders, setEnabledProviders] = useState([]); const [hasSSOProviders, setHasSSOProviders] = useState(false); const [_enableLogin, setEnableLogin] = useState(null); const backendProbe = useBackendProbe(); @@ -226,25 +227,17 @@ export default function Login() { ); } - // Known OAuth providers that have dedicated backend support - const KNOWN_OAUTH_PROVIDERS = ['github', 'google', 'apple', 'azure', 'keycloak', 'oidc'] as const; - type KnownOAuthProvider = typeof KNOWN_OAUTH_PROVIDERS[number]; - - const signInWithProvider = async (provider: string) => { + const signInWithProvider = async (provider: OAuthProvider) => { try { setIsSigningIn(true); setError(null); - // Map unknown providers to 'oidc' for the backend redirect - const backendProvider: KnownOAuthProvider = KNOWN_OAUTH_PROVIDERS.includes(provider as KnownOAuthProvider) - ? (provider as KnownOAuthProvider) - : 'oidc'; + console.log(`[Login] Signing in with provider: ${provider}`); - console.log(`[Login] Signing in with ${provider} (backend: ${backendProvider})`); - - // Redirect to Spring OAuth2 endpoint + // Redirect to Spring OAuth2 endpoint using the actual provider ID from backend + // The backend returns the correct registration ID (e.g., 'authentik', 'oidc', 'keycloak') const { error } = await springAuth.signInWithOAuth({ - provider: backendProvider, + provider: provider, options: { redirectTo: `${BASE_PATH}/auth/callback` } }); diff --git a/frontend/src/proprietary/routes/login/OAuthButtons.test.tsx b/frontend/src/proprietary/routes/login/OAuthButtons.test.tsx new file mode 100644 index 0000000000..62f121d68c --- /dev/null +++ b/frontend/src/proprietary/routes/login/OAuthButtons.test.tsx @@ -0,0 +1,291 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { MantineProvider } from '@mantine/core'; +import OAuthButtons from '@app/routes/login/OAuthButtons'; + +// Mock i18n +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string, fallback?: string) => fallback || key, + }), +})); + +const TestWrapper = ({ children }: { children: React.ReactNode }) => ( + {children} +); + +describe('OAuthButtons', () => { + const mockOnProviderClick = vi.fn(); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should render known providers with correct labels', () => { + const enabledProviders = ['google', 'github', 'authentik']; + + render( + + + + ); + + // Check that known providers are rendered with their labels + expect(screen.getByText('Google')).toBeTruthy(); + expect(screen.getByText('GitHub')).toBeTruthy(); + expect(screen.getByText('Authentik')).toBeTruthy(); + }); + + it('should render unknown provider with capitalized label and generic icon', () => { + const enabledProviders = ['mycompany']; + + render( + + + + ); + + // Unknown provider should be capitalized + expect(screen.getByText('Mycompany')).toBeTruthy(); + + // Check that button has generic OIDC icon + const button = screen.getByText('Mycompany').closest('button'); + expect(button).toBeTruthy(); + const img = button?.querySelector('img'); + expect(img?.src).toContain('oidc.svg'); + }); + + it('should call onProviderClick with actual provider ID (not "oidc")', async () => { + const user = userEvent.setup(); + const enabledProviders = ['mycompany']; + + render( + + + + ); + + const button = screen.getByText('Mycompany'); + await user.click(button); + + // Should use actual provider ID 'mycompany', NOT 'oidc' + expect(mockOnProviderClick).toHaveBeenCalledWith('mycompany'); + }); + + it('should call onProviderClick with "authentik" when authentik is clicked', async () => { + const user = userEvent.setup(); + const enabledProviders = ['authentik']; + + render( + + + + ); + + const button = screen.getByText('Authentik'); + await user.click(button); + + expect(mockOnProviderClick).toHaveBeenCalledWith('authentik'); + }); + + it('should call onProviderClick with "oidc" when OIDC is explicitly configured', async () => { + const user = userEvent.setup(); + const enabledProviders = ['oidc']; + + render( + + + + ); + + const button = screen.getByText('OIDC'); + await user.click(button); + + expect(mockOnProviderClick).toHaveBeenCalledWith('oidc'); + }); + + it('should disable buttons when isSubmitting is true', () => { + const enabledProviders = ['google', 'github']; + + render( + + + + ); + + const googleButton = screen.getByText('Google').closest('button') as HTMLButtonElement; + const githubButton = screen.getByText('GitHub').closest('button') as HTMLButtonElement; + + expect(googleButton.disabled).toBe(true); + expect(githubButton.disabled).toBe(true); + }); + + it('should render nothing when no providers are enabled', () => { + const { container } = render( + + + + ); + + // Should render null/nothing (excluding Mantine's style tags) + const hasContent = Array.from(container.children).some( + child => child.tagName.toLowerCase() !== 'style' + ); + expect(hasContent).toBe(false); + }); + + it('should render multiple unknown providers with correct IDs', async () => { + const user = userEvent.setup(); + const enabledProviders = ['company1', 'company2', 'company3']; + + render( + + + + ); + + // All should be capitalized + expect(screen.getByText('Company1')).toBeTruthy(); + expect(screen.getByText('Company2')).toBeTruthy(); + expect(screen.getByText('Company3')).toBeTruthy(); + + // Click each and verify correct ID is passed + await user.click(screen.getByText('Company1')); + expect(mockOnProviderClick).toHaveBeenCalledWith('company1'); + + await user.click(screen.getByText('Company2')); + expect(mockOnProviderClick).toHaveBeenCalledWith('company2'); + + await user.click(screen.getByText('Company3')); + expect(mockOnProviderClick).toHaveBeenCalledWith('company3'); + }); + + it('should use correct icon for known providers', () => { + const enabledProviders = ['google', 'github', 'authentik', 'keycloak']; + + render( + + + + ); + + // Check that each known provider has its specific icon + const googleButton = screen.getByText('Google').closest('button'); + expect(googleButton?.querySelector('img')?.src).toContain('google.svg'); + + const githubButton = screen.getByText('GitHub').closest('button'); + expect(githubButton?.querySelector('img')?.src).toContain('github.svg'); + + const authentikButton = screen.getByText('Authentik').closest('button'); + expect(authentikButton?.querySelector('img')?.src).toContain('authentik.svg'); + + const keycloakButton = screen.getByText('Keycloak').closest('button'); + expect(keycloakButton?.querySelector('img')?.src).toContain('keycloak.svg'); + }); + + it('should handle mixed known and unknown providers', async () => { + const user = userEvent.setup(); + const enabledProviders = ['google', 'mycompany', 'authentik', 'custom']; + + render( + + + + ); + + // Known providers with correct labels + expect(screen.getByText('Google')).toBeTruthy(); + expect(screen.getByText('Authentik')).toBeTruthy(); + + // Unknown providers with capitalized labels + expect(screen.getByText('Mycompany')).toBeTruthy(); + expect(screen.getByText('Custom')).toBeTruthy(); + + // Click each and verify IDs are preserved + await user.click(screen.getByText('Google')); + expect(mockOnProviderClick).toHaveBeenCalledWith('google'); + + await user.click(screen.getByText('Mycompany')); + expect(mockOnProviderClick).toHaveBeenCalledWith('mycompany'); + + await user.click(screen.getByText('Authentik')); + expect(mockOnProviderClick).toHaveBeenCalledWith('authentik'); + + await user.click(screen.getByText('Custom')); + expect(mockOnProviderClick).toHaveBeenCalledWith('custom'); + }); + + it('should maintain provider ID consistency - critical for OAuth redirect', async () => { + const user = userEvent.setup(); + + // This test ensures the fix for GitHub issue #5141 + // The provider ID used in the button click MUST match the backend registration ID + // Previously, unknown providers were mapped to 'oidc', breaking the OAuth flow + + const enabledProviders = ['authentik', 'okta', 'auth0']; + + render( + + + + ); + + // Each provider should use its actual ID, not 'oidc' + await user.click(screen.getByText('Authentik')); + expect(mockOnProviderClick).toHaveBeenLastCalledWith('authentik'); + + await user.click(screen.getByText('Okta')); + expect(mockOnProviderClick).toHaveBeenLastCalledWith('okta'); + + await user.click(screen.getByText('Auth0')); + expect(mockOnProviderClick).toHaveBeenLastCalledWith('auth0'); + + // Verify none were called with 'oidc' instead of their actual ID + expect(mockOnProviderClick).not.toHaveBeenCalledWith('oidc'); + }); +}); diff --git a/frontend/src/proprietary/routes/login/OAuthButtons.tsx b/frontend/src/proprietary/routes/login/OAuthButtons.tsx index aaa280519f..d62edfdc15 100644 --- a/frontend/src/proprietary/routes/login/OAuthButtons.tsx +++ b/frontend/src/proprietary/routes/login/OAuthButtons.tsx @@ -1,5 +1,6 @@ import { useTranslation } from 'react-i18next'; import { BASE_PATH } from '@app/constants/app'; +import { type OAuthProvider } from '@app/auth/oauthTypes'; // Debug flag to show all providers for UI testing // Set to true to see all SSO options regardless of backend configuration @@ -22,10 +23,10 @@ export const oauthProviderConfig: Record void + onProviderClick: (provider: OAuthProvider) => void isSubmitting: boolean layout?: 'vertical' | 'grid' | 'icons' - enabledProviders?: string[] // List of enabled provider IDs from backend + enabledProviders?: OAuthProvider[] // List of enabled provider IDs from backend } export default function OAuthButtons({ onProviderClick, isSubmitting, layout = 'vertical', enabledProviders = [] }: OAuthButtonsProps) { From bb201ef9c10609398576fb4cc1d2033a9d4034e4 Mon Sep 17 00:00:00 2001 From: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com> Date: Fri, 5 Dec 2025 23:22:32 +0000 Subject: [PATCH 3/4] Chore/bump gradle version number (#5176) bump version number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2ae1355944..85f6a480de 100644 --- a/build.gradle +++ b/build.gradle @@ -57,7 +57,7 @@ repositories { allprojects { group = 'stirling.software' - version = '2.1.0' + version = '2.1.1' configurations.configureEach { exclude group: 'commons-logging', module: 'commons-logging' From 7faf7e50facf487e95b1ae1917b4611a3b30bfe8 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 6 Dec 2025 00:06:11 +0000 Subject: [PATCH 4/4] Chang etext on intro (#5160) # Description of Changes --- ## 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) ### 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. --- .../public/locales/ar-AR/translation.toml | 6 +++--- .../public/locales/az-AZ/translation.toml | 2 +- .../public/locales/bg-BG/translation.toml | 2 +- .../public/locales/ca-CA/translation.toml | 10 +++++----- .../public/locales/cs-CZ/translation.toml | 4 ++-- .../public/locales/da-DK/translation.toml | 8 ++++---- .../public/locales/de-DE/translation.toml | 2 +- .../public/locales/el-GR/translation.toml | 6 +++--- .../public/locales/en-GB/translation.toml | 2 +- .../public/locales/es-ES/translation.toml | 2 +- .../public/locales/eu-ES/translation.toml | 2 +- .../public/locales/fa-IR/translation.toml | 8 ++++---- .../public/locales/fr-FR/translation.toml | 8 ++++---- .../public/locales/ga-IE/translation.toml | 6 +++--- .../public/locales/hi-IN/translation.toml | 20 +++++++++---------- .../public/locales/hr-HR/translation.toml | 2 +- .../public/locales/hu-HU/translation.toml | 2 +- .../public/locales/id-ID/translation.toml | 6 +++--- .../public/locales/it-IT/translation.toml | 4 ++-- .../public/locales/ja-JP/translation.toml | 2 +- .../public/locales/ko-KR/translation.toml | 2 +- .../public/locales/ml-ML/translation.toml | 2 +- .../public/locales/nl-NL/translation.toml | 2 +- .../public/locales/no-NB/translation.toml | 2 +- .../public/locales/pl-PL/translation.toml | 8 ++++---- .../public/locales/pt-BR/translation.toml | 4 ++-- .../public/locales/pt-PT/translation.toml | 2 +- .../public/locales/ro-RO/translation.toml | 8 ++++---- .../public/locales/ru-RU/translation.toml | 2 +- .../public/locales/sk-SK/translation.toml | 2 +- .../public/locales/sl-SI/translation.toml | 2 +- .../locales/sr-LATN-RS/translation.toml | 4 ++-- .../public/locales/sv-SE/translation.toml | 2 +- .../public/locales/th-TH/translation.toml | 6 +++--- .../public/locales/tr-TR/translation.toml | 2 +- .../public/locales/uk-UA/translation.toml | 4 ++-- .../public/locales/vi-VN/translation.toml | 4 ++-- .../public/locales/zh-BO/translation.toml | 4 ++-- .../public/locales/zh-CN/translation.toml | 2 +- .../public/locales/zh-TW/translation.toml | 2 +- .../onboarding/slides/ServerLicenseSlide.tsx | 2 +- 41 files changed, 86 insertions(+), 86 deletions(-) diff --git a/frontend/public/locales/ar-AR/translation.toml b/frontend/public/locales/ar-AR/translation.toml index 4c437dbf45..4376a1dea7 100644 --- a/frontend/public/locales/ar-AR/translation.toml +++ b/frontend/public/locales/ar-AR/translation.toml @@ -3828,8 +3828,8 @@ title = "التحليلات" description = "تساعدنا هذه الملفات على فهم كيفية استخدام أدواتنا، كي نركّز على بناء الميزات الأكثر قيمة لمجتمعنا. كن مطمئنًا—‏Stirling PDF لا يمكنه ولن يتتبع محتوى المستندات التي تعمل عليها." [cookieBanner.services] -posthog = "PostHog Analytics" -scarf = "Scarf Pixel" +posthog = "تحليلات PostHog" +scarf = "Scarf بكسل" [removeMetadata] submit = "إزالة البيانات الوصفية" @@ -5177,7 +5177,7 @@ upgrade = "الترقية الآن →" freeTitle = "ترخيص الخادم" overLimitTitle = "مطلوب ترخيص خادم" overLimitBody = "ترخيصنا يسمح حتى {{freeTierLimit}} مستخدمين مجاناً لكل خادم. لديك {{overLimitUserCopy}} مستخدمي Stirling. للمتابعة دون انقطاع، ارقَ إلى خطة خادم Stirling - مقاعد غير محدودة، تحرير نصوص PDF، وتحكم إداري كامل مقابل $99/خادم/شهرياً." -freeBody = "ترخيص Open-Core لدينا يسمح حتى {{freeTierLimit}} مستخدمين مجاناً لكل خادم. للتوسع بسلاسة والحصول على وصول مبكر إلى أداة تحرير نصوص PDF الجديدة، نوصي بخطة خادم Stirling - تحرير كامل ومقاعد غير محدودة مقابل $99/خادم/شهرياً." +freeBody = "يتيح ترخيصنا Open-Core ما يصل إلى {{freeTierLimit}} مستخدمًا مجانًا لكل خادم. للتوسع دون انقطاع، نوصي بخطة Stirling Server - مقاعد غير محدودة ودعم SSO مقابل $99/server/mo." [onboarding.desktopInstall] title = "تنزيل" diff --git a/frontend/public/locales/az-AZ/translation.toml b/frontend/public/locales/az-AZ/translation.toml index 092b0fda76..fe13e4d0f6 100644 --- a/frontend/public/locales/az-AZ/translation.toml +++ b/frontend/public/locales/az-AZ/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "İndi yüksəlt →" freeTitle = "Server lisenziyası" overLimitTitle = "Server lisenziyası tələb olunur" overLimitBody = "Lisenziyalaşmamız hər server üçün pulsuz olaraq maksimum {{freeTierLimit}} istifadəçiyə icazə verir. Sizdə {{overLimitUserCopy}} Stirling istifadəçisi var. Fasiləsiz davam etmək üçün Stirling Server planına yüksəldin - limitsiz yerlər, PDF mətn redaktəsi və tam admin nəzarəti cəmi $99/server/ay." -freeBody = "Bizim Open-Core lisenziyası hər server üçün pulsuz olaraq maksimum {{freeTierLimit}} istifadəçiyə icazə verir. Fasiləsiz miqyaslanmaq və yeni PDF mətn redaktəsi alətimizə erkən çıxış əldə etmək üçün Stirling Server planını tövsiyə edirik — tam redaktə və limitsiz yerlər $99/server/ay." +freeBody = "Bizim Open-Core lisenziyalaşdırmamız hər server üçün pulsuz olaraq ən çox {{freeTierLimit}} istifadəçiyə icazə verir. Fasiləsiz miqyaslama üçün Stirling Server planını tövsiyə edirik - limitsiz yerlər və SSO dəstəyi $99/server/ay." [onboarding.desktopInstall] title = "Yüklə" diff --git a/frontend/public/locales/bg-BG/translation.toml b/frontend/public/locales/bg-BG/translation.toml index 13ae297672..adc099082e 100644 --- a/frontend/public/locales/bg-BG/translation.toml +++ b/frontend/public/locales/bg-BG/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Надградете сега →" freeTitle = "Лиценз за сървър" overLimitTitle = "Необходим е лиценз за сървър" overLimitBody = "Нашият лиценз позволява до {{freeTierLimit}} безплатни потребители на сървър. Имате {{overLimitUserCopy}} потребители на Stirling. За да продължите без прекъсвания, надградете до плана Stirling Server – неограничени места, редакция на PDF текст и пълен админ контрол за $99/сървър/месец." -freeBody = "Нашият Open-Core лиценз позволява до {{freeTierLimit}} безплатни потребители на сървър. За да мащабирате без прекъсвания и да получите ранен достъп до нашия нов инструмент за редакция на PDF текст, препоръчваме плана Stirling Server – пълно редактиране и неограничени места за $99/сървър/месец." +freeBody = "Нашият лицензен модел Open-Core позволява до {{freeTierLimit}} потребители безплатно на сървър. За безпрепятствено мащабиране препоръчваме плана Stirling Server - неограничени места и поддръжка на SSO за $99/server/mo." [onboarding.desktopInstall] title = "Изтегляне" diff --git a/frontend/public/locales/ca-CA/translation.toml b/frontend/public/locales/ca-CA/translation.toml index 85090e5a56..d521141f40 100644 --- a/frontend/public/locales/ca-CA/translation.toml +++ b/frontend/public/locales/ca-CA/translation.toml @@ -352,7 +352,7 @@ teams = "Equips" title = "Configuració" systemSettings = "Configuració del sistema" features = "Funcions" -endpoints = "Endpoints" +endpoints = "Punts finals" database = "Base de dades" advanced = "Avançat" @@ -561,7 +561,7 @@ totalEndpoints = "Total d'endpoints" totalVisits = "Total de visites" showing = "Mostrant" selectedVisits = "Visites seleccionades" -endpoint = "Endpoint" +endpoint = "Punt final" visits = "Visites" percentage = "Percentatge" loading = "Carregant..." @@ -4366,7 +4366,7 @@ features = "Banderes de funcions" processing = "Processament" [admin.settings.advanced.endpoints] -label = "Endpoints" +label = "Punts finals" manage = "Gestiona els endpoints de l'API" description = "La gestió d'endpoints es configura via YAML. Consulteu la documentació per a detalls sobre com habilitar/deshabilitar endpoints específics." @@ -5177,7 +5177,7 @@ upgrade = "Actualitza ara →" freeTitle = "Llicència del servidor" overLimitTitle = "Cal una llicència de servidor" overLimitBody = "La nostra llicència permet fins a {{freeTierLimit}} usuaris gratuïts per servidor. Tens {{overLimitUserCopy}} usuaris de Stirling. Per continuar sense interrupcions, actualitza al pla Stirling Server: seients il·limitats, edició de text de PDF i control d'administració complet per 99 $/servidor/mes." -freeBody = "La nostra llicència Open-Core permet fins a {{freeTierLimit}} usuaris gratuïts per servidor. Per escalar sense interrupcions i obtenir accés anticipat a la nova eina d'edició de text PDF, recomanem el pla Stirling Server: edició completa i seients il·limitats per 99 $/servidor/mes." +freeBody = "La nostra llicència Open-Core permet fins a {{freeTierLimit}} usuaris gratuïts per servidor. Per escalar sense interrupcions, recomanem el pla Stirling Server - places il·limitades i suport SSO per $99/servidor/mes." [onboarding.desktopInstall] title = "Baixa" @@ -5754,7 +5754,7 @@ title = "Gràfic d'ús dels endpoints" [usage.table] title = "Estadístiques detallades" -endpoint = "Endpoint" +endpoint = "Punt final" visits = "Visites" percentage = "Percentatge" noData = "No hi ha dades disponibles" diff --git a/frontend/public/locales/cs-CZ/translation.toml b/frontend/public/locales/cs-CZ/translation.toml index d1ffd5b7f8..4d580e79a8 100644 --- a/frontend/public/locales/cs-CZ/translation.toml +++ b/frontend/public/locales/cs-CZ/translation.toml @@ -4176,7 +4176,7 @@ description = "Sledovat akce uživatelů a systémové události pro compliance [admin.settings.security.audit.level] label = "Úroveň auditu" -description = "0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE" +description = "0=VYPNUTO, 1=ZÁKLADNÍ, 2=STANDARDNÍ, 3=PODROBNÝ" [admin.settings.security.audit.retentionDays] label = "Doba uchování auditů (dny)" @@ -5177,7 +5177,7 @@ upgrade = "Upgradovat nyní →" freeTitle = "Serverová licence" overLimitTitle = "Vyžadována serverová licence" overLimitBody = "Naše licencování umožňuje až {{freeTierLimit}} uživatelů zdarma na server. Máte {{overLimitUserCopy}} uživatelů Stirling. Pro nepřerušené používání přejděte na plán Stirling Server – neomezený počet míst, úpravy textu PDF a plná správa za 99 $/server/měsíc." -freeBody = "Naše licencování Open-Core umožňuje až {{freeTierLimit}} uživatelů zdarma na server. Pro nepřerušený růst a přednostní přístup k našemu novému nástroji pro úpravu textu PDF doporučujeme plán Stirling Server – plné úpravy a neomezený počet míst za 99 $/server/měsíc." +freeBody = "Naše licencování Open-Core umožňuje až {{freeTierLimit}} uživatelů zdarma na server. Pro nepřerušované škálování doporučujeme plán Stirling Server - neomezený počet míst a podpora SSO za $99/server/měs." [onboarding.desktopInstall] title = "Stáhnout" diff --git a/frontend/public/locales/da-DK/translation.toml b/frontend/public/locales/da-DK/translation.toml index d3a6cca28d..9ed522f5a7 100644 --- a/frontend/public/locales/da-DK/translation.toml +++ b/frontend/public/locales/da-DK/translation.toml @@ -1221,9 +1221,9 @@ pdfaDigitalSignatureWarning = "PDF'en indeholder en digital signatur. Dette vil fileFormat = "Filformat" wordDoc = "Word-dokument" wordDocExt = "Word-dokument (.docx)" -odtExt = "OpenDocument Text (.odt)" +odtExt = "OpenDocument-tekst (.odt)" pptExt = "PowerPoint (.pptx)" -odpExt = "OpenDocument Presentation (.odp)" +odpExt = "OpenDocument-præsentation (.odp)" txtExt = "Almindelig tekst (.txt)" rtfExt = "Rich Text Format (.rtf)" selectedFiles = "Valgte filer" @@ -3790,7 +3790,7 @@ version = "Nuværende udgivelse" title = "API-dokumentation" header = "API-dokumentation" desc = "Se og test Stirling PDF API-endpoints" -tags = "api,documentation,swagger,endpoints,development" +tags = "api,dokumentation,swagger,endepunkter,udvikling" [cookieBanner.popUp] title = "Sådan bruger vi cookies" @@ -5177,7 +5177,7 @@ upgrade = "Opgrader nu →" freeTitle = "Serverlicens" overLimitTitle = "Serverlicens påkrævet" overLimitBody = "Vores licens tillader op til {{freeTierLimit}} brugere gratis pr. server. Du har {{overLimitUserCopy}} Stirling-brugere. For at fortsætte uden afbrydelser skal du opgradere til Stirling Server-abonnementet – ubegrænsede pladser, PDF-tekstredigering og fuld admin-kontrol for $99/server/md." -freeBody = "Vores Open-Core-licens tillader op til {{freeTierLimit}} brugere gratis pr. server. For at skalere uden afbrydelser og få tidlig adgang til vores nye PDF-tekstredigeringsværktøj anbefaler vi Stirling Server-planen – fuld redigering og ubegrænsede pladser for $99/server/md." +freeBody = "Vores Open-Core-licens tillader op til {{freeTierLimit}} brugere gratis pr. server. For at skalere uden afbrydelser anbefaler vi Stirling Server-planen – ubegrænsede pladser og SSO-understøttelse for $99/server/md." [onboarding.desktopInstall] title = "Download" diff --git a/frontend/public/locales/de-DE/translation.toml b/frontend/public/locales/de-DE/translation.toml index 70dcf86b20..29dc3bf9fa 100644 --- a/frontend/public/locales/de-DE/translation.toml +++ b/frontend/public/locales/de-DE/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Jetzt upgraden →" freeTitle = "Server-Lizenz" overLimitTitle = "Server-Lizenz erforderlich" overLimitBody = "Unsere Lizenz erlaubt bis zu {{freeTierLimit}} Nutzer pro Server kostenlos. Sie haben {{overLimitUserCopy}} Stirling-Nutzer. Um ohne Unterbrechung fortzufahren, upgraden Sie auf den Stirling-Server-Plan – unbegrenzte Plätze, PDF-Textbearbeitung und volle Admin-Kontrolle für $99/Server/Monat." -freeBody = "Unsere Open-Core-Lizenz erlaubt bis zu {{freeTierLimit}} Nutzer pro Server kostenlos. Für unterbrechungsfreies Skalieren und frühen Zugriff auf unser neues PDF-Textbearbeitungs-Tool empfehlen wir den Stirling-Server-Plan – volle Bearbeitung und unbegrenzte Plätze für $99/Server/Monat." +freeBody = "Unsere Open-Core-Lizenz erlaubt bis zu {{freeTierLimit}} Nutzern pro Server kostenlos. Um unterbrechungsfrei zu skalieren, empfehlen wir den Stirling Server-Plan - unbegrenzte Plätze und SSO-Unterstützung für $99/Server/Monat." [onboarding.desktopInstall] title = "Download" diff --git a/frontend/public/locales/el-GR/translation.toml b/frontend/public/locales/el-GR/translation.toml index d3cf4c860b..5b47558c7d 100644 --- a/frontend/public/locales/el-GR/translation.toml +++ b/frontend/public/locales/el-GR/translation.toml @@ -3790,7 +3790,7 @@ version = "Τρέχουσα έκδοση" title = "Τεκμηρίωση API" header = "Τεκμηρίωση API" desc = "Προβάλετε και δοκιμάστε τα endpoints του Stirling PDF API" -tags = "api,documentation,swagger,endpoints,development" +tags = "api,τεκμηρίωση,swagger,τελικά σημεία,ανάπτυξη" [cookieBanner.popUp] title = "Πώς χρησιμοποιούμε τα cookies" @@ -4482,7 +4482,7 @@ label = "Ενεργοποίηση προσκλήσεων μέσω email" description = "Να επιτρέπεται στους διαχειριστές να προσκαλούν χρήστες μέσω email με αυτόματα παραγόμενους κωδικούς" [admin.settings.mail.frontendUrl] -label = "Frontend URL" +label = "URL front-end" description = "Βασικό URL για το frontend (π.χ. https://pdf.example.com). Χρησιμοποιείται για τη δημιουργία συνδέσμων πρόσκλησης στα email. Αφήστε κενό για χρήση του backend URL." [admin.settings.legal] @@ -5177,7 +5177,7 @@ upgrade = "Αναβάθμιση τώρα →" freeTitle = "Άδεια διακομιστή" overLimitTitle = "Απαιτείται άδεια διακομιστή" overLimitBody = "Η αδειοδότηση μας επιτρέπει έως {{freeTierLimit}} χρήστες δωρεάν ανά διακομιστή. Έχετε {{overLimitUserCopy}} χρήστες Stirling. Για να συνεχίσετε χωρίς διακοπές, αναβαθμίστε στο πλάνο Stirling Server - απεριόριστες θέσεις, επεξεργασία κειμένου PDF και πλήρης έλεγχος διαχειριστή για $99/server/μήνα." -freeBody = "Η αδειοδότηση Open-Core μας επιτρέπει έως {{freeTierLimit}} χρήστες δωρεάν ανά διακομιστή. Για απρόσκοπτη κλιμάκωση και έγκαιρη πρόσβαση στο νέο εργαλείο επεξεργασίας κειμένου PDF, προτείνουμε το πλάνο Stirling Server - πλήρης επεξεργασία και απεριόριστες θέσεις για $99/server/μήνα." +freeBody = "Οι άδειες χρήσης Open-Core επιτρέπουν έως και {{freeTierLimit}} χρήστες δωρεάν ανά διακομιστή. Για απρόσκοπτη κλιμάκωση, προτείνουμε το πλάνο Stirling Server - απεριόριστες θέσεις και υποστήριξη SSO με $99/διακομιστή/μήνα." [onboarding.desktopInstall] title = "Λήψη" diff --git a/frontend/public/locales/en-GB/translation.toml b/frontend/public/locales/en-GB/translation.toml index c60df3a850..fcdcf592f2 100644 --- a/frontend/public/locales/en-GB/translation.toml +++ b/frontend/public/locales/en-GB/translation.toml @@ -5263,7 +5263,7 @@ upgrade = "Upgrade now →" freeTitle = "Server License" overLimitTitle = "Server License Needed" overLimitBody = "Our licensing permits up to {{freeTierLimit}} users for free per server. You have {{overLimitUserCopy}} Stirling users. To continue uninterrupted, upgrade to the Stirling Server plan - unlimited seats, PDF text editing, and full admin control for $99/server/mo." -freeBody = "Our Open-Core licensing permits up to {{freeTierLimit}} users for free per server. To scale uninterrupted and get early access to our new PDF text editing tool, we recommend the Stirling Server plan - full editing and unlimited seats for $99/server/mo." +freeBody = "Our Open-Core licensing permits up to {{freeTierLimit}} users for free per server. To scale uninterrupted, we recommend the Stirling Server plan - unlimited seats and SSO support for $99/server/mo." [onboarding.desktopInstall] title = "Download" diff --git a/frontend/public/locales/es-ES/translation.toml b/frontend/public/locales/es-ES/translation.toml index 437c0ac2c4..fb0969a1a3 100644 --- a/frontend/public/locales/es-ES/translation.toml +++ b/frontend/public/locales/es-ES/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Actualizar ahora →" freeTitle = "Licencia del servidor" overLimitTitle = "Se necesita licencia de servidor" overLimitBody = "Nuestra licencia permite hasta {{freeTierLimit}} usuarios gratis por servidor. Tiene {{overLimitUserCopy}} usuarios de Stirling. Para continuar sin interrupciones, actualice al plan Stirling Server: plazas ilimitadas, edición de texto PDF y control total de administración por 99 $/servidor/mes." -freeBody = "Nuestra licencia Open-Core permite hasta {{freeTierLimit}} usuarios gratis por servidor. Para escalar sin interrupciones y obtener acceso anticipado a nuestra nueva herramienta de edición de texto PDF, recomendamos el plan Stirling Server: edición completa y plazas ilimitadas por 99 $/servidor/mes." +freeBody = "Nuestra licencia Open-Core permite hasta {{freeTierLimit}} usuarios gratis por servidor. Para escalar sin interrupciones, recomendamos el plan Stirling Server - plazas ilimitadas y soporte SSO por $99/servidor/mes." [onboarding.desktopInstall] title = "Descargar" diff --git a/frontend/public/locales/eu-ES/translation.toml b/frontend/public/locales/eu-ES/translation.toml index 3fc897147c..96dfbfcdcf 100644 --- a/frontend/public/locales/eu-ES/translation.toml +++ b/frontend/public/locales/eu-ES/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Eguneratu orain →" freeTitle = "Zerbitzari-lizentzia" overLimitTitle = "Beharrezkoa da zerbitzari-lizentzia" overLimitBody = "Gure lizentziak baimentzen ditu {{freeTierLimit}} erabiltzaile doan zerbitzari bakoitzeko. {{overLimitUserCopy}} Stirling erabiltzaile dituzu. Jarraitzeko etenik gabe, eguneratu Stirling Server planera - eserleku mugagabeak, PDF testu-edizioa, eta admin kontrol osoa $99/zerbitzari/hilean." -freeBody = "Gure Open-Core lizentziak {{freeTierLimit}} erabiltzaile arte baimentzen ditu doan zerbitzari bakoitzeko. Etenik gabe eskalatzeko eta gure PDF testu-edizio tresna berrirako sarbide goiztiarra lortzeko, gomendatzen dugu Stirling Server plana - edizio osoa eta eserleku mugagabeak $99/zerbitzari/hilean." +freeBody = "Gure Open-Core lizentziak zerbitzari bakoitzeko doan gehienez {{freeTierLimit}} erabiltzaile baimentzen ditu. Etenik gabe eskalatzeko, Stirling Server plana gomendatzen dugu - eserleku mugagabeak eta SSO euskarria $99/server/mo." [onboarding.desktopInstall] title = "Deskargatu" diff --git a/frontend/public/locales/fa-IR/translation.toml b/frontend/public/locales/fa-IR/translation.toml index 8102a9c7c3..26bebba53a 100644 --- a/frontend/public/locales/fa-IR/translation.toml +++ b/frontend/public/locales/fa-IR/translation.toml @@ -4258,11 +4258,11 @@ label = "URL صادرکننده" description = "Issuer URL ارائه‌دهنده OAuth2" [admin.settings.connections.oauth2.clientId] -label = "Client ID" +label = "شناسهٔ کلاینت" description = "Client ID مربوط به OAuth2 از ارائه‌دهنده شما" [admin.settings.connections.oauth2.clientSecret] -label = "Client Secret" +label = "راز کلاینت" description = "Client Secret مربوط به OAuth2 از ارائه‌دهنده شما" [admin.settings.connections.oauth2.useAsUsername] @@ -4293,7 +4293,7 @@ label = "ارائه‌دهنده" description = "نام ارائه‌دهنده SAML2" [admin.settings.connections.saml2.registrationId] -label = "Registration ID" +label = "شناسهٔ ثبت‌نام" description = "شناسه ثبت‌نام SAML2" [admin.settings.connections.saml2.autoCreateUser] @@ -5177,7 +5177,7 @@ upgrade = "همین حالا ارتقا بده →" freeTitle = "لایسنس سرور" overLimitTitle = "نیاز به لایسنس سرور" overLimitBody = "مجوز ما تا {{freeTierLimit}} کاربر رایگان به‌ازای هر سرور را مجاز می‌داند. شما {{overLimitUserCopy}} کاربر Stirling دارید. برای ادامه بدون وقفه، به پلن Stirling Server ارتقا دهید - صندلی نامحدود، ویرایش متن PDF و کنترل کامل ادمین با 99$ به‌ازای هر سرور در ماه." -freeBody = "مجوز Open-Core ما تا {{freeTierLimit}} کاربر رایگان به‌ازای هر سرور را مجاز می‌داند. برای مقیاس‌پذیری بدون وقفه و دسترسی زودهنگام به ابزار ویرایش متن PDF جدیدمان، پلن Stirling Server را پیشنهاد می‌کنیم - ویرایش کامل و صندلی نامحدود با 99$ به‌ازای هر سرور در ماه." +freeBody = "مجوز Open-Core ما به‌ازای هر سرور اجازهٔ استفادهٔ رایگان برای حداکثر {{freeTierLimit}} کاربر را می‌دهد. برای مقیاس‌دهی بدون وقفه، طرح Stirling Server را توصیه می‌کنیم - تعداد کاربران نامحدود و پشتیبانی از SSO با $99/سرور/ماه." [onboarding.desktopInstall] title = "دانلود" diff --git a/frontend/public/locales/fr-FR/translation.toml b/frontend/public/locales/fr-FR/translation.toml index 5ae44d3f7b..1f413e9270 100644 --- a/frontend/public/locales/fr-FR/translation.toml +++ b/frontend/public/locales/fr-FR/translation.toml @@ -363,7 +363,7 @@ connections = "Connexions" [settings.licensingAnalytics] title = "Licences et analyses" -plan = "Plan" +plan = "Forfait" audit = "Audit" usageAnalytics = "Analyses d'utilisation" @@ -4550,7 +4550,7 @@ successMessage = "Fichier de licence téléversé et activé avec succès. Aucun title = "Licence active" file = "Source: Fichier de licence ({{path}})" key = "Source: Clé de licence" -type = "Type: {{type}}" +type = "Type : {{type}}" noInput = "Veuillez fournir une clé de licence ou téléverser un fichier de certificat" success = "Succès" @@ -5177,7 +5177,7 @@ upgrade = "Mettre à niveau maintenant →" freeTitle = "Licence serveur" overLimitTitle = "Licence serveur requise" overLimitBody = "Notre licence autorise jusqu’à {{freeTierLimit}} utilisateurs gratuits par serveur. Vous avez {{overLimitUserCopy}} utilisateurs Stirling. Pour continuer sans interruption, passez au plan Stirling Server — places illimitées, édition de texte PDF et contrôle d’administration complet pour 99 $/serveur/mois." -freeBody = "Notre licence Open-Core autorise jusqu’à {{freeTierLimit}} utilisateurs gratuits par serveur. Pour évoluer sans interruption et accéder en avant-première à notre nouvel outil d’édition de texte PDF, nous recommandons le plan Stirling Server — édition complète et places illimitées pour 99 $/serveur/mois." +freeBody = "Notre régime de licence Open-Core autorise jusqu'à {{freeTierLimit}} utilisateurs gratuitement par serveur. Pour évoluer sans interruption, nous recommandons le forfait Stirling Server - places illimitées et prise en charge du SSO pour 99 $/serveur/mois." [onboarding.desktopInstall] title = "Télécharger" @@ -5892,7 +5892,7 @@ paragraph = "Page de paragraphe" sparse = "Texte clairsemé" [pdfTextEditor.groupingMode] -auto = "Auto" +auto = "Automatique" paragraph = "Paragraphe" singleLine = "Ligne unique" diff --git a/frontend/public/locales/ga-IE/translation.toml b/frontend/public/locales/ga-IE/translation.toml index 254c8578a9..77d27e093a 100644 --- a/frontend/public/locales/ga-IE/translation.toml +++ b/frontend/public/locales/ga-IE/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Uasghrádaigh anois →" freeTitle = "Ceadúnas Freastalaí" overLimitTitle = "Ceadúnas Freastalaí de dhíth" overLimitBody = "Ceadaíonn ár gceadúnú suas le {{freeTierLimit}} úsáideoir in aisce in aghaidh freastalaí. Tá {{overLimitUserCopy}} úsáideoir Stirling agat. Chun leanúint gan bhriseadh, uasghrádaigh go plean Freastalaí Stirling - suíocháin neamhtheoranta, eagarthóireacht téacs PDF, agus lánrialú riaracháin ar $99/freastalaí/mí." -freeBody = "Ceadaíonn ár gceadúnú Open-Core suas le {{freeTierLimit}} úsáideoir in aisce in aghaidh freastalaí. Chun méadú gan bhriseadh agus rochtain luath a fháil ar ár uirlis eagarthóireachta téacs PDF nua, molaimid Plean Freastalaí Stirling - eagarthóireacht iomlán agus suíocháin neamhtheoranta ar $99/freastalaí/mí." +freeBody = "Ceadaíonn ár gceadúnú Open-Core suas le {{freeTierLimit}} úsáideoirí saor in aisce in aghaidh an fhreastalaí. Chun scálú gan bhriseadh, molaimid an plean Stirling Server - suíocháin neamhtheoranta agus tacaíocht SSO ar $99/server/mo." [onboarding.desktopInstall] title = "Íoslódáil" @@ -5333,8 +5333,8 @@ emailDisabled = "Teastaíonn cumraíocht SMTP agus mail.enableInvites=true sna s [workspace.people.license] users = "úsáideoirí" availableSlots = "Áiteanna Ar Fáil" -grandfathered = "Grandfathered" -grandfatheredShort = "{{count}} grandfathered" +grandfathered = "Ceadaithe roimhe seo" +grandfatheredShort = "{{count}} ceadaithe roimhe seo" fromLicense = "ón gceadúnas" slotsAvailable = "{{count}} áit(í) úsáideora ar fáil" noSlotsAvailable = "Níl aon áiteanna ar fáil" diff --git a/frontend/public/locales/hi-IN/translation.toml b/frontend/public/locales/hi-IN/translation.toml index c569e48ba8..011479c9f1 100644 --- a/frontend/public/locales/hi-IN/translation.toml +++ b/frontend/public/locales/hi-IN/translation.toml @@ -834,7 +834,7 @@ title = "PDF हस्ताक्षर सत्यापित करें" desc = "PDF दस्तावेजों में डिजिटल हस्ताक्षर और प्रमाणपत्रों को सत्यापित करें" [home.swagger] -tags = "API,documentation,test" +tags = "API,दस्तावेज़ीकरण,परीक्षण" title = "API दस्तावेज़ीकरण" desc = "API दस्तावेज़ देखें और एंडपॉइंट टेस्ट करें" @@ -883,7 +883,7 @@ title = "रंग बदलें/उलटें" desc = "PDF दस्तावेज़ों में रंगों को प्रतिस्थापित या उलटें" [home.devApi] -tags = "API,development,documentation" +tags = "API,विकास,दस्तावेज़ीकरण" title = "API" desc = "API दस्तावेज़ के लिए लिंक" @@ -922,7 +922,7 @@ title = "PDF टेक्स्ट एडिटर" desc = "PDF फ़ाइलों के भीतर मौजूदा टेक्स्ट और इमेज संपादित करें" [home.addText] -tags = "text,annotation,label" +tags = "पाठ,टिप्पणी,लेबल" title = "टेक्स्ट जोड़ें" desc = "अपने PDF में कहीं भी कस्टम टेक्स्ट जोड़ें" @@ -1840,7 +1840,7 @@ title = "उन्नत" tags = "कम्प्रेस,छोटा,छोटा" [unlockPDFForms] -tags = "remove,delete,form,field,readonly" +tags = "हटाएं,मिटाएं,फॉर्म,फ़ील्ड,रीड-ओनली" title = "फॉर्म फ़ील्ड से Read-Only हटाएं" header = "PDF फॉर्म अनलॉक करें" submit = "Remove" @@ -2747,7 +2747,7 @@ submit = "जमा करें" failed = "मल्टी-पृष्ठ लेआउट बनाते समय त्रुटि हुई।" [bookletImposition] -tags = "booklet,imposition,printing,binding,folding,signature" +tags = "बुकलेट,इम्पोज़िशन,प्रिंटिंग,बाइंडिंग,फोल्डिंग,सिग्नेचर" title = "बुकलेट इम्पोज़िशन" header = "बुकलेट इम्पोज़िशन" submit = "बुकलेट बनाएँ" @@ -2846,7 +2846,7 @@ scaleFactor = "एक पृष्ठ का ज़ूम स्तर (क् submit = "जमा करें" [adjustPageScale] -tags = "resize,modify,dimension,adapt" +tags = "आकार बदलें,संशोधित करें,आयाम,अनुकूलित करें" title = "पृष्ठ स्केल समायोजित करें" header = "पृष्ठ स्केल समायोजित करें" submit = "पृष्ठ स्केल समायोजित करें" @@ -3396,7 +3396,7 @@ certHint = "कस्टम ट्रस्ट स्रोत के विर title = "सत्यापन सेटिंग्स" [replaceColor] -tags = "Replace Colour,Page operations,Back end,server side" +tags = "रंग बदलें,पृष्ठ संचालन,Back end,server side" [replaceColor.labels] settings = "सेटिंग्स" @@ -3790,7 +3790,7 @@ version = "वर्तमान रिलीज़" title = "API दस्तावेज़ीकरण" header = "API दस्तावेज़ीकरण" desc = "Stirling PDF API एंडपॉइंट्स देखें और परीक्षण करें" -tags = "api,documentation,swagger,endpoints,development" +tags = "api,दस्तावेज़ीकरण,swagger,endpoints,विकास" [cookieBanner.popUp] title = "हम कुकीज़ का उपयोग कैसे करते हैं" @@ -5177,7 +5177,7 @@ upgrade = "अभी अपग्रेड करें →" freeTitle = "सर्वर लाइसेंस" overLimitTitle = "सर्वर लाइसेंस आवश्यक" overLimitBody = "हमारा लाइसेंसिंग प्रति सर्वर अधिकतम {{freeTierLimit}} उपयोगकर्ताओं को मुफ्त अनुमति देता है। आपके पास {{overLimitUserCopy}} Stirling उपयोगकर्ता हैं। बिना बाधा के जारी रखने के लिए, Stirling Server प्लान में अपग्रेड करें - अनलिमिटेड सीट्स, PDF टेक्स्ट एडिटिंग, और पूर्ण एडमिन नियंत्रण $99/server/mo में।" -freeBody = "हमारा Open-Core लाइसेंसिंग प्रति सर्वर अधिकतम {{freeTierLimit}} उपयोगकर्ताओं को मुफ्त अनुमति देता है। बिना बाधा स्केल करने और हमारे नए PDF टेक्स्ट एडिटिंग टूल की प्रारंभिक पहुँच पाने के लिए हम Stirling Server प्लान की सलाह देते हैं - पूर्ण एडिटिंग और अनलिमिटेड सीट्स $99/server/mo में।" +freeBody = "हमारा Open-Core लाइसेंसिंग प्रति सर्वर अधिकतम {{freeTierLimit}} उपयोगकर्ताओं को निःशुल्क अनुमति देता है। बिना रुकावट स्केल करने के लिए, हम Stirling Server प्लान की अनुशंसा करते हैं - असीमित सीटें और SSO समर्थन $99/सर्वर/माह पर।" [onboarding.desktopInstall] title = "डाउनलोड" @@ -6005,7 +6005,7 @@ insufficientPermissions = "आपके पास यह क्रिया क [addText] title = "टेक्स्ट जोड़ें" header = "PDFs में टेक्स्ट जोड़ें" -tags = "text,annotation,label" +tags = "पाठ,टिप्पणी,लेबल" applySignatures = "टेक्स्ट लागू करें" [addText.text] diff --git a/frontend/public/locales/hr-HR/translation.toml b/frontend/public/locales/hr-HR/translation.toml index 3e68d5ce53..4a97a35589 100644 --- a/frontend/public/locales/hr-HR/translation.toml +++ b/frontend/public/locales/hr-HR/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Nadogradi odmah →" freeTitle = "Poslužiteljska licenca" overLimitTitle = "Potrebna poslužiteljska licenca" overLimitBody = "Naše licenciranje dopušta do {{freeTierLimit}} korisnika besplatno po poslužitelju. Imate {{overLimitUserCopy}} Stirling korisnika. Za nesmetan nastavak, nadogradite na Stirling Server plan - neograničena mjesta, uređivanje teksta u PDF-u i puna admin kontrola za $99/server/mo." -freeBody = "Naše Open-Core licenciranje dopušta do {{freeTierLimit}} korisnika besplatno po poslužitelju. Za nesmetano skaliranje i rani pristup našem novom alatu za uređivanje teksta u PDF-u, preporučujemo Stirling Server plan - potpuno uređivanje i neograničena mjesta za $99/server/mo." +freeBody = "Naše licenciranje Open-Core omogućuje do {{freeTierLimit}} korisnika besplatno po poslužitelju. Za neometano skaliranje preporučujemo Stirling Server plan - neograničena mjesta i podrška za SSO za $99/poslužitelj/mj." [onboarding.desktopInstall] title = "Preuzimanje" diff --git a/frontend/public/locales/hu-HU/translation.toml b/frontend/public/locales/hu-HU/translation.toml index 5c70e8a2e3..edbacf219d 100644 --- a/frontend/public/locales/hu-HU/translation.toml +++ b/frontend/public/locales/hu-HU/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Frissítés most →" freeTitle = "Szerverlicenc" overLimitTitle = "Szerverlicenc szükséges" overLimitBody = "Licencelésünk szerverenként legfeljebb {{freeTierLimit}} felhasználót enged ingyen. Önnek {{overLimitUserCopy}} Stirling felhasználója van. A zavartalan használathoz váltson a Stirling Server csomagra – korlátlan hely, PDF szövegszerkesztés és teljes adminisztrátori vezérlés $99/szerver/hó áron." -freeBody = "Az Open-Core licencelésünk szerverenként legfeljebb {{freeTierLimit}} felhasználót enged ingyen. A zavartalan bővüléshez és az új PDF szövegszerkesztő eszköz korai eléréséhez a Stirling Server csomagot ajánljuk – teljes szerkesztés és korlátlan hely $99/szerver/hó áron." +freeBody = "A Open-Core licencünk szerverenként legfeljebb {{freeTierLimit}} felhasználót engedélyez ingyenesen. A zökkenőmentes skálázáshoz a Stirling Server csomagot ajánljuk - korlátlan felhasználó és SSO támogatás $99/szerver/hó." [onboarding.desktopInstall] title = "Letöltés" diff --git a/frontend/public/locales/id-ID/translation.toml b/frontend/public/locales/id-ID/translation.toml index 78fff67354..d260fe5517 100644 --- a/frontend/public/locales/id-ID/translation.toml +++ b/frontend/public/locales/id-ID/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Upgrade sekarang →" freeTitle = "Lisensi Server" overLimitTitle = "Perlu Lisensi Server" overLimitBody = "Lisensi kami mengizinkan hingga {{freeTierLimit}} pengguna gratis per server. Anda memiliki {{overLimitUserCopy}} pengguna Stirling. Untuk terus berjalan tanpa gangguan, upgrade ke paket Stirling Server - kursi tanpa batas, pengeditan teks PDF, dan kontrol admin penuh seharga $99/server/bulan." -freeBody = "Lisensi Open-Core kami mengizinkan hingga {{freeTierLimit}} pengguna gratis per server. Untuk skala tanpa hambatan dan mendapatkan akses awal ke alat pengeditan teks PDF baru kami, kami sarankan paket Stirling Server - pengeditan penuh dan kursi tanpa batas seharga $99/server/bulan." +freeBody = "Lisensi Open-Core kami mengizinkan hingga {{freeTierLimit}} pengguna gratis per server. Untuk meningkatkan skala tanpa gangguan, kami merekomendasikan paket Stirling Server - pengguna tanpa batas dan dukungan SSO seharga $99/server/mo." [onboarding.desktopInstall] title = "Unduh" @@ -5433,7 +5433,7 @@ hideComparison = "Sembunyikan Perbandingan Fitur" featureComparison = "Perbandingan Fitur" from = "Mulai" perMonth = "/bulan" -perSeat = "/seat" +perSeat = "/pengguna" withServer = "+ Paket Server" licensedSeats = "Berlisensi: {{count}} seat" includedInCurrent = "Termasuk dalam Paket Anda" @@ -5594,7 +5594,7 @@ modalTitle = "Mulai - {{planName}}" title = "Pilih Periode Penagihan" savingsNote = "Hemat {{percent}}% dengan penagihan tahunan" basePrice = "Harga Dasar" -seatPrice = "Per Seat" +seatPrice = "Per Pengguna" totalForSeats = "Total ({{count}} seat)" selectMonthly = "Pilih Bulanan" selectYearly = "Pilih Tahunan" diff --git a/frontend/public/locales/it-IT/translation.toml b/frontend/public/locales/it-IT/translation.toml index 806cfa5716..79f6e0e7ad 100644 --- a/frontend/public/locales/it-IT/translation.toml +++ b/frontend/public/locales/it-IT/translation.toml @@ -4176,7 +4176,7 @@ description = "Traccia azioni degli utenti ed eventi di sistema per conformità [admin.settings.security.audit.level] label = "Livello audit" -description = "0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE" +description = "0=SPENTO, 1=BASE, 2=STANDARD, 3=DETTAGLIATO" [admin.settings.security.audit.retentionDays] label = "Conservazione audit (giorni)" @@ -5177,7 +5177,7 @@ upgrade = "Esegui upgrade ora →" freeTitle = "Licenza server" overLimitTitle = "Licenza server necessaria" overLimitBody = "La nostra licenza consente fino a {{freeTierLimit}} utenti gratuiti per server. Hai {{overLimitUserCopy}} utenti Stirling. Per continuare senza interruzioni, esegui l'upgrade al piano Stirling Server - posti illimitati, modifica del testo PDF e pieno controllo admin a $99/server/mese." -freeBody = "La nostra licenza Open-Core consente fino a {{freeTierLimit}} utenti gratuiti per server. Per scalare senza interruzioni e ottenere accesso anticipato al nuovo strumento di modifica testo PDF, consigliamo il piano Stirling Server - modifica completa e posti illimitati a $99/server/mese." +freeBody = "La nostra licenza Open-Core consente fino a {{freeTierLimit}} utenti gratuiti per server. Per scalare senza interruzioni, consigliamo il piano Stirling Server - posti illimitati e supporto SSO a $99/server/mese." [onboarding.desktopInstall] title = "Download" diff --git a/frontend/public/locales/ja-JP/translation.toml b/frontend/public/locales/ja-JP/translation.toml index bceb6975fd..67a4affb39 100644 --- a/frontend/public/locales/ja-JP/translation.toml +++ b/frontend/public/locales/ja-JP/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "今すぐアップグレード →" freeTitle = "サーバーライセンス" overLimitTitle = "サーバーライセンスが必要です" overLimitBody = "当社のライセンスでは、サーバーごとに {{freeTierLimit}} ユーザーまで無料です。現在 {{overLimitUserCopy}} の Stirling ユーザーがいます。中断なく利用を続けるには、Stirling Server プランにアップグレードしてください - 無制限席数、PDF テキスト編集、完全な管理機能が $99/サーバー/月 です。" -freeBody = "当社の オープンコア ライセンスでは、サーバーごとに最大 {{freeTierLimit}} ユーザーまで無料です。中断なく拡張し、新しい PDF テキスト編集ツール に早期アクセスするには、Stirling Server プランをお勧めします。完全編集と 無制限席数 が $99/サーバー/月 です。" +freeBody = "当社のOpen-Coreライセンスでは、サーバーごとに最大{{freeTierLimit}}ユーザーまで無料でご利用いただけます。中断なくスケールするには、Stirling Server プランをおすすめします - 無制限の席数とSSO サポートで $99/サーバー/月。" [onboarding.desktopInstall] title = "ダウンロード" diff --git a/frontend/public/locales/ko-KR/translation.toml b/frontend/public/locales/ko-KR/translation.toml index 3dcd0c2377..63f274a25b 100644 --- a/frontend/public/locales/ko-KR/translation.toml +++ b/frontend/public/locales/ko-KR/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "지금 업그레이드 →" freeTitle = "서버 라이선스" overLimitTitle = "서버 라이선스 필요" overLimitBody = "당사의 라이선스는 서버당 무료로 최대 {{freeTierLimit}}명의 사용자를 허용합니다. 현재 {{overLimitUserCopy}}명의 Stirling 사용자가 있습니다. 중단 없이 계속 사용하려면 Stirling Server 플랜으로 업그레이드하세요 - 무제한 좌석, PDF 텍스트 편집, 전체 관리자 제어 제공, $99/서버/월." -freeBody = "당사의 Open-Core 라이선스는 서버당 최대 {{freeTierLimit}}명의 사용자를 무료로 허용합니다. 중단 없이 확장하고 새로운 PDF 텍스트 편집 도구에 조기 액세스하려면 Stirling Server 플랜을 권장합니다 - 전체 편집과 무제한 좌석을 $99/서버/월에 제공합니다." +freeBody = "당사의 Open-Core 라이선스는 서버당 최대 {{freeTierLimit}}명의 사용자를 무료로 허용합니다. 중단 없이 확장하려면 Stirling Server 플랜을 권장합니다 - 무제한 좌석 및 SSO 지원, $99/서버/월." [onboarding.desktopInstall] title = "다운로드" diff --git a/frontend/public/locales/ml-ML/translation.toml b/frontend/public/locales/ml-ML/translation.toml index 92106dd931..4cfe475f0a 100644 --- a/frontend/public/locales/ml-ML/translation.toml +++ b/frontend/public/locales/ml-ML/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "ഇപ്പോൾ അപ്‌ഗ്രേഡ് ചെയ്യു freeTitle = "സെർവർ ലൈസൻസ്" overLimitTitle = "സെർവർ ലൈസൻസ് ആവശ്യമാണ്" overLimitBody = "ഞങ്ങളുടെ ലൈസൻസിംഗ് ഓരോ സെർവർക്കും പരമാവധി {{freeTierLimit}} ഉപയോക്താക്കളെ സൗജന്യമായി അനുവദിക്കുന്നു. നിങ്ങള്ക്ക് {{overLimitUserCopy}} Stirling ഉപയോക്താക്കളുണ്ട്. തടസ്സമില്ലാതെ തുടരാൻ, Stirling Server പ്ലാനിലേക്ക് അപ്‌ഗ്രേഡ് ചെയ്യുക - unlimited seats, PDF text editing, പൂർണ്ണ അഡ്മിൻ നിയന്ത്രണം, $99/server/mo." -freeBody = "ഞങ്ങളുടെ Open-Core ലൈസൻസിംഗ് ഓരോ സെർവർക്കും പരമാവധി {{freeTierLimit}} ഉപയോക്താക്കളെ സൗജന്യമായി അനുവദിക്കുന്നു. തടസ്സമില്ലാതെ സ്കെയിൽ ചെയ്യാനും പുതിയ PDF text editing tool ന് മുൻകാല ആക്സസ് നേടാനും, Stirling Server പ്ലാൻ ഞങ്ങൾ ശുപാർശ ചെയ്യുന്നു - പൂർണ്ണ എഡിറ്റിംഗും unlimited seats ഉം $99/server/mo." +freeBody = "ഞങ്ങളുടെ Open-Core ലൈസൻസിംഗ് ഓരോ സെർവർക്കും പരമാവധി {{freeTierLimit}} ഉപയോക്താക്കളെ സൗജന്യമായി അനുവദിക്കുന്നു. തടസ്സമില്ലാതെ സ്‌കെയിൽ ചെയ്യാൻ, ഞങ്ങൾ Stirling Server പ്ലാൻ ശുപാർശ ചെയ്യുന്നു - പരിമിതിയില്ലാത്ത സീറ്റുകൾയും SSO പിന്തുണയും for $99/server/mo." [onboarding.desktopInstall] title = "ഡൗൺലോഡ്" diff --git a/frontend/public/locales/nl-NL/translation.toml b/frontend/public/locales/nl-NL/translation.toml index b5c5cf99b4..97532092bf 100644 --- a/frontend/public/locales/nl-NL/translation.toml +++ b/frontend/public/locales/nl-NL/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Nu upgraden →" freeTitle = "Serverlicentie" overLimitTitle = "Serverlicentie vereist" overLimitBody = "Onze licentie staat tot {{freeTierLimit}} gebruikers gratis per server toe. Je hebt {{overLimitUserCopy}} Stirling-gebruikers. Om zonder onderbreking door te gaan, upgrade naar het Stirling Server-plan - onbeperkte plaatsen, PDF-tekstbewerking en volledige admincontrole voor $99/server/maand." -freeBody = "Onze Open-Core-licentie staat tot {{freeTierLimit}} gebruikers gratis per server toe. Om ononderbroken te schalen en vroege toegang te krijgen tot onze nieuwe PDF-tekstbewerkingstool, raden we het Stirling Server-plan aan - volledige bewerking en onbeperkte plaatsen voor $99/server/maand." +freeBody = "Onze Open-Core-licentie staat tot {{freeTierLimit}} gebruikers per server gratis toe. Om ononderbroken op te schalen, raden we het Stirling Server-abonnement aan - onbeperkte plaatsen en SSO-ondersteuning voor $99/server/maand." [onboarding.desktopInstall] title = "Downloaden" diff --git a/frontend/public/locales/no-NB/translation.toml b/frontend/public/locales/no-NB/translation.toml index 7dc8b431a9..eaf40fae07 100644 --- a/frontend/public/locales/no-NB/translation.toml +++ b/frontend/public/locales/no-NB/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Oppgrader nå →" freeTitle = "Serverlisens" overLimitTitle = "Serverlisens kreves" overLimitBody = "Lisensieringen vår tillater opptil {{freeTierLimit}} brukere gratis per server. Du har {{overLimitUserCopy}} Stirling-brukere. For å fortsette uten avbrudd, oppgrader til Stirling Server-planen – ubegrensede plasser, PDF-tekstredigering og full admin-kontroll for $99/server/mnd." -freeBody = "Vår Open-Core-lisensiering tillater opptil {{freeTierLimit}} brukere gratis per server. For å skalere uten avbrudd og få tidlig tilgang til vårt nye PDF-tekstredigeringsverktøy, anbefaler vi Stirling Server-planen – full redigering og ubegrensede plasser for $99/server/mnd." +freeBody = "Vår Open-Core-lisensiering tillater opptil {{freeTierLimit}} brukere gratis per server. For å skalere uten avbrudd anbefaler vi Stirling Server-planen - ubegrensede plasser og SSO-støtte for $99/server/mnd." [onboarding.desktopInstall] title = "Last ned" diff --git a/frontend/public/locales/pl-PL/translation.toml b/frontend/public/locales/pl-PL/translation.toml index e652c88d22..ebf36642ae 100644 --- a/frontend/public/locales/pl-PL/translation.toml +++ b/frontend/public/locales/pl-PL/translation.toml @@ -568,7 +568,7 @@ loading = "Ładowanie..." failedToLoad = "Nie udało się załadować danych punktów końcowych. Spróbuj odświeżyć." home = "Strona główna" login = "Logowanie" -top = "Top" +top = "Najlepsze" numberOfVisits = "Liczba wizyt" visitsTooltip = "Wizyty: {0} ({1}% całości)" retry = "Spróbuj ponownie" @@ -1225,7 +1225,7 @@ odtExt = "Tekst OpenDocument (.odt)" pptExt = "PowerPoint (.pptx)" odpExt = "Prezentacja OpenDocument (.odp)" txtExt = "Tekst niesformatowany (.txt)" -rtfExt = "Rich Text Format (.rtf)" +rtfExt = "Format RTF (.rtf)" selectedFiles = "Wybrane pliki" noFileSelected = "Nie wybrano pliku. Użyj panelu plików, aby dodać pliki." convertFiles = "Konwertuj pliki" @@ -5177,7 +5177,7 @@ upgrade = "Ulepsz teraz →" freeTitle = "Licencja serwera" overLimitTitle = "Wymagana licencja serwera" overLimitBody = "Nasza licencja pozwala na maks. {{freeTierLimit}} użytkowników bez opłat na serwer. Masz {{overLimitUserCopy}} użytkowników Stirling. Aby kontynuować bez przerw, przejdź na plan Stirling Server – nielimitowane miejsca, edycja tekstu PDF i pełna kontrola administracyjna za 99 USD/serwer/mies." -freeBody = "Nasza licencja Open-Core pozwala na maks. {{freeTierLimit}} użytkowników bez opłat na serwer. Aby skalować bez przerw i uzyskać wczesny dostęp do nowego narzędzia edycji tekstu PDF, polecamy plan Stirling Server – pełna edycja i nielimitowane miejsca za 99 USD/serwer/mies." +freeBody = "Nasza licencja Open-Core pozwala na maksymalnie {{freeTierLimit}} użytkowników bezpłatnie na serwer. Aby skalować bez zakłóceń, zalecamy plan Stirling Server - nielimitowana liczba miejsc i obsługa SSO za $99/serwer/mies." [onboarding.desktopInstall] title = "Pobierz" @@ -5586,7 +5586,7 @@ emailInvalid = "Wpisz poprawny adres e‑mail" title = "Podaj e‑mail" description = "Użyjemy go do wysłania klucza licencyjnego i rachunków." emailLabel = "Adres e‑mail" -emailPlaceholder = "your@email.com" +emailPlaceholder = "twoj@email.com" continue = "Kontynuuj" modalTitle = "Zaczynamy – {{planName}}" diff --git a/frontend/public/locales/pt-BR/translation.toml b/frontend/public/locales/pt-BR/translation.toml index baaa4215b4..9a6d58d672 100644 --- a/frontend/public/locales/pt-BR/translation.toml +++ b/frontend/public/locales/pt-BR/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Fazer upgrade agora →" freeTitle = "Licença do servidor" overLimitTitle = "Necessária licença do servidor" overLimitBody = "Nossa licença permite até {{freeTierLimit}} usuários grátis por servidor. Você tem {{overLimitUserCopy}} usuários do Stirling. Para continuar sem interrupções, faça upgrade para o plano Stirling Server - assentos ilimitados, edição de texto em PDF e controle total de admin por US$ 99/servidor/mês." -freeBody = "Nossa licença Open-Core permite até {{freeTierLimit}} usuários grátis por servidor. Para escalar sem interrupções e ter acesso antecipado à nova ferramenta de edição de texto em PDF, recomendamos o plano Stirling Server - edição completa e assentos ilimitados por US$ 99/servidor/mês." +freeBody = "Nossa licença Open-Core permite até {{freeTierLimit}} usuários gratuitos por servidor. Para escalar sem interrupções, recomendamos o plano Stirling Server - assentos ilimitados e suporte a SSO por US$ 99/servidor/mês." [onboarding.desktopInstall] title = "Download" @@ -5586,7 +5586,7 @@ emailInvalid = "Digite um endereço de e-mail válido" title = "Informe seu e-mail" description = "Usaremos isso para enviar sua chave de licença e recibos." emailLabel = "Endereço de e-mail" -emailPlaceholder = "your@email.com" +emailPlaceholder = "seu@email.com" continue = "Continuar" modalTitle = "Começar - {{planName}}" diff --git a/frontend/public/locales/pt-PT/translation.toml b/frontend/public/locales/pt-PT/translation.toml index 0691f5f35c..49a02e369e 100644 --- a/frontend/public/locales/pt-PT/translation.toml +++ b/frontend/public/locales/pt-PT/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Atualizar agora →" freeTitle = "Licença do servidor" overLimitTitle = "É necessária licença de servidor" overLimitBody = "A nossa licença permite até {{freeTierLimit}} utilizadores gratuitos por servidor. Tem {{overLimitUserCopy}} utilizadores Stirling. Para continuar sem interrupções, atualize para o plano Stirling Server - lugares ilimitados, edição de texto em PDF e controlo total de administração por $99/servidor/mês." -freeBody = "A nossa licença Open-Core permite até {{freeTierLimit}} utilizadores gratuitos por servidor. Para escalar sem interrupções e obter acesso antecipado à nossa nova ferramenta de edição de texto PDF, recomendamos o plano Stirling Server - edição completa e lugares ilimitados por $99/servidor/mês." +freeBody = "O nosso licenciamento Open-Core permite até {{freeTierLimit}} utilizadores gratuitos por servidor. Para escalar sem interrupções, recomendamos o plano Stirling Server - lugares ilimitados e suporte SSO por $99/servidor/mês." [onboarding.desktopInstall] title = "Transferir" diff --git a/frontend/public/locales/ro-RO/translation.toml b/frontend/public/locales/ro-RO/translation.toml index c323b3354a..005cc5f211 100644 --- a/frontend/public/locales/ro-RO/translation.toml +++ b/frontend/public/locales/ro-RO/translation.toml @@ -3948,7 +3948,7 @@ files = "Fișiere" activity = "Jurnal" help = "Ajutor" account = "Cont" -config = "Config" +config = "Configurare" settings = "Setări" adminSettings = "Setări admin" allTools = "All Tools" @@ -4176,7 +4176,7 @@ description = "Urmărește acțiunile utilizatorilor și evenimentele de sistem [admin.settings.security.audit.level] label = "Nivel audit" -description = "0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE" +description = "0=OPRIT, 1=DE BAZĂ, 2=STANDARD, 3=DETALIAT" [admin.settings.security.audit.retentionDays] label = "Păstrare audit (zile)" @@ -5177,7 +5177,7 @@ upgrade = "Fă upgrade acum →" freeTitle = "Licență server" overLimitTitle = "Necesită licență de server" overLimitBody = "Politica noastră de licențiere permite până la {{freeTierLimit}} utilizatori gratuit per server. Ai {{overLimitUserCopy}} utilizatori Stirling. Pentru a continua fără întreruperi, fă upgrade la planul Stirling Server - locuri nelimitate, editare text PDF și control complet de admin pentru $99/server/lună." -freeBody = "Licențierea noastră Open-Core permite până la {{freeTierLimit}} utilizatori gratuit per server. Pentru a scala fără întreruperi și a primi acces timpuriu la noul nostru instrument de editare text PDF, recomandăm planul Stirling Server - editare completă și locuri nelimitate pentru $99/server/lună." +freeBody = "Licențierea noastră Open-Core permite până la {{freeTierLimit}} utilizatori gratuit per server. Pentru scalare fără întreruperi, recomandăm planul Stirling Server - locuri nelimitate și suport SSO pentru $99/server/lună." [onboarding.desktopInstall] title = "Descărcare" @@ -5984,7 +5984,7 @@ warnings = "Avertizări" suggestions = "Note" currentPageFonts = "Fonturi pe această pagină" allFonts = "Toate fonturile" -fallback = "fallback" +fallback = "rezervă" missing = "lipsește" perfectMessage = "Toate fonturile pot fi redate perfect." warningMessage = "Unele fonturi pot să nu fie redate corect." diff --git a/frontend/public/locales/ru-RU/translation.toml b/frontend/public/locales/ru-RU/translation.toml index b72009fa67..69f8f6712c 100644 --- a/frontend/public/locales/ru-RU/translation.toml +++ b/frontend/public/locales/ru-RU/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Обновить сейчас →" freeTitle = "Серверная лицензия" overLimitTitle = "Требуется серверная лицензия" overLimitBody = "Наша лицензия допускает до {{freeTierLimit}} пользователей бесплатно на сервер. У вас {{overLimitUserCopy}} пользователей Stirling. Чтобы продолжить без перебоев, перейдите на тариф Stirling Server — неограниченные места, редактирование текста в PDF и полный админ‑контроль за $99/server/mo." -freeBody = "Наша лицензия Open-Core допускает до {{freeTierLimit}} пользователей бесплатно на сервер. Чтобы масштабироваться без ограничений и раньше получить доступ к новому инструменту редактирования текста в PDF, рекомендуем тариф Stirling Server — полный редактор и неограниченные места за $99/server/mo." +freeBody = "Наша лицензия Open-Core позволяет бесплатно использовать до {{freeTierLimit}} пользователей на сервер. Для бесшовного масштабирования мы рекомендуем план Stirling Server - неограниченное число мест и поддержка SSO за $99/сервер/мес." [onboarding.desktopInstall] title = "Скачать" diff --git a/frontend/public/locales/sk-SK/translation.toml b/frontend/public/locales/sk-SK/translation.toml index 183112a67d..7a196d6998 100644 --- a/frontend/public/locales/sk-SK/translation.toml +++ b/frontend/public/locales/sk-SK/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Upgradovať teraz →" freeTitle = "Serverová licencia" overLimitTitle = "Potrebná serverová licencia" overLimitBody = "Naše licencovanie povoľuje až {{freeTierLimit}} používateľov zdarma na server. Máte {{overLimitUserCopy}} používateľov Stirling. Ak chcete pokračovať bez prerušenia, prejdite na plán Stirling Server - neobmedzené miesta, úpravy textu PDF a plná správa pre $99/server/mo." -freeBody = "Naše licencovanie Open-Core povoľuje až {{freeTierLimit}} používateľov zdarma na server. Ak chcete škálovať bez prerušenia a získať skorý prístup k nášmu novému nástroju na úpravu textu PDF, odporúčame plán Stirling Server - plné úpravy a neobmedzené miesta za $99/server/mo." +freeBody = "Naše licencovanie Open-Core umožňuje až {{freeTierLimit}} používateľov zadarmo na server. Na plynulé škálovanie odporúčame plán Stirling Server - neobmedzený počet používateľov a podporu SSO za $99/server/mes." [onboarding.desktopInstall] title = "Stiahnuť" diff --git a/frontend/public/locales/sl-SI/translation.toml b/frontend/public/locales/sl-SI/translation.toml index f8bc314929..8233f0d422 100644 --- a/frontend/public/locales/sl-SI/translation.toml +++ b/frontend/public/locales/sl-SI/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Nadgradi zdaj →" freeTitle = "Licenca strežnika" overLimitTitle = "Potrebna licenca strežnika" overLimitBody = "Naše licenciranje brezplačno omogoča do {{freeTierLimit}} uporabnikov na strežnik. Imate {{overLimitUserCopy}} uporabnikov Stirling. Za nemoteno uporabo nadgradite na načrt Stirling Server – neomejena mesta, urejanje besedila PDF in popoln skrbniški nadzor za $99/strežnik/mesec." -freeBody = "Naše licenciranje Open-Core brezplačno omogoča do {{freeTierLimit}} uporabnikov na strežnik. Za nemoteno rast in zgodnji dostop do našega novega orodja za urejanje besedila PDF priporočamo načrt Stirling Server – polno urejanje in neomejena mesta za $99/strežnik/mesec." +freeBody = "Naše licenciranje Open-Core omogoča do {{freeTierLimit}} uporabnikov brezplačno na strežnik. Za nemoteno skaliranje priporočamo načrt Stirling Server - neomejena mesta in podpora za SSO za $99/strežnik/mesec." [onboarding.desktopInstall] title = "Prenesi" diff --git a/frontend/public/locales/sr-LATN-RS/translation.toml b/frontend/public/locales/sr-LATN-RS/translation.toml index fd433c25e9..17ce6f3402 100644 --- a/frontend/public/locales/sr-LATN-RS/translation.toml +++ b/frontend/public/locales/sr-LATN-RS/translation.toml @@ -1225,7 +1225,7 @@ odtExt = "OpenDocument tekst (.odt)" pptExt = "PowerPoint (.pptx)" odpExt = "OpenDocument prezentacija (.odp)" txtExt = "Običan tekst (.txt)" -rtfExt = "Rich Text Format (.rtf)" +rtfExt = "Format obogaćenog teksta (.rtf)" selectedFiles = "Izabrane datoteke" noFileSelected = "Nije izabrana nijedna datoteka. Koristite panel datoteka da dodate datoteke." convertFiles = "Konvertuj datoteke" @@ -5177,7 +5177,7 @@ upgrade = "Nadogradite sada →" freeTitle = "Serverska licenca" overLimitTitle = "Potrebna serverska licenca" overLimitBody = "Naše licenciranje dozvoljava do {{freeTierLimit}} korisnika besplatno po serveru. Imate {{overLimitUserCopy}} Stirling korisnika. Da nastavite bez prekida, pređite na Stirling Server plan - neograničena mesta, uređivanje PDF teksta i puna admin kontrola za $99/server/mes." -freeBody = "Naše Open-Core licenciranje dozvoljava do {{freeTierLimit}} korisnika besplatno po serveru. Da se bez prekida skalirate i dobijete rani pristup našem novom alatu za uređivanje PDF teksta, preporučujemo Stirling Server plan - puno uređivanje i neograničena mesta za $99/server/mes." +freeBody = "Naše licenciranje Open-Core dozvoljava do {{freeTierLimit}} korisnika besplatno po serveru. Za neometano skaliranje, preporučujemo plan Stirling Server - neograničena mesta i SSO podrška za $99/server/mo." [onboarding.desktopInstall] title = "Preuzmi" diff --git a/frontend/public/locales/sv-SE/translation.toml b/frontend/public/locales/sv-SE/translation.toml index 5c100fe78e..ccafc28b93 100644 --- a/frontend/public/locales/sv-SE/translation.toml +++ b/frontend/public/locales/sv-SE/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Uppgradera nu →" freeTitle = "Serverlicens" overLimitTitle = "Serverlicens krävs" overLimitBody = "Vår licensiering tillåter upp till {{freeTierLimit}} användare gratis per server. Du har {{overLimitUserCopy}} Stirling-användare. För att fortsätta utan avbrott, uppgradera till Stirling Server-planen - obegränsade platser, PDF-textredigering och full adminkontroll för $99/server/mån." -freeBody = "Vår Open-Core-licens tillåter upp till {{freeTierLimit}} användare gratis per server. För att skala utan avbrott och få tidig åtkomst till vårt nya PDF-textredigeringsverktyg rekommenderar vi Stirling Server-planen - full redigering och obegränsade platser för $99/server/mån." +freeBody = "Vår Open-Core-licens tillåter upp till {{freeTierLimit}} användare gratis per server. För att skala utan avbrott rekommenderar vi Stirling Server plan - obegränsat antal platser och SSO-stöd för $99/server/månad." [onboarding.desktopInstall] title = "Ladda ner" diff --git a/frontend/public/locales/th-TH/translation.toml b/frontend/public/locales/th-TH/translation.toml index 386b699f8a..ce03b4a138 100644 --- a/frontend/public/locales/th-TH/translation.toml +++ b/frontend/public/locales/th-TH/translation.toml @@ -1221,9 +1221,9 @@ pdfaDigitalSignatureWarning = "PDF มีลายเซ็นดิจิทั fileFormat = "รูปแบบไฟล์" wordDoc = "เอกสาร Word" wordDocExt = "เอกสาร Word (.docx)" -odtExt = "OpenDocument Text (.odt)" +odtExt = "ข้อความ OpenDocument (.odt)" pptExt = "PowerPoint (.pptx)" -odpExt = "OpenDocument Presentation (.odp)" +odpExt = "งานนำเสนอ OpenDocument (.odp)" txtExt = "ข้อความล้วน (.txt)" rtfExt = "Rich Text Format (.rtf)" selectedFiles = "ไฟล์ที่เลือก" @@ -5177,7 +5177,7 @@ upgrade = "อัปเกรดเลย →" freeTitle = "ไลเซนส์เซิร์ฟเวอร์" overLimitTitle = "ต้องใช้ไลเซนส์เซิร์ฟเวอร์" overLimitBody = "สิทธิ์การใช้งานของเรารองรับผู้ใช้ได้ฟรีสูงสุด {{freeTierLimit}} คนต่อเซิร์ฟเวอร์ ขณะนี้คุณมีผู้ใช้ Stirling {{overLimitUserCopy}} คน เพื่อใช้งานต่อเนื่อง โปรดอัปเกรดเป็นแพ็กเกจ Stirling Server - ที่นั่งไม่จำกัด แก้ไขข้อความ PDF และควบคุมแอดมินเต็มรูปแบบ ราคา $99/ต่อเซิร์ฟเวอร์/เดือน" -freeBody = "ไลเซนส์แบบ Open-Core ของเรารองรับผู้ใช้ได้ฟรีสูงสุด {{freeTierLimit}} คนต่อเซิร์ฟเวอร์ เพื่อขยายการใช้งานได้ต่อเนื่องและเข้าถึง เครื่องมือแก้ไขข้อความ PDF ล่วงหน้า เราแนะนำแพ็กเกจ Stirling Server - แก้ไขได้เต็มรูปแบบและ ที่นั่งไม่จำกัด ราคา $99/ต่อเซิร์ฟเวอร์/เดือน" +freeBody = "สัญญาอนุญาตแบบ Open-Core ของเราอนุญาตให้ใช้งานฟรีได้สูงสุด {{freeTierLimit}} ผู้ใช้ต่อเซิร์ฟเวอร์หนึ่งเครื่อง เพื่อขยายการใช้งานอย่างต่อเนื่อง เราขอแนะนำแผน Stirling Server - ที่นั่งไม่จำกัด และ รองรับ SSO ในราคา $99/เซิร์ฟเวอร์/เดือน" [onboarding.desktopInstall] title = "ดาวน์โหลด" diff --git a/frontend/public/locales/tr-TR/translation.toml b/frontend/public/locales/tr-TR/translation.toml index 52d46d7fe2..342452574b 100644 --- a/frontend/public/locales/tr-TR/translation.toml +++ b/frontend/public/locales/tr-TR/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "Şimdi yükselt →" freeTitle = "Sunucu Lisansı" overLimitTitle = "Sunucu Lisansı Gerekli" overLimitBody = "Lisansımız, sunucu başına ücretsiz olarak en fazla {{freeTierLimit}} kullanıcıya izin verir. {{overLimitUserCopy}} Stirling kullanıcınız var. Kesintisiz devam etmek için Stirling Server planına yükseltin - sınırsız koltuk, PDF metin düzenleme ve tam yönetici kontrolü $99/server/ay." -freeBody = "Open-Core lisansımız, sunucu başına ücretsiz olarak en fazla {{freeTierLimit}} kullanıcıya izin verir. Kesintisiz ölçeklemek ve yeni PDF metin düzenleme aracımıza erken erişim almak için Stirling Server planını öneririz - tam düzenleme ve sınırsız koltuk $99/server/ay." +freeBody = "Open-Core lisanslamamız, sunucu başına en fazla {{freeTierLimit}} kullanıcıya ücretsiz izin verir. Kesintisiz ölçeklendirme için Stirling Server planını öneririz - sınırsız kullanıcı ve SSO desteği için $99/sunucu/ay." [onboarding.desktopInstall] title = "İndir" diff --git a/frontend/public/locales/uk-UA/translation.toml b/frontend/public/locales/uk-UA/translation.toml index b261dc02a2..a239459907 100644 --- a/frontend/public/locales/uk-UA/translation.toml +++ b/frontend/public/locales/uk-UA/translation.toml @@ -3790,7 +3790,7 @@ version = "Текущий релиз" title = "Документація API" header = "Документація API" desc = "Переглядайте та тестуйте кінцеві точки API Stirling PDF" -tags = "api,documentation,swagger,endpoints,development" +tags = "api,документація,swagger,кінцеві точки,розробка" [cookieBanner.popUp] title = "Як ми використовуємо файли cookie" @@ -5177,7 +5177,7 @@ upgrade = "Оновити зараз →" freeTitle = "Ліцензія сервера" overLimitTitle = "Потрібна ліцензія сервера" overLimitBody = "Наша ліцензія дозволяє до {{freeTierLimit}} користувачів безкоштовно на сервер. У вас {{overLimitUserCopy}} користувачів Stirling. Щоб працювати без перерв, перейдіть на план Stirling Server — необмежена кількість місць, редагування тексту PDF та повний адмін-контроль за $99/server/mo." -freeBody = "Наша Open-Core ліцензія дозволяє до {{freeTierLimit}} користувачів безкоштовно на сервер. Щоб масштабуватися безперервно та отримати ранній доступ до нового інструмента редагування тексту PDF, рекомендуємо план Stirling Server — повне редагування та необмежена кількість місць за $99/server/mo." +freeBody = "Наша ліцензія Open-Core дозволяє до {{freeTierLimit}} користувачів безкоштовно на сервер. Щоб масштабуватися без перерв, рекомендуємо план Stirling Server — необмежена кількість місць і підтримка SSO за $99/сервер/міс." [onboarding.desktopInstall] title = "Завантажити" diff --git a/frontend/public/locales/vi-VN/translation.toml b/frontend/public/locales/vi-VN/translation.toml index 7e9860bb97..b73cc807d6 100644 --- a/frontend/public/locales/vi-VN/translation.toml +++ b/frontend/public/locales/vi-VN/translation.toml @@ -301,7 +301,7 @@ saveSettings = "Lưu cài đặt thao tác" pipelineNamePrompt = "Nhập tên pipeline tại đây" selectOperation = "Chọn thao tác" addOperationButton = "Thêm thao tác" -pipelineHeader = "Pipeline:" +pipelineHeader = "Chuỗi xử lý:" saveButton = "Tải xuống" validateButton = "Xác thực" @@ -5177,7 +5177,7 @@ upgrade = "Nâng cấp ngay →" freeTitle = "Giấy phép Server" overLimitTitle = "Cần giấy phép Server" overLimitBody = "Giấy phép của chúng tôi cho phép tối đa {{freeTierLimit}} người dùng miễn phí mỗi server. Bạn có {{overLimitUserCopy}} người dùng Stirling. Để tiếp tục không gián đoạn, hãy nâng cấp lên gói Stirling Server - số ghế không giới hạn, chỉnh sửa văn bản PDF và toàn quyền quản trị với $99/server/tháng." -freeBody = "Giấy phép Open-Core của chúng tôi cho phép tối đa {{freeTierLimit}} người dùng miễn phí mỗi server. Để mở rộng không gián đoạn và truy cập sớm công cụ chỉnh sửa văn bản PDF mới, chúng tôi khuyến nghị gói Stirling Server - chỉnh sửa đầy đủ và số ghế không giới hạn với $99/server/tháng." +freeBody = "Giấy phép Open-Core của chúng tôi cho phép tối đa {{freeTierLimit}} người dùng miễn phí cho mỗi máy chủ. Để mở rộng quy mô liền mạch, chúng tôi khuyến nghị gói Stirling Server - số lượng người dùng không giới hạn và hỗ trợ SSO với giá $99/máy chủ/tháng." [onboarding.desktopInstall] title = "Tải xuống" diff --git a/frontend/public/locales/zh-BO/translation.toml b/frontend/public/locales/zh-BO/translation.toml index 847e423d08..e14cbeb6ba 100644 --- a/frontend/public/locales/zh-BO/translation.toml +++ b/frontend/public/locales/zh-BO/translation.toml @@ -1181,7 +1181,7 @@ selectFilesPlaceholder = "在主视图中选择文件以开始" settings = "设置" conversionCompleted = "转换完成" results = "结果" -defaultFilename = "converted_file" +defaultFilename = "已转换_文件" conversionResults = "转换结果" convertFrom = "从以下格式转换" convertTo = "转换为" @@ -5177,7 +5177,7 @@ upgrade = "立即升级 →" freeTitle = "服务器许可证" overLimitTitle = "需要服务器许可证" overLimitBody = "我们的许可每台服务器最多允许 {{freeTierLimit}} 名用户免费使用。您共有 {{overLimitUserCopy}} 名 Stirling 用户。为避免中断,请升级到 Stirling Server 方案 - 无限席位、PDF 文本编辑,以及每台服务器 $99/月 的完整管理员控制。" -freeBody = "我们的 开源内核(Open-Core) 许可允许每台服务器最多 {{freeTierLimit}} 名用户免费使用。为顺畅扩展并抢先体验全新的 PDF 文本编辑工具,我们推荐 Stirling Server 方案 - 完整编辑与 无限席位,$99/服务器/月。" +freeBody = "我们的Open-Core许可允许每台服务器最多{{freeTierLimit}}名用户免费使用。为实现不中断的扩展,我们推荐 Stirling Server 方案 - 不限席位并提供SSO 支持,$99/服务器/月。" [onboarding.desktopInstall] title = "下载" diff --git a/frontend/public/locales/zh-CN/translation.toml b/frontend/public/locales/zh-CN/translation.toml index fed6c1d605..f79e74cab9 100644 --- a/frontend/public/locales/zh-CN/translation.toml +++ b/frontend/public/locales/zh-CN/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "立即升级 →" freeTitle = "服务器许可证" overLimitTitle = "需要服务器许可证" overLimitBody = "我们的许可允许每台服务器最多免费 {{freeTierLimit}} 名用户。您有 {{overLimitUserCopy}} 名 Stirling 用户。为不间断使用,请升级至 Stirling Server 方案 - 无限席位、PDF 文本编辑,以及 $99/server/mo 的完整管理员控制。" -freeBody = "我们的 Open-Core 许可允许每台服务器最多免费 {{freeTierLimit}} 名用户。为无缝扩展并抢先体验全新的 PDF 文本编辑工具,推荐 Stirling Server 方案 - 完整编辑与 无限席位,$99/server/mo。" +freeBody = "我们的 Open-Core 许可允许每台服务器最多 {{freeTierLimit}} 名用户免费使用。为实现不间断扩展,我们推荐 Stirling Server 方案 - 无限席位 和 SSO 支持,$99/server/mo." [onboarding.desktopInstall] title = "下载" diff --git a/frontend/public/locales/zh-TW/translation.toml b/frontend/public/locales/zh-TW/translation.toml index 8b2f97b5d1..6dff92e6ad 100644 --- a/frontend/public/locales/zh-TW/translation.toml +++ b/frontend/public/locales/zh-TW/translation.toml @@ -5177,7 +5177,7 @@ upgrade = "立即升級 →" freeTitle = "伺服器授權" overLimitTitle = "需要伺服器授權" overLimitBody = "我們的授權允許每台伺服器最多 {{freeTierLimit}} 位使用者免費使用。你有 {{overLimitUserCopy}} 位 Stirling 使用者。若要不中斷使用,請升級至 Stirling Server 方案 - 不限席次、PDF 文字編輯,以及完整管理控制,每台伺服器 $99/月。" -freeBody = "我們的 Open-Core 授權允許每台伺服器最多 {{freeTierLimit}} 位使用者免費使用。若要無縫擴充並搶先體驗全新的 PDF 文字編輯工具,建議升級至 Stirling Server 方案 - 完整編輯與 不限席次,每台伺服器 $99/月。" +freeBody = "我們的 Open-Core 授權允許每台伺服器最多 {{freeTierLimit}} 位使用者免費使用。若要無縫擴充,我們建議選用 Stirling Server 方案 - 不限席次 與 SSO 支援,每伺服器每月 $99。" [onboarding.desktopInstall] title = "下載" diff --git a/frontend/src/core/components/onboarding/slides/ServerLicenseSlide.tsx b/frontend/src/core/components/onboarding/slides/ServerLicenseSlide.tsx index f118d2ac02..3da69103b5 100644 --- a/frontend/src/core/components/onboarding/slides/ServerLicenseSlide.tsx +++ b/frontend/src/core/components/onboarding/slides/ServerLicenseSlide.tsx @@ -39,7 +39,7 @@ export default function ServerLicenseSlide({ licenseNotice }: ServerLicenseSlide components={{ strong: , }} - defaults="Our Open-Core licensing permits up to {{freeTierLimit}} users for free per server. To scale uninterrupted and get early access to our new PDF text editing tool, we recommend the Stirling Server plan - full editing and unlimited seats for $99/server/mo." + defaults="Our Open-Core licensing permits up to {{freeTierLimit}} users for free per server. To scale uninterrupted, we recommend the Stirling Server plan - unlimited seats and SSO support for $99/server/mo." /> );