]> git.saurik.com Git - apple/icu.git/blobdiff - icuSources/common/uloc.cpp
ICU-66108.tar.gz
[apple/icu.git] / icuSources / common / uloc.cpp
index 88a221c6024fe370aa8cab459d0509450da22a1b..d473899a7df99d55df16e6a12c357ec62d7967d5 100644 (file)
@@ -1,6 +1,8 @@
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
 /*
 **********************************************************************
-*   Copyright (C) 1997-2014, International Business Machines
+*   Copyright (C) 1997-2016, International Business Machines
 *   Corporation and others.  All Rights Reserved.
 **********************************************************************
 *
 #include "uarrsort.h"
 #include "uenumimp.h"
 #include "uassert.h"
+#include "charstr.h"
 
 #include <stdio.h> /* for sprintf */
 
+U_NAMESPACE_USE
+
 /* ### Declarations **************************************************/
 
 /* Locale stuff from locid.cpp */
@@ -92,88 +97,94 @@ locale_getKeywords(const char *localeID,
  * The range qaa-qtz is reserved for local use
  */
 /* Generated using org.unicode.cldr.icu.GenerateISO639LanguageTables */
-/* ISO639 table version is 20130531 */
+/* ISO639 table version is 20150505 */
+/* Subsequent hand addition of selected languages */
 static const char * const LANGUAGES[] = {
-    "aa",  "ab",  "ace", "ach", "ada", "ady", "ae",  "af",  
-    "afa", "afh", "agq", "ain", "ak",  "akk", "ale", "alg", 
-    "alt", "am",  "an",  "ang", "anp", "apa", "ar",  "arc", 
-    "arn", "arp", "art", "arw", "as",  "asa", "ast", "ath", 
-    "aus", "av",  "awa", "ay",  "az",  
-    "ba",  "bad", "bai", "bal", "ban", "bas", "bat", "bax", 
-    "bbj", "be",  "bej", "bem", "ber", "bez", "bfd", "bg",  
-    "bh",  "bho", "bi",  "bik", "bin", "bkm", "bla", "bm",  
-    "bn",  "bnt", "bo",  "br",  "bra", "brx", "bs",  "bss", 
-    "btk", "bua", "bug", "bum", "byn", "byv", 
-    "ca",  "cad", "cai", "car", "cau", "cay", "cch", "ce",  
-    "ceb", "cel", "cgg", "ch",  "chb", "chg", "chk", "chm", 
-    "chn", "cho", "chp", "chr", "chy", "ckb", "cmc", "co",  
-    "cop", "cpe", "cpf", "cpp", "cr",  "crh", "crp", "cs",  
-    "csb", "cu",  "cus", "cv",  "cy",  
-    "da",  "dak", "dar", "dav", "day", "de",  "del", "den", 
-    "dgr", "din", "dje", "doi", "dra", "dsb", "dua", "dum", 
-    "dv",  "dyo", "dyu", "dz",  "dzg", 
-    "ebu", "ee",  "efi", "egy", "eka", "el",  "elx", "en",  
-    "enm", "eo",  "es",  "et",  "eu",  "ewo", 
-    "fa",  "fan", "fat", "ff",  "fi",  "fil", "fiu", "fj",  
-    "fo",  "fon", "fr",  "frm", "fro", "frr", "frs", "fur", 
-    "fy",  
-    "ga",  "gaa", "gay", "gba", "gd",  "gem", "gez", "gil", 
-    "gl",  "gmh", "gn",  "goh", "gon", "gor", "got", "grb", 
-    "grc", "gsw", "gu",  "guz", "gv",  "gwi", 
-    "ha",  "hai", "haw", "he",  "hi",  "hil", "him", "hit", 
-    "hmn", "ho",  "hr",  "hsb", "ht",  "hu",  "hup", "hy",  
-    "hz",  
-    "ia",  "iba", "ibb", "id",  "ie",  "ig",  "ii",  "ijo", 
-    "ik",  "ilo", "inc", "ine", "inh", "io",  "ira", "iro", 
-    "is",  "it",  "iu",  
-    "ja",  "jbo", "jgo", "jmc", "jpr", "jrb", "jv",  
-    "ka",  "kaa", "kab", "kac", "kaj", "kam", "kar", "kaw", 
-    "kbd", "kbl", "kcg", "kde", "kea", "kfo", "kg",  "kha", 
-    "khi", "kho", "khq", "ki",  "kj",  "kk",  "kkj", "kl",  
-    "kln", "km",  "kmb", "kn",  "ko",  "kok", "kos", "kpe", 
-    "kr",  "krc", "krl", "kro", "kru", "ks",  "ksb", "ksf", 
-    "ksh", "ku",  "kum", "kut", "kv",  "kw",  "ky",  
-    "la",  "lad", "lag", "lah", "lam", "lb",  "lez", "lg",  
-    "li",  "lkt", "ln",  "lo",  "lol", "loz", "lt",  "lu",  
-    "lua", "lui", "lun", "luo", "lus", "luy", "lv",  
-    "mad", "maf", "mag", "mai", "mak", "man", "map", "mas", 
-    "mde", "mdf", "mdr", "men", "mer", "mfe", "mg",  "mga", 
-    "mgh", "mgo", "mh",  "mi",  "mic", "min", "mis", "mk",  
-    "mkh", "ml",  "mn",  "mnc", "mni", "mno", "mo",  "moh", 
-    "mos", "mr",  "ms",  "mt",  "mua", "mul", "mun", "mus", 
-    "mwl", "mwr", "my",  "mye", "myn", "myv", 
-    "na",  "nah", "nai", "nap", "naq", "nb",  "nd",  "nds", 
-    "ne",  "new", "ng",  "nia", "nic", "niu", "nl",  "nmg", 
-    "nn",  "nnh", "no",  "nog", "non", "nqo", "nr",  "nso", 
-    "nub", "nus", "nv",  "nwc", "ny",  "nym", "nyn", "nyo", 
-    "nzi", 
-    "oc",  "oj",  "om",  "or",  "os",  "osa", "ota", "oto", 
-    "pa",  "paa", "pag", "pal", "pam", "pap", "pau", "peo", 
-    "phi", "phn", "pi",  "pl",  "pon", "pra", "pro", "ps",  
-    "pt",  
-    "qu",  
-    "raj", "rap", "rar", "rm",  "rn",  "ro",  "roa", "rof", 
-    "rom", "ru",  "rup", "rw",  "rwk", 
-    "sa",  "sad", "sah", "sai", "sal", "sam", "saq", "sas", 
-    "sat", "sba", "sbp", "sc",  "scn", "sco", "sd",  "se",  
-    "see", "seh", "sel", "sem", "ses", "sg",  "sga", "sgn", 
-    "shi", "shn", "shu", "si",  "sid", "sio", "sit", 
-    "sk",  "sl",  "sla", "sm",  "sma", "smi", "smj", "smn", 
-    "sms", "sn",  "snk", "so",  "sog", "son", "sq",  "sr",  
-    "srn", "srr", "ss",  "ssa", "ssy", "st",  "su",  "suk", 
-    "sus", "sux", "sv",  "sw",  "swb", "swc", "syc", "syr", 
-    "ta",  "tai", "te",  "tem", "teo", "ter", "tet", "tg",  
-    "th",  "ti",  "tig", "tiv", "tk",  "tkl", "tl",  "tlh", 
-    "tli", "tmh", "tn",  "to",  "tog", "tpi", "tr",  "trv", 
-    "ts",  "tsi", "tt",  "tum", "tup", "tut", "tvl", "tw",  
-    "twq", "ty",  "tyv", "tzm", 
-    "udm", "ug",  "uga", "uk",  "umb", "und", "ur",  "uz",  
-    "vai", "ve",  "vi",  "vo",  "vot", "vun", 
-    "wa",  "wae", "wak", "wal", "war", "was", "wen", "wo",  
-    "xal", "xh",  "xog", 
-    "yao", "yap", "yav", "ybb", "yi",  "yo",  "ypk", "yue", 
-    "za",  "zap", "zbl", "zen", "zgh", "zh",  "znd", "zu",  
-    "zun", "zxx", "zza", 
+    "aa",  "ab",  "ace", "ach", "ada", "ady", "ae",  "aeb",
+    "af",  "afh", "agq", "ain", "ak",  "akk", "akz", "ale",
+    "aln", "alt", "am",  "an",  "ang", "anp", "ar",  "arc",
+    "arn", "aro", "arp", "arq", "ars", "arw", "ary", "arz", "as",
+    "asa", "ase", "ast", "av",  "avk", "awa", "ay",  "az",
+    "ba",  "bal", "ban", "bar", "bas", "bax", "bbc", "bbj",
+    "be",  "bej", "bem", "bew", "bez", "bfd", "bfq", "bg",
+    "bgn", "bho", "bi",  "bik", "bin", "bjn", "bkm", "bla",
+    "bm",  "bn",  "bo",  "bpy", "bqi", "br",  "bra", "brh",
+    "brx", "bs",  "bss", "bua", "bug", "bum", "byn", "byv",
+    "ca",  "cad", "car", "cay", "cch", "ccp", "ce",  "ceb", "cgg",
+    "ch",  "chb", "chg", "chk", "chm", "chn", "cho", "chp",
+    "chr", "chy", "ckb", "co",  "cop", "cps", "cr",  "crh",
+    "cs",  "csb", "cu",  "cv",  "cy",
+    "da",  "dak", "dar", "dav", "de",  "del", "den", "dgr",
+    "din", "dje", "doi", "dsb", "dtp", "dua", "dum", "dv",
+    "dyo", "dyu", "dz",  "dzg",
+    "ebu", "ee",  "efi", "egl", "egy", "eka", "el",  "elx",
+    "en",  "enm", "eo",  "es",  "esu", "et",  "eu",  "ewo",
+    "ext",
+    "fa",  "fan", "fat", "ff",  "fi",  "fil", "fit", "fj",
+    "fo",  "fon", "fr",  "frc", "frm", "fro", "frp", "frr",
+    "frs", "fur", "fy",
+    "ga",  "gaa", "gag", "gan", "gay", "gba", "gbz", "gd",
+    "gez", "gil", "gl",  "glk", "gmh", "gn",  "goh", "gom",
+    "gon", "gor", "got", "grb", "grc", "gsw", "gu",  "guc",
+    "gur", "guz", "gv",  "gwi",
+    "ha",  "hai", "hak", "haw", "he",  "hi",  "hif", "hil",
+    "hit", "hmn", "ho",  "hr",  "hsb", "hsn", "ht",  "hu",
+    "hup", "hy",  "hz",
+    "ia",  "iba", "ibb", "id",  "ie",  "ig",  "ii",  "ik",
+    "ilo", "inh", "io",  "is",  "it",  "iu",  "izh",
+    "ja",  "jam", "jbo", "jgo", "jmc", "jpr", "jrb", "jut",
+    "jv",
+    "ka",  "kaa", "kab", "kac", "kaj", "kam", "kaw", "kbd",
+    "kbl", "kcg", "kde", "kea", "ken", "kfo", "kg",  "kgp",
+    "kha", "kho", "khq", "khw", "ki",  "kiu", "kj",  "kk",
+    "kkj", "kl",  "kln", "km",  "kmb", "kn",  "ko",  "koi",
+    "kok", "kos", "kpe", "kr",  "krc", "kri", "krj", "krl",
+    "kru", "ks",  "ksb", "ksf", "ksh", "ku",  "kum", "kut",
+    "kv",  "kw",  "ky",
+    "la",  "lad", "lag", "lah", "lam", "lb",  "lez", "lfn",
+    "lg",  "li",  "lij", "liv", "lkt", "lmo", "ln",  "lo",
+    "lol", "loz", "lrc", "lt",  "ltg", "lu",  "lua", "lui",
+    "lun", "luo", "lus", "luy", "lv",  "lzh", "lzz",
+    "mad", "maf", "mag", "mai", "mak", "man", "mas", "mde",
+    "mdf", "mdh", "mdr", "men", "mer", "mfe", "mg",  "mga",
+    "mgh", "mgo", "mh",  "mi",  "mic", "min", "mis", "mk",
+    "ml",  "mn",  "mnc", "mni", "mo",
+    "moh", "mos", "mr",  "mrj",
+    "ms",  "mt",  "mua", "mul", "mus", "mwl", "mwr", "mwv",
+    "my",  "mye", "myv", "mzn",
+    "na",  "nan", "nap", "naq", "nb",  "nd",  "nds", "ne",
+    "new", "ng",  "nia", "niu", "njo", "nl",  "nmg", "nn",
+    "nnh", "no",  "nog", "non", "nov", "nqo", "nr",  "nso",
+    "nus", "nv",  "nwc", "ny",  "nym", "nyn", "nyo", "nzi",
+    "oc",  "oj",  "om",  "or",  "os",  "osa", "ota",
+    "pa",  "pag", "pal", "pam", "pap", "pau", "pcd", "pdc",
+    "pdt", "peo", "pfl", "phn", "pi",  "pl",  "pms", "pnt",
+    "pon", "prg", "pro", "ps",  "pt",
+    "qu",  "quc", "qug",
+    "raj", "rap", "rar", "rgn", "rif", "rm",  "rn",  "ro",
+    "rof", "rom", "rtm", "ru",  "rue", "rug", "rup",
+    "rw",  "rwk",
+    "sa",  "sad", "sah", "sam", "saq", "sas", "sat", "saz",
+    "sba", "sbp", "sc",  "scn", "sco", "sd",  "sdc", "sdh",
+    "se",  "see", "seh", "sei", "sel", "ses", "sg",  "sga",
+    "sgs", "shi", "shn", "shu", "si",  "sid", "sk",
+    "sl",  "sli", "sly", "sm",  "sma", "smj", "smn", "sms",
+    "sn",  "snk", "so",  "sog", "sq",  "sr",  "srn", "srr",
+    "ss",  "ssy", "st",  "stq", "su",  "suk", "sus", "sux",
+    "sv",  "sw",  "swb", "swc", "syc", "syr", "szl",
+    "ta",  "tcy", "te",  "tem", "teo", "ter", "tet", "tg",
+    "th",  "ti",  "tig", "tiv", "tk",  "tkl", "tkr", "tl",
+    "tlh", "tli", "tly", "tmh", "tn",  "to",  "tog", "tpi",
+    "tr",  "tru", "trv", "ts",  "tsd", "tsi", "tt",  "ttt",
+    "tum", "tvl", "tw",  "twq", "ty",  "tyv", "tzm",
+    "udm", "ug",  "uga", "uk",  "umb", "und", "ur",  "uz",
+    "vai", "ve",  "vec", "vep", "vi",  "vls", "vmf", "vo",
+    "vot", "vro", "vun",
+    "wa",  "wae", "wal", "war", "was", "wbp", "wo",  "wuu",
+    "xal", "xh",  "xmf", "xog",
+    "yao", "yap", "yav", "ybb", "yi",  "yo",  "yrl", "yue",
+    "za",  "zap", "zbl", "zea", "zen", "zgh", "zh",  "zu",
+    "zun", "zxx", "zza",
 NULL,
     "in",  "iw",  "ji",  "jw",  "sh",    /* obsolete language codes */
 NULL
@@ -203,88 +214,94 @@ static const char* const REPLACEMENT_LANGUAGES[]={
  * the two lists in LANGUAGES.
  */
 /* Generated using org.unicode.cldr.icu.GenerateISO639LanguageTables */
-/* ISO639 table version is 20130531 */
+/* ISO639 table version is 20150505 */
+/* Subsequent hand addition of selected languages */
 static const char * const LANGUAGES_3[] = {
-    "aar", "abk", "ace", "ach", "ada", "ady", "ave", "afr", 
-    "afa", "afh", "agq", "ain", "aka", "akk", "ale", "alg", 
-    "alt", "amh", "arg", "ang", "anp", "apa", "ara", "arc", 
-    "arn", "arp", "art", "arw", "asm", "asa", "ast", "ath", 
-    "aus", "ava", "awa", "aym", "aze", 
-    "bak", "bad", "bai", "bal", "ban", "bas", "bat", "bax", 
-    "bbj", "bel", "bej", "bem", "ber", "bez", "bfd", "bul", 
-    "bih", "bho", "bis", "bik", "bin", "bkm", "bla", "bam", 
-    "ben", "bnt", "bod", "bre", "bra", "brx", "bos", "bss", 
-    "btk", "bua", "bug", "bum", "byn", "byv", 
-    "cat", "cad", "cai", "car", "cau", "cay", "cch", "che", 
-    "ceb", "cel", "cgg", "cha", "chb", "chg", "chk", "chm", 
-    "chn", "cho", "chp", "chr", "chy", "ckb", "cmc", "cos", 
-    "cop", "cpe", "cpf", "cpp", "cre", "crh", "crp", "ces", 
-    "csb", "chu", "cus", "chv", "cym", 
-    "dan", "dak", "dar", "dav", "day", "deu", "del", "den", 
-    "dgr", "din", "dje", "doi", "dra", "dsb", "dua", "dum", 
-    "div", "dyo", "dyu", "dzo", "dzg", 
-    "ebu", "ewe", "efi", "egy", "eka", "ell", "elx", "eng", 
-    "enm", "epo", "spa", "est", "eus", "ewo", 
-    "fas", "fan", "fat", "ful", "fin", "fil", "fiu", "fij", 
-    "fao", "fon", "fra", "frm", "fro", "frr", "frs", "fur", 
-    "fry", 
-    "gle", "gaa", "gay", "gba", "gla", "gem", "gez", "gil", 
-    "glg", "gmh", "grn", "goh", "gon", "gor", "got", "grb", 
-    "grc", "gsw", "guj", "guz", "glv", "gwi", 
-    "hau", "hai", "haw", "heb", "hin", "hil", "him", "hit", 
-    "hmn", "hmo", "hrv", "hsb", "hat", "hun", "hup", "hye", 
-    "her", 
-    "ina", "iba", "ibb", "ind", "ile", "ibo", "iii", "ijo", 
-    "ipk", "ilo", "inc", "ine", "inh", "ido", "ira", "iro", 
-    "isl", "ita", "iku", 
-    "jpn", "jbo", "jgo", "jmc", "jpr", "jrb", "jav", 
-    "kat", "kaa", "kab", "kac", "kaj", "kam", "kar", "kaw", 
-    "kbd", "kbl", "kcg", "kde", "kea", "kfo", "kon", "kha", 
-    "khi", "kho", "khq", "kik", "kua", "kaz", "kkj", "kal", 
-    "kln", "khm", "kmb", "kan", "kor", "kok", "kos", "kpe", 
-    "kau", "krc", "krl", "kro", "kru", "kas", "ksb", "ksf", 
-    "ksh", "kur", "kum", "kut", "kom", "cor", "kir", 
-    "lat", "lad", "lag", "lah", "lam", "ltz", "lez", "lug", 
-    "lim", "lkt", "lin", "lao", "lol", "loz", "lit", "lub", 
-    "lua", "lui", "lun", "luo", "lus", "luy", "lav", 
-    "mad", "maf", "mag", "mai", "mak", "man", "map", "mas", 
-    "mde", "mdf", "mdr", "men", "mer", "mfe", "mlg", "mga", 
-    "mgh", "mgo", "mah", "mri", "mic", "min", "mis", "mkd", 
-    "mkh", "mal", "mon", "mnc", "mni", "mno", "mol", "moh", 
-    "mos", "mar", "msa", "mlt", "mua", "mul", "mun", "mus", 
-    "mwl", "mwr", "mya", "mye", "myn", "myv", 
-    "nau", "nah", "nai", "nap", "naq", "nob", "nde", "nds", 
-    "nep", "new", "ndo", "nia", "nic", "niu", "nld", "nmg", 
-    "nno", "nnh", "nor", "nog", "non", "nqo", "nbl", "nso", 
-    "nub", "nus", "nav", "nwc", "nya", "nym", "nyn", "nyo", 
-    "nzi", 
-    "oci", "oji", "orm", "ori", "oss", "osa", "ota", "oto", 
-    "pan", "paa", "pag", "pal", "pam", "pap", "pau", "peo", 
-    "phi", "phn", "pli", "pol", "pon", "pra", "pro", "pus", 
-    "por", 
-    "que", 
-    "raj", "rap", "rar", "roh", "run", "ron", "roa", "rof", 
-    "rom", "rus", "rup", "kin", "rwk", 
-    "san", "sad", "sah", "sai", "sal", "sam", "saq", "sas", 
-    "sat", "sba", "sbp", "srd", "scn", "sco", "snd", "sme", 
-    "see", "seh", "sel", "sem", "ses", "sag", "sga", "sgn", 
-    "shi", "shn", "shu", "sin", "sid", "sio", "sit", 
-    "slk", "slv", "sla", "smo", "sma", "smi", "smj", "smn", 
-    "sms", "sna", "snk", "som", "sog", "son", "sqi", "srp", 
-    "srn", "srr", "ssw", "ssa", "ssy", "sot", "sun", "suk", 
-    "sus", "sux", "swe", "swa", "swb", "swc", "syc", "syr", 
-    "tam", "tai", "tel", "tem", "teo", "ter", "tet", "tgk", 
-    "tha", "tir", "tig", "tiv", "tuk", "tkl", "tgl", "tlh", 
-    "tli", "tmh", "tsn", "ton", "tog", "tpi", "tur", "trv", 
-    "tso", "tsi", "tat", "tum", "tup", "tut", "tvl", "twi", 
-    "twq", "tah", "tyv", "tzm", 
-    "udm", "uig", "uga", "ukr", "umb", "und", "urd", "uzb", 
-    "vai", "ven", "vie", "vol", "vot", "vun", 
-    "wln", "wae", "wak", "wal", "war", "was", "wen", "wol", 
-    "xal", "xho", "xog", 
-    "yao", "yap", "yav", "ybb", "yid", "yor", "ypk", "yue", 
-    "zha", "zap", "zbl", "zen", "zgh", "zho", "znd", "zul", 
-    "zun", "zxx", "zza", 
+    "aar", "abk", "ace", "ach", "ada", "ady", "ave", "aeb",
+    "afr", "afh", "agq", "ain", "aka", "akk", "akz", "ale",
+    "aln", "alt", "amh", "arg", "ang", "anp", "ara", "arc",
+    "arn", "aro", "arp", "arq", "ars", "arw", "ary", "arz", "asm",
+    "asa", "ase", "ast", "ava", "avk", "awa", "aym", "aze",
+    "bak", "bal", "ban", "bar", "bas", "bax", "bbc", "bbj",
+    "bel", "bej", "bem", "bew", "bez", "bfd", "bfq", "bul",
+    "bgn", "bho", "bis", "bik", "bin", "bjn", "bkm", "bla",
+    "bam", "ben", "bod", "bpy", "bqi", "bre", "bra", "brh",
+    "brx", "bos", "bss", "bua", "bug", "bum", "byn", "byv",
+    "cat", "cad", "car", "cay", "cch", "ccp", "che", "ceb", "cgg",
+    "cha", "chb", "chg", "chk", "chm", "chn", "cho", "chp",
+    "chr", "chy", "ckb", "cos", "cop", "cps", "cre", "crh",
+    "ces", "csb", "chu", "chv", "cym",
+    "dan", "dak", "dar", "dav", "deu", "del", "den", "dgr",
+    "din", "dje", "doi", "dsb", "dtp", "dua", "dum", "div",
+    "dyo", "dyu", "dzo", "dzg",
+    "ebu", "ewe", "efi", "egl", "egy", "eka", "ell", "elx",
+    "eng", "enm", "epo", "spa", "esu", "est", "eus", "ewo",
+    "ext",
+    "fas", "fan", "fat", "ful", "fin", "fil", "fit", "fij",
+    "fao", "fon", "fra", "frc", "frm", "fro", "frp", "frr",
+    "frs", "fur", "fry",
+    "gle", "gaa", "gag", "gan", "gay", "gba", "gbz", "gla",
+    "gez", "gil", "glg", "glk", "gmh", "grn", "goh", "gom",
+    "gon", "gor", "got", "grb", "grc", "gsw", "guj", "guc",
+    "gur", "guz", "glv", "gwi",
+    "hau", "hai", "hak", "haw", "heb", "hin", "hif", "hil",
+    "hit", "hmn", "hmo", "hrv", "hsb", "hsn", "hat", "hun",
+    "hup", "hye", "her",
+    "ina", "iba", "ibb", "ind", "ile", "ibo", "iii", "ipk",
+    "ilo", "inh", "ido", "isl", "ita", "iku", "izh",
+    "jpn", "jam", "jbo", "jgo", "jmc", "jpr", "jrb", "jut",
+    "jav",
+    "kat", "kaa", "kab", "kac", "kaj", "kam", "kaw", "kbd",
+    "kbl", "kcg", "kde", "kea", "ken", "kfo", "kon", "kgp",
+    "kha", "kho", "khq", "khw", "kik", "kiu", "kua", "kaz",
+    "kkj", "kal", "kln", "khm", "kmb", "kan", "kor", "koi",
+    "kok", "kos", "kpe", "kau", "krc", "kri", "krj", "krl",
+    "kru", "kas", "ksb", "ksf", "ksh", "kur", "kum", "kut",
+    "kom", "cor", "kir",
+    "lat", "lad", "lag", "lah", "lam", "ltz", "lez", "lfn",
+    "lug", "lim", "lij", "liv", "lkt", "lmo", "lin", "lao",
+    "lol", "loz", "lrc", "lit", "ltg", "lub", "lua", "lui",
+    "lun", "luo", "lus", "luy", "lav", "lzh", "lzz",
+    "mad", "maf", "mag", "mai", "mak", "man", "mas", "mde",
+    "mdf", "mdh", "mdr", "men", "mer", "mfe", "mlg", "mga",
+    "mgh", "mgo", "mah", "mri", "mic", "min", "mis", "mkd",
+    "mal", "mon", "mnc", "mni", "mol",
+    "moh", "mos", "mar", "mrj",
+    "msa", "mlt", "mua", "mul", "mus", "mwl", "mwr", "mwv",
+    "mya", "mye", "myv", "mzn",
+    "nau", "nan", "nap", "naq", "nob", "nde", "nds", "nep",
+    "new", "ndo", "nia", "niu", "njo", "nld", "nmg", "nno",
+    "nnh", "nor", "nog", "non", "nov", "nqo", "nbl", "nso",
+    "nus", "nav", "nwc", "nya", "nym", "nyn", "nyo", "nzi",
+    "oci", "oji", "orm", "ori", "oss", "osa", "ota",
+    "pan", "pag", "pal", "pam", "pap", "pau", "pcd", "pdc",
+    "pdt", "peo", "pfl", "phn", "pli", "pol", "pms", "pnt",
+    "pon", "prg", "pro", "pus", "por",
+    "que", "quc", "qug",
+    "raj", "rap", "rar", "rgn", "rif", "roh", "run", "ron",
+    "rof", "rom", "rtm", "rus", "rue", "rug", "rup",
+    "kin", "rwk",
+    "san", "sad", "sah", "sam", "saq", "sas", "sat", "saz",
+    "sba", "sbp", "srd", "scn", "sco", "snd", "sdc", "sdh",
+    "sme", "see", "seh", "sei", "sel", "ses", "sag", "sga",
+    "sgs", "shi", "shn", "shu", "sin", "sid", "slk",
+    "slv", "sli", "sly", "smo", "sma", "smj", "smn", "sms",
+    "sna", "snk", "som", "sog", "sqi", "srp", "srn", "srr",
+    "ssw", "ssy", "sot", "stq", "sun", "suk", "sus", "sux",
+    "swe", "swa", "swb", "swc", "syc", "syr", "szl",
+    "tam", "tcy", "tel", "tem", "teo", "ter", "tet", "tgk",
+    "tha", "tir", "tig", "tiv", "tuk", "tkl", "tkr", "tgl",
+    "tlh", "tli", "tly", "tmh", "tsn", "ton", "tog", "tpi",
+    "tur", "tru", "trv", "tso", "tsd", "tsi", "tat", "ttt",
+    "tum", "tvl", "twi", "twq", "tah", "tyv", "tzm",
+    "udm", "uig", "uga", "ukr", "umb", "und", "urd", "uzb",
+    "vai", "ven", "vec", "vep", "vie", "vls", "vmf", "vol",
+    "vot", "vro", "vun",
+    "wln", "wae", "wal", "war", "was", "wbp", "wol", "wuu",
+    "xal", "xho", "xmf", "xog",
+    "yao", "yap", "yav", "ybb", "yid", "yor", "yrl", "yue",
+    "zha", "zap", "zbl", "zea", "zen", "zgh", "zho", "zul",
+    "zun", "zxx", "zza",
 NULL,
 /*  "in",  "iw",  "ji",  "jw",  "sh",                          */
     "ind", "heb", "yid", "jaw", "srp",
@@ -316,19 +333,19 @@ NULL
  * http://www.iso.org/iso/en/prods-services/iso3166ma/03updates-on-iso-3166/nlv3e-rou.html
  */
 static const char * const COUNTRIES[] = {
-    "AD",  "AE",  "AF",  "AG",  "AI",  "AL",  "AM",
+    "AC",  "AD",  "AE",  "AF",  "AG",  "AI",  "AL",  "AM",
     "AO",  "AQ",  "AR",  "AS",  "AT",  "AU",  "AW",  "AX",  "AZ",
     "BA",  "BB",  "BD",  "BE",  "BF",  "BG",  "BH",  "BI",
     "BJ",  "BL",  "BM",  "BN",  "BO",  "BQ",  "BR",  "BS",  "BT",  "BV",
     "BW",  "BY",  "BZ",  "CA",  "CC",  "CD",  "CF",  "CG",
-    "CH",  "CI",  "CK",  "CL",  "CM",  "CN",  "CO",  "CR",
-    "CU",  "CV",  "CW",  "CX",  "CY",  "CZ",  "DE",  "DJ",  "DK",
-    "DM",  "DO",  "DZ",  "EC",  "EE",  "EG",  "EH",  "ER",
+    "CH",  "CI",  "CK",  "CL",  "CM",  "CN",  "CO",  "CP",  "CR",
+    "CU",  "CV",  "CW",  "CX",  "CY",  "CZ",  "DE",  "DG",  "DJ",  "DK",
+    "DM",  "DO",  "DZ",  "EA",  "EC",  "EE",  "EG",  "EH",  "ER",
     "ES",  "ET",  "FI",  "FJ",  "FK",  "FM",  "FO",  "FR",
     "GA",  "GB",  "GD",  "GE",  "GF",  "GG",  "GH",  "GI",  "GL",
     "GM",  "GN",  "GP",  "GQ",  "GR",  "GS",  "GT",  "GU",
     "GW",  "GY",  "HK",  "HM",  "HN",  "HR",  "HT",  "HU",
-    "ID",  "IE",  "IL",  "IM",  "IN",  "IO",  "IQ",  "IR",  "IS",
+    "IC",  "ID",  "IE",  "IL",  "IM",  "IN",  "IO",  "IQ",  "IR",  "IS",
     "IT",  "JE",  "JM",  "JO",  "JP",  "KE",  "KG",  "KH",  "KI",
     "KM",  "KN",  "KP",  "KR",  "KW",  "KY",  "KZ",  "LA",
     "LB",  "LC",  "LI",  "LK",  "LR",  "LS",  "LT",  "LU",
@@ -341,11 +358,11 @@ static const char * const COUNTRIES[] = {
     "PW",  "PY",  "QA",  "RE",  "RO",  "RS",  "RU",  "RW",  "SA",
     "SB",  "SC",  "SD",  "SE",  "SG",  "SH",  "SI",  "SJ",
     "SK",  "SL",  "SM",  "SN",  "SO",  "SR",  "SS",  "ST",  "SV",
-    "SX",  "SY",  "SZ",  "TC",  "TD",  "TF",  "TG",  "TH",  "TJ",
+    "SX",  "SY",  "SZ",  "TA",  "TC",  "TD",  "TF",  "TG",  "TH",  "TJ",
     "TK",  "TL",  "TM",  "TN",  "TO",  "TR",  "TT",  "TV",
     "TW",  "TZ",  "UA",  "UG",  "UM",  "US",  "UY",  "UZ",
     "VA",  "VC",  "VE",  "VG",  "VI",  "VN",  "VU",  "WF",
-    "WS",  "YE",  "YT",  "ZA",  "ZM",  "ZW",
+    "WS",  "XK",  "YE",  "YT",  "ZA",  "ZM",  "ZW",
 NULL,
     "AN",  "BU", "CS", "FX", "RO", "SU", "TP", "YD", "YU", "ZR",   /* obsolete country codes */
 NULL
@@ -356,9 +373,9 @@ static const char* const DEPRECATED_COUNTRIES[] = {
 };
 static const char* const REPLACEMENT_COUNTRIES[] = {
 /*  "AN", "BU", "CS", "DD", "DY", "FX", "HV", "NH", "RH", "SU", "TP", "UK", "VD", "YD", "YU", "ZR" */
-    "CW", "MM", "RS", "DE", "BJ", "FR", "BF", "VU", "ZW", "RU", "TL", "GB", "VN", "YE", "RS", "CD", NULL, NULL  /* replacement country codes */      
+    "CW", "MM", "RS", "DE", "BJ", "FR", "BF", "VU", "ZW", "RU", "TL", "GB", "VN", "YE", "RS", "CD", NULL, NULL  /* replacement country codes */
 };
-    
+
 /**
  * Table of 3-letter country codes.
  *
@@ -373,8 +390,8 @@ static const char* const REPLACEMENT_COUNTRIES[] = {
  * the two lists in COUNTRIES.
  */
 static const char * const COUNTRIES_3[] = {
-/*  "AD",  "AE",  "AF",  "AG",  "AI",  "AL",  "AM",      */
-    "AND", "ARE", "AFG", "ATG", "AIA", "ALB", "ARM",
+/*  "AC",  "AD",  "AE",  "AF",  "AG",  "AI",  "AL",  "AM",      */
+    "ASC", "AND", "ARE", "AFG", "ATG", "AIA", "ALB", "ARM",
 /*  "AO",  "AQ",  "AR",  "AS",  "AT",  "AU",  "AW",  "AX",  "AZ",     */
     "AGO", "ATA", "ARG", "ASM", "AUT", "AUS", "ABW", "ALA", "AZE",
 /*  "BA",  "BB",  "BD",  "BE",  "BF",  "BG",  "BH",  "BI",     */
@@ -383,12 +400,12 @@ static const char * const COUNTRIES_3[] = {
     "BEN", "BLM", "BMU", "BRN", "BOL", "BES", "BRA", "BHS", "BTN", "BVT",
 /*  "BW",  "BY",  "BZ",  "CA",  "CC",  "CD",  "CF",  "CG",     */
     "BWA", "BLR", "BLZ", "CAN", "CCK", "COD", "CAF", "COG",
-/*  "CH",  "CI",  "CK",  "CL",  "CM",  "CN",  "CO",  "CR",     */
-    "CHE", "CIV", "COK", "CHL", "CMR", "CHN", "COL", "CRI",
-/*  "CU",  "CV",  "CW",  "CX",  "CY",  "CZ",  "DE",  "DJ",  "DK",     */
-    "CUB", "CPV", "CUW", "CXR", "CYP", "CZE", "DEU", "DJI", "DNK",
-/*  "DM",  "DO",  "DZ",  "EC",  "EE",  "EG",  "EH",  "ER",     */
-    "DMA", "DOM", "DZA", "ECU", "EST", "EGY", "ESH", "ERI",
+/*  "CH",  "CI",  "CK",  "CL",  "CM",  "CN",  "CO",  "CP",  "CR",     */
+    "CHE", "CIV", "COK", "CHL", "CMR", "CHN", "COL", "CPT", "CRI",
+/*  "CU",  "CV",  "CW",  "CX",  "CY",  "CZ",  "DE",  "DG",  "DJ",  "DK",     */
+    "CUB", "CPV", "CUW", "CXR", "CYP", "CZE", "DEU", "DGA", "DJI", "DNK",
+/*  "DM",  "DO",  "DZ",  "EA",  "EC",  "EE",  "EG",  "EH",  "ER",     */
+    "DMA", "DOM", "DZA", "EA ", "ECU", "EST", "EGY", "ESH", "ERI", /* no valid 3-letter code for EA */
 /*  "ES",  "ET",  "FI",  "FJ",  "FK",  "FM",  "FO",  "FR",     */
     "ESP", "ETH", "FIN", "FJI", "FLK", "FSM", "FRO", "FRA",
 /*  "GA",  "GB",  "GD",  "GE",  "GF",  "GG",  "GH",  "GI",  "GL",     */
@@ -397,8 +414,8 @@ static const char * const COUNTRIES_3[] = {
     "GMB", "GIN", "GLP", "GNQ", "GRC", "SGS", "GTM", "GUM",
 /*  "GW",  "GY",  "HK",  "HM",  "HN",  "HR",  "HT",  "HU",     */
     "GNB", "GUY", "HKG", "HMD", "HND", "HRV", "HTI", "HUN",
-/*  "ID",  "IE",  "IL",  "IM",  "IN",  "IO",  "IQ",  "IR",  "IS" */
-    "IDN", "IRL", "ISR", "IMN", "IND", "IOT", "IRQ", "IRN", "ISL",
+/*  "IC",  "ID",  "IE",  "IL",  "IM",  "IN",  "IO",  "IQ",  "IR",  "IS" */
+    "IC ", "IDN", "IRL", "ISR", "IMN", "IND", "IOT", "IRQ", "IRN", "ISL", /* no valid 3-letter code for IC */
 /*  "IT",  "JE",  "JM",  "JO",  "JP",  "KE",  "KG",  "KH",  "KI",     */
     "ITA", "JEY", "JAM", "JOR", "JPN", "KEN", "KGZ", "KHM", "KIR",
 /*  "KM",  "KN",  "KP",  "KR",  "KW",  "KY",  "KZ",  "LA",     */
@@ -423,16 +440,16 @@ static const char * const COUNTRIES_3[] = {
     "SLB", "SYC", "SDN", "SWE", "SGP", "SHN", "SVN", "SJM",
 /*  "SK",  "SL",  "SM",  "SN",  "SO",  "SR",  "SS",  "ST",  "SV",     */
     "SVK", "SLE", "SMR", "SEN", "SOM", "SUR", "SSD", "STP", "SLV",
-/*  "SX",  "SY",  "SZ",  "TC",  "TD",  "TF",  "TG",  "TH",  "TJ",     */
-    "SXM", "SYR", "SWZ", "TCA", "TCD", "ATF", "TGO", "THA", "TJK",
+/*  "SX",  "SY",  "SZ",  "TA",  "TC",  "TD",  "TF",  "TG",  "TH",  "TJ",     */
+    "SXM", "SYR", "SWZ", "TAA", "TCA", "TCD", "ATF", "TGO", "THA", "TJK",
 /*  "TK",  "TL",  "TM",  "TN",  "TO",  "TR",  "TT",  "TV",     */
     "TKL", "TLS", "TKM", "TUN", "TON", "TUR", "TTO", "TUV",
 /*  "TW",  "TZ",  "UA",  "UG",  "UM",  "US",  "UY",  "UZ",     */
     "TWN", "TZA", "UKR", "UGA", "UMI", "USA", "URY", "UZB",
 /*  "VA",  "VC",  "VE",  "VG",  "VI",  "VN",  "VU",  "WF",     */
     "VAT", "VCT", "VEN", "VGB", "VIR", "VNM", "VUT", "WLF",
-/*  "WS",  "YE",  "YT",  "ZA",  "ZM",  "ZW",          */
-    "WSM", "YEM", "MYT", "ZAF", "ZMB", "ZWE",
+/*  "WS",  "XK",  "YE",  "YT",  "ZA",  "ZM",  "ZW",          */
+    "WSM", "XKK", "YEM", "MYT", "ZAF", "ZMB", "ZWE",
 NULL,
 /*  "AN",  "BU",  "CS",  "FX",  "RO", "SU",  "TP",  "YD",  "YU",  "ZR" */
     "ANT", "BUR", "SCG", "FXX", "ROM", "SUN", "TMP", "YMD", "YUG", "ZAR",
@@ -442,8 +459,6 @@ NULL
 typedef struct CanonicalizationMap {
     const char *id;          /* input ID */
     const char *canonicalID; /* canonicalized output ID */
-    const char *keyword;     /* keyword, or NULL if none */
-    const char *value;       /* keyword value, or NULL if kw==NULL */
 } CanonicalizationMap;
 
 /**
@@ -451,79 +466,37 @@ typedef struct CanonicalizationMap {
  * different semantic kinds of transformations.
  */
 static const CanonicalizationMap CANONICALIZE_MAP[] = {
-    { "",               "en_US_POSIX", NULL, NULL }, /* .NET name */
-    { "c",              "en_US_POSIX", NULL, NULL }, /* POSIX name */
-    { "posix",          "en_US_POSIX", NULL, NULL }, /* POSIX name (alias of C) */
-    { "art_LOJBAN",     "jbo", NULL, NULL }, /* registered name */
-    { "az_AZ_CYRL",     "az_Cyrl_AZ", NULL, NULL }, /* .NET name */
-    { "az_AZ_LATN",     "az_Latn_AZ", NULL, NULL }, /* .NET name */
-    { "ca_ES_PREEURO",  "ca_ES", "currency", "ESP" },
-    { "de__PHONEBOOK",  "de", "collation", "phonebook" }, /* Old ICU name */
-    { "de_AT_PREEURO",  "de_AT", "currency", "ATS" },
-    { "de_DE_PREEURO",  "de_DE", "currency", "DEM" },
-    { "de_LU_PREEURO",  "de_LU", "currency", "LUF" },
-    { "el_GR_PREEURO",  "el_GR", "currency", "GRD" },
-    { "en_BE_PREEURO",  "en_BE", "currency", "BEF" },
-    { "en_IE_PREEURO",  "en_IE", "currency", "IEP" },
-    { "es__TRADITIONAL", "es", "collation", "traditional" }, /* Old ICU name */
-    { "es_ES_PREEURO",  "es_ES", "currency", "ESP" },
-    { "eu_ES_PREEURO",  "eu_ES", "currency", "ESP" },
-    { "fi_FI_PREEURO",  "fi_FI", "currency", "FIM" },
-    { "fr_BE_PREEURO",  "fr_BE", "currency", "BEF" },
-    { "fr_FR_PREEURO",  "fr_FR", "currency", "FRF" },
-    { "fr_LU_PREEURO",  "fr_LU", "currency", "LUF" },
-    { "ga_IE_PREEURO",  "ga_IE", "currency", "IEP" },
-    { "gl_ES_PREEURO",  "gl_ES", "currency", "ESP" },
-    { "hi__DIRECT",     "hi", "collation", "direct" }, /* Old ICU name */
-    { "it_IT_PREEURO",  "it_IT", "currency", "ITL" },
-    { "ja_JP_TRADITIONAL", "ja_JP", "calendar", "japanese" }, /* Old ICU name */
-    { "nb_NO_NY",       "nn_NO", NULL, NULL },  /* "markus said this was ok" :-) */
-    { "nl_BE_PREEURO",  "nl_BE", "currency", "BEF" },
-    { "nl_NL_PREEURO",  "nl_NL", "currency", "NLG" },
-    { "pt_PT_PREEURO",  "pt_PT", "currency", "PTE" },
-    { "sr_SP_CYRL",     "sr_Cyrl_RS", NULL, NULL }, /* .NET name */
-    { "sr_SP_LATN",     "sr_Latn_RS", NULL, NULL }, /* .NET name */
-    { "sr_YU_CYRILLIC", "sr_Cyrl_RS", NULL, NULL }, /* Linux name */
-    { "th_TH_TRADITIONAL", "th_TH", "calendar", "buddhist" }, /* Old ICU name */
-    { "uz_UZ_CYRILLIC", "uz_Cyrl_UZ", NULL, NULL }, /* Linux name */
-    { "uz_UZ_CYRL",     "uz_Cyrl_UZ", NULL, NULL }, /* .NET name */
-    { "uz_UZ_LATN",     "uz_Latn_UZ", NULL, NULL }, /* .NET name */
-    { "zh_CHS",         "zh_Hans", NULL, NULL }, /* .NET name */
-    { "zh_CHT",         "zh_Hant", NULL, NULL }, /* .NET name */
-    { "zh_GAN",         "gan", NULL, NULL }, /* registered name */
-    { "zh_GUOYU",       "zh", NULL, NULL }, /* registered name */
-    { "zh_HAKKA",       "hak", NULL, NULL }, /* registered name */
-    { "zh_MIN_NAN",     "nan", NULL, NULL }, /* registered name */
-    { "zh_WUU",         "wuu", NULL, NULL }, /* registered name */
-    { "zh_XIANG",       "hsn", NULL, NULL }, /* registered name */
-    { "zh_YUE",         "yue", NULL, NULL }, /* registered name */
-};
-
-typedef struct VariantMap {
-    const char *variant;          /* input ID */
-    const char *keyword;     /* keyword, or NULL if none */
-    const char *value;       /* keyword value, or NULL if kw==NULL */
-} VariantMap;
-
-static const VariantMap VARIANT_MAP[] = {
-    { "EURO",   "currency", "EUR" },
-    { "PINYIN", "collation", "pinyin" }, /* Solaris variant */
-    { "STROKE", "collation", "stroke" }  /* Solaris variant */
+    { "",               "en_US_POSIX" }, /* .NET name */ // open ICU 64 deleted, we restore
+    { "c",              "en_US_POSIX" }, /* POSIX name */ // open ICU 64 deleted, we restore
+    { "posix",          "en_US_POSIX" }, /* POSIX name (alias of C) */ // open ICU 64 deleted, we restore
+    { "art_LOJBAN",     "jbo" }, /* registered name */
+    { "hy__AREVELA",    "hy" }, /* Registered IANA variant */
+    { "hy__AREVMDA",    "hyw" }, /* Registered IANA variant */
+    { "zh_GAN",         "gan" }, /* registered name */
+    { "zh_GUOYU",       "zh" }, /* registered name */
+    { "zh_HAKKA",       "hak" }, /* registered name */
+    { "zh_MIN_NAN",     "nan" }, /* registered name */
+    { "zh_WUU",         "wuu" }, /* registered name */
+    { "zh_XIANG",       "hsn" }, /* registered name */
+    { "zh_YUE",         "yue" }, /* registered name */
 };
 
 /* ### BCP47 Conversion *******************************************/
 /* Test if the locale id has BCP47 u extension and does not have '@' */
 #define _hasBCP47Extension(id) (id && uprv_strstr(id, "@") == NULL && getShortestSubtagLength(localeID) == 1)
 /* Converts the BCP47 id to Unicode id. Does nothing to id if conversion fails */
-#define _ConvertBCP47(finalID, id, buffer, length,err) \
-        if (uloc_forLanguageTag(id, buffer, length, NULL, err) <= 0 || U_FAILURE(*err)) { \
-            finalID=id; \
-        } else { \
-            finalID=buffer; \
-        }
+#define _ConvertBCP47(finalID, id, buffer, length,err) UPRV_BLOCK_MACRO_BEGIN { \
+    if (uloc_forLanguageTag(id, buffer, length, NULL, err) <= 0 || \
+            U_FAILURE(*err) || *err == U_STRING_NOT_TERMINATED_WARNING) { \
+        finalID=id; \
+        if (*err == U_STRING_NOT_TERMINATED_WARNING) { *err = U_BUFFER_OVERFLOW_ERROR; } \
+    } else { \
+        finalID=buffer; \
+    } \
+} UPRV_BLOCK_MACRO_END
 /* Gets the size of the shortest subtag in the given localeID. */
 static int32_t getShortestSubtagLength(const char *localeID) {
-    int32_t localeIDLength = uprv_strlen(localeID);
+    int32_t localeIDLength = static_cast<int32_t>(uprv_strlen(localeID));
     int32_t length = localeIDLength;
     int32_t tmpLength = 0;
     int32_t i;
@@ -548,6 +521,10 @@ static int32_t getShortestSubtagLength(const char *localeID) {
 }
 
 /* ### Keywords **************************************************/
+#define UPRV_ISDIGIT(c) (((c) >= '0') && ((c) <= '9'))
+#define UPRV_ISALPHANUM(c) (uprv_isASCIILetter(c) || UPRV_ISDIGIT(c) )
+/* Punctuation/symbols allowed in legacy key values */
+#define UPRV_OK_VALUE_PUNCTUATION(c) ((c) == '_' || (c) == '-' || (c) == '+' || (c) == '/')
 
 #define ULOC_KEYWORD_BUFFER_LEN 25
 #define ULOC_MAX_NO_KEYWORDS 25
@@ -584,21 +561,27 @@ locale_getKeywordsStart(const char *localeID) {
  */
 static int32_t locale_canonKeywordName(char *buf, const char *keywordName, UErrorCode *status)
 {
-  int32_t i;
-  int32_t keywordNameLen = (int32_t)uprv_strlen(keywordName);
-  
-  if(keywordNameLen >= ULOC_KEYWORD_BUFFER_LEN) {
-    /* keyword name too long for internal buffer */
-    *status = U_INTERNAL_PROGRAM_ERROR;
-          return 0;
+  int32_t keywordNameLen = 0;
+
+  for (; *keywordName != 0; keywordName++) {
+    if (!UPRV_ISALPHANUM(*keywordName)) {
+      *status = U_ILLEGAL_ARGUMENT_ERROR; /* malformed keyword name */
+      return 0;
+    }
+    if (keywordNameLen < ULOC_KEYWORD_BUFFER_LEN - 1) {
+      buf[keywordNameLen++] = uprv_tolower(*keywordName);
+    } else {
+      /* keyword name too long for internal buffer */
+      *status = U_INTERNAL_PROGRAM_ERROR;
+      return 0;
+    }
   }
-  
-  /* normalize the keyword name */
-  for(i = 0; i < keywordNameLen; i++) {
-    buf[i] = uprv_tolower(keywordName[i]);
+  if (keywordNameLen == 0) {
+    *status = U_ILLEGAL_ARGUMENT_ERROR; /* empty keyword name */
+    return 0;
   }
-  buf[i] = 0;
-    
+  buf[keywordNameLen] = 0; /* terminate */
+
   return keywordNameLen;
 }
 
@@ -616,24 +599,16 @@ compareKeywordStructs(const void * /*context*/, const void *left, const void *ri
     return uprv_strcmp(leftString, rightString);
 }
 
-/**
- * Both addKeyword and addValue must already be in canonical form.
- * Either both addKeyword and addValue are NULL, or neither is NULL.
- * If they are not NULL they must be zero terminated.
- * If addKeyword is not NULL is must have length small enough to fit in KeywordStruct.keyword.
- */
 static int32_t
 _getKeywords(const char *localeID,
              char prev,
              char *keywords, int32_t keywordCapacity,
              char *values, int32_t valuesCapacity, int32_t *valLen,
              UBool valuesToo,
-             const char* addKeyword,
-             const char* addValue,
              UErrorCode *status)
 {
     KeywordStruct keywordList[ULOC_MAX_NO_KEYWORDS];
-    
+
     int32_t maxKeywords = ULOC_MAX_NO_KEYWORDS;
     int32_t numKeywords = 0;
     const char* pos = localeID;
@@ -694,7 +669,7 @@ _getKeywords(const char *localeID,
             }
 
             /* Premature end or zero-length value */
-            if (!equalSign || equalSign == semicolon) {
+            if (!*equalSign || equalSign == semicolon) {
                 *status = U_INVALID_FORMAT_ERROR;
                 return 0;
             }
@@ -728,37 +703,10 @@ _getKeywords(const char *localeID,
             }
         } while(pos);
 
-        /* Handle addKeyword/addValue. */
-        if (addKeyword != NULL) {
-            UBool duplicate = FALSE;
-            U_ASSERT(addValue != NULL);
-            /* Search for duplicate; if found, do nothing. Explicit keyword
-               overrides addKeyword. */
-            for (j=0; j<numKeywords; ++j) {
-                if (uprv_strcmp(keywordList[j].keyword, addKeyword) == 0) {
-                    duplicate = TRUE;
-                    break;
-                }
-            }
-            if (!duplicate) {
-                if (numKeywords == maxKeywords) {
-                    *status = U_INTERNAL_PROGRAM_ERROR;
-                    return 0;
-                }
-                uprv_strcpy(keywordList[numKeywords].keyword, addKeyword);
-                keywordList[numKeywords].keywordLen = (int32_t)uprv_strlen(addKeyword);
-                keywordList[numKeywords].valueStart = addValue;
-                keywordList[numKeywords].valueLen = (int32_t)uprv_strlen(addValue);
-                ++numKeywords;
-            }
-        } else {
-            U_ASSERT(addValue == NULL);
-        }
-
         /* now we have a list of keywords */
         /* we need to sort it */
         uprv_sortArray(keywordList, numKeywords, sizeof(KeywordStruct), compareKeywordStructs, NULL, FALSE, status);
-        
+
         /* Now construct the keyword part */
         for(i = 0; i < numKeywords; i++) {
             if(keywordsLen + keywordList[i].keywordLen + 1< keywordCapacity) {
@@ -771,13 +719,13 @@ _getKeywords(const char *localeID,
             }
             keywordsLen += keywordList[i].keywordLen + 1;
             if(valuesToo) {
-                if(keywordsLen + keywordList[i].valueLen < keywordCapacity) {
+                if(keywordsLen + keywordList[i].valueLen <= keywordCapacity) {
                     uprv_strncpy(keywords+keywordsLen, keywordList[i].valueStart, keywordList[i].valueLen);
                 }
                 keywordsLen += keywordList[i].valueLen;
-                
+
                 if(i < numKeywords - 1) {
-                    if(keywordsLen < keywordCapacity) {       
+                    if(keywordsLen < keywordCapacity) {
                         keywords[keywordsLen] = ';';
                     }
                     keywordsLen++;
@@ -797,7 +745,7 @@ _getKeywords(const char *localeID,
                 *valLen = valuesLen;
             }
         }
-        return u_terminateChars(keywords, keywordCapacity, keywordsLen, status);   
+        return u_terminateChars(keywords, keywordCapacity, keywordsLen, status);
     } else {
         return 0;
     }
@@ -812,7 +760,7 @@ locale_getKeywords(const char *localeID,
                    UErrorCode *status) {
     return _getKeywords(localeID, prev, keywords, keywordCapacity,
                         values, valuesCapacity, valLen, valuesToo,
-                        NULL, NULL, status);
+                        status);
 }
 
 U_CAPI int32_t U_EXPORT2
@@ -820,92 +768,116 @@ uloc_getKeywordValue(const char* localeID,
                      const char* keywordName,
                      char* buffer, int32_t bufferCapacity,
                      UErrorCode* status)
-{ 
+{
+    if (buffer != nullptr) {
+        buffer[0] = '\0';
+    }
     const char* startSearchHere = NULL;
     const char* nextSeparator = NULL;
     char keywordNameBuffer[ULOC_KEYWORD_BUFFER_LEN];
     char localeKeywordNameBuffer[ULOC_KEYWORD_BUFFER_LEN];
-    int32_t i = 0;
     int32_t result = 0;
 
     if(status && U_SUCCESS(*status) && localeID) {
       char tempBuffer[ULOC_FULLNAME_CAPACITY];
       const char* tmpLocaleID;
 
+      if (keywordName == NULL || keywordName[0] == 0) {
+        *status = U_ILLEGAL_ARGUMENT_ERROR;
+        return 0;
+      }
+
+      locale_canonKeywordName(keywordNameBuffer, keywordName, status);
+      if(U_FAILURE(*status)) {
+        return 0;
+      }
+
       if (_hasBCP47Extension(localeID)) {
           _ConvertBCP47(tmpLocaleID, localeID, tempBuffer, sizeof(tempBuffer), status);
       } else {
           tmpLocaleID=localeID;
       }
-    
-      startSearchHere = uprv_strchr(tmpLocaleID, '@'); /* TODO: REVISIT: shouldn't this be locale_getKeywordsStart ? */
+
+      startSearchHere = locale_getKeywordsStart(tmpLocaleID);
       if(startSearchHere == NULL) {
           /* no keywords, return at once */
           return 0;
       }
 
-      locale_canonKeywordName(keywordNameBuffer, keywordName, status);
-      if(U_FAILURE(*status)) {
-        return 0;
-      }
-    
       /* find the first keyword */
       while(startSearchHere) {
-          startSearchHere++;
-          /* skip leading spaces (allowed?) */
+          const char* keyValueTail;
+          int32_t keyValueLen;
+
+          startSearchHere++; /* skip @ or ; */
+          nextSeparator = uprv_strchr(startSearchHere, '=');
+          if(!nextSeparator) {
+              *status = U_ILLEGAL_ARGUMENT_ERROR; /* key must have =value */
+              return 0;
+          }
+          /* strip leading & trailing spaces (TC decided to tolerate these) */
           while(*startSearchHere == ' ') {
               startSearchHere++;
           }
-          nextSeparator = uprv_strchr(startSearchHere, '=');
-          /* need to normalize both keyword and keyword name */
-          if(!nextSeparator) {
-              break;
+          keyValueTail = nextSeparator;
+          while (keyValueTail > startSearchHere && *(keyValueTail-1) == ' ') {
+              keyValueTail--;
           }
-          if(nextSeparator - startSearchHere >= ULOC_KEYWORD_BUFFER_LEN) {
+          /* now keyValueTail points to first char after the keyName */
+          /* copy & normalize keyName from locale */
+          if (startSearchHere == keyValueTail) {
+              *status = U_ILLEGAL_ARGUMENT_ERROR; /* empty keyword name in passed-in locale */
+              return 0;
+          }
+          keyValueLen = 0;
+          while (startSearchHere < keyValueTail) {
+            if (!UPRV_ISALPHANUM(*startSearchHere)) {
+              *status = U_ILLEGAL_ARGUMENT_ERROR; /* malformed keyword name */
+              return 0;
+            }
+            if (keyValueLen < ULOC_KEYWORD_BUFFER_LEN - 1) {
+              localeKeywordNameBuffer[keyValueLen++] = uprv_tolower(*startSearchHere++);
+            } else {
               /* keyword name too long for internal buffer */
               *status = U_INTERNAL_PROGRAM_ERROR;
               return 0;
+            }
           }
-          for(i = 0; i < nextSeparator - startSearchHere; i++) {
-              localeKeywordNameBuffer[i] = uprv_tolower(startSearchHere[i]);
-          }
-          /* trim trailing spaces */
-          while(startSearchHere[i-1] == ' ') {
-              i--;
-              U_ASSERT(i>=0);
-          }
-          localeKeywordNameBuffer[i] = 0;
-        
+          localeKeywordNameBuffer[keyValueLen] = 0; /* terminate */
+
           startSearchHere = uprv_strchr(nextSeparator, ';');
-        
+
           if(uprv_strcmp(keywordNameBuffer, localeKeywordNameBuffer) == 0) {
-              nextSeparator++;
+               /* current entry matches the keyword. */
+             nextSeparator++; /* skip '=' */
+              /* First strip leading & trailing spaces (TC decided to tolerate these) */
               while(*nextSeparator == ' ') {
-                  nextSeparator++;
+                nextSeparator++;
               }
-              /* we actually found the keyword. Copy the value */
-              if(startSearchHere && startSearchHere - nextSeparator < bufferCapacity) {
-                  while(*(startSearchHere-1) == ' ') {
-                      startSearchHere--;
-                  }
-                  uprv_strncpy(buffer, nextSeparator, startSearchHere - nextSeparator);
-                  result = u_terminateChars(buffer, bufferCapacity, (int32_t)(startSearchHere - nextSeparator), status);
-              } else if(!startSearchHere && (int32_t)uprv_strlen(nextSeparator) < bufferCapacity) { /* last item in string */
-                  i = (int32_t)uprv_strlen(nextSeparator);
-                  while(nextSeparator[i - 1] == ' ') {
-                      i--;
-                  }
-                  uprv_strncpy(buffer, nextSeparator, i);
-                  result = u_terminateChars(buffer, bufferCapacity, i, status);
-              } else {
-                  /* give a bigger buffer, please */
-                  *status = U_BUFFER_OVERFLOW_ERROR;
-                  if(startSearchHere) {
-                      result = (int32_t)(startSearchHere - nextSeparator);
-                  } else {
-                      result = (int32_t)uprv_strlen(nextSeparator); 
-                  }
+              keyValueTail = (startSearchHere)? startSearchHere: nextSeparator + uprv_strlen(nextSeparator);
+              while(keyValueTail > nextSeparator && *(keyValueTail-1) == ' ') {
+                keyValueTail--;
+              }
+              /* Now copy the value, but check well-formedness */
+              if (nextSeparator == keyValueTail) {
+                *status = U_ILLEGAL_ARGUMENT_ERROR; /* empty key value name in passed-in locale */
+                return 0;
               }
+              keyValueLen = 0;
+              while (nextSeparator < keyValueTail) {
+                if (!UPRV_ISALPHANUM(*nextSeparator) && !UPRV_OK_VALUE_PUNCTUATION(*nextSeparator)) {
+                  *status = U_ILLEGAL_ARGUMENT_ERROR; /* malformed key value */
+                  return 0;
+                }
+                if (keyValueLen < bufferCapacity) {
+                  /* Should we lowercase value to return here? Tests expect as-is. */
+                  buffer[keyValueLen++] = *nextSeparator++;
+                } else { /* keep advancing so we return correct length in case of overflow */
+                  keyValueLen++;
+                  nextSeparator++;
+                }
+              }
+              result = u_terminateChars(buffer, bufferCapacity, keyValueLen, status);
               return result;
           }
       }
@@ -924,51 +896,64 @@ uloc_setKeywordValue(const char* keywordName,
     int32_t keywordValueLen;
     int32_t bufLen;
     int32_t needLen = 0;
-    int32_t foundValueLen;
-    int32_t keywordAtEnd = 0; /* is the keyword at the end of the string? */
     char keywordNameBuffer[ULOC_KEYWORD_BUFFER_LEN];
+    char keywordValueBuffer[ULOC_KEYWORDS_CAPACITY+1];
     char localeKeywordNameBuffer[ULOC_KEYWORD_BUFFER_LEN];
-    int32_t i = 0;
     int32_t rc;
     char* nextSeparator = NULL;
     char* nextEqualsign = NULL;
     char* startSearchHere = NULL;
     char* keywordStart = NULL;
-    char *insertHere = NULL;
-    if(U_FAILURE(*status)) { 
-        return -1; 
+    CharString updatedKeysAndValues;
+    int32_t updatedKeysAndValuesLen;
+    UBool handledInputKeyAndValue = FALSE;
+    char keyValuePrefix = '@';
+
+    if(U_FAILURE(*status)) {
+        return -1;
     }
-    if(bufferCapacity>1) {
-        bufLen = (int32_t)uprv_strlen(buffer);
-    } else {
+    if (keywordName == NULL || keywordName[0] == 0 || bufferCapacity <= 1) {
         *status = U_ILLEGAL_ARGUMENT_ERROR;
         return 0;
     }
+    bufLen = (int32_t)uprv_strlen(buffer);
     if(bufferCapacity<bufLen) {
         /* The capacity is less than the length?! Is this NULL terminated? */
         *status = U_ILLEGAL_ARGUMENT_ERROR;
         return 0;
     }
-    if(keywordValue && !*keywordValue) { 
-        keywordValue = NULL;
-    }
-    if(keywordValue) {
-        keywordValueLen = (int32_t)uprv_strlen(keywordValue);
-    } else { 
-        keywordValueLen = 0;
-    }
     keywordNameLen = locale_canonKeywordName(keywordNameBuffer, keywordName, status);
     if(U_FAILURE(*status)) {
         return 0;
     }
+
+    keywordValueLen = 0;
+    if(keywordValue) {
+        while (*keywordValue != 0) {
+            if (!UPRV_ISALPHANUM(*keywordValue) && !UPRV_OK_VALUE_PUNCTUATION(*keywordValue)) {
+                *status = U_ILLEGAL_ARGUMENT_ERROR; /* malformed key value */
+                return 0;
+            }
+            if (keywordValueLen < ULOC_KEYWORDS_CAPACITY) {
+                /* Should we force lowercase in value to set? */
+                keywordValueBuffer[keywordValueLen++] = *keywordValue++;
+            } else {
+                /* keywordValue too long for internal buffer */
+                *status = U_INTERNAL_PROGRAM_ERROR;
+                return 0;
+            }
+        }
+    }
+    keywordValueBuffer[keywordValueLen] = 0; /* terminate */
+
     startSearchHere = (char*)locale_getKeywordsStart(buffer);
     if(startSearchHere == NULL || (startSearchHere[1]==0)) {
-        if(!keywordValue) { /* no keywords = nothing to remove */
-            return bufLen; 
+        if(keywordValueLen == 0) { /* no keywords = nothing to remove */
+            return bufLen;
         }
 
         needLen = bufLen+1+keywordNameLen+1+keywordValueLen;
-        if(startSearchHere) { /* had a single @ */ 
+        if(startSearchHere) { /* had a single @ */
             needLen--; /* already had the @ */
             /* startSearchHere points at the @ */
         } else {
@@ -978,133 +963,137 @@ uloc_setKeywordValue(const char* keywordName,
             *status = U_BUFFER_OVERFLOW_ERROR;
             return needLen; /* no change */
         }
-        *startSearchHere = '@';
-        startSearchHere++;
+        *startSearchHere++ = '@';
         uprv_strcpy(startSearchHere, keywordNameBuffer);
         startSearchHere += keywordNameLen;
-        *startSearchHere = '=';
-        startSearchHere++;
-        uprv_strcpy(startSearchHere, keywordValue);
-        startSearchHere+=keywordValueLen;
+        *startSearchHere++ = '=';
+        uprv_strcpy(startSearchHere, keywordValueBuffer);
         return needLen;
     } /* end shortcut - no @ */
-    
+
     keywordStart = startSearchHere;
     /* search for keyword */
     while(keywordStart) {
-        keywordStart++;
-        /* skip leading spaces (allowed?) */
+        const char* keyValueTail;
+        int32_t keyValueLen;
+
+        keywordStart++; /* skip @ or ; */
+        nextEqualsign = uprv_strchr(keywordStart, '=');
+        if (!nextEqualsign) {
+            *status = U_ILLEGAL_ARGUMENT_ERROR; /* key must have =value */
+            return 0;
+        }
+        /* strip leading & trailing spaces (TC decided to tolerate these) */
         while(*keywordStart == ' ') {
             keywordStart++;
         }
-        nextEqualsign = uprv_strchr(keywordStart, '=');
-        /* need to normalize both keyword and keyword name */
-        if(!nextEqualsign) {
-            break;
+        keyValueTail = nextEqualsign;
+        while (keyValueTail > keywordStart && *(keyValueTail-1) == ' ') {
+            keyValueTail--;
         }
-        if(nextEqualsign - keywordStart >= ULOC_KEYWORD_BUFFER_LEN) {
-            /* keyword name too long for internal buffer */
-            *status = U_INTERNAL_PROGRAM_ERROR;
+        /* now keyValueTail points to first char after the keyName */
+        /* copy & normalize keyName from locale */
+        if (keywordStart == keyValueTail) {
+            *status = U_ILLEGAL_ARGUMENT_ERROR; /* empty keyword name in passed-in locale */
             return 0;
         }
-        for(i = 0; i < nextEqualsign - keywordStart; i++) {
-            localeKeywordNameBuffer[i] = uprv_tolower(keywordStart[i]);
-        }
-        /* trim trailing spaces */
-        while(keywordStart[i-1] == ' ') {
-            i--;
+        keyValueLen = 0;
+        while (keywordStart < keyValueTail) {
+            if (!UPRV_ISALPHANUM(*keywordStart)) {
+                *status = U_ILLEGAL_ARGUMENT_ERROR; /* malformed keyword name */
+                return 0;
+            }
+            if (keyValueLen < ULOC_KEYWORD_BUFFER_LEN - 1) {
+                localeKeywordNameBuffer[keyValueLen++] = uprv_tolower(*keywordStart++);
+            } else {
+                /* keyword name too long for internal buffer */
+                *status = U_INTERNAL_PROGRAM_ERROR;
+                return 0;
+            }
         }
-        U_ASSERT(i>=0 && i<ULOC_KEYWORD_BUFFER_LEN);
-        localeKeywordNameBuffer[i] = 0;
+        localeKeywordNameBuffer[keyValueLen] = 0; /* terminate */
 
         nextSeparator = uprv_strchr(nextEqualsign, ';');
+
+        /* start processing the value part */
+        nextEqualsign++; /* skip '=' */
+        /* First strip leading & trailing spaces (TC decided to tolerate these) */
+        while(*nextEqualsign == ' ') {
+            nextEqualsign++;
+        }
+        keyValueTail = (nextSeparator)? nextSeparator: nextEqualsign + uprv_strlen(nextEqualsign);
+        while(keyValueTail > nextEqualsign && *(keyValueTail-1) == ' ') {
+            keyValueTail--;
+        }
+        if (nextEqualsign == keyValueTail) {
+            *status = U_ILLEGAL_ARGUMENT_ERROR; /* empty key value in passed-in locale */
+            return 0;
+        }
+
         rc = uprv_strcmp(keywordNameBuffer, localeKeywordNameBuffer);
         if(rc == 0) {
-            nextEqualsign++;
-            while(*nextEqualsign == ' ') {
-                nextEqualsign++;
-            }
-            /* we actually found the keyword. Change the value */
-            if (nextSeparator) {
-                keywordAtEnd = 0;
-                foundValueLen = (int32_t)(nextSeparator - nextEqualsign);
-            } else {
-                keywordAtEnd = 1;
-                foundValueLen = (int32_t)uprv_strlen(nextEqualsign);
-            }
-            if(keywordValue) { /* adding a value - not removing */
-              if(foundValueLen == keywordValueLen) {
-                uprv_strncpy(nextEqualsign, keywordValue, keywordValueLen);
-                return bufLen; /* no change in size */
-              } else if(foundValueLen > keywordValueLen) {
-                int32_t delta = foundValueLen - keywordValueLen;
-                if(nextSeparator) { /* RH side */
-                  uprv_memmove(nextSeparator - delta, nextSeparator, bufLen-(nextSeparator-buffer));
-                }
-                uprv_strncpy(nextEqualsign, keywordValue, keywordValueLen);
-                bufLen -= delta;
-                buffer[bufLen]=0;
-                return bufLen;
-              } else { /* FVL < KVL */
-                int32_t delta = keywordValueLen - foundValueLen;
-                if((bufLen+delta) >= bufferCapacity) {
-                  *status = U_BUFFER_OVERFLOW_ERROR;
-                  return bufLen+delta;
-                }
-                if(nextSeparator) { /* RH side */
-                  uprv_memmove(nextSeparator+delta,nextSeparator, bufLen-(nextSeparator-buffer));
-                }
-                uprv_strncpy(nextEqualsign, keywordValue, keywordValueLen);
-                bufLen += delta;
-                buffer[bufLen]=0;
-                return bufLen;
-              }
-            } else { /* removing a keyword */
-              if(keywordAtEnd) {
-                /* zero out the ';' or '@' just before startSearchhere */
-                keywordStart[-1] = 0;
-                return (int32_t)((keywordStart-buffer)-1); /* (string length without keyword) minus separator */
-              } else {
-                uprv_memmove(keywordStart, nextSeparator+1, bufLen-((nextSeparator+1)-buffer));
-                keywordStart[bufLen-((nextSeparator+1)-buffer)]=0;
-                return (int32_t)(bufLen-((nextSeparator+1)-keywordStart));
-              }
+            /* Current entry matches the input keyword. Update the entry */
+            if(keywordValueLen > 0) { /* updating a value */
+                updatedKeysAndValues.append(keyValuePrefix, *status);
+                keyValuePrefix = ';'; /* for any subsequent key-value pair */
+                updatedKeysAndValues.append(keywordNameBuffer, keywordNameLen, *status);
+                updatedKeysAndValues.append('=', *status);
+                updatedKeysAndValues.append(keywordValueBuffer, keywordValueLen, *status);
+            } /* else removing this entry, don't emit anything */
+            handledInputKeyAndValue = TRUE;
+        } else {
+           /* input keyword sorts earlier than current entry, add before current entry */
+            if (rc < 0 && keywordValueLen > 0 && !handledInputKeyAndValue) {
+                /* insert new entry at this location */
+                updatedKeysAndValues.append(keyValuePrefix, *status);
+                keyValuePrefix = ';'; /* for any subsequent key-value pair */
+                updatedKeysAndValues.append(keywordNameBuffer, keywordNameLen, *status);
+                updatedKeysAndValues.append('=', *status);
+                updatedKeysAndValues.append(keywordValueBuffer, keywordValueLen, *status);
+                handledInputKeyAndValue = TRUE;
             }
-        } else if(rc<0){ /* end match keyword */
-          /* could insert at this location. */
-          insertHere = keywordStart;
+            /* copy the current entry */
+            updatedKeysAndValues.append(keyValuePrefix, *status);
+            keyValuePrefix = ';'; /* for any subsequent key-value pair */
+            updatedKeysAndValues.append(localeKeywordNameBuffer, keyValueLen, *status);
+            updatedKeysAndValues.append('=', *status);
+            updatedKeysAndValues.append(nextEqualsign, static_cast<int32_t>(keyValueTail-nextEqualsign), *status);
+        }
+        if (!nextSeparator && keywordValueLen > 0 && !handledInputKeyAndValue) {
+            /* append new entry at the end, it sorts later than existing entries */
+            updatedKeysAndValues.append(keyValuePrefix, *status);
+            /* skip keyValuePrefix update, no subsequent key-value pair */
+            updatedKeysAndValues.append(keywordNameBuffer, keywordNameLen, *status);
+            updatedKeysAndValues.append('=', *status);
+            updatedKeysAndValues.append(keywordValueBuffer, keywordValueLen, *status);
+            handledInputKeyAndValue = TRUE;
         }
         keywordStart = nextSeparator;
     } /* end loop searching */
-    
-    if(!keywordValue) {
-      return bufLen; /* removal of non-extant keyword - no change */
-    }
 
-    /* we know there is at least one keyword. */
-    needLen = bufLen+1+keywordNameLen+1+keywordValueLen;
+    /* Any error from updatedKeysAndValues.append above would be internal and not due to
+     * problems with the passed-in locale. So if we did encounter problems with the
+     * passed-in locale above, those errors took precedence and overrode any error
+     * status from updatedKeysAndValues.append, and also caused a return of 0. If there
+     * are errors here they are from updatedKeysAndValues.append; they do cause an
+     * error return but the passed-in locale is unmodified and the original bufLen is
+     * returned.
+     */
+    if (!handledInputKeyAndValue || U_FAILURE(*status)) {
+        /* if input key/value specified removal of a keyword not present in locale, or
+         * there was an error in CharString.append, leave original locale alone. */
+        return bufLen;
+    }
+
+    updatedKeysAndValuesLen = updatedKeysAndValues.length();
+    /* needLen = length of the part before '@' + length of updated key-value part including '@' */
+    needLen = (int32_t)(startSearchHere - buffer) + updatedKeysAndValuesLen;
     if(needLen >= bufferCapacity) {
         *status = U_BUFFER_OVERFLOW_ERROR;
         return needLen; /* no change */
     }
-    
-    if(insertHere) {
-      uprv_memmove(insertHere+(1+keywordNameLen+1+keywordValueLen), insertHere, bufLen-(insertHere-buffer));
-      keywordStart = insertHere;
-    } else {
-      keywordStart = buffer+bufLen;
-      *keywordStart = ';';
-      keywordStart++;
-    }
-    uprv_strncpy(keywordStart, keywordNameBuffer, keywordNameLen);
-    keywordStart += keywordNameLen;
-    *keywordStart = '=';
-    keywordStart++;
-    uprv_strncpy(keywordStart, keywordValue, keywordValueLen); /* terminates. */
-    keywordStart+=keywordValueLen;
-    if(insertHere) {
-      *keywordStart = ';';
-      keywordStart++;
+    if (updatedKeysAndValuesLen > 0) {
+        uprv_strncpy(startSearchHere, updatedKeysAndValues.data(), updatedKeysAndValuesLen);
     }
     buffer[needLen]=0;
     return needLen;
@@ -1123,20 +1112,6 @@ uloc_setKeywordValue(const char* keywordName,
  */
 #define _isTerminator(a)  ((a==0)||(a=='.')||(a=='@'))
 
-static char* _strnchr(const char* str, int32_t len, char c) {
-    U_ASSERT(str != 0 && len >= 0);
-    while (len-- != 0) {
-        char d = *str;
-        if (d == c) {
-            return (char*) str;
-        } else if (d == 0) {
-            break;
-        }
-        ++str;
-    }
-    return NULL;
-}
-
 /**
  * Lookup 'key' in the array 'list'.  The array 'list' should contain
  * a NULL entry, followed by more entries, and a second NULL entry.
@@ -1182,7 +1157,7 @@ _copyCount(char *dest, int32_t destCapacity, const char *src) {
     }
 }
 
-U_CFUNC const char* 
+U_CFUNC const char*
 uloc_getCurrentCountryID(const char* oldID){
     int32_t offset = _findIndex(DEPRECATED_COUNTRIES, oldID);
     if (offset >= 0) {
@@ -1190,13 +1165,13 @@ uloc_getCurrentCountryID(const char* oldID){
     }
     return oldID;
 }
-U_CFUNC const char* 
+U_CFUNC const char*
 uloc_getCurrentLanguageID(const char* oldID){
     int32_t offset = _findIndex(DEPRECATED_LANGUAGES, oldID);
     if (offset >= 0) {
         return REPLACEMENT_LANGUAGES[offset];
     }
-    return oldID;        
+    return oldID;
 }
 /*
  * the internal functions _getLanguage(), _getCountry(), _getVariant()
@@ -1225,7 +1200,7 @@ ulocimp_getLanguage(const char *localeID,
         i+=2;
         localeID+=2;
     }
-    
+
     /* copy the language as far as possible and count its length */
     while(!_isTerminator(*localeID) && !_isIDSeparator(*localeID)) {
         if(i<languageCapacity) {
@@ -1400,7 +1375,7 @@ _getVariantEx(const char *localeID,
             localeID++;
         }
     }
-    
+
     return i;
 }
 
@@ -1411,50 +1386,6 @@ _getVariant(const char *localeID,
     return _getVariantEx(localeID, prev, variant, variantCapacity, FALSE);
 }
 
-/**
- * Delete ALL instances of a variant from the given list of one or
- * more variants.  Example: "FOO_EURO_BAR_EURO" => "FOO_BAR".
- * @param variants the source string of one or more variants,
- * separated by '_'.  This will be MODIFIED IN PLACE.  Not zero
- * terminated; if it is, trailing zero will NOT be maintained.
- * @param variantsLen length of variants
- * @param toDelete variant to delete, without separators, e.g.  "EURO"
- * or "PREEURO"; not zero terminated
- * @param toDeleteLen length of toDelete
- * @return number of characters deleted from variants
- */
-static int32_t
-_deleteVariant(char* variants, int32_t variantsLen,
-               const char* toDelete, int32_t toDeleteLen)
-{
-    int32_t delta = 0; /* number of chars deleted */
-    for (;;) {
-        UBool flag = FALSE;
-        if (variantsLen < toDeleteLen) {
-            return delta;
-        }
-        if (uprv_strncmp(variants, toDelete, toDeleteLen) == 0 &&
-            (variantsLen == toDeleteLen ||
-             (flag=(variants[toDeleteLen] == '_'))))
-        {
-            int32_t d = toDeleteLen + (flag?1:0);
-            variantsLen -= d;
-            delta += d;
-            if (variantsLen > 0) {
-                uprv_memmove(variants, variants+d, variantsLen);
-            }
-        } else {
-            char* p = _strnchr(variants, variantsLen, '_');
-            if (p == NULL) {
-                return delta;
-            }
-            ++p;
-            variantsLen -= (int32_t)(p - variants);
-            variants = p;
-        }
-    }
-}
-
 /* Keyword enumeration */
 
 typedef struct UKeywordsContext {
@@ -1462,6 +1393,8 @@ typedef struct UKeywordsContext {
     char* current;
 } UKeywordsContext;
 
+U_CDECL_BEGIN
+
 static void U_CALLCONV
 uloc_kw_closeKeywords(UEnumeration *enumerator) {
     uprv_free(((UKeywordsContext *)enumerator->context)->keywords);
@@ -1480,7 +1413,7 @@ uloc_kw_countKeywords(UEnumeration *en, UErrorCode * /*status*/) {
     return result;
 }
 
-static const char* U_CALLCONV 
+static const char * U_CALLCONV
 uloc_kw_nextKeyword(UEnumeration* en,
                     int32_t* resultLength,
                     UErrorCode* /*status*/) {
@@ -1498,12 +1431,15 @@ uloc_kw_nextKeyword(UEnumeration* en,
     return result;
 }
 
-static void U_CALLCONV 
-uloc_kw_resetKeywords(UEnumeration* en, 
+static void U_CALLCONV
+uloc_kw_resetKeywords(UEnumeration* en,
                       UErrorCode* /*status*/) {
     ((UKeywordsContext *)en->context)->current = ((UKeywordsContext *)en->context)->keywords;
 }
 
+U_CDECL_END
+
+
 static const UEnumeration gKeywordsEnum = {
     NULL,
     NULL,
@@ -1517,36 +1453,34 @@ static const UEnumeration gKeywordsEnum = {
 U_CAPI UEnumeration* U_EXPORT2
 uloc_openKeywordList(const char *keywordList, int32_t keywordListSize, UErrorCode* status)
 {
-    UKeywordsContext *myContext = NULL;
-    UEnumeration *result = NULL;
+    LocalMemory<UKeywordsContext> myContext;
+    LocalMemory<UEnumeration> result;
 
-    if(U_FAILURE(*status)) {
-        return NULL;
+    if (U_FAILURE(*status)) {
+        return nullptr;
     }
-    result = (UEnumeration *)uprv_malloc(sizeof(UEnumeration));
-    /* Null pointer test */
-    if (result == NULL) {
+    myContext.adoptInstead(static_cast<UKeywordsContext *>(uprv_malloc(sizeof(UKeywordsContext))));
+    result.adoptInstead(static_cast<UEnumeration *>(uprv_malloc(sizeof(UEnumeration))));
+    if (myContext.isNull() || result.isNull()) {
         *status = U_MEMORY_ALLOCATION_ERROR;
-        return NULL;
+        return nullptr;
     }
-    uprv_memcpy(result, &gKeywordsEnum, sizeof(UEnumeration));
-    myContext = static_cast<UKeywordsContext *>(uprv_malloc(sizeof(UKeywordsContext)));
-    if (myContext == NULL) {
+    uprv_memcpy(result.getAlias(), &gKeywordsEnum, sizeof(UEnumeration));
+    myContext->keywords = static_cast<char *>(uprv_malloc(keywordListSize+1));
+    if (myContext->keywords == nullptr) {
         *status = U_MEMORY_ALLOCATION_ERROR;
-        uprv_free(result);
-        return NULL;
+        return nullptr;
     }
-    myContext->keywords = (char *)uprv_malloc(keywordListSize+1);
     uprv_memcpy(myContext->keywords, keywordList, keywordListSize);
     myContext->keywords[keywordListSize] = 0;
     myContext->current = myContext->keywords;
-    result->context = myContext;
-    return result;
+    result->context = myContext.orphan();
+    return result.orphan();
 }
 
 U_CAPI UEnumeration* U_EXPORT2
 uloc_openKeywords(const char* localeID,
-                        UErrorCode* status) 
+                        UErrorCode* status)
 {
     int32_t i=0;
     char keywords[256];
@@ -1557,7 +1491,7 @@ uloc_openKeywords(const char* localeID,
     if(status==NULL || U_FAILURE(*status)) {
         return 0;
     }
-    
+
     if (_hasBCP47Extension(localeID)) {
         _ConvertBCP47(tmpLocaleID, localeID, tempBuffer, sizeof(tempBuffer), status);
     } else {
@@ -1606,7 +1540,7 @@ uloc_openKeywords(const char* localeID,
 #define OPTION_SET(options, mask) ((options & mask) != 0)
 
 static const char i_default[] = {'i', '-', 'd', 'e', 'f', 'a', 'u', 'l', 't'};
-#define I_DEFAULT_LENGTH (sizeof i_default / sizeof i_default[0])
+#define I_DEFAULT_LENGTH UPRV_LENGTHOF(i_default)
 
 /**
  * Canonicalize the given localeID, to level 1 or to level 2,
@@ -1628,15 +1562,13 @@ _canonicalize(const char* localeID,
     const char* tmpLocaleID;
     const char* keywordAssign = NULL;
     const char* separatorIndicator = NULL;
-    const char* addKeyword = NULL;
-    const char* addValue = NULL;
     char* name;
     char* variant = NULL; /* pointer into name, or NULL */
 
     if (U_FAILURE(*err)) {
         return 0;
     }
-    
+
     if (_hasBCP47Extension(localeID)) {
         _ConvertBCP47(tmpLocaleID, localeID, tempBuffer, sizeof(tempBuffer), err);
     } else {
@@ -1664,11 +1596,11 @@ _canonicalize(const char* localeID,
 
     if(len == I_DEFAULT_LENGTH && uprv_strncmp(origLocaleID, i_default, len) == 0) {
         const char *d = uloc_getDefault();
-        
+
         len = (int32_t)uprv_strlen(d);
 
         if (name != NULL) {
-            uprv_strncpy(name, d, len);
+            uprv_memcpy(name, d, len);
         }
     } else if(_isIDSeparator(*tmpLocaleID)) {
         const char *scriptID;
@@ -1794,29 +1726,8 @@ _canonicalize(const char* localeID,
             }
         }
 
-        /* Handle generic variants first */
-        if (variant) {
-            for (j=0; j<(int32_t)(sizeof(VARIANT_MAP)/sizeof(VARIANT_MAP[0])); j++) {
-                const char* variantToCompare = VARIANT_MAP[j].variant;
-                int32_t n = (int32_t)uprv_strlen(variantToCompare);
-                int32_t variantLen = _deleteVariant(variant, uprv_min(variantSize, (nameCapacity-len)), variantToCompare, n);
-                len -= variantLen;
-                if (variantLen > 0) {
-                    if (len > 0 && name[len-1] == '_') { /* delete trailing '_' */
-                        --len;
-                    }
-                    addKeyword = VARIANT_MAP[j].keyword;
-                    addValue = VARIANT_MAP[j].value;
-                    break;
-                }
-            }
-            if (len > 0 && len <= nameCapacity && name[len-1] == '_') { /* delete trailing '_' */
-                --len;
-            }
-        }
-
         /* Look up the ID in the canonicalization map */
-        for (j=0; j<(int32_t)(sizeof(CANONICALIZE_MAP)/sizeof(CANONICALIZE_MAP[0])); j++) {
+        for (j=0; j<UPRV_LENGTHOF(CANONICALIZE_MAP); j++) {
             const char* id = CANONICALIZE_MAP[j].id;
             int32_t n = (int32_t)uprv_strlen(id);
             if (len == n && uprv_strncmp(name, id, n) == 0) {
@@ -1824,10 +1735,6 @@ _canonicalize(const char* localeID,
                     break; /* Don't remap "" if keywords present */
                 }
                 len = _copyCount(name, nameCapacity, CANONICALIZE_MAP[j].canonicalID);
-                if (CANONICALIZE_MAP[j].keyword) {
-                    addKeyword = CANONICALIZE_MAP[j].keyword;
-                    addValue = CANONICALIZE_MAP[j].value;
-                }
                 break;
             }
         }
@@ -1842,14 +1749,7 @@ _canonicalize(const char* localeID,
             ++len;
             ++fieldCount;
             len += _getKeywords(tmpLocaleID+1, '@', (len<nameCapacity ? name+len : NULL), nameCapacity-len,
-                                NULL, 0, NULL, TRUE, addKeyword, addValue, err);
-        } else if (addKeyword != NULL) {
-            U_ASSERT(addValue != NULL && len < nameCapacity);
-            /* inelegant but works -- later make _getKeywords do this? */
-            len += _copyCount(name+len, nameCapacity-len, "@");
-            len += _copyCount(name+len, nameCapacity-len, addKeyword);
-            len += _copyCount(name+len, nameCapacity-len, "=");
-            len += _copyCount(name+len, nameCapacity-len, addValue);
+                                NULL, 0, NULL, TRUE, err);
         }
     }
 
@@ -1870,10 +1770,10 @@ uloc_getParent(const char*    localeID,
 {
     const char *lastUnderscore;
     int32_t i;
-    
+
     if (U_FAILURE(*err))
         return 0;
-    
+
     if (localeID == NULL)
         localeID = uloc_getDefault();
 
@@ -1887,6 +1787,7 @@ uloc_getParent(const char*    localeID,
     if(i>0 && parent != localeID) {
         uprv_memcpy(parent, localeID, uprv_min(i, parentCapacity));
     }
+
     return u_terminateChars(parent, parentCapacity, i, err);
 }
 
@@ -1902,7 +1803,7 @@ uloc_getLanguage(const char*    localeID,
     if (err==NULL || U_FAILURE(*err)) {
         return 0;
     }
-    
+
     if(localeID==NULL) {
         localeID=uloc_getDefault();
     }
@@ -1939,7 +1840,7 @@ U_CAPI int32_t  U_EXPORT2
 uloc_getCountry(const char* localeID,
             char* country,
             int32_t countryCapacity,
-            UErrorCode* err) 
+            UErrorCode* err)
 {
     int32_t i=0;
 
@@ -1972,16 +1873,16 @@ U_CAPI int32_t  U_EXPORT2
 uloc_getVariant(const char* localeID,
                 char* variant,
                 int32_t variantCapacity,
-                UErrorCode* err) 
+                UErrorCode* err)
 {
     char tempBuffer[ULOC_FULLNAME_CAPACITY];
     const char* tmpLocaleID;
     int32_t i=0;
-    
+
     if(err==NULL || U_FAILURE(*err)) {
         return 0;
     }
-    
+
     if (_hasBCP47Extension(localeID)) {
         _ConvertBCP47(tmpLocaleID, localeID, tempBuffer, sizeof(tempBuffer), err);
     } else {
@@ -1990,7 +1891,7 @@ uloc_getVariant(const char* localeID,
         }
         tmpLocaleID=localeID;
     }
-    
+
     /* Skip the language */
     ulocimp_getLanguage(tmpLocaleID, NULL, 0, &tmpLocaleID);
     if(_isIDSeparator(*tmpLocaleID)) {
@@ -2018,7 +1919,7 @@ uloc_getVariant(const char* localeID,
             }
         }
     }
-    
+
     /* removed by weiv. We don't want to handle POSIX variants anymore. Use canonicalization function */
     /* if we do not have a variant tag yet then try a POSIX variant after '@' */
 /*
@@ -2033,7 +1934,7 @@ U_CAPI int32_t  U_EXPORT2
 uloc_getName(const char* localeID,
              char* name,
              int32_t nameCapacity,
-             UErrorCode* err)  
+             UErrorCode* err)
 {
     return _canonicalize(localeID, name, nameCapacity, 0, err);
 }
@@ -2042,7 +1943,7 @@ U_CAPI int32_t  U_EXPORT2
 uloc_getBaseName(const char* localeID,
                  char* name,
                  int32_t nameCapacity,
-                 UErrorCode* err)  
+                 UErrorCode* err)
 {
     return _canonicalize(localeID, name, nameCapacity, _ULOC_STRIP_KEYWORDS, err);
 }
@@ -2051,18 +1952,18 @@ U_CAPI int32_t  U_EXPORT2
 uloc_canonicalize(const char* localeID,
                   char* name,
                   int32_t nameCapacity,
-                  UErrorCode* err)  
+                  UErrorCode* err)
 {
     return _canonicalize(localeID, name, nameCapacity, _ULOC_CANONICALIZE, err);
 }
-  
+
 U_CAPI const char*  U_EXPORT2
-uloc_getISO3Language(const char* localeID) 
+uloc_getISO3Language(const char* localeID)
 {
     int16_t offset;
     char lang[ULOC_LANG_CAPACITY];
     UErrorCode err = U_ZERO_ERROR;
-    
+
     if (localeID == NULL)
     {
         localeID = uloc_getDefault();
@@ -2077,12 +1978,12 @@ uloc_getISO3Language(const char* localeID)
 }
 
 U_CAPI const char*  U_EXPORT2
-uloc_getISO3Country(const char* localeID) 
+uloc_getISO3Country(const char* localeID)
 {
     int16_t offset;
     char cntry[ULOC_LANG_CAPACITY];
     UErrorCode err = U_ZERO_ERROR;
-    
+
     if (localeID == NULL)
     {
         localeID = uloc_getDefault();
@@ -2093,20 +1994,37 @@ uloc_getISO3Country(const char* localeID)
     offset = _findIndex(COUNTRIES, cntry);
     if (offset < 0)
         return "";
-    
+
     return COUNTRIES_3[offset];
 }
 
 U_CAPI uint32_t  U_EXPORT2
-uloc_getLCID(const char* localeID) 
+uloc_getLCID(const char* localeID)
 {
     UErrorCode status = U_ZERO_ERROR;
     char       langID[ULOC_FULLNAME_CAPACITY];
+    uint32_t   lcid = 0;
 
-    uloc_getLanguage(localeID, langID, sizeof(langID), &status);
+    /* Check for incomplete id. */
+    if (!localeID || uprv_strlen(localeID) < 2) {
+        return 0;
+    }
+
+    // First, attempt Windows platform lookup if available, but fall
+    // through to catch any special cases (ICU vs Windows name differences).
+    lcid = uprv_convertToLCIDPlatform(localeID, &status);
     if (U_FAILURE(status)) {
         return 0;
     }
+    if (lcid > 0) {
+        // Windows found an LCID, return that
+        return lcid;
+    }
+
+    uloc_getLanguage(localeID, langID, sizeof(langID), &status);
+    if (U_FAILURE(status) || status == U_STRING_NOT_TERMINATED_WARNING) {
+        return 0;
+    }
 
     if (uprv_strchr(localeID, '@')) {
         // uprv_convertToLCID does not support keywords other than collation.
@@ -2116,21 +2034,21 @@ uloc_getLCID(const char* localeID)
         char tmpLocaleID[ULOC_FULLNAME_CAPACITY];
 
         len = uloc_getKeywordValue(localeID, "collation", collVal,
-            sizeof(collVal)/sizeof(collVal[0]) - 1, &status);
+            UPRV_LENGTHOF(collVal) - 1, &status);
 
         if (U_SUCCESS(status) && len > 0) {
             collVal[len] = 0;
 
             len = uloc_getBaseName(localeID, tmpLocaleID,
-                sizeof(tmpLocaleID)/sizeof(tmpLocaleID[0]) - 1, &status);
+                UPRV_LENGTHOF(tmpLocaleID) - 1, &status);
 
-            if (U_SUCCESS(status)) {
+            if (U_SUCCESS(status) && len > 0) {
                 tmpLocaleID[len] = 0;
 
                 len = uloc_setKeywordValue("collation", collVal, tmpLocaleID,
-                    sizeof(tmpLocaleID)/sizeof(tmpLocaleID[0]) - len - 1, &status);
+                    UPRV_LENGTHOF(tmpLocaleID) - len - 1, &status);
 
-                if (U_SUCCESS(status)) {
+                if (U_SUCCESS(status) && len > 0) {
                     tmpLocaleID[len] = 0;
                     return uprv_convertToLCID(langID, tmpLocaleID, &status);
                 }
@@ -2161,12 +2079,12 @@ uloc_getDefault()
 
 U_CAPI void  U_EXPORT2
 uloc_setDefault(const char*   newDefaultLocale,
-             UErrorCode* err) 
+             UErrorCode* err)
 {
     if (U_FAILURE(*err))
         return;
     /* the error code isn't currently used for anything by this function*/
-    
+
     /* propagate change to C++ */
     locale_set_default(newDefaultLocale);
 }
@@ -2178,7 +2096,7 @@ uloc_setDefault(const char*   newDefaultLocale,
  * terminated with a null pointer.
  */
 U_CAPI const char* const*  U_EXPORT2
-uloc_getISOLanguages() 
+uloc_getISOLanguages()
 {
     return LANGUAGES;
 }
@@ -2190,7 +2108,7 @@ uloc_getISOLanguages()
  * terminated with a null pointer.
  */
 U_CAPI const char* const*  U_EXPORT2
-uloc_getISOCountries() 
+uloc_getISOCountries()
 {
     return COUNTRIES;
 }
@@ -2235,10 +2153,10 @@ _uloc_strtod(const char *start, char **end) {
     }
 }
 
-typedef struct { 
+typedef struct {
     float q;
     int32_t dummy;  /* to avoid uninitialized memory copy from qsort */
-    char *locale;
+    char locale[ULOC_FULLNAME_CAPACITY+1];
 } _acceptLangItem;
 
 static int32_t U_CALLCONV
@@ -2261,8 +2179,8 @@ uloc_acceptLanguageCompare(const void * /*context*/, const void *a, const void *
     }
 
 #if defined(ULOC_DEBUG)
-    /*  fprintf(stderr, "a:[%s:%g], b:[%s:%g] -> %d\n", 
-    aa->locale, aa->q, 
+    /*  fprintf(stderr, "a:[%s:%g], b:[%s:%g] -> %d\n",
+    aa->locale, aa->q,
     bb->locale, bb->q,
     rc);*/
 #endif
@@ -2270,7 +2188,7 @@ uloc_acceptLanguageCompare(const void * /*context*/, const void *a, const void *
     return rc;
 }
 
-/* 
+/*
 mt-mt, ja;q=0.76, en-us;q=0.95, en;q=0.92, en-gb;q=0.89, fr;q=0.87, iu-ca;q=0.84, iu;q=0.82, ja-jp;q=0.79, mt;q=0.97, de-de;q=0.74, de;q=0.71, es;q=0.68, it-it;q=0.66, it;q=0.63, vi-vn;q=0.61, vi;q=0.58, nl-nl;q=0.55, nl;q=0.53
 */
 
@@ -2280,9 +2198,7 @@ uloc_acceptLanguageFromHTTP(char *result, int32_t resultAvailable, UAcceptResult
                             UEnumeration* availableLocales,
                             UErrorCode *status)
 {
-    _acceptLangItem *j;
-    _acceptLangItem smallBuffer[30];
-    char **strs;
+  MaybeStackArray<_acceptLangItem, 4> items; // Struct for collecting items.
     char tmp[ULOC_FULLNAME_CAPACITY +1];
     int32_t n = 0;
     const char *itemEnd;
@@ -2292,11 +2208,7 @@ uloc_acceptLanguageFromHTTP(char *result, int32_t resultAvailable, UAcceptResult
     int32_t res;
     int32_t i;
     int32_t l = (int32_t)uprv_strlen(httpAcceptLanguage);
-    int32_t jSize;
-    char *tempstr; /* Use for null pointer check */
 
-    j = smallBuffer;
-    jSize = sizeof(smallBuffer)/sizeof(smallBuffer[0]);
     if(U_FAILURE(*status)) {
         return -1;
     }
@@ -2309,7 +2221,7 @@ uloc_acceptLanguageFromHTTP(char *result, int32_t resultAvailable, UAcceptResult
         if(!itemEnd) {
             itemEnd = httpAcceptLanguage+l; /* end of string */
         }
-        if(paramEnd && paramEnd<itemEnd) { 
+        if(paramEnd && paramEnd<itemEnd) {
             /* semicolon (;) is closer than end (,) */
             t = paramEnd+1;
             if(*t=='q') {
@@ -2324,27 +2236,29 @@ uloc_acceptLanguageFromHTTP(char *result, int32_t resultAvailable, UAcceptResult
             while(isspace(*t)) {
                 t++;
             }
-            j[n].q = (float)_uloc_strtod(t,NULL);
+            items[n].q = (float)_uloc_strtod(t,NULL);
         } else {
             /* no semicolon - it's 1.0 */
-            j[n].q = 1.0f;
+            items[n].q = 1.0f;
             paramEnd = itemEnd;
         }
-        j[n].dummy=0;
+        items[n].dummy=0;
         /* eat spaces prior to semi */
         for(t=(paramEnd-1);(paramEnd>s)&&isspace(*t);t--)
             ;
-        /* Check for null pointer from uprv_strndup */
-        tempstr = uprv_strndup(s,(int32_t)((t+1)-s));
-        if (tempstr == NULL) {
-            *status = U_MEMORY_ALLOCATION_ERROR;
-            return -1;
-        }
-        j[n].locale = tempstr;
-        uloc_canonicalize(j[n].locale,tmp,sizeof(tmp)/sizeof(tmp[0]),status);
-        if(strcmp(j[n].locale,tmp)) {
-            uprv_free(j[n].locale);
-            j[n].locale=uprv_strdup(tmp);
+        int32_t slen = static_cast<int32_t>(((t+1)-s));
+        if(slen > ULOC_FULLNAME_CAPACITY) {
+          *status = U_BUFFER_OVERFLOW_ERROR;
+          return -1; // too big
+        }
+        uprv_strncpy(items[n].locale, s, slen);
+        items[n].locale[slen]=0; // terminate
+        int32_t clen = uloc_canonicalize(items[n].locale, tmp, UPRV_LENGTHOF(tmp)-1, status);
+        if(U_FAILURE(*status)) return -1;
+        if((clen!=slen) || (uprv_strncmp(items[n].locale, tmp, slen))) {
+            // canonicalization had an effect- copy back
+            uprv_strncpy(items[n].locale, tmp, clen);
+            items[n].locale[clen] = 0; // terminate
         }
 #if defined(ULOC_DEBUG)
         /*fprintf(stderr,"%d: s <%s> q <%g>\n", n, j[n].locale, j[n].q);*/
@@ -2354,42 +2268,22 @@ uloc_acceptLanguageFromHTTP(char *result, int32_t resultAvailable, UAcceptResult
         while(*s==',') { /* eat duplicate commas */
             s++;
         }
-        if(n>=jSize) {
-            if(j==smallBuffer) {  /* overflowed the small buffer. */
-                j = static_cast<_acceptLangItem *>(uprv_malloc(sizeof(j[0])*(jSize*2)));
-                if(j!=NULL) {
-                    uprv_memcpy(j,smallBuffer,sizeof(j[0])*jSize);
-                }
-#if defined(ULOC_DEBUG)
-                fprintf(stderr,"malloced at size %d\n", jSize);
-#endif
-            } else {
-                j = static_cast<_acceptLangItem *>(uprv_realloc(j, sizeof(j[0])*jSize*2));
+        if(n>=items.getCapacity()) { // If we need more items
+          if(NULL == items.resize(items.getCapacity()*2, items.getCapacity())) {
+              *status = U_MEMORY_ALLOCATION_ERROR;
+              return -1;
+          }
 #if defined(ULOC_DEBUG)
-                fprintf(stderr,"re-alloced at size %d\n", jSize);
+          fprintf(stderr,"malloced at size %d\n", items.getCapacity());
 #endif
-            }
-            jSize *= 2;
-            if(j==NULL) {
-                *status = U_MEMORY_ALLOCATION_ERROR;
-                return -1;
-            }
         }
     }
-    uprv_sortArray(j, n, sizeof(j[0]), uloc_acceptLanguageCompare, NULL, TRUE, status);
-    if(U_FAILURE(*status)) {
-        if(j != smallBuffer) {
-#if defined(ULOC_DEBUG)
-            fprintf(stderr,"freeing j %p\n", j);
-#endif
-            uprv_free(j);
-        }
+    uprv_sortArray(items.getAlias(), n, sizeof(items[0]), uloc_acceptLanguageCompare, NULL, TRUE, status);
+    if (U_FAILURE(*status)) {
         return -1;
     }
-    strs = static_cast<char **>(uprv_malloc((size_t)(sizeof(strs[0])*n)));
-    /* Check for null pointer */
-    if (strs == NULL) {
-        uprv_free(j); /* Free to avoid memory leak */
+    LocalMemory<const char*> strs(NULL);
+    if (strs.allocateInsteadAndReset(n) == NULL) {
         *status = U_MEMORY_ALLOCATION_ERROR;
         return -1;
     }
@@ -2397,26 +2291,16 @@ uloc_acceptLanguageFromHTTP(char *result, int32_t resultAvailable, UAcceptResult
 #if defined(ULOC_DEBUG)
         /*fprintf(stderr,"%d: s <%s> q <%g>\n", i, j[i].locale, j[i].q);*/
 #endif
-        strs[i]=j[i].locale;
-    }
-    res =  uloc_acceptLanguage(result, resultAvailable, outResult, 
-        (const char**)strs, n, availableLocales, status);
-    for(i=0;i<n;i++) {
-        uprv_free(strs[i]);
-    }
-    uprv_free(strs);
-    if(j != smallBuffer) {
-#if defined(ULOC_DEBUG)
-        fprintf(stderr,"freeing j %p\n", j);
-#endif
-        uprv_free(j);
+        strs[i]=items[i].locale;
     }
+    res =  uloc_acceptLanguage(result, resultAvailable, outResult,
+                               strs.getAlias(), n, availableLocales, status);
     return res;
 }
 
 
 U_CAPI int32_t U_EXPORT2
-uloc_acceptLanguage(char *result, int32_t resultAvailable, 
+uloc_acceptLanguage(char *result, int32_t resultAvailable,
                     UAcceptResult *outResult, const char **acceptList,
                     int32_t acceptListCount,
                     UEnumeration* availableLocales,
@@ -2440,13 +2324,13 @@ uloc_acceptLanguage(char *result, int32_t resultAvailable,
 #if defined(ULOC_DEBUG)
         fprintf(stderr,"%02d: %s\n", i, acceptList[i]);
 #endif
-        while((l=uenum_next(availableLocales, NULL, status))) {
+        while((l=uenum_next(availableLocales, NULL, status)) != NULL) {
 #if defined(ULOC_DEBUG)
             fprintf(stderr,"  %s\n", l);
 #endif
             len = (int32_t)uprv_strlen(l);
             if(!uprv_strcmp(acceptList[i], l)) {
-                if(outResult) { 
+                if(outResult) {
                     *outResult = ULOC_ACCEPT_VALID;
                 }
 #if defined(ULOC_DEBUG)
@@ -2459,15 +2343,15 @@ uloc_acceptLanguage(char *result, int32_t resultAvailable,
                     uprv_free(fallbackList[j]);
                 }
                 uprv_free(fallbackList);
-                return u_terminateChars(result, resultAvailable, len, status);   
+                return u_terminateChars(result, resultAvailable, len, status);
             }
             if(len>maxLen) {
                 maxLen = len;
             }
         }
-        uenum_reset(availableLocales, status);    
+        uenum_reset(availableLocales, status);
         /* save off parent info */
-        if(uloc_getParent(acceptList[i], tmp, sizeof(tmp)/sizeof(tmp[0]), status)!=0) {
+        if(uloc_getParent(acceptList[i], tmp, UPRV_LENGTHOF(tmp), status)!=0) {
             fallbackList[i] = uprv_strdup(tmp);
         } else {
             fallbackList[i]=0;
@@ -2480,13 +2364,13 @@ uloc_acceptLanguage(char *result, int32_t resultAvailable,
 #if defined(ULOC_DEBUG)
                 fprintf(stderr,"Try: [%s]", fallbackList[i]);
 #endif
-                while((l=uenum_next(availableLocales, NULL, status))) {
+                while((l=uenum_next(availableLocales, NULL, status)) != NULL) {
 #if defined(ULOC_DEBUG)
                     fprintf(stderr,"  %s\n", l);
 #endif
                     len = (int32_t)uprv_strlen(l);
                     if(!uprv_strcmp(fallbackList[i], l)) {
-                        if(outResult) { 
+                        if(outResult) {
                             *outResult = ULOC_ACCEPT_FALLBACK;
                         }
 #if defined(ULOC_DEBUG)
@@ -2502,9 +2386,9 @@ uloc_acceptLanguage(char *result, int32_t resultAvailable,
                         return u_terminateChars(result, resultAvailable, len, status);
                     }
                 }
-                uenum_reset(availableLocales, status);    
+                uenum_reset(availableLocales, status);
 
-                if(uloc_getParent(fallbackList[i], tmp, sizeof(tmp)/sizeof(tmp[0]), status)!=0) {
+                if(uloc_getParent(fallbackList[i], tmp, UPRV_LENGTHOF(tmp), status)!=0) {
                     uprv_free(fallbackList[i]);
                     fallbackList[i] = uprv_strdup(tmp);
                 } else {
@@ -2513,7 +2397,7 @@ uloc_acceptLanguage(char *result, int32_t resultAvailable,
                 }
             }
         }
-        if(outResult) { 
+        if(outResult) {
             *outResult = ULOC_ACCEPT_FAILED;
         }
     }
@@ -2546,9 +2430,6 @@ uloc_toUnicodeLocaleType(const char* keyword, const char* value)
     return bcpType;
 }
 
-#define UPRV_ISDIGIT(c) (((c) >= '0') && ((c) <= '9'))
-#define UPRV_ISALPHANUM(c) (uprv_isASCIILetter(c) || UPRV_ISDIGIT(c) )
-
 static UBool
 isWellFormedLegacyKey(const char* legacyKey)
 {
@@ -2591,11 +2472,10 @@ uloc_toLegacyKey(const char* keyword)
         // Checks if the specified locale key is well-formed with the legacy locale syntax.
         //
         // Note:
-        //  Neither ICU nor LDML/CLDR provides the definition of keyword syntax.
-        //  However, a key should not contain '=' obviously. For now, all existing
-        //  keys are using ASCII alphabetic letters only. We won't add any new key
-        //  that is not compatible with the BCP 47 syntax. Therefore, we assume
-        //  a valid key consist from [0-9a-zA-Z], no symbols.
+        //  LDML/CLDR provides some definition of keyword syntax in
+        //  * http://www.unicode.org/reports/tr35/#Unicode_locale_identifier and
+        //  * http://www.unicode.org/reports/tr35/#Old_Locale_Extension_Syntax
+        //  Keys can only consist of [0-9a-zA-Z].
         if (isWellFormedLegacyKey(keyword)) {
             return keyword;
         }
@@ -2611,12 +2491,11 @@ uloc_toLegacyType(const char* keyword, const char* value)
         // Checks if the specified locale type is well-formed with the legacy locale syntax.
         //
         // Note:
-        //  Neither ICU nor LDML/CLDR provides the definition of keyword syntax.
-        //  However, a type should not contain '=' obviously. For now, all existing
-        //  types are using ASCII alphabetic letters with a few symbol letters. We won't
-        //  add any new type that is not compatible with the BCP 47 syntax except timezone
-        //  IDs. For now, we assume a valid type start with [0-9a-zA-Z], but may contain
-        //  '-' '_' '/' in the middle.
+        //  LDML/CLDR provides some definition of keyword syntax in
+        //  * http://www.unicode.org/reports/tr35/#Unicode_locale_identifier and
+        //  * http://www.unicode.org/reports/tr35/#Old_Locale_Extension_Syntax
+        //  Values (types) can only consist of [0-9a-zA-Z], plus for legacy values
+        //  we allow [/_-+] in the middle (e.g. "Etc/GMT+1", "Asia/Tel_Aviv")
         if (isWellFormedLegacyType(value)) {
             return value;
         }