RubyでISO国名コード2文字を絵文字の国旗に変換する(翻訳)
アプリケーションで、国名の参照をISO 3166-1 alpha-2標準の2文字のコードとしてインラインで保存することがよくあります。たとえば「GB」は英国、「US」は米国を表すという具合です。
しかし絵文字でやりたい人たちがいるならば受けて立ちましょう。
def emoji_flag(country_code)
cc = country_code.to_s.upcase
return unless cc =~ /\A[A-Z]{2}\z/
cc.codepoints.map { |c| (c + 127397).chr(Encoding::UTF_8) }.join
end
しくみ
最初の2行は「防御的コード」と呼ばれるもので、無関係な文字が入力されてもメソッドがおかしくならないようにします。
cc = country_code.to_s.upcase
上のコードは、入力文字を英大文字に揃えます。
return unless cc =~ /\A[A-Z]{2}\z/
上のコードは、英大文字2文字でない文字列が渡された場合にnil
を返します(早期脱出)。
「メソッドが常に文字列を返すよう、nil
ではなく""
を返すべき」という意見もおありかと思いますが、面倒でもnil
を返す方がRubyらしいと言えます。
少々うまくやれた点を解説
すべての国にはアルファベット2文字の一意のコードが割り当てられています(一部の国には以前から割り当てられていますが)。
UNICODEには英大文字に対応する(Enclosed Alphanumeric Supplement)と呼ばれるブロックがあり、その中にRegional indicator symbol(地域指示記号)と呼ばれる特殊な大文字が26個連続で存在します。たとえば大文字のA
には🇦
という地域指示記号が対応しています。
UNICODEにあるこの地域指示記号を2つ並べると、OS上で国旗の絵文字がレンダリングされます。たとえば、画面に表示される🇪🇺
という絵文字は、🇪
と🇺
が連続したUTF-8文字列で構成されています。
Unicodeの文字テーブルには、通常の英大文字の他にこうした地域指示記号も含まれています。
UNICODE英大文字A
のコードポイントは65
、それに対応する🇦
のコードポイントは127,462
です。そして、大文字(A
〜Z
)と、それに対応する地域指示記号(🇦
〜🇿
)のコードポイントの差は、どの大文字でも常に127,397
です。この「マジックナンバー」こそが変換方法の鍵となります。
冒頭のコードの主な機能は、2文字の文字列を分割し、それぞれの文字をRubyの#codepoints
メソッドでASCII(UTF-8)文字テーブルの数値表現に変換することです。そして各コードポイントに127,397
を足し、この新しい参照をUTF-8エンコード文字に戻します。最後に2つの地域指示記号を#join
してString
に戻します。
フランスを例に、これまでの手順をおさらいすると以下のようになるでしょう。
このコードは、RubyのUTF-8テキストレンダリングとモダンなOSを組み合わせると「2個の地域指示記号」ではなく国旗が表示されるという動作に依存しています。
UNICODE文字テーブルの構造がうまく考えられているおかげで、このメソッドはある程度寛容に動作します。以下のように未割り当ての国コードを渡すと、2つの地域指示記号をフォールバック表示します。
概要
原著者の許諾を得て翻訳・公開いたします。
regional indicator symbolやregional indicator characterは、仮訳の「地域指示記号」で統一しました。
また、一部のサンプルコードについては見やすさのためGistを使っています。