R/transliterate.R

Defines functions bt_transliterate bt_remove_accents .bt_translit_apply

# ASCII folding / transliteration without a Unicode dependency.
#
# `removeaccents()` and `transliterate()` used to call
# `stringi::stri_trans_general()`, which pulls in ICU. The maps below are
# extracted verbatim from ICU's "Latin-ASCII" and "Any-Latin; Latin-ASCII"
# transliterators (see bench/make-translit-maps.R) so the common European
# cases match ICU exactly, with no Imports beyond base R.
#
# Coverage: Latin-1 Supplement, Latin Extended-A, Romanian comma-below and
# capital sharp S for removeaccents(); plus the Greek and Cyrillic blocks for
# transliterate(). Scripts ICU would also romanise (Han, Kana, Arabic,
# Hebrew, Devanagari, Thai, ...) are left unchanged rather than guessed at.
# An upper-case letter that expands to a digraph stays upper case (Greek
# Theta -> "TH", so an all-caps expansion); ICU title-cases it in words.

.bt_ascii_map <- c(
  "\u00c0" = "A",
  "\u00c1" = "A",
  "\u00c2" = "A",
  "\u00c3" = "A",
  "\u00c4" = "A",
  "\u00c5" = "A",
  "\u00c6" = "AE",
  "\u00c7" = "C",
  "\u00c8" = "E",
  "\u00c9" = "E",
  "\u00ca" = "E",
  "\u00cb" = "E",
  "\u00cc" = "I",
  "\u00cd" = "I",
  "\u00ce" = "I",
  "\u00cf" = "I",
  "\u00d0" = "D",
  "\u00d1" = "N",
  "\u00d2" = "O",
  "\u00d3" = "O",
  "\u00d4" = "O",
  "\u00d5" = "O",
  "\u00d6" = "O",
  "\u00d8" = "O",
  "\u00d9" = "U",
  "\u00da" = "U",
  "\u00db" = "U",
  "\u00dc" = "U",
  "\u00dd" = "Y",
  "\u00de" = "TH",
  "\u00df" = "ss",
  "\u00e0" = "a",
  "\u00e1" = "a",
  "\u00e2" = "a",
  "\u00e3" = "a",
  "\u00e4" = "a",
  "\u00e5" = "a",
  "\u00e6" = "ae",
  "\u00e7" = "c",
  "\u00e8" = "e",
  "\u00e9" = "e",
  "\u00ea" = "e",
  "\u00eb" = "e",
  "\u00ec" = "i",
  "\u00ed" = "i",
  "\u00ee" = "i",
  "\u00ef" = "i",
  "\u00f0" = "d",
  "\u00f1" = "n",
  "\u00f2" = "o",
  "\u00f3" = "o",
  "\u00f4" = "o",
  "\u00f5" = "o",
  "\u00f6" = "o",
  "\u00f8" = "o",
  "\u00f9" = "u",
  "\u00fa" = "u",
  "\u00fb" = "u",
  "\u00fc" = "u",
  "\u00fd" = "y",
  "\u00fe" = "th",
  "\u00ff" = "y",
  "\u0100" = "A",
  "\u0101" = "a",
  "\u0102" = "A",
  "\u0103" = "a",
  "\u0104" = "A",
  "\u0105" = "a",
  "\u0106" = "C",
  "\u0107" = "c",
  "\u0108" = "C",
  "\u0109" = "c",
  "\u010a" = "C",
  "\u010b" = "c",
  "\u010c" = "C",
  "\u010d" = "c",
  "\u010e" = "D",
  "\u010f" = "d",
  "\u0110" = "D",
  "\u0111" = "d",
  "\u0112" = "E",
  "\u0113" = "e",
  "\u0114" = "E",
  "\u0115" = "e",
  "\u0116" = "E",
  "\u0117" = "e",
  "\u0118" = "E",
  "\u0119" = "e",
  "\u011a" = "E",
  "\u011b" = "e",
  "\u011c" = "G",
  "\u011d" = "g",
  "\u011e" = "G",
  "\u011f" = "g",
  "\u0120" = "G",
  "\u0121" = "g",
  "\u0122" = "G",
  "\u0123" = "g",
  "\u0124" = "H",
  "\u0125" = "h",
  "\u0126" = "H",
  "\u0127" = "h",
  "\u0128" = "I",
  "\u0129" = "i",
  "\u012a" = "I",
  "\u012b" = "i",
  "\u012c" = "I",
  "\u012d" = "i",
  "\u012e" = "I",
  "\u012f" = "i",
  "\u0130" = "I",
  "\u0131" = "i",
  "\u0132" = "IJ",
  "\u0133" = "ij",
  "\u0134" = "J",
  "\u0135" = "j",
  "\u0136" = "K",
  "\u0137" = "k",
  "\u0138" = "q",
  "\u0139" = "L",
  "\u013a" = "l",
  "\u013b" = "L",
  "\u013c" = "l",
  "\u013d" = "L",
  "\u013e" = "l",
  "\u013f" = "L",
  "\u0140" = "l",
  "\u0141" = "L",
  "\u0142" = "l",
  "\u0143" = "N",
  "\u0144" = "n",
  "\u0145" = "N",
  "\u0146" = "n",
  "\u0147" = "N",
  "\u0148" = "n",
  "\u0149" = "'n",
  "\u014a" = "N",
  "\u014b" = "n",
  "\u014c" = "O",
  "\u014d" = "o",
  "\u014e" = "O",
  "\u014f" = "o",
  "\u0150" = "O",
  "\u0151" = "o",
  "\u0152" = "OE",
  "\u0153" = "oe",
  "\u0154" = "R",
  "\u0155" = "r",
  "\u0156" = "R",
  "\u0157" = "r",
  "\u0158" = "R",
  "\u0159" = "r",
  "\u015a" = "S",
  "\u015b" = "s",
  "\u015c" = "S",
  "\u015d" = "s",
  "\u015e" = "S",
  "\u015f" = "s",
  "\u0160" = "S",
  "\u0161" = "s",
  "\u0162" = "T",
  "\u0163" = "t",
  "\u0164" = "T",
  "\u0165" = "t",
  "\u0166" = "T",
  "\u0167" = "t",
  "\u0168" = "U",
  "\u0169" = "u",
  "\u016a" = "U",
  "\u016b" = "u",
  "\u016c" = "U",
  "\u016d" = "u",
  "\u016e" = "U",
  "\u016f" = "u",
  "\u0170" = "U",
  "\u0171" = "u",
  "\u0172" = "U",
  "\u0173" = "u",
  "\u0174" = "W",
  "\u0175" = "w",
  "\u0176" = "Y",
  "\u0177" = "y",
  "\u0178" = "Y",
  "\u0179" = "Z",
  "\u017a" = "z",
  "\u017b" = "Z",
  "\u017c" = "z",
  "\u017d" = "Z",
  "\u017e" = "z",
  "\u017f" = "s",
  "\u0218" = "S",
  "\u0219" = "s",
  "\u021a" = "T",
  "\u021b" = "t",
  "\u1e9e" = "SS"
)

.bt_greek_map <- c(
  "\u0374" = "'",
  "\u037a" = "i",
  "\u037e" = ";",
  "\u0386" = "A",
  "\u0388" = "E",
  "\u0389" = "E",
  "\u038a" = "I",
  "\u038c" = "O",
  "\u038e" = "Y",
  "\u038f" = "O",
  "\u0390" = "i",
  "\u0391" = "A",
  "\u0392" = "B",
  "\u0393" = "G",
  "\u0394" = "D",
  "\u0395" = "E",
  "\u0396" = "Z",
  "\u0397" = "E",
  "\u0398" = "TH",
  "\u0399" = "I",
  "\u039a" = "K",
  "\u039b" = "L",
  "\u039c" = "M",
  "\u039d" = "N",
  "\u039e" = "X",
  "\u039f" = "O",
  "\u03a0" = "P",
  "\u03a1" = "R",
  "\u03a3" = "S",
  "\u03a4" = "T",
  "\u03a5" = "Y",
  "\u03a6" = "PH",
  "\u03a7" = "CH",
  "\u03a8" = "PS",
  "\u03a9" = "O",
  "\u03aa" = "I",
  "\u03ab" = "Y",
  "\u03ac" = "a",
  "\u03ad" = "e",
  "\u03ae" = "e",
  "\u03af" = "i",
  "\u03b0" = "y",
  "\u03b1" = "a",
  "\u03b2" = "b",
  "\u03b3" = "g",
  "\u03b4" = "d",
  "\u03b5" = "e",
  "\u03b6" = "z",
  "\u03b7" = "e",
  "\u03b8" = "th",
  "\u03b9" = "i",
  "\u03ba" = "k",
  "\u03bb" = "l",
  "\u03bc" = "m",
  "\u03bd" = "n",
  "\u03be" = "x",
  "\u03bf" = "o",
  "\u03c0" = "p",
  "\u03c1" = "r",
  "\u03c2" = "s",
  "\u03c3" = "s",
  "\u03c4" = "t",
  "\u03c5" = "y",
  "\u03c6" = "ph",
  "\u03c7" = "ch",
  "\u03c8" = "ps",
  "\u03c9" = "o",
  "\u03ca" = "i",
  "\u03cb" = "y",
  "\u03cc" = "o",
  "\u03cd" = "y",
  "\u03ce" = "o",
  "\u03d0" = "b",
  "\u03d1" = "th",
  "\u03d2" = "Y",
  "\u03d3" = "Y",
  "\u03d4" = "Y",
  "\u03d5" = "ph",
  "\u03d6" = "p",
  "\u03f0" = "k",
  "\u03f1" = "r",
  "\u03f2" = "s",
  "\u03f3" = "j",
  "\u03f4" = "TH",
  "\u03f5" = "e",
  "\u03f7" = "S",
  "\u03f8" = "s",
  "\u03f9" = "S",
  "\u03fa" = "S",
  "\u03fb" = "s"
)

.bt_cyrillic_map <- c(
  "\u0400" = "E",
  "\u0401" = "E",
  "\u0402" = "D",
  "\u0403" = "G",
  "\u0404" = "E",
  "\u0405" = "Z",
  "\u0406" = "I",
  "\u0407" = "I",
  "\u0408" = "J",
  "\u0409" = "L",
  "\u040a" = "N",
  "\u040b" = "C",
  "\u040c" = "K",
  "\u040d" = "I",
  "\u040e" = "U",
  "\u040f" = "D",
  "\u0410" = "A",
  "\u0411" = "B",
  "\u0412" = "V",
  "\u0413" = "G",
  "\u0414" = "D",
  "\u0415" = "E",
  "\u0416" = "Z",
  "\u0417" = "Z",
  "\u0418" = "I",
  "\u0419" = "J",
  "\u041a" = "K",
  "\u041b" = "L",
  "\u041c" = "M",
  "\u041d" = "N",
  "\u041e" = "O",
  "\u041f" = "P",
  "\u0420" = "R",
  "\u0421" = "S",
  "\u0422" = "T",
  "\u0423" = "U",
  "\u0424" = "F",
  "\u0425" = "H",
  "\u0426" = "C",
  "\u0427" = "C",
  "\u0428" = "S",
  "\u0429" = "S",
  "\u042b" = "Y",
  "\u042d" = "E",
  "\u042e" = "U",
  "\u042f" = "A",
  "\u0430" = "a",
  "\u0431" = "b",
  "\u0432" = "v",
  "\u0433" = "g",
  "\u0434" = "d",
  "\u0435" = "e",
  "\u0436" = "z",
  "\u0437" = "z",
  "\u0438" = "i",
  "\u0439" = "j",
  "\u043a" = "k",
  "\u043b" = "l",
  "\u043c" = "m",
  "\u043d" = "n",
  "\u043e" = "o",
  "\u043f" = "p",
  "\u0440" = "r",
  "\u0441" = "s",
  "\u0442" = "t",
  "\u0443" = "u",
  "\u0444" = "f",
  "\u0445" = "h",
  "\u0446" = "c",
  "\u0447" = "c",
  "\u0448" = "s",
  "\u0449" = "s",
  "\u044a" = "\"",
  "\u044b" = "y",
  "\u044c" = "'",
  "\u044d" = "e",
  "\u044e" = "u",
  "\u044f" = "a",
  "\u0450" = "e",
  "\u0451" = "e",
  "\u0452" = "d",
  "\u0453" = "g",
  "\u0454" = "e",
  "\u0455" = "z",
  "\u0456" = "i",
  "\u0457" = "i",
  "\u0458" = "j",
  "\u0459" = "l",
  "\u045a" = "n",
  "\u045b" = "c",
  "\u045c" = "k",
  "\u045d" = "i",
  "\u045e" = "u",
  "\u045f" = "d"
)

.bt_translit_apply <- function(x, map) {
  x <- enc2utf8(as.character(x))
  todo <- !is.na(x) & grepl("[^\001-\177]", x, perl = TRUE)
  if (!any(todo)) {
    return(x)
  }
  keys <- names(map)
  x[todo] <- vapply(strsplit(x[todo], "", fixed = TRUE), function(cs) {
    idx <- match(cs, keys)
    hit <- !is.na(idx)
    cs[hit] <- map[idx[hit]]
    paste0(cs, collapse = "")
  }, character(1), USE.NAMES = FALSE)
  x
}

bt_remove_accents <- function(x) {
  .bt_translit_apply(x, .bt_ascii_map)
}

bt_transliterate <- function(x) {
  .bt_translit_apply(x, c(.bt_greek_map, .bt_cyrillic_map, .bt_ascii_map))
}

Try the basetable package in your browser

Any scripts or data that you put into this service are public.

basetable documentation built on Sept. 18, 2026, 9:13 a.m.