Search code examples
javascalalocalecase-sensitivescala.js

Scala vs Java toUpperCase/toLowerCase


Scala borrows Java String methods like toUpperCase/toLowerCase.

However, the way it does so is not very consistent:

  • Scala on JVM stick close to Java semantics, thus:
    • toUpperCase() is locale-sensitive and sticks to default locale (giving you infamous i → İ problem in Turkish locale)
    • to avoid that and keep locale-insensitive (en_US / C-like) process, you need to specifically do toUpperCase(Locale.ROOT)
  • Scala.JS does not implement a concept of Locale, thus:
    • toUpperCase() works in locale-insensitive manner
    • toUpperCase(Locale locale) method is effectively not available in ScalaJS

How do I implement locale-insensitive case conversion that will work in Scala on both JVM/JS?

I can think of several ways, all of them as ugly:

Method 1: My own implementation

Implement my own toUpperCase for specifically 26 ASCII characters of English alphabet.

Method 1.1: My own implementation using Scala chars

Basically the same, but at least reuse Scala toUpper to convert individual chars.

Method 2: Interface

Implement something like

trait CaseChangeOps {
  def toUpperCase(s: String): String
}

object Main {
  var caseChanger: CaseChanger
}

// whenever I want to use it, do it like that:
Main.caseChanger.toUpperCase("like this") // => "LIKE THIS"

in shared code, and then in JS have:

object CaseChangerJs {
  def toUpperCase(s: String): String = s.toUpperCase
}

object MainJs {
  Main.caseChanger = CaseChangerJs
}

... and in JVM:

object CaseChangerJvm {
  def toUpperCase(s: String): String = s.toUpperCase(Locale.ROOT)
}

object MainJvm {
  Main.caseChanger = CaseChangerJvm
}

Method 3: bring external scala-java-locales

There is a distinct 3rd party library scala-java-locales, which is listed as ScalaJS-compatible, and can be used to augument ScalaJS.

Looks like a massive overkill, though, as I literally only need locale-insensitive case conversions, not the whole thing for all possible locales.

Any better ideas?


Solution

  • The standard approach is close to your method 2, but much simpler. In shared code you just call

    Platform.toUpperLocaleInsensitive(string)
    

    which has different implementations on JVM and JS:

    // JVM
    object Platform {
      def toUpperLocaleInsensitive(s: String) = s.toUpperCase(Locale.ROOT)
    
      // other methods with different implementations
    }
    
    // JS
    object Platform {
      def toUpperLocaleInsensitive(s: String) = s.toUpperCase()
    
      // other methods with different implementations
    }
    

    See the description of a similar case in Hands-on Scala.js.

    This works because shared code doesn't need to compile by itself, only together with platform-specific code.