From 2b7ceff3e1faabe44b1f72886a0727650c6d93bd Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 7 Oct 2024 14:27:15 +0700 Subject: [PATCH 01/31] chore(developer): add baseline tests for bcp47 codes to kmc-package Relates-to: #12012 --- .../test/fixtures/bcp47/invalid_bcp47_1.kps | 27 ++++++++++++ .../kmc-package/test/fixtures/bcp47/test.kmx | Bin 0 -> 370 bytes .../test/fixtures/bcp47/valid_bcp47.kps | 39 ++++++++++++++++++ .../kmc-package/test/test-package-compiler.ts | 24 +++++++++++ 4 files changed, 90 insertions(+) create mode 100644 developer/src/kmc-package/test/fixtures/bcp47/invalid_bcp47_1.kps create mode 100644 developer/src/kmc-package/test/fixtures/bcp47/test.kmx create mode 100644 developer/src/kmc-package/test/fixtures/bcp47/valid_bcp47.kps diff --git a/developer/src/kmc-package/test/fixtures/bcp47/invalid_bcp47_1.kps b/developer/src/kmc-package/test/fixtures/bcp47/invalid_bcp47_1.kps new file mode 100644 index 00000000000..8a656e38baa --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/bcp47/invalid_bcp47_1.kps @@ -0,0 +1,27 @@ + + + + 10.0.700.0 + 7.0 + + + 1.0 + valid_bcp47 + + + + test.kmx + + + + + Invalid BCP 47 + test + 1.0 + + Not a language tag + + + + + diff --git a/developer/src/kmc-package/test/fixtures/bcp47/test.kmx b/developer/src/kmc-package/test/fixtures/bcp47/test.kmx new file mode 100644 index 0000000000000000000000000000000000000000..9e4bd15d391dea7a1b7e234e8f4f9ee80541836b GIT binary patch literal 370 zcmZvYO$$Lm6o%is!p_3S)4l&6lDtbj2`G0q?@%bfVlSjM&#>Fd| literal 0 HcmV?d00001 diff --git a/developer/src/kmc-package/test/fixtures/bcp47/valid_bcp47.kps b/developer/src/kmc-package/test/fixtures/bcp47/valid_bcp47.kps new file mode 100644 index 00000000000..72461e04c28 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/bcp47/valid_bcp47.kps @@ -0,0 +1,39 @@ + + + + 10.0.700.0 + 7.0 + + + + + + + + + + + + 1.0 + valid_bcp47 + + + + test.kmx + + + + + Valid BCP 47 + test + 1.0 + + Central Khmer (Khmer, Cambodia) + SENCOTEN + Unregistered variant of Straits Salish + Private variant of "Old" Khmer + + + + + diff --git a/developer/src/kmc-package/test/test-package-compiler.ts b/developer/src/kmc-package/test/test-package-compiler.ts index 7952e2145a2..16c523bda9a 100644 --- a/developer/src/kmc-package/test/test-package-compiler.ts +++ b/developer/src/kmc-package/test/test-package-compiler.ts @@ -12,6 +12,7 @@ import { makePathToFixture } from './helpers/index.js'; import { KmpCompiler } from '../src/compiler/kmp-compiler.js'; import { PackageCompilerMessages } from '../src/compiler/package-compiler-messages.js'; +import { PackageValidation } from '../src/compiler/package-validation.js'; const debug = false; @@ -29,6 +30,13 @@ describe('KmpCompiler', function () { assert.isTrue(await kmpCompiler.init(callbacks, null)); }); + this.afterEach(function() { + if(this.currentTest?.isFailed()) { + callbacks.printMessages(); + } + callbacks.clear(); + }); + for (let modelID of MODELS) { const kpsPath = modelID.includes('withfolders') ? makePathToFixture(modelID, 'source', `${modelID}.model.kps`) : makePathToFixture(modelID, `${modelID}.model.kps`); @@ -284,4 +292,20 @@ describe('KmpCompiler', function () { assert.equal(kmpJson.keyboards[0].version, '4.0'); // picks up example.kmx's version }); + it(`should handle a range of valid BCP47 tags`, function () { + const inputFilename = makePathToFixture('bcp47', 'valid_bcp47.kps'); + const kmpJson = kmpCompiler.transformKpsToKmpObject(inputFilename); + assert.isNotNull(kmpJson); + const validation = new PackageValidation(callbacks, {}); + assert.isTrue(validation.validate(inputFilename, kmpJson)); + }); + + it(`should reject an invalid BCP47 tag`, function () { + const inputFilename = makePathToFixture('bcp47', 'invalid_bcp47_1.kps'); + const kmpJson = kmpCompiler.transformKpsToKmpObject(inputFilename); + assert.isNotNull(kmpJson); + const validation = new PackageValidation(callbacks, {}); + assert.isFalse(validation.validate(inputFilename, kmpJson)); + }); + }); From bf7f4c2bc2ac067b4c8620e58fb465126464fa22 Mon Sep 17 00:00:00 2001 From: "Dr Mark C. Sinclair" Date: Wed, 27 Nov 2024 08:34:06 +0000 Subject: [PATCH 02/31] chore(common/web): add initial removeExtension test cases --- common/web/types/test/util/test-file-types.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 common/web/types/test/util/test-file-types.ts diff --git a/common/web/types/test/util/test-file-types.ts b/common/web/types/test/util/test-file-types.ts new file mode 100644 index 00000000000..2299f86d999 --- /dev/null +++ b/common/web/types/test/util/test-file-types.ts @@ -0,0 +1,16 @@ +import 'mocha'; +import { assert } from 'chai'; +import { removeExtension } from '../../src/util/file-types.js'; + +describe('Test of File-Types', () => { + describe('Test of removeExtension()', () => { + it('can remove file extension', () => { + const filename = removeExtension("file.kmn"); + assert.deepEqual(filename, "file"); + }); + it('can handle no file extension', () => { + const filename = removeExtension("file"); + assert.deepEqual(filename, "file"); + }); + }); +}); \ No newline at end of file From 3682766ff7f314259955bdafc3f57dff17499e36 Mon Sep 17 00:00:00 2001 From: "Dr Mark C. Sinclair" Date: Thu, 28 Nov 2024 02:15:46 +0000 Subject: [PATCH 03/31] chore(common/web): add copyright banner --- common/web/types/test/util/test-file-types.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/web/types/test/util/test-file-types.ts b/common/web/types/test/util/test-file-types.ts index 2299f86d999..45bfa555d8f 100644 --- a/common/web/types/test/util/test-file-types.ts +++ b/common/web/types/test/util/test-file-types.ts @@ -1,3 +1,11 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by Dr Mark C. Sinclair on 2024-11-28 + * + * Test code for file-types.ts + */ + import 'mocha'; import { assert } from 'chai'; import { removeExtension } from '../../src/util/file-types.js'; From 324c57f793d3b6e1283ed8cc48b01a514bfcd192 Mon Sep 17 00:00:00 2001 From: "Dr Mark C. Sinclair" Date: Thu, 28 Nov 2024 02:19:19 +0000 Subject: [PATCH 04/31] chore(common/web): test Source file extension removal --- common/web/types/test/util/test-file-types.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/common/web/types/test/util/test-file-types.ts b/common/web/types/test/util/test-file-types.ts index 45bfa555d8f..929cf22ceea 100644 --- a/common/web/types/test/util/test-file-types.ts +++ b/common/web/types/test/util/test-file-types.ts @@ -10,11 +10,23 @@ import 'mocha'; import { assert } from 'chai'; import { removeExtension } from '../../src/util/file-types.js'; +const sourceExts = [ // can't use Source, as it is const enum + '.model.ts', + '.kpj', + '.kmn', + '.xml', + '.kvks', + '.keyman-touch-layout', +]; + describe('Test of File-Types', () => { describe('Test of removeExtension()', () => { - it('can remove file extension', () => { - const filename = removeExtension("file.kmn"); - assert.deepEqual(filename, "file"); + it('can remove Source file extension', () => { + sourceExts.forEach((ext) => { + const filename = `file${ext}`; + const actual = removeExtension(filename); + assert.deepEqual(actual, "file"); + }); }); it('can handle no file extension', () => { const filename = removeExtension("file"); From 1782f8db091ff9cc6ef9c46541bcd36a3fb81a23 Mon Sep 17 00:00:00 2001 From: "Dr Mark C. Sinclair" Date: Thu, 28 Nov 2024 03:03:48 +0000 Subject: [PATCH 05/31] chore(common/web): add four test cases for sourceOrBinaryTypeFromFilename() --- common/web/types/test/util/test-file-types.ts | 56 +++++++++++++++---- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/common/web/types/test/util/test-file-types.ts b/common/web/types/test/util/test-file-types.ts index 929cf22ceea..962f69955b2 100644 --- a/common/web/types/test/util/test-file-types.ts +++ b/common/web/types/test/util/test-file-types.ts @@ -8,21 +8,25 @@ import 'mocha'; import { assert } from 'chai'; -import { removeExtension } from '../../src/util/file-types.js'; - -const sourceExts = [ // can't use Source, as it is const enum - '.model.ts', - '.kpj', - '.kmn', - '.xml', - '.kvks', - '.keyman-touch-layout', -]; +import { + ALL, + ALL_SOURCE, + ALL_BINARY, + removeExtension, + sourceOrBinaryTypeFromFilename +} from '../../src/util/file-types.js'; describe('Test of File-Types', () => { describe('Test of removeExtension()', () => { it('can remove Source file extension', () => { - sourceExts.forEach((ext) => { + ALL_SOURCE.forEach((ext) => { + const filename = `file${ext}`; + const actual = removeExtension(filename); + assert.deepEqual(actual, "file"); + }); + }); + it('can remove Binary file extension', () => { + ALL_BINARY.forEach((ext) => { const filename = `file${ext}`; const actual = removeExtension(filename); assert.deepEqual(actual, "file"); @@ -33,4 +37,34 @@ describe('Test of File-Types', () => { assert.deepEqual(filename, "file"); }); }); + describe('Test of sourceOrBinaryTypeFromFilename()', () => { + it('can extract Source file extension', () => { + ALL_SOURCE.forEach((ext) => { + const filename = `file${ext}`; + const actual = sourceOrBinaryTypeFromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); + it('can extract Binary file extension', () => { + ALL_BINARY.forEach((ext) => { + const filename = `file${ext}`; + const actual = sourceOrBinaryTypeFromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); + it('returns null for unmatched file extension', () => { + const ext = ".cpp"; + assert.isFalse((Object.values(ALL) as string[]).includes(ext)); + const filename = `file${ext}`; + const actual = sourceOrBinaryTypeFromFilename(filename); + assert.isNull(actual); + }); + it('can extract upper case file extension', () => { + const ext = ALL[0]; + const upperCaseExt = ext.toUpperCase(); + const filename = `file${upperCaseExt}`; + const actual = sourceOrBinaryTypeFromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); }); \ No newline at end of file From d08b6933f401c7ddffe6f9c0a49119156ad08044 Mon Sep 17 00:00:00 2001 From: "Dr Mark C. Sinclair" Date: Thu, 28 Nov 2024 03:16:17 +0000 Subject: [PATCH 06/31] chore(common/web): add four test cases for sourceTypeFromFilename() --- common/web/types/test/util/test-file-types.ts | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/common/web/types/test/util/test-file-types.ts b/common/web/types/test/util/test-file-types.ts index 962f69955b2..8b8605e3bb9 100644 --- a/common/web/types/test/util/test-file-types.ts +++ b/common/web/types/test/util/test-file-types.ts @@ -13,7 +13,8 @@ import { ALL_SOURCE, ALL_BINARY, removeExtension, - sourceOrBinaryTypeFromFilename + sourceOrBinaryTypeFromFilename, + sourceTypeFromFilename, } from '../../src/util/file-types.js'; describe('Test of File-Types', () => { @@ -67,4 +68,34 @@ describe('Test of File-Types', () => { assert.deepEqual(actual, ext); }); }); + describe('Test of sourceTypeFromFilename()', () => { + it('can extract Source file extension', () => { + ALL_SOURCE.forEach((ext) => { + const filename = `file${ext}`; + const actual = sourceTypeFromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); + it('returns null for a Binary file extension', () => { + ALL_BINARY.forEach((ext) => { + const filename = `file${ext}`; + const actual = sourceTypeFromFilename(filename); + assert.isNull(actual); + }); + }); + it('returns null for unmatched file extension', () => { + const ext = ".cpp"; + assert.isFalse((Object.values(ALL) as string[]).includes(ext)); + const filename = `file${ext}`; + const actual = sourceTypeFromFilename(filename); + assert.isNull(actual); + }); + it('can extract upper case file extension', () => { + const ext = ALL_SOURCE[0]; + const upperCaseExt = ext.toUpperCase(); + const filename = `file${upperCaseExt}`; + const actual = sourceTypeFromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); }); \ No newline at end of file From 5a56d0f15993b93be68cecd35edb8503d55ff8a1 Mon Sep 17 00:00:00 2001 From: "Dr Mark C. Sinclair" Date: Thu, 28 Nov 2024 03:21:16 +0000 Subject: [PATCH 07/31] chore(common/web): add four test cases for binaryTypeFromFilename() --- common/web/types/test/util/test-file-types.ts | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/common/web/types/test/util/test-file-types.ts b/common/web/types/test/util/test-file-types.ts index 8b8605e3bb9..787bb80d59c 100644 --- a/common/web/types/test/util/test-file-types.ts +++ b/common/web/types/test/util/test-file-types.ts @@ -15,6 +15,7 @@ import { removeExtension, sourceOrBinaryTypeFromFilename, sourceTypeFromFilename, + binaryTypeFromFilename, } from '../../src/util/file-types.js'; describe('Test of File-Types', () => { @@ -85,7 +86,7 @@ describe('Test of File-Types', () => { }); it('returns null for unmatched file extension', () => { const ext = ".cpp"; - assert.isFalse((Object.values(ALL) as string[]).includes(ext)); + assert.isFalse((Object.values(ALL_SOURCE) as string[]).includes(ext)); const filename = `file${ext}`; const actual = sourceTypeFromFilename(filename); assert.isNull(actual); @@ -98,4 +99,34 @@ describe('Test of File-Types', () => { assert.deepEqual(actual, ext); }); }); + describe('Test of binaryTypeFromFilename()', () => { + it('returns null for a Source file extension', () => { + ALL_SOURCE.forEach((ext) => { + const filename = `file${ext}`; + const actual = binaryTypeFromFilename(filename); + assert.isNull(actual); + }); + }); + it('can extract Binary file extension', () => { + ALL_BINARY.forEach((ext) => { + const filename = `file${ext}`; + const actual = binaryTypeFromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); + it('returns null for unmatched file extension', () => { + const ext = ".cpp"; + assert.isFalse((Object.values(ALL_BINARY) as string[]).includes(ext)); + const filename = `file${ext}`; + const actual = binaryTypeFromFilename(filename); + assert.isNull(actual); + }); + it('can extract upper case file extension', () => { + const ext = ALL_BINARY[0]; + const upperCaseExt = ext.toUpperCase(); + const filename = `file${upperCaseExt}`; + const actual = binaryTypeFromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); }); \ No newline at end of file From 589698b799cbe7314aae1a6dd57807f2075a3e65 Mon Sep 17 00:00:00 2001 From: "Dr Mark C. Sinclair" Date: Thu, 28 Nov 2024 05:11:14 +0000 Subject: [PATCH 08/31] chore(common/web): add three test cases for filenameIs() --- common/web/types/test/util/test-file-types.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/common/web/types/test/util/test-file-types.ts b/common/web/types/test/util/test-file-types.ts index 787bb80d59c..7eb24cee1b4 100644 --- a/common/web/types/test/util/test-file-types.ts +++ b/common/web/types/test/util/test-file-types.ts @@ -12,10 +12,12 @@ import { ALL, ALL_SOURCE, ALL_BINARY, + Binary, removeExtension, sourceOrBinaryTypeFromFilename, sourceTypeFromFilename, binaryTypeFromFilename, + filenameIs, } from '../../src/util/file-types.js'; describe('Test of File-Types', () => { @@ -129,4 +131,31 @@ describe('Test of File-Types', () => { assert.deepEqual(actual, ext); }); }); + describe('Test of filenameIs()', () => { + it('can identify Source file extension', () => { + ALL_SOURCE.forEach((ext) => { + const filename = `file${ext}`; + const actual = filenameIs(filename, ext); + assert.isTrue(actual); + }); + }); + it('can identify Binary file extension', () => { + ALL_BINARY.forEach((ext) => { + const filename = `file${ext}`; + if (ext == Binary.Model) { // Special case for .model.js + const actual = filenameIs(filename, Binary.WebKeyboard); + assert.isFalse(actual); + } + const actual = filenameIs(filename, ext); + assert.isTrue(actual); + }); + }); + it('can identify upper case file extension', () => { + const ext = ALL[0]; + const upperCaseExt = ext.toUpperCase(); + const filename = `file${upperCaseExt}`; + const actual = filenameIs(filename, ext); + assert.isTrue(actual); + }); + }); }); \ No newline at end of file From 920b702eef3a800545d58faca47032363d6c18b2 Mon Sep 17 00:00:00 2001 From: "Dr Mark C. Sinclair" Date: Thu, 28 Nov 2024 05:56:22 +0000 Subject: [PATCH 09/31] chore(common/web): add five test cases for replaceExtension(), two commented out as failing --- common/web/types/test/util/test-file-types.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/common/web/types/test/util/test-file-types.ts b/common/web/types/test/util/test-file-types.ts index 7eb24cee1b4..91356999043 100644 --- a/common/web/types/test/util/test-file-types.ts +++ b/common/web/types/test/util/test-file-types.ts @@ -18,6 +18,7 @@ import { sourceTypeFromFilename, binaryTypeFromFilename, filenameIs, + replaceExtension, } from '../../src/util/file-types.js'; describe('Test of File-Types', () => { @@ -158,4 +159,40 @@ describe('Test of File-Types', () => { assert.isTrue(actual); }); }); + describe('Test of replaceExtension()', () => { + it('can replace an extension', () => { + const oldExt = ".cpp"; + const newExt = ".js"; + const oldFilename = `file${oldExt}`; + const newFilename = `file${newExt}`; + const actual = replaceExtension(oldFilename, oldExt, newExt); + assert.deepEqual(actual, newFilename); + }); + it('should return null for incorrect old extension (too short)', () => { + const oldExt = ".ts"; + const newExt = ".js"; + const oldFilename = `file.c`; + const actual = replaceExtension(oldFilename, oldExt, newExt); + assert.isNull(actual); + }); + it('should return null for incorrect old extension (too long)', () => { + const oldExt = ".ts"; + const newExt = ".js"; + const oldFilename = `file.cpp`; + const actual = replaceExtension(oldFilename, oldExt, newExt); + assert.isNull(actual); + }); + // it('should return null for null old extension', () => { + // const newExt = ".js"; + // const oldFilename = `file.ts`; + // const actual = replaceExtension(oldFilename, null, newExt); + // assert.isNull(actual); + // }); + // it('should return null for null new extension', () => { + // const oldExt = ".ts"; + // const oldFilename = `file.ts`; + // const actual = replaceExtension(oldFilename, oldExt, null); + // assert.isNull(actual); + // }); + }); }); \ No newline at end of file From 9fd2d4a674d1e0ee5988256c3795ef3cf4db3ab9 Mon Sep 17 00:00:00 2001 From: "Dr Mark C. Sinclair" Date: Thu, 28 Nov 2024 06:07:21 +0000 Subject: [PATCH 10/31] chore(common/web): add five test cases for fromFilename() --- common/web/types/test/util/test-file-types.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/common/web/types/test/util/test-file-types.ts b/common/web/types/test/util/test-file-types.ts index 91356999043..9450bba2e7d 100644 --- a/common/web/types/test/util/test-file-types.ts +++ b/common/web/types/test/util/test-file-types.ts @@ -13,6 +13,7 @@ import { ALL_SOURCE, ALL_BINARY, Binary, + fromFilename, removeExtension, sourceOrBinaryTypeFromFilename, sourceTypeFromFilename, @@ -22,6 +23,41 @@ import { } from '../../src/util/file-types.js'; describe('Test of File-Types', () => { + describe('Test of fromFilename()', () => { + it('can extract Source file extension', () => { + ALL_SOURCE.forEach((ext) => { + const filename = `file${ext}`; + const actual = fromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); + it('can extract Binary file extension', () => { + ALL_BINARY.forEach((ext) => { + const filename = `file${ext}`; + const actual = fromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); + it('can extract unmatched file extension', () => { + const ext = ".cpp"; + assert.isFalse((Object.values(ALL_SOURCE) as string[]).includes(ext)); + const filename = `file${ext}`; + const actual = fromFilename(filename); + assert.deepEqual(actual, ext); + }); + it('returns empty string for no file extension', () => { + const filename = `file`; + const actual = fromFilename(filename); + assert.deepEqual(actual, ""); + }); + it('can extract upper case file extension', () => { + const ext = ALL_SOURCE[0]; + const upperCaseExt = ext.toUpperCase(); + const filename = `file${upperCaseExt}`; + const actual = fromFilename(filename); + assert.deepEqual(actual, ext); + }); + }); describe('Test of removeExtension()', () => { it('can remove Source file extension', () => { ALL_SOURCE.forEach((ext) => { From b923e13930c744cec9578111d7c251afef59a506 Mon Sep 17 00:00:00 2001 From: "Dr Mark C. Sinclair" Date: Fri, 29 Nov 2024 03:42:40 +0000 Subject: [PATCH 11/31] chore(common/web): add blank line at end of file --- common/web/types/test/util/test-file-types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/web/types/test/util/test-file-types.ts b/common/web/types/test/util/test-file-types.ts index 9450bba2e7d..3f4d6870918 100644 --- a/common/web/types/test/util/test-file-types.ts +++ b/common/web/types/test/util/test-file-types.ts @@ -1,7 +1,7 @@ /* * Keyman is copyright (C) SIL Global. MIT License. * - * Created by Dr Mark C. Sinclair on 2024-11-28 + * Created by Dr Mark C. Sinclair on 2024-11-29 * * Test code for file-types.ts */ @@ -231,4 +231,4 @@ describe('Test of File-Types', () => { // assert.isNull(actual); // }); }); -}); \ No newline at end of file +}); From 01a47c3ea967bda8f4cefa2373d47e099d6dc8a7 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 29 Nov 2024 13:48:21 -0600 Subject: [PATCH 12/31] feat(developer): support local imports - added a new reader callback option, localImportsPaths - due to the CLDR issue #12749 use base="" - add tests - some bugfixes in import messages - add an ImportStatus section to determine if something is a local import Fixes: #10649 --- .../common/web/utils/src/common-messages.ts | 8 ++-- .../ldml-keyboard/ldml-keyboard-xml-reader.ts | 36 +++++++++++++++--- .../types/ldml-keyboard/ldml-keyboard-xml.ts | 8 +++- .../fixtures/ldml-keyboard/import-local.xml | 7 ++++ .../ldml-keyboard/invalid-import-local.xml | 7 ++++ .../keys-Zyyy-morepunctuation.xml | 6 +++ .../test/helpers/reader-callback-test.ts | 21 +++++++++-- .../kmx/ldml-keyboard-xml-reader.tests.ts | 37 ++++++++++++++++++- 8 files changed, 114 insertions(+), 16 deletions(-) create mode 100644 developer/src/common/web/utils/test/fixtures/ldml-keyboard/import-local.xml create mode 100644 developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-import-local.xml create mode 100644 developer/src/common/web/utils/test/fixtures/ldml-keyboard/keys-Zyyy-morepunctuation.xml diff --git a/developer/src/common/web/utils/src/common-messages.ts b/developer/src/common/web/utils/src/common-messages.ts index 724dadf9cd8..c35ae136fde 100644 --- a/developer/src/common/web/utils/src/common-messages.ts +++ b/developer/src/common/web/utils/src/common-messages.ts @@ -20,17 +20,19 @@ export class CommonTypesMessages { static ERROR_ImportInvalidBase = SevError | 0x0002; static Error_ImportInvalidBase = (o: { base: string, path: string, subtag: string }) => m(this.ERROR_ImportInvalidBase, - `Import element with base ${def(o.base)} is unsupported. Only ${constants.cldr_import_base} is supported.`); + `Import element with base ${def(o.base)} is unsupported. Only ${constants.cldr_import_base} or empty (for local) is supported.`); static ERROR_ImportInvalidPath = SevError | 0x0003; static Error_ImportInvalidPath = (o: { base: string, path: string, subtag: string }) => m(this.ERROR_ImportInvalidPath, - `Import element with invalid path ${def(o.path)}: expected the form '${constants.cldr_version_latest}/*.xml`); + `Import element with invalid path ${def(o.path)}: expected the form '${constants.cldr_version_latest}/*.xml'`); static ERROR_ImportReadFail = SevError | 0x0004; static Error_ImportReadFail = (o: { base: string, path: string, subtag: string }) => m(this.ERROR_ImportReadFail, - `Import could not read data with path ${def(o.path)}: expected the form '${constants.cldr_version_latest}/*.xml'`); + `Import could not read data with path ${def(o.path)}`, + // for CLDR, give guidance on the suggested path + (o.base === constants.cldr_import_base) ? `expected the form '${constants.cldr_version_latest}/*.xml' for ${o.base}` : undefined); static ERROR_ImportWrongRoot = SevError | 0x0005; static Error_ImportWrongRoot = (o: { base: string, path: string, subtag: string }) => diff --git a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts index 41d4c699f40..559a7c421ff 100644 --- a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -19,7 +19,10 @@ interface NameAndProps { }; export class LDMLKeyboardXMLSourceFileReaderOptions { + /** path to the CLDR imports */ importsPath: string; + /** ordered list of paths for local imports */ + localImportsPaths: string[]; }; export class LDMLKeyboardXMLSourceFileReader { @@ -35,6 +38,16 @@ export class LDMLKeyboardXMLSourceFileReader { return this.callbacks.loadFile(importPath); } + readLocalImportFile(path: string): Uint8Array { + // try each of the local imports paths + for (const localPath of this.options.localImportsPaths) { + const importPath = this.callbacks.resolveFilename(localPath, path); + const data = this.callbacks.loadFile(importPath); + if (data) return data; + } + return null; // was not able to load from any of the paths + } + /** * xml2js will not place single-entry objects into arrays. * Easiest way to fix this is to box them ourselves as needed @@ -203,16 +216,24 @@ export class LDMLKeyboardXMLSourceFileReader { */ private resolveOneImport(obj: any, subtag: string, asImport: LKImport, implied? : boolean) : boolean { const { base, path } = asImport; - if (base !== constants.cldr_import_base) { + if (base && base !== constants.cldr_import_base) { this.callbacks.reportMessage(CommonTypesMessages.Error_ImportInvalidBase({base, path, subtag})); return false; } - const paths = path.split('/'); - if (paths[0] == '' || paths[1] == '' || paths.length !== 2) { - this.callbacks.reportMessage(CommonTypesMessages.Error_ImportInvalidPath({base, path, subtag})); - return false; + let importData: Uint8Array; + + if (base === constants.cldr_import_base) { + // CLDR import + const paths = path.split('/'); + if (paths[0] == '' || paths[1] == '' || paths.length !== 2) { + this.callbacks.reportMessage(CommonTypesMessages.Error_ImportInvalidPath({base, path, subtag})); + return false; + } + importData = this.readImportFile(paths[0], paths[1]); + } else { + // local import + importData = this.readLocalImportFile(path); } - const importData: Uint8Array = this.readImportFile(paths[0], paths[1]); if (!importData || !importData.length) { this.callbacks.reportMessage(CommonTypesMessages.Error_ImportReadFail({base, path, subtag})); return false; @@ -241,6 +262,9 @@ export class LDMLKeyboardXMLSourceFileReader { // mark all children as an implied import subsubval.forEach(o => o[ImportStatus.impliedImport] = basePath); } + if (!base) { + subsubval.forEach(o => o[ImportStatus.localImport] = path); + } if (!obj[subsubtag]) { obj[subsubtag] = []; // start with empty array diff --git a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml.ts b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml.ts index 696194573f8..62322ae25e2 100644 --- a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml.ts +++ b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml.ts @@ -38,7 +38,7 @@ export interface LKImport { /** * import base, currently `cldr` is supported */ - base: string; + base?: string; /** * path to imported resource, of the form `45/*.xml` */ @@ -199,6 +199,8 @@ export class ImportStatus { static impliedImport = Symbol('LDML implied import'); /** item came in via import */ static import = Symbol('LDML import'); + /** item came in via local (not CLDR) import */ + static localImport = Symbol('LDML local import'); /** @returns true if the object was loaded through an implied import */ static isImpliedImport(o : any) : boolean { @@ -208,5 +210,9 @@ export class ImportStatus { static isImport(o : any) : boolean { return o && !!o[ImportStatus.import]; } + /** @returns true if the object was loaded through an explicit import */ + static isLocalImport(o : any) : boolean { + return o && !!o[ImportStatus.localImport]; + } }; diff --git a/developer/src/common/web/utils/test/fixtures/ldml-keyboard/import-local.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/import-local.xml new file mode 100644 index 00000000000..b3cc7cabdff --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/import-local.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-import-local.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-import-local.xml new file mode 100644 index 00000000000..e8ac9dc9640 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/invalid-import-local.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/developer/src/common/web/utils/test/fixtures/ldml-keyboard/keys-Zyyy-morepunctuation.xml b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/keys-Zyyy-morepunctuation.xml new file mode 100644 index 00000000000..768e81e7cd2 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/ldml-keyboard/keys-Zyyy-morepunctuation.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/developer/src/common/web/utils/test/helpers/reader-callback-test.ts b/developer/src/common/web/utils/test/helpers/reader-callback-test.ts index c67d49dfdb4..8c2e4295209 100644 --- a/developer/src/common/web/utils/test/helpers/reader-callback-test.ts +++ b/developer/src/common/web/utils/test/helpers/reader-callback-test.ts @@ -7,9 +7,11 @@ import { LDMLKeyboardXMLSourceFile } from '../../src/types/ldml-keyboard/ldml-ke import { LDMLKeyboardTestDataXMLSourceFile } from '../../src/types/ldml-keyboard/ldml-keyboard-testdata-xml.js'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { fileURLToPath } from 'url'; +import { dirname, sep } from 'node:path'; const readerOptions: LDMLKeyboardXMLSourceFileReaderOptions = { - importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)) + importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)), + localImportsPaths: [], }; export interface CompilationCase { @@ -76,18 +78,29 @@ export interface TestDataCase { export function testReaderCases(cases : CompilationCase[]) { // we need our own callbacks rather than using the global so messages don't get mixed const callbacks = new TestCompilerCallbacks(); - const reader = new LDMLKeyboardXMLSourceFileReader(readerOptions, callbacks); for (const testcase of cases) { const expectFailure = testcase.throws || !!(testcase.errors); // if true, we expect this to fail const testHeading = expectFailure ? `should fail to load: ${testcase.subpath}`: `should load: ${testcase.subpath}`; it(testHeading, function () { callbacks.clear(); - - const data = loadFile(makePathToFixture('ldml-keyboard', testcase.subpath)); + const path = makePathToFixture('ldml-keyboard', testcase.subpath); + // update readerOptions to point to the source dir. Need a trailing separator. + readerOptions.localImportsPaths = [ dirname(path) + sep ]; + const reader = new LDMLKeyboardXMLSourceFileReader(readerOptions, callbacks); + const data = loadFile(path); assert.ok(data, `reading ${testcase.subpath}`); const source = reader.load(data); if (!testcase.loadfail) { + if (!source) { + // print any loading errs here + if (testcase.warnings) { + assert.includeDeepMembers(callbacks.messages, testcase.warnings, 'expected warnings to be included'); + } else if (!expectFailure) { + // no warnings, so expect zero messages + assert.deepEqual(callbacks.messages, [], 'expected zero messages'); + } + } assert.ok(source, `loading ${testcase.subpath}`); } else { assert.notOk(source, `loading ${testcase.subpath} (expected failure)`); diff --git a/developer/src/common/web/utils/test/kmx/ldml-keyboard-xml-reader.tests.ts b/developer/src/common/web/utils/test/kmx/ldml-keyboard-xml-reader.tests.ts index 17c35660245..e3e6fc1e9fb 100644 --- a/developer/src/common/web/utils/test/kmx/ldml-keyboard-xml-reader.tests.ts +++ b/developer/src/common/web/utils/test/kmx/ldml-keyboard-xml-reader.tests.ts @@ -119,11 +119,33 @@ describe('ldml keyboard xml reader tests', function () { // 'hash' is an import but not implied assert.isFalse(ImportStatus.isImpliedImport(k.find(({id}) => id === 'hash'))); assert.isTrue(ImportStatus.isImport(k.find(({id}) => id === 'hash'))); + assert.isFalse(ImportStatus.isLocalImport(k.find(({id}) => id === 'hash'))); // 'zz' is not imported assert.isFalse(ImportStatus.isImpliedImport(k.find(({id}) => id === 'zz'))); assert.isFalse(ImportStatus.isImport(k.find(({id}) => id === 'zz'))); }, }, + { + subpath: 'import-local.xml', + callback: (data, source, subpath, callbacks) => { + assert.ok(source?.keyboard3?.keys); + const k = pluckKeysFromKeybag(source?.keyboard3?.keys.key, ['interrobang','snail']); + assert.sameDeepOrderedMembers(k.map((entry) => { + // Drop the Symbol members from the returned keys; assertions may expect their presence. + return { + id: entry.id, + output: entry.output + }; + }), [ + { id: 'interrobang', output: '‽' }, + { id: 'snail', output: '@' }, + ]); + // all of the keys are implied imports here + assert.isFalse(ImportStatus.isImpliedImport(source?.keyboard3?.keys.key.find(({id}) => id === 'snail'))); + assert.isTrue(ImportStatus.isImport(source?.keyboard3?.keys.key.find(({id}) => id === 'snail'))); + assert.isTrue(ImportStatus.isLocalImport(source?.keyboard3?.keys.key.find(({id}) => id === 'snail'))); + }, + }, { subpath: 'invalid-import-base.xml', loadfail: true, @@ -135,12 +157,23 @@ describe('ldml keyboard xml reader tests', function () { }), ], }, + { + subpath: 'invalid-import-local.xml', + loadfail: true, + errors: [ + CommonTypesMessages.Error_ImportReadFail({ + base: undefined, + path: 'keys-Zyyy-DOESNOTEXIST.xml', + subtag: 'keys' + }), + ], + }, { subpath: 'invalid-import-path.xml', loadfail: true, errors: [ CommonTypesMessages.Error_ImportInvalidPath({ - base: null, + base: 'cldr', path: '45/too/many/slashes/leading/to/nothing-Zxxx-does-not-exist.xml', subtag: null, }), @@ -151,7 +184,7 @@ describe('ldml keyboard xml reader tests', function () { loadfail: true, errors: [ CommonTypesMessages.Error_ImportReadFail({ - base: null, + base: 'cldr', path: '45/none-Zxxx-does-not-exist.xml', subtag: null, }), From e99f6ac46a5d2d4b983fd0319e68b1c641f50a49 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 29 Nov 2024 13:56:41 -0600 Subject: [PATCH 13/31] feat(developer): support local imports in kmc - basic changes to support local imports Fixes: #10649 --- .../src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts | 4 +++- developer/src/kmc/src/commands/buildTestData/index.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/developer/src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts b/developer/src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts index 6ac98c9b92e..643f31e9596 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts @@ -4,6 +4,7 @@ import { CompilerOptions, CompilerCallbacks } from '@keymanapp/developer-utils'; import { LDMLKeyboardXMLSourceFileReader } from '@keymanapp/developer-utils'; import { BuildActivity } from './BuildActivity.js'; import { fileURLToPath } from 'url'; +import { dirname, sep } from 'node:path'; export class BuildLdmlKeyboard extends BuildActivity { public get name(): string { return 'LDML keyboard'; } @@ -13,7 +14,8 @@ export class BuildLdmlKeyboard extends BuildActivity { public async build(infile: string, outfile: string, callbacks: CompilerCallbacks, options: CompilerOptions): Promise { // TODO-LDML: consider hardware vs touch -- touch-only layout will not have a .kvk const ldmlCompilerOptions: kmcLdml.LdmlCompilerOptions = {...options, readerOptions: { - importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)) + importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)), + localImportsPaths: [ dirname(infile) + sep ], // local dir }}; const compiler = new kmcLdml.LdmlKeyboardCompiler(); return await super.runCompiler(compiler, infile, outfile, callbacks, ldmlCompilerOptions); diff --git a/developer/src/kmc/src/commands/buildTestData/index.ts b/developer/src/kmc/src/commands/buildTestData/index.ts index c958673c2fa..39b149765c5 100644 --- a/developer/src/kmc/src/commands/buildTestData/index.ts +++ b/developer/src/kmc/src/commands/buildTestData/index.ts @@ -7,6 +7,7 @@ import { fileURLToPath } from 'url'; import { CommandLineBaseOptions } from 'src/util/baseOptions.js'; import { exitProcess } from '../../util/sysexits.js'; import { InfrastructureMessages } from '../../messages/infrastructureMessages.js'; +import { dirname, sep } from 'node:path'; export async function buildTestData(infile: string, _options: any, commander: any): Promise { const options: CommandLineBaseOptions = commander.optsWithGlobals(); @@ -17,7 +18,8 @@ export async function buildTestData(infile: string, _options: any, commander: an saveDebug: false, shouldAddCompilerVersion: false, readerOptions: { - importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)) + importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)), + localImportsPaths: [ dirname(infile) + sep ], // local dir } }; From 01fed4b2abb8b68e9842fe6119d69da0289febab Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 29 Nov 2024 14:11:29 -0600 Subject: [PATCH 14/31] feat(developer): test local imports in kmc-ldml - also improve callback visibility when XML fails to load Fixes: #10649 --- .../test/fixtures/sections/keys/import-local.xml | 16 ++++++++++++++++ .../sections/keys/keys-Zyyy-morepunctuation.xml | 6 ++++++ developer/src/kmc-ldml/test/helpers/index.ts | 9 ++++++++- developer/src/kmc-ldml/test/keys.tests.ts | 13 +++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 developer/src/kmc-ldml/test/fixtures/sections/keys/import-local.xml create mode 100644 developer/src/kmc-ldml/test/fixtures/sections/keys/keys-Zyyy-morepunctuation.xml diff --git a/developer/src/kmc-ldml/test/fixtures/sections/keys/import-local.xml b/developer/src/kmc-ldml/test/fixtures/sections/keys/import-local.xml new file mode 100644 index 00000000000..d97d711301d --- /dev/null +++ b/developer/src/kmc-ldml/test/fixtures/sections/keys/import-local.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-ldml/test/fixtures/sections/keys/keys-Zyyy-morepunctuation.xml b/developer/src/kmc-ldml/test/fixtures/sections/keys/keys-Zyyy-morepunctuation.xml new file mode 100644 index 00000000000..768e81e7cd2 --- /dev/null +++ b/developer/src/kmc-ldml/test/fixtures/sections/keys/keys-Zyyy-morepunctuation.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/developer/src/kmc-ldml/test/helpers/index.ts b/developer/src/kmc-ldml/test/helpers/index.ts index 3bb67278fbe..283459688fe 100644 --- a/developer/src/kmc-ldml/test/helpers/index.ts +++ b/developer/src/kmc-ldml/test/helpers/index.ts @@ -38,7 +38,8 @@ export const compilerTestCallbacks = new TestCompilerCallbacks(); export const compilerTestOptions: LdmlCompilerOptions = { readerOptions: { - importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)) + importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)), + localImportsPaths: [], // will be fixed up in loadSectionFixture } }; @@ -59,8 +60,14 @@ export async function loadSectionFixture(compilerClass: SectionCompilerNew, file const data = callbacks.loadFile(inputFilename); assert.isNotNull(data, `Failed to read file ${inputFilename}`); + compilerTestOptions.readerOptions.localImportsPaths = [ path.dirname(inputFilename) + path.sep ]; + const reader = new LDMLKeyboardXMLSourceFileReader(compilerTestOptions.readerOptions, callbacks); const source = reader.load(data); + if (!source) { + // print any callbacks here + assert.sameDeepMembers(callbacks.messages, [], `Errors loading ${inputFilename}`); + } assert.isNotNull(source, `Failed to load XML from ${inputFilename}`); if (!reader.validate(source)) { diff --git a/developer/src/kmc-ldml/test/keys.tests.ts b/developer/src/kmc-ldml/test/keys.tests.ts index b639c194cc3..cfb37e6baf5 100644 --- a/developer/src/kmc-ldml/test/keys.tests.ts +++ b/developer/src/kmc-ldml/test/keys.tests.ts @@ -199,6 +199,19 @@ describe('keys', function () { assert.equal(flickw.flicks[0].keyId.value, 'dd'); }, }, + { + subpath: 'sections/keys/import-local.xml', + callback: (keys, subpath, callbacks) => { + assert.isNotNull(keys); + assert.equal((keys).keys.length, 2 + KeysCompiler.reserved_count); + const [snail] = (keys).keys.filter(({ id }) => id.value === 'snail'); + assert.ok(snail,`Missing the snail`); + assert.equal(snail.to.value, `@`, `Snail's value`); + const [interrobang] = (keys).keys.filter(({ id }) => id.value === 'interrobang'); + assert.ok(interrobang,`Missing the interrobang`); + assert.equal(interrobang.to.value, `‽`, `Interrobang's value`); + }, + }, ], keysDependencies); }); From b1b6587a7994810e19fd3d66ed01a9e351638e75 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 29 Nov 2024 14:19:35 -0600 Subject: [PATCH 15/31] feat(core): test local imports in kmc Fixes: #10649 --- .../unit/ldml/keyboards/k_015_importlocal.xml | 22 +++++++++++++++++++ .../keyboards/keys-Zyyy-morepunctuation.xml | 6 +++++ core/tests/unit/ldml/keyboards/meson.build | 1 + 3 files changed, 29 insertions(+) create mode 100644 core/tests/unit/ldml/keyboards/k_015_importlocal.xml create mode 100644 core/tests/unit/ldml/keyboards/keys-Zyyy-morepunctuation.xml diff --git a/core/tests/unit/ldml/keyboards/k_015_importlocal.xml b/core/tests/unit/ldml/keyboards/k_015_importlocal.xml new file mode 100644 index 00000000000..36bd5c6942f --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_015_importlocal.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/keys-Zyyy-morepunctuation.xml b/core/tests/unit/ldml/keyboards/keys-Zyyy-morepunctuation.xml new file mode 100644 index 00000000000..768e81e7cd2 --- /dev/null +++ b/core/tests/unit/ldml/keyboards/keys-Zyyy-morepunctuation.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/core/tests/unit/ldml/keyboards/meson.build b/core/tests/unit/ldml/keyboards/meson.build index 15f458c587f..949acb78996 100644 --- a/core/tests/unit/ldml/keyboards/meson.build +++ b/core/tests/unit/ldml/keyboards/meson.build @@ -26,6 +26,7 @@ tests_without_testdata = [ 'k_010_mt', 'k_011_mt_iso', 'k_012_other', + 'k_015_importlocal', 'k_030_transform_plus', 'k_100_keytest', 'k_101_keytest', From 48da8fd613272148f9acb7ba70b0396dee39d755 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Mon, 2 Dec 2024 11:55:13 -0600 Subject: [PATCH 16/31] Apply suggestions from code review Co-authored-by: Marc Durdin --- developer/src/common/web/utils/src/common-messages.ts | 2 +- .../src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts | 8 +++++--- .../utils/src/types/ldml-keyboard/ldml-keyboard-xml.ts | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/developer/src/common/web/utils/src/common-messages.ts b/developer/src/common/web/utils/src/common-messages.ts index c35ae136fde..6eca08d37c4 100644 --- a/developer/src/common/web/utils/src/common-messages.ts +++ b/developer/src/common/web/utils/src/common-messages.ts @@ -20,7 +20,7 @@ export class CommonTypesMessages { static ERROR_ImportInvalidBase = SevError | 0x0002; static Error_ImportInvalidBase = (o: { base: string, path: string, subtag: string }) => m(this.ERROR_ImportInvalidBase, - `Import element with base ${def(o.base)} is unsupported. Only ${constants.cldr_import_base} or empty (for local) is supported.`); + `Import element with base ${def(o.base)} is unsupported. Only ${constants.cldr_import_base} or empty (for local) are supported.`); static ERROR_ImportInvalidPath = SevError | 0x0003; static Error_ImportInvalidPath = (o: { base: string, path: string, subtag: string }) => diff --git a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts index 559a7c421ff..2a5754b45b2 100644 --- a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -42,8 +42,9 @@ export class LDMLKeyboardXMLSourceFileReader { // try each of the local imports paths for (const localPath of this.options.localImportsPaths) { const importPath = this.callbacks.resolveFilename(localPath, path); - const data = this.callbacks.loadFile(importPath); - if (data) return data; + if(this.callbacks.fs.existsSync(importPath)) { + return this.callbacks.loadFile(importPath); + } } return null; // was not able to load from any of the paths } @@ -216,6 +217,7 @@ export class LDMLKeyboardXMLSourceFileReader { */ private resolveOneImport(obj: any, subtag: string, asImport: LKImport, implied? : boolean) : boolean { const { base, path } = asImport; + // If base is not an empty string (or null/undefined), then it must be 'cldr' if (base && base !== constants.cldr_import_base) { this.callbacks.reportMessage(CommonTypesMessages.Error_ImportInvalidBase({base, path, subtag})); return false; @@ -262,7 +264,7 @@ export class LDMLKeyboardXMLSourceFileReader { // mark all children as an implied import subsubval.forEach(o => o[ImportStatus.impliedImport] = basePath); } - if (!base) { + if (base !== constants.cldr_import_base) { subsubval.forEach(o => o[ImportStatus.localImport] = path); } diff --git a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml.ts b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml.ts index 62322ae25e2..cfff0fd433b 100644 --- a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml.ts +++ b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml.ts @@ -38,7 +38,7 @@ export interface LKImport { /** * import base, currently `cldr` is supported */ - base?: string; + base?: 'cldr'; /** * path to imported resource, of the form `45/*.xml` */ From f4f03551096c384d2ea1be5cf591953f8ce56c64 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Mon, 2 Dec 2024 15:40:54 -0600 Subject: [PATCH 17/31] feat(developer): local imports directory fixes - use path.join instead of path.resolve - rename importsPath to cldrImportsPath - remove k_015_importlocal from core, as it duplicated tests in common and developer Fixes: #10649 --- .../unit/ldml/keyboards/k_015_importlocal.xml | 22 ------------------- .../keyboards/keys-Zyyy-morepunctuation.xml | 6 ----- core/tests/unit/ldml/keyboards/meson.build | 1 - .../ldml-keyboard/ldml-keyboard-xml-reader.ts | 6 ++--- .../test/helpers/reader-callback-test.ts | 8 +++---- developer/src/kmc-ldml/test/helpers/index.ts | 4 ++-- .../buildClasses/BuildLdmlKeyboard.ts | 6 ++--- .../kmc/src/commands/buildTestData/index.ts | 6 ++--- 8 files changed, 15 insertions(+), 44 deletions(-) delete mode 100644 core/tests/unit/ldml/keyboards/k_015_importlocal.xml delete mode 100644 core/tests/unit/ldml/keyboards/keys-Zyyy-morepunctuation.xml diff --git a/core/tests/unit/ldml/keyboards/k_015_importlocal.xml b/core/tests/unit/ldml/keyboards/k_015_importlocal.xml deleted file mode 100644 index 36bd5c6942f..00000000000 --- a/core/tests/unit/ldml/keyboards/k_015_importlocal.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/core/tests/unit/ldml/keyboards/keys-Zyyy-morepunctuation.xml b/core/tests/unit/ldml/keyboards/keys-Zyyy-morepunctuation.xml deleted file mode 100644 index 768e81e7cd2..00000000000 --- a/core/tests/unit/ldml/keyboards/keys-Zyyy-morepunctuation.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/core/tests/unit/ldml/keyboards/meson.build b/core/tests/unit/ldml/keyboards/meson.build index 949acb78996..15f458c587f 100644 --- a/core/tests/unit/ldml/keyboards/meson.build +++ b/core/tests/unit/ldml/keyboards/meson.build @@ -26,7 +26,6 @@ tests_without_testdata = [ 'k_010_mt', 'k_011_mt_iso', 'k_012_other', - 'k_015_importlocal', 'k_030_transform_plus', 'k_100_keytest', 'k_101_keytest', diff --git a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts index 2a5754b45b2..928bb94bef7 100644 --- a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -20,7 +20,7 @@ interface NameAndProps { export class LDMLKeyboardXMLSourceFileReaderOptions { /** path to the CLDR imports */ - importsPath: string; + cldrImportsPath: string; /** ordered list of paths for local imports */ localImportsPaths: string[]; }; @@ -34,14 +34,14 @@ export class LDMLKeyboardXMLSourceFileReader { } readImportFile(version: string, subpath: string): Uint8Array { - const importPath = this.callbacks.resolveFilename(this.options.importsPath, `${version}/${subpath}`); + const importPath = this.callbacks.resolveFilename(this.options.cldrImportsPath, `${version}/${subpath}`); return this.callbacks.loadFile(importPath); } readLocalImportFile(path: string): Uint8Array { // try each of the local imports paths for (const localPath of this.options.localImportsPaths) { - const importPath = this.callbacks.resolveFilename(localPath, path); + const importPath = this.callbacks.path.join(localPath, path); if(this.callbacks.fs.existsSync(importPath)) { return this.callbacks.loadFile(importPath); } diff --git a/developer/src/common/web/utils/test/helpers/reader-callback-test.ts b/developer/src/common/web/utils/test/helpers/reader-callback-test.ts index 8c2e4295209..d865ab2f46a 100644 --- a/developer/src/common/web/utils/test/helpers/reader-callback-test.ts +++ b/developer/src/common/web/utils/test/helpers/reader-callback-test.ts @@ -7,10 +7,10 @@ import { LDMLKeyboardXMLSourceFile } from '../../src/types/ldml-keyboard/ldml-ke import { LDMLKeyboardTestDataXMLSourceFile } from '../../src/types/ldml-keyboard/ldml-keyboard-testdata-xml.js'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { fileURLToPath } from 'url'; -import { dirname, sep } from 'node:path'; +import { dirname } from 'node:path'; const readerOptions: LDMLKeyboardXMLSourceFileReaderOptions = { - importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)), + cldrImportsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)), localImportsPaths: [], }; @@ -85,8 +85,8 @@ export function testReaderCases(cases : CompilationCase[]) { it(testHeading, function () { callbacks.clear(); const path = makePathToFixture('ldml-keyboard', testcase.subpath); - // update readerOptions to point to the source dir. Need a trailing separator. - readerOptions.localImportsPaths = [ dirname(path) + sep ]; + // update readerOptions to point to the source dir. + readerOptions.localImportsPaths = [ dirname(path) ]; const reader = new LDMLKeyboardXMLSourceFileReader(readerOptions, callbacks); const data = loadFile(path); assert.ok(data, `reading ${testcase.subpath}`); diff --git a/developer/src/kmc-ldml/test/helpers/index.ts b/developer/src/kmc-ldml/test/helpers/index.ts index 283459688fe..bd438c4ca11 100644 --- a/developer/src/kmc-ldml/test/helpers/index.ts +++ b/developer/src/kmc-ldml/test/helpers/index.ts @@ -38,7 +38,7 @@ export const compilerTestCallbacks = new TestCompilerCallbacks(); export const compilerTestOptions: LdmlCompilerOptions = { readerOptions: { - importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)), + cldrImportsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)), localImportsPaths: [], // will be fixed up in loadSectionFixture } }; @@ -60,7 +60,7 @@ export async function loadSectionFixture(compilerClass: SectionCompilerNew, file const data = callbacks.loadFile(inputFilename); assert.isNotNull(data, `Failed to read file ${inputFilename}`); - compilerTestOptions.readerOptions.localImportsPaths = [ path.dirname(inputFilename) + path.sep ]; + compilerTestOptions.readerOptions.localImportsPaths = [ path.dirname(inputFilename) ]; const reader = new LDMLKeyboardXMLSourceFileReader(compilerTestOptions.readerOptions, callbacks); const source = reader.load(data); diff --git a/developer/src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts b/developer/src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts index 643f31e9596..6a6300b646b 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildLdmlKeyboard.ts @@ -4,7 +4,7 @@ import { CompilerOptions, CompilerCallbacks } from '@keymanapp/developer-utils'; import { LDMLKeyboardXMLSourceFileReader } from '@keymanapp/developer-utils'; import { BuildActivity } from './BuildActivity.js'; import { fileURLToPath } from 'url'; -import { dirname, sep } from 'node:path'; +import { dirname } from 'node:path'; export class BuildLdmlKeyboard extends BuildActivity { public get name(): string { return 'LDML keyboard'; } @@ -14,8 +14,8 @@ export class BuildLdmlKeyboard extends BuildActivity { public async build(infile: string, outfile: string, callbacks: CompilerCallbacks, options: CompilerOptions): Promise { // TODO-LDML: consider hardware vs touch -- touch-only layout will not have a .kvk const ldmlCompilerOptions: kmcLdml.LdmlCompilerOptions = {...options, readerOptions: { - importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)), - localImportsPaths: [ dirname(infile) + sep ], // local dir + cldrImportsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)), + localImportsPaths: [ dirname(infile) ], // local dir }}; const compiler = new kmcLdml.LdmlKeyboardCompiler(); return await super.runCompiler(compiler, infile, outfile, callbacks, ldmlCompilerOptions); diff --git a/developer/src/kmc/src/commands/buildTestData/index.ts b/developer/src/kmc/src/commands/buildTestData/index.ts index 39b149765c5..5343c81702d 100644 --- a/developer/src/kmc/src/commands/buildTestData/index.ts +++ b/developer/src/kmc/src/commands/buildTestData/index.ts @@ -7,7 +7,7 @@ import { fileURLToPath } from 'url'; import { CommandLineBaseOptions } from 'src/util/baseOptions.js'; import { exitProcess } from '../../util/sysexits.js'; import { InfrastructureMessages } from '../../messages/infrastructureMessages.js'; -import { dirname, sep } from 'node:path'; +import { dirname } from 'node:path'; export async function buildTestData(infile: string, _options: any, commander: any): Promise { const options: CommandLineBaseOptions = commander.optsWithGlobals(); @@ -18,8 +18,8 @@ export async function buildTestData(infile: string, _options: any, commander: an saveDebug: false, shouldAddCompilerVersion: false, readerOptions: { - importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)), - localImportsPaths: [ dirname(infile) + sep ], // local dir + cldrImportsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)), + localImportsPaths: [ dirname(infile) ], // local dir } }; From 3dd5207252c2c51785d45f7420a05fd7586c52d2 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Wed, 4 Dec 2024 10:47:12 +0700 Subject: [PATCH 18/31] chore(mac): update SIL logo in About window to include Andika glyph Fixes: #12159 --- .../Keyman4MacIM.xcodeproj/project.pbxproj | 8 ++-- .../Keyman4MacIM/Images/SILInBlue76.png | Bin 2377 -> 0 bytes .../Base.lproj/KMAboutWindowController.xib | 36 ++++++++++-------- .../KMAboutWindow/KMAboutBGView.m | 1 + .../KMAboutWindow/SILAndikaV1RGB.png | Bin 0 -> 4725 bytes 5 files changed, 25 insertions(+), 20 deletions(-) delete mode 100644 mac/Keyman4MacIM/Keyman4MacIM/Images/SILInBlue76.png create mode 100644 mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/SILAndikaV1RGB.png diff --git a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj index 51cf7185509..483998a16e0 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj +++ b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 29BE9D872CA3C21900B67DE7 /* KMModifierMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 29BE9D862CA3C21900B67DE7 /* KMModifierMapping.m */; }; 29C1CDE22C5B2F8B003C23BB /* KMSettingsRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = D861B03E2C5747F70003675E /* KMSettingsRepository.m */; }; 29C1CDE32C5B2F8B003C23BB /* KMDataRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = 29015ABC2C58D86F00CCBB94 /* KMDataRepository.m */; }; + 29DD5F442CFEF88000683388 /* SILAndikaV1RGB.png in Resources */ = {isa = PBXBuildFile; fileRef = 29DD5F432CFEF88000683388 /* SILAndikaV1RGB.png */; }; 37A245C12565DFA6000BBF92 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37A245C02565DFA6000BBF92 /* Assets.xcassets */; }; 37AE5C9D239A7B770086CC7C /* qrcode.min.js in Resources */ = {isa = PBXBuildFile; fileRef = 37AE5C9C239A7B770086CC7C /* qrcode.min.js */; }; 37C2B0CB25FF2C350092E16A /* Help in Resources */ = {isa = PBXBuildFile; fileRef = 37C2B0CA25FF2C340092E16A /* Help */; }; @@ -84,7 +85,6 @@ E213601E2142D7C000A043B7 /* keyman-88.png in Resources */ = {isa = PBXBuildFile; fileRef = E213601D2142D7BF00A043B7 /* keyman-88.png */; }; E21799051FC5B7BC00F2D66A /* KMInputMethodEventHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = E21799041FC5B7BC00F2D66A /* KMInputMethodEventHandler.m */; }; E21E645820C04EA8000D6274 /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = E21E645720C04EA7000D6274 /* logo.png */; }; - E23380FB21407AA100B90591 /* SILInBlue76.png in Resources */ = {isa = PBXBuildFile; fileRef = E23380FA21407AA100B90591 /* SILInBlue76.png */; }; E240F599202DED740000067D /* KMPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = E240F598202DED740000067D /* KMPackage.m */; }; E2585A7F20DD6C3C00CBB994 /* KMMethodEventHandlerTests.kmx in Resources */ = {isa = PBXBuildFile; fileRef = E2585A7E20DD6C3C00CBB994 /* KMMethodEventHandlerTests.kmx */; }; E2585A8120DD7CF100CBB994 /* TestAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E2585A8020DD7CF100CBB994 /* TestAppDelegate.m */; }; @@ -275,6 +275,7 @@ 29D470972C648D5200224B4F /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/KMKeyboardHelpWindowController.strings; sourceTree = ""; }; 29D470982C648D5200224B4F /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/MainMenu.strings; sourceTree = ""; }; 29D470992C648D7100224B4F /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = ""; }; + 29DD5F432CFEF88000683388 /* SILAndikaV1RGB.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = SILAndikaV1RGB.png; sourceTree = ""; }; 29DD8400276C49E20066A16E /* am */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = am; path = am.lproj/KMAboutWindowController.strings; sourceTree = ""; }; 29DD8401276C49E20066A16E /* am */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = am; path = am.lproj/preferences.strings; sourceTree = ""; }; 29DD8402276C49E30066A16E /* am */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = am; path = am.lproj/KMInfoWindowController.strings; sourceTree = ""; }; @@ -379,7 +380,6 @@ E21799031FC5B74D00F2D66A /* KMInputMethodEventHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMInputMethodEventHandler.h; sourceTree = ""; }; E21799041FC5B7BC00F2D66A /* KMInputMethodEventHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMInputMethodEventHandler.m; sourceTree = ""; }; E21E645720C04EA7000D6274 /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = logo.png; path = Keyman4MacIM/KMConfiguration/logo.png; sourceTree = SOURCE_ROOT; }; - E23380FA21407AA100B90591 /* SILInBlue76.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = SILInBlue76.png; path = Keyman4MacIM/Images/SILInBlue76.png; sourceTree = SOURCE_ROOT; }; E240F597202DED300000067D /* KMPackage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMPackage.h; sourceTree = ""; }; E240F598202DED740000067D /* KMPackage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMPackage.m; sourceTree = ""; }; E2585A7E20DD6C3C00CBB994 /* KMMethodEventHandlerTests.kmx */ = {isa = PBXFileReference; lastKnownFileType = file; path = KMMethodEventHandlerTests.kmx; sourceTree = ""; }; @@ -639,7 +639,7 @@ 98BF92421BF01CBE0002126A /* KMAboutWindowController.m */, 293EA3E827140D8100545EED /* KMAboutWindowController.xib */, E213601D2142D7BF00A043B7 /* keyman-88.png */, - E23380FA21407AA100B90591 /* SILInBlue76.png */, + 29DD5F432CFEF88000683388 /* SILAndikaV1RGB.png */, 98BF92461BF01D890002126A /* image.jpg */, 98BF92531BF040A50002126A /* title.png */, ); @@ -846,7 +846,6 @@ 989C9C131A7876DE00A20425 /* Images.xcassets in Resources */, 98E2CEA11A92C39C00AE2455 /* InfoPlist.strings in Resources */, E21E645820C04EA8000D6274 /* logo.png in Resources */, - E23380FB21407AA100B90591 /* SILInBlue76.png in Resources */, 9874C3211B536847000BB543 /* info.png in Resources */, 98E672A01B532F5E00DBDE2F /* KMDownloadKBWindowController.xib in Resources */, 37AE5C9D239A7B770086CC7C /* qrcode.min.js in Resources */, @@ -862,6 +861,7 @@ 293EA3E627140D8100545EED /* KMAboutWindowController.xib in Resources */, 37A245C12565DFA6000BBF92 /* Assets.xcassets in Resources */, 293EA3EB27140DEC00545EED /* preferences.xib in Resources */, + 29DD5F442CFEF88000683388 /* SILAndikaV1RGB.png in Resources */, 9800EC5A1C02940300BF0FB5 /* keyman-for-mac-os-license.html in Resources */, 989C9C161A7876DE00A20425 /* MainMenu.xib in Resources */, 29B42A602728343B00EDD5D3 /* KMKeyboardHelpWindowController.xib in Resources */, diff --git a/mac/Keyman4MacIM/Keyman4MacIM/Images/SILInBlue76.png b/mac/Keyman4MacIM/Keyman4MacIM/Images/SILInBlue76.png deleted file mode 100644 index 0275859531c4ed974058085193c614d8e7fc2fbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2377 zcmaJ@c~leU7EcshD2i54t7xY|txL9=nxb$9YWiAdyJp6hYE3V&xEz|Cmw4`|i^V#G6FM!UDylLw4Uz;^3wgDMchwW!@KW zVRzy@)*KX#kw{XgLGNB}|P zqmPKfqm)YpD%41aT|RW1(M+I8q=3aXGptHLa3CJh>PBs_Ilgeo;b#aB}nF9HH=0)oJZ;4ok_8cdkLCZr7V3W&YS%%A{+E_i~F@9prGhT!;!lOpwiBf((#F1Nj0rTfk%jLmvte&7#%_!ld${ zSj0(4(c!pRz+faNC)1Nzbkw3{fP6lm!GstPL?b+CSc(aUZ8Q_+H_RYKFqK7b#`UNP za52L1s1+Ad2&ErVFq)OhkHjWys8B@77&h3<0O?GI(df!+&>O?UkiTSn)*FjRF(ZsH z1VgPB6>%RLzhN*@yT1=K?5I|K^fOy6hQU15Dh6UejED)x2vOjmB0{-fxkXQ0AZ(DL zDy!>Tg;YqfTJ>rHM+z}{e2C3wiRDr;2=YO;h{xeGr7V!c_Apjq!f@E6LR>*zSp8pE{@-E+5(@(3s3ihL z4a2RXFc_>DZgH6biDa-jfVoOor8nUyrkm%w&mqN#MW2MI8Edtss|6gJFnC)2QN!qN+G-789m02#IQy0N3LNWT<~d27G8|(7Sp_V1P|pqF6LF zqJgajoU$lfu@qQ}YEd9U35X3=WH9jnhO1Q#dICGR`#{6^gnZEaNpBB(5(DGfew46yH2`vtM3Y&D$vG zJ}_+pylG61QzS)hf}%v#_&!8MneKTad(3#>(|0Ppl3uz;TI27psh#qB!oDnZoQtpF? z#)v+@_{ag0XFs{MB<^)U(LLI|8Z?)cdnziUc6L3va<)iTe_J1OZ5EQ zolfbCK>ySicd}BPy6EgxlSkGRqZ=w$M&DTPW#9Qlq9(S;HlL+MeGXMMulFoiwK#?U zyj}hxqAsL`eb|52?d_x&U8V3I zH_s<@vTRZ{HF$;dO82h^Xg(GDjndVs(e~A7T@J(kU}Nj*^+=&DWJaN^Y1Rp>{)dvD zm`Pg;WFw%C%XYC-xZNFWDAQdSxG(Ek?zTYV6Z}nF&MJM@(YZYrnnL<&uTGHl8hr0{ zx;5(#I^6kNR!l|~I&RdS36(tG=U(7gbatkD%H9o^IgCb|JxDPmLC{5B0?VREs-2~y~i{UADHrtN|nERe_ZdVbV|X(bvK))sheXqj4p&^ z?d6GgD&D^`_DmH7XjRP3|wy$7{qW%q*He6pq;-*9*0v1`3wjmWIp5xqT${1+aK%A1@6=1>4((6jp%r>vpjG+(y{PR$|EajrcmKzeT~iu% zU;HyO_+L`*Jrr7NG8sL)&a=K;xH1z6DeV4d-9Xht?r$wv@hJSrL^A)u#U~wYo*j{b zH)ku*v{``{sQ)c&@_M*W%k!4s8XsGl=U{8`GX?nSqx-C;5>5cg1o$0Svq(8;&OH%Y8HSLGxr;qIp z-C8pC)XL6|lJz|yk+EI@?^-5qouk0A4;kh(%$`?zEB#e_W^rD3#yZ~U=iXV7m)a4| SvCFQ%Dhk;$=^^o&jQ<0Ev%~lR diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/Base.lproj/KMAboutWindowController.xib b/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/Base.lproj/KMAboutWindowController.xib index b6c018d1a56..fa15b43024f 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/Base.lproj/KMAboutWindowController.xib +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMAboutWindow/Base.lproj/KMAboutWindowController.xib @@ -1,8 +1,8 @@ - + - + @@ -20,7 +20,7 @@ - + @@ -42,8 +42,12 @@ - - + + + + + + @@ -100,29 +104,29 @@ -