Merge branch 'po-popover' into 'master'
Use po for overlay layout translations too Closes #316 See merge request World/Phosh/squeekboard!504
This commit is contained in:
@ -1,22 +0,0 @@
|
|||||||
ara Арабски
|
|
||||||
be Белгийски
|
|
||||||
bg Български
|
|
||||||
br Бразилски
|
|
||||||
cz Чешки
|
|
||||||
de Немски
|
|
||||||
dk Датски
|
|
||||||
es Испански
|
|
||||||
emoji Емоджи
|
|
||||||
fi Фински
|
|
||||||
fr Френски
|
|
||||||
gr Гръцки
|
|
||||||
it Италиански
|
|
||||||
jp Японски
|
|
||||||
no Норвежки
|
|
||||||
pl Полски
|
|
||||||
ru Руски
|
|
||||||
se Шведски
|
|
||||||
th Тайски
|
|
||||||
ua Украински
|
|
||||||
terminal Терминал
|
|
||||||
us Английски (САЩ)
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
be Belgická
|
|
||||||
cz Česká
|
|
||||||
cz+qwerty Česká (QWERTY)
|
|
||||||
de Německá
|
|
||||||
dk Dánská
|
|
||||||
emoji Emoji
|
|
||||||
es Španělská
|
|
||||||
fi Finská
|
|
||||||
fr Francouzská
|
|
||||||
gr Řecká
|
|
||||||
it Italská
|
|
||||||
jp Japonská
|
|
||||||
jp+kana Japonská (Kana)
|
|
||||||
no Norská
|
|
||||||
pl Polská
|
|
||||||
ru Ruská
|
|
||||||
se Švédská
|
|
||||||
terminal Terminál
|
|
||||||
th Thajská
|
|
||||||
ua Ukrajinská
|
|
||||||
us Anglická (USA)
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
emoji Emoji
|
|
||||||
terminal Terminal
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
emoji Emoji
|
|
||||||
terminal Terminal
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
us Inglés (EE.UU.)
|
|
||||||
de Alemán
|
|
||||||
el Griego
|
|
||||||
es Español
|
|
||||||
it Italiano
|
|
||||||
jp+kana Japonés (Kana)
|
|
||||||
no Noruego
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
emoji ایموجی
|
|
||||||
terminal ترمینال
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
be Belgjic
|
|
||||||
br Brasilian
|
|
||||||
de Todesc
|
|
||||||
dk Danês
|
|
||||||
es Spagnûl
|
|
||||||
fi Finlandês
|
|
||||||
fr Francês
|
|
||||||
it+fur Furlan
|
|
||||||
gr Grêc
|
|
||||||
it Talian
|
|
||||||
jp+kana Gjaponês (Kana)
|
|
||||||
no Norvegjês
|
|
||||||
pl Polac
|
|
||||||
ru Rus
|
|
||||||
se Svedês
|
|
||||||
terminal Terminâl
|
|
||||||
ua Ucrain
|
|
||||||
us American (USA)
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
be בלגית
|
|
||||||
br פורטוגזית (ברזיל)
|
|
||||||
cz צ'כית
|
|
||||||
de גרמנית
|
|
||||||
dk דנית
|
|
||||||
es ספרדית
|
|
||||||
emoji אימוג'י
|
|
||||||
fi פינית
|
|
||||||
fr צרפתית
|
|
||||||
gr יוונית
|
|
||||||
il עברית
|
|
||||||
it איטלקית
|
|
||||||
no נורווגית
|
|
||||||
pl פולנית
|
|
||||||
ru רוסית
|
|
||||||
se שוודית
|
|
||||||
terminal טרמינל
|
|
||||||
ua אוקראינית
|
|
||||||
us אנגלית (ארה"ב)
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
de Գերմաներէն
|
|
||||||
es Իսպաներէն
|
|
||||||
fi Ֆիններէն
|
|
||||||
gr Յունարէն
|
|
||||||
it Իտալերէն
|
|
||||||
no Նորուեգերէն
|
|
||||||
pl Լեհերէն
|
|
||||||
ru Ռուսերէն
|
|
||||||
se Շուեդերէն
|
|
||||||
terminal Տերմինալ
|
|
||||||
us Անգլերէն (ԱՄՆ)
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
emoji emoji
|
|
||||||
terminal terminal
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
de Немецкий
|
|
||||||
es Испанский
|
|
||||||
fi Финский
|
|
||||||
gr Греческий
|
|
||||||
it Итальянский
|
|
||||||
no Норвежский
|
|
||||||
pl Польский
|
|
||||||
ru Русский
|
|
||||||
se Шведский
|
|
||||||
terminal Терминал
|
|
||||||
us Английский (США)
|
|
||||||
23
data/popover.ui
Normal file
23
data/popover.ui
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<menu id="app-menu">
|
||||||
|
<item>
|
||||||
|
<!-- translators: This is a emmoji keyboard layout -->
|
||||||
|
<attribute name="label" translatable="yes">Emoji</attribute>
|
||||||
|
<attribute name="action">layout</attribute>
|
||||||
|
<attribute name="target">emoji</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<!-- translators: This is a terminal keyboard layout -->
|
||||||
|
<attribute name="label" translatable="yes">Terminal</attribute>
|
||||||
|
<attribute name="action">layout</attribute>
|
||||||
|
<attribute name="target">terminal</attribute>
|
||||||
|
</item>
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Keyboard Settings</attribute>
|
||||||
|
<attribute name="action">settings</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
|
</menu>
|
||||||
|
</interface>
|
||||||
@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- Currently only serves translations until we make popover.rs use it -->
|
|
||||||
<interface>
|
|
||||||
<menu>
|
|
||||||
<section>
|
|
||||||
<item>
|
|
||||||
<attribute name="label" translatable="yes">Keyboard Settings</attribute>
|
|
||||||
<attribute name="action">settings</attribute>
|
|
||||||
</item>
|
|
||||||
</section>
|
|
||||||
</menu>
|
|
||||||
</interface>
|
|
||||||
@ -4,7 +4,7 @@
|
|||||||
<file compressed="true">common.css</file>
|
<file compressed="true">common.css</file>
|
||||||
<file compressed="true">style.css</file>
|
<file compressed="true">style.css</file>
|
||||||
<file compressed="true">style-Adwaita:dark.css</file>
|
<file compressed="true">style-Adwaita:dark.css</file>
|
||||||
<file compressed="true" preprocess="xml-stripblanks">popup.ui</file>
|
<file compressed="true" preprocess="xml-stripblanks">popover.ui</file>
|
||||||
<file>icons/key-enter.svg</file>
|
<file>icons/key-enter.svg</file>
|
||||||
<file>icons/key-shift.svg</file>
|
<file>icons/key-shift.svg</file>
|
||||||
<file>icons/keyboard-mode-symbolic.svg</file>
|
<file>icons/keyboard-mode-symbolic.svg</file>
|
||||||
|
|||||||
@ -26,7 +26,6 @@ pub mod imservice;
|
|||||||
mod keyboard;
|
mod keyboard;
|
||||||
mod layout;
|
mod layout;
|
||||||
mod locale;
|
mod locale;
|
||||||
mod locale_config;
|
|
||||||
mod manager;
|
mod manager;
|
||||||
mod outputs;
|
mod outputs;
|
||||||
mod popover;
|
mod popover;
|
||||||
|
|||||||
@ -88,15 +88,6 @@ impl Drop for XkbInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct Translation<'a>(pub &'a str);
|
|
||||||
|
|
||||||
impl<'a> Translation<'a> {
|
|
||||||
pub fn to_owned(&'a self) -> OwnedTranslation {
|
|
||||||
OwnedTranslation(self.0.to_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct OwnedTranslation(pub String);
|
pub struct OwnedTranslation(pub String);
|
||||||
|
|
||||||
|
|||||||
@ -1,535 +0,0 @@
|
|||||||
/*! Locale detection and management.
|
|
||||||
* Based on https://github.com/rust-locale/locale_config
|
|
||||||
*
|
|
||||||
* Ready for deletion/replacement once Debian starts packaging this,
|
|
||||||
* although this version doesn't need lazy_static.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2016–2019 Jan Hudec <bulb@ucw.cz>
|
|
||||||
Copyright (c) 2016 A.J. Gardner <aaron.j.gardner@gmail.com>
|
|
||||||
Copyright (c) 2019, Bastien Orivel <eijebong@bananium.fr>
|
|
||||||
Copyright (c) 2019, Igor Gnatenko <i.gnatenko.brain@gmail.com>
|
|
||||||
Copyright (c) 2019, Sophie Tauchert <999eagle@999eagle.moe>
|
|
||||||
*/
|
|
||||||
|
|
||||||
use regex::Regex;
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
/// Errors that may be returned by `locale_config`.
|
|
||||||
#[derive(Copy,Clone,Debug,PartialEq,Eq)]
|
|
||||||
pub enum Error {
|
|
||||||
/// Provided definition was not well formed.
|
|
||||||
///
|
|
||||||
/// This is returned when provided configuration string does not match even the rather loose
|
|
||||||
/// definition for language range from [RFC4647] or the composition format used by `Locale`.
|
|
||||||
///
|
|
||||||
/// [RFC4647]: https://www.rfc-editor.org/rfc/rfc4647.txt
|
|
||||||
NotWellFormed,
|
|
||||||
/// Placeholder for adding more errors in future. **Do not match!**.
|
|
||||||
__NonExhaustive,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ::std::fmt::Display for Error {
|
|
||||||
fn fmt(&self, out: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
|
||||||
out.write_str(match self {
|
|
||||||
&Error::NotWellFormed => "Language tag is not well-formed.",
|
|
||||||
// this is exception: here we do want exhaustive match so we don't publish version with
|
|
||||||
// missing descriptions by mistake.
|
|
||||||
&Error::__NonExhaustive => panic!("Placeholder error must not be instantiated!"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Convenience Result alias.
|
|
||||||
type Result<T> = ::std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
/// Iterator over `LanguageRange`s for specific category in a `Locale`
|
|
||||||
///
|
|
||||||
/// Returns `LanguageRange`s in the `Locale` that are applicable to provided category. The tags
|
|
||||||
/// are returned in order of preference, which means the category-specific ones first and then
|
|
||||||
/// the generic ones.
|
|
||||||
///
|
|
||||||
/// The iterator is guaranteed to return at least one value.
|
|
||||||
pub struct TagsFor<'a, 'c> {
|
|
||||||
src: &'a str,
|
|
||||||
tags: std::str::Split<'a, &'static str>,
|
|
||||||
category: Option<&'c str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'c> Iterator for TagsFor<'a, 'c> {
|
|
||||||
type Item = LanguageRange<'a>;
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
if let Some(cat) = self.category {
|
|
||||||
while let Some(s) = self.tags.next() {
|
|
||||||
if s.starts_with(cat) && s[cat.len()..].starts_with("=") {
|
|
||||||
return Some(
|
|
||||||
LanguageRange { language: Cow::Borrowed(&s[cat.len()+1..]) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.category = None;
|
|
||||||
self.tags = self.src.split(",");
|
|
||||||
}
|
|
||||||
while let Some(s) = self.tags.next() {
|
|
||||||
if s.find('=').is_none() {
|
|
||||||
return Some(
|
|
||||||
LanguageRange{ language: Cow::Borrowed(s) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Language and culture identifier.
|
|
||||||
///
|
|
||||||
/// This object holds a [RFC4647] extended language range.
|
|
||||||
///
|
|
||||||
/// The internal data may be owned or shared from object with lifetime `'a`. The lifetime can be
|
|
||||||
/// extended using the `into_static()` method, which internally clones the data as needed.
|
|
||||||
///
|
|
||||||
/// # Syntax
|
|
||||||
///
|
|
||||||
/// The range is composed of `-`-separated alphanumeric subtags, possibly replaced by `*`s. It
|
|
||||||
/// might be empty.
|
|
||||||
///
|
|
||||||
/// In agreement with [RFC4647], this object only requires that the tag matches:
|
|
||||||
///
|
|
||||||
/// ```ebnf
|
|
||||||
/// language_tag = (alpha{1,8} | "*")
|
|
||||||
/// ("-" (alphanum{1,8} | "*"))*
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// The exact interpretation is up to the downstream localization provider, but it expected that
|
|
||||||
/// it will be matched against a normalized [RFC5646] language tag, which has the structure:
|
|
||||||
///
|
|
||||||
/// ```ebnf
|
|
||||||
/// language_tag = language
|
|
||||||
/// ("-" script)?
|
|
||||||
/// ("-" region)?
|
|
||||||
/// ("-" variant)*
|
|
||||||
/// ("-" extension)*
|
|
||||||
/// ("-" private)?
|
|
||||||
///
|
|
||||||
/// language = alpha{2,3} ("-" alpha{3}){0,3}
|
|
||||||
///
|
|
||||||
/// script = aplha{4}
|
|
||||||
///
|
|
||||||
/// region = alpha{2}
|
|
||||||
/// | digit{3}
|
|
||||||
///
|
|
||||||
/// variant = alphanum{5,8}
|
|
||||||
/// | digit alphanum{3}
|
|
||||||
///
|
|
||||||
/// extension = [0-9a-wyz] ("-" alphanum{2,8})+
|
|
||||||
///
|
|
||||||
/// private = "x" ("-" alphanum{1,8})+
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// * `language` is an [ISO639] 2-letter or, where not defined, 3-letter code. A code for
|
|
||||||
/// macro-language might be followed by code of specific dialect.
|
|
||||||
/// * `script` is an [ISO15924] 4-letter code.
|
|
||||||
/// * `region` is either an [ISO3166] 2-letter code or, for areas other than countries, [UN M.49]
|
|
||||||
/// 3-digit numeric code.
|
|
||||||
/// * `variant` is a string indicating variant of the language.
|
|
||||||
/// * `extension` and `private` define additional options. The private part has same structure as
|
|
||||||
/// the Unicode [`-u-` extension][u_ext]. Available options are documented for the facets that
|
|
||||||
/// use them.
|
|
||||||
///
|
|
||||||
/// The values obtained by inspecting the system are normalized according to those rules.
|
|
||||||
///
|
|
||||||
/// The content will be case-normalized as recommended in [RFC5646] §2.1.1, namely:
|
|
||||||
///
|
|
||||||
/// * `language` is written in lowercase,
|
|
||||||
/// * `script` is written with first capital,
|
|
||||||
/// * `country` is written in uppercase and
|
|
||||||
/// * all other subtags are written in lowercase.
|
|
||||||
///
|
|
||||||
/// When detecting system configuration, additional options that may be generated under the
|
|
||||||
/// [`-u-` extension][u_ext] currently are:
|
|
||||||
///
|
|
||||||
/// * `cf` — Currency format (`account` for parenthesized negative values, `standard` for minus
|
|
||||||
/// sign).
|
|
||||||
/// * `fw` — First day of week (`mon` to `sun`).
|
|
||||||
/// * `hc` — Hour cycle (`h12` for 1–12, `h23` for 0–23).
|
|
||||||
/// * `ms` — Measurement system (`metric` or `ussystem`).
|
|
||||||
/// * `nu` — Numbering system—only decimal systems are currently used.
|
|
||||||
/// * `va` — Variant when locale is specified in Unix format and the tag after `@` does not
|
|
||||||
/// correspond to any variant defined in [Language subtag registry].
|
|
||||||
///
|
|
||||||
/// And under the `-x-` extension, following options are defined:
|
|
||||||
///
|
|
||||||
/// * `df` — Date format:
|
|
||||||
///
|
|
||||||
/// * `iso`: Short date should be in ISO format of `yyyy-MM-dd`.
|
|
||||||
///
|
|
||||||
/// For example `-df-iso`.
|
|
||||||
///
|
|
||||||
/// * `dm` — Decimal separator for monetary:
|
|
||||||
///
|
|
||||||
/// Followed by one or more Unicode codepoints in hexadecimal. For example `-dm-002d` means to
|
|
||||||
/// use comma.
|
|
||||||
///
|
|
||||||
/// * `ds` — Decimal separator for numbers:
|
|
||||||
///
|
|
||||||
/// Followed by one or more Unicode codepoints in hexadecimal. For example `-ds-002d` means to
|
|
||||||
/// use comma.
|
|
||||||
///
|
|
||||||
/// * `gm` — Group (thousand) separator for monetary:
|
|
||||||
///
|
|
||||||
/// Followed by one or more Unicode codepoints in hexadecimal. For example `-dm-00a0` means to
|
|
||||||
/// use non-breaking space.
|
|
||||||
///
|
|
||||||
/// * `gs` — Group (thousand) separator for numbers:
|
|
||||||
///
|
|
||||||
/// Followed by one or more Unicode codepoints in hexadecimal. For example `-ds-00a0` means to
|
|
||||||
/// use non-breaking space.
|
|
||||||
///
|
|
||||||
/// * `ls` — List separator:
|
|
||||||
///
|
|
||||||
/// Followed by one or more Unicode codepoints in hexadecimal. For example, `-ds-003b` means to
|
|
||||||
/// use a semicolon.
|
|
||||||
///
|
|
||||||
/// [RFC5646]: https://www.rfc-editor.org/rfc/rfc5646.txt
|
|
||||||
/// [RFC4647]: https://www.rfc-editor.org/rfc/rfc4647.txt
|
|
||||||
/// [ISO639]: https://en.wikipedia.org/wiki/ISO_639
|
|
||||||
/// [ISO15924]: https://en.wikipedia.org/wiki/ISO_15924
|
|
||||||
/// [ISO3166]: https://en.wikipedia.org/wiki/ISO_3166
|
|
||||||
/// [UN M.49]: https://en.wikipedia.org/wiki/UN_M.49
|
|
||||||
/// [u_ext]: http://www.unicode.org/reports/tr35/#u_Extension
|
|
||||||
/// [Language subtag registry]: https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
|
|
||||||
#[derive(Clone,Debug,Eq,Hash,PartialEq)]
|
|
||||||
pub struct LanguageRange<'a> {
|
|
||||||
language: Cow<'a, str>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> LanguageRange<'a> {
|
|
||||||
/// Return LanguageRange for the invariant locale.
|
|
||||||
///
|
|
||||||
/// Invariant language is identified simply by empty string.
|
|
||||||
pub fn invariant() -> LanguageRange<'static> {
|
|
||||||
LanguageRange { language: Cow::Borrowed("") }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create language tag from Unix/Linux/GNU locale tag.
|
|
||||||
///
|
|
||||||
/// Unix locale tags have the form
|
|
||||||
///
|
|
||||||
/// > *language* [ `_` *region* ] [ `.` *encoding* ] [ `@` *variant* ]
|
|
||||||
///
|
|
||||||
/// The *language* and *region* have the same format as RFC5646. *Encoding* is not relevant
|
|
||||||
/// here, since Rust always uses Utf-8. That leaves *variant*, which is unfortunately rather
|
|
||||||
/// free-form. So this function will translate known variants to corresponding RFC5646 subtags
|
|
||||||
/// and represent anything else with Unicode POSIX variant (`-u-va-`) extension.
|
|
||||||
///
|
|
||||||
/// Note: This function is public here for benefit of applications that may come across this
|
|
||||||
/// kind of tags from other sources than system configuration.
|
|
||||||
pub fn from_unix(s: &str) -> Result<LanguageRange<'static>> {
|
|
||||||
let unix_tag_regex = Regex::new(r"(?ix) ^
|
|
||||||
(?P<language> [[:alpha:]]{2,3} )
|
|
||||||
(?: _ (?P<region> [[:alpha:]]{2} | [[:digit:]]{3} ))?
|
|
||||||
(?: \. (?P<encoding> [0-9a-zA-Z-]{1,20} ))?
|
|
||||||
(?: @ (?P<variant> [[:alnum:]]{1,20} ))?
|
|
||||||
$ ").unwrap();
|
|
||||||
|
|
||||||
let unix_invariant_regex = Regex::new(r"(?ix) ^
|
|
||||||
(?: c | posix )
|
|
||||||
(?: \. (?: [0-9a-zA-Z-]{1,20} ))?
|
|
||||||
$ ").unwrap();
|
|
||||||
|
|
||||||
if let Some(caps) = unix_tag_regex.captures(s) {
|
|
||||||
let src_variant = caps.name("variant").map(|m| m.as_str()).unwrap_or("").to_ascii_lowercase();
|
|
||||||
let mut res = caps.name("language").map(|m| m.as_str()).unwrap().to_ascii_lowercase();
|
|
||||||
let region = caps.name("region").map(|m| m.as_str()).unwrap_or("");
|
|
||||||
let mut script = "";
|
|
||||||
let mut variant = "";
|
|
||||||
let mut uvariant = "";
|
|
||||||
match src_variant.as_ref() {
|
|
||||||
// Variants seen in the wild in GNU LibC (via http://lh.2xlibre.net/) or in Debian
|
|
||||||
// GNU/Linux Stretch system. Treatment of things not found in RFC5646 subtag registry
|
|
||||||
// (http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)
|
|
||||||
// or CLDR according to notes at https://wiki.openoffice.org/wiki/LocaleMapping.
|
|
||||||
// Dialects:
|
|
||||||
// aa_ER@saaho - NOTE: Can't be found under that name in RFC5646 subtag registry,
|
|
||||||
// but there is language Saho with code ssy, which is likely that thing.
|
|
||||||
"saaho" if res == "aa" => res = String::from("ssy"),
|
|
||||||
// Scripts:
|
|
||||||
// @arabic
|
|
||||||
"arabic" => script = "Arab",
|
|
||||||
// @cyrillic
|
|
||||||
"cyrl" => script = "Cyrl",
|
|
||||||
"cyrillic" => script = "Cyrl",
|
|
||||||
// @devanagari
|
|
||||||
"devanagari" => script = "Deva",
|
|
||||||
// @hebrew
|
|
||||||
"hebrew" => script = "Hebr",
|
|
||||||
// tt@iqtelif
|
|
||||||
// Neither RFC5646 subtag registry nor CLDR knows anything about this, but as best
|
|
||||||
// as I can tell it is Tatar name for Latin (default is Cyrillic).
|
|
||||||
"iqtelif" => script = "Latn",
|
|
||||||
// @Latn
|
|
||||||
"latn" => script = "Latn",
|
|
||||||
// @latin
|
|
||||||
"latin" => script = "Latn",
|
|
||||||
// en@shaw
|
|
||||||
"shaw" => script = "Shaw",
|
|
||||||
// Variants:
|
|
||||||
// sr@ijekavianlatin
|
|
||||||
"ijekavianlatin" => {
|
|
||||||
script = "Latn";
|
|
||||||
variant = "ijekavsk";
|
|
||||||
},
|
|
||||||
// sr@ije
|
|
||||||
"ije" => variant = "ijekavsk",
|
|
||||||
// sr@ijekavian
|
|
||||||
"ijekavian" => variant = "ijekavsk",
|
|
||||||
// ca@valencia
|
|
||||||
"valencia" => variant = "valencia",
|
|
||||||
// Currencies:
|
|
||||||
// @euro - NOTE: We follow suite of Java and Openoffice and ignore it, because it
|
|
||||||
// is default for all locales where it sometimes appears now, and because we use
|
|
||||||
// explicit currency in monetary formatting anyway.
|
|
||||||
"euro" => {},
|
|
||||||
// Collation:
|
|
||||||
// gez@abegede - NOTE: This is collation, but CLDR does not have any code for it,
|
|
||||||
// so we for the moment leave it fall through as -u-va- instead of -u-co-.
|
|
||||||
// Anything else:
|
|
||||||
// en@boldquot, en@quot, en@piglatin - just randomish stuff
|
|
||||||
// @cjknarrow - beware, it's gonna end up as -u-va-cjknarro due to lenght limit
|
|
||||||
s if s.len() <= 8 => uvariant = &*s,
|
|
||||||
s => uvariant = &s[0..8], // the subtags are limited to 8 chars, but some are longer
|
|
||||||
};
|
|
||||||
if script != "" {
|
|
||||||
res.push('-');
|
|
||||||
res.push_str(script);
|
|
||||||
}
|
|
||||||
if region != "" {
|
|
||||||
res.push('-');
|
|
||||||
res.push_str(&*region.to_ascii_uppercase());
|
|
||||||
}
|
|
||||||
if variant != "" {
|
|
||||||
res.push('-');
|
|
||||||
res.push_str(variant);
|
|
||||||
}
|
|
||||||
if uvariant != "" {
|
|
||||||
res.push_str("-u-va-");
|
|
||||||
res.push_str(uvariant);
|
|
||||||
}
|
|
||||||
return Ok(LanguageRange {
|
|
||||||
language: Cow::Owned(res)
|
|
||||||
});
|
|
||||||
} else if unix_invariant_regex.is_match(s) {
|
|
||||||
return Ok(LanguageRange::invariant())
|
|
||||||
} else {
|
|
||||||
return Err(Error::NotWellFormed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> AsRef<str> for LanguageRange<'a> {
|
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
self.language.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Locale configuration.
|
|
||||||
///
|
|
||||||
/// Users may accept several languages in some order of preference and may want to use rules from
|
|
||||||
/// different culture for some particular aspect of the program behaviour, and operating systems
|
|
||||||
/// allow them to specify this (to various extent).
|
|
||||||
///
|
|
||||||
/// The `Locale` objects represent the user configuration. They contain:
|
|
||||||
///
|
|
||||||
/// - The primary `LanguageRange`.
|
|
||||||
/// - Optional category-specific overrides.
|
|
||||||
/// - Optional fallbacks in case data (usually translations) for the primary language are not
|
|
||||||
/// available.
|
|
||||||
///
|
|
||||||
/// The set of categories is open-ended. The `locale` crate uses five well-known categories
|
|
||||||
/// `messages`, `numeric`, `time`, `collate` and `monetary`, but some systems define additional
|
|
||||||
/// ones (GNU Linux has additionally `paper`, `name`, `address`, `telephone` and `measurement`) and
|
|
||||||
/// these are provided in the user default `Locale` and other libraries can use them.
|
|
||||||
///
|
|
||||||
/// `Locale` is represented by a `,`-separated sequence of tags in `LanguageRange` syntax, where
|
|
||||||
/// all except the first one may be preceded by category name and `=` sign.
|
|
||||||
///
|
|
||||||
/// The first tag indicates the default locale, the tags prefixed by category names indicate
|
|
||||||
/// _overrides_ for those categories and the remaining tags indicate fallbacks.
|
|
||||||
///
|
|
||||||
/// Note that a syntactically valid value of HTTP `Accept-Language` header is a valid `Locale`. Not
|
|
||||||
/// the other way around though due to the presence of category selectors.
|
|
||||||
// TODO: Interning
|
|
||||||
#[derive(Clone,Debug,Eq,Hash,PartialEq)]
|
|
||||||
pub struct Locale {
|
|
||||||
// TODO: Intern the string for performance reasons
|
|
||||||
// XXX: Store pre-split to LanguageTags?
|
|
||||||
inner: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Locale {
|
|
||||||
/// Construct invariant locale.
|
|
||||||
///
|
|
||||||
/// Invariant locale is represented simply with empty string.
|
|
||||||
pub fn invariant() -> Locale {
|
|
||||||
Locale::from(LanguageRange::invariant())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Append fallback language tag.
|
|
||||||
///
|
|
||||||
/// Adds fallback to the end of the list.
|
|
||||||
pub fn add(&mut self, tag: &LanguageRange) {
|
|
||||||
for i in self.inner.split(',') {
|
|
||||||
if i == tag.as_ref() {
|
|
||||||
return; // don't add duplicates
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.inner.push_str(",");
|
|
||||||
self.inner.push_str(tag.as_ref());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Append category override.
|
|
||||||
///
|
|
||||||
/// Appending new override for a category that already has one will not replace the existing
|
|
||||||
/// override. This might change in future.
|
|
||||||
pub fn add_category(&mut self, category: &str, tag: &LanguageRange) {
|
|
||||||
if self.inner.split(',').next().unwrap() == tag.as_ref() {
|
|
||||||
return; // don't add useless override equal to the primary tag
|
|
||||||
}
|
|
||||||
for i in self.inner.split(',') {
|
|
||||||
if i.starts_with(category) &&
|
|
||||||
i[category.len()..].starts_with("=") &&
|
|
||||||
&i[category.len() + 1..] == tag.as_ref() {
|
|
||||||
return; // don't add duplicates
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.inner.push_str(",");
|
|
||||||
self.inner.push_str(category);
|
|
||||||
self.inner.push_str("=");
|
|
||||||
self.inner.push_str(tag.as_ref());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over `LanguageRange`s in this `Locale` applicable to given category.
|
|
||||||
///
|
|
||||||
/// Returns `LanguageRange`s in the `Locale` that are applicable to provided category. The tags
|
|
||||||
/// are returned in order of preference, which means the category-specific ones first and then
|
|
||||||
/// the generic ones.
|
|
||||||
///
|
|
||||||
/// The iterator is guaranteed to return at least one value.
|
|
||||||
pub fn tags_for<'a, 'c>(&'a self, category: &'c str) -> TagsFor<'a, 'c> {
|
|
||||||
let mut tags = self.inner.split(",");
|
|
||||||
while let Some(s) = tags.clone().next() {
|
|
||||||
if s.starts_with(category) && s[category.len()..].starts_with("=") {
|
|
||||||
return TagsFor {
|
|
||||||
src: self.inner.as_ref(),
|
|
||||||
tags: tags,
|
|
||||||
category: Some(category),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
tags.next();
|
|
||||||
}
|
|
||||||
return TagsFor {
|
|
||||||
src: self.inner.as_ref(),
|
|
||||||
tags: self.inner.split(","),
|
|
||||||
category: None,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Locale is specified by a string tag. This is the way to access it.
|
|
||||||
// FIXME: Do we want to provide the full string representation? We would have it as single string
|
|
||||||
// then.
|
|
||||||
impl AsRef<str> for Locale {
|
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
self.inner.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<LanguageRange<'a>> for Locale {
|
|
||||||
fn from(t: LanguageRange<'a>) -> Locale {
|
|
||||||
Locale {
|
|
||||||
inner: t.language.into_owned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tag(s: &str) -> Result<LanguageRange> {
|
|
||||||
LanguageRange::from_unix(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Read /etc/locale.alias
|
|
||||||
fn tag_inv(s: &str) -> LanguageRange {
|
|
||||||
tag(s).unwrap_or(LanguageRange::invariant())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn system_locale() -> Option<Locale> {
|
|
||||||
// LC_ALL overrides everything
|
|
||||||
if let Ok(all) = env::var("LC_ALL") {
|
|
||||||
if let Ok(t) = tag(all.as_ref()) {
|
|
||||||
return Some(Locale::from(t));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// LANG is default
|
|
||||||
let mut loc =
|
|
||||||
if let Ok(lang) = env::var("LANG") {
|
|
||||||
Locale::from(tag_inv(lang.as_ref()))
|
|
||||||
} else {
|
|
||||||
Locale::invariant()
|
|
||||||
};
|
|
||||||
// category overrides
|
|
||||||
for &(cat, var) in [
|
|
||||||
("ctype", "LC_CTYPE"),
|
|
||||||
("numeric", "LC_NUMERIC"),
|
|
||||||
("time", "LC_TIME"),
|
|
||||||
("collate", "LC_COLLATE"),
|
|
||||||
("monetary", "LC_MONETARY"),
|
|
||||||
("messages", "LC_MESSAGES"),
|
|
||||||
("paper", "LC_PAPER"),
|
|
||||||
("name", "LC_NAME"),
|
|
||||||
("address", "LC_ADDRESS"),
|
|
||||||
("telephone", "LC_TELEPHONE"),
|
|
||||||
("measurement", "LC_MEASUREMENT"),
|
|
||||||
].iter() {
|
|
||||||
if let Ok(val) = env::var(var) {
|
|
||||||
if let Ok(tag) = tag(val.as_ref())
|
|
||||||
{
|
|
||||||
loc.add_category(cat, &tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// LANGUAGE defines fallbacks
|
|
||||||
if let Ok(langs) = env::var("LANGUAGE") {
|
|
||||||
for i in langs.split(':') {
|
|
||||||
if i != "" {
|
|
||||||
if let Ok(tag) = tag(i) {
|
|
||||||
loc.add(&tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if loc.as_ref() != "" {
|
|
||||||
return Some(loc);
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::LanguageRange;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unix_tags() {
|
|
||||||
assert_eq!("cs-CZ", LanguageRange::from_unix("cs_CZ.UTF-8").unwrap().as_ref());
|
|
||||||
assert_eq!("sr-RS-ijekavsk", LanguageRange::from_unix("sr_RS@ijekavian").unwrap().as_ref());
|
|
||||||
assert_eq!("sr-Latn-ijekavsk", LanguageRange::from_unix("sr.UTF-8@ijekavianlatin").unwrap().as_ref());
|
|
||||||
assert_eq!("en-Arab", LanguageRange::from_unix("en@arabic").unwrap().as_ref());
|
|
||||||
assert_eq!("en-Arab", LanguageRange::from_unix("en.UTF-8@arabic").unwrap().as_ref());
|
|
||||||
assert_eq!("de-DE", LanguageRange::from_unix("DE_de.UTF-8@euro").unwrap().as_ref());
|
|
||||||
assert_eq!("ssy-ER", LanguageRange::from_unix("aa_ER@saaho").unwrap().as_ref());
|
|
||||||
assert!(LanguageRange::from_unix("foo_BAR").is_err());
|
|
||||||
assert!(LanguageRange::from_unix("en@arabic.UTF-8").is_err());
|
|
||||||
assert_eq!("", LanguageRange::from_unix("C").unwrap().as_ref());
|
|
||||||
assert_eq!("", LanguageRange::from_unix("C.UTF-8").unwrap().as_ref());
|
|
||||||
assert_eq!("", LanguageRange::from_unix("C.ISO-8859-1").unwrap().as_ref());
|
|
||||||
assert_eq!("", LanguageRange::from_unix("POSIX").unwrap().as_ref());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
184
src/popover.rs
184
src/popover.rs
@ -5,9 +5,7 @@ use gtk;
|
|||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use ::layout::c::{ Bounds, EekGtkKeyboard };
|
use ::layout::c::{ Bounds, EekGtkKeyboard };
|
||||||
use ::locale;
|
use ::locale::{ OwnedTranslation, compare_current_locale };
|
||||||
use ::locale::{ OwnedTranslation, Translation, compare_current_locale };
|
|
||||||
use ::locale_config::system_locale;
|
|
||||||
use ::logging;
|
use ::logging;
|
||||||
use ::manager;
|
use ::manager;
|
||||||
use ::resources;
|
use ::resources;
|
||||||
@ -20,10 +18,9 @@ use gio::SimpleActionExt;
|
|||||||
use glib::translate::FromGlibPtrNone;
|
use glib::translate::FromGlibPtrNone;
|
||||||
use glib::variant::ToVariant;
|
use glib::variant::ToVariant;
|
||||||
#[cfg(not(feature = "gtk_v0_5"))]
|
#[cfg(not(feature = "gtk_v0_5"))]
|
||||||
use gtk::BuilderExtManual;
|
use gtk::prelude::*;
|
||||||
use gtk::PopoverExt;
|
use gtk::PopoverExt;
|
||||||
use gtk::WidgetExt;
|
use gtk::WidgetExt;
|
||||||
use std::io::Write;
|
|
||||||
use ::logging::Warn;
|
use ::logging::Warn;
|
||||||
|
|
||||||
mod c {
|
mod c {
|
||||||
@ -111,46 +108,6 @@ mod variants {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_menu_builder(inputs: Vec<(&str, OwnedTranslation)>) -> gtk::Builder {
|
|
||||||
let mut xml: Vec<u8> = Vec::new();
|
|
||||||
writeln!(
|
|
||||||
xml,
|
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
|
||||||
<interface>
|
|
||||||
<menu id=\"app-menu\">
|
|
||||||
<section>"
|
|
||||||
).unwrap();
|
|
||||||
for (input_name, translation) in inputs {
|
|
||||||
writeln!(
|
|
||||||
xml,
|
|
||||||
"
|
|
||||||
<item>
|
|
||||||
<attribute name=\"label\" translatable=\"yes\">{}</attribute>
|
|
||||||
<attribute name=\"action\">layout</attribute>
|
|
||||||
<attribute name=\"target\">{}</attribute>
|
|
||||||
</item>",
|
|
||||||
translation.0,
|
|
||||||
input_name,
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
writeln!(
|
|
||||||
xml,
|
|
||||||
"
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<item>
|
|
||||||
<attribute name=\"label\" translatable=\"yes\">Keyboard Settings</attribute>
|
|
||||||
<attribute name=\"action\">settings</attribute>
|
|
||||||
</item>
|
|
||||||
</section>
|
|
||||||
</menu>
|
|
||||||
</interface>"
|
|
||||||
).unwrap();
|
|
||||||
gtk::Builder::new_from_string(
|
|
||||||
&String::from_utf8(xml).expect("Bad menu definition")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_settings(schema_name: &str) -> Option<gio::Settings> {
|
fn get_settings(schema_name: &str) -> Option<gio::Settings> {
|
||||||
let mut error_handler = logging::Print{};
|
let mut error_handler = logging::Print{};
|
||||||
gio::SettingsSchemaSource::get_default()
|
gio::SettingsSchemaSource::get_default()
|
||||||
@ -250,13 +207,8 @@ fn get_current_layout(
|
|||||||
/// Translates all provided layout names according to current locale,
|
/// Translates all provided layout names according to current locale,
|
||||||
/// for the purpose of display (i.e. errors will be caught and reported)
|
/// for the purpose of display (i.e. errors will be caught and reported)
|
||||||
fn translate_layout_names(layouts: &Vec<LayoutId>) -> Vec<OwnedTranslation> {
|
fn translate_layout_names(layouts: &Vec<LayoutId>) -> Vec<OwnedTranslation> {
|
||||||
// This procedure is rather ugly...
|
// `XkbInfo` being temporary means that its return values must be
|
||||||
// Xkb lookup *must not* be applied to non-system layouts,
|
// copied, forcing the use of `OwnedTranslation`.
|
||||||
// so both translators can't be merged into one lookup table,
|
|
||||||
// therefore must be done in two steps.
|
|
||||||
// `XkbInfo` being temporary also means
|
|
||||||
// that its return values must be copied,
|
|
||||||
// forcing the use of `OwnedTranslation`.
|
|
||||||
enum Status {
|
enum Status {
|
||||||
/// xkb names should get all translated here
|
/// xkb names should get all translated here
|
||||||
Translated(OwnedTranslation),
|
Translated(OwnedTranslation),
|
||||||
@ -265,7 +217,7 @@ fn translate_layout_names(layouts: &Vec<LayoutId>) -> Vec<OwnedTranslation> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to take all xkb names from gnome-desktop's xkb info.
|
// Attempt to take all xkb names from gnome-desktop's xkb info.
|
||||||
let xkb_translator = locale::XkbInfo::new();
|
let xkb_translator = ::locale::XkbInfo::new();
|
||||||
|
|
||||||
let translated_names = layouts.iter()
|
let translated_names = layouts.iter()
|
||||||
.map(|id| match id {
|
.map(|id| match id {
|
||||||
@ -277,49 +229,15 @@ fn translate_layout_names(layouts: &Vec<LayoutId>) -> Vec<OwnedTranslation> {
|
|||||||
&format!("No display name for xkb layout {}", name),
|
&format!("No display name for xkb layout {}", name),
|
||||||
).unwrap_or_else(|| Status::Remaining(name.clone()))
|
).unwrap_or_else(|| Status::Remaining(name.clone()))
|
||||||
},
|
},
|
||||||
LayoutId::Local(name) => Status::Remaining(name.clone()),
|
LayoutId::Local (_) => unreachable!(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Non-xkb layouts and weird xkb layouts
|
translated_names
|
||||||
// still need to be looked up in the internal database.
|
.map(|status| match status {
|
||||||
let builtin_translations = system_locale()
|
Status::Remaining(name) => OwnedTranslation(name),
|
||||||
.map(|locale|
|
Status::Translated(t) => t,
|
||||||
locale.tags_for("messages")
|
})
|
||||||
.next().unwrap() // guaranteed to exist
|
.collect()
|
||||||
.as_ref()
|
|
||||||
.to_owned()
|
|
||||||
)
|
|
||||||
.or_print(logging::Problem::Surprise, "No locale detected")
|
|
||||||
.and_then(|lang| {
|
|
||||||
resources::get_layout_names(lang.as_str())
|
|
||||||
.or_print(
|
|
||||||
logging::Problem::Surprise,
|
|
||||||
&format!("No translations for locale {}", lang),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
match builtin_translations {
|
|
||||||
Some(translations) => {
|
|
||||||
translated_names
|
|
||||||
.map(|status| match status {
|
|
||||||
Status::Remaining(name) => {
|
|
||||||
translations.get(name.as_str())
|
|
||||||
.unwrap_or(&Translation(name.as_str()))
|
|
||||||
.to_owned()
|
|
||||||
},
|
|
||||||
Status::Translated(t) => t,
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
translated_names
|
|
||||||
.map(|status| match status {
|
|
||||||
Status::Remaining(name) => OwnedTranslation(name),
|
|
||||||
Status::Translated(t) => t,
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show(
|
pub fn show(
|
||||||
@ -350,12 +268,12 @@ pub fn show(
|
|||||||
.chain(overlay_layouts)
|
.chain(overlay_layouts)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let translated_names = translate_layout_names(&all_layouts);
|
let translated_names = translate_layout_names(&system_layouts);
|
||||||
|
|
||||||
// sorted collection of human and machine names
|
// sorted collection of language layouts
|
||||||
let mut human_names: Vec<(OwnedTranslation, LayoutId)> = translated_names
|
let mut human_names: Vec<(OwnedTranslation, LayoutId)> = translated_names
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(all_layouts.clone().into_iter())
|
.zip(system_layouts.clone().into_iter())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
human_names.sort_unstable_by(|(tr_a, layout_a), (tr_b, layout_b)| {
|
human_names.sort_unstable_by(|(tr_a, layout_a), (tr_b, layout_b)| {
|
||||||
@ -367,32 +285,14 @@ pub fn show(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// GVariant doesn't natively support `enum`s,
|
let builder = gtk::Builder::new_from_resource("/sm/puri/squeekboard/popover.ui");
|
||||||
// so the `choices` vector will serve as a lookup table.
|
let model: gio::Menu = builder.get_object("app-menu").unwrap();
|
||||||
let choices_with_translations: Vec<(String, (OwnedTranslation, LayoutId))>
|
|
||||||
= human_names.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, human_entry)| {(
|
|
||||||
format!("{}_{}", i, human_entry.1.get_name()),
|
|
||||||
human_entry,
|
|
||||||
)}).collect();
|
|
||||||
|
|
||||||
|
for (tr, l) in human_names.iter().rev() {
|
||||||
let builder = make_menu_builder(
|
let detailed_action = format!("layout::{}", l.get_name());
|
||||||
choices_with_translations.iter()
|
let item = gio::MenuItem::new(Some(&tr.0), Some(detailed_action.as_str()));
|
||||||
.map(|(id, (translation, _))| (id.as_str(), (*translation).clone()))
|
model.prepend_item (&item);
|
||||||
.collect()
|
}
|
||||||
);
|
|
||||||
|
|
||||||
let choices: Vec<(String, LayoutId)>
|
|
||||||
= choices_with_translations.into_iter()
|
|
||||||
.map(|(id, (_tr, layout))| (id, layout))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Much more debuggable to populate the model & menu
|
|
||||||
// from a string representation
|
|
||||||
// than add items imperatively
|
|
||||||
let model: gio::MenuModel = builder.get_object("app-menu").unwrap();
|
|
||||||
|
|
||||||
let menu = gtk::Popover::new_from_model(Some(&window), &model);
|
let menu = gtk::Popover::new_from_model(Some(&window), &model);
|
||||||
menu.set_pointing_to(>k::Rectangle {
|
menu.set_pointing_to(>k::Rectangle {
|
||||||
@ -403,32 +303,36 @@ pub fn show(
|
|||||||
});
|
});
|
||||||
menu.set_constrain_to(gtk::PopoverConstraint::None);
|
menu.set_constrain_to(gtk::PopoverConstraint::None);
|
||||||
|
|
||||||
|
let action_group = gio::SimpleActionGroup::new();
|
||||||
|
|
||||||
if let Some(current_layout) = get_current_layout(manager, &system_layouts) {
|
if let Some(current_layout) = get_current_layout(manager, &system_layouts) {
|
||||||
let current_name_variant = choices.iter()
|
let current_layout_name = all_layouts.iter()
|
||||||
.find(
|
.find(
|
||||||
|(_id, layout)| layout == ¤t_layout
|
|l| l.get_name() == current_layout.get_name()
|
||||||
).unwrap()
|
).unwrap()
|
||||||
.0.to_variant();
|
.get_name();
|
||||||
|
log_print!(logging::Level::Debug, "Current Layout {}", current_layout_name);
|
||||||
|
|
||||||
let layout_action = gio::SimpleAction::new_stateful(
|
let layout_action = gio::SimpleAction::new_stateful(
|
||||||
"layout",
|
"layout",
|
||||||
Some(current_name_variant.type_()),
|
Some(current_layout_name.to_variant().type_()),
|
||||||
¤t_name_variant,
|
¤t_layout_name.to_variant()
|
||||||
);
|
);
|
||||||
|
|
||||||
let menu_inner = menu.clone();
|
let menu_inner = menu.clone();
|
||||||
layout_action.connect_change_state(move |_action, state| {
|
layout_action.connect_change_state(move |_action, state| {
|
||||||
match state {
|
match state {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
|
log_print!(logging::Level::Debug, "Selected layout {}", v);
|
||||||
v.get::<String>()
|
v.get::<String>()
|
||||||
.or_print(
|
.or_print(
|
||||||
logging::Problem::Bug,
|
logging::Problem::Bug,
|
||||||
&format!("Variant is not string: {:?}", v)
|
&format!("Variant is not string: {:?}", v)
|
||||||
)
|
)
|
||||||
.map(|state| {
|
.map(|state| {
|
||||||
let (_id, layout) = choices.iter()
|
let layout = all_layouts.iter()
|
||||||
.find(
|
.find(
|
||||||
|choices| state == choices.0
|
|choices| state == choices.get_name()
|
||||||
).unwrap();
|
).unwrap();
|
||||||
set_visible_layout(
|
set_visible_layout(
|
||||||
manager,
|
manager,
|
||||||
@ -443,20 +347,18 @@ pub fn show(
|
|||||||
};
|
};
|
||||||
menu_inner.popdown();
|
menu_inner.popdown();
|
||||||
});
|
});
|
||||||
|
|
||||||
let settings_action = gio::SimpleAction::new("settings", None);
|
|
||||||
settings_action.connect_activate(move |_, _| {
|
|
||||||
let s = CString::new("region").unwrap();
|
|
||||||
unsafe { c::popover_open_settings_panel(s.as_ptr()) };
|
|
||||||
});
|
|
||||||
|
|
||||||
let action_group = gio::SimpleActionGroup::new();
|
|
||||||
action_group.add_action(&layout_action);
|
action_group.add_action(&layout_action);
|
||||||
action_group.add_action(&settings_action);
|
|
||||||
|
|
||||||
menu.insert_action_group("popup", Some(&action_group));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let settings_action = gio::SimpleAction::new("settings", None);
|
||||||
|
settings_action.connect_activate(move |_, _| {
|
||||||
|
let s = CString::new("region").unwrap();
|
||||||
|
unsafe { c::popover_open_settings_panel(s.as_ptr()) };
|
||||||
|
});
|
||||||
|
action_group.add_action(&settings_action);
|
||||||
|
|
||||||
|
menu.insert_action_group("popup", Some(&action_group));
|
||||||
|
|
||||||
menu.bind_model(Some(&model), Some("popup"));
|
menu.bind_model(Some(&model), Some("popup"));
|
||||||
menu.popup();
|
menu.popup();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,6 @@
|
|||||||
* This could be done using GResource, but that would need additional work.
|
* This could be done using GResource, but that would need additional work.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use ::locale::Translation;
|
|
||||||
|
|
||||||
use std::iter::FromIterator;
|
|
||||||
|
|
||||||
// TODO: keep a list of what is a language layout,
|
// TODO: keep a list of what is a language layout,
|
||||||
// and what a convenience layout. "_wide" is not a layout,
|
// and what a convenience layout. "_wide" is not a layout,
|
||||||
// neither is "number"
|
// neither is "number"
|
||||||
@ -125,46 +120,6 @@ pub fn get_overlays() -> Vec<&'static str> {
|
|||||||
OVERLAY_NAMES.to_vec()
|
OVERLAY_NAMES.to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translations of the layout identifier strings
|
|
||||||
static LAYOUT_NAMES: &[(&'static str, &'static str)] = &[
|
|
||||||
("de-DE", include_str!("../data/langs/de-DE.txt")),
|
|
||||||
("en-US", include_str!("../data/langs/en-US.txt")),
|
|
||||||
("es-ES", include_str!("../data/langs/es-ES.txt")),
|
|
||||||
("fur-IT", include_str!("../data/langs/fur-IT.txt")),
|
|
||||||
("he-IL", include_str!("../data/langs/he-IL.txt")),
|
|
||||||
("ja-JP", include_str!("../data/langs/ja-JP.txt")),
|
|
||||||
("pl-PL", include_str!("../data/langs/pl-PL.txt")),
|
|
||||||
("ru-RU", include_str!("../data/langs/ru-RU.txt")),
|
|
||||||
];
|
|
||||||
|
|
||||||
pub fn get_layout_names(lang: &str)
|
|
||||||
-> Option<HashMap<&'static str, Translation<'static>>>
|
|
||||||
{
|
|
||||||
let translations = LAYOUT_NAMES.iter()
|
|
||||||
.find(|(name, _data)| *name == lang)
|
|
||||||
.map(|(_name, data)| *data);
|
|
||||||
translations.map(make_mapping)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_line(line: &str) -> Option<(&str, Translation)> {
|
|
||||||
let comment = line.trim().starts_with("#");
|
|
||||||
if comment {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let mut iter = line.splitn(2, " ");
|
|
||||||
let name = iter.next().unwrap();
|
|
||||||
// will skip empty and unfinished lines
|
|
||||||
iter.next().map(|tr| (name, Translation(tr.trim())))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_mapping(data: &str) -> HashMap<&str, Translation> {
|
|
||||||
HashMap::from_iter(
|
|
||||||
data.split("\n")
|
|
||||||
.filter_map(parse_line)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -175,32 +130,4 @@ mod test {
|
|||||||
assert!(get_keyboard(&format!("{}/us", name)).is_some());
|
assert!(get_keyboard(&format!("{}/us", name)).is_some());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn mapping_line() {
|
|
||||||
assert_eq!(
|
|
||||||
Some(("name", Translation("translation"))),
|
|
||||||
parse_line("name translation")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn mapping_bad() {
|
|
||||||
assert_eq!(None, parse_line("bad"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn mapping_empty() {
|
|
||||||
assert_eq!(None, parse_line(""));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn mapping_comment() {
|
|
||||||
assert_eq!(None, parse_line("# comment"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn mapping_comment_offset() {
|
|
||||||
assert_eq!(None, parse_line(" # comment"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user