feat(ios/engine): full kmp.json decoding via Codable

This commit is contained in:
jahorton 2020-06-02 09:45:24 +07:00
parent fe0791580a
commit aa932a0df5
14 changed files with 400 additions and 68 deletions

View file

@ -176,12 +176,14 @@
CE808A4D236697D500713E6B /* Reachability.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0FC9FC22D66D9E00D33F86 /* Reachability.framework */; };
CE808A4F236697D800713E6B /* XCGLogger.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1687ACCC1FD8DE5300926D69 /* XCGLogger.framework */; };
CE808A532366980A00713E6B /* Zip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 165C8B8B1FD8220600D4A78D /* Zip.framework */; };
CE867FEF2485E97A00B2EAED /* KMPMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE867FEE2485E97A00B2EAED /* KMPMetadata.swift */; };
CE867FF12485EE1500B2EAED /* KMPInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE867FF02485EE1500B2EAED /* KMPInfo.swift */; };
CE88042F247F4B97009BCA1A /* ExpectationDownloaderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88042E247F4B97009BCA1A /* ExpectationDownloaderDelegate.swift */; };
CE8EDEB123F53D1A009E1FF6 /* FileManagementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A079DD1223194B100581263 /* FileManagementTests.swift */; };
CE8EDEB323F53F96009E1FF6 /* VersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8EDEB223F53F96009E1FF6 /* VersionTests.swift */; };
CE973D812484A56500F66045 /* PackageJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE973D802484A56500F66045 /* PackageJSON.swift */; };
CE973D832484A5DC00F66045 /* PackageJSON.bundle in Resources */ = {isa = PBXBuildFile; fileRef = CE973D822484A5DB00F66045 /* PackageJSON.bundle */; };
CE973D852484A71700F66045 /* PackageJSONTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE973D842484A71700F66045 /* PackageJSONTests.swift */; };
CE973D852484A71700F66045 /* KMPJSONTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE973D842484A71700F66045 /* KMPJSONTests.swift */; };
CE973D872484BBC200F66045 /* KMPLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE973D862484BBC200F66045 /* KMPLanguage.swift */; };
CE973D892484DBA600F66045 /* KMPSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE973D882484DBA600F66045 /* KMPSystem.swift */; };
CE973D8B2484DCA300F66045 /* KMPOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE973D8A2484DCA300F66045 /* KMPOptions.swift */; };
@ -455,11 +457,13 @@
CE7A26D723CEEC630005955C /* Colors+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Colors+Extension.swift"; sourceTree = "<group>"; };
CE7A26DA23CEEF640005955C /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
CE7ADD6523DE89FC00BC9A00 /* Alerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alerts.swift; sourceTree = "<group>"; };
CE867FEE2485E97A00B2EAED /* KMPMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMPMetadata.swift; sourceTree = "<group>"; };
CE867FF02485EE1500B2EAED /* KMPInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMPInfo.swift; sourceTree = "<group>"; };
CE88042E247F4B97009BCA1A /* ExpectationDownloaderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpectationDownloaderDelegate.swift; sourceTree = "<group>"; };
CE8EDEB223F53F96009E1FF6 /* VersionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionTests.swift; sourceTree = "<group>"; };
CE973D802484A56500F66045 /* PackageJSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageJSON.swift; sourceTree = "<group>"; };
CE973D822484A5DB00F66045 /* PackageJSON.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = PackageJSON.bundle; sourceTree = "<group>"; };
CE973D842484A71700F66045 /* PackageJSONTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageJSONTests.swift; sourceTree = "<group>"; };
CE973D842484A71700F66045 /* KMPJSONTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMPJSONTests.swift; sourceTree = "<group>"; };
CE973D862484BBC200F66045 /* KMPLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMPLanguage.swift; sourceTree = "<group>"; };
CE973D882484DBA600F66045 /* KMPSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMPSystem.swift; sourceTree = "<group>"; };
CE973D8A2484DCA300F66045 /* KMPOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMPOptions.swift; sourceTree = "<group>"; };
@ -710,7 +714,7 @@
CE4459CD23FBBA8D003151FD /* AppDelegate.swift */,
CEF888D423FE786C00667693 /* KeyboardScaleTests.swift */,
CEDFEF8E23FE43B700BECF39 /* MigrationTests.swift */,
CE973D842484A71700F66045 /* PackageJSONTests.swift */,
CE973D842484A71700F66045 /* KMPJSONTests.swift */,
);
path = KeymanEngineTests;
sourceTree = "<group>";
@ -856,16 +860,26 @@
path = "Resource Management";
sourceTree = "<group>";
};
CE973D7F2484A07300F66045 /* KMP Packaging */ = {
CE867FF22485F38300B2EAED /* kmp.json */ = {
isa = PBXGroup;
children = (
1645D5962036C9F80076C51B /* KMPKeyboard.swift */,
CE867FF02485EE1500B2EAED /* KMPInfo.swift */,
CE867FEE2485E97A00B2EAED /* KMPMetadata.swift */,
CE973D862484BBC200F66045 /* KMPLanguage.swift */,
9A079E362238680700581263 /* KMPLexicalModel.swift */,
CE973D882484DBA600F66045 /* KMPSystem.swift */,
CE973D8A2484DCA300F66045 /* KMPOptions.swift */,
CE973D8C2484DD1900F66045 /* KMPFile.swift */,
);
path = kmp.json;
sourceTree = "<group>";
};
CE973D7F2484A07300F66045 /* KMP Packaging */ = {
isa = PBXGroup;
children = (
CE867FF22485F38300B2EAED /* kmp.json */,
);
name = "KMP Packaging";
sourceTree = "<group>";
};
@ -1376,7 +1390,7 @@
CE37C39123FCD617007031C6 /* LexicalModels.swift in Sources */,
CE37C38D23FCD41E007031C6 /* Keyboards.swift in Sources */,
CE9CD88323FCC4E3002BF2F8 /* UserDefaults.swift in Sources */,
CE973D852484A71700F66045 /* PackageJSONTests.swift in Sources */,
CE973D852484A71700F66045 /* KMPJSONTests.swift in Sources */,
CEF888D523FE786C00667693 /* KeyboardScaleTests.swift in Sources */,
CE07E3D6247E41DB00DFA9D2 /* URLSessionMock.swift in Sources */,
CE8EDEB323F53F96009E1FF6 /* VersionTests.swift in Sources */,
@ -1431,6 +1445,7 @@
C06D37401F81F5C400F61AE0 /* LanguageDetailViewController.swift in Sources */,
9A9CB0862241717C00231FB9 /* LexicalModelRepositoryDelegate.swift in Sources */,
C0CB633C1FA6F61500617CDB /* Notifications.swift in Sources */,
CE867FF12485EE1500B2EAED /* KMPInfo.swift in Sources */,
C0EF3E7B1F95B65300CE9BD4 /* KeymanWebDelegate.swift in Sources */,
C06D37411F81F5C400F61AE0 /* LanguageViewController.swift in Sources */,
C06D37421F81F5C400F61AE0 /* KeyboardPickerButton.swift in Sources */,
@ -1453,6 +1468,7 @@
C040E5101F8606E300901EE4 /* TextField.swift in Sources */,
CE71705823A9C14D00A924A1 /* ResourceFileManager.swift in Sources */,
9A082559227589360051EBB0 /* Formatter+ISODateExtension.swift in Sources */,
CE867FEF2485E97A00B2EAED /* KMPMetadata.swift in Sources */,
C0324B8D1F87480700AF3785 /* TextFieldDelegateProxy.swift in Sources */,
CE973D8B2484DCA300F66045 /* KMPOptions.swift in Sources */,
C075EB061F8EFF870041F4BD /* String+Helpers.swift in Sources */,

View file

@ -11,5 +11,10 @@ import Foundation
public struct KMPFile: Codable {
var name: String
var description: String
var copyLocation: Int?
enum CodingKeys: String, CodingKey {
case name
case description
// case copyLocation // deliberately omitted
}
}

View file

@ -0,0 +1,30 @@
//
// KMPInfo.swift
// KeymanEngine
//
// Created by Joshua Horton on 6/2/20.
// Copyright © 2020 SIL International. All rights reserved.
//
import Foundation
class KMPInfoItem: Codable {
var description: String
var url: String?
}
class KMPInfo: Codable {
var name: KMPInfoItem?
var author: KMPInfoItem?
var copyright: KMPInfoItem?
var website: KMPInfoItem?
var version: KMPInfoItem?
enum CodingKeys: String, CodingKey {
case name
case author
case copyright
case website
case version
}
}

View file

@ -0,0 +1,60 @@
//
// KMPInfo.swift
// KeymanEngine
//
// Created by Joshua Horton on 6/2/20.
// Copyright © 2020 SIL International. All rights reserved.
//
import Foundation
public class KMPMetadata: Codable {
var system: KMPSystem
var options: KMPOptions
var info: KMPInfo?
var files: [KMPFile]?
var keyboards: [KMPKeyboard]?
var lexicalModels: [KMPLexicalModel]?
enum CodingKeys: String, CodingKey {
case system
case options
case info
case files
case keyboards
case lexicalModels
// The following reflect members that may exist, according to our schema,
// but that are currently unused by Keyman for iOS.
// case strings
}
enum PackageType: String {
case Keyboard
case LexicalModel
case Unsupported
}
var isValid: Bool {
if keyboards != nil && lexicalModels != nil {
return false
}
if keyboards == nil && lexicalModels == nil {
return false
}
return true
}
var packageType: PackageType {
if !isValid {
return .Unsupported
} else if keyboards != nil {
return .Keyboard
} else if lexicalModels != nil {
return .LexicalModel
} else {
return .Unsupported
}
}
}

View file

@ -0,0 +1,124 @@
//
// PackageJSONTests.swift
// KeymanEngineTests
//
// Created by Joshua Horton on 6/1/20.
// Copyright © 2020 SIL International. All rights reserved.
//
import XCTest
@testable import KeymanEngine
class KMPJSONTests: XCTestCase {
private func loadObjectFromJSON<Type: Decodable>(at file: URL) throws -> Type {
let fileContents: String = try String(contentsOf: file, encoding: .utf8).replacingOccurrences(of: "\r", with: "")
let jsonData = fileContents.data(using: .utf8)!
let decoder = JSONDecoder()
return try decoder.decode(Type.self, from: jsonData)
}
func testLanguageDecoding() throws {
let lang_en: KMPLanguage = try loadObjectFromJSON(at: TestUtils.PackageJSON.language_en)
XCTAssertEqual(lang_en.name, "English")
XCTAssertEqual(lang_en.languageId, "en")
let lang_km: KMPLanguage = try loadObjectFromJSON(at: TestUtils.PackageJSON.language_km)
XCTAssertEqual(lang_km.name, "Central Khmer (Khmer, Cambodia)")
XCTAssertEqual(lang_km.languageId, "km")
}
// A helper method, since these are useful in two separate tests.
func keyboard_khmer_angkor_assertions(_ khmer_angkor: KMPKeyboard) {
XCTAssertEqual(khmer_angkor.languages.count, 1)
XCTAssertEqual(khmer_angkor.languages[0].name, "Central Khmer (Khmer, Cambodia)")
XCTAssertEqual(khmer_angkor.languages[0].languageId, "km")
XCTAssertEqual(khmer_angkor.name, "Khmer Angkor")
XCTAssertEqual(khmer_angkor.keyboardId, "khmer_angkor")
XCTAssertEqual(khmer_angkor.version, "1.0.6")
XCTAssertEqual(khmer_angkor.font, "Mondulkiri-R.ttf")
XCTAssertEqual(khmer_angkor.osk, "Mondulkiri-R.ttf")
XCTAssertEqual(khmer_angkor.isRTL, false)
}
func testKeyboardDecoding() throws {
let khmer_angkor: KMPKeyboard = try loadObjectFromJSON(at: TestUtils.PackageJSON.keyboard_khmer_angkor)
keyboard_khmer_angkor_assertions(khmer_angkor)
}
// A helper method, since these are useful in two separate tests.
func lexical_model_nrc_en_mtnt_assertions(_ nrc_en_mtnt: KMPLexicalModel) {
XCTAssertEqual(nrc_en_mtnt.name, "English dictionary (MTNT)")
XCTAssertEqual(nrc_en_mtnt.lexicalModelId, "nrc.en.mtnt")
// Our example case does not define either of these two values.
// One (isRTL), we assume a default value for.
XCTAssertEqual(nrc_en_mtnt.version, nil)
XCTAssertEqual(nrc_en_mtnt.isRTL, false)
XCTAssertEqual(nrc_en_mtnt.languages.count, 3)
XCTAssertEqual(nrc_en_mtnt.languages[0].languageId, "en")
XCTAssertEqual(nrc_en_mtnt.languages[1].languageId, "en-us")
XCTAssertEqual(nrc_en_mtnt.languages[2].languageId, "en-ca")
}
func testLexicalModelDecoding() throws {
let nrc_en_mtnt: KMPLexicalModel = try loadObjectFromJSON(at: TestUtils.PackageJSON.model_nrc_en_mtnt)
lexical_model_nrc_en_mtnt_assertions(nrc_en_mtnt)
}
func testKeyboardPackageInfoDecoding() throws {
let khmer_angkor: KMPMetadata = try loadObjectFromJSON(at: TestUtils.PackageJSON.kmp_json_khmer_angkor)
XCTAssertTrue(khmer_angkor.isValid)
XCTAssertEqual(khmer_angkor.packageType, KMPMetadata.PackageType.Keyboard)
XCTAssertNotNil(khmer_angkor.keyboards)
XCTAssertEqual(khmer_angkor.keyboards!.count, 1)
keyboard_khmer_angkor_assertions(khmer_angkor.keyboards![0])
XCTAssertNil(khmer_angkor.lexicalModels)
XCTAssertNotNil(khmer_angkor.files)
XCTAssertEqual(khmer_angkor.files!.count, 16)
XCTAssertNotNil(khmer_angkor.info)
XCTAssertEqual(khmer_angkor.info!.version!.description, "1.0.6")
XCTAssertNil(khmer_angkor.info!.version!.url)
XCTAssertEqual(khmer_angkor.info!.author!.description, "Makara Sok")
XCTAssertEqual(khmer_angkor.info!.author!.url, "mailto:makara@keyman.com")
XCTAssertEqual(khmer_angkor.system.fileVersion, "7.0")
XCTAssertNil(khmer_angkor.options.graphicFile)
XCTAssertNil(khmer_angkor.options.readmeFile)
}
func testLexicalModelPackageInfoDecoding() throws {
let nrc_en_mtnt: KMPMetadata = try loadObjectFromJSON(at: TestUtils.PackageJSON.kmp_json_nrc_en_mtnt)
XCTAssertTrue(nrc_en_mtnt.isValid)
XCTAssertEqual(nrc_en_mtnt.packageType, KMPMetadata.PackageType.LexicalModel)
XCTAssertNotNil(nrc_en_mtnt.lexicalModels)
XCTAssertEqual(nrc_en_mtnt.lexicalModels!.count, 1)
lexical_model_nrc_en_mtnt_assertions(nrc_en_mtnt.lexicalModels![0])
XCTAssertNil(nrc_en_mtnt.keyboards)
XCTAssertNotNil(nrc_en_mtnt.files)
XCTAssertEqual(nrc_en_mtnt.files!.count, 1)
XCTAssertNotNil(nrc_en_mtnt.info)
XCTAssertEqual(nrc_en_mtnt.info!.version!.description, "0.1.3")
XCTAssertNil(nrc_en_mtnt.info!.version!.url)
XCTAssertEqual(nrc_en_mtnt.info!.author!.description, "Eddie Antonio Santos")
XCTAssertEqual(nrc_en_mtnt.info!.author!.url, "mailto:easantos@ualberta.ca")
XCTAssertEqual(nrc_en_mtnt.system.fileVersion, "12.0")
XCTAssertNil(nrc_en_mtnt.options.graphicFile)
XCTAssertNil(nrc_en_mtnt.options.readmeFile)
}
}

View file

@ -1,62 +0,0 @@
//
// PackageJSONTests.swift
// KeymanEngineTests
//
// Created by Joshua Horton on 6/1/20.
// Copyright © 2020 SIL International. All rights reserved.
//
import XCTest
@testable import KeymanEngine
class PackageJSONTests: XCTestCase {
private func loadObjectFromJSON<Type: Decodable>(at file: URL) throws -> Type {
let fileContents: String = try String(contentsOf: file, encoding: .utf8).replacingOccurrences(of: "\r", with: "")
let jsonData = fileContents.data(using: .utf8)!
let decoder = JSONDecoder()
return try decoder.decode(Type.self, from: jsonData)
}
func testLanguageDecoding() throws {
let lang_en: KMPLanguage = try loadObjectFromJSON(at: TestUtils.PackageJSON.language_en)
XCTAssertEqual(lang_en.name, "English")
XCTAssertEqual(lang_en.languageId, "en")
let lang_km: KMPLanguage = try loadObjectFromJSON(at: TestUtils.PackageJSON.language_km)
XCTAssertEqual(lang_km.name, "Central Khmer (Khmer, Cambodia)")
XCTAssertEqual(lang_km.languageId, "km")
}
func testKeyboardDecoding() throws {
let khmer_angkor: KMPKeyboard = try loadObjectFromJSON(at: TestUtils.PackageJSON.keyboard_khmer_angkor)
XCTAssertEqual(khmer_angkor.languages.count, 1)
XCTAssertEqual(khmer_angkor.languages[0].name, "Central Khmer (Khmer, Cambodia)")
XCTAssertEqual(khmer_angkor.languages[0].languageId, "km")
XCTAssertEqual(khmer_angkor.name, "Khmer Angkor")
XCTAssertEqual(khmer_angkor.keyboardId, "khmer_angkor")
XCTAssertEqual(khmer_angkor.version, "1.0.6")
XCTAssertEqual(khmer_angkor.font, "Mondulkiri-R.ttf")
XCTAssertEqual(khmer_angkor.osk, "Mondulkiri-R.ttf")
XCTAssertEqual(khmer_angkor.isRTL, false)
}
func testLexicalModelDecoding() throws {
let nrc_en_mtnt: KMPLexicalModel = try loadObjectFromJSON(at: TestUtils.PackageJSON.model_nrc_en_mtnt)
XCTAssertEqual(nrc_en_mtnt.name, "English dictionary (MTNT)")
XCTAssertEqual(nrc_en_mtnt.lexicalModelId, "nrc.en.mtnt")
// Our example case does not define either of these two values.
// One (isRTL), we assume a default value for.
XCTAssertEqual(nrc_en_mtnt.version, nil)
XCTAssertEqual(nrc_en_mtnt.isRTL, false)
XCTAssertEqual(nrc_en_mtnt.languages.count, 3)
XCTAssertEqual(nrc_en_mtnt.languages[0].languageId, "en")
XCTAssertEqual(nrc_en_mtnt.languages[1].languageId, "en-us")
XCTAssertEqual(nrc_en_mtnt.languages[2].languageId, "en-ca")
}
}

View file

@ -19,5 +19,8 @@ extension TestUtils {
static let keyboard_khmer_angkor = jsonBundle.url(forResource: "keyboard-khmer_angkor", withExtension: "json")!
static let model_nrc_en_mtnt = jsonBundle.url(forResource: "model-nrc.en.mtnt", withExtension: "json")!
static let kmp_json_khmer_angkor = jsonBundle.url(forResource: "kmp-json-khmer_angkor", withExtension: "json")!
static let kmp_json_nrc_en_mtnt = jsonBundle.url(forResource: "kmp-json-nrc.en.mtnt", withExtension: "json")!
}
}

View file

@ -0,0 +1,104 @@
{
"system": {
"keymanDeveloperVersion": "11.0.1356.0",
"fileVersion": "7.0"
},
"options": {
},
"info": {
"name": {
"description": "Khmer Angkor"
},
"copyright": {
"description": "\u00A9 2015-2019 SIL International"
},
"author": {
"description": "Makara Sok",
"url": "mailto:makara@keyman.com"
},
"version": {
"description": "1.0.6"
}
},
"files": [
{
"name": "khmer_angkor.js",
"description": "File khmer_angkor.js"
},
{
"name": "khmer_angkor.kvk",
"description": "File khmer_angkor.kvk"
},
{
"name": "khmer_angkor.kmx",
"description": "Keyboard Khmer Angkor"
},
{
"name": "keyboard_layout.png",
"description": "File keyboard_layout.png"
},
{
"name": "welcome.htm",
"description": "File welcome.htm"
},
{
"name": "FONTLOG.txt",
"description": "File FONTLOG.txt"
},
{
"name": "Mondulkiri-B.ttf",
"description": "Font Khmer Mondulkiri Bold"
},
{
"name": "Mondulkiri-BI.ttf",
"description": "Font Khmer Mondulkiri Bold Italic"
},
{
"name": "Mondulkiri-I.ttf",
"description": "Font Khmer Mondulkiri Italic"
},
{
"name": "Mondulkiri-R.ttf",
"description": "Font Khmer Mondulkiri"
},
{
"name": "OFL.txt",
"description": "File OFL.txt"
},
{
"name": "OFL-FAQ.txt",
"description": "File OFL-FAQ.txt"
},
{
"name": "KAK_Documentation_EN.pdf",
"description": "File KAK_Documentation_EN.pdf"
},
{
"name": "KAK_Documentation_KH.pdf",
"description": "File KAK_Documentation_KH.pdf"
},
{
"name": "kmp.inf",
"description": "Package information"
},
{
"name": "kmp.json",
"description": "Package information (JSON)"
}
],
"keyboards": [
{
"name": "Khmer Angkor",
"id": "khmer_angkor",
"version": "1.0.6",
"oskFont": "Mondulkiri-R.ttf",
"displayFont": "Mondulkiri-R.ttf",
"languages": [
{
"name": "Central Khmer (Khmer, Cambodia)",
"id": "km"
}
]
}
]
}

View file

@ -0,0 +1,52 @@
{
"system": {
"keymanDeveloperVersion": "12.0.1500.0",
"fileVersion": "12.0"
},
"options": {
"followKeyboardVersion": true
},
"info": {
"author": {
"description": "Eddie Antonio Santos",
"url": "mailto:easantos@ualberta.ca"
},
"copyright": {
"description": "© 2019 National Research Council Canada"
},
"name": {
"description": "English language model mined from MTNT"
},
"version": {
"description": "0.1.3"
}
},
"files": [
{
"name": "nrc.en.mtnt.model.js",
"description": "Lexical model nrc.en.mtnt.model.js",
"copyLocation": "0",
"fileType": ".model.js"
}
],
"lexicalModels": [
{
"name": "English dictionary (MTNT)",
"id": "nrc.en.mtnt",
"languages": [
{
"name": "English",
"id": "en"
},
{
"name": "English (US)",
"id": "en-us"
},
{
"name": "English (Canada)",
"id": "en-ca"
}
]
}
]
}