react-intlでReact Nativeアプリの国際化対応をする

はじめに

だいぶ前にReact Nativeで開発中のアプリを国際化するにあたってreact-intlを使ったのでそのあたりのメモをブログに書いていたが、下書きのまま公開していなかった。今回、改めて別のアプリで同様のことをする必要があって過去の自分の下書きを見つつ作業をしたので、改めて整理したりして書き直して世に出すことにした。

国際化対応のライブラリ

簡単に調査したところReactNativeでのi18n対応を行うためのライブラリとしてはよく見かけたのは以下。

  • react-intl (format.js)
  • react-i18next
  • react-native-localize

ract-native-localize以外はReact Native用というわけではない。Github上のスター数は2022年5月時点で上から順に13.1K、7.3K、1.8Kとreact-intlが圧倒的ではあるが、NPMの週間ダウンロード数ではreact-i18nextが上回っている状況だった。

参考までにNext.jsの公式が紹介していた比較的新しめのライブラリとして以下のようなものもあるがこれらはそもそもReact Nativeで使えるかもわからない。

ちなみにnext-intlはreact-intlが使ってるFormat.jsをベースにしてるってことなので似た感じなのかも。

react-intlはメッセージファイルを用意して、FormattedMessageというコンポーネントで記述する。react-i18nextはファイルを用意するのは同じだがt()という関数でメッセージを引っ張ってくるだけとシンプルな感じ。全般的にreact-intlのほうが記述は冗長な感じなのでラッパー関数なりユーティリティなりを作ったほうが良さそう。特にJSX内の対象文字列の箇所でFormattedMessageという長めの名前のコンポーネントが出現するのは個人的にはちょっと嫌だなと思った。

一方で、i18nextは使ったことがある開発メンバーからは不評だった。

結論としては冒険するところでもないので一番ユーザベースの多いreact-intlを導入することにした。メンバーの利用経験もあるし、フォーマットも国際化対応してくれるみたいだし。

導入

まずはインストール。

yarn add react-intl

とりあえず英語と日本語のロケールのファイルを作成する。フォルダ構成は推奨?の構成にならってひとまずプロジェクトルート直下にlangディレクトリを作ってen-US.jsonja.jsonというファイルを作成する。

{
  "title": "ホーム",
  "name": "{name} 様",
  "message": "こんにちは"
}
{
  "title": "HOME",
  "name": "Mr/Ms {name}"
}

内容的には普通のjsonで、jsonのキーがそのままメッセージのキーになっている。 このキーをどうするかは悩ましい。昔国際化対応したときもここで悩んだ思い出。実はこのあたりを自動化する仕組みがreact-intlというかformat.jsには存在する。が、その辺は後述。

さて、ファイルを作ったらApp.tsxを以下のように。<IntlProvider />でまるっと囲む。

import { IntlProvider, FormattedMessage, FormattedNumber } from 'react-intl'
import ja from './lang/ja.json'
import en from './lang/en-US'

  return (
    <IntlProvider messages={ja} locale="ja" defaultLocale="en">
      <Root>
        <Provider store={store}>
          <View style={{ flex: 1 }}>
            <Routes />
          </View>
        </Provider>
      </Root>
    </IntlProvider>
  )

そして文字列を表示している箇所をこんな感じで指定するだけだ。

<FormattedMessage id="title" />

ただし、これだとInput要素などのプレースホルダの値に設定したくてもいれられない。というわけでローレベルAPIも用意されている。

export const InputSomething = (): React.ReactElement => {
  const intl = useIntl()
  const placeholder = intl.formatMessage({ id: 'title' })
  return (
    <View>
      <TextInput
        placeholder={placeholder}
        value=""
      />
    </View>
  )

基本的にはこんなところだ。あとは各言語ごとにすべてのメッセージを用意して、置き換えることになる。

もう一歩すすめる

さて、ここまでのやり方はどちらかというと古き良き国際化対応って感じだ。ここで気になるのが前述のとおりキーをどうやって作成管理していくかが課題になりがちではある。メッセージが少ないうちはいいが画面数が増えたりメッセージ数が増えてくると結構大変。また、ソースコード上にはそのキーが表示されているので実際のメッセージがわかりにくいという問題もある。

そんな問題を少しばかり解決してくれるのがMessage ExtractionとMessage Distributionの仕組み。これはソースコード中でid (キー) を指定するのではなく、descriptionとデフォルトのメッセージを指定した上でextractを実行すると指定した言語のファイルが生成されるというもの。その後、そのファイルをcompileすることでキーが自動で生成されてくるのだ。

というわけでやっていく。

まず、cliをインストールする。これでextractが使えるようになる。

yarn add -D @formatjs/cli

そうしたら package.jsonに設定を入れる。scriptsにとりあえずは以下のように追記すればいい。

  "scripts": {
    "extract": "formatjs extract",
    "compile": "formatjs compile"
  },

なお、これでももちろん動くけどコマンドの引数とかが面倒なので自分の場合は実際にはこんな感じにしている。

  "scripts": {
    "extract": "formatjs extract 'src/**/*.ts*' --out-file lang/en.json",
    "compile": "formatjs compile lang/en.json --ast --out-file src/compiled-lang/en.json",
  },

ここでは英語のメッセージファイルだけを対象にしているがこのあたりの話は後述する。

あとはeslintとbabelのプラグインなども入れておく

yarn add -D eslint-plugin-formatjs
yarn add -D babel-plugin-formatjs
yarn add -D @formatjs/ts-transformer

eslintの設定に以下の内容を追加する。自分の場合は.eslintrc.jsを編集。

{
  "plugins": ["formatjs"],
  "rules": {
    "formatjs/no-offset": "error"
  }
}

といっても実際には別のプラグインがすでに導入されている等のケースも多いと思うので、その場合は既存の部分に追加しておく。

babelのほうはbabel.config.jsを編集した。こちらにも以下の内容を追加する。

{
  "plugins": [
    [
      "formatjs",
      {
        "idInterpolationPattern": "[sha512:contenthash:base64:6]",
        "ast": true
      }
    ]
  ]
}

最後にjest.config.jsonに以下を追加。

module.exports = {
  globals: {
    'ts-jest': {
      astTransformers: {
        before: [
          {
            path: '@formatjs/ts-transformer/ts-jest-integration',
            options: {
              // options
              overrideIdFn: '[sha512:contenthash:base64:6]',
              ast: true,
            },
          },
        ],
      },
    },
  },
};

ここまでしたら後は書くだけ。まずはメッセージは以下のように指定する形に変更する。

<FormattedMessage defaultMessage="HOME" description="the title for something" />

description、つまりこのメッセージの説明とデフォルトで表示するメッセージをdefaultMessageで指定する。ポイントは先のやり方とは違ってキー(id)を指定していないことだ。ソースコード中にキーしか書かれていないときと比べて可読性があがると言えるのではないか。

次にextractを実行していく。ソースコード中に記されたメッセージを文字通り『抽出』してキー(id)を自動生成したメッセージファイルをlang以下にjson形式で作成してくれる。ファイル名はpackage.jsonに指定したファイル名になる。

ファイルの中身はこんな感じになっている。ソースコード中で指定したものがすべて抽出されてキーが設定されて羅列されているはずだ。以下の例ではFoqPXLというのが自動で作成されたキーだ。

{
  "FoqPXL": {
    "defaultMessage": "HOME",
    "description": "the title for something"
  },

なお、この自動生成されたキーはdefaultMessagedescriptionをもとにしたハッシュのようでこれらを変更すると新たに生成されて上書きされるので要注意。

そして、生成されたファイルを編集する。つまり翻訳作業だ。翻訳作業に関してはyarn extractで生成されたファイルであるlang/en.jsonをコピーするなりして翻訳後の内容に書き換えていけばいい。このときキーについては変更してはいけない。先の内容だとこんな風に翻訳する。descriptionはどちらでも構わない。

  "FoqPXL": {
    "defaultMessage": "ホーム",
    "description": "the title for something"
  },

翻訳作業が終わったらこのファイルをlang/ja.jsonとでもして保存する。日本語以外にも翻訳対象があるなら同様の手順を踏む。なお、このextractコマンドはdescriptiondefaultMessageに変更がなければキーはそのまま前回以前のものが維持される。

最後、編集が終わったらcompileする。compile結果のファイルはアプリの中から参照できる場所に保存しておく必要がある。compile自体はformatjs compileを実行するだけなんだけど翻訳対象の言語が複数ある場合、つまりメッセージファイルが複数ある場合にはcompileも言語ごとに実行する必要がある。yarn compileで設定しておく場合はこのあたりも考慮しておく必要がある。自分の場合は以下のようになっている。

  "scripts": {
    "extract": "formatjs extract 'src/**/*.ts*' --out-file lang/en.json",
    "compile": "yarn intl:compile:en && yarn intl:compile:ja",
    "compile:en": "formatjs compile lang/en.json --ast --out-file src/compiled-lang/en.json",
    "compile:ja": "formatjs compile lang/ja.json --ast --out-file src/compiled-lang/ja.json",
  },

さて、実行するとこういうファイルが生成される。

{
  "FoqPXL": [
    {
      "type": 0,
      "value": "HOME"
    }
  ]
}

ここまでで言語ごとのメッセージ切り替えはひとまず完了だが実際にアプリケーションに組み込むとなるとこれだけでは終わらない。例えば言語設定の変更をどうするか。ユーザが設定可能にするならばそのUIを用意することに加えてユーザ設定に応じてApp.tsxに設定したロケールを切り替える処理を実装することが必要。なお、自分の場合はデバイスロケール設定をもとに設定する方法をとっている。

バイスからロケールを取得する

さて、ついでと言ってはなんだけどどうせならデバイスロケール情報をもとに表示する言語を自動的に切り替えたい。これは以下のような感じでデバイスから言語設定を取得して条件分岐して IntlProvider に渡してあげればいい。React Nativeの場合、iOSAndroidで取得方法が微妙に違うので注意。

こんな感じ。

import { Platform, NativeModules } from 'react-native';

export const getLocale = (): string => {
  const deviceLanguage =
    Platform.OS === 'ios'
      ? NativeModules.SettingsManager.settings.AppleLocale ||
        NativeModules.SettingsManager.settings.AppleLanguages[0] // for iOS 13+
      : NativeModules.I18nManager.localeIdentifier;

  let locale: 'ja' | 'en' | 'pt' | 'es';
  if (/^ja/.test(deviceLanguage)) {
    locale = 'ja';
  } else if (/^en/.test(deviceLanguage)) {
    locale = 'en';
  } else if (/^pt/.test(deviceLanguage)) {
    locale = 'pt';
  } else if (/^es/.test(deviceLanguage)) {
    locale = 'es';
  } else {
    locale = 'en';
  }

  return locale;
};

前半の処理がiOSとAndroidそれぞれでデバイスのロケール設定を取得する処理。後半は実際に取得するロケール情報は`言語_地域`みたいな感じだったりもする。`en_US`のように。なので地域は考慮せずにロケール設定するようにしているだけ。ここはお好みで構わないかと。

## ロケールに応じたメッセージファイルを読み込む
ここは正直やり方はなんでもいいかと思う。普通にロケールに応じて`if`で条件分岐したり`switch`で処理したりとお好みで。

## 実際の翻訳フローについて
さて、ここまでreact-intlおよびformatjsを道入してきたわけだけど実際の翻訳フローについても考える必要がある。ここはいろんな状況によるので一概には言えないがFormat.jsとしては以下のような流れを想定しているようだ。

1. メッセージをextractしたらその結果をリポジトリにコミット
2. CIでTMS(Trascription Management System)が取り出し(pull)
3. TMSで翻訳管理をしつつ翻訳担当が翻訳
4. 翻訳結果のファイルをリポジトリにコミット
5. コミットされたらcompileを実行

TMSはいろんなサービスやプロダクトがあって、Foramt.jsの形式に対応しているサービスもいくつかある。TMSを使うと翻訳済、未翻訳の管理や機械翻訳の実施などができたりする。

自分の場合はいろいろTransifexを試したりWeblateというOSSプロダクトを試したりしたものの、最終的にはextractで生成したファイルをもとにコピーして各言語のファイルを作成し直接翻訳する形を取ることにした。これは現状では翻訳担当が別にいるわけではなく開発担当=翻訳担当でもあるのでTMSを入れると逆に手間がかかると感じたため。

extractで生成したファイルをコピーすると言ったが、実際にはまるっと単純にコピーしてしまうと毎回翻訳内容が消えてしまうのでそれぞれのファイルの内容をキーをもとにチェックして、すでにキーが存在している場合はコピーしないというスクリプトを実装して実行している。これをデフォルト言語(英語)以外で実行している感じだ。

2022年4月に買った書籍/マンガ

先月に引き続きAudibleを活用しています。問題はやはり聞くのと読むのでは速度に差があって聞くほうが遅いということ。というわけでAudibleは2.5倍速で聞いてます。これが限界。

今月は仕事で認証認可について考える機会が多かったのでそれ関連の本が多め。

書籍

Web APIの設計

  • 最近Web APIの設計をする機会があり参考がてら購入
  • といっても基本的には認証周りだけを参照

自然な英語で話せる・書ける動詞120

プロを目指す人のためのTypeScript入門

  • プロになりたいので買った
  • 周りでも購入してる人多し
  • でも自分はまだ未読…

マンガでわかるJavaScriptのPromise

  • 謎のネコが出てきてPromiseについて説明してくれるという謎の本
  • 一見ふざけた感じだが、その実、内容としてはいたってまとも
  • 実際にPromiseの解説もわかりやすくてよい復習になった

はじめての設計をやり抜くための本

  • こういう本を読んだことがなかったのだけど、評判のよかった本の第2版ということで購入してみた
  • 未読

まつもとゆきひろ 言語のしくみ

哲学人生問答

  • 評判が良かったので購入
  • パラパラと読んでみたがやはりこういう内容は自分には向いていないようだ

雰囲気で使わずきちんと理解する!整理してOAuth2.0を使うためのチュートリアルガイド

  • 認証認可について考える必要があって購入
  • この本はわかりやすい
  • ただし、どちらかというと利用者側の視点での説明

OAuth、OAuth認証OpenID Connectの違いを整理して理解できる本

booth.pm

  • 前述の本が基本的にOAuthについてしか書かれていないのに対して同じ著者によるOIDCに関するもの
  • こちらもわかりやすかった。

Web API: The Good Parts

  • 認証をどうすべきか的な参考になるかと思って購入したもののその観点では参考にはならなかった
  • よいAPI設計とはみたいな話が中心でそれはそれで面白い

FACTFULNESS(ファクトフルネス)10の思い込みを乗り越え、データを基に世界を正しく見る習慣

  • この世は誤解と先入観に満ちているという話がいろんな例をもとに一冊を通じて終始述べられてる
  • タイトルそのままで、だからこそちゃんと事実に目を向けるべきって話
  • ちなみに日本語訳者の上杉氏はNext.jsで有名なVercelのエンジニア

コミック

るろうに剣心明治剣客浪漫譚・北海道編― 7

  • 主人公が一度も出てこなかった

MFゴースト(14)

君が獣になる前に(2)

金色のガッシュ!! 2 Page 2

  • 先月に引き続き無料で
  • といってもガッシュをまだ読み終わっていないのでこちらも手を付けていない

東京卍リベンジャーズ(27

弱虫ペダル 77

  • この作者は魅力的なキャラクターを作るのが本当にうまい

orange : 7 ―大切なあなたへ―

  • いまさら出た7巻
  • 今度こそ本当の完結
  • でも少し蛇足感があるのが正直なところ

タコピーの原罪 下

  • ミーハー気分で買ったが上巻以上に何も思うところがなかった。僕とはあわないようだ

小説

同志少女よ、敵を撃て

  • キノベス! 2022 第1位、2022年本屋大賞ノミネート、第166回直木賞候補作、第9回高校生直木賞候補作、第11回アガサ・クリスティー賞大賞受賞作ってことなので読んでみた
  • 時期が時期だからかなんかリアルさを感じなかった。フィクションなんだからと言われればそれまでだけど
  • それにしても作者はなぜソ連を部隊にしたのかな

ペッパーズ・ゴースト

  • ようやく読んだ
  • いつもの伊坂幸太郎ワールド
  • 賛否はあるものの、自分としては面白かった

DAHON K3 のタイヤをビッグアップルに交換した

先月買ったDAHON K3ネタです。

今回はDAHON K3としてはド定番のカスタムです。標準タイヤからシュワルベのビッグアップル14インチに変更するというものですね。

やるかやらないか悩んでいたものの標準タイヤで1ヶ月ほど過ごしてみて10kmぐらい移動するとやっぱり辛いなってことで交換することにしました。

いわく、乗り心地は大幅に改善し、標準でも思ったより良く走るK3がさらによく走るようになるという噂。交換しない理由はないって話なのでこの話に乗っかることにしました。

タイヤ交換なんてやったことない上にネットで情報収集したところ、どうやら個体差でいろんなものに干渉する可能性、特にリアディレイラーに干渉すると調整が結構大変みたいな話なので購入した店舗にお願いしようかと思ってたんですが、せっかくなので一度自分でやってみようかなと。

で、ダメだったら店に泣きつこうということでやっていきます。

準備

というわけで自分でやるにあたって色々と購入する必要があるわけですが、このタイミングでタイヤ自体の入手が結構大変ということを知りました。どうやら人気で品薄だそうで。

困ったもののいろんなショップを探しまくってなんとかゲット。

売っているところでも定価以上になっていることが多い中、定価で購入できました。これ、手に入る時に予備を買っておいたほうがいいですよ。

続いてチューブです。タイヤに合わせてチューブ買う必要があるんですが、最初何も考えずにシュワルベのチューブを買ったんですね。そうしたらバルブ(空気入れるところ)が英式で、仏式のK3のホイールにつけようとするならバルブ穴の加工が必要ということでキャンセル。たまにセットで売ってるものあるのですが要注意です。自分が無知だっただけですが。

調べたところ標準チューブのままでも一応いけるみたいですがあまり良くないってことらしく、こちらのパナレーサーのチューブが鉄板てことで購入。

あとは作業用にタイヤレバーてのが必要ってことだったのでこちらも購入。初めて使ったので他と比べられないんですけど硬くて良かった気がします。

というわけで届いたものたち。

f:id:Keisuke69:20220430213102j:image
f:id:Keisuke69:20220430213059j:image
f:id:Keisuke69:20220430213105j:image

 

あとは普通にタイヤ外したりするために六角レンチが必要になりますが今回はもともと持ってたやつでOKでした。ちなみにもともと持ってるのはこちら。大きいものから小さいものまで揃ってるので便利。

交換作業

まずタイヤを外していくのですが作業は自転車をひっくり返して行います。

f:id:Keisuke69:20220430205058j:image

まずは前輪から外します。

f:id:Keisuke69:20220430205245j:image

外すのは簡単で六角レンチでシャフトを外すだけです。

f:id:Keisuke69:20220430205242j:image

こんなやつ。

f:id:Keisuke69:20220430205638j:image

というわけで外せました。外すにあたってブレーキが干渉するので先にタイヤの空気を抜いてしまったほうがいいです。

f:id:Keisuke69:20220430205907j:image

隣にビッグアップルを並べて比較してみます。この時点でビッグアップルの太さがわかります。
f:id:Keisuke69:20220430205912j:image

大きさも同じ14インチでも一回り違います。てことはですね、スピードが増すことが期待できます。
f:id:Keisuke69:20220430205910j:image

続いて後輪です。後輪はチェーンやらがあるので前輪よりは苦労しました。

まずは後輪は前輪と違ってブレーキを緩めました。

この左側のところを緩めるとブレーキのワイヤーが緩みます。

f:id:Keisuke69:20220430213309j:image

そして外す。
f:id:Keisuke69:20220430213312j:image

あとは前輪と同じように六角レンチでタイヤを止めているものを外します。

後輪は前輪と違って両サイドからそれぞれ締められていますのでパーツが2個になります。

f:id:Keisuke69:20220430213435j:image

で、チェーンを外すのに四苦八苦したのですが、ここの部分を手でグイッと前に引っ張るとチェーンが緩むので比較的楽に外せました。

f:id:Keisuke69:20220430213609j:image

外したチェーンは適当にリアディレイラーに引っ掛けておいたのですが正しい方法は知りません。

というわけで外れたのですが外した後輪の写真を撮るの忘れてました。

さて、ここからはメインのタイヤ交換です。まずはホイールからタイヤを外します。

こんな感じでタイヤレバーを引っ掛けます。

f:id:Keisuke69:20220430213835j:image

スポーク1、2本をあけてもう一個タイヤレバーを差し込みます。そうしたら下の写真のようにテコの原理で手前にグイッと倒します。結構力がいるかもです。

f:id:Keisuke69:20220430213912j:image

あとは残りの一本を差し込んでつつーっと一周回せば外れます。

f:id:Keisuke69:20220430214022j:image

タイヤの片側がホイールから外れたら中にあるチューブを引っ張り出します。

f:id:Keisuke69:20220430214129j:image

バルブに止められている固定リング?を外してホイールから外します。タイヤも外します。

こんな感じで外せました。

f:id:Keisuke69:20220430214226j:image

f:id:Keisuke69:20220430214326j:image

次はビッグアップルをホイールにはめていきます。

まずは片側だけはめた状態にします。

f:id:Keisuke69:20220430214425j:image

そして交換するチューブを用意します。固定リングとキャップは外します。

f:id:Keisuke69:20220430214445j:image

f:id:Keisuke69:20220430214519j:image

ホイールのバルブ穴にバルブを通します。この時点で固定リングを止めてしまってもいいかも。

f:id:Keisuke69:20220430214604j:image

あとはグイグイ入れていくだけです。

チューブを入れ終わったらタイヤの残りの側をはめるのですが、これが一番苦労しました。

かなり硬くてはめるのが一筋縄ではいかず、写真を撮るのも忘れてしばらく格闘してました。

最終的にはタイヤレバーを使って外す時の逆ではめたのが自分的にはベストの方法でした。

が、残念ながら写真撮り忘れ…。

もしここの部分の詳細知りたいって人がいたら気が向いたらもう一度バラして写真撮ってお見せします。

あと、実際にはトラブルが発生しまして。四苦八苦しているときにグイグイやりすぎたりしたからか交換したばかりのチューブに穴が空いてしまうという。

2本しか買ってなくて予備がなかったのでひとまずこの日は標準チューブでしのぐことに。

で、その場でAmazonで注文して翌日届いて無事にチューブ交換も完了。ありがとうAmazon

完成

というわけで無事に交換完了。問題なく折り畳めます。

f:id:Keisuke69:20220430215834j:image
f:id:Keisuke69:20220430215831j:image

問題の干渉ですがとても幸運なことにブレーキはもちろんリアディレイラーの干渉もなし。これは有り難い。

インプレッション

といっても交換後にまだほとんど乗っておらず少し試した程度なので正直よくわかりません。

でも確かに地面の起伏が腕にビリビリ来てた感じはだいぶ軽減された気がします。これは期待大。

もう少し乗ってみての感想

続報です。

1時間ほど乗ってみました。結論からすると最高オブ最高です。

激しく地面の振動が伝わってた場所を走ってみたりしたのですがかなり緩和されてます。

タイヤが太くなったことで漕ぎ出しが重くなるって話も見かけてたのですがそれもあまり気にならず。

これは確かに変えない理由はないかも。ただし重量も少し増えるので輪行メインの人だとトレードオフかな。

Amplify ConsoleでCORSの設定を行う

AWSのAmpify ConsoleでCORSの設定が必要になったんだけど、やり方についてググっても意外とドンピシャな情報がなかったのでメモ。

結論から言うと特段それようの設定があるわけではなくベタにヘッダを指定するだけだった。これはAmplify ConsoleのカスタムヘッダでCORSで必要となる一連の設定をするだけでいい。

この設定はマネージメントコンソールからもできるし、プロジェクトのトップディレクトリ直下にcustomHeaders.ymlというファイルに記述しておくことも可能。

マネージメントコンソールからやる場合はアプリを選択してカスタムヘッダの設定画面を開けばエディタがあるのでそこに直接記述する。記述したものを後からダウンロードすることも可能。

こんな感じの内容をYAML形式で記述する。

customHeaders:
  - pattern: '*.json'
    headers: 
    - key: 'Access-Control-Allow-Headers'
      value: '*'
    - key: 'Access-Control-Allow-Methods'
      value: 'GET'
    - key: 'Access-Control-Allow-Origin'
      value: '*'

patternでは対象となるリソースを指定する。ここではルートディレクトリにあるjsonファイルすべてを対象にしているが、特定のパス配下のjsonファイルだけを指定したい場合はパスも含めて記述すればいい。当然jsonではなくJSのファイルも指定可能だ。

また、今回はサンプルなのでOriginはワイルドカードで指定している。特定のオリジンのみ許可したい場合はここをhttps://www.example.com/みたいな感じで指定すればいい。

メソッドも同様だ。サンプルではGETのみ許可しているが他のメソッドも許可する場合はカンマ区切りで指定する。GET, POST, PUT, OPTIONS, DELETEみたいな感じで。

今回は省略しているが、もちろんAlllow-Control-Expose-Headersなんかも指定可能。

マネージメントコンソールの場合は記述して、保存したらその時点で有効になる。

©Keisuke Nishitani, 2020   プライバシーポリシー