Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Right-to-Left (RTL) Language Support for Login and Recovery Screens #7220

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
# --------------------------------------------------------------------------------------

# This file contains the language switcher configurations
# The format of the file is <language switcher name>=<language code>,<language name>
# The format of the file is <language switcher name>=<language code>,<language name>,text direction(rtl/ltr)
# The default text direction is set to "ltr".
lang.switch.en_US=us,English - United States
lang.switch.fr_FR=fr,Français - France
lang.switch.es_ES=es,Español - España
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
<%@ page import="org.apache.commons.text.StringEscapeUtils" %>
<%@ page import="org.wso2.carbon.identity.application.authentication.endpoint.util.AuthenticationEndpointUtil" %>
<%@ page import="java.io.File" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.FileReader" %>

<%-- Include tenant context --%>
<jsp:directive.include file="init-url.jsp"/>
Expand All @@ -29,18 +31,71 @@
<%-- Branding Preferences --%>
<jsp:directive.include file="branding-preferences.jsp"/>


<%-- Extract the name of the stylesheet--%>
<%
String themeName = "wso2is";
String language = "en"; // Default language
Cookie[] userCookies = request.getCookies();
if (userCookies != null) {
for (Cookie cookie : userCookies) {
if ("ui_lang".equals(cookie.getName())) {
language = cookie.getValue();
break;
}
}
}

// Specify the file path
String filePath = application.getRealPath("/") + "/WEB-INF/classes/LanguageOptions.properties";

// Create a Map to store the language direction
Map<String, String> languageDirectionMap = new HashMap<>();

// Use a BufferedReader to read the file content
savindi7 marked this conversation as resolved.
Show resolved Hide resolved
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
// Ignore comments and empty lines
if (!line.trim().startsWith("#") && !line.trim().isEmpty()) {
// Split the line into key and value using '='
String[] keyValue = line.split("=");
if (keyValue.length == 2) { // Ensure valid format
// Extract the language code from the key
String[] keyParts = keyValue[0].split("\\.");
String languageCode = keyParts[keyParts.length - 1];

// Split the value into components (languageCode, languageName, direction)
String[] valueParts = keyValue[1].split(",");
if (valueParts.length >= 3) { // Ensure the value has at least 3 parts
String direction = valueParts[2].trim();
languageDirectionMap.put(languageCode, direction);
} else {
languageDirectionMap.put(languageCode, "ltr"); // Default to LTR if direction is missing
}
}
}
}
} catch (Exception e) {
throw e;
}

// Get the current language's direction
String direction = languageDirectionMap.getOrDefault(language, "ltr");

String themeSuffix = "";
if ("rtl".equals(languageDirectionMap.get(language))) {
themeSuffix = ".rtl";
}

File themeDir = new File(request.getSession().getServletContext().getRealPath("/")
+ "/" + "libs/themes/" + themeName + "/");
String[] fileNames = themeDir.list();
String themeFileName = "";

for(String file: fileNames) {
savindi7 marked this conversation as resolved.
Show resolved Hide resolved
if(file.endsWith("min.css")) {
if(file.endsWith(themeSuffix + ".min.css")) {
themeFileName = file;
break;
savindi7 marked this conversation as resolved.
Show resolved Hide resolved
}
}
%>
Expand Down Expand Up @@ -109,3 +164,10 @@
}
}
</style>

<script type="text/javascript">
const direction = "<%= direction %>";
if (direction) {
savindi7 marked this conversation as resolved.
Show resolved Hide resolved
document.documentElement.setAttribute("dir", direction);
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
# --------------------------------------------------------------------------------------

# This file contains the language switcher configurations
# The format of the file is <language switcher name>=<language code>,<language name>
# The format of the file is <language switcher name>=<language code>,<language name>,text direction(rtl/ltr)
# The default text direction is set to "ltr".
lang.switch.en_US=us,English - United States
lang.switch.fr_FR=fr,Français - France
lang.switch.es_ES=es,Español - España
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
<%@ page import="org.wso2.carbon.identity.mgt.endpoint.util.IdentityManagementEndpointUtil" %>
<%@ page import="org.owasp.encoder.Encode" %>
<%@ page import="java.io.File" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.FileReader" %>

<%-- Localization --%>
<jsp:directive.include file="localize.jsp" />
Expand All @@ -33,14 +35,68 @@
<!-- Extract the name of the stylesheet-->
<%
String themeName = "wso2is";
String language = "en"; // Default language
Cookie[] userCookies = request.getCookies();
if (userCookies != null) {
for (Cookie cookie : userCookies) {
if ("ui_lang".equals(cookie.getName())) {
language = cookie.getValue();
break;
}
}
}

// Specify the file path
String filePath = application.getRealPath("/") + "/WEB-INF/classes/LanguageOptions.properties";

// Create a Map to store the language direction
Map<String, String> languageDirectionMap = new HashMap<>();

// Use a BufferedReader to read the file content
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
// Ignore comments and empty lines
if (!line.trim().startsWith("#") && !line.trim().isEmpty()) {
// Split the line into key and value using '='
String[] keyValue = line.split("=");
if (keyValue.length == 2) { // Ensure valid format
// Extract the language code from the key
String[] keyParts = keyValue[0].split("\\.");
String languageCode = keyParts[keyParts.length - 1];

// Split the value into components (languageCode, languageName, direction)
String[] valueParts = keyValue[1].split(",");
if (valueParts.length >= 3) { // Ensure the value has at least 3 parts
String direction = valueParts[2].trim();
languageDirectionMap.put(languageCode, direction);
} else {
languageDirectionMap.put(languageCode, "ltr"); // Default to LTR if direction is missing
}
}
}
}
} catch (Exception e) {
throw e;
}

// Get the current language's direction
String direction = languageDirectionMap.getOrDefault(language, "ltr");

String themeSuffix = "";
if ("rtl".equals(languageDirectionMap.get(language))) {
themeSuffix = ".rtl";
}

File themeDir = new File(request.getSession().getServletContext().getRealPath("/")
+ "/" + "libs/themes/" + themeName + "/");
String[] fileNames = themeDir.list();
String themeFileName = "";

for(String file: fileNames) {
if(file.endsWith("min.css")) {
if(file.endsWith(themeSuffix + ".min.css")) {
themeFileName = file;
break;
}
}
%>
Expand Down Expand Up @@ -96,3 +152,10 @@
<%
}
%>

<script type="text/javascript">
const direction = "<%= direction %>";
if (direction) {
document.documentElement.setAttribute("dir", direction);
}
</script>
1 change: 1 addition & 0 deletions modules/theme/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"rc-tree": "^4.0.0-beta.2",
"replace": "^1.1.5",
"rimraf": "^3.0.2",
"rtlcss": "^4.3.0",
"semantic-ui-css": "^2.4.1",
"semantic-ui-less": "^2.4.1",
"ts-jest": "^29.1.2",
Expand Down
20 changes: 18 additions & 2 deletions modules/theme/scripts/build.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020, WSO2 LLC. (https://www.wso2.com). All Rights Reserved.
* Copyright (c) 2020, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand All @@ -25,6 +25,7 @@ const fs = require("fs-extra");
const lessToJson = require("less-to-json");
const mergeFiles = require("merge-files");
const replace = require("replace");
const rtlcss = require("rtlcss");
const { Theme } = require("../src/theme");

/**
Expand Down Expand Up @@ -147,6 +148,16 @@ const writeFile = (theme, file, content) => {
log.info(theme + "/" + "theme" + file + " generated.");
};

/**
* Generates RTL CSS files using rtlcss.
*
* @param {string} ltrCss - LTR CSS content.
* @returns {string} RTL-compatible CSS content.
*/
const generateRTLCSS = (ltrCss) => {
return rtlcss.process(ltrCss);
};

/*
* Copy semantic.js files to each theme to make them self contained
*
Expand Down Expand Up @@ -253,10 +264,15 @@ const generateThemes = () => {

return Theme.compile(themeIndexFile, {}).then((output) => {
const minifiedOutput = new CleanCSS().minify(output.css);
const rtlCSS = generateRTLCSS(output.css); // Generate RTL CSS
const rtlMinCSS = new CleanCSS().minify(rtlCSS); // Minify RTL CSS

const files = {
".css": output.css,
".css.map": output.map,
".min.css": minifiedOutput.styles
".min.css": minifiedOutput.styles,
".rtl.css": rtlCSS,
".rtl.min.css": rtlMinCSS.styles
};

Object.keys(files).map((key) => {
Expand Down
Loading