mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-10-25 11:17:28 +02:00 
			
		
		
		
	Add: Configurable UI Language Support with Dynamic Filtering (#2846)
# Description of Changes ### Summary - Added support for configuring UI languages via `settings.yml` (`languages` field). - Modified `LanguageService` to respect the configured languages, while ensuring British English (`en_GB`) is always enabled. - Updated Thymeleaf templates to dynamically display only the allowed languages. - Improved logging and refactored some list-to-set conversions for better efficiency. ### Why the Change? - Allows administrators to limit available UI languages instead of displaying all detected languages. - Provides better customization options and simplifies language management. ### Challenges Encountered - Ensuring backwards compatibility: If `languages` is empty, all languages remain enabled. - Handling `Set<String>` instead of `List<String>` in `LanguageService` for optimized lookups. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [x] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [x] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [x] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details.
This commit is contained in:
		
							parent
							
								
									46c53a9c88
								
							
						
					
					
						commit
						b37457b41d
					
				| @ -18,9 +18,7 @@ Any SVG flags are fine; most of the current ones were sourced from [here](https: | |||||||
| For example, to add Polish, you would add: | For example, to add Polish, you would add: | ||||||
| 
 | 
 | ||||||
| ```html | ```html | ||||||
| <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pl_PL"> | <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'pl_PL')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pl_PL"> <img th:src="@{'/images/flags/pl.svg'}" alt="icon" width="20" height="15"> Polski</a> | ||||||
|     <img src="images/flags/pl.svg" alt="icon" width="20" height="15"> Polski |  | ||||||
| </a> |  | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| The `data-bs-language-code` is the code used to reference the file in the next step. | The `data-bs-language-code` is the code used to reference the file in the next step. | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ import java.io.IOException; | |||||||
| import java.nio.file.Files; | import java.nio.file.Files; | ||||||
| import java.nio.file.Path; | import java.nio.file.Path; | ||||||
| import java.nio.file.Paths; | import java.nio.file.Paths; | ||||||
|  | import java.util.List; | ||||||
| import java.util.Properties; | import java.util.Properties; | ||||||
| import java.util.function.Predicate; | import java.util.function.Predicate; | ||||||
| 
 | 
 | ||||||
| @ -74,6 +75,11 @@ public class AppConfig { | |||||||
|                 : "null"; |                 : "null"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Bean(name = "languages") | ||||||
|  |     public List<String> languages() { | ||||||
|  |         return applicationProperties.getUi().getLanguages(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Bean(name = "navBarText") |     @Bean(name = "navBarText") | ||||||
|     public String navBarText() { |     public String navBarText() { | ||||||
|         String defaultNavBar = |         String defaultNavBar = | ||||||
|  | |||||||
| @ -2,7 +2,9 @@ package stirling.software.SPDF.controller.api; | |||||||
| 
 | 
 | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.io.PrintWriter; | import java.io.PrintWriter; | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.Set; | ||||||
| 
 | 
 | ||||||
| import org.springframework.web.bind.annotation.GetMapping; | import org.springframework.web.bind.annotation.GetMapping; | ||||||
| import org.springframework.web.bind.annotation.RequestMapping; | import org.springframework.web.bind.annotation.RequestMapping; | ||||||
| @ -26,11 +28,11 @@ public class AdditionalLanguageJsController { | |||||||
|     @Hidden |     @Hidden | ||||||
|     @GetMapping(value = "/additionalLanguageCode.js", produces = "application/javascript") |     @GetMapping(value = "/additionalLanguageCode.js", produces = "application/javascript") | ||||||
|     public void generateAdditionalLanguageJs(HttpServletResponse response) throws IOException { |     public void generateAdditionalLanguageJs(HttpServletResponse response) throws IOException { | ||||||
|         List<String> supportedLanguages = languageService.getSupportedLanguages(); |         Set<String> supportedLanguages = languageService.getSupportedLanguages(); | ||||||
|         response.setContentType("application/javascript"); |         response.setContentType("application/javascript"); | ||||||
|         PrintWriter writer = response.getWriter(); |         PrintWriter writer = response.getWriter(); | ||||||
|         // Erstelle das JavaScript dynamisch |         // Erstelle das JavaScript dynamisch | ||||||
|         writer.println("const supportedLanguages = " + toJsonArray(supportedLanguages) + ";"); |         writer.println("const supportedLanguages = " + toJsonArray(new ArrayList<>(supportedLanguages)) + ";"); | ||||||
|         // Generiere die `getDetailedLanguageCode`-Funktion |         // Generiere die `getDetailedLanguageCode`-Funktion | ||||||
|         writer.println( |         writer.println( | ||||||
|                 """ |                 """ | ||||||
|  | |||||||
| @ -36,8 +36,9 @@ public class DatabaseWebController { | |||||||
|         } |         } | ||||||
|         List<FileInfo> backupList = databaseService.getBackupList(); |         List<FileInfo> backupList = databaseService.getBackupList(); | ||||||
|         model.addAttribute("backupFiles", backupList); |         model.addAttribute("backupFiles", backupList); | ||||||
|         model.addAttribute("databaseVersion", databaseService.getH2Version()); |         String dbVersion = databaseService.getH2Version(); | ||||||
|         if ("Unknown".equalsIgnoreCase(databaseService.getH2Version())) { |         model.addAttribute("databaseVersion", dbVersion); | ||||||
|  |         if ("Unknown".equalsIgnoreCase(dbVersion)) { | ||||||
|             model.addAttribute("infoMessage", "notSupported"); |             model.addAttribute("infoMessage", "notSupported"); | ||||||
|         } |         } | ||||||
|         return "database"; |         return "database"; | ||||||
|  | |||||||
| @ -265,7 +265,8 @@ public class ApplicationProperties { | |||||||
|                             return getKeycloak(); |                             return getKeycloak(); | ||||||
|                         default: |                         default: | ||||||
|                             throw new UnsupportedProviderException( |                             throw new UnsupportedProviderException( | ||||||
|                                     "Logout from the provider is not supported? Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues"); |                                     "Logout from the provider is not supported? Report it at" | ||||||
|  |                                             + " https://github.com/Stirling-Tools/Stirling-PDF/issues"); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @ -313,10 +314,10 @@ public class ApplicationProperties { | |||||||
|         @Override |         @Override | ||||||
|         public String toString() { |         public String toString() { | ||||||
|             return """ |             return """ | ||||||
|                     Driver { |             Driver { | ||||||
|                       driverName='%s' |               driverName='%s' | ||||||
|                     } |             } | ||||||
|                     """ |             """ | ||||||
|                     .formatted(driverName); |                     .formatted(driverName); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -326,6 +327,7 @@ public class ApplicationProperties { | |||||||
|         private String appName; |         private String appName; | ||||||
|         private String homeDescription; |         private String homeDescription; | ||||||
|         private String appNameNavbar; |         private String appNameNavbar; | ||||||
|  |         private List<String> languages; | ||||||
| 
 | 
 | ||||||
|         public String getAppName() { |         public String getAppName() { | ||||||
|             return appName != null && appName.trim().length() > 0 ? appName : null; |             return appName != null && appName.trim().length() > 0 ? appName : null; | ||||||
|  | |||||||
| @ -1,41 +1,57 @@ | |||||||
| package stirling.software.SPDF.service; | package stirling.software.SPDF.service; | ||||||
| 
 | 
 | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.util.ArrayList; | import java.util.HashSet; | ||||||
| import java.util.List; | import java.util.Set; | ||||||
|  | import java.util.stream.Collectors; | ||||||
|  | import java.util.Arrays; | ||||||
| 
 | 
 | ||||||
| import org.springframework.core.io.Resource; | import org.springframework.core.io.Resource; | ||||||
| import org.springframework.core.io.support.PathMatchingResourcePatternResolver; | import org.springframework.core.io.support.PathMatchingResourcePatternResolver; | ||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
| 
 | 
 | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import stirling.software.SPDF.model.ApplicationProperties; | ||||||
|  | 
 | ||||||
| @Service | @Service | ||||||
|  | @Slf4j | ||||||
| public class LanguageService { | public class LanguageService { | ||||||
| 
 | 
 | ||||||
|  |     private final ApplicationProperties applicationProperties; | ||||||
|     private final PathMatchingResourcePatternResolver resourcePatternResolver = |     private final PathMatchingResourcePatternResolver resourcePatternResolver = | ||||||
|             new PathMatchingResourcePatternResolver(); |             new PathMatchingResourcePatternResolver(); | ||||||
| 
 | 
 | ||||||
|     public List<String> getSupportedLanguages() { |     public LanguageService( | ||||||
|         List<String> supportedLanguages = new ArrayList<>(); |             ApplicationProperties applicationProperties) { | ||||||
|  |         this.applicationProperties = applicationProperties; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|  |     public Set<String> getSupportedLanguages() { | ||||||
|         try { |         try { | ||||||
|             Resource[] resources = |             Resource[] resources = | ||||||
|                     resourcePatternResolver.getResources("classpath*:messages_*.properties"); |                     resourcePatternResolver.getResources("classpath*:messages_*.properties"); | ||||||
|             for (Resource resource : resources) { |  | ||||||
|                 if (resource.exists() && resource.isReadable()) { |  | ||||||
|                     String filename = resource.getFilename(); |  | ||||||
|                     if (filename != null |  | ||||||
|                             && filename.startsWith("messages_") |  | ||||||
|                             && filename.endsWith(".properties")) { |  | ||||||
|                         String languageCode = |  | ||||||
|                                 filename.replace("messages_", "").replace(".properties", ""); |  | ||||||
|                         supportedLanguages.add(languageCode); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } catch (IOException e) { |  | ||||||
|             e.printStackTrace(); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         return supportedLanguages; |             return Arrays.stream(resources) | ||||||
|  |                     .map(Resource::getFilename) | ||||||
|  |                     .filter( | ||||||
|  |                             filename -> | ||||||
|  |                                     filename != null | ||||||
|  |                                             && filename.startsWith("messages_") | ||||||
|  |                                             && filename.endsWith(".properties")) | ||||||
|  |                     .map(filename -> filename.replace("messages_", "").replace(".properties", "")) | ||||||
|  |                     .filter( | ||||||
|  |                             languageCode -> { | ||||||
|  |                                 Set<String> allowedLanguages = | ||||||
|  |                                         new HashSet<>(applicationProperties.getUi().getLanguages()); | ||||||
|  |                                 return allowedLanguages.isEmpty() | ||||||
|  |                                         || allowedLanguages.contains(languageCode) | ||||||
|  |                                         || "en_GB".equals(languageCode); | ||||||
|  |                             }) | ||||||
|  |                     .collect(Collectors.toSet()); | ||||||
|  | 
 | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             log.error("Error retrieving supported languages", e); | ||||||
|  |             return new HashSet<>(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -101,6 +101,7 @@ ui: | |||||||
|   appName: '' # application's visible name |   appName: '' # application's visible name | ||||||
|   homeDescription: '' # short description or tagline shown on the homepage |   homeDescription: '' # short description or tagline shown on the homepage | ||||||
|   appNameNavbar: '' # name displayed on the navigation bar |   appNameNavbar: '' # name displayed on the navigation bar | ||||||
|  |   languages: [] # If empty, all languages are enabled. To display only German and Polish ["de_DE", "pl_PL"]. British English is always enabled. | ||||||
| 
 | 
 | ||||||
| endpoints: | endpoints: | ||||||
|   toRemove: [] # list endpoints to disable (e.g. ['img-to-pdf', 'remove-pages']) |   toRemove: [] # list endpoints to disable (e.g. ['img-to-pdf', 'remove-pages']) | ||||||
|  | |||||||
| @ -1,42 +1,42 @@ | |||||||
| <th:block th:fragment="langs"> | <th:block th:fragment="langs"> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="bg_BG"> <img th:src="@{'/images/flags/bg.svg'}" alt="icon" width="20" height="15"> Български</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'bg_BG')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="bg_BG"> <img th:src="@{'/images/flags/bg.svg'}" alt="icon" width="20" height="15"> Български</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ar_AR"> <img th:src="@{'/images/flags/sa.svg'}" alt="icon" width="20" height="15"> العربية</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'ar_AR')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ar_AR"> <img th:src="@{'/images/flags/sa.svg'}" alt="icon" width="20" height="15"> العربية</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ca_CA"> <img th:src="@{'/images/flags/es-ct.svg'}" alt="icon" width="20" height="15"> Català</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'ca_CA')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ca_CA"> <img th:src="@{'/images/flags/es-ct.svg'}" alt="icon" width="20" height="15"> Català</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="zh_CN"> <img th:src="@{'/images/flags/cn.svg'}" alt="icon" width="20" height="15"> 简体中文</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'zh_CN')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="zh_CN"> <img th:src="@{'/images/flags/cn.svg'}" alt="icon" width="20" height="15"> 简体中文</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="zh_TW"> <img th:src="@{'/images/flags/tw.svg'}" alt="icon" width="20" height="15"> 繁體中文</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'zh_TW')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="zh_TW"> <img th:src="@{'/images/flags/tw.svg'}" alt="icon" width="20" height="15"> 繁體中文</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="zh_BO"> <img th:src="@{'/images/flags/cn.svg'}" alt="icon" width="20" height="15"> བོད་ཡིག</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'zh_BO')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="zh_BO"> <img th:src="@{'/images/flags/cn.svg'}" alt="icon" width="20" height="15"> བོད་ཡིག</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="az_AZ"> <img th:src="@{'/images/flags/az.svg'}" alt="icon" width="20" height="15"> Azərbaycan Dili</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'az_AZ')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="az_AZ"> <img th:src="@{'/images/flags/az.svg'}" alt="icon" width="20" height="15"> Azərbaycan Dili</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="da_DK"> <img th:src="@{'/images/flags/dk.svg'}" alt="icon" width="20" height="15"> Dansk</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'da_DK')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="da_DK"> <img th:src="@{'/images/flags/dk.svg'}" alt="icon" width="20" height="15"> Dansk</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="de_DE"> <img th:src="@{'/images/flags/de.svg'}" alt="icon" width="20" height="15"> Deutsch</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'de_DE')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="de_DE"> <img th:src="@{'/images/flags/de.svg'}" alt="icon" width="20" height="15"> Deutsch</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="en_GB"> <img th:src="@{'/images/flags/gb.svg'}" alt="icon" width="20" height="15"> English (GB)</a> |                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="en_GB"> <img th:src="@{'/images/flags/gb.svg'}" alt="icon" width="20" height="15"> English (GB)</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="en_US"> <img th:src="@{'/images/flags/us.svg'}" alt="icon" width="20" height="15"> English (US)</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'en_US')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="en_US"> <img th:src="@{'/images/flags/us.svg'}" alt="icon" width="20" height="15"> English (US)</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="eu_ES"> <img th:src="@{'/images/flags/eu.svg'}" alt="icon" width="20" height="15"> Euskara</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'eu_ES')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="eu_ES"> <img th:src="@{'/images/flags/eu.svg'}" alt="icon" width="20" height="15"> Euskara</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="es_ES"> <img th:src="@{'/images/flags/es.svg'}" alt="icon" width="20" height="15"> Español</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'es_ES')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="es_ES"> <img th:src="@{'/images/flags/es.svg'}" alt="icon" width="20" height="15"> Español</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="fr_FR"> <img th:src="@{'/images/flags/fr.svg'}" alt="icon" width="20" height="15"> Français</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'fr_FR')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="fr_FR"> <img th:src="@{'/images/flags/fr.svg'}" alt="icon" width="20" height="15"> Français</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="id_ID"> <img th:src="@{'/images/flags/id.svg'}" alt="icon" width="20" height="15"> Indonesia</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'id_ID')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="id_ID"> <img th:src="@{'/images/flags/id.svg'}" alt="icon" width="20" height="15"> Indonesia</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ga_IE"> <img th:src="@{'/images/flags/ie.svg'}" alt="icon" width="20" height="15"> Irish</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'ga_IE')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ga_IE"> <img th:src="@{'/images/flags/ie.svg'}" alt="icon" width="20" height="15"> Irish</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="it_IT"> <img th:src="@{'/images/flags/it.svg'}" alt="icon" width="20" height="15"> Italiano</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'it_IT')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="it_IT"> <img th:src="@{'/images/flags/it.svg'}" alt="icon" width="20" height="15"> Italiano</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="nl_NL"> <img th:src="@{'/images/flags/nl.svg'}" alt="icon" width="20" height="15"> Nederlands</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'nl_NL')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="nl_NL"> <img th:src="@{'/images/flags/nl.svg'}" alt="icon" width="20" height="15"> Nederlands</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="fa_IR"> <img th:src="@{'/images/flags/ir.svg'}" alt="icon" width="20" height="15"> پارسی</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'fa_IR')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="fa_IR"> <img th:src="@{'/images/flags/ir.svg'}" alt="icon" width="20" height="15"> پارسی</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pl_PL"> <img th:src="@{'/images/flags/pl.svg'}" alt="icon" width="20" height="15"> Polski</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'pl_PL')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pl_PL"> <img th:src="@{'/images/flags/pl.svg'}" alt="icon" width="20" height="15"> Polski</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pt_BR"> <img th:src="@{'/images/flags/pt_br.svg'}" alt="icon" width="20" height="15"> Português (BR)</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'pt_BR')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pt_BR"> <img th:src="@{'/images/flags/pt_br.svg'}" alt="icon" width="20" height="15"> Português (BR)</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pt_PT"> <img th:src="@{'/images/flags/pt_pt.svg'}" alt="icon" width="20" height="15"> Português (PT)</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'pt_PT')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pt_PT"> <img th:src="@{'/images/flags/pt_pt.svg'}" alt="icon" width="20" height="15"> Português (PT)</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ro_RO"> <img th:src="@{'/images/flags/ro.svg'}" alt="icon" width="20" height="15"> Romanian</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'ro_RO')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ro_RO"> <img th:src="@{'/images/flags/ro.svg'}" alt="icon" width="20" height="15"> Romanian</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="sk_SK"> <img th:src="@{'/images/flags/sk.svg'}" alt="icon" width="20" height="15"> Slovensky</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'sk_SK')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="sk_SK"> <img th:src="@{'/images/flags/sk.svg'}" alt="icon" width="20" height="15"> Slovensky</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="sl_SI"> <img th:src="@{'/images/flags/si.svg'}" alt="icon" width="20" height="15"> Slovenian</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'sl_SI')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="sl_SI"> <img th:src="@{'/images/flags/si.svg'}" alt="icon" width="20" height="15"> Slovenian</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="sv_SE"> <img th:src="@{'/images/flags/se.svg'}" alt="icon" width="20" height="15"> Svenska</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'sv_SE')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="sv_SE"> <img th:src="@{'/images/flags/se.svg'}" alt="icon" width="20" height="15"> Svenska</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="tr_TR"> <img th:src="@{'/images/flags/tr.svg'}" alt="icon" width="20" height="15"> Türkçe</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'tr_TR')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="tr_TR"> <img th:src="@{'/images/flags/tr.svg'}" alt="icon" width="20" height="15"> Türkçe</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ru_RU"> <img th:src="@{'/images/flags/ru.svg'}" alt="icon" width="20" height="15"> Русский</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'ru_RU')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ru_RU"> <img th:src="@{'/images/flags/ru.svg'}" alt="icon" width="20" height="15"> Русский</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ko_KR"> <img th:src="@{'/images/flags/kr.svg'}" alt="icon" width="20" height="15"> 한국어</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'ko_KR')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ko_KR"> <img th:src="@{'/images/flags/kr.svg'}" alt="icon" width="20" height="15"> 한국어</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ja_JP"> <img th:src="@{'/images/flags/jp.svg'}" alt="icon" width="20" height="15"> 日本語</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'ja_JP')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ja_JP"> <img th:src="@{'/images/flags/jp.svg'}" alt="icon" width="20" height="15"> 日本語</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="el_GR"> <img th:src="@{'/images/flags/gr.svg'}" alt="icon" width="20" height="15"> Ελληνικά</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'el_GR')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="el_GR"> <img th:src="@{'/images/flags/gr.svg'}" alt="icon" width="20" height="15"> Ελληνικά</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="hu_HU"> <img th:src="@{'/images/flags/hu.svg'}" alt="icon" width="20" height="15"> Hungarian</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'hu_HU')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="hu_HU"> <img th:src="@{'/images/flags/hu.svg'}" alt="icon" width="20" height="15"> Hungarian</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="hi_IN"> <img th:src="@{'/images/flags/in.svg'}" alt="icon" width="20" height="15"> हिन्दी</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'hi_IN')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="hi_IN"> <img th:src="@{'/images/flags/in.svg'}" alt="icon" width="20" height="15"> हिन्दी</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="sr_LATN_RS"> <img th:src="@{'/images/flags/rs.svg'}" alt="icon" width="20" height="15"> Srpski</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'sr_LATN_RS')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="sr_LATN_RS"> <img th:src="@{'/images/flags/rs.svg'}" alt="icon" width="20" height="15"> Srpski</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="uk_UA"> <img th:src="@{'/images/flags/ua.svg'}" alt="icon" width="20" height="15"> Українська</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'uk_UA')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="uk_UA"> <img th:src="@{'/images/flags/ua.svg'}" alt="icon" width="20" height="15"> Українська</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="cs_CZ"> <img th:src="@{'/images/flags/cz.svg'}" alt="icon" width="20" height="15"> Česky</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'cs_CZ')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="cs_CZ"> <img th:src="@{'/images/flags/cz.svg'}" alt="icon" width="20" height="15"> Česky</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="hr_HR"> <img th:src="@{'/images/flags/hr.svg'}" alt="icon" width="20" height="15"> Hrvatski</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'hr_HR')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="hr_HR"> <img th:src="@{'/images/flags/hr.svg'}" alt="icon" width="20" height="15"> Hrvatski</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="no_NB"> <img th:src="@{'/images/flags/no.svg'}" alt="icon" width="20" height="15"> Norsk</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'no_NB')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="no_NB"> <img th:src="@{'/images/flags/no.svg'}" alt="icon" width="20" height="15"> Norsk</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="th_TH"> <img th:src="@{'/images/flags/th.svg'}" alt="icon" width="20" height="15"> ไทย</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'th_TH')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="th_TH"> <img th:src="@{'/images/flags/th.svg'}" alt="icon" width="20" height="15"> ไทย</a> | ||||||
|                       <a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="vi_VN"> <img th:src="@{'/images/flags/vn.svg'}" alt="icon" width="20" height="15"> Tiếng Việt</a> |                       <a th:if="${#lists.isEmpty(@languages) or #lists.contains(@languages, 'vi_VN')}" class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="vi_VN"> <img th:src="@{'/images/flags/vn.svg'}" alt="icon" width="20" height="15"> Tiếng Việt</a> | ||||||
| </th:block> | </th:block> | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user