mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 13:20:08 +03:00
Compare commits
17
Commits
SSOServer
...
extraFonts
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec46172a33 | ||
|
|
c980ee10c0 | ||
|
|
fa4d2bc09a | ||
|
|
7faf7e50fa | ||
|
|
bb201ef9c1 | ||
|
|
82dbcfbb9b | ||
|
|
9fd8fd89ed | ||
|
|
3a2370ea1f | ||
|
|
e7db714091 | ||
|
|
c6b4a2b141 | ||
|
|
7459463a3c | ||
|
|
c9bf436895 | ||
|
|
f8dbf171e1 | ||
|
|
e59c717dc0 | ||
|
|
f2bffe2dc6 | ||
|
|
5d827df08c | ||
|
|
bdb3c887f3 |
@@ -112,7 +112,6 @@ public class ApplicationProperties {
|
||||
@Data
|
||||
public static class Security {
|
||||
private Boolean enableLogin;
|
||||
private Boolean csrfDisabled;
|
||||
private InitialLogin initialLogin = new InitialLogin();
|
||||
private OAUTH2 oauth2 = new OAUTH2();
|
||||
private SAML2 saml2 = new SAML2();
|
||||
|
||||
@@ -254,10 +254,7 @@ public class PostHogService {
|
||||
properties,
|
||||
"security_enableLogin",
|
||||
applicationProperties.getSecurity().getEnableLogin());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"security_csrfDisabled",
|
||||
applicationProperties.getSecurity().getCsrfDisabled());
|
||||
addIfNotEmpty(properties, "security_csrfDisabled", true);
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"security_loginAttemptCount",
|
||||
|
||||
@@ -34,7 +34,6 @@ public class InitialSetup {
|
||||
public void init() throws IOException {
|
||||
initUUIDKey();
|
||||
initSecretKey();
|
||||
initEnableCSRFSecurity();
|
||||
initLegalUrls();
|
||||
initSetAppVersion();
|
||||
GeneralUtils.extractPipeline();
|
||||
@@ -59,19 +58,6 @@ public class InitialSetup {
|
||||
applicationProperties.getAutomaticallyGenerated().setKey(secretKey);
|
||||
}
|
||||
}
|
||||
|
||||
public void initEnableCSRFSecurity() throws IOException {
|
||||
if (GeneralUtils.isVersionHigher(
|
||||
"0.46.0", applicationProperties.getAutomaticallyGenerated().getAppVersion())) {
|
||||
Boolean csrf = applicationProperties.getSecurity().getCsrfDisabled();
|
||||
if (!csrf) {
|
||||
GeneralUtils.saveKeyToSettings("security.csrfDisabled", false);
|
||||
GeneralUtils.saveKeyToSettings("system.enableAnalytics", true);
|
||||
applicationProperties.getSecurity().setCsrfDisabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void initLegalUrls() throws IOException {
|
||||
// Initialize Terms and Conditions
|
||||
String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
|
||||
@@ -95,7 +81,7 @@ public class InitialSetup {
|
||||
isNewServer =
|
||||
existingVersion == null
|
||||
|| existingVersion.isEmpty()
|
||||
|| existingVersion.equals("0.0.0");
|
||||
|| "0.0.0".equals(existingVersion);
|
||||
|
||||
String appVersion = "0.0.0";
|
||||
Resource resource = new ClassPathResource("version.properties");
|
||||
|
||||
@@ -124,7 +124,6 @@ public class SettingsController {
|
||||
ApplicationProperties.Security security = applicationProperties.getSecurity();
|
||||
|
||||
settings.put("enableLogin", security.getEnableLogin());
|
||||
settings.put("csrfDisabled", security.getCsrfDisabled());
|
||||
settings.put("loginMethod", security.getLoginMethod());
|
||||
settings.put("loginAttemptCount", security.getLoginAttemptCount());
|
||||
settings.put("loginResetTimeMinutes", security.getLoginResetTimeMinutes());
|
||||
@@ -159,12 +158,6 @@ public class SettingsController {
|
||||
.getSecurity()
|
||||
.setEnableLogin((Boolean) settings.get("enableLogin"));
|
||||
}
|
||||
if (settings.containsKey("csrfDisabled")) {
|
||||
GeneralUtils.saveKeyToSettings("security.csrfDisabled", settings.get("csrfDisabled"));
|
||||
applicationProperties
|
||||
.getSecurity()
|
||||
.setCsrfDisabled((Boolean) settings.get("csrfDisabled"));
|
||||
}
|
||||
if (settings.containsKey("loginMethod")) {
|
||||
GeneralUtils.saveKeyToSettings("security.loginMethod", settings.get("loginMethod"));
|
||||
applicationProperties
|
||||
|
||||
-2
@@ -31,12 +31,10 @@ import stirling.software.common.model.api.PDFFile;
|
||||
import stirling.software.common.service.JobOwnershipService;
|
||||
import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.WebResponseUtils;
|
||||
import stirling.software.proprietary.security.config.PremiumEndpoint;
|
||||
|
||||
@Slf4j
|
||||
@ConvertApi
|
||||
@RequiredArgsConstructor
|
||||
@PremiumEndpoint
|
||||
public class ConvertPdfJsonController {
|
||||
|
||||
private final PdfJsonConversionService pdfJsonConversionService;
|
||||
+6
-7
@@ -4,8 +4,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -13,6 +11,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
@Controller
|
||||
@@ -63,9 +62,10 @@ public class ReactRoutingController {
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping(value = {"/", "/index.html"}, produces = MediaType.TEXT_HTML_VALUE)
|
||||
public ResponseEntity<String> serveIndexHtml(HttpServletRequest request)
|
||||
throws IOException {
|
||||
@GetMapping(
|
||||
value = {"/", "/index.html"},
|
||||
produces = MediaType.TEXT_HTML_VALUE)
|
||||
public ResponseEntity<String> serveIndexHtml(HttpServletRequest request) throws IOException {
|
||||
if (indexHtmlExists && cachedIndexHtml != null) {
|
||||
return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(cachedIndexHtml);
|
||||
}
|
||||
@@ -75,8 +75,7 @@ public class ReactRoutingController {
|
||||
|
||||
@GetMapping(
|
||||
"/{path:^(?!api|static|robots\\.txt|favicon\\.ico|manifest.*\\.json|pipeline|pdfjs|pdfjs-legacy|fonts|images|files|css|js|assets|locales|modern-logo|classic-logo|Login|og_images|samples)[^\\.]*$}")
|
||||
public ResponseEntity<String> forwardRootPaths(HttpServletRequest request)
|
||||
throws IOException {
|
||||
public ResponseEntity<String> forwardRootPaths(HttpServletRequest request) throws IOException {
|
||||
return serveIndexHtml(request);
|
||||
}
|
||||
|
||||
|
||||
+64
-2
@@ -33,8 +33,12 @@ public class PdfJsonFallbackFontService {
|
||||
public static final String FALLBACK_FONT_CJK_ID = "fallback-noto-cjk";
|
||||
public static final String FALLBACK_FONT_JP_ID = "fallback-noto-jp";
|
||||
public static final String FALLBACK_FONT_KR_ID = "fallback-noto-korean";
|
||||
public static final String FALLBACK_FONT_TC_ID = "fallback-noto-tc";
|
||||
public static final String FALLBACK_FONT_AR_ID = "fallback-noto-arabic";
|
||||
public static final String FALLBACK_FONT_TH_ID = "fallback-noto-thai";
|
||||
public static final String FALLBACK_FONT_DEVANAGARI_ID = "fallback-noto-devanagari";
|
||||
public static final String FALLBACK_FONT_MALAYALAM_ID = "fallback-noto-malayalam";
|
||||
public static final String FALLBACK_FONT_TIBETAN_ID = "fallback-noto-tibetan";
|
||||
|
||||
// Font name aliases map PDF font names to available fallback fonts
|
||||
// This provides better visual consistency when editing PDFs
|
||||
@@ -59,6 +63,22 @@ public class PdfJsonFallbackFontService {
|
||||
Map.entry("dejavuserif", "fallback-dejavu-serif"),
|
||||
Map.entry("dejavumono", "fallback-dejavu-mono"),
|
||||
Map.entry("dejavusansmono", "fallback-dejavu-mono"),
|
||||
// Traditional Chinese fonts (Taiwan, Hong Kong, Macau)
|
||||
Map.entry("mingliu", "fallback-noto-tc"),
|
||||
Map.entry("pmingliu", "fallback-noto-tc"),
|
||||
Map.entry("microsoftjhenghei", "fallback-noto-tc"),
|
||||
Map.entry("jhenghei", "fallback-noto-tc"),
|
||||
Map.entry("kaiti", "fallback-noto-tc"),
|
||||
Map.entry("kaiu", "fallback-noto-tc"),
|
||||
Map.entry("dfkaib5", "fallback-noto-tc"),
|
||||
Map.entry("dfkai", "fallback-noto-tc"),
|
||||
// Simplified Chinese fonts (Mainland China) - more common
|
||||
Map.entry("simsun", "fallback-noto-cjk"),
|
||||
Map.entry("simhei", "fallback-noto-cjk"),
|
||||
Map.entry("microsoftyahei", "fallback-noto-cjk"),
|
||||
Map.entry("yahei", "fallback-noto-cjk"),
|
||||
Map.entry("songti", "fallback-noto-cjk"),
|
||||
Map.entry("heiti", "fallback-noto-cjk"),
|
||||
// Noto Sans - Google's universal font (use as last resort generic fallback)
|
||||
Map.entry("noto", "fallback-noto-sans"),
|
||||
Map.entry("notosans", "fallback-noto-sans"));
|
||||
@@ -83,6 +103,12 @@ public class PdfJsonFallbackFontService {
|
||||
"classpath:/static/fonts/NotoSansKR-Regular.ttf",
|
||||
"NotoSansKR-Regular",
|
||||
"ttf")),
|
||||
Map.entry(
|
||||
FALLBACK_FONT_TC_ID,
|
||||
new FallbackFontSpec(
|
||||
"classpath:/static/fonts/NotoSansTC-Regular.ttf",
|
||||
"NotoSansTC-Regular",
|
||||
"ttf")),
|
||||
Map.entry(
|
||||
FALLBACK_FONT_AR_ID,
|
||||
new FallbackFontSpec(
|
||||
@@ -95,6 +121,24 @@ public class PdfJsonFallbackFontService {
|
||||
"classpath:/static/fonts/NotoSansThai-Regular.ttf",
|
||||
"NotoSansThai-Regular",
|
||||
"ttf")),
|
||||
Map.entry(
|
||||
FALLBACK_FONT_DEVANAGARI_ID,
|
||||
new FallbackFontSpec(
|
||||
"classpath:/static/fonts/NotoSansDevanagari-Regular.ttf",
|
||||
"NotoSansDevanagari-Regular",
|
||||
"ttf")),
|
||||
Map.entry(
|
||||
FALLBACK_FONT_MALAYALAM_ID,
|
||||
new FallbackFontSpec(
|
||||
"classpath:/static/fonts/NotoSansMalayalam-Regular.ttf",
|
||||
"NotoSansMalayalam-Regular",
|
||||
"ttf")),
|
||||
Map.entry(
|
||||
FALLBACK_FONT_TIBETAN_ID,
|
||||
new FallbackFontSpec(
|
||||
"classpath:/static/fonts/NotoSerifTibetan-Regular.ttf",
|
||||
"NotoSerifTibetan-Regular",
|
||||
"ttf")),
|
||||
// Liberation Sans family
|
||||
Map.entry(
|
||||
"fallback-liberation-sans",
|
||||
@@ -484,6 +528,20 @@ public class PdfJsonFallbackFontService {
|
||||
*/
|
||||
public String resolveFallbackFontId(int codePoint) {
|
||||
Character.UnicodeBlock block = Character.UnicodeBlock.of(codePoint);
|
||||
|
||||
// Bopomofo is primarily used in Taiwan for Traditional Chinese phonetic annotation
|
||||
if (block == Character.UnicodeBlock.BOPOMOFO
|
||||
|| block == Character.UnicodeBlock.BOPOMOFO_EXTENDED) {
|
||||
return FALLBACK_FONT_TC_ID;
|
||||
}
|
||||
|
||||
// Compatibility ideographs are primarily used by Traditional Chinese encodings (e.g., Big5,
|
||||
// HKSCS) so prefer the Traditional Chinese fallback here.
|
||||
if (block == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
|
||||
|| block == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT) {
|
||||
return FALLBACK_FONT_TC_ID;
|
||||
}
|
||||
|
||||
if (block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
|
||||
|| block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
|
||||
|| block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B
|
||||
@@ -492,19 +550,23 @@ public class PdfJsonFallbackFontService {
|
||||
|| block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_E
|
||||
|| block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_F
|
||||
|| block == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
|
||||
|| block == Character.UnicodeBlock.BOPOMOFO
|
||||
|| block == Character.UnicodeBlock.BOPOMOFO_EXTENDED
|
||||
|| block == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) {
|
||||
return FALLBACK_FONT_CJK_ID;
|
||||
}
|
||||
|
||||
Character.UnicodeScript script = Character.UnicodeScript.of(codePoint);
|
||||
return switch (script) {
|
||||
// HAN script is used by both Simplified and Traditional Chinese
|
||||
// Default to Simplified (mainland China, 1.4B speakers) as it's more common
|
||||
// Traditional Chinese PDFs are detected via font name aliases (MingLiU, PMingLiU, etc.)
|
||||
case HAN -> FALLBACK_FONT_CJK_ID;
|
||||
case HIRAGANA, KATAKANA -> FALLBACK_FONT_JP_ID;
|
||||
case HANGUL -> FALLBACK_FONT_KR_ID;
|
||||
case ARABIC -> FALLBACK_FONT_AR_ID;
|
||||
case THAI -> FALLBACK_FONT_TH_ID;
|
||||
case DEVANAGARI -> FALLBACK_FONT_DEVANAGARI_ID;
|
||||
case MALAYALAM -> FALLBACK_FONT_MALAYALAM_ID;
|
||||
case TIBETAN -> FALLBACK_FONT_TIBETAN_ID;
|
||||
default -> FALLBACK_FONT_ID;
|
||||
};
|
||||
}
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
security:
|
||||
enableLogin: true # set to 'true' to enable login
|
||||
csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production)
|
||||
loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1
|
||||
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
|
||||
loginMethod: all # Accepts values like 'all' and 'normal'(only Login with Username/Password), 'oauth2'(only Login with OAuth2) or 'saml2'(only Login with SAML2)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4
-50
@@ -1,7 +1,6 @@
|
||||
package stirling.software.proprietary.security.configuration;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -25,8 +24,6 @@ import org.springframework.security.saml2.provider.service.web.authentication.Op
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
|
||||
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
|
||||
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
@@ -47,7 +44,6 @@ import stirling.software.proprietary.security.database.repository.PersistentLogi
|
||||
import stirling.software.proprietary.security.filter.IPRateLimitingFilter;
|
||||
import stirling.software.proprietary.security.filter.JwtAuthenticationFilter;
|
||||
import stirling.software.proprietary.security.filter.UserAuthenticationFilter;
|
||||
import stirling.software.proprietary.security.model.User;
|
||||
import stirling.software.proprietary.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
||||
import stirling.software.proprietary.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
||||
import stirling.software.proprietary.security.saml2.CustomSaml2AuthenticationFailureHandler;
|
||||
@@ -198,9 +194,7 @@ public class SecurityConfiguration {
|
||||
http.cors(cors -> cors.disable());
|
||||
}
|
||||
|
||||
if (securityProperties.getCsrfDisabled() || !loginEnabledValue) {
|
||||
http.csrf(CsrfConfigurer::disable);
|
||||
}
|
||||
http.csrf(CsrfConfigurer::disable);
|
||||
|
||||
if (loginEnabledValue) {
|
||||
boolean v2Enabled = appConfig.v2Enabled();
|
||||
@@ -210,48 +204,6 @@ public class SecurityConfiguration {
|
||||
.addFilterBefore(rateLimitingFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
.addFilterBefore(jwtAuthenticationFilter, UserAuthenticationFilter.class);
|
||||
|
||||
if (!securityProperties.getCsrfDisabled()) {
|
||||
CookieCsrfTokenRepository cookieRepo =
|
||||
CookieCsrfTokenRepository.withHttpOnlyFalse();
|
||||
CsrfTokenRequestAttributeHandler requestHandler =
|
||||
new CsrfTokenRequestAttributeHandler();
|
||||
requestHandler.setCsrfRequestAttributeName(null);
|
||||
http.csrf(
|
||||
csrf ->
|
||||
csrf.ignoringRequestMatchers(
|
||||
request -> {
|
||||
String uri = request.getRequestURI();
|
||||
|
||||
// Ignore CSRF for auth endpoints
|
||||
if (uri.startsWith("/api/v1/auth/")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String apiKey = request.getHeader("X-API-KEY");
|
||||
// If there's no API key, don't ignore CSRF
|
||||
// (return false)
|
||||
if (apiKey == null || apiKey.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// Validate API key using existing UserService
|
||||
try {
|
||||
Optional<User> user =
|
||||
userService.getUserByApiKey(apiKey);
|
||||
// If API key is valid, ignore CSRF (return
|
||||
// true)
|
||||
// If API key is invalid, don't ignore CSRF
|
||||
// (return false)
|
||||
return user.isPresent();
|
||||
} catch (Exception e) {
|
||||
// If there's any error validating the API
|
||||
// key, don't ignore CSRF
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.csrfTokenRepository(cookieRepo)
|
||||
.csrfTokenRequestHandler(requestHandler));
|
||||
}
|
||||
|
||||
http.sessionManagement(
|
||||
sessionManagement -> {
|
||||
if (v2Enabled) {
|
||||
@@ -331,7 +283,9 @@ public class SecurityConfiguration {
|
||||
formLogin ->
|
||||
formLogin
|
||||
.loginPage("/login") // Redirect here when unauthenticated
|
||||
.loginProcessingUrl("/perform_login") // Process form posts here (not /login)
|
||||
.loginProcessingUrl(
|
||||
"/perform_login") // Process form posts here (not
|
||||
// /login)
|
||||
.successHandler(
|
||||
new CustomAuthenticationSuccessHandler(
|
||||
loginAttemptService,
|
||||
|
||||
+8
@@ -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;
|
||||
}
|
||||
|
||||
+20
-4
@@ -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();
|
||||
|
||||
+11
-5
@@ -67,19 +67,25 @@ public class CustomSaml2AuthenticationSuccessHandler
|
||||
|
||||
boolean userExists = userService.usernameExistsIgnoreCase(username);
|
||||
|
||||
// Check if user is eligible for SAML (grandfathered or system has paid license)
|
||||
// Check if user is eligible for SAML (grandfathered or system has ENTERPRISE license)
|
||||
if (userExists) {
|
||||
stirling.software.proprietary.security.model.User user =
|
||||
userService.findByUsernameIgnoreCase(username).orElse(null);
|
||||
|
||||
if (user != null && !licenseSettingsService.isOAuthEligible(user)) {
|
||||
// User is not grandfathered and no paid license - block SAML login
|
||||
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.isOAuthEligible(null)) {
|
||||
// No existing user and no paid license -> block auto creation
|
||||
} 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;
|
||||
|
||||
+82
-6
@@ -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;
|
||||
|
||||
@@ -343,17 +344,76 @@ public class UserLicenseSettingsService {
|
||||
* @param user The user to check
|
||||
* @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() : "<new user>";
|
||||
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;
|
||||
}
|
||||
|
||||
// Users can use OAuth/SAML only if system has ENTERPRISE license
|
||||
boolean hasEnterpriseLicense = hasEnterpriseLicense();
|
||||
log.debug("OAuth eligibility check: hasEnterpriseLicense={}", hasEnterpriseLicense);
|
||||
return hasEnterpriseLicense;
|
||||
// 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.info(
|
||||
"OAuth eligibility result: hasPaidLicense={}, user={}, eligible={}",
|
||||
hasPaid,
|
||||
username,
|
||||
hasPaid);
|
||||
return hasPaid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a user is eligible to use SAML authentication.
|
||||
*
|
||||
* <p>A user is eligible if:
|
||||
*
|
||||
* <ul>
|
||||
* <li>They are grandfathered for OAuth (existing user before policy change), OR
|
||||
* <li>The system has an ENTERPRISE license (SAML is enterprise-only)
|
||||
* </ul>
|
||||
*
|
||||
* @param user The user to check
|
||||
* @return true if the user can use SAML
|
||||
*/
|
||||
public boolean isSamlEligible(User user) {
|
||||
String username = (user != null) ? user.getUsername() : "<new user>";
|
||||
log.info("SAML2 eligibility check for user: {}", username);
|
||||
|
||||
// Grandfathered users always have SAML access
|
||||
if (user != null && user.isOauthGrandfathered()) {
|
||||
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.info(
|
||||
"SAML2 eligibility result: hasEnterpriseLicense={}, user={}, eligible={}",
|
||||
hasEnterprise,
|
||||
username,
|
||||
hasEnterprise);
|
||||
return hasEnterprise;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -495,8 +555,12 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -510,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;
|
||||
}
|
||||
}
|
||||
|
||||
+162
@@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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"
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>This test validates the logic that was changed in OAuth2Configuration.java line 220:
|
||||
*
|
||||
* <pre>
|
||||
* // BEFORE (bug):
|
||||
* .redirectUri(REDIRECT_URI_PATH + "oidc") // Always "oidc"
|
||||
*
|
||||
* // AFTER (fix):
|
||||
* .redirectUri(REDIRECT_URI_PATH + name) // Dynamic provider name
|
||||
* </pre>
|
||||
*/
|
||||
@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.
|
||||
*
|
||||
* <p>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
|
||||
*
|
||||
* <p>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");
|
||||
}
|
||||
}
|
||||
+218
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ repositories {
|
||||
|
||||
allprojects {
|
||||
group = 'stirling.software'
|
||||
version = '2.0.3'
|
||||
version = '2.1.1'
|
||||
|
||||
configurations.configureEach {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
|
||||
Generated
+41
-16
@@ -456,6 +456,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -499,6 +500,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -579,6 +581,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.5.0.tgz",
|
||||
"integrity": "sha512-Yrh9XoVaT8cUgzgqpJ7hx5wg6BqQrCFirqqlSwVb+Ly9oNn4fZbR9GycIWmzJOU5XBnaOJjXfQSaDyoNP0woNA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@embedpdf/engines": "1.5.0",
|
||||
"@embedpdf/models": "1.5.0"
|
||||
@@ -678,6 +681,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.5.0.tgz",
|
||||
"integrity": "sha512-p7PTNNaIr4gH3jLwX+eLJe1DeUXgi21kVGN6SRx/pocH8esg4jqoOeD/YiRRZoZnPOiy0jBXVhkPkwSmY7a2hQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
@@ -694,6 +698,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.5.0.tgz",
|
||||
"integrity": "sha512-ckHgTfvkW6c5Ta7Mc+Dl9C2foVnvEpqEJ84wyBnqrU0OWbe/jsiPhyKBVeartMGqNI/kVfaQTXupyrKhekAVmg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
@@ -711,6 +716,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.5.0.tgz",
|
||||
"integrity": "sha512-P4YpIZfaW69etYIjphyaL4cGl2pB14h3OdTE0tRQ2pZYZHFLTvlt4q9B3PVSdhlSrHK5nob7jfLGon2U7xCslg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
@@ -764,6 +770,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.5.0.tgz",
|
||||
"integrity": "sha512-ywwSj0ByrlkvrJIHKRzqxARkOZriki8VJUC+T4MV8fGyF4CzvCRJyKlPktahFz+VxhoodqTh7lBCib68dH+GvA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
@@ -798,6 +805,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.5.0.tgz",
|
||||
"integrity": "sha512-RNmTZCZ8X1mA8cw9M7TMDuhO9GtkOalGha2bBL3En3D1IlDRS7PzNNMSMV7eqT7OQICSTltlpJ8p8Qi5esvL/Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
@@ -834,6 +842,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.5.0.tgz",
|
||||
"integrity": "sha512-zrxLBAZQoPswDuf9q9DrYaQc6B0Ysc2U1hueTjNH/4+ydfl0BFXZkKR63C2e3YmWtXvKjkoIj0GyPzsiBORLUw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
@@ -909,6 +918,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.5.0.tgz",
|
||||
"integrity": "sha512-G8GDyYRhfehw72+r4qKkydnA5+AU8qH67g01Y12b0DzI0VIzymh/05Z4dK8DsY3jyWPXJfw2hlg5+KDHaMBHgQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.5.0"
|
||||
},
|
||||
@@ -1064,6 +1074,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
||||
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
@@ -1107,6 +1118,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
|
||||
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
@@ -2137,6 +2149,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.6.tgz",
|
||||
"integrity": "sha512-paTl+0x+O/QtgMtqVJaG8maD8sfiOdgPmLOyG485FmeGZ1L3KMdEkhxZtmdGlDFsLXhmMGQ57ducT90bvhXX5A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.27.16",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -2187,6 +2200,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.6.tgz",
|
||||
"integrity": "sha512-liHfaWXHAkLjJy+Bkr29UsCwAoDQ/a64WrM67lksx8F0qqyjR5RQH8zVlhuOjdpQnwtlUkE/YiTvbJiPcoI0bw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"react": "^18.x || ^19.x"
|
||||
}
|
||||
@@ -2254,6 +2268,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz",
|
||||
"integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@mui/core-downloads-tracker": "^7.3.5",
|
||||
@@ -3186,6 +3201,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.9.0.tgz",
|
||||
"integrity": "sha512-ggs5k+/0FUJcIgNY08aZTqpBTtbExkJMYMLSMwyucrhtWexVOEY1KJmhBsxf+E/Q15f5rbwBpj+t0t2AW2oCsQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12.16"
|
||||
}
|
||||
@@ -3304,7 +3320,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz",
|
||||
"integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"acorn": "^8.9.0"
|
||||
}
|
||||
@@ -4081,6 +4096,7 @@
|
||||
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
@@ -4409,6 +4425,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@@ -4419,6 +4436,7 @@
|
||||
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
@@ -4488,6 +4506,7 @@
|
||||
"integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.46.3",
|
||||
"@typescript-eslint/types": "8.46.3",
|
||||
@@ -5201,7 +5220,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz",
|
||||
"integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.24"
|
||||
}
|
||||
@@ -5211,7 +5229,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.24.tgz",
|
||||
"integrity": "sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.24",
|
||||
"@vue/shared": "3.5.24"
|
||||
@@ -5222,7 +5239,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz",
|
||||
"integrity": "sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.24",
|
||||
"@vue/runtime-core": "3.5.24",
|
||||
@@ -5235,7 +5251,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.24.tgz",
|
||||
"integrity": "sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.5.24",
|
||||
"@vue/shared": "3.5.24"
|
||||
@@ -5262,6 +5277,7 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -5669,7 +5685,6 @@
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@@ -5946,6 +5961,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.19",
|
||||
"caniuse-lite": "^1.0.30001751",
|
||||
@@ -6993,7 +7009,8 @@
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1521046.tgz",
|
||||
"integrity": "sha512-vhE6eymDQSKWUXwwA37NtTTVEzjtGVfDr3pRbsWEQ5onH/Snp2c+2xZHWJJawG/0hCCJLRGt4xVtEVUVILol4w==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/dezalgo": {
|
||||
"version": "1.0.4",
|
||||
@@ -7388,6 +7405,7 @@
|
||||
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -7558,6 +7576,7 @@
|
||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.9",
|
||||
@@ -7724,8 +7743,7 @@
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
||||
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "10.4.0",
|
||||
@@ -7790,7 +7808,6 @@
|
||||
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.2.tgz",
|
||||
"integrity": "sha512-DgvlIQeowRNyvLPWW4PT7Gu13WznY288Du086E751mwwbsgr29ytBiYeLzAGIo0qk3Ujob0SDk8TiSaM5WQzNg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||
}
|
||||
@@ -8881,6 +8898,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6"
|
||||
},
|
||||
@@ -9357,7 +9375,6 @@
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
||||
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.6"
|
||||
}
|
||||
@@ -9678,6 +9695,7 @@
|
||||
"integrity": "sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@acemir/cssom": "^0.9.19",
|
||||
"@asamuzakjp/dom-selector": "^6.7.3",
|
||||
@@ -10264,8 +10282,7 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
|
||||
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
@@ -11411,6 +11428,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -11690,6 +11708,7 @@
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz",
|
||||
"integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
@@ -12072,6 +12091,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -12081,6 +12101,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
||||
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -13592,7 +13613,6 @@
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
|
||||
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@@ -13801,6 +13821,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -14102,6 +14123,7 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -14183,6 +14205,7 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"napi-postinstall": "^0.3.0"
|
||||
},
|
||||
@@ -14387,6 +14410,7 @@
|
||||
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -14538,6 +14562,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -14551,6 +14576,7 @@
|
||||
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/expect": "3.2.4",
|
||||
@@ -15162,8 +15188,7 @@
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
|
||||
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
|
||||
@@ -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 = "ترخيصنا يسمح حتى <strong>{{freeTierLimit}}</strong> مستخدمين مجاناً لكل خادم. لديك <strong>{{overLimitUserCopy}}</strong> مستخدمي Stirling. للمتابعة دون انقطاع، ارقَ إلى خطة خادم Stirling - <strong>مقاعد غير محدودة</strong>، تحرير نصوص PDF، وتحكم إداري كامل مقابل $99/خادم/شهرياً."
|
||||
freeBody = "ترخيص <strong>Open-Core</strong> لدينا يسمح حتى <strong>{{freeTierLimit}}</strong> مستخدمين مجاناً لكل خادم. للتوسع بسلاسة والحصول على وصول مبكر إلى <strong>أداة تحرير نصوص PDF</strong> الجديدة، نوصي بخطة خادم Stirling - تحرير كامل و<strong>مقاعد غير محدودة</strong> مقابل $99/خادم/شهرياً."
|
||||
freeBody = "يتيح ترخيصنا <strong>Open-Core</strong> ما يصل إلى <strong>{{freeTierLimit}}</strong> مستخدمًا مجانًا لكل خادم. للتوسع دون انقطاع، نوصي بخطة Stirling Server - <strong>مقاعد غير محدودة</strong> و<strong>دعم SSO</strong> مقابل $99/server/mo."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "تنزيل"
|
||||
|
||||
@@ -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 <strong>{{freeTierLimit}}</strong> istifadəçiyə icazə verir. Sizdə <strong>{{overLimitUserCopy}}</strong> Stirling istifadəçisi var. Fasiləsiz davam etmək üçün Stirling Server planına yüksəldin - <strong>limitsiz yerlər</strong>, PDF mətn redaktəsi və tam admin nəzarəti cəmi $99/server/ay."
|
||||
freeBody = "Bizim <strong>Open-Core</strong> lisenziyası hər server üçün pulsuz olaraq maksimum <strong>{{freeTierLimit}}</strong> istifadəçiyə icazə verir. Fasiləsiz miqyaslanmaq və yeni <strong>PDF mətn redaktəsi alətimizə</strong> erkən çıxış əldə etmək üçün Stirling Server planını tövsiyə edirik — tam redaktə və <strong>limitsiz yerlər</strong> $99/server/ay."
|
||||
freeBody = "Bizim <strong>Open-Core</strong> lisenziyalaşdırmamız hər server üçün pulsuz olaraq ən çox <strong>{{freeTierLimit}}</strong> istifadəçiyə icazə verir. Fasiləsiz miqyaslama üçün Stirling Server planını tövsiyə edirik - <strong>limitsiz yerlər</strong> və <strong>SSO dəstəyi</strong> $99/server/ay."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Yüklə"
|
||||
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "Надградете сега →"
|
||||
freeTitle = "Лиценз за сървър"
|
||||
overLimitTitle = "Необходим е лиценз за сървър"
|
||||
overLimitBody = "Нашият лиценз позволява до <strong>{{freeTierLimit}}</strong> безплатни потребители на сървър. Имате <strong>{{overLimitUserCopy}}</strong> потребители на Stirling. За да продължите без прекъсвания, надградете до плана Stirling Server – <strong>неограничени места</strong>, редакция на PDF текст и пълен админ контрол за $99/сървър/месец."
|
||||
freeBody = "Нашият <strong>Open-Core</strong> лиценз позволява до <strong>{{freeTierLimit}}</strong> безплатни потребители на сървър. За да мащабирате без прекъсвания и да получите ранен достъп до нашия нов <strong>инструмент за редакция на PDF текст</strong>, препоръчваме плана Stirling Server – пълно редактиране и <strong>неограничени места</strong> за $99/сървър/месец."
|
||||
freeBody = "Нашият лицензен модел <strong>Open-Core</strong> позволява до <strong>{{freeTierLimit}}</strong> потребители безплатно на сървър. За безпрепятствено мащабиране препоръчваме плана Stirling Server - <strong>неограничени места</strong> и <strong>поддръжка на SSO</strong> за $99/server/mo."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Изтегляне"
|
||||
|
||||
@@ -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 <strong>{{freeTierLimit}}</strong> usuaris gratuïts per servidor. Tens <strong>{{overLimitUserCopy}}</strong> usuaris de Stirling. Per continuar sense interrupcions, actualitza al pla Stirling Server: <strong>seients il·limitats</strong>, edició de text de PDF i control d'administració complet per 99 $/servidor/mes."
|
||||
freeBody = "La nostra llicència <strong>Open-Core</strong> permet fins a <strong>{{freeTierLimit}}</strong> usuaris gratuïts per servidor. Per escalar sense interrupcions i obtenir accés anticipat a la nova <strong>eina d'edició de text PDF</strong>, recomanem el pla Stirling Server: edició completa i <strong>seients il·limitats</strong> per 99 $/servidor/mes."
|
||||
freeBody = "La nostra llicència <strong>Open-Core</strong> permet fins a <strong>{{freeTierLimit}}</strong> usuaris gratuïts per servidor. Per escalar sense interrupcions, recomanem el pla Stirling Server - <strong>places il·limitades</strong> i <strong>suport SSO</strong> 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"
|
||||
|
||||
@@ -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ž <strong>{{freeTierLimit}}</strong> uživatelů zdarma na server. Máte <strong>{{overLimitUserCopy}}</strong> uživatelů Stirling. Pro nepřerušené používání přejděte na plán Stirling Server – <strong>neomezený počet míst</strong>, úpravy textu PDF a plná správa za 99 $/server/měsíc."
|
||||
freeBody = "Naše licencování <strong>Open-Core</strong> umožňuje až <strong>{{freeTierLimit}}</strong> uživatelů zdarma na server. Pro nepřerušený růst a přednostní přístup k našemu novému <strong>nástroji pro úpravu textu PDF</strong> doporučujeme plán Stirling Server – plné úpravy a <strong>neomezený počet míst</strong> za 99 $/server/měsíc."
|
||||
freeBody = "Naše licencování <strong>Open-Core</strong> umožňuje až <strong>{{freeTierLimit}}</strong> uživatelů zdarma na server. Pro nepřerušované škálování doporučujeme plán Stirling Server - <strong>neomezený počet míst</strong> a <strong>podpora SSO</strong> za $99/server/měs."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Stáhnout"
|
||||
|
||||
@@ -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 <strong>{{freeTierLimit}}</strong> brugere gratis pr. server. Du har <strong>{{overLimitUserCopy}}</strong> Stirling-brugere. For at fortsætte uden afbrydelser skal du opgradere til Stirling Server-abonnementet – <strong>ubegrænsede pladser</strong>, PDF-tekstredigering og fuld admin-kontrol for $99/server/md."
|
||||
freeBody = "Vores <strong>Open-Core</strong>-licens tillader op til <strong>{{freeTierLimit}}</strong> brugere gratis pr. server. For at skalere uden afbrydelser og få tidlig adgang til vores nye <strong>PDF-tekstredigeringsværktøj</strong> anbefaler vi Stirling Server-planen – fuld redigering og <strong>ubegrænsede pladser</strong> for $99/server/md."
|
||||
freeBody = "Vores <strong>Open-Core</strong>-licens tillader op til <strong>{{freeTierLimit}}</strong> brugere gratis pr. server. For at skalere uden afbrydelser anbefaler vi Stirling Server-planen – <strong>ubegrænsede pladser</strong> og <strong>SSO-understøttelse</strong> for $99/server/md."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Download"
|
||||
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "Jetzt upgraden →"
|
||||
freeTitle = "Server-Lizenz"
|
||||
overLimitTitle = "Server-Lizenz erforderlich"
|
||||
overLimitBody = "Unsere Lizenz erlaubt bis zu <strong>{{freeTierLimit}}</strong> Nutzer pro Server kostenlos. Sie haben <strong>{{overLimitUserCopy}}</strong> Stirling-Nutzer. Um ohne Unterbrechung fortzufahren, upgraden Sie auf den Stirling-Server-Plan – <strong>unbegrenzte Plätze</strong>, PDF-Textbearbeitung und volle Admin-Kontrolle für $99/Server/Monat."
|
||||
freeBody = "Unsere <strong>Open-Core</strong>-Lizenz erlaubt bis zu <strong>{{freeTierLimit}}</strong> Nutzer pro Server kostenlos. Für unterbrechungsfreies Skalieren und frühen Zugriff auf unser neues <strong>PDF-Textbearbeitungs-Tool</strong> empfehlen wir den Stirling-Server-Plan – volle Bearbeitung und <strong>unbegrenzte Plätze</strong> für $99/Server/Monat."
|
||||
freeBody = "Unsere <strong>Open-Core</strong>-Lizenz erlaubt bis zu <strong>{{freeTierLimit}}</strong> Nutzern pro Server kostenlos. Um unterbrechungsfrei zu skalieren, empfehlen wir den Stirling Server-Plan - <strong>unbegrenzte Plätze</strong> und <strong>SSO-Unterstützung</strong> für $99/Server/Monat."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Download"
|
||||
|
||||
@@ -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 = "Η αδειοδότηση μας επιτρέπει έως <strong>{{freeTierLimit}}</strong> χρήστες δωρεάν ανά διακομιστή. Έχετε <strong>{{overLimitUserCopy}}</strong> χρήστες Stirling. Για να συνεχίσετε χωρίς διακοπές, αναβαθμίστε στο πλάνο Stirling Server - <strong>απεριόριστες θέσεις</strong>, επεξεργασία κειμένου PDF και πλήρης έλεγχος διαχειριστή για $99/server/μήνα."
|
||||
freeBody = "Η αδειοδότηση <strong>Open-Core</strong> μας επιτρέπει έως <strong>{{freeTierLimit}}</strong> χρήστες δωρεάν ανά διακομιστή. Για απρόσκοπτη κλιμάκωση και έγκαιρη πρόσβαση στο νέο <strong>εργαλείο επεξεργασίας κειμένου PDF</strong>, προτείνουμε το πλάνο Stirling Server - πλήρης επεξεργασία και <strong>απεριόριστες θέσεις</strong> για $99/server/μήνα."
|
||||
freeBody = "Οι άδειες χρήσης <strong>Open-Core</strong> επιτρέπουν έως και <strong>{{freeTierLimit}}</strong> χρήστες δωρεάν ανά διακομιστή. Για απρόσκοπτη κλιμάκωση, προτείνουμε το πλάνο Stirling Server - <strong>απεριόριστες θέσεις</strong> και <strong>υποστήριξη SSO</strong> με $99/διακομιστή/μήνα."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Λήψη"
|
||||
|
||||
@@ -3036,6 +3036,91 @@ title = "Get Info on PDF"
|
||||
header = "Get Info on PDF"
|
||||
submit = "Get Info"
|
||||
downloadJson = "Download JSON"
|
||||
processing = "Extracting information..."
|
||||
results = "Results"
|
||||
noResults = "Run the tool to generate a report."
|
||||
downloads = "Downloads"
|
||||
noneDetected = "None detected"
|
||||
indexTitle = "Index"
|
||||
|
||||
[getPdfInfo.report]
|
||||
entryLabel = "Full information summary"
|
||||
shortTitle = "PDF Information"
|
||||
|
||||
[getPdfInfo.sections]
|
||||
metadata = "Metadata"
|
||||
formFields = "Form Fields"
|
||||
basicInfo = "Basic Info"
|
||||
documentInfo = "Document Info"
|
||||
compliance = "Compliance"
|
||||
encryption = "Encryption"
|
||||
permissions = "Permissions"
|
||||
other = "Other"
|
||||
perPageInfo = "Per Page Info"
|
||||
tableOfContents = "Table of Contents"
|
||||
|
||||
[getPdfInfo.other]
|
||||
attachments = "Attachments"
|
||||
embeddedFiles = "Embedded Files"
|
||||
javaScript = "JavaScript"
|
||||
layers = "Layers"
|
||||
structureTree = "StructureTree"
|
||||
xmp = "XMPMetadata"
|
||||
|
||||
[getPdfInfo.perPage]
|
||||
size = "Size"
|
||||
annotations = "Annotations"
|
||||
images = "Images"
|
||||
links = "Links"
|
||||
fonts = "Fonts"
|
||||
xobjects = "XObject Counts"
|
||||
multimedia = "Multimedia"
|
||||
|
||||
[getPdfInfo.summary]
|
||||
pages = "Pages"
|
||||
fileSize = "File Size"
|
||||
pdfVersion = "PDF Version"
|
||||
language = "Language"
|
||||
title = "PDF Summary"
|
||||
author = "Author"
|
||||
created = "Created"
|
||||
modified = "Modified"
|
||||
permsAll = "All Permissions Allowed"
|
||||
permsRestricted = "{{count}} restrictions"
|
||||
permsMixed = "Some permissions restricted"
|
||||
hasCompliance = "Has compliance standards"
|
||||
noCompliance = "No Compliance Standards"
|
||||
basic = "Basic Information"
|
||||
documentInfo = "Document Information"
|
||||
securityTitle = "Security Status"
|
||||
technical = "Technical"
|
||||
overviewTitle = "PDF Overview"
|
||||
|
||||
[getPdfInfo.summary.security]
|
||||
encrypted = "Encrypted PDF - Password protection present"
|
||||
unencrypted = "Unencrypted PDF - No password protection"
|
||||
|
||||
[getPdfInfo.summary.tech]
|
||||
images = "Images"
|
||||
fonts = "Fonts"
|
||||
formFields = "Form Fields"
|
||||
embeddedFiles = "Embedded Files"
|
||||
javaScript = "JavaScript"
|
||||
layers = "Layers"
|
||||
bookmarks = "Bookmarks"
|
||||
multimedia = "Multimedia"
|
||||
|
||||
[getPdfInfo.summary.overview]
|
||||
untitled = "an untitled document"
|
||||
unknown = "Unknown Author"
|
||||
text = "This is a {{pages}}-page PDF titled {{title}} created by {{author}} (PDF version {{version}})."
|
||||
|
||||
[getPdfInfo.error]
|
||||
partial = "Some files could not be processed."
|
||||
unexpected = "Unexpected error during extraction."
|
||||
|
||||
[getPdfInfo.status]
|
||||
complete = "Extraction complete"
|
||||
|
||||
[extractPage]
|
||||
tags = "extract"
|
||||
@@ -3454,8 +3539,8 @@ signinTitle = "Please sign in"
|
||||
ssoSignIn = "Login via Single Sign-on"
|
||||
oAuth2AutoCreateDisabled = "OAUTH2 Auto-Create User Disabled"
|
||||
oAuth2AdminBlockedUser = "Registration or logging in of non-registered users is currently blocked. Please contact the administrator."
|
||||
oAuth2RequiresLicense = "OAuth/SSO login requires a paid license (Server or Enterprise). Please contact the administrator to upgrade your plan."
|
||||
saml2RequiresLicense = "SAML login requires a paid license (Server or Enterprise). Please contact the administrator to upgrade your plan."
|
||||
oAuth2RequiresLicense = "OAuth/SSO login requires a Server or Enterprise license. Please contact the administrator to upgrade your plan."
|
||||
saml2RequiresLicense = "SAML login requires an Enterprise license. Please contact the administrator to upgrade your plan."
|
||||
maxUsersReached = "Maximum number of users reached for your current license. Please contact the administrator to upgrade your plan or add more seats."
|
||||
oauth2RequestNotFound = "Authorization request not found"
|
||||
oauth2InvalidUserInfoResponse = "Invalid User Info Response"
|
||||
@@ -5178,7 +5263,7 @@ upgrade = "Upgrade now →"
|
||||
freeTitle = "Server License"
|
||||
overLimitTitle = "Server License Needed"
|
||||
overLimitBody = "Our licensing permits up to <strong>{{freeTierLimit}}</strong> users for free per server. You have <strong>{{overLimitUserCopy}}</strong> Stirling users. To continue uninterrupted, upgrade to the Stirling Server plan - <strong>unlimited seats</strong>, PDF text editing, and full admin control for $99/server/mo."
|
||||
freeBody = "Our <strong>Open-Core</strong> licensing permits up to <strong>{{freeTierLimit}}</strong> users for free per server. To scale uninterrupted and get early access to our new <strong>PDF text editing tool</strong>, we recommend the Stirling Server plan - full editing and <strong>unlimited seats</strong> for $99/server/mo."
|
||||
freeBody = "Our <strong>Open-Core</strong> licensing permits up to <strong>{{freeTierLimit}}</strong> users for free per server. To scale uninterrupted, we recommend the Stirling Server plan - <strong>unlimited seats</strong> and <strong>SSO support</strong> for $99/server/mo."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Download"
|
||||
@@ -5816,6 +5901,7 @@ subtitle = "Sign in with your Stirling account"
|
||||
[setup.selfhosted]
|
||||
title = "Sign in to Server"
|
||||
subtitle = "Enter your server credentials"
|
||||
link = "or connect to a self-hosted account"
|
||||
|
||||
[setup.server]
|
||||
title = "Connect to Server"
|
||||
@@ -5834,6 +5920,14 @@ description = "Enter the full URL of your self-hosted Stirling PDF server"
|
||||
emptyUrl = "Please enter a server URL"
|
||||
unreachable = "Could not connect to server"
|
||||
testFailed = "Connection test failed"
|
||||
configFetch = "Failed to fetch server configuration. Please check the URL and try again."
|
||||
|
||||
[setup.server.error.securityDisabled]
|
||||
title = "Login Not Enabled"
|
||||
body = "This server does not have login enabled. To connect to this server, you must enable authentication:"
|
||||
step1 = "Set DOCKER_ENABLE_SECURITY=true in your environment"
|
||||
step2 = "Or set security.enableLogin=true in settings.yml"
|
||||
step3 = "Restart the server"
|
||||
|
||||
[setup.login]
|
||||
title = "Sign In"
|
||||
@@ -5906,6 +6000,7 @@ earlyAccess = "Early Access"
|
||||
reset = "Reset Changes"
|
||||
downloadJson = "Download JSON"
|
||||
generatePdf = "Generate PDF"
|
||||
saveChanges = "Save Changes"
|
||||
|
||||
[pdfTextEditor.options.autoScaleText]
|
||||
title = "Auto-scale text to fit boxes"
|
||||
@@ -5943,6 +6038,8 @@ alpha = "This alpha viewer is still evolving—certain fonts, colours, transpare
|
||||
[pdfTextEditor.empty]
|
||||
title = "No document loaded"
|
||||
subtitle = "Load a PDF or JSON file to begin editing text content."
|
||||
dropzone = "Drag and drop a PDF or JSON file here, or click to browse"
|
||||
dropzoneWithFiles = "Select a file from the Files tab, or drag and drop a PDF or JSON file here, or click to browse"
|
||||
|
||||
[pdfTextEditor.welcomeBanner]
|
||||
title = "Welcome to PDF Text Editor (Early Access)"
|
||||
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "Actualizar ahora →"
|
||||
freeTitle = "Licencia del servidor"
|
||||
overLimitTitle = "Se necesita licencia de servidor"
|
||||
overLimitBody = "Nuestra licencia permite hasta <strong>{{freeTierLimit}}</strong> usuarios gratis por servidor. Tiene <strong>{{overLimitUserCopy}}</strong> usuarios de Stirling. Para continuar sin interrupciones, actualice al plan Stirling Server: <strong>plazas ilimitadas</strong>, edición de texto PDF y control total de administración por 99 $/servidor/mes."
|
||||
freeBody = "Nuestra licencia <strong>Open-Core</strong> permite hasta <strong>{{freeTierLimit}}</strong> usuarios gratis por servidor. Para escalar sin interrupciones y obtener acceso anticipado a nuestra nueva <strong>herramienta de edición de texto PDF</strong>, recomendamos el plan Stirling Server: edición completa y <strong>plazas ilimitadas</strong> por 99 $/servidor/mes."
|
||||
freeBody = "Nuestra licencia <strong>Open-Core</strong> permite hasta <strong>{{freeTierLimit}}</strong> usuarios gratis por servidor. Para escalar sin interrupciones, recomendamos el plan Stirling Server - <strong>plazas ilimitadas</strong> y <strong>soporte SSO</strong> por $99/servidor/mes."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Descargar"
|
||||
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "Eguneratu orain →"
|
||||
freeTitle = "Zerbitzari-lizentzia"
|
||||
overLimitTitle = "Beharrezkoa da zerbitzari-lizentzia"
|
||||
overLimitBody = "Gure lizentziak baimentzen ditu <strong>{{freeTierLimit}}</strong> erabiltzaile doan zerbitzari bakoitzeko. <strong>{{overLimitUserCopy}}</strong> Stirling erabiltzaile dituzu. Jarraitzeko etenik gabe, eguneratu Stirling Server planera - <strong>eserleku mugagabeak</strong>, PDF testu-edizioa, eta admin kontrol osoa $99/zerbitzari/hilean."
|
||||
freeBody = "Gure <strong>Open-Core</strong> lizentziak <strong>{{freeTierLimit}}</strong> erabiltzaile arte baimentzen ditu doan zerbitzari bakoitzeko. Etenik gabe eskalatzeko eta gure <strong>PDF testu-edizio tresna</strong> berrirako sarbide goiztiarra lortzeko, gomendatzen dugu Stirling Server plana - edizio osoa eta <strong>eserleku mugagabeak</strong> $99/zerbitzari/hilean."
|
||||
freeBody = "Gure <strong>Open-Core</strong> lizentziak zerbitzari bakoitzeko doan gehienez <strong>{{freeTierLimit}}</strong> erabiltzaile baimentzen ditu. Etenik gabe eskalatzeko, Stirling Server plana gomendatzen dugu - <strong>eserleku mugagabeak</strong> eta <strong>SSO euskarria</strong> $99/server/mo."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Deskargatu"
|
||||
|
||||
@@ -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 = "مجوز ما تا <strong>{{freeTierLimit}}</strong> کاربر رایگان بهازای هر سرور را مجاز میداند. شما <strong>{{overLimitUserCopy}}</strong> کاربر Stirling دارید. برای ادامه بدون وقفه، به پلن Stirling Server ارتقا دهید - <strong>صندلی نامحدود</strong>، ویرایش متن PDF و کنترل کامل ادمین با 99$ بهازای هر سرور در ماه."
|
||||
freeBody = "مجوز <strong>Open-Core</strong> ما تا <strong>{{freeTierLimit}}</strong> کاربر رایگان بهازای هر سرور را مجاز میداند. برای مقیاسپذیری بدون وقفه و دسترسی زودهنگام به <strong>ابزار ویرایش متن PDF</strong> جدیدمان، پلن Stirling Server را پیشنهاد میکنیم - ویرایش کامل و <strong>صندلی نامحدود</strong> با 99$ بهازای هر سرور در ماه."
|
||||
freeBody = "مجوز <strong>Open-Core</strong> ما بهازای هر سرور اجازهٔ استفادهٔ رایگان برای حداکثر <strong>{{freeTierLimit}}</strong> کاربر را میدهد. برای مقیاسدهی بدون وقفه، طرح Stirling Server را توصیه میکنیم - <strong>تعداد کاربران نامحدود</strong> و <strong>پشتیبانی از SSO</strong> با $99/سرور/ماه."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "دانلود"
|
||||
|
||||
@@ -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’à <strong>{{freeTierLimit}}</strong> utilisateurs gratuits par serveur. Vous avez <strong>{{overLimitUserCopy}}</strong> utilisateurs Stirling. Pour continuer sans interruption, passez au plan Stirling Server — <strong>places illimitées</strong>, édition de texte PDF et contrôle d’administration complet pour 99 $/serveur/mois."
|
||||
freeBody = "Notre licence <strong>Open-Core</strong> autorise jusqu’à <strong>{{freeTierLimit}}</strong> utilisateurs gratuits par serveur. Pour évoluer sans interruption et accéder en avant-première à notre nouvel <strong>outil d’édition de texte PDF</strong>, nous recommandons le plan Stirling Server — édition complète et <strong>places illimitées</strong> pour 99 $/serveur/mois."
|
||||
freeBody = "Notre régime de licence <strong>Open-Core</strong> autorise jusqu'à <strong>{{freeTierLimit}}</strong> utilisateurs gratuitement par serveur. Pour évoluer sans interruption, nous recommandons le forfait Stirling Server - <strong>places illimitées</strong> et <strong>prise en charge du SSO</strong> 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"
|
||||
|
||||
|
||||
@@ -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 <strong>{{freeTierLimit}}</strong> úsáideoir in aisce in aghaidh freastalaí. Tá <strong>{{overLimitUserCopy}}</strong> úsáideoir Stirling agat. Chun leanúint gan bhriseadh, uasghrádaigh go plean Freastalaí Stirling - <strong>suíocháin neamhtheoranta</strong>, eagarthóireacht téacs PDF, agus lánrialú riaracháin ar $99/freastalaí/mí."
|
||||
freeBody = "Ceadaíonn ár gceadúnú <strong>Open-Core</strong> suas le <strong>{{freeTierLimit}}</strong> úsáideoir in aisce in aghaidh freastalaí. Chun méadú gan bhriseadh agus rochtain luath a fháil ar ár <strong>uirlis eagarthóireachta téacs PDF</strong> nua, molaimid Plean Freastalaí Stirling - eagarthóireacht iomlán agus <strong>suíocháin neamhtheoranta</strong> ar $99/freastalaí/mí."
|
||||
freeBody = "Ceadaíonn ár gceadúnú <strong>Open-Core</strong> suas le <strong>{{freeTierLimit}}</strong> úsáideoirí saor in aisce in aghaidh an fhreastalaí. Chun scálú gan bhriseadh, molaimid an plean Stirling Server - <strong>suíocháin neamhtheoranta</strong> agus <strong>tacaíocht SSO</strong> 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"
|
||||
|
||||
@@ -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 = "हमारा लाइसेंसिंग प्रति सर्वर अधिकतम <strong>{{freeTierLimit}}</strong> उपयोगकर्ताओं को मुफ्त अनुमति देता है। आपके पास <strong>{{overLimitUserCopy}}</strong> Stirling उपयोगकर्ता हैं। बिना बाधा के जारी रखने के लिए, Stirling Server प्लान में अपग्रेड करें - <strong>अनलिमिटेड सीट्स</strong>, PDF टेक्स्ट एडिटिंग, और पूर्ण एडमिन नियंत्रण $99/server/mo में।"
|
||||
freeBody = "हमारा <strong>Open-Core</strong> लाइसेंसिंग प्रति सर्वर अधिकतम <strong>{{freeTierLimit}}</strong> उपयोगकर्ताओं को मुफ्त अनुमति देता है। बिना बाधा स्केल करने और हमारे नए <strong>PDF टेक्स्ट एडिटिंग टूल</strong> की प्रारंभिक पहुँच पाने के लिए हम Stirling Server प्लान की सलाह देते हैं - पूर्ण एडिटिंग और <strong>अनलिमिटेड सीट्स</strong> $99/server/mo में।"
|
||||
freeBody = "हमारा <strong>Open-Core</strong> लाइसेंसिंग प्रति सर्वर अधिकतम <strong>{{freeTierLimit}}</strong> उपयोगकर्ताओं को निःशुल्क अनुमति देता है। बिना रुकावट स्केल करने के लिए, हम Stirling Server प्लान की अनुशंसा करते हैं - <strong>असीमित सीटें</strong> और <strong>SSO समर्थन</strong> $99/सर्वर/माह पर।"
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "डाउनलोड"
|
||||
@@ -6005,7 +6005,7 @@ insufficientPermissions = "आपके पास यह क्रिया क
|
||||
[addText]
|
||||
title = "टेक्स्ट जोड़ें"
|
||||
header = "PDFs में टेक्स्ट जोड़ें"
|
||||
tags = "text,annotation,label"
|
||||
tags = "पाठ,टिप्पणी,लेबल"
|
||||
applySignatures = "टेक्स्ट लागू करें"
|
||||
|
||||
[addText.text]
|
||||
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "Nadogradi odmah →"
|
||||
freeTitle = "Poslužiteljska licenca"
|
||||
overLimitTitle = "Potrebna poslužiteljska licenca"
|
||||
overLimitBody = "Naše licenciranje dopušta do <strong>{{freeTierLimit}}</strong> korisnika besplatno po poslužitelju. Imate <strong>{{overLimitUserCopy}}</strong> Stirling korisnika. Za nesmetan nastavak, nadogradite na Stirling Server plan - <strong>neograničena mjesta</strong>, uređivanje teksta u PDF-u i puna admin kontrola za $99/server/mo."
|
||||
freeBody = "Naše <strong>Open-Core</strong> licenciranje dopušta do <strong>{{freeTierLimit}}</strong> korisnika besplatno po poslužitelju. Za nesmetano skaliranje i rani pristup našem novom <strong>alatu za uređivanje teksta u PDF-u</strong>, preporučujemo Stirling Server plan - potpuno uređivanje i <strong>neograničena mjesta</strong> za $99/server/mo."
|
||||
freeBody = "Naše licenciranje <strong>Open-Core</strong> omogućuje do <strong>{{freeTierLimit}}</strong> korisnika besplatno po poslužitelju. Za neometano skaliranje preporučujemo Stirling Server plan - <strong>neograničena mjesta</strong> i <strong>podrška za SSO</strong> za $99/poslužitelj/mj."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Preuzimanje"
|
||||
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "Frissítés most →"
|
||||
freeTitle = "Szerverlicenc"
|
||||
overLimitTitle = "Szerverlicenc szükséges"
|
||||
overLimitBody = "Licencelésünk szerverenként legfeljebb <strong>{{freeTierLimit}}</strong> felhasználót enged ingyen. Önnek <strong>{{overLimitUserCopy}}</strong> Stirling felhasználója van. A zavartalan használathoz váltson a Stirling Server csomagra – <strong>korlátlan hely</strong>, PDF szövegszerkesztés és teljes adminisztrátori vezérlés $99/szerver/hó áron."
|
||||
freeBody = "Az <strong>Open-Core</strong> licencelésünk szerverenként legfeljebb <strong>{{freeTierLimit}}</strong> felhasználót enged ingyen. A zavartalan bővüléshez és az új <strong>PDF szövegszerkesztő eszköz</strong> korai eléréséhez a Stirling Server csomagot ajánljuk – teljes szerkesztés és <strong>korlátlan hely</strong> $99/szerver/hó áron."
|
||||
freeBody = "A <strong>Open-Core</strong> licencünk szerverenként legfeljebb <strong>{{freeTierLimit}}</strong> felhasználót engedélyez ingyenesen. A zökkenőmentes skálázáshoz a Stirling Server csomagot ajánljuk - <strong>korlátlan felhasználó</strong> és <strong>SSO támogatás</strong> $99/szerver/hó."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Letöltés"
|
||||
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "Upgrade sekarang →"
|
||||
freeTitle = "Lisensi Server"
|
||||
overLimitTitle = "Perlu Lisensi Server"
|
||||
overLimitBody = "Lisensi kami mengizinkan hingga <strong>{{freeTierLimit}}</strong> pengguna gratis per server. Anda memiliki <strong>{{overLimitUserCopy}}</strong> pengguna Stirling. Untuk terus berjalan tanpa gangguan, upgrade ke paket Stirling Server - <strong>kursi tanpa batas</strong>, pengeditan teks PDF, dan kontrol admin penuh seharga $99/server/bulan."
|
||||
freeBody = "Lisensi <strong>Open-Core</strong> kami mengizinkan hingga <strong>{{freeTierLimit}}</strong> pengguna gratis per server. Untuk skala tanpa hambatan dan mendapatkan akses awal ke <strong>alat pengeditan teks PDF</strong> baru kami, kami sarankan paket Stirling Server - pengeditan penuh dan <strong>kursi tanpa batas</strong> seharga $99/server/bulan."
|
||||
freeBody = "Lisensi <strong>Open-Core</strong> kami mengizinkan hingga <strong>{{freeTierLimit}}</strong> pengguna gratis per server. Untuk meningkatkan skala tanpa gangguan, kami merekomendasikan paket Stirling Server - <strong>pengguna tanpa batas</strong> dan <strong>dukungan SSO</strong> 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"
|
||||
|
||||
@@ -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 <strong>{{freeTierLimit}}</strong> utenti gratuiti per server. Hai <strong>{{overLimitUserCopy}}</strong> utenti Stirling. Per continuare senza interruzioni, esegui l'upgrade al piano Stirling Server - <strong>posti illimitati</strong>, modifica del testo PDF e pieno controllo admin a $99/server/mese."
|
||||
freeBody = "La nostra licenza <strong>Open-Core</strong> consente fino a <strong>{{freeTierLimit}}</strong> utenti gratuiti per server. Per scalare senza interruzioni e ottenere accesso anticipato al nuovo <strong>strumento di modifica testo PDF</strong>, consigliamo il piano Stirling Server - modifica completa e <strong>posti illimitati</strong> a $99/server/mese."
|
||||
freeBody = "La nostra licenza <strong>Open-Core</strong> consente fino a <strong>{{freeTierLimit}}</strong> utenti gratuiti per server. Per scalare senza interruzioni, consigliamo il piano Stirling Server - <strong>posti illimitati</strong> e <strong>supporto SSO</strong> a $99/server/mese."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Download"
|
||||
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "今すぐアップグレード →"
|
||||
freeTitle = "サーバーライセンス"
|
||||
overLimitTitle = "サーバーライセンスが必要です"
|
||||
overLimitBody = "当社のライセンスでは、サーバーごとに <strong>{{freeTierLimit}}</strong> ユーザーまで無料です。現在 <strong>{{overLimitUserCopy}}</strong> の Stirling ユーザーがいます。中断なく利用を続けるには、Stirling Server プランにアップグレードしてください - <strong>無制限席数</strong>、PDF テキスト編集、完全な管理機能が $99/サーバー/月 です。"
|
||||
freeBody = "当社の <strong>オープンコア</strong> ライセンスでは、サーバーごとに最大 <strong>{{freeTierLimit}}</strong> ユーザーまで無料です。中断なく拡張し、新しい <strong>PDF テキスト編集ツール</strong> に早期アクセスするには、Stirling Server プランをお勧めします。完全編集と <strong>無制限席数</strong> が $99/サーバー/月 です。"
|
||||
freeBody = "当社の<strong>Open-Core</strong>ライセンスでは、サーバーごとに最大<strong>{{freeTierLimit}}</strong>ユーザーまで無料でご利用いただけます。中断なくスケールするには、Stirling Server プランをおすすめします - <strong>無制限の席数</strong>と<strong>SSO サポート</strong>で $99/サーバー/月。"
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "ダウンロード"
|
||||
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "지금 업그레이드 →"
|
||||
freeTitle = "서버 라이선스"
|
||||
overLimitTitle = "서버 라이선스 필요"
|
||||
overLimitBody = "당사의 라이선스는 서버당 무료로 최대 <strong>{{freeTierLimit}}</strong>명의 사용자를 허용합니다. 현재 <strong>{{overLimitUserCopy}}</strong>명의 Stirling 사용자가 있습니다. 중단 없이 계속 사용하려면 Stirling Server 플랜으로 업그레이드하세요 - <strong>무제한 좌석</strong>, PDF 텍스트 편집, 전체 관리자 제어 제공, $99/서버/월."
|
||||
freeBody = "당사의 <strong>Open-Core</strong> 라이선스는 서버당 최대 <strong>{{freeTierLimit}}</strong>명의 사용자를 무료로 허용합니다. 중단 없이 확장하고 새로운 <strong>PDF 텍스트 편집 도구</strong>에 조기 액세스하려면 Stirling Server 플랜을 권장합니다 - 전체 편집과 <strong>무제한 좌석</strong>을 $99/서버/월에 제공합니다."
|
||||
freeBody = "당사의 <strong>Open-Core</strong> 라이선스는 서버당 최대 <strong>{{freeTierLimit}}</strong>명의 사용자를 무료로 허용합니다. 중단 없이 확장하려면 Stirling Server 플랜을 권장합니다 - <strong>무제한 좌석</strong> 및 <strong>SSO 지원</strong>, $99/서버/월."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "다운로드"
|
||||
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "ഇപ്പോൾ അപ്ഗ്രേഡ് ചെയ്യു
|
||||
freeTitle = "സെർവർ ലൈസൻസ്"
|
||||
overLimitTitle = "സെർവർ ലൈസൻസ് ആവശ്യമാണ്"
|
||||
overLimitBody = "ഞങ്ങളുടെ ലൈസൻസിംഗ് ഓരോ സെർവർക്കും പരമാവധി <strong>{{freeTierLimit}}</strong> ഉപയോക്താക്കളെ സൗജന്യമായി അനുവദിക്കുന്നു. നിങ്ങള്ക്ക് <strong>{{overLimitUserCopy}}</strong> Stirling ഉപയോക്താക്കളുണ്ട്. തടസ്സമില്ലാതെ തുടരാൻ, Stirling Server പ്ലാനിലേക്ക് അപ്ഗ്രേഡ് ചെയ്യുക - <strong>unlimited seats</strong>, PDF text editing, പൂർണ്ണ അഡ്മിൻ നിയന്ത്രണം, $99/server/mo."
|
||||
freeBody = "ഞങ്ങളുടെ <strong>Open-Core</strong> ലൈസൻസിംഗ് ഓരോ സെർവർക്കും പരമാവധി <strong>{{freeTierLimit}}</strong> ഉപയോക്താക്കളെ സൗജന്യമായി അനുവദിക്കുന്നു. തടസ്സമില്ലാതെ സ്കെയിൽ ചെയ്യാനും പുതിയ <strong>PDF text editing tool</strong> ന് മുൻകാല ആക്സസ് നേടാനും, Stirling Server പ്ലാൻ ഞങ്ങൾ ശുപാർശ ചെയ്യുന്നു - പൂർണ്ണ എഡിറ്റിംഗും <strong>unlimited seats</strong> ഉം $99/server/mo."
|
||||
freeBody = "ഞങ്ങളുടെ <strong>Open-Core</strong> ലൈസൻസിംഗ് ഓരോ സെർവർക്കും പരമാവധി <strong>{{freeTierLimit}}</strong> ഉപയോക്താക്കളെ സൗജന്യമായി അനുവദിക്കുന്നു. തടസ്സമില്ലാതെ സ്കെയിൽ ചെയ്യാൻ, ഞങ്ങൾ Stirling Server പ്ലാൻ ശുപാർശ ചെയ്യുന്നു - <strong>പരിമിതിയില്ലാത്ത സീറ്റുകൾ</strong>യും <strong>SSO പിന്തുണ</strong>യും for $99/server/mo."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "ഡൗൺലോഡ്"
|
||||
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "Nu upgraden →"
|
||||
freeTitle = "Serverlicentie"
|
||||
overLimitTitle = "Serverlicentie vereist"
|
||||
overLimitBody = "Onze licentie staat tot <strong>{{freeTierLimit}}</strong> gebruikers gratis per server toe. Je hebt <strong>{{overLimitUserCopy}}</strong> Stirling-gebruikers. Om zonder onderbreking door te gaan, upgrade naar het Stirling Server-plan - <strong>onbeperkte plaatsen</strong>, PDF-tekstbewerking en volledige admincontrole voor $99/server/maand."
|
||||
freeBody = "Onze <strong>Open-Core</strong>-licentie staat tot <strong>{{freeTierLimit}}</strong> gebruikers gratis per server toe. Om ononderbroken te schalen en vroege toegang te krijgen tot onze nieuwe <strong>PDF-tekstbewerkingstool</strong>, raden we het Stirling Server-plan aan - volledige bewerking en <strong>onbeperkte plaatsen</strong> voor $99/server/maand."
|
||||
freeBody = "Onze <strong>Open-Core</strong>-licentie staat tot <strong>{{freeTierLimit}}</strong> gebruikers per server gratis toe. Om ononderbroken op te schalen, raden we het Stirling Server-abonnement aan - <strong>onbeperkte plaatsen</strong> en <strong>SSO-ondersteuning</strong> voor $99/server/maand."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Downloaden"
|
||||
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "Oppgrader nå →"
|
||||
freeTitle = "Serverlisens"
|
||||
overLimitTitle = "Serverlisens kreves"
|
||||
overLimitBody = "Lisensieringen vår tillater opptil <strong>{{freeTierLimit}}</strong> brukere gratis per server. Du har <strong>{{overLimitUserCopy}}</strong> Stirling-brukere. For å fortsette uten avbrudd, oppgrader til Stirling Server-planen – <strong>ubegrensede plasser</strong>, PDF-tekstredigering og full admin-kontroll for $99/server/mnd."
|
||||
freeBody = "Vår <strong>Open-Core</strong>-lisensiering tillater opptil <strong>{{freeTierLimit}}</strong> brukere gratis per server. For å skalere uten avbrudd og få tidlig tilgang til vårt nye <strong>PDF-tekstredigeringsverktøy</strong>, anbefaler vi Stirling Server-planen – full redigering og <strong>ubegrensede plasser</strong> for $99/server/mnd."
|
||||
freeBody = "Vår <strong>Open-Core</strong>-lisensiering tillater opptil <strong>{{freeTierLimit}}</strong> brukere gratis per server. For å skalere uten avbrudd anbefaler vi Stirling Server-planen - <strong>ubegrensede plasser</strong> og <strong>SSO-støtte</strong> for $99/server/mnd."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Last ned"
|
||||
|
||||
@@ -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. <strong>{{freeTierLimit}}</strong> użytkowników bez opłat na serwer. Masz <strong>{{overLimitUserCopy}}</strong> użytkowników Stirling. Aby kontynuować bez przerw, przejdź na plan Stirling Server – <strong>nielimitowane miejsca</strong>, edycja tekstu PDF i pełna kontrola administracyjna za 99 USD/serwer/mies."
|
||||
freeBody = "Nasza licencja <strong>Open-Core</strong> pozwala na maks. <strong>{{freeTierLimit}}</strong> użytkowników bez opłat na serwer. Aby skalować bez przerw i uzyskać wczesny dostęp do nowego <strong>narzędzia edycji tekstu PDF</strong>, polecamy plan Stirling Server – pełna edycja i <strong>nielimitowane miejsca</strong> za 99 USD/serwer/mies."
|
||||
freeBody = "Nasza licencja <strong>Open-Core</strong> pozwala na maksymalnie <strong>{{freeTierLimit}}</strong> użytkowników bezpłatnie na serwer. Aby skalować bez zakłóceń, zalecamy plan Stirling Server - <strong>nielimitowana liczba miejsc</strong> i <strong>obsługa SSO</strong> 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}}"
|
||||
|
||||
|
||||
@@ -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é <strong>{{freeTierLimit}}</strong> usuários grátis por servidor. Você tem <strong>{{overLimitUserCopy}}</strong> usuários do Stirling. Para continuar sem interrupções, faça upgrade para o plano Stirling Server - <strong>assentos ilimitados</strong>, edição de texto em PDF e controle total de admin por US$ 99/servidor/mês."
|
||||
freeBody = "Nossa licença <strong>Open-Core</strong> permite até <strong>{{freeTierLimit}}</strong> usuários grátis por servidor. Para escalar sem interrupções e ter acesso antecipado à nova <strong>ferramenta de edição de texto em PDF</strong>, recomendamos o plano Stirling Server - edição completa e <strong>assentos ilimitados</strong> por US$ 99/servidor/mês."
|
||||
freeBody = "Nossa licença <strong>Open-Core</strong> permite até <strong>{{freeTierLimit}}</strong> usuários gratuitos por servidor. Para escalar sem interrupções, recomendamos o plano Stirling Server - <strong>assentos ilimitados</strong> e <strong>suporte a SSO</strong> 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}}"
|
||||
|
||||
|
||||
@@ -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é <strong>{{freeTierLimit}}</strong> utilizadores gratuitos por servidor. Tem <strong>{{overLimitUserCopy}}</strong> utilizadores Stirling. Para continuar sem interrupções, atualize para o plano Stirling Server - <strong>lugares ilimitados</strong>, edição de texto em PDF e controlo total de administração por $99/servidor/mês."
|
||||
freeBody = "A nossa licença <strong>Open-Core</strong> permite até <strong>{{freeTierLimit}}</strong> utilizadores gratuitos por servidor. Para escalar sem interrupções e obter acesso antecipado à nossa nova <strong>ferramenta de edição de texto PDF</strong>, recomendamos o plano Stirling Server - edição completa e <strong>lugares ilimitados</strong> por $99/servidor/mês."
|
||||
freeBody = "O nosso licenciamento <strong>Open-Core</strong> permite até <strong>{{freeTierLimit}}</strong> utilizadores gratuitos por servidor. Para escalar sem interrupções, recomendamos o plano Stirling Server - <strong>lugares ilimitados</strong> e <strong>suporte SSO</strong> por $99/servidor/mês."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Transferir"
|
||||
|
||||
@@ -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 <strong>{{freeTierLimit}}</strong> utilizatori gratuit per server. Ai <strong>{{overLimitUserCopy}}</strong> utilizatori Stirling. Pentru a continua fără întreruperi, fă upgrade la planul Stirling Server - <strong>locuri nelimitate</strong>, editare text PDF și control complet de admin pentru $99/server/lună."
|
||||
freeBody = "Licențierea noastră <strong>Open-Core</strong> permite până la <strong>{{freeTierLimit}}</strong> utilizatori gratuit per server. Pentru a scala fără întreruperi și a primi acces timpuriu la noul nostru <strong>instrument de editare text PDF</strong>, recomandăm planul Stirling Server - editare completă și <strong>locuri nelimitate</strong> pentru $99/server/lună."
|
||||
freeBody = "Licențierea noastră <strong>Open-Core</strong> permite până la <strong>{{freeTierLimit}}</strong> utilizatori gratuit per server. Pentru scalare fără întreruperi, recomandăm planul Stirling Server - <strong>locuri nelimitate</strong> și <strong>suport SSO</strong> 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."
|
||||
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "Обновить сейчас →"
|
||||
freeTitle = "Серверная лицензия"
|
||||
overLimitTitle = "Требуется серверная лицензия"
|
||||
overLimitBody = "Наша лицензия допускает до <strong>{{freeTierLimit}}</strong> пользователей бесплатно на сервер. У вас <strong>{{overLimitUserCopy}}</strong> пользователей Stirling. Чтобы продолжить без перебоев, перейдите на тариф Stirling Server — <strong>неограниченные места</strong>, редактирование текста в PDF и полный админ‑контроль за $99/server/mo."
|
||||
freeBody = "Наша лицензия <strong>Open-Core</strong> допускает до <strong>{{freeTierLimit}}</strong> пользователей бесплатно на сервер. Чтобы масштабироваться без ограничений и раньше получить доступ к новому <strong>инструменту редактирования текста в PDF</strong>, рекомендуем тариф Stirling Server — полный редактор и <strong>неограниченные места</strong> за $99/server/mo."
|
||||
freeBody = "Наша лицензия <strong>Open-Core</strong> позволяет бесплатно использовать до <strong>{{freeTierLimit}}</strong> пользователей на сервер. Для бесшовного масштабирования мы рекомендуем план Stirling Server - <strong>неограниченное число мест</strong> и <strong>поддержка SSO</strong> за $99/сервер/мес."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Скачать"
|
||||
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "Upgradovať teraz →"
|
||||
freeTitle = "Serverová licencia"
|
||||
overLimitTitle = "Potrebná serverová licencia"
|
||||
overLimitBody = "Naše licencovanie povoľuje až <strong>{{freeTierLimit}}</strong> používateľov zdarma na server. Máte <strong>{{overLimitUserCopy}}</strong> používateľov Stirling. Ak chcete pokračovať bez prerušenia, prejdite na plán Stirling Server - <strong>neobmedzené miesta</strong>, úpravy textu PDF a plná správa pre $99/server/mo."
|
||||
freeBody = "Naše licencovanie <strong>Open-Core</strong> povoľuje až <strong>{{freeTierLimit}}</strong> používateľov zdarma na server. Ak chcete škálovať bez prerušenia a získať skorý prístup k nášmu novému <strong>nástroju na úpravu textu PDF</strong>, odporúčame plán Stirling Server - plné úpravy a <strong>neobmedzené miesta</strong> za $99/server/mo."
|
||||
freeBody = "Naše licencovanie <strong>Open-Core</strong> umožňuje až <strong>{{freeTierLimit}}</strong> používateľov zadarmo na server. Na plynulé škálovanie odporúčame plán Stirling Server - <strong>neobmedzený počet používateľov</strong> a <strong>podporu SSO</strong> za $99/server/mes."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Stiahnuť"
|
||||
|
||||
@@ -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 <strong>{{freeTierLimit}}</strong> uporabnikov na strežnik. Imate <strong>{{overLimitUserCopy}}</strong> uporabnikov Stirling. Za nemoteno uporabo nadgradite na načrt Stirling Server – <strong>neomejena mesta</strong>, urejanje besedila PDF in popoln skrbniški nadzor za $99/strežnik/mesec."
|
||||
freeBody = "Naše licenciranje <strong>Open-Core</strong> brezplačno omogoča do <strong>{{freeTierLimit}}</strong> uporabnikov na strežnik. Za nemoteno rast in zgodnji dostop do našega novega <strong>orodja za urejanje besedila PDF</strong> priporočamo načrt Stirling Server – polno urejanje in <strong>neomejena mesta</strong> za $99/strežnik/mesec."
|
||||
freeBody = "Naše licenciranje <strong>Open-Core</strong> omogoča do <strong>{{freeTierLimit}}</strong> uporabnikov brezplačno na strežnik. Za nemoteno skaliranje priporočamo načrt Stirling Server - <strong>neomejena mesta</strong> in <strong>podpora za SSO</strong> za $99/strežnik/mesec."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Prenesi"
|
||||
|
||||
@@ -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 <strong>{{freeTierLimit}}</strong> korisnika besplatno po serveru. Imate <strong>{{overLimitUserCopy}}</strong> Stirling korisnika. Da nastavite bez prekida, pređite na Stirling Server plan - <strong>neograničena mesta</strong>, uređivanje PDF teksta i puna admin kontrola za $99/server/mes."
|
||||
freeBody = "Naše <strong>Open-Core</strong> licenciranje dozvoljava do <strong>{{freeTierLimit}}</strong> korisnika besplatno po serveru. Da se bez prekida skalirate i dobijete rani pristup našem novom <strong>alatu za uređivanje PDF teksta</strong>, preporučujemo Stirling Server plan - puno uređivanje i <strong>neograničena mesta</strong> za $99/server/mes."
|
||||
freeBody = "Naše licenciranje <strong>Open-Core</strong> dozvoljava do <strong>{{freeTierLimit}}</strong> korisnika besplatno po serveru. Za neometano skaliranje, preporučujemo plan Stirling Server - <strong>neograničena mesta</strong> i <strong>SSO podrška</strong> za $99/server/mo."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Preuzmi"
|
||||
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "Uppgradera nu →"
|
||||
freeTitle = "Serverlicens"
|
||||
overLimitTitle = "Serverlicens krävs"
|
||||
overLimitBody = "Vår licensiering tillåter upp till <strong>{{freeTierLimit}}</strong> användare gratis per server. Du har <strong>{{overLimitUserCopy}}</strong> Stirling-användare. För att fortsätta utan avbrott, uppgradera till Stirling Server-planen - <strong>obegränsade platser</strong>, PDF-textredigering och full adminkontroll för $99/server/mån."
|
||||
freeBody = "Vår <strong>Open-Core</strong>-licens tillåter upp till <strong>{{freeTierLimit}}</strong> användare gratis per server. För att skala utan avbrott och få tidig åtkomst till vårt nya <strong>PDF-textredigeringsverktyg</strong> rekommenderar vi Stirling Server-planen - full redigering och <strong>obegränsade platser</strong> för $99/server/mån."
|
||||
freeBody = "Vår <strong>Open-Core</strong>-licens tillåter upp till <strong>{{freeTierLimit}}</strong> användare gratis per server. För att skala utan avbrott rekommenderar vi Stirling Server plan - <strong>obegränsat antal platser</strong> och <strong>SSO-stöd</strong> för $99/server/månad."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Ladda ner"
|
||||
|
||||
@@ -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 = "สิทธิ์การใช้งานของเรารองรับผู้ใช้ได้ฟรีสูงสุด <strong>{{freeTierLimit}}</strong> คนต่อเซิร์ฟเวอร์ ขณะนี้คุณมีผู้ใช้ Stirling <strong>{{overLimitUserCopy}}</strong> คน เพื่อใช้งานต่อเนื่อง โปรดอัปเกรดเป็นแพ็กเกจ Stirling Server - <strong>ที่นั่งไม่จำกัด</strong> แก้ไขข้อความ PDF และควบคุมแอดมินเต็มรูปแบบ ราคา $99/ต่อเซิร์ฟเวอร์/เดือน"
|
||||
freeBody = "ไลเซนส์แบบ <strong>Open-Core</strong> ของเรารองรับผู้ใช้ได้ฟรีสูงสุด <strong>{{freeTierLimit}}</strong> คนต่อเซิร์ฟเวอร์ เพื่อขยายการใช้งานได้ต่อเนื่องและเข้าถึง <strong>เครื่องมือแก้ไขข้อความ PDF</strong> ล่วงหน้า เราแนะนำแพ็กเกจ Stirling Server - แก้ไขได้เต็มรูปแบบและ <strong>ที่นั่งไม่จำกัด</strong> ราคา $99/ต่อเซิร์ฟเวอร์/เดือน"
|
||||
freeBody = "สัญญาอนุญาตแบบ <strong>Open-Core</strong> ของเราอนุญาตให้ใช้งานฟรีได้สูงสุด <strong>{{freeTierLimit}}</strong> ผู้ใช้ต่อเซิร์ฟเวอร์หนึ่งเครื่อง เพื่อขยายการใช้งานอย่างต่อเนื่อง เราขอแนะนำแผน Stirling Server - <strong>ที่นั่งไม่จำกัด</strong> และ <strong>รองรับ SSO</strong> ในราคา $99/เซิร์ฟเวอร์/เดือน"
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "ดาวน์โหลด"
|
||||
|
||||
@@ -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 <strong>{{freeTierLimit}}</strong> kullanıcıya izin verir. <strong>{{overLimitUserCopy}}</strong> Stirling kullanıcınız var. Kesintisiz devam etmek için Stirling Server planına yükseltin - <strong>sınırsız koltuk</strong>, PDF metin düzenleme ve tam yönetici kontrolü $99/server/ay."
|
||||
freeBody = "<strong>Open-Core</strong> lisansımız, sunucu başına ücretsiz olarak en fazla <strong>{{freeTierLimit}}</strong> kullanıcıya izin verir. Kesintisiz ölçeklemek ve yeni <strong>PDF metin düzenleme aracımıza</strong> erken erişim almak için Stirling Server planını öneririz - tam düzenleme ve <strong>sınırsız koltuk</strong> $99/server/ay."
|
||||
freeBody = "<strong>Open-Core</strong> lisanslamamız, sunucu başına en fazla <strong>{{freeTierLimit}}</strong> kullanıcıya ücretsiz izin verir. Kesintisiz ölçeklendirme için Stirling Server planını öneririz - <strong>sınırsız kullanıcı</strong> ve <strong>SSO desteği</strong> için $99/sunucu/ay."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "İndir"
|
||||
|
||||
@@ -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 = "Наша ліцензія дозволяє до <strong>{{freeTierLimit}}</strong> користувачів безкоштовно на сервер. У вас <strong>{{overLimitUserCopy}}</strong> користувачів Stirling. Щоб працювати без перерв, перейдіть на план Stirling Server — <strong>необмежена кількість місць</strong>, редагування тексту PDF та повний адмін-контроль за $99/server/mo."
|
||||
freeBody = "Наша <strong>Open-Core</strong> ліцензія дозволяє до <strong>{{freeTierLimit}}</strong> користувачів безкоштовно на сервер. Щоб масштабуватися безперервно та отримати ранній доступ до нового <strong>інструмента редагування тексту PDF</strong>, рекомендуємо план Stirling Server — повне редагування та <strong>необмежена кількість місць</strong> за $99/server/mo."
|
||||
freeBody = "Наша ліцензія <strong>Open-Core</strong> дозволяє до <strong>{{freeTierLimit}}</strong> користувачів безкоштовно на сервер. Щоб масштабуватися без перерв, рекомендуємо план Stirling Server — <strong>необмежена кількість місць</strong> і <strong>підтримка SSO</strong> за $99/сервер/міс."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Завантажити"
|
||||
|
||||
@@ -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 <strong>{{freeTierLimit}}</strong> người dùng miễn phí mỗi server. Bạn có <strong>{{overLimitUserCopy}}</strong> 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 - <strong>số ghế không giới hạn</strong>, 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 <strong>Open-Core</strong> của chúng tôi cho phép tối đa <strong>{{freeTierLimit}}</strong> 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 <strong>công cụ chỉnh sửa văn bản PDF</strong> mới, chúng tôi khuyến nghị gói Stirling Server - chỉnh sửa đầy đủ và <strong>số ghế không giới hạn</strong> với $99/server/tháng."
|
||||
freeBody = "Giấy phép <strong>Open-Core</strong> của chúng tôi cho phép tối đa <strong>{{freeTierLimit}}</strong> 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 - <strong>số lượng người dùng không giới hạn</strong> và <strong>hỗ trợ SSO</strong> với giá $99/máy chủ/tháng."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "Tải xuống"
|
||||
|
||||
@@ -1181,7 +1181,7 @@ selectFilesPlaceholder = "在主视图中选择文件以开始"
|
||||
settings = "设置"
|
||||
conversionCompleted = "转换完成"
|
||||
results = "结果"
|
||||
defaultFilename = "converted_file"
|
||||
defaultFilename = "已转换_文件"
|
||||
conversionResults = "转换结果"
|
||||
convertFrom = "从以下格式转换"
|
||||
convertTo = "转换为"
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "立即升级 →"
|
||||
freeTitle = "服务器许可证"
|
||||
overLimitTitle = "需要服务器许可证"
|
||||
overLimitBody = "我们的许可每台服务器最多允许 <strong>{{freeTierLimit}}</strong> 名用户免费使用。您共有 <strong>{{overLimitUserCopy}}</strong> 名 Stirling 用户。为避免中断,请升级到 Stirling Server 方案 - <strong>无限席位</strong>、PDF 文本编辑,以及每台服务器 $99/月 的完整管理员控制。"
|
||||
freeBody = "我们的 <strong>开源内核(Open-Core)</strong> 许可允许每台服务器最多 <strong>{{freeTierLimit}}</strong> 名用户免费使用。为顺畅扩展并抢先体验全新的 <strong>PDF 文本编辑工具</strong>,我们推荐 Stirling Server 方案 - 完整编辑与 <strong>无限席位</strong>,$99/服务器/月。"
|
||||
freeBody = "我们的<strong>Open-Core</strong>许可允许每台服务器最多<strong>{{freeTierLimit}}</strong>名用户免费使用。为实现不中断的扩展,我们推荐 Stirling Server 方案 - <strong>不限席位</strong>并提供<strong>SSO 支持</strong>,$99/服务器/月。"
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "下载"
|
||||
|
||||
@@ -5177,7 +5177,7 @@ upgrade = "立即升级 →"
|
||||
freeTitle = "服务器许可证"
|
||||
overLimitTitle = "需要服务器许可证"
|
||||
overLimitBody = "我们的许可允许每台服务器最多免费 <strong>{{freeTierLimit}}</strong> 名用户。您有 <strong>{{overLimitUserCopy}}</strong> 名 Stirling 用户。为不间断使用,请升级至 Stirling Server 方案 - <strong>无限席位</strong>、PDF 文本编辑,以及 $99/server/mo 的完整管理员控制。"
|
||||
freeBody = "我们的 <strong>Open-Core</strong> 许可允许每台服务器最多免费 <strong>{{freeTierLimit}}</strong> 名用户。为无缝扩展并抢先体验全新的 <strong>PDF 文本编辑工具</strong>,推荐 Stirling Server 方案 - 完整编辑与 <strong>无限席位</strong>,$99/server/mo。"
|
||||
freeBody = "我们的 <strong>Open-Core</strong> 许可允许每台服务器最多 <strong>{{freeTierLimit}}</strong> 名用户免费使用。为实现不间断扩展,我们推荐 Stirling Server 方案 - <strong>无限席位</strong> 和 <strong>SSO 支持</strong>,$99/server/mo."
|
||||
|
||||
[onboarding.desktopInstall]
|
||||
title = "下载"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user