package locale_config
import std.regex.{Regex,MatchData}
// import std.unittest
import std.collection.{collectArray, ArrayList}
var REGULAR_LANGUAGE_RANGE_REGEX: Option<Regex> = None
var LANGUAGE_RANGE_REGEX: Option<Regex> = None
var UNIX_INVARIANT_REGEX: Option<Regex> = None
var UNIX_TAG_REGEX: Option<Regex> = None
type RawL = String
extend MatchData{
func matchStr(i:Int64){
try {
this.matchString(i)
}catch(e:Exception){
""
}
}
func matchStr(i:String){
try{
this.matchString(i)
}catch(e:Exception){
""
}
}
}
/**
* \namespace locale_config
* @brief LocaleConfig
*/
func canon_lower(o: Option<String>) {
match (o) {
case None => ""
case Some(s) => s.toAsciiLower()
}
}
func canon_script(o: String) {
// log.SimpleLogger().info("Canon script ${o}")
if (o == "") {
return ""
}
if (!(o.size >= 2 && o[0..1] == '-')) {
throw IllegalFormatException()
}
if (o.get(1).getOrThrow().isAsciiUpperCase() && o.toRuneArray()[2..].iterator().all {t => t.isAsciiLowerCase()}) {
o
} else {
"-" + o.toRuneArray()[1].toAsciiUpperCase().toString() + String(
collectArray<Rune>(o.toRuneArray()[2..].iterator().map {s => s.toAsciiLowerCase()}))
}
}
func canon_upper(o: String): String {
if (o.size == 0) {
return ""
}
if (!(o.size >= 2 && o[0..1] == "-")) {
throw IllegalFormatException()
}
if (o.iterator().all {i => i.isAsciiLowerCase()}) {
o
} else {
o.toAsciiUpper()
}
}
/**
* @brief 语言与文化识别器。
* 这个类持有一个 [RFC4647] 所规定的语言范围。
* @see https://www.rfc-editor.org/rfc/rfc5646.txt
* @see https://www.rfc-editor.org/rfc/rfc4647.txt
*/
public struct LanguageRange {
public var l: RawL
/**
* Construct LanguageRange from string, with normalization.
*
* LanguageRange must follow the [RFC4647] syntax.
* It will be case-normalized as recommended in [RFC5646] §2.1.1, namely:
*
* * `language`, if recognized, is written in lowercase,
* * `script`, if recognized, is written with first capital,
* * `country`, if recognized, is written in uppercase and
* * all other subtags are written in lowercase.
*
* @see [RFC5646] https://www.rfc-editor.org/rfc/rfc5646.txt
* @see [RFC4647] https://www.rfc-editor.org/rfc/rfc4647.txt
*/
public init(lt: String, r!: Bool = false) {
if (r) {
this.l = lt
return
}
if (lt == "") {
this.l = lt
return
} else {
if (REGULAR_LANGUAGE_RANGE_REGEX.isNone()) {
REGULAR_LANGUAGE_RANGE_REGEX = Some(
Regex(
(#"^(?P<language>(?:[[:alpha:]]{2,3}(?:-[[:alpha:]]{3}){0,3}|\*))(?P<script>-(?:[[:alpha:]]{4}|\*))?(?P<region>-(?:[[:alpha:]]{2}|[[:digit:]]{3}|\* ))?(?P<rest>(?:-(?:[[:alnum:]]{1,8}|\*))*)$"#)
))
}
let m = REGULAR_LANGUAGE_RANGE_REGEX.getOrThrow().find(lt,group:true)
if (m.isSome()) {
let m = m.getOrThrow()
// log.getGlobalLogger().info("Regex ${m.groupNumber()} L:${m.matchStr(1)} S:${if(m.groupNumber()>=2){m.matchStr(2)}else{" "}} R:${if(m.groupNumber()>=3){m.matchStr(3)}else{" "}} r:${if(m.groupNumber()>=4){m.matchStr(4)}else{""}}")
let lang = canon_lower(m.matchString("language"))
// println("Regex ${m.groupNumber()} L:${m.matchString(1)} S:${if(m.groupNumber()>=2){m.matchString(2)}else{" "}} R:${if(m.groupNumber()>=3){m.matchString(3)}else{" "}} r:${if(m.groupNumber()>=4){m.matchString(4)}else{""}}")
let script = canon_script(if (m.groupCount() >= 2) {
m.matchStr("script")
} else {
""
})
let region = canon_upper(if (m.groupCount() >= 3) {
m.matchStr("region")
} else {
""
})
let rest = canon_lower(if (m.groupCount() >= 4) {
m.matchStr("rest")
} else {
""
})
// log.SimpleLogger().info("L:${lang} S:${script} R:${region} r:${rest}")
this.l = lang + script + region + rest
} else {
if (LANGUAGE_RANGE_REGEX.isNone()) {
LANGUAGE_RANGE_REGEX = Regex(#"^(?:[[:alpha:]]{1,8}|\*)(?:-(?: [[:alnum:]]{1,8}|\*))*$"#)
}
match (LANGUAGE_RANGE_REGEX.getOrThrow().find(lt,group:true)) {
case Some(_) => this.l = canon_lower(lt)
case None => throw IllegalFormatException("Not well formed languague range")
}
}
}
}
public static func New(lt: String) {
return LanguageRange(lt).toString()
}
/**
* @brief 返回用于默认语言环境的 LanguageRange。
*
* 默认语言通过空字符串简单标识。
*/
public static func invariant() {
LanguageRange("")
}
/**
* @brief 从 Unix/Linux/GNU 本地化标签创建语言标签。
*
* Unix 本地化标签的格式为
*
* > *语言* [ `_` *区域* ] [ `.` *编码* ] [ `@` *变体* ]
*
* @note 语言 和 区域 的格式与 RFC5646 相同。编码 在这里不相关,因为 仓颉 始终使用 Utf-8。剩下的 变体,不幸的是,它的格式相对自由。因此,这个函数将已知的变体转换为相应的 RFC5646 子标签,并用 Unicode POSIX 变体(`-u-va-`)扩展表示其他任何内容。
*
* @note 这个函数在这里是公开的,以便那些可能遇到来自于系统配置之外的这种标签的应用程序使用。
*/
public static func fromUnix(s: String): LanguageRange {
if (UNIX_TAG_REGEX.isNone()) {
UNIX_TAG_REGEX = Regex(
#"^(?P<language>[[:alpha:]]{2,3})(?:_(?P<region>[[:alpha:]]{2}|[[:digit:]]{3}))?(?:\.(?P<encoding>[0-9a-zA-Z-]{1,20}))?(?:@(?P<variant>[[:alnum:]]{1,20}))?$"#
)
}
match (UNIX_TAG_REGEX.getOrThrow().find(s,group:true)) {
case Some(caps) =>
let src_variant = if (caps.groupCount() >= 4) {
caps.matchStr(4).toAsciiLower()
} else {
""
}
var res = caps.matchStr(1).toAsciiLower()
let region = caps.matchStr(2)
var script = ""
var variant = ""
var uvariant = ""
match (src_variant) {
case "arabic" => script = "Arab"
case "cyrl" => script = "Cyrl"
case "cyrillic" => script = "Cyrl"
case "devanagari" => script = "Deva"
case "hebrew" => script = "Hebr"
case "iqtelif" => script = "Latn"
case "latn" => script = "Latn"
case "latin" => script = "Latn"
case "shaw" => script = "Shaw"
case "ijekavianlatin" =>
script = "Latn"
variant = "ijekavsk"
case "ije" => variant = "ijekavsk"
case "ijekavian" => variant = "ijekavsk"
case "valencia" => variant = "valencia"
// case "euro"=>
case s =>
if (s != "euro") {
if (s.size <= 8) {
uvariant = s
} else {
uvariant = s[0..8]
}
if (res == "aa" && s == "saaho") {
res = "ssy"
}
}
}
if (script != "") {
res += "-"
res += script
}
if (region != "") {
res += "-"
res += region.toAsciiUpper()
}
if (variant != "") {
res += "-"
res += variant
}
if (uvariant != "") {
res += "-u-va-"
res += uvariant
}
LanguageRange(res, r: true)
case None =>
if (UNIX_INVARIANT_REGEX.isNone()) {
UNIX_INVARIANT_REGEX = Regex(#"^(?:c|posix)(?:\.(?:[0-9a-zA-Z-]{1,20}))?$"#)
}
match (UNIX_INVARIANT_REGEX.getOrThrow().find(s,group:true)) {
case Some(_) => LanguageRange.invariant()
case None => throw IllegalFormatException("Not well formed")
}
}
}
}
extend LanguageRange <: ToString {
public func toString(): String {
this.l
}
}
extend LanguageRange <: Equatable<LanguageRange> {
public operator func ==(l: LanguageRange) {
this.toString() == l.toString()
}
}
var LOCALE_ELEMENT_REGEX: Option<Regex> = None
/**
* @brief 本地化配置。
*
*
*/
public class Locale {
public var inner: String
init(i: String) {
this.inner = i
}
/**
* @brief 从字符串创建 Locale 配置。
*/
public static func new(s: String): Locale {
var i = s.lazySplit(",")
var res = Locale.from(LanguageRange(i.next().getOrThrow()))
if (LOCALE_ELEMENT_REGEX.isNone()) {
LOCALE_ELEMENT_REGEX = Regex(#"^(?:(?P<category>[[:alpha:]]{1,20})=)?(?P<tag>(?:[[:alnum:]]|-|\*)+)$"#)
}
for (f in i) {
if (let Some(caps) <- LOCALE_ELEMENT_REGEX.getOrThrow().find(f,group:true)) {
let tag = LanguageRange(caps.matchStr("tag"))
if (caps.matchStr("category").size != 0) {
let cat = caps.matchStr("category")
res.addCategory(cat.toAsciiLower(), tag.toString())
} else {
res.add(tag)
}
} else {
throw IllegalArgumentException("Not well formed")
}
}
res
}
public func add(tag: LanguageRange): Unit {
for (i in this.inner.lazySplit(',')) {
if (i == tag.toString()) {
return
}
}
this.inner += ",${tag}"
}
public func toTags() {
Tags(this.inner.split(","))
}
public func addCategory(category: String, tag: String): Unit {
if (this.inner.lazySplit(',').next() == tag) {
return
}
for (i in this.inner.lazySplit(',')) {
if (i.startsWith(category) && i[category.size..].startsWith("=") && i[category.size + 1..] == tag) {
return
}
}
this.inner += ",${category}=${tag}"
}
public func toTagsFor(category: String) {
var tags = this.inner.split(",")
let ti1 = tags.iterator()
let ti2 = tags.iterator()
while (let Some(s) <- ti2.next()) {
if (s.startsWith(category) && s[category.size..].startsWith("=")) {
return TagsFor(this.inner, collectArray<String>(ti1), category)
}
ti1.next()
}
TagsFor(this.inner, this.inner.split(","), None)
}
public static func from(lr: LanguageRange): Locale {
Locale(lr.toString())
}
public static func invariant(): Locale {
Locale.from(LanguageRange.invariant())
}
}
extend Locale <: ToString {
public func toString() {
this.inner
}
}
public struct TagsFor {
public let src: String
public let tags: Array<String>
public let category: Option<String>
public init(src: String, tags: Array<String>, category: Option<String>) {
this.src = src
this.tags = tags
this.category = category
}
}
extend TagsFor <: Iterable<TagsForIteratorItem> {
public func iterator() {
TagsForIterator(this)
}
}
private type TagsIteratorItemOld = (Option<String>, LanguageRange)
public class TagsIteratorItem {
public var category: Option<String>
public var tag: LanguageRange
public init(category: Option<String>, tag: LanguageRange) {
this.category = category
this.tag = tag
}
}
extend TagsIteratorItem <: Equatable<TagsIteratorItem> {
public operator func ==(right: TagsIteratorItem) {
this.category == right.category && this.tag == right.tag
}
}
extend TagsIteratorItem <: Equatable<TagsIteratorItemOld> {
public operator func ==(right: TagsIteratorItemOld): Bool {
this.category == right[0] && this.tag == right[1]
}
}
public class TagsIterator <: Iterator<TagsIteratorItem> {
let tags: Iterator<String>
public init(t: Tags) {
this.tags = t.tags.iterator()
}
public func next(): Option<TagsIteratorItem> {
if (let Some(s) <- this.tags.next()) {
let f = {
=>
var st = s.toRuneArray()
for (i in 0..st.size) {
if (st[i] == r'=') {
return Some(i)
}
}
Option<Int64>.None
}()
if (let Some(i) <- f) {
TagsIteratorItem(String(s.toRuneArray()[..i]), LanguageRange(String(s.toRuneArray()[i + 1..]), r: true))
} else {
TagsIteratorItem(None, LanguageRange(s))
}
} else {
None
}
}
}
public struct Tags {
public let tags: Array<String>
public init(tags: Array<String>) {
this.tags = tags
}
}
extend Tags <: Iterable<TagsIteratorItem> {
public func iterator() {
TagsIterator(this)
}
}
type TagsForIteratorItem = LanguageRange
public class TagsForIterator <: Iterator<TagsForIteratorItem> {
var tags: Iterator<String>
var category: Option<String>
let src: String
public init(tf: TagsFor) {
this.tags = tf.tags.iterator()
this.category = tf.category
this.src = tf.src
}
public func next(): Option<TagsForIteratorItem> {
if (let Some(cat) <- this.category) {
while (let Some(s) <- this.tags.next()) {
if (s.startsWith(cat) && s[cat.size..].startsWith("=")) {
return LanguageRange(s[cat.size + 1..])
}
}
this.category = None
this.tags = this.src.split(",").iterator()
}
while (let Some(s) <- this.tags.next()) {
let f = {
=>
var st = s.toRuneArray()
for (i in 0..st.size) {
if (st[i] == r'=') {
return Some(i)
}
}
Option<Int64>.None
}()
if (f.isNone()) {
return Some(LanguageRange(s))
}
}
None
}
}
let USER_LOCALE = system_locale()
var GLOBAL_LOCALE = USER_LOCALE
extend Locale{
/**
* @brief 由操作系统提供的 Locale 配置。
*/
public static prop userDefault:Option<Locale>{
get(){
USER_LOCALE
}
}
/**
* @brief 全局 Locale 配置,默认为 locale_config.Locale.userDefault 。
*/
public static mut prop globalDefault:Option<Locale>{
get(){
GLOBAL_LOCALE
}
set(v){
GLOBAL_LOCALE = v
}
}
}
let CURRENT_LOCALE = ThreadLocal<Locale>()
extend Locale{
/**
* @brief 获取当前线程的 Locale 配置,默认下为 locale_config.Locale.globalDefault 。
* @note 当被赋值为None时,current将被设置为 locale_config.Locale.globalDefault 的副本。
*/
public static mut prop current:Option<Locale>{
get(){
if(let Some(l)<-CURRENT_LOCALE.get()){
l
}else{
GLOBAL_LOCALE
}
}
set(v){
if(v.isNone()){
CURRENT_LOCALE.set(GLOBAL_LOCALE)
}else{
CURRENT_LOCALE.set(v)
}
}
}
}