import ceylon.collection {
import ceylon.language.meta.declaration {

"Aggregates localized information associated with a certain 
 locale, including:
 - the local [[language]],
 - the local [[currency]],
 - localized date, time, currency, and numeric [[formats]],
 - local representations of other [[languages]] and
 The locale also provides access to associated localized 
 message bundles via [[messages]]."
shared sealed class Locale(language, formats, 
    languages, currencies, currencyCode=null,
    lowercaseMappings = emptyMap, 
    uppercaseMappings = emptyMap) {
    "The language of this locale."
    shared Language language;
    "Localized date, time, currency, and numeric formats
     for this locale."
    shared Formats formats;
    "Localized representations of other languages."
    shared Map<String,Language> languages;
    "Localized representations of other currencies."
    shared Map<String,Currency> currencies;
    "Localized mappings of uppercase characters to lowercase."
    Map<Character,String> lowercaseMappings;
    "Localized mappings of lowercase characters to uppercase."
    Map<Character,String> uppercaseMappings;
    String? currencyCode;
    "The currency of this locale."
    shared Currency? currency 
            => if (exists currencyCode) 
            then currencies[currencyCode] 
            else null;
    "A string with the characters of the given string
     converted to uppercase according to the rules of this
    shared String uppercase(String string)
            => uppercaseMappings.fold(string)((str, mapping) 
            => str.replace(mapping.key.string, mapping.item))
    "A string with the characters of the given string
     converted to lowercase according to the rules of this
    shared String lowercase(String string) 
            => lowercaseMappings.fold(string)((str, mapping) 
            => str.replace(mapping.key.string, mapping.item))
    string => language.string;
    function search(Module mod, String name) {
        function find(String tag) 
                => mod.resourceByPath(
                    name + tag + ".properties");
        value lang = language.languageCode;
        value country = language.countryCode;
        value variant = language.variant;
        if (exists country) {
            if (exists variant) {
                value tag = 
                        "_" + lang + 
                        "_" + country + 
                        "_" + variant;
                if (exists result = find(tag)) {
                     return result;
            value tag = 
                    "_" + lang + 
                    "_" + country;
            if (exists result = find(tag)) {
                return result;
        value tag = "_" + lang;
        if (exists result = find(tag)) {
            return result;
        return find("");
    function path(Package pack, String name) 
            => "/" + pack.qualifiedName.replace(".", "/")
            + "/" + name;
    "Given a [[Module]] or [[Package]] and the name of a 
     resource bundle belonging to that package or module, 
     return a map of string keys to string values for this 
     For example, suppose the system locale is `en-AU`, and 
     this code occurs in the module ``:
         value messages = systemLocale.messages(`module`, \"Errors\");
     Then the returned map `messages` will contain 
     entries from a properties file in the resources of the 
     module ``. The following files will be 
     searched, in order:
     1. `/hello/world/`
     2. `/hello/world/`
     3. `/hello/world/`
     If no properties file is found, the map with be empty."
    shared Map<String,String> messages(
        "The module to which the resource bundle belongs."
        Module|Package component, 
        "The name of the resource bundle, or `\"Messages\"`
         by default."
        String name = "Messages") {
        value resource =
            switch (component)
            case (is Module)
                search(component, name)
            case (is Package)
                    .resourceByPath(path(component, name));
        value map = HashMap<String, String>();
        if (exists resource) {
            parsePropertiesFile { 
                textContent = resource.textContent(); 
                handleEntry = map.put;
        return map;

"Returns a [[Locale]] containing information about the
 locale with the given locale [[tag]]."
shared Locale? locale(String tag) {
    value filePath = tag + ".txt";
    if (exists resource = 
            localeModule.resourceByPath(filePath)) {
        value lines = 
        value [language,currency] 
                = parseLanguage(lines, tag);
        return Locale {
            language = language;
            currencyCode = currency;
            formats = parseFormats(lines);
            languages = parseLanguages(lines);
            currencies = parseCurrencies(lines);
            lowercaseMappings = parseCaseMappings(lines);
            uppercaseMappings = parseCaseMappings(lines);
    else {
        return null;

"Returns a [[Locale]] containing information about the
 locale of the current system."
see (`value system.locale`)
shared Locale systemLocale {
    "locale data for current locale must exist"
    assert (exists systemLocaleCache);
    return systemLocaleCache;

Locale? systemLocaleCache = locale(system.locale);

Module localeModule = `module ceylon.locale`;