You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by so...@apache.org on 2022/01/12 03:22:26 UTC

[wicket] branch wicket-9.x updated: [WICKET-6943] Save info about Locale direction LTR/RTL to Session metadata (#488)

This is an automated email from the ASF dual-hosted git repository.

solomax pushed a commit to branch wicket-9.x
in repository https://gitbox.apache.org/repos/asf/wicket.git


The following commit(s) were added to refs/heads/wicket-9.x by this push:
     new 883f56d  [WICKET-6943] Save info about Locale direction LTR/RTL to Session metadata (#488)
883f56d is described below

commit 883f56d3d9dda6e2932942c361f22d388fe8de96
Author: Maxim Solodovnik <so...@gmail.com>
AuthorDate: Wed Jan 12 10:09:27 2022 +0700

    [WICKET-6943] Save info about Locale direction LTR/RTL to Session metadata (#488)
    
    [WICKET-6943] Save info about language direction of session locale (RTL/LTR)
---
 .../src/main/java/org/apache/wicket/Session.java   | 55 ++++++++++++++++++++++
 .../wicket/protocol/http/WebSessionTest.java       | 55 +++++++++++++++++++++-
 2 files changed, 109 insertions(+), 1 deletion(-)

diff --git a/wicket-core/src/main/java/org/apache/wicket/Session.java b/wicket-core/src/main/java/org/apache/wicket/Session.java
index c4152c9..3b389c4 100644
--- a/wicket-core/src/main/java/org/apache/wicket/Session.java
+++ b/wicket-core/src/main/java/org/apache/wicket/Session.java
@@ -27,6 +27,8 @@ import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Supplier;
+import java.util.regex.Pattern;
+
 import org.apache.wicket.application.IClassResolver;
 import org.apache.wicket.authorization.IAuthorizationStrategy;
 import org.apache.wicket.core.request.ClientInfo;
@@ -130,6 +132,15 @@ public abstract class Session implements IClusterable, IEventSink, IMetadataCont
 	/** Name of session attribute under which this session is stored */
 	public static final String SESSION_ATTRIBUTE_NAME = "session";
 
+	/**
+	 * taken from Google Closure Templates BidiUtils
+	 *
+	 * A regular expression for matching right-to-left language codes. See
+	 * {@link #isRtlLanguage} for the design.
+	 */
+	private static final Pattern RTL_LOCALE_RE = Pattern.compile("^(ar|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|.*[-_](Arab|Hebr|Thaa|Nkoo|Tfng))"
+					+ "(?!.*[-_](Latn|Cyrl)($|-|_))($|-|_)");
+
 	/** a sequence used for whenever something session-specific needs a unique value */
 	private final AtomicInteger sequence = new AtomicInteger(1);
 
@@ -185,6 +196,36 @@ public abstract class Session implements IClusterable, IEventSink, IMetadataCont
 	}
 
 	/**
+	 * Check if a BCP 47 / III language code indicates an RTL (right-to-left) language, i.e.
+	 * either: - a language code explicitly specifying one of the right-to-left
+	 * scripts, e.g. "az-Arab", or
+	 * <p>
+	 * - a language code specifying one of the languages normally written in a
+	 * right-to-left script, e.g. "fa" (Farsi), except ones explicitly
+	 * specifying Latin or Cyrillic script (which are the usual LTR (left-to-right)
+	 * alternatives).
+	 * <p>
+	 * <a href="http://www.unicode.org/iso15924/iso15924-num.html">
+	 * The list of right-to-left scripts appears in the 100-199 range in</a>, of which Arabic and
+	 * Hebrew are by far the most widely used. We also recognize Thaana, N'Ko,
+	 * and Tifinagh, which also have significant modern usage. The rest (Syriac,
+	 * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern
+	 * usage and are not recognized. The languages usually written in a
+	 * right-to-left script are taken as those with 
+	 * <a href="http://www.iana.org/assignments/language-subtag-registry">Suppress-Script</a>:
+	 * Hebr|Arab|Thaa|Nkoo|Tfng, as well as
+	 * Sindhi (sd) and Uyghur (ug). The presence of other subtags of the
+	 * language code, e.g. regions like EG (Egypt), is ignored.
+	 *
+	 * @param locale - locale to check
+	 * @return <code>true</code> in case passed locale is right-to-left
+	 */
+	private static boolean isRtlLanguage(final Locale locale) {
+		Args.notNull(locale, "locale");
+		return RTL_LOCALE_RE.matcher(locale.toLanguageTag()).find();
+	}
+
+	/**
 	 * Cached instance of agent info which is typically designated by calling
 	 * {@link Session#getClientInfo()}.
 	 */
@@ -202,6 +243,9 @@ public abstract class Session implements IClusterable, IEventSink, IMetadataCont
 	/** The locale to use when loading resources for this session. */
 	private final AtomicReference<Locale> locale;
 
+	/** True if locale's language is RTL (right-to-left) */
+	private boolean rtlLocale = false;
+
 	/** Session level meta data. */
 	private MetaDataEntry<?>[] metaData;
 
@@ -240,6 +284,7 @@ public abstract class Session implements IClusterable, IEventSink, IMetadataCont
 				"Request#getLocale() cannot return null, request has to have a locale set on it");
 		}
 		this.locale = new AtomicReference<>(locale);
+		rtlLocale = isRtlLanguage(locale);
 
 		pageAccessSynchronizer = new PageAccessSynchronizerProvider();
 	}
@@ -606,12 +651,22 @@ public abstract class Session implements IClusterable, IEventSink, IMetadataCont
 		if (!Objects.equal(getLocale(), locale))
 		{
 			this.locale.set(locale);
+			rtlLocale = isRtlLanguage(locale);
 			dirty();
 		}
 		return this;
 	}
 
 	/**
+	 * Method to determine if language of current locale is RTL (right-to-left) or not
+	 *
+	 * @return <code>true</code> if language of session locale is RTL (right-to-left), <code>false</code> otherwise
+	 */
+	public boolean isRtlLocale() {
+		return rtlLocale;
+	}
+
+	/**
 	 * Sets the metadata for this session using the given key. If the metadata object is not of the
 	 * correct type for the metadata key, an IllegalArgumentException will be thrown. For
 	 * information on creating MetaDataKeys, see {@link MetaDataKey}.
diff --git a/wicket-core/src/test/java/org/apache/wicket/protocol/http/WebSessionTest.java b/wicket-core/src/test/java/org/apache/wicket/protocol/http/WebSessionTest.java
index 2eb05a7..164cda6 100644
--- a/wicket-core/src/test/java/org/apache/wicket/protocol/http/WebSessionTest.java
+++ b/wicket-core/src/test/java/org/apache/wicket/protocol/http/WebSessionTest.java
@@ -17,11 +17,14 @@
 package org.apache.wicket.protocol.http;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 import java.util.Locale;
+import java.util.stream.Stream;
 
 import org.apache.wicket.Session;
 import org.apache.wicket.WicketRuntimeException;
@@ -31,6 +34,8 @@ import org.apache.wicket.protocol.http.mock.MockHttpSession;
 import org.apache.wicket.request.Url;
 import org.apache.wicket.util.tester.WicketTester;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
 
 /**
  * @author Timo Rantalaiho
@@ -87,7 +92,7 @@ class WebSessionTest
 		Session session = tester.getSession();
 
 		session.getPageManager();
-		
+
 		session.detach();
 
 		try
@@ -100,4 +105,52 @@ class WebSessionTest
 			assertEquals("The request has been processed. Access to pages is no longer allowed", ex.getMessage());
 		}
 	}
+
+	private static Stream<String> provideLTRtags() {
+		return Stream.of("en", "en-US", "ko", "bg", "ar-Latn", "fa-Cyrl");
+	}
+
+	private static Stream<String> provideRTLtags() {
+		return Stream.of("ar", "dv", "he", "iw", "fa", "nqo", "ps", "sd", "ug", "ur", "yi", "en-Arab-US", "ru-Hebr", "nl-Thaa", "fi-Nkoo", "fr-Tfng");
+	}
+
+	private Session createSessionViaConstructor(String langTag) {
+		MockWebRequest rq = new MockWebRequest(Url.parse("/"));
+		rq.setLocale(Locale.forLanguageTag(langTag));
+		return new WebSession(rq);
+	}
+
+	@ParameterizedTest
+	@MethodSource("provideLTRtags")
+	void testConstructorLtr(String langTag) {
+		Session session = createSessionViaConstructor(langTag);
+		assertFalse(session.isRtlLocale(), langTag + " should be LTR (left-to-right)");
+	}
+
+	@ParameterizedTest
+	@MethodSource("provideRTLtags")
+	void testConstructorRtl(String langTag) {
+		Session session = createSessionViaConstructor(langTag);
+		assertTrue(session.isRtlLocale(), langTag + " should be RTL (right-to-left)");
+	}
+
+	@ParameterizedTest
+	@MethodSource("provideLTRtags")
+	void testSetterLtr(String langTag) {
+		WicketTester tester = new WicketTester(new MockApplication());
+		Session session = tester.getSession();
+
+		session.setLocale(Locale.forLanguageTag(langTag));
+		assertFalse(session.isRtlLocale(), langTag + " should be LTR (left-to-right)");
+	}
+
+	@ParameterizedTest
+	@MethodSource("provideRTLtags")
+	void testSetterRtl(String langTag) {
+		WicketTester tester = new WicketTester(new MockApplication());
+		Session session = tester.getSession();
+
+		session.setLocale(Locale.forLanguageTag(langTag));
+		assertTrue(session.isRtlLocale(), langTag + " should be RTL (right-to-left)");
+	}
 }