プログラミング

エンジョイSwiftUIプログラミングその21(多言語化その8:多言語JSONデータ作成)

多言語化のシリーズの一応の最終回にしようかなと思います。今回は、多言語のJSONデータをどのように作成し、処理するかをご紹介します。今回もまた簡単なアプリを作成しました。こんな感じです。

手順としては、

  1. 新しいProjectを作成する。
  2. メニューデータのstructを宣言して、中のpropertyを決定する。
  3. 対応するJSON(JavaScript Object Notationの略)データを準備する。
  4. Jsondecorderを使って、JSONデータをメニューデータ([MenuItem])に読み込む。
  5. info.plistの「App Transport Security Settings」を設定した後、WebViewを表示する関数(UIRepresentableView)を作成する。
  6. 英語、ドイツ語、日本語の、メニューのNavigationLinkをList中で作成する。
  7. Buildして実行する。

では、projectを一つ立ち上げてください(名前はJSONExample1とでもしてください)。まず、ContentView.swiftに以下のコードを貼り付けてください。

ここでcodeの貼り付けでSytax highlighterのWordPressPluginを2種類使ってみました。最初のものは「Enlighter – Customizable Syntax Highlighter」で、次のものは「Prismatic」です。どちらも設定でいろいろ変わる様ですが、SwiftUIにいいのはどんな感じかいろいろ試してみることにします。

//
//  ContentView.swift
//  JSONExample1
//
//  Created by 宍戸知行 on 2020/04/12.
//  Copyright © 2020 宍戸知行. All rights reserved.
//

import SwiftUI

struct MenuItem: Codable, Identifiable {
    var id: Int
    var itemCategory: String
    var score: Int
    var imageName: String
    var isFavorite: Bool
    var jURLString: String
    var jTitle: String
    var jContent: String
    var dURLString: String
    var dTitle: String
    var dContent: String
    var eURLString: String
    var eTitle: String
    var eContent: String
}
//[MenuItem]のシンスタンスを作成
let menuItemData: [MenuItem] = load("menuItemData.json")
//JSONを読み込むための関数
func load<T: Decodable>(_ filename: String, as type: T.Type = T.self) -> T {
    let data: Data
    
    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
        else {
            fatalError("Couldn't find \(filename) in main bundle.")
    }
    
    do {
        data = try Data(contentsOf: file)
    } catch {
        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
    }
    
    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}


struct ContentView: View {
    var body: some View {
        NavigationView{
            List{
                ForEach(menuItemData) { menuItem in
                    VStack{
                        NavigationLink(
                        destination: WebView(webURLString: menuItem.jURLString)){
                            Text(menuItem.jTitle)
                        }
                        .background(Color.blue)
                    }
                    VStack{
                        NavigationLink(
                        destination: WebView(webURLString: menuItem.eURLString)){
                            Text(menuItem.eTitle)
                        }
                        .background(Color.red)
                    }
                    VStack{
                        NavigationLink(
                        destination: WebView(webURLString: menuItem.dURLString)){
                            Text(menuItem.dTitle)
                        }
                        .background(Color.yellow)
                    }
                }
            }//Listの最後の}
                .navigationBarTitle("3ヶ国語メニューリンク")
        }//NavigationViewの最後の}
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
/
//  ContentView.swift
//  JSONExample1
//
//  Created by 宍戸知行 on 2020/04/12.
//  Copyright © 2020 宍戸知行. All rights reserved.
//

import SwiftUI

struct MenuItem: Codable, Identifiable {
    var id: Int
    var itemCategory: String
    var score: Int
    var imageName: String
    var isFavorite: Bool
    var jURLString: String
    var jTitle: String
    var jContent: String
    var dURLString: String
    var dTitle: String
    var dContent: String
    var eURLString: String
    var eTitle: String
    var eContent: String
}
//[MenuItem]のシンスタンスを作成
let menuItemData: [MenuItem] = load("menuItemData.json")
//JSONを読み込むための関数
func load<T: Decodable>(_ filename: String, as type: T.Type = T.self) -> T {
    let data: Data
    
    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
        else {
            fatalError("Couldn't find \(filename) in main bundle.")
    }
    
    do {
        data = try Data(contentsOf: file)
    } catch {
        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
    }
    
    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}


struct ContentView: View {
    var body: some View {
        NavigationView{
            List{
                ForEach(menuItemData) { menuItem in
                    VStack{
                        NavigationLink(
                        destination: WebView(webURLString: menuItem.jURLString)){
                            Text(menuItem.jTitle)
                        }
                        .background(Color.blue)
                    }
                    VStack{
                        NavigationLink(
                        destination: WebView(webURLString: menuItem.eURLString)){
                            Text(menuItem.eTitle)
                        }
                        .background(Color.red)
                    }
                    VStack{
                        NavigationLink(
                        destination: WebView(webURLString: menuItem.dURLString)){
                            Text(menuItem.dTitle)
                        }
                        .background(Color.yellow)
                    }
                }
            }//Listの最後の}
                .navigationBarTitle("3ヶ国語メニューリンク")
        }//NavigationViewの最後の}
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

まず、MenuItemの定義を見てみましょう。

JSONDecorderでdecodeするためにCodableプロトコルと、List表示を簡単するためにIdentifiableプロトコル(propertyにidを指定)を適用します(日本語は「批准」とか使うのでしょうかね?)。protocolの説明はここのサイトなどにもありますよ。上は実際の「ヘルパーKアプリ」で使用したものです。多言語化に関係あるのは、jURLStringが日本語のホームページアドレス、jTitleが日本語のメニューの名前、jContentが日本語メニューページ中のtextです。以下同様に英語が(eURLString, eTitle, eContent)、ドイツ語が(dURLString, dTitle, dContent)です。

一つ工夫したのが、itemCategoryで、例えば「”000PR1MF1VE1SP1DK1″」「”001PR0MF0VE0SP0DK1″」「”002PR0MF0VE0SP0DK1″」の様に、多言語共通で各メニューに「バーコード」を付けたことです。具体的には、最初の3桁の整数が「アイテム番号」PRは「パスタ&ライス」で、次の「0/1」は該当する場合は「1」該当しない場合は「0」というようにタグをつけました。MFは「肉魚」、VEは「野菜」、「SP」は「スープ」、「DK」は「ドリンクお菓子」といった具合です。このように多言語で共通に認識処理できる「バーコード」をつけると便利だと思います。「ヘルパーK」ではこのバーコードに基づいてメニューのカテゴリ分類をしています。

そして、次に、JSONデータを作成します。JSONデータの作成は、これまた大瀧さんのおすすめで「CotEditor」を使用しています。具体的にはこんな感じです。

[
	{
        "id":  0,
        "itemCategory": "000PR1MF1VE1SP1DK1",
        "score": 0,
        "imageName": "Item000L",
        "isFavorite": true,
        "jURLString": "https://bfaaap.com",
        "jTitle": "ピアノ補助ペダルシステムbFaaaP",
        "jContent": "bFaaaPとは足に障害を持つチャレンジドや子供達が使用可能なピアノの補助ペダルシステムで、「barrier-Free assist as a Pedal」の頭文字をとって名付けました。ピアノの演奏が好きであってもペダル操作ができないため歯がゆい思いをしてきた人もbFaaaPを使用することでより楽しくピアノが演奏できるようになりました。現在のところ市販する予定はありませんが、ライセンスを受けて受託製造しようと考える方がいましたらご連絡いただけると幸いです。bFaaaPの開発過程で得た知見や製造方法、プログラム等もできるだけオープンにしていきたいと考えています。",
        
        "dURLString": "https://bfaaap.com/wpd/ueber-bfaaap",
        "dTitle": "bFaaaP barrierefreie Unterstützung wie ein Pedal",
        "dContent": "Der bFaaaP (Abkürzung für barrier-Free assist as a pedal (barrierefreie Unterstützung als Pedal)) kann für Behinderte mit Beinbehinderungen und Kinder verwendet werden. Die Verwendung von bFaaaP ermöglicht ein schönes Klavierspiel, das von denjenigen, die ein Klavierpedal nicht drücken können, gespielt wird. Das bFaaaP ist nicht im Handel erhältlich und wir suchen einen Lizenznehmer, der die bFaaaP herstellen und vermarkten will. Hier legen wir die Ergebnisse der Entwicklung, die Herstellung des bFaaaP und seine Programme offen. Wir haben uns an der Entwicklung von bFaaaP sehr gefreut und freuen uns, die Freude mit Ihnen zu teilen. Bitte besuchen Sie auch die Scrapbox-Seiten zu bFaaaP für weitere detaillierte Informationen.",
        "eURLString": "https://bfaaap.com/wpe/",
        "eTitle": "bFaaaP barrier-Free assist as a Pedal",
        "eContent": "The bFaaaP (abbreviation of barrier-Free assist as a pedal) can be used for the challenged with leg disabilities and kids.  Use of bFaaaP allows for enjoyable piano performance played by those who want to but cannot depress a piano pedal.  The bFaaaP is not commercially available and we are looking for a licensee who plans to manufacture and market the bFaaaP.  Herein, we disclose the findings obtained through the development, how to manufacture the bFaaaP, and its programs. We, by ourselves, have enjoyed throughout the course of development of bFaaaP and are happy to share the joy with you.  Also, please visit out Scrapbox pages about bFaaaP for further detailed information.",
    },
    {
        "id":  1,
        "itemCategory": "001PR0MF0VE0SP0DK1",
        "score": 0,
        "imageName": "Item001L",
        "isFavorite": false,
        "jURLString": "https://bfaaap.com/2018/12/22/ヘルパーkの早業3分クッキング(紅茶牛乳)/",
        "jTitle": "紅茶牛乳",
        "jContent": "その1 紅茶牛乳最近のマイブームはピアノの練習をした後に「紅茶牛乳」を飲むことです。「ミルクティー」というとおしゃれな響きがありますが、柄でもないので「紅茶牛乳」と呼んでます。今日はヘルパーKの作り方を紹介します。レシピ1.コップに牛乳を6分目ぐらい注ぎます。2.レンジに入れて「温め」で沸騰する前までチンします。3.ティーパックを入れてぐるぐるかき混ぜます。(ヘルパーMさんから糸を切ってグルグルするともっと紅茶が出るというご意見をいただきました。)おしまい。ティーパックにお湯を注いでぐるぐる混ぜてその後牛乳にいれるのとでは、違いがあるようですよ。飲み物、飲料、ドリンク",
        "dURLString": "https://bfaaap.com/wpd/2019/07/17/teemilch-haushelferin-k-heisst-helfer-k-in-japan/",
        "dTitle": "Teemilch",
        "dContent": "Teemilch 1 Mein Boom ist es, nach dem Klaviertraining Teemilch zu trinken. Obwohl Milchtee einen stilvollen Klang hat, handelt es sich nicht um einen Griff, daher wird er Teemilch genannt. Heute werde ich vorstellen, wie man Helfer K macht. Rezept 1. Gießen Sie die Milch für ca. 6 Minuten in die Tasse. 2. In die Pfanne geben und mit warm kochen . 3. In eine Teepackung geben und umrühren. (Wir haben die Meinung von Herrn Helfer M erhalten, dass Sie mehr Tee bekommen, wenn Sie den Faden abschneiden.) Ende. Es scheint, dass es einen Unterschied gibt, ob heißes Wasser in eine Teepackung gegossen, gemischt und dann in Milch gegossen wird. Getränke, Getränke, Getränke",
        
        "eURLString": "https://bfaaap.com/wpe/2019/07/17/tea-milk/",
        "eTitle": "Tea milk",
        "eContent": "Tea milk 1 My boom is to drink tea milk after practicing the piano. Although milk tea has a stylish sound, it is not a handle, so it is called tea milk. Today I will introduce how to make helper K. Recipe 1. Pour milk into cup for about 6 minutes. 2. Put in the range and cook until boil with warm. 3. Put in a tea pack and stir. (We received an opinion from Mr. Helper M that when you cut the thread, you get more tea.) End. It seems that there is a difference between pouring hot water into a tea pack and mixing it and then pouring it into milk. Drinks, beverages, drinks",




    },
    {
        "id":  2,
        "itemCategory": "002PR0MF0VE0SP0DK1",
        "score": 0,
        "imageName": "Item002L",
        "isFavorite": false,
        "jURLString": "https://bfaaap.com/2019/01/08/ヘルパーkの早業3分クッキングその2(りんごラ/",
        "jTitle": "りんごラッシー",
        "jContent": "その2 りんごラッシーラッシーというとインド料理のドリンクの「マンゴーラッシー」を思い浮かべますが、いろいろな果物でも出来るようです。冬の季節だと果物の種類が減ってきますが、「りんご」がラッシーと合うのではないかと思っています。ヘルパーKが教えてくれました。今日はその作り方を紹介します。材料(一人前)1.りんご 半分2.プレーンヨーグルト(無糖)100g(ヘルパーKはブルガリアヨーグルトがよいとおっしゃっています)3.牛乳 150cc4.ハチミツ 大さじ1りんごの準備半分に切って、さらに8分の1にして皮をむいて、小さく切る(下の写真を参考にしてください)。他の4分の1のものも同様にしてぶつ切りにする。ミキサーへの投入1.りんごを投入ヨーグルト100gを量り、ミキサーに投入。牛乳150ccを計量カップで量り、ミキサーに投入。ハチミツ大さじ1杯を量り、ミキサーに投入。ミキサーの蓋をして30秒混ぜる。コップに注いで出来上がり!飲み物、飲料、ドリンク",
        
        "dURLString": "https://bfaaap.com/wpd/2019/07/17/apfel-lassi/",
        "dTitle": "Apfel-Lassi",
        "dContent": "Teil 2 Ich stelle mir Apfel-Lassi-Lassi als Mango-Lassi vor, ein Getränk der indischen Küche, aber anscheinend kann man es mit verschiedenen Früchten machen. In der Wintersaison nimmt die Vielfalt der Früchte ab, aber ich denke, dass Äpfel mit Lassy zusammenpassen können. Helfer K hat es mir erzählt. Heute werde ich vorstellen, wie es gemacht wird. Zutaten (vorher eine Person) 1. Apfelhälfte 2. Naturjoghurt (zuckerfrei) 100 g (Helfer K sagte, Sie möchten bulgarischen Joghurt) 3. Milch 150 ccm 4. Honig Schneiden Sie eine Hälfte eines Esslöffels Apfel in die Hälfte und weitere 8 Minuten Schälen Sie es in 1 und schneiden Sie es in kleine Stücke (siehe Abbildung unten). Machen Sie dasselbe für die anderen Quartale. Laden in den Mixer 1. Laden von Äpfeln Wiegen von 100 g Joghurt und Laden in den Mixer. Mit einem Messbecher 150 ccm Milch abwiegen und in den Mixer geben. Einen Esslöffel Honig wiegen und in den Mixer geben. Den Mischer schließen und 30 Sekunden lang mischen. In eine Tasse gießen und fertig! Getränke, Getränke, Getränke",
        
        "eURLString": "https://bfaaap.com/wpe/2019/07/17/apple-lassi/",
        "eTitle": "Apple lassi",
        "eContent": "Part 2 I think of apple lassi lassi as mango lassi, a drink of Indian cuisine, but it seems that you can do it with various fruits. In the winter season, the variety of fruits decreases, but I think that apples may match with Lassy. Helper K told me. Today I will introduce how to make it. Ingredients (one person before) 1. Apple half 2. Plain yogurt (sugar-free) 100 g (Helper K said you would like Bulgarian yogurt) 3. Milk 150 cc 4. Honey Cut one half of a tablespoon apple into half and further 8 minutes Peel into 1 and cut into small pieces (please refer to the photo below). Do the same for the other quarters. Loading into the mixer 1. Loading apples Weighing 100 g of yogurt and loading into the mixer. Weigh 150 cc of milk with a measuring cup and put it into the mixer. Weigh a tablespoon of honey and put it in the mixer. Close the mixer and mix for 30 seconds. Pour into a cup and it's done! Drinks, beverages, drinks",
    },
    ]

では、このJSONデータ(名前はmenuItemData.jsonとします)をprojectに以下の写真の様にして追加します。

そして、JSONデータを[MenuItem]のメニューデータにロード(読み込む)するやり方を説明します。

1では、”menuItemData.json”ファイルを引数としてload関数を実行して、menuItemDataという[MenuItem」の中身を作成します。

恥ずかしながら今でも完全に理解しているとは言い難いのですが、2の意味が理解できませんでした。この「T」というのは型のことでここでは[MenuItem]を指します。上記した様に「MenuItem」は「Coadable」のプロトコルを適用していますのでその下位概念の「Decodable」が適用できます。ここでload関数の第一引数の「filename」は”menuItemData.json”というJSONファイルを指定し、次の「as type:」では、「T.Type」に「T.self」を代入していますが、「self」が後置されています。この意味が最初わかりませんでした。調べたところ、これは「型の型」で、この後置したselfにより「自身の型」の値が取れるという意味の様です。したがって、selfを前置する場合とは意味が全く違う様ですね。

そして、load関数の戻り値(return)は「T」となっていますから、[MenuItem]配列のメニューデータが返ってくるので、それをmenuItemDataに1で代入するという感じです。

具体的はload関数の処理は3段階あって、1段階目で”menuDataItem.json”ファイルのurlアドレスを取得し、2段回目でそのurlアドレスの中身を「data」として取り出し、3段階目でJSONDecoderを使ってdataから情報を「T([MenuItem])」へと出力してreturnするという動作です。この動作はnilを返す可能性があるので、do-catch構文を使います。これで多言語JSONデータをメニューデータにすることができました。

多言語メニューのWeb表示

この部分は、「その13(多言語化その4:言語を切り替えてWeb表示)」で説明しましたので、詳細はそちらを参照ください。

簡単には、まずは、info.plistの「App Transport Security Settings」を写真のように記載します。

そして、WebView.swiftファイルを追加して、以下のコードをコピペします。

//
//  WebView.swift
//  JSONExample1
//
//  Created by 宍戸知行 on 2020/04/12.
//  Copyright © 2020 宍戸知行. All rights reserved.
//


import SwiftUI
import WebKit

struct WebView : UIViewRepresentable {
    var webURLString: String
    @ObservedObject var webViewClass = WebViewClass()
    
    func makeUIView(context: Context) -> WKWebView  {
        let viewConfiguration = WKWebViewConfiguration()
        self.webViewClass.webView = WKWebView(frame: .zero, configuration: viewConfiguration)
        self.webViewClass.webView.uiDelegate = self.webViewClass
        return self.webViewClass.webView
    }
    func updateUIView(_ uiView: WKWebView, context: Context) {
        let encoderUrlString: String = self.webURLString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        if let url = URL(string: encoderUrlString) {
            let req = URLRequest(url: url)
            uiView.load(req)
        }
    }
}

//webViewClassをclassのobjectとして提供する
final class WebViewClass: UIViewController, ObservableObject, WKUIDelegate{
    @Published var webView: WKWebView!
    
    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        if navigationAction.targetFrame == nil {
            webView.load(navigationAction.request)
        }
        return nil
    }
    
}

そして、ContentView.swiftに英語、ドイツ語、日本語の、メニューのNavigationLinkをList中で作成します。それぞれの言語のリンクに色をつけました。このリストには、個別に個々のリストにbackgroudColorを付けることが現在のSwiftUIではできないことは「その18(Navigation LinkのListのbackgroundは現在設定できない)」に書いているのでご参照ください。

さあ、実行してみましょう!多言語データを使って自在に多言語ウエブページにアクセスすることができるようになりました。

関連記事一覧

コメント

この記事へのコメントはありません。